Skip to content

Commit 9780d31

Browse files
committed
Add mac native support, tests, clean up
1 parent 04dbd79 commit 9780d31

16 files changed

Lines changed: 509 additions & 246 deletions

.github/workflows/build.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ on:
88
branches: [ master ]
99

1010
jobs:
11-
build:
11+
build-ios:
1212
runs-on: macos-latest
1313
steps:
1414
- uses: actions/checkout@master
1515
- name: Start xcodebuild build
1616
run: xcodebuild -configuration Debug clean build test -project Example/Example.xcodeproj -scheme Example -destination "platform=iOS Simulator,name=iPhone 16"
17+
build-macos:
18+
runs-on: macos-latest
19+
steps:
20+
- uses: actions/checkout@master
21+
- name: Start xcodebuild macOS build
22+
run: xcodebuild -configuration Debug clean build -project Example/Example.xcodeproj -scheme Example -destination "platform=macOS"
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "2630"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES"
8+
buildArchitectures = "Automatic">
9+
</BuildAction>
10+
<TestAction
11+
buildConfiguration = "Debug"
12+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
13+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
14+
shouldUseLaunchSchemeArgsEnv = "YES"
15+
shouldAutocreateTestPlan = "YES">
16+
<Testables>
17+
<TestableReference
18+
skipped = "NO">
19+
<BuildableReference
20+
BuildableIdentifier = "primary"
21+
BlueprintIdentifier = "PianoKeyboardTests"
22+
BuildableName = "PianoKeyboardTests"
23+
BlueprintName = "PianoKeyboardTests"
24+
ReferencedContainer = "container:">
25+
</BuildableReference>
26+
</TestableReference>
27+
</Testables>
28+
</TestAction>
29+
<LaunchAction
30+
buildConfiguration = "Debug"
31+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
32+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
33+
launchStyle = "0"
34+
useCustomWorkingDirectory = "NO"
35+
ignoresPersistentStateOnLaunch = "NO"
36+
debugDocumentVersioning = "YES"
37+
debugServiceExtension = "internal"
38+
allowLocationSimulation = "YES">
39+
</LaunchAction>
40+
<ProfileAction
41+
buildConfiguration = "Release"
42+
shouldUseLaunchSchemeArgsEnv = "YES"
43+
savedToolIdentifier = ""
44+
useCustomWorkingDirectory = "NO"
45+
debugDocumentVersioning = "YES">
46+
</ProfileAction>
47+
<AnalyzeAction
48+
buildConfiguration = "Debug">
49+
</AnalyzeAction>
50+
<ArchiveAction
51+
buildConfiguration = "Release"
52+
revealArchiveInOrganizer = "YES">
53+
</ArchiveAction>
54+
</Scheme>

Example/Example.xcodeproj/project.pbxproj

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,14 +306,18 @@
306306
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
307307
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
308308
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
309-
IPHONEOS_DEPLOYMENT_TARGET = 15;
309+
IPHONEOS_DEPLOYMENT_TARGET = 17;
310310
LD_RUNPATH_SEARCH_PATHS = (
311311
"$(inherited)",
312312
"@executable_path/Frameworks",
313313
);
314-
MARKETING_VERSION = 1.0;
314+
MACOSX_DEPLOYMENT_TARGET = 14.0;
315+
MARKETING_VERSION = 1.1;
315316
PRODUCT_BUNDLE_IDENTIFIER = com.mellowmuse.Example;
316317
PRODUCT_NAME = "$(TARGET_NAME)";
318+
SDKROOT = auto;
319+
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
320+
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
317321
SWIFT_EMIT_LOC_STRINGS = YES;
318322
SWIFT_VERSION = 5.0;
319323
TARGETED_DEVICE_FAMILY = "1,2";
@@ -336,14 +340,18 @@
336340
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
337341
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
338342
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
339-
IPHONEOS_DEPLOYMENT_TARGET = 15;
343+
IPHONEOS_DEPLOYMENT_TARGET = 17;
340344
LD_RUNPATH_SEARCH_PATHS = (
341345
"$(inherited)",
342346
"@executable_path/Frameworks",
343347
);
344-
MARKETING_VERSION = 1.0;
348+
MACOSX_DEPLOYMENT_TARGET = 14.0;
349+
MARKETING_VERSION = 1.1;
345350
PRODUCT_BUNDLE_IDENTIFIER = com.mellowmuse.Example;
346351
PRODUCT_NAME = "$(TARGET_NAME)";
352+
SDKROOT = auto;
353+
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
354+
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
347355
SWIFT_EMIT_LOC_STRINGS = YES;
348356
SWIFT_VERSION = 5.0;
349357
TARGETED_DEVICE_FAMILY = "1,2";

Example/Example/AudioEngine.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,15 @@ class AudioEngine {
3333
delay.feedback = 75.0
3434
delay.lowPassCutoff = 16000.0
3535

36+
#if os(iOS)
3637
let audioSession = AVAudioSession.sharedInstance()
37-
38-
do {
39-
try AVAudioSession.sharedInstance().setCategory(.playback)
40-
} catch {
41-
print("Error: AudioSession couldn't set category")
42-
}
43-
4438
do {
39+
try audioSession.setCategory(.playback)
4540
try audioSession.setActive(true)
4641
} catch {
47-
print("Error: AudioSession couldn't set category active")
42+
print("Error: AudioSession configuration failed")
4843
}
44+
#endif
4945

5046
if engine.isRunning {
5147
print("Audio engine already running")

Example/Example/ContentView.swift

Lines changed: 52 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import SwiftUI
99
import PianoKeyboard
1010

1111
struct ContentView: View {
12-
@ObservedObject private var viewModel: PianoKeyboardViewModel
13-
@State var styleIndex: Int
12+
13+
@Bindable private var viewModel: PianoKeyboardViewModel
14+
@State private var styleIndex: Int
1415

1516
private let audioEngine: AudioEngine
1617

@@ -25,91 +26,73 @@ struct ContentView: View {
2526
}
2627

2728
var body: some View {
28-
VStack(spacing: 0) {
29-
ZStack(alignment: .bottom) {
30-
Rectangle()
31-
.fill(
32-
LinearGradient(gradient: Gradient(stops: [
33-
Gradient.Stop(color: Color(white: 0.2), location: 0),
34-
Gradient.Stop(color: Color(white: 0.3), location: 0.96),
35-
Gradient.Stop(color: .black, location: 1),
36-
]), startPoint: .top, endPoint: .bottom)
37-
)
38-
.shadow(radius: 8)
29+
GeometryReader { geometry in
30+
VStack(spacing: 0) {
31+
ZStack(alignment: .bottom) {
32+
Rectangle()
33+
.fill(
34+
LinearGradient(gradient: Gradient(stops: [
35+
Gradient.Stop(color: Color(white: 0.2), location: 0),
36+
Gradient.Stop(color: Color(white: 0.3), location: 0.96),
37+
Gradient.Stop(color: .black, location: 1),
38+
]), startPoint: .top, endPoint: .bottom)
39+
)
40+
.shadow(radius: 8)
3941

40-
HStack(alignment: .top) {
41-
VStack(alignment: .leading, spacing: 10) {
42-
Stepper("Keys: \(viewModel.numberOfKeys)") {
43-
viewModel.numberOfKeys += 1
44-
} onDecrement: {
45-
viewModel.numberOfKeys -= 1
46-
}
47-
.font(.subheadline.bold())
48-
.foregroundColor(.white)
42+
VStack(alignment: .trailing) {
43+
HStack(spacing: 30) {
44+
Stepper("Keys") {
45+
viewModel.numberOfKeys += 1
46+
} onDecrement: {
47+
viewModel.numberOfKeys -= 1
48+
}
4949

50-
Stepper("Style:", value: $styleIndex, in: 0...2)
51-
.font(.subheadline.bold())
52-
.foregroundColor(.white)
53-
.tint(.blue)
50+
Stepper("Style", value: $styleIndex, in: 0...2)
5451

55-
}
56-
.padding(EdgeInsets(top: 0, leading: 20, bottom: 30, trailing: 0))
57-
.frame(width: 200)
52+
Toggle("Latch", isOn: $viewModel.latch)
53+
.toggleStyle(.switch)
54+
.tint(.blue)
5855

56+
HStack {
57+
Text("Notes:")
58+
Text("\(viewModel.keysPressed.joined(separator: ", "))")
59+
}
5960

60-
VStack {
61-
Toggle("Latch:", isOn: $viewModel.latch)
62-
.font(.subheadline.bold())
63-
.foregroundColor(.white)
64-
65-
HStack {
66-
Text("Notes:")
67-
.font(.subheadline.bold())
68-
.foregroundStyle(.white)
6961
Spacer()
70-
Text("\(viewModel.keysPressed.joined(separator: ", "))")
71-
.font(.subheadline.bold())
72-
.foregroundStyle(.white)
7362
}
74-
}
75-
.padding(EdgeInsets(top: 0, leading: 20, bottom: 30, trailing: 0))
76-
.frame(width: 200)
63+
.foregroundStyle(.white)
64+
.padding(20)
7765

78-
Spacer()
66+
Spacer()
7967

80-
Text("PianoKeyboard")
81-
.font(.title2.bold())
82-
.foregroundColor(.white)
83-
.padding(30)
68+
Text("PianoKeyboard")
69+
.font(.title.bold())
70+
.foregroundColor(.white)
71+
.padding(20)
72+
}
8473
}
85-
}
86-
.frame(height: UIScreen.main.bounds.height * 0.45)
74+
.frame(height: geometry.size.height * 0.45)
8775

88-
if styleIndex == 0 {
89-
PianoKeyboardView(viewModel: viewModel, style: ClassicStyle(sfKeyWidthMultiplier: 0.55))
90-
.frame(height: UIScreen.main.bounds.height * 0.55)
91-
} else if styleIndex == 1 {
92-
PianoKeyboardView(viewModel: viewModel, style: ModernStyle())
93-
.frame(height: UIScreen.main.bounds.height * 0.55)
94-
} else if styleIndex == 2{
95-
PianoKeyboardView(viewModel: viewModel, style: CustomStyle(showLabels: true))
96-
.frame(height: UIScreen.main.bounds.height * 0.55)
76+
if styleIndex == 0 {
77+
PianoKeyboardView(viewModel: viewModel, style: ClassicStyle(sfKeyWidthMultiplier: 0.55))
78+
.frame(height: geometry.size.height * 0.55)
79+
} else if styleIndex == 1 {
80+
PianoKeyboardView(viewModel: viewModel, style: ModernStyle())
81+
.frame(height: geometry.size.height * 0.55)
82+
} else if styleIndex == 2{
83+
PianoKeyboardView(viewModel: viewModel, style: CustomStyle(showLabels: true))
84+
.frame(height: geometry.size.height * 0.55)
85+
}
9786
}
87+
.background(.black)
9888
}
99-
.background(.black)
10089
.onAppear() {
10190
viewModel.delegate = audioEngine
10291
audioEngine.start()
10392
}
10493
}
10594
}
10695

107-
struct ContentView_Previews: PreviewProvider {
108-
static var previews: some View {
109-
NavigationView {
110-
ContentView()
111-
}
112-
.navigationViewStyle(.stack)
113-
.previewInterfaceOrientation(.landscapeLeft)
114-
}
96+
#Preview(traits: .landscapeRight) {
97+
ContentView()
11598
}

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import PackageDescription
44
let package = Package(
55
name: "PianoKeyboard",
66
platforms: [
7-
.iOS("15.0")
7+
.iOS("17.0"),
8+
.macOS("14.0")
89
],
910
products: [
1011
.library(

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ PianoKeyboard
33

44
[![Build and test](https://github.com/garynewby/PianoKeyboard/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/garynewby/PianoKeyboard/actions/workflows/build.yml)
55

6-
A SwiftUI piano keyboard view for iPhone and iPad.
6+
A SwiftUI piano keyboard view for iPhone, iPad, and macOS.
77

88
Easily customisable styles, configurable
99
- Number of keys
@@ -43,6 +43,13 @@ func pianoKeyUp(_ keyNumber: Int) {
4343
### Requirements
4444

4545
- Swift 5, SwiftUI
46+
- iOS 15.0+
47+
- macOS 12.0+
48+
49+
### Platform notes
50+
51+
- iOS and iPadOS use native multi-touch input.
52+
- macOS uses native AppKit pointer/trackpad input.
4653

4754
## Author
4855

Source/PianoKeyViewModel.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Foundation
1010
public struct PianoKeyViewModel {
1111
let keyIndex: Int
1212
let noteOffset: Int
13+
1314
public var touchDown = false
1415
public var latched = false
1516

Source/PianoKeyboardView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import SwiftUI
99

1010
public struct PianoKeyboardView<T: KeyboardStyle>: View {
11-
@ObservedObject private var viewModel: PianoKeyboardViewModel
11+
private var viewModel: PianoKeyboardViewModel
1212
var style: T
1313

1414
public init(

0 commit comments

Comments
 (0)