Support haptic feedback on new Magic Keyboard

This commit is contained in:
Shadowfacts 2024-07-06 17:10:29 -07:00
parent 3cb0f46533
commit 5abd265195
5 changed files with 87 additions and 21 deletions

View File

@ -204,14 +204,19 @@ class FastAccountSwitcherViewController: UIViewController {
@objc private func handleLongPress(_ recognizer: UIGestureRecognizer) { @objc private func handleLongPress(_ recognizer: UIGestureRecognizer) {
switch recognizer.state { switch recognizer.state {
case .began: case .began:
show()
#if !os(visionOS) #if !os(visionOS)
UIImpactFeedbackGenerator(style: .heavy).impactOccurred() if #available(iOS 17.5, *) {
selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator() UIImpactFeedbackGenerator(style: .heavy, view: view).impactOccurred(at: CGPoint(x: view.bounds.midX, y: view.bounds.midY))
selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator(view: view)
} else {
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator()
}
selectionChangedFeedbackGenerator?.prepare() selectionChangedFeedbackGenerator?.prepare()
#endif #endif
show()
case .changed: case .changed:
let location = recognizer.location(in: view) let location = recognizer.location(in: view)
@ -260,7 +265,11 @@ class FastAccountSwitcherViewController: UIViewController {
#if !os(visionOS) #if !os(visionOS)
if hapticFeedback { if hapticFeedback {
selectionChangedFeedbackGenerator?.selectionChanged() if #available(iOS 17.5, *) {
selectionChangedFeedbackGenerator?.selectionChanged(at: location)
} else {
selectionChangedFeedbackGenerator?.selectionChanged()
}
selectionChangedFeedbackGenerator?.prepare() selectionChangedFeedbackGenerator?.prepare()
} }
#endif #endif

View File

@ -282,7 +282,11 @@ private class VideoScrubbingControl: UIControl {
sendActions(for: .editingDidBegin) sendActions(for: .editingDidBegin)
#if !os(visionOS) #if !os(visionOS)
feedbackGenerator = UIImpactFeedbackGenerator(style: .light) if #available(iOS 17.5, *) {
feedbackGenerator = UIImpactFeedbackGenerator(style: .light, view: self)
} else {
feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
}
feedbackGenerator!.prepare() feedbackGenerator!.prepare()
#endif #endif
@ -301,7 +305,11 @@ private class VideoScrubbingControl: UIControl {
let newFractionComplete = max(0, min(1, unclampedFractionComplete)) let newFractionComplete = max(0, min(1, unclampedFractionComplete))
#if !os(visionOS) #if !os(visionOS)
if newFractionComplete != fractionComplete && (newFractionComplete == 0 || newFractionComplete == 1) { if newFractionComplete != fractionComplete && (newFractionComplete == 0 || newFractionComplete == 1) {
feedbackGenerator!.impactOccurred(intensity: 0.5) if #available(iOS 17.5, *) {
feedbackGenerator!.impactOccurred(intensity: 0.5, at: location)
} else {
feedbackGenerator!.impactOccurred(intensity: 0.5)
}
} }
#endif #endif
fractionComplete = newFractionComplete fractionComplete = newFractionComplete

View File

@ -150,7 +150,13 @@ class CustomAlertActionsView: UIControl {
private var separatorSizeConstraints: [NSLayoutConstraint] = [] private var separatorSizeConstraints: [NSLayoutConstraint] = []
#if !os(visionOS) #if !os(visionOS)
private let generator = UISelectionFeedbackGenerator() private lazy var generator: UISelectionFeedbackGenerator = {
if #available(iOS 17.5, *) {
UISelectionFeedbackGenerator(view: self)
} else {
UISelectionFeedbackGenerator()
}
}()
#endif #endif
private var currentSelectedActionIndex: Int? private var currentSelectedActionIndex: Int?
@ -313,7 +319,13 @@ class CustomAlertActionsView: UIControl {
actionButtons[currentSelectedActionIndex].backgroundColor = nil actionButtons[currentSelectedActionIndex].backgroundColor = nil
} }
#if !os(visionOS) #if !os(visionOS)
generator.selectionChanged() if #available(iOS 17.5, *) {
let view = selectedButton!.element
let location = convert(CGPoint(x: view.bounds.midX, y: view.bounds.midY), from: view)
generator.selectionChanged(at: location)
} else {
generator.selectionChanged()
}
#endif #endif
} }

View File

@ -30,7 +30,13 @@ class PollOptionsView: UIControl {
static let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95) static let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
#if !os(visionOS) #if !os(visionOS)
private let generator = UISelectionFeedbackGenerator() private lazy var generator: UISelectionFeedbackGenerator = {
if #available(iOS 17.5, *) {
UISelectionFeedbackGenerator(view: self)
} else {
UISelectionFeedbackGenerator()
}
}()
#endif #endif
override var isEnabled: Bool { override var isEnabled: Bool {
@ -159,7 +165,11 @@ class PollOptionsView: UIControl {
animator.startAnimation() animator.startAnimation()
#if !os(visionOS) #if !os(visionOS)
generator.selectionChanged() if #available(iOS 17.5, *) {
generator.selectionChanged(at: view.center)
} else {
generator.selectionChanged()
}
generator.prepare() generator.prepare()
#endif #endif
@ -172,8 +182,10 @@ class PollOptionsView: UIControl {
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let location = touch.location(in: self) let location = touch.location(in: self)
let newIndex = optionView(at: location)?.1 let newIndexAndOption = optionView(at: location)
let newIndex = newIndexAndOption?.1
let option = newIndexAndOption?.0
if newIndex != currentSelectedOptionIndex { if newIndex != currentSelectedOptionIndex {
currentSelectedOptionIndex = newIndex currentSelectedOptionIndex = newIndex
@ -189,8 +201,12 @@ class PollOptionsView: UIControl {
animator.startAnimation() animator.startAnimation()
#if !os(visionOS) #if !os(visionOS)
if newIndex != nil { if let option {
generator.selectionChanged() if #available(iOS 17.5, *) {
generator.selectionChanged(at: option.center)
} else {
generator.selectionChanged()
}
generator.prepare() generator.prepare()
} }
#endif #endif

View File

@ -24,7 +24,13 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
private var changeSelectionPanRecognizer: UIGestureRecognizer! private var changeSelectionPanRecognizer: UIGestureRecognizer!
private var selectedOptionAtStartOfPan: Value? private var selectedOptionAtStartOfPan: Value?
#if !os(visionOS) #if !os(visionOS)
private lazy var selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator() private lazy var selectionChangedFeedbackGenerator: UISelectionFeedbackGenerator = {
if #available(iOS 17.5, *) {
UISelectionFeedbackGenerator(view: self)
} else {
UISelectionFeedbackGenerator()
}
}()
#endif #endif
override var intrinsicContentSize: CGSize { override var intrinsicContentSize: CGSize {
@ -111,13 +117,19 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
func setSelectedOption(_ value: Value, animated: Bool) { func setSelectedOption(_ value: Value, animated: Bool) {
guard selectedOption != value, guard selectedOption != value,
options.contains(where: { $0.value == value }) else { let index = options.firstIndex(where: { $0.value == value }) else {
return return
} }
#if !os(visionOS) #if !os(visionOS)
if selectedOption != nil { if selectedOption != nil {
selectionChangedFeedbackGenerator.selectionChanged() if #available(iOS 17.5, *) {
let optionView = optionsStack.arrangedSubviews[index]
let location = convert(CGPoint(x: optionView.bounds.midX, y: optionView.bounds.midY), from: optionView)
selectionChangedFeedbackGenerator.selectionChanged(at: location)
} else {
selectionChangedFeedbackGenerator.selectionChanged()
}
} }
#endif #endif
@ -158,15 +170,19 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
// MARK: Interaction // MARK: Interaction
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard gestureRecognizer === self.panGestureRecognizer else {
return true
}
let beganOnSelectedOption: Bool let beganOnSelectedOption: Bool
if let selectedIndex = options.firstIndex(where: { $0.value == selectedOption }), if let selectedIndex = options.firstIndex(where: { $0.value == selectedOption }),
optionsStack.arrangedSubviews[selectedIndex].frame.contains(self.panGestureRecognizer.location(in: optionsStack)) { optionsStack.arrangedSubviews[selectedIndex].frame.contains(gestureRecognizer.location(in: optionsStack)) {
beganOnSelectedOption = true beganOnSelectedOption = true
} else { } else {
beganOnSelectedOption = false beganOnSelectedOption = false
} }
// only begin changing selection if the gesutre started on the currently selected item // only begin changing selection if the gesture started on the currently selected item
// otherwise, let the scroll view handle things // otherwise, let the scroll view handle things
if gestureRecognizer == self.changeSelectionPanRecognizer { if gestureRecognizer == self.changeSelectionPanRecognizer {
return beganOnSelectedOption return beganOnSelectedOption
@ -223,7 +239,12 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
} }
animator.startAnimation() animator.startAnimation()
#if !os(visionOS) #if !os(visionOS)
selectionChangedFeedbackGenerator.selectionChanged() if #available(iOS 17.5, *) {
let locationInSelf = convert(location, from: optionsStack)
selectionChangedFeedbackGenerator.selectionChanged(at: locationInSelf)
} else {
selectionChangedFeedbackGenerator.selectionChanged()
}
#endif #endif
return true return true
} else { } else {