- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 26
 
Open
Labels
bugSomething isn't workingSomething isn't working
Description
macOS Version(s) Used to Build
macOS 13 Ventura
Xcode Version(s)
Xcode 14
Description
The key handles tap with delay if the Кeyboard is in a ScrollView. See the modified KeyboardDemo example below.
The bug only occurs on iOS.
On macOS, the Keyboard in a ScrollView works correctly.
Crash Logs, Screenshots or Other Attachments (if applicable)
Modified ContentView.swift from KeyboardDemo to demonstrate the bug:
import Keyboard
import SwiftUI
import Tonic
let evenSpacingInitialSpacerRatio: [Letter: CGFloat] = [
    .C: 0.0,
    .D: 2.0 / 12.0,
    .E: 4.0 / 12.0,
    .F: 0.0 / 12.0,
    .G: 1.0 / 12.0,
    .A: 3.0 / 12.0,
    .B: 5.0 / 12.0
]
let evenSpacingSpacerRatio: [Letter: CGFloat] = [
    .C: 7.0 / 12.0,
    .D: 7.0 / 12.0,
    .E: 7.0 / 12.0,
    .F: 7.0 / 12.0,
    .G: 7.0 / 12.0,
    .A: 7.0 / 12.0,
    .B: 7.0 / 12.0
]
let evenSpacingRelativeBlackKeyWidth: CGFloat = 7.0 / 12.0
struct PitchRange: Identifiable {
    let range: ClosedRange<Int>
    let id: Int
    
