Load more direction changing
This commit is contained in:
parent
7f937601b1
commit
90d750464b
|
@ -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)
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue