Tusker/Tusker/Views/Poll/PollOptionsView.swift

183 lines
5.5 KiB
Swift

//
// PollOptionsView.swift
// Tusker
//
// Created by Shadowfacts on 4/26/21.
// Copyright © 2021 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
class PollOptionsView: UIControl {
var checkedOptionIndices: [Int] {
options.enumerated().filter { $0.element.checkbox.isChecked }.map(\.offset)
}
var checkedOptionsChanged: (() -> Void)?
private let stack: UIStackView
private var options: [PollOptionView] = []
private var poll: Poll!
private var animator: UIViewPropertyAnimator!
private var currentSelectedOptionIndex: Int?
private let animationDuration: TimeInterval = 0.1
private let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
private let generator = UISelectionFeedbackGenerator()
override var isEnabled: Bool {
didSet {
options.forEach { $0.checkbox.readOnly = !isEnabled }
}
}
override init(frame: CGRect) {
stack = UIStackView()
super.init(frame: frame)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.spacing = 4
addSubview(stack)
NSLayoutConstraint.activate([
stack.leadingAnchor.constraint(equalTo: leadingAnchor),
stack.trailingAnchor.constraint(equalTo: trailingAnchor),
stack.topAnchor.constraint(equalTo: topAnchor),
stack.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func updateUI(poll: Poll) {
self.poll = poll
options.forEach { $0.removeFromSuperview() }
options = poll.options.enumerated().map { (index, opt) in
let optionView = PollOptionView(poll: poll, option: opt)
optionView.checkbox.readOnly = !isEnabled
optionView.checkbox.isChecked = poll.ownVotes?.contains(index) ?? false
optionView.checkbox.voted = poll.voted ?? false
stack.addArrangedSubview(optionView)
return optionView
}
accessibilityElements = options
}
private func selectOption(_ option: PollOptionView) {
if poll.multiple {
option.checkbox.isChecked.toggle()
} else {
for opt in options {
if opt === option {
opt.checkbox.isChecked = true
} else {
opt.checkbox.isChecked = false
}
}
}
checkedOptionsChanged?()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// don't let subviews receive touch events
return self
}
// MARK: - UIControl
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
guard isEnabled else {
return false
}
for (index, view) in options.enumerated() {
if view.point(inside: touch.location(in: view), with: event) {
currentSelectedOptionIndex = index
animator = UIViewPropertyAnimator(duration: animationDuration, curve: .easeInOut) {
view.transform = self.scaledTransform
}
animator.startAnimation()
generator.selectionChanged()
generator.prepare()
return true
}
}
return false
}
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
var newIndex: Int? = nil
for (index, view) in options.enumerated() {
if view.point(inside: touch.location(in: view), with: event) {
newIndex = index
break
}
}
if newIndex != currentSelectedOptionIndex {
currentSelectedOptionIndex = newIndex
UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseInOut) {
for (index, view) in self.options.enumerated() {
view.transform = index == newIndex ? self.scaledTransform : .identity
}
}
if newIndex != nil {
generator.selectionChanged()
generator.prepare()
}
}
return true
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
func selectOption() {
guard let index = currentSelectedOptionIndex else { return }
let option = options[index]
animator = UIViewPropertyAnimator(duration: animationDuration, curve: .easeInOut) {
option.transform = .identity
self.selectOption(option)
}
animator.startAnimation()
}
if animator.isRunning {
animator.addCompletion { (_) in
selectOption()
}
} else {
selectOption()
}
}
override func cancelTracking(with event: UIEvent?) {
super.cancelTracking(with: event)
UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseInOut) {
for view in self.options {
view.transform = .identity
}
}
}
}