    var lowerBound: Int { range.lowerBound }
    var upperBound: Int { range.upperBound }
}
struct ContentView: View {
    @State private var currentKeyboardId = 2
    internal let ranges = [36...47, 48...59, 60...71, 72...83, 84...95, 96...107].enumerated().map {
        PitchRange(range: $0.element, id: $0.offset)
    }
    func noteOn(pitch: Pitch, point: CGPoint) {
        print("note on \(pitch)”)
    }
    func noteOff(pitch: Pitch) {
        print("note off \(pitch)”)
    }
    func noteOnWithVerticalVelocity(pitch: Pitch, point: CGPoint) {
        print("note on \(pitch), midiVelocity: \(Int(point.y * 127))”)
    }
    func noteOnWithReversedVerticalVelocity(pitch: Pitch, point: CGPoint) {
        print("note on \(pitch), midiVelocity: \(Int((1.0 - point.y) * 127))”)
    }
    var randomColors: [Color] = (0 ... 12).map { _ in
        Color(red: Double.random(in: 0 ... 1),
              green: Double.random(in: 0 ... 1),
              blue: Double.random(in: 0 ... 1), opacity: 1)
    }
    @State var lowNote = 24
    @State var highNote = 48
    @State var scaleIndex = Scale.allCases.firstIndex(of: .chromatic) ?? 0 {
        didSet {
            if scaleIndex >= Scale.allCases.count { scaleIndex = 0 }
            if scaleIndex < 0 { scaleIndex = Scale.allCases.count - 1 }
            scale = Scale.allCases[scaleIndex]
        }
    }
    @State var scale: Scale = .chromatic
    @State var root: NoteClass = .C
    @State var rootIndex = 0
    @Environment(\.colorScheme) var colorScheme
    var body: some View {
        HStack {
            Keyboard(layout: .verticalIsomorphic(pitchRange: Pitch(48) ... Pitch(77))).frame(width: 100)
            VStack {
                HStack {
                    Stepper("Lowest Note: \(Pitch(intValue: lowNote).note(in: .C).description)”,
                            onIncrement: {
                                if lowNote < 126, highNote > lowNote + 12 {
                                    lowNote += 1
                                }
                            },
                            onDecrement: {
                                if lowNote > 0 {
                                    lowNote -= 1
                                }
                            })
                    Stepper("Highest Note: \(Pitch(intValue: highNote).note(in: .C).description)”,
                            onIncrement: {
                                if highNote < 126 {
                                    highNote += 1
                                }
                            },
                            onDecrement: {
                                if highNote > 1, highNote > lowNote + 12 {
                                    highNote -= 1
                                }
                            })
                }
                
/// BUG DEMO BEGIN
                GeometryReader { geoProxy in
                    ScrollViewReader { scrollProxy in
                        ScrollView(.horizontal, showsIndicators: false) {
                            HStack(spacing: 0) {
                                ForEach(ranges) { range in
                                    Keyboard(layout: .piano(pitchRange: Pitch(intValue: range.lowerBound) ... Pitch(intValue: range.upperBound)),
                                             noteOn: noteOnWithVerticalVelocity(pitch:point:), noteOff: noteOff)
                                    .frame(minWidth: geoProxy.size.width * 0.5)
                                    .id(range.id)
                                }
                            }
                            .frame(height: 200)
                            .frame(maxWidth: .infinity)
                        }
                        .background(.black)
                        .onChange(of: currentKeyboardId) { newValue in
                            withAnimation {
                                scrollProxy.scrollTo(newValue)
                            }
                        }
                        .onAppear {
                            scrollProxy.scrollTo(currentKeyboardId)
                        }
                    }
                }
/// BUG DEMO END
                HStack {
                    Stepper("Root: \(root.description)”,
                            onIncrement: {
                        let allSharpNotes = (0...11).map { Note(pitch: Pitch(intValue: $0)).noteClass }
                        var index = allSharpNotes.firstIndex(of: root.canonicalNote.noteClass) ?? 0
                        index += 1
                        if index > 11 { index = 0}
                        if index < 0 { index = 1}
                        rootIndex = index
                        root = allSharpNotes[index]
                    },
                            onDecrement: {
                        let allSharpNotes = (0...11).map { Note(pitch: Pitch(intValue: $0)).noteClass }
                        var index = allSharpNotes.firstIndex(of: root.canonicalNote.noteClass) ?? 0
                        index -= 1
                        if index > 11 { index = 0}
                        if index < 0 { index = 1}
                        rootIndex = index
                        root = allSharpNotes[index]
                    })
                    Stepper("Scale: \(scale.description)”,
                            onIncrement: { scaleIndex += 1 },
                            onDecrement: { scaleIndex -= 1 })
                }
                Keyboard(layout: .isomorphic(pitchRange:
                                                Pitch(intValue: 12 + rootIndex) ... Pitch(intValue: 84 + rootIndex),
                                             root: root,
                                             scale: scale),
                         noteOn: noteOnWithReversedVerticalVelocity(pitch:point:), noteOff: noteOff)
                .frame(minWidth: 100, minHeight: 100)
                Keyboard(layout: .guitar(),
                         noteOn: noteOn, noteOff: noteOff) { pitch, isActivated in
                    KeyboardKey(pitch: pitch,
                                isActivated: isActivated,
                                text: pitch.note(in: .F).description,
                                pressedColor: Color(PitchColor.newtonian[Int(pitch.pitchClass)]),
                                alignment: .center)
                }
                .frame(minWidth: 100, minHeight: 100)
                Keyboard(layout: .isomorphic(pitchRange: Pitch(48) ... Pitch(65))) { pitch, isActivated in
                    KeyboardKey(pitch: pitch,
                                isActivated: isActivated,
                                text: pitch.note(in: .F).description,
                                pressedColor: Color(PitchColor.newtonian[Int(pitch.pitchClass)]))
                }
                .frame(minWidth: 100, minHeight: 100)
                Keyboard(latching: true, noteOn: noteOn, noteOff: noteOff) { pitch, isActivated in
                    if isActivated {
                        ZStack {
                            Rectangle().foregroundColor(.black)
                            VStack {
                                Spacer()
                                Text(pitch.note(in: .C).description).font(.largeTitle)
                            }.padding()
                        }
                    } else {
                        Rectangle().foregroundColor(randomColors[Int(pitch.intValue) % 12])
                    }
                }
                .frame(minWidth: 100, minHeight: 100)
            }
            Keyboard(
                layout: .verticalPiano(pitchRange: Pitch(48) ... Pitch(77),
                                       initialSpacerRatio: evenSpacingInitialSpacerRatio,
                                       spacerRatio: evenSpacingSpacerRatio,
                                       relativeBlackKeyWidth: evenSpacingRelativeBlackKeyWidth)
            ).frame(width: 100)
        }
        .background(colorScheme == .dark ?
                    Color.clear : Color(red: 0.9, green: 0.9, blue: 0.9))
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
