Load more direction changing

This commit is contained in:
Justin Mazzocchi 2020-10-04 18:25:02 -07:00
parent 7f937601b1
commit 90d750464b
No known key found for this signature in database
GPG Key ID: E223E6937AAFB01C
3 changed files with 74 additions and 13 deletions

View File

@ -77,6 +77,22 @@ class TableViewController: UITableViewController {
viewModel.request(maxID: nil, minID: nil)
}
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView.isDragging else { return }
let up = scrollView.panGestureRecognizer.translation(in: scrollView.superview).y > 0
for loadMoreView in visibleLoadMoreViews {
loadMoreView.directionChanged(up: up)
}
}
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
for loadMoreView in visibleLoadMoreViews {
loadMoreView.finalizeDirectionChange()
}
}
override func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
@ -101,6 +117,8 @@ class TableViewController: UITableViewController {
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
viewModel.itemSelected(item)
@ -160,6 +178,10 @@ extension TableViewController {
}
private extension TableViewController {
var visibleLoadMoreViews: [LoadMoreView] {
tableView.visibleCells.compactMap { $0.contentView as? LoadMoreView }
}
func setupViewModelBindings() {
viewModel.title.sink { [weak self] in self?.navigationItem.title = $0 }.store(in: &cancellables)

View File

@ -3,17 +3,16 @@
import Combine
import ServiceLayer
public struct LoadMoreViewModel {
public let loading: AnyPublisher<Bool, Never>
final public class LoadMoreViewModel: ObservableObject {
public var direction = LoadMore.Direction.up
@Published public private(set) var loading = false
public let events: AnyPublisher<AnyPublisher<CollectionItemEvent, Error>, Never>
private let loadMoreService: LoadMoreService
private let eventsSubject = PassthroughSubject<AnyPublisher<CollectionItemEvent, Error>, Never>()
private let loadingSubject = PassthroughSubject<Bool, Never>()
init(loadMoreService: LoadMoreService) {
self.loadMoreService = loadMoreService
loading = loadingSubject.eraseToAnyPublisher()
events = eventsSubject.eraseToAnyPublisher()
}
}
@ -21,10 +20,10 @@ public struct LoadMoreViewModel {
extension LoadMoreViewModel {
func loadMore() {
eventsSubject.send(
loadMoreService.request(direction: .down)
loadMoreService.request(direction: direction)
.handleEvents(
receiveSubscription: { _ in loadingSubject.send(true) },
receiveCompletion: { _ in loadingSubject.send(false) })
receiveSubscription: { [weak self] _ in self?.loading = true },
receiveCompletion: { [weak self] _ in self?.loading = false })
.map { _ in CollectionItemEvent.ignorableOutput }
.eraseToAnyPublisher())
}

View File

@ -3,11 +3,14 @@
import Combine
import UIKit
class LoadMoreView: UIView {
final class LoadMoreView: UIView {
private let leadingArrowImageView = UIImageView()
private let trailingArrowImageView = UIImageView()
private let label = UILabel()
private let activityIndicatorView = UIActivityIndicatorView()
private var loadMoreConfiguration: LoadMoreContentConfiguration
private var loadingCancellable: AnyCancellable?
private var directionChange = LoadMoreView.directionChangeMax
init(configuration: LoadMoreContentConfiguration) {
self.loadMoreConfiguration = configuration
@ -15,6 +18,7 @@ class LoadMoreView: UIView {
super.init(frame: .zero)
initialSetup()
applyLoadMoreConfiguration()
}
@available(*, unavailable)
@ -23,6 +27,26 @@ class LoadMoreView: UIView {
}
}
extension LoadMoreView {
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)
}
}
extension LoadMoreView: UIContentView {
var configuration: UIContentConfiguration {
get { loadMoreConfiguration }
@ -37,15 +61,15 @@ extension LoadMoreView: UIContentView {
}
private extension LoadMoreView {
func initialSetup() {
let leadingArrowImageView = UIImageView()
let trailingArrowImageView = UIImageView()
static let directionChangeMax = CGFloat.pi
static let directionChangeIncrement = CGFloat.pi / 10
func initialSetup() {
for arrowImageView in [leadingArrowImageView, trailingArrowImageView] {
addSubview(arrowImageView)
arrowImageView.translatesAutoresizingMaskIntoConstraints = false
arrowImageView.image = UIImage(
systemName: "arrow.up.circle",
systemName: "arrow.up",
withConfiguration: UIImage.SymbolConfiguration(
pointSize: UIFont.preferredFont(forTextStyle: .title2).pointSize))
arrowImageView.setContentHuggingPriority(.required, for: .horizontal)
@ -81,11 +105,27 @@ private extension LoadMoreView {
}
func applyLoadMoreConfiguration() {
loadingCancellable = loadMoreConfiguration.viewModel.loading.sink { [weak self] in
loadingCancellable = loadMoreConfiguration.viewModel.$loading.sink { [weak self] in
guard let self = self else { return }
self.label.isHidden = $0
$0 ? self.activityIndicatorView.startAnimating() : self.activityIndicatorView.stopAnimating()
}
}
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)
}
}