parent
c367a2e9f1
commit
3cb0f46533
|
@ -14,6 +14,7 @@ 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 {
|
||||||
|
@ -33,6 +34,12 @@ 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,9 +24,10 @@ 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?
|
||||||
|
|
||||||
private let animationDuration: TimeInterval = 0.1
|
static let animationDuration: TimeInterval = 0.1
|
||||||
private 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 let generator = UISelectionFeedbackGenerator()
|
||||||
|
@ -59,6 +60,8 @@ 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) {
|
||||||
|
@ -121,6 +124,20 @@ 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 {
|
||||||
|
@ -132,8 +149,12 @@ 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
|
||||||
|
|
||||||
animator = UIViewPropertyAnimator(duration: animationDuration, curve: .easeInOut) {
|
if animator?.isRunning == true {
|
||||||
view.transform = self.scaledTransform
|
animator.stopAnimation(true)
|
||||||
|
}
|
||||||
|
animator = UIViewPropertyAnimator(duration: Self.animationDuration, curve: .easeInOut) {
|
||||||
|
view.transform = Self.scaledTransform
|
||||||
|
view.hovered = true
|
||||||
}
|
}
|
||||||
animator.startAnimation()
|
animator.startAnimation()
|
||||||
|
|
||||||
|
@ -151,27 +172,21 @@ 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)
|
||||||
var newIndex: Int? = nil
|
let newIndex = optionView(at: location)?.1
|
||||||
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(location) {
|
|
||||||
newIndex = index
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if newIndex != currentSelectedOptionIndex {
|
if newIndex != currentSelectedOptionIndex {
|
||||||
currentSelectedOptionIndex = newIndex
|
currentSelectedOptionIndex = newIndex
|
||||||
|
|
||||||
UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseInOut) {
|
if animator.isRunning {
|
||||||
|
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 newIndex != nil {
|
if newIndex != nil {
|
||||||
|
@ -190,14 +205,15 @@ 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: animationDuration, curve: .easeInOut) {
|
animator = UIViewPropertyAnimator(duration: Self.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 {
|
if animator?.isRunning == true {
|
||||||
animator.addCompletion { (_) in
|
animator.addCompletion { (_) in
|
||||||
selectOption()
|
selectOption()
|
||||||
}
|
}
|
||||||
|
@ -208,12 +224,52 @@ class PollOptionsView: UIControl {
|
||||||
|
|
||||||
override func cancelTracking(with event: UIEvent?) {
|
override func cancelTracking(with event: UIEvent?) {
|
||||||
super.cancelTracking(with: event)
|
super.cancelTracking(with: event)
|
||||||
UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseInOut) {
|
if animator?.isRunning == true {
|
||||||
|
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,6 +65,7 @@ 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)
|
||||||
|
|
Loading…
Reference in New Issue