Skip to content

The Key handles tap with delay if the Keyboard is in a ScrollView (on iOS) #33

@tanukineiri

Description

@tanukineiri

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)

Screenshot from KeyboardDemo

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

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions