Compare commits
No commits in common. "dcc5f7f71624617151f83bfdc0b7c510eac9f433" and "3eceffbb6b57de0ebedf2f399b3827e6f44b6295" have entirely different histories.
dcc5f7f716
...
3eceffbb6b
|
@ -152,21 +152,14 @@ extension GalleryViewController: GalleryItemViewControllerDelegate {
|
||||||
|
|
||||||
extension GalleryViewController: UIViewControllerTransitioningDelegate {
|
extension GalleryViewController: UIViewControllerTransitioningDelegate {
|
||||||
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||||
#if os(visionOS)
|
|
||||||
return nil
|
|
||||||
#else
|
|
||||||
if let sourceView = galleryDataSource.galleryContentTransitionSourceView(forItemAt: initialItemIndex) {
|
if let sourceView = galleryDataSource.galleryContentTransitionSourceView(forItemAt: initialItemIndex) {
|
||||||
return GalleryPresentationAnimationController(sourceView: sourceView)
|
return GalleryPresentationAnimationController(sourceView: sourceView)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
|
||||||
#if os(visionOS)
|
|
||||||
return nil
|
|
||||||
#else
|
|
||||||
if let sourceView = galleryDataSource.galleryContentTransitionSourceView(forItemAt: currentItemViewController.itemIndex) {
|
if let sourceView = galleryDataSource.galleryContentTransitionSourceView(forItemAt: currentItemViewController.itemIndex) {
|
||||||
let translation: CGPoint?
|
let translation: CGPoint?
|
||||||
let velocity: CGPoint?
|
let velocity: CGPoint?
|
||||||
|
@ -182,6 +175,5 @@ extension GalleryViewController: UIViewControllerTransitioningDelegate {
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,19 +204,14 @@ 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)
|
||||||
if #available(iOS 17.5, *) {
|
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||||
UIImpactFeedbackGenerator(style: .heavy, view: view).impactOccurred(at: CGPoint(x: view.bounds.midX, y: view.bounds.midY))
|
selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator()
|
||||||
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)
|
||||||
|
|
||||||
|
@ -265,11 +260,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
if #available(iOS 17.5, *) {
|
selectionChangedFeedbackGenerator?.selectionChanged()
|
||||||
selectionChangedFeedbackGenerator?.selectionChanged(at: location)
|
|
||||||
} else {
|
|
||||||
selectionChangedFeedbackGenerator?.selectionChanged()
|
|
||||||
}
|
|
||||||
selectionChangedFeedbackGenerator?.prepare()
|
selectionChangedFeedbackGenerator?.prepare()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -270,13 +270,8 @@ private class VideoScrubbingControl: UIControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||||
if touch.type == .pencil || touch.type == .indirectPointer {
|
touchStartLocation = touch.location(in: self)
|
||||||
touchStartLocation = .zero
|
scrubbingStartFraction = fractionComplete
|
||||||
scrubbingStartFraction = 0
|
|
||||||
} else {
|
|
||||||
touchStartLocation = touch.location(in: self)
|
|
||||||
scrubbingStartFraction = fractionComplete
|
|
||||||
}
|
|
||||||
|
|
||||||
animator = UIViewPropertyAnimator(duration: 0.1, curve: .linear)
|
animator = UIViewPropertyAnimator(duration: 0.1, curve: .linear)
|
||||||
animator!.addAnimations {
|
animator!.addAnimations {
|
||||||
|
@ -287,28 +282,17 @@ private class VideoScrubbingControl: UIControl {
|
||||||
sendActions(for: .editingDidBegin)
|
sendActions(for: .editingDidBegin)
|
||||||
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
if #available(iOS 17.5, *) {
|
feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
||||||
feedbackGenerator = UIImpactFeedbackGenerator(style: .light, view: self)
|
|
||||||
} else {
|
|
||||||
feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
|
|
||||||
}
|
|
||||||
feedbackGenerator!.prepare()
|
feedbackGenerator!.prepare()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
updateScrubbing(for: touch)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||||
updateScrubbing(for: touch)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateScrubbing(for touch: UITouch) {
|
|
||||||
guard let touchStartLocation,
|
guard let touchStartLocation,
|
||||||
let scrubbingStartFraction else {
|
let scrubbingStartFraction else {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
let location = touch.location(in: self)
|
let location = touch.location(in: self)
|
||||||
let translation = CGPoint(x: location.x - touchStartLocation.x, y: location.y - touchStartLocation.y)
|
let translation = CGPoint(x: location.x - touchStartLocation.x, y: location.y - touchStartLocation.y)
|
||||||
|
@ -317,11 +301,7 @@ 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) {
|
||||||
if #available(iOS 17.5, *) {
|
feedbackGenerator!.impactOccurred(intensity: 0.5)
|
||||||
feedbackGenerator!.impactOccurred(intensity: 0.5, at: location)
|
|
||||||
} else {
|
|
||||||
feedbackGenerator!.impactOccurred(intensity: 0.5)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
fractionComplete = newFractionComplete
|
fractionComplete = newFractionComplete
|
||||||
|
@ -338,6 +318,8 @@ private class VideoScrubbingControl: UIControl {
|
||||||
transform = CGAffineTransform(scaleX: 1 + stretchAmount / bounds.width, y: 1 + 0.5 * (1 - stretchFactor))
|
transform = CGAffineTransform(scaleX: 1 + stretchAmount / bounds.width, y: 1 + 0.5 * (1 - stretchFactor))
|
||||||
.translatedBy(x: sign(unclampedFractionComplete) * stretchAmount / 2, y: 0)
|
.translatedBy(x: sign(unclampedFractionComplete) * stretchAmount / 2, y: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||||
|
|
|
@ -150,16 +150,9 @@ class CustomAlertActionsView: UIControl {
|
||||||
private var separatorSizeConstraints: [NSLayoutConstraint] = []
|
private var separatorSizeConstraints: [NSLayoutConstraint] = []
|
||||||
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
private lazy var generator: UISelectionFeedbackGenerator = {
|
private let generator = UISelectionFeedbackGenerator()
|
||||||
if #available(iOS 17.5, *) {
|
|
||||||
UISelectionFeedbackGenerator(view: self)
|
|
||||||
} else {
|
|
||||||
UISelectionFeedbackGenerator()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
#endif
|
#endif
|
||||||
private var currentSelectedActionIndex: Int?
|
private var currentSelectedActionIndex: Int?
|
||||||
private var showPressedMenuWorkItem: DispatchWorkItem?
|
|
||||||
|
|
||||||
init(config: CustomAlertController.Configuration, dismiss: @escaping () -> Void) {
|
init(config: CustomAlertController.Configuration, dismiss: @escaping () -> Void) {
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
|
@ -320,42 +313,13 @@ class CustomAlertActionsView: UIControl {
|
||||||
actionButtons[currentSelectedActionIndex].backgroundColor = nil
|
actionButtons[currentSelectedActionIndex].backgroundColor = nil
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
if #available(iOS 17.5, *) {
|
generator.selectionChanged()
|
||||||
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
|
||||||
|
|
||||||
if let showPressedMenuWorkItem {
|
|
||||||
showPressedMenuWorkItem.cancel()
|
|
||||||
self.showPressedMenuWorkItem = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSelectedActionIndex = selectedButton?.offset
|
currentSelectedActionIndex = selectedButton?.offset
|
||||||
selectedButton?.element.backgroundColor = .secondarySystemFill
|
selectedButton?.element.backgroundColor = .secondarySystemFill
|
||||||
|
|
||||||
if let currentSelectedActionIndex,
|
|
||||||
case .menu(_) = reorderedActions[currentSelectedActionIndex].style,
|
|
||||||
case let button = actionButtons[currentSelectedActionIndex],
|
|
||||||
let interaction = button.contextMenuInteraction,
|
|
||||||
showPressedMenuWorkItem == nil {
|
|
||||||
showPressedMenuWorkItem = DispatchWorkItem {
|
|
||||||
if #available(iOS 17.4, *) {
|
|
||||||
button.performPrimaryAction()
|
|
||||||
} else {
|
|
||||||
let selector = NSSelectorFromString(["Location:", "At", "Menu", "present", "_"].reversed().joined())
|
|
||||||
if interaction.responds(to: selector) {
|
|
||||||
interaction.perform(selector, with: recognizer.location(in: button))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250), execute: showPressedMenuWorkItem!)
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
generator.prepare()
|
generator.prepare()
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -14,7 +14,6 @@ class PollOptionView: UIView {
|
||||||
private static let minHeight: CGFloat = 35
|
private static let minHeight: CGFloat = 35
|
||||||
private static let cornerRadius = 0.1 * minHeight
|
private static let cornerRadius = 0.1 * minHeight
|
||||||
private static let unselectedBackgroundColor = UIColor(white: 0.5, alpha: 0.25)
|
private static let unselectedBackgroundColor = UIColor(white: 0.5, alpha: 0.25)
|
||||||
private static let hoveredBackgroundColor = UIColor(white: 0.35, alpha: 0.25)
|
|
||||||
|
|
||||||
private(set) var label: EmojiLabel!
|
private(set) var label: EmojiLabel!
|
||||||
@Lazy private var checkbox: PollOptionCheckboxView = PollOptionCheckboxView().configure {
|
@Lazy private var checkbox: PollOptionCheckboxView = PollOptionCheckboxView().configure {
|
||||||
|
@ -34,12 +33,6 @@ class PollOptionView: UIView {
|
||||||
private var labelLeadingToSelfConstraint: NSLayoutConstraint!
|
private var labelLeadingToSelfConstraint: NSLayoutConstraint!
|
||||||
private var fillViewWidthConstraint: NSLayoutConstraint?
|
private var fillViewWidthConstraint: NSLayoutConstraint?
|
||||||
|
|
||||||
var hovered: Bool = false {
|
|
||||||
didSet {
|
|
||||||
backgroundColor = hovered ? PollOptionView.hoveredBackgroundColor : PollOptionView.unselectedBackgroundColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
|
|
|
@ -24,19 +24,12 @@ class PollOptionsView: UIControl {
|
||||||
private var poll: Poll!
|
private var poll: Poll!
|
||||||
private var animator: UIViewPropertyAnimator!
|
private var animator: UIViewPropertyAnimator!
|
||||||
private var currentSelectedOptionIndex: Int?
|
private var currentSelectedOptionIndex: Int?
|
||||||
private var currentHoveredOptionIndex: Int?
|
|
||||||
|
|
||||||
static let animationDuration: TimeInterval = 0.1
|
private let animationDuration: TimeInterval = 0.1
|
||||||
static let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
private let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
||||||
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
private lazy var generator: UISelectionFeedbackGenerator = {
|
private let generator = UISelectionFeedbackGenerator()
|
||||||
if #available(iOS 17.5, *) {
|
|
||||||
UISelectionFeedbackGenerator(view: self)
|
|
||||||
} else {
|
|
||||||
UISelectionFeedbackGenerator()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
override var isEnabled: Bool {
|
override var isEnabled: Bool {
|
||||||
|
@ -66,8 +59,6 @@ class PollOptionsView: UIControl {
|
||||||
stack.topAnchor.constraint(equalTo: topAnchor),
|
stack.topAnchor.constraint(equalTo: topAnchor),
|
||||||
stack.bottomAnchor.constraint(equalTo: bottomAnchor),
|
stack.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
addGestureRecognizer(UIHoverGestureRecognizer(target: self, action: #selector(hoverRecognized)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
|
@ -130,20 +121,6 @@ class PollOptionsView: UIControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func optionView(at point: CGPoint) -> (PollOptionView, Int)? {
|
|
||||||
for (index, view) in options.enumerated() {
|
|
||||||
// don't use view.frame because it changes when a transform is applied
|
|
||||||
var frame = CGRect(x: 0, y: view.center.y - view.bounds.height / 2, width: view.bounds.width, height: view.bounds.height)
|
|
||||||
if index != options.count - 1 {
|
|
||||||
frame = frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: -stack.spacing, right: 0))
|
|
||||||
}
|
|
||||||
if frame.contains(point) {
|
|
||||||
return (view, index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - UIControl
|
// MARK: - UIControl
|
||||||
|
|
||||||
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||||
|
@ -155,21 +132,13 @@ class PollOptionsView: UIControl {
|
||||||
if view.point(inside: touch.location(in: view), with: event) {
|
if view.point(inside: touch.location(in: view), with: event) {
|
||||||
currentSelectedOptionIndex = index
|
currentSelectedOptionIndex = index
|
||||||
|
|
||||||
if animator?.isRunning == true {
|
animator = UIViewPropertyAnimator(duration: animationDuration, curve: .easeInOut) {
|
||||||
animator.stopAnimation(true)
|
view.transform = self.scaledTransform
|
||||||
}
|
|
||||||
animator = UIViewPropertyAnimator(duration: Self.animationDuration, curve: .easeInOut) {
|
|
||||||
view.transform = Self.scaledTransform
|
|
||||||
view.hovered = true
|
|
||||||
}
|
}
|
||||||
animator.startAnimation()
|
animator.startAnimation()
|
||||||
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
if #available(iOS 17.5, *) {
|
generator.selectionChanged()
|
||||||
generator.selectionChanged(at: view.center)
|
|
||||||
} else {
|
|
||||||
generator.selectionChanged()
|
|
||||||
}
|
|
||||||
generator.prepare()
|
generator.prepare()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -182,31 +151,30 @@ 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 newIndexAndOption = optionView(at: location)
|
var newIndex: Int? = nil
|
||||||
let newIndex = newIndexAndOption?.1
|
for (index, view) in options.enumerated() {
|
||||||
let option = newIndexAndOption?.0
|
var frame = view.frame
|
||||||
|
if index != options.count - 1 {
|
||||||
|
frame = frame.inset(by: UIEdgeInsets(top: 0, left: 0, bottom: -stack.spacing, right: 0))
|
||||||
|
}
|
||||||
|
if frame.contains(location) {
|
||||||
|
newIndex = index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if newIndex != currentSelectedOptionIndex {
|
if newIndex != currentSelectedOptionIndex {
|
||||||
currentSelectedOptionIndex = newIndex
|
currentSelectedOptionIndex = newIndex
|
||||||
|
|
||||||
if animator.isRunning {
|
UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseInOut) {
|
||||||
animator.stopAnimation(true)
|
|
||||||
}
|
|
||||||
animator = UIViewPropertyAnimator(duration: Self.animationDuration, curve: .easeInOut) {
|
|
||||||
for (index, view) in self.options.enumerated() {
|
for (index, view) in self.options.enumerated() {
|
||||||
view.transform = index == newIndex ? Self.scaledTransform : .identity
|
view.transform = index == newIndex ? self.scaledTransform : .identity
|
||||||
view.hovered = index == newIndex
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
animator.startAnimation()
|
|
||||||
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
if let option {
|
if newIndex != nil {
|
||||||
if #available(iOS 17.5, *) {
|
generator.selectionChanged()
|
||||||
generator.selectionChanged(at: option.center)
|
|
||||||
} else {
|
|
||||||
generator.selectionChanged()
|
|
||||||
}
|
|
||||||
generator.prepare()
|
generator.prepare()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -221,15 +189,14 @@ class PollOptionsView: UIControl {
|
||||||
func selectOption() {
|
func selectOption() {
|
||||||
guard let index = currentSelectedOptionIndex else { return }
|
guard let index = currentSelectedOptionIndex else { return }
|
||||||
let option = options[index]
|
let option = options[index]
|
||||||
animator = UIViewPropertyAnimator(duration: Self.animationDuration, curve: .easeInOut) {
|
animator = UIViewPropertyAnimator(duration: animationDuration, curve: .easeInOut) {
|
||||||
option.transform = .identity
|
option.transform = .identity
|
||||||
option.hovered = false
|
|
||||||
self.selectOption(option)
|
self.selectOption(option)
|
||||||
}
|
}
|
||||||
animator.startAnimation()
|
animator.startAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
if animator?.isRunning == true {
|
if animator.isRunning {
|
||||||
animator.addCompletion { (_) in
|
animator.addCompletion { (_) in
|
||||||
selectOption()
|
selectOption()
|
||||||
}
|
}
|
||||||
|
@ -240,52 +207,12 @@ class PollOptionsView: UIControl {
|
||||||
|
|
||||||
override func cancelTracking(with event: UIEvent?) {
|
override func cancelTracking(with event: UIEvent?) {
|
||||||
super.cancelTracking(with: event)
|
super.cancelTracking(with: event)
|
||||||
if animator?.isRunning == true {
|
UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseInOut) {
|
||||||
animator.stopAnimation(true)
|
|
||||||
}
|
|
||||||
animator = UIViewPropertyAnimator(duration: Self.animationDuration, curve: .easeInOut) {
|
|
||||||
for view in self.options {
|
for view in self.options {
|
||||||
view.transform = .identity
|
view.transform = .identity
|
||||||
view.hovered = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
animator.startAnimation()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc private func hoverRecognized(_ recognizer: UIHoverGestureRecognizer) {
|
|
||||||
guard let (option, index) = optionView(at: recognizer.location(in: self)) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch recognizer.state {
|
|
||||||
case .began, .changed:
|
|
||||||
if index != currentHoveredOptionIndex {
|
|
||||||
let oldIndex = currentHoveredOptionIndex
|
|
||||||
currentHoveredOptionIndex = index
|
|
||||||
if animator?.isRunning == true {
|
|
||||||
animator.stopAnimation(true)
|
|
||||||
}
|
|
||||||
animator = UIViewPropertyAnimator(duration: Self.animationDuration, curve: .easeInOut) {
|
|
||||||
option.hovered = true
|
|
||||||
if let oldIndex {
|
|
||||||
self.options[oldIndex].hovered = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
animator.startAnimation()
|
|
||||||
}
|
|
||||||
case .ended, .cancelled:
|
|
||||||
if let currentHoveredOptionIndex {
|
|
||||||
self.currentHoveredOptionIndex = nil
|
|
||||||
if animator?.isRunning == true {
|
|
||||||
animator.stopAnimation(true)
|
|
||||||
}
|
|
||||||
animator = UIViewPropertyAnimator(duration: Self.animationDuration, curve: .easeInOut) {
|
|
||||||
self.options[currentHoveredOptionIndex].hovered = false
|
|
||||||
}
|
|
||||||
animator.startAnimation()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,6 @@ class StatusPollView: UIView, StatusContentView {
|
||||||
addSubview(infoLabel)
|
addSubview(infoLabel)
|
||||||
|
|
||||||
voteButton = UIButton(configuration: .plain())
|
voteButton = UIButton(configuration: .plain())
|
||||||
voteButton.isPointerInteractionEnabled = true
|
|
||||||
voteButton.translatesAutoresizingMaskIntoConstraints = false
|
voteButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
voteButton.addTarget(self, action: #selector(votePressed), for: .touchUpInside)
|
voteButton.addTarget(self, action: #selector(votePressed), for: .touchUpInside)
|
||||||
voteButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
voteButton.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
||||||
|
|
|
@ -24,13 +24,7 @@ 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 {
|
||||||
|
@ -117,19 +111,13 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
|
||||||
|
|
||||||
func setSelectedOption(_ value: Value, animated: Bool) {
|
func setSelectedOption(_ value: Value, animated: Bool) {
|
||||||
guard selectedOption != value,
|
guard selectedOption != value,
|
||||||
let index = options.firstIndex(where: { $0.value == value }) else {
|
options.contains(where: { $0.value == value }) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
if selectedOption != nil {
|
if selectedOption != nil {
|
||||||
if #available(iOS 17.5, *) {
|
selectionChangedFeedbackGenerator.selectionChanged()
|
||||||
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
|
||||||
|
|
||||||
|
@ -170,19 +158,15 @@ 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(gestureRecognizer.location(in: optionsStack)) {
|
optionsStack.arrangedSubviews[selectedIndex].frame.contains(self.panGestureRecognizer.location(in: optionsStack)) {
|
||||||
beganOnSelectedOption = true
|
beganOnSelectedOption = true
|
||||||
} else {
|
} else {
|
||||||
beganOnSelectedOption = false
|
beganOnSelectedOption = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// only begin changing selection if the gesture started on the currently selected item
|
// only begin changing selection if the gesutre 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
|
||||||
|
@ -239,12 +223,7 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
|
||||||
}
|
}
|
||||||
animator.startAnimation()
|
animator.startAnimation()
|
||||||
#if !os(visionOS)
|
#if !os(visionOS)
|
||||||
if #available(iOS 17.5, *) {
|
selectionChangedFeedbackGenerator.selectionChanged()
|
||||||
let locationInSelf = convert(location, from: optionsStack)
|
|
||||||
selectionChangedFeedbackGenerator.selectionChanged(at: locationInSelf)
|
|
||||||
} else {
|
|
||||||
selectionChangedFeedbackGenerator.selectionChanged()
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue