From 5abd26519525f044ce85ad528161d785dc8e5f72 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sat, 6 Jul 2024 17:10:29 -0700 Subject: [PATCH] Support haptic feedback on new Magic Keyboard --- .../FastAccountSwitcherViewController.swift | 19 ++++++++--- .../Gallery/VideoControlsViewController.swift | 12 +++++-- .../Utilities/CustomAlertController.swift | 16 +++++++-- Tusker/Views/Poll/PollOptionsView.swift | 28 ++++++++++++---- Tusker/Views/ScrollingSegmentedControl.swift | 33 +++++++++++++++---- 5 files changed, 87 insertions(+), 21 deletions(-) diff --git a/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift b/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift index 07fa866d..692cf23a 100644 --- a/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift +++ b/Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift @@ -204,14 +204,19 @@ class FastAccountSwitcherViewController: UIViewController { @objc private func handleLongPress(_ recognizer: UIGestureRecognizer) { switch recognizer.state { case .began: + show() + #if !os(visionOS) - UIImpactFeedbackGenerator(style: .heavy).impactOccurred() - selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator() + if #available(iOS 17.5, *) { + 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() #endif - show() - case .changed: let location = recognizer.location(in: view) @@ -260,7 +265,11 @@ class FastAccountSwitcherViewController: UIViewController { #if !os(visionOS) if hapticFeedback { - selectionChangedFeedbackGenerator?.selectionChanged() + if #available(iOS 17.5, *) { + selectionChangedFeedbackGenerator?.selectionChanged(at: location) + } else { + selectionChangedFeedbackGenerator?.selectionChanged() + } selectionChangedFeedbackGenerator?.prepare() } #endif diff --git a/Tusker/Screens/Gallery/VideoControlsViewController.swift b/Tusker/Screens/Gallery/VideoControlsViewController.swift index f99b52fd..94d163e3 100644 --- a/Tusker/Screens/Gallery/VideoControlsViewController.swift +++ b/Tusker/Screens/Gallery/VideoControlsViewController.swift @@ -282,7 +282,11 @@ private class VideoScrubbingControl: UIControl { sendActions(for: .editingDidBegin) #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() #endif @@ -301,7 +305,11 @@ private class VideoScrubbingControl: UIControl { let newFractionComplete = max(0, min(1, unclampedFractionComplete)) #if !os(visionOS) 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 fractionComplete = newFractionComplete diff --git a/Tusker/Screens/Utilities/CustomAlertController.swift b/Tusker/Screens/Utilities/CustomAlertController.swift index 81a67761..512e1a2a 100644 --- a/Tusker/Screens/Utilities/CustomAlertController.swift +++ b/Tusker/Screens/Utilities/CustomAlertController.swift @@ -150,7 +150,13 @@ class CustomAlertActionsView: UIControl { private var separatorSizeConstraints: [NSLayoutConstraint] = [] #if !os(visionOS) - private let generator = UISelectionFeedbackGenerator() + private lazy var generator: UISelectionFeedbackGenerator = { + if #available(iOS 17.5, *) { + UISelectionFeedbackGenerator(view: self) + } else { + UISelectionFeedbackGenerator() + } + }() #endif private var currentSelectedActionIndex: Int? @@ -313,7 +319,13 @@ class CustomAlertActionsView: UIControl { actionButtons[currentSelectedActionIndex].backgroundColor = nil } #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 } diff --git a/Tusker/Views/Poll/PollOptionsView.swift b/Tusker/Views/Poll/PollOptionsView.swift index 4abf7d62..fb052b69 100644 --- a/Tusker/Views/Poll/PollOptionsView.swift +++ b/Tusker/Views/Poll/PollOptionsView.swift @@ -30,7 +30,13 @@ class PollOptionsView: UIControl { static let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95) #if !os(visionOS) - private let generator = UISelectionFeedbackGenerator() + private lazy var generator: UISelectionFeedbackGenerator = { + if #available(iOS 17.5, *) { + UISelectionFeedbackGenerator(view: self) + } else { + UISelectionFeedbackGenerator() + } + }() #endif override var isEnabled: Bool { @@ -159,7 +165,11 @@ class PollOptionsView: UIControl { animator.startAnimation() #if !os(visionOS) - generator.selectionChanged() + if #available(iOS 17.5, *) { + generator.selectionChanged(at: view.center) + } else { + generator.selectionChanged() + } generator.prepare() #endif @@ -172,8 +182,10 @@ class PollOptionsView: UIControl { override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { 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 { currentSelectedOptionIndex = newIndex @@ -189,8 +201,12 @@ class PollOptionsView: UIControl { animator.startAnimation() #if !os(visionOS) - if newIndex != nil { - generator.selectionChanged() + if let option { + if #available(iOS 17.5, *) { + generator.selectionChanged(at: option.center) + } else { + generator.selectionChanged() + } generator.prepare() } #endif diff --git a/Tusker/Views/ScrollingSegmentedControl.swift b/Tusker/Views/ScrollingSegmentedControl.swift index 3e3c195b..e954b34e 100644 --- a/Tusker/Views/ScrollingSegmentedControl.swift +++ b/Tusker/Views/ScrollingSegmentedControl.swift @@ -24,7 +24,13 @@ class ScrollingSegmentedControl: UIScrollView, UIGestureRecogni private var changeSelectionPanRecognizer: UIGestureRecognizer! private var selectedOptionAtStartOfPan: Value? #if !os(visionOS) - private lazy var selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator() + private lazy var selectionChangedFeedbackGenerator: UISelectionFeedbackGenerator = { + if #available(iOS 17.5, *) { + UISelectionFeedbackGenerator(view: self) + } else { + UISelectionFeedbackGenerator() + } + }() #endif override var intrinsicContentSize: CGSize { @@ -111,13 +117,19 @@ class ScrollingSegmentedControl: UIScrollView, UIGestureRecogni func setSelectedOption(_ value: Value, animated: Bool) { guard selectedOption != value, - options.contains(where: { $0.value == value }) else { + let index = options.firstIndex(where: { $0.value == value }) else { return } #if !os(visionOS) 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 @@ -158,15 +170,19 @@ class ScrollingSegmentedControl: UIScrollView, UIGestureRecogni // MARK: Interaction override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + guard gestureRecognizer === self.panGestureRecognizer else { + return true + } + let beganOnSelectedOption: Bool 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 } else { 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 if gestureRecognizer == self.changeSelectionPanRecognizer { return beganOnSelectedOption @@ -223,7 +239,12 @@ class ScrollingSegmentedControl: UIScrollView, UIGestureRecogni } animator.startAnimation() #if !os(visionOS) - selectionChangedFeedbackGenerator.selectionChanged() + if #available(iOS 17.5, *) { + let locationInSelf = convert(location, from: optionsStack) + selectionChangedFeedbackGenerator.selectionChanged(at: locationInSelf) + } else { + selectionChangedFeedbackGenerator.selectionChanged() + } #endif return true } else {