metatext/Views/UIKit/Content Views/LoadMoreView.swift

179 lines
7.5 KiB
Swift
Raw Normal View History

2020-10-04 08:39:54 +00:00
// Copyright © 2020 Metabolist. All rights reserved.
import Combine
import UIKit
2020-10-05 01:25:02 +00:00
final class LoadMoreView: UIView {
private let leadingArrowImageView = UIImageView()
private let trailingArrowImageView = UIImageView()
2020-10-04 08:39:54 +00:00
private let label = UILabel()
private let activityIndicatorView = UIActivityIndicatorView()
private var loadMoreConfiguration: LoadMoreContentConfiguration
private var loadingCancellable: AnyCancellable?
2020-10-05 01:25:02 +00:00
private var directionChange = LoadMoreView.directionChangeMax
2020-10-04 08:39:54 +00:00
init(configuration: LoadMoreContentConfiguration) {
self.loadMoreConfiguration = configuration
super.init(frame: .zero)
initialSetup()
2020-10-05 01:25:02 +00:00
applyLoadMoreConfiguration()
2020-10-04 08:39:54 +00:00
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
2020-10-05 01:25:02 +00:00
extension LoadMoreView {
static let accessibilityCustomAction =
Notification.Name("com.metabolist.metatext.load-more-view.accessibility-custom-action")
2021-01-20 02:47:21 +00:00
static var estimatedHeight: CGFloat {
.defaultSpacing * 2 + UIFont.preferredFont(forTextStyle: .title2).lineHeight
}
2020-10-05 01:25:02 +00:00
func directionChanged(up: Bool) {
guard !loadMoreConfiguration.viewModel.loading else { return }
if up, directionChange < Self.directionChangeMax {
directionChange += Self.directionChangeIncrement
} else if !up, directionChange > -Self.directionChangeMax {
directionChange -= Self.directionChangeIncrement
}
updateDirectionChange(animated: false)
}
func finalizeDirectionChange() {
directionChange = directionChange > 0 ? Self.directionChangeMax : -Self.directionChangeMax
updateDirectionChange(animated: true)
}
}
2020-10-04 08:39:54 +00:00
extension LoadMoreView: UIContentView {
var configuration: UIContentConfiguration {
get { loadMoreConfiguration }
set {
guard let loadMoreConfiguration = newValue as? LoadMoreContentConfiguration else { return }
self.loadMoreConfiguration = loadMoreConfiguration
applyLoadMoreConfiguration()
}
}
}
private extension LoadMoreView {
2020-10-05 01:25:02 +00:00
static let directionChangeMax = CGFloat.pi
static let directionChangeIncrement = CGFloat.pi / 10
2020-10-04 08:39:54 +00:00
2021-02-02 21:32:39 +00:00
// swiftlint:disable:next function_body_length
2020-10-05 01:25:02 +00:00
func initialSetup() {
2020-10-04 08:39:54 +00:00
for arrowImageView in [leadingArrowImageView, trailingArrowImageView] {
addSubview(arrowImageView)
arrowImageView.translatesAutoresizingMaskIntoConstraints = false
arrowImageView.image = UIImage(
2020-10-05 01:25:02 +00:00
systemName: "arrow.up",
2020-10-04 08:39:54 +00:00
withConfiguration: UIImage.SymbolConfiguration(
pointSize: UIFont.preferredFont(forTextStyle: .title2).pointSize))
2021-03-09 06:42:45 +00:00
arrowImageView.contentMode = .scaleAspectFit
2020-10-04 08:39:54 +00:00
arrowImageView.setContentHuggingPriority(.required, for: .horizontal)
}
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.font = .preferredFont(forTextStyle: .title2)
label.adjustsFontForContentSizeCategory = true
label.textColor = label.tintColor
label.text = NSLocalizedString("load-more", comment: "")
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
addSubview(activityIndicatorView)
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
activityIndicatorView.hidesWhenStopped = true
NSLayoutConstraint.activate([
2021-03-09 06:42:45 +00:00
heightAnchor.constraint(greaterThanOrEqualToConstant: .minimumButtonDimension * 2),
2020-10-04 08:39:54 +00:00
leadingArrowImageView.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor),
2021-03-09 06:42:45 +00:00
leadingArrowImageView.topAnchor.constraint(greaterThanOrEqualTo: readableContentGuide.topAnchor),
leadingArrowImageView.bottomAnchor.constraint(greaterThanOrEqualTo: readableContentGuide.bottomAnchor),
leadingArrowImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
2020-10-04 08:39:54 +00:00
label.leadingAnchor.constraint(equalTo: leadingArrowImageView.trailingAnchor),
label.topAnchor.constraint(greaterThanOrEqualTo: readableContentGuide.topAnchor),
label.bottomAnchor.constraint(greaterThanOrEqualTo: readableContentGuide.bottomAnchor),
label.trailingAnchor.constraint(equalTo: trailingArrowImageView.leadingAnchor),
2021-03-09 06:42:45 +00:00
label.centerYAnchor.constraint(equalTo: centerYAnchor),
trailingArrowImageView.topAnchor.constraint(greaterThanOrEqualTo: readableContentGuide.topAnchor),
trailingArrowImageView.bottomAnchor.constraint(greaterThanOrEqualTo: readableContentGuide.bottomAnchor),
2020-10-04 08:39:54 +00:00
trailingArrowImageView.trailingAnchor.constraint(equalTo: readableContentGuide.trailingAnchor),
2021-03-09 06:42:45 +00:00
trailingArrowImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
2020-10-04 08:39:54 +00:00
activityIndicatorView.centerXAnchor.constraint(equalTo: centerXAnchor),
activityIndicatorView.centerYAnchor.constraint(equalTo: centerYAnchor)
])
2021-02-02 21:32:39 +00:00
isAccessibilityElement = true
accessibilityLabel = NSLocalizedString("load-more", comment: "")
2021-02-17 07:06:41 +00:00
let aboveAccessibilityActionName: String
let belowAccessibilityActionName: String
switch loadMoreConfiguration.viewModel.identityContext.appPreferences.statusWord {
case .toot:
2021-02-19 06:35:18 +00:00
aboveAccessibilityActionName = NSLocalizedString("load-more.above.accessibility.toot", comment: "")
belowAccessibilityActionName = NSLocalizedString("load-more.below.accessibility.toot", comment: "")
2021-02-17 07:06:41 +00:00
case .post:
2021-02-19 06:35:18 +00:00
aboveAccessibilityActionName = NSLocalizedString("load-more.above.accessibility.post", comment: "")
belowAccessibilityActionName = NSLocalizedString("load-more.below.accessibility.post", comment: "")
2021-02-17 07:06:41 +00:00
}
2021-02-02 21:32:39 +00:00
accessibilityCustomActions = [
UIAccessibilityCustomAction(
2021-02-17 07:06:41 +00:00
name: aboveAccessibilityActionName) { [weak self] _ in
self?.directionChange = -Self.directionChangeMax
self?.updateDirectionChange(animated: false)
NotificationCenter.default.post(name: Self.accessibilityCustomAction, object: self)
2021-02-02 21:32:39 +00:00
return true
},
UIAccessibilityCustomAction(
2021-02-17 07:06:41 +00:00
name: belowAccessibilityActionName) { [weak self] _ in
self?.directionChange = Self.directionChangeMax
self?.updateDirectionChange(animated: false)
NotificationCenter.default.post(name: Self.accessibilityCustomAction, object: self)
2021-02-02 21:32:39 +00:00
return true
}
]
2020-10-04 08:39:54 +00:00
}
func applyLoadMoreConfiguration() {
2020-10-05 01:25:02 +00:00
loadingCancellable = loadMoreConfiguration.viewModel.$loading.sink { [weak self] in
2020-10-04 08:39:54 +00:00
guard let self = self else { return }
self.label.isHidden = $0
$0 ? self.activityIndicatorView.startAnimating() : self.activityIndicatorView.stopAnimating()
}
}
2020-10-05 01:25:02 +00:00
func updateDirectionChange(animated: Bool) {
if animated {
UIView.animate(withDuration: 0.1) {
self.performDirectionChangeUpdates()
}
} else {
self.performDirectionChangeUpdates()
}
}
func performDirectionChangeUpdates() {
loadMoreConfiguration.viewModel.direction = directionChange > 0 ? .up : .down
leadingArrowImageView.transform = CGAffineTransform(rotationAngle: .pi / 2 - directionChange / 2)
trailingArrowImageView.transform = CGAffineTransform(rotationAngle: -.pi / 2 + directionChange / 2)
}
2020-10-04 08:39:54 +00:00
}