forked from square/Listable
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathListViewController.swift
186 lines (155 loc) · 5.96 KB
/
ListViewController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//
// ListViewController.swift
// ListableUI
//
// Created by Kyle Van Essen on 6/21/20.
//
import Foundation
import UIKit
///
/// A class which provides an easy way to set up and display a `ListView`,
/// The `ListViewController` itself manages setup and presentation of the list.
///
/// As a consumer of the API, all you need to do is override one method:
/// ```
/// func configure(list : inout ListProperties) {
/// ...
/// }
/// ```
/// In which you set up and configure the list as needed.
///
/// In order to reload the list when content changes or other display changes are required, call
/// ```
/// func reload(animated : Bool = false)
/// ```
/// Which will update the list with the new contents returned from your `configure` method.
/// If the `ListViewController`'s view is not loaded, this method has no effect.
///
open class ListViewController : UIViewController
{
//
// MARK: Configuration
//
/// The default value for `clearsSelectionOnViewWillAppear` is true.
/// This parameter allows mirroring the `clearsSelectionOnViewWillAppear`
/// as available from `UITableViewController` or `UICollectionViewController`.
public var clearsSelectionOnViewWillAppear : Bool = true
//
// MARK: Methods To Override
//
/// Override this method to configure your list how you'd like to.
/// The properties on `ListProperties` closely mirror those on `ListView`
/// itself, allowing you to fully configure and work with a list without needing to maintain
/// and manage the view instance yourself.
///
/// Example
/// -------
/// ```
/// override func configure(list : inout ListProperties)
/// {
/// list.appearance = .myAppearance
///
/// list.layout = .table { appearance in
/// // Configure the appearance.
/// }
///
/// list.stateObserver.onContentChanged { info in
/// MyLogger.log(...)
/// }
///
/// list("first-section") { section in
/// section += self.myPodcasts.map { podcast in
/// PodcastItem(podcast)
/// }
/// }
/// }
/// ```
/// You should not call super in your overridden implementation.
///
open func configure(list : inout ListProperties)
{
fatalError("Subclasses of `ListViewController` must override `configure(list:)` to customize the content of their list view.")
}
//
// MARK: Updating Content
//
public func reload(animated : Bool = false)
{
guard let listView = self.listView else {
return
}
listView.configure { list in
list.animatesChanges = animated
self.configure(list: &list)
}
}
//
// MARK: - Internal & Private Methods -
//
// MARK: Initialization
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
{
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable) required public init?(coder: NSCoder) { fatalError() }
// MARK: UIViewController
private var listView : ListView?
public override func loadView() {
let listView = ListView()
self.listView = listView
self.view = listView
}
private var hasViewAppeared : Bool = false
open override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(animated)
if self.hasViewAppeared == false {
self.hasViewAppeared = true
self.reload(animated: false)
} else {
if self.clearsSelectionOnViewWillAppear {
self.listView?.clearSelectionDuringViewWillAppear(alongside: self.transitionCoordinator, animated: animated)
}
}
}
}
public extension ListView {
/// A method which provides `Behavior.SelectionMode.single`'s `clearsSelectionOnViewWillAppear` behaviour.
/// By default, this method is called by `ListViewController`. However if you are not using `ListViewController` you
/// will need to call this method yourself one of two ways:
///
/// 1) If subclassing `UIViewController`: within your view controller's `viewWillAppear` method.
///
/// 2) By invoking this same method on your `ListActions` that you have wired up to your list view. Use this
/// in the case that you do not have access to your list view at all, such as when using `BlueprintUILists`.
///
/// // Behaviour from UIKit Eng: https://twitter.com/smileyborg/status/1279473615553982464
///
func clearSelectionDuringViewWillAppear(alongside coordinator: UIViewControllerTransitionCoordinator?, animated : Bool) {
guard case Behavior.SelectionMode.single = behavior.selectionMode else {
return
}
guard let indexPath = storage.presentationState.selectedIndexPaths.first else {
return
}
let item = storage.presentationState.item(at: indexPath)
guard let coordinator = coordinator else {
// No transition coordinator is available – we should just deselect return in this case.
item.set(isSelected: false, performCallbacks: true)
collectionView.deselectItem(at: indexPath, animated: animated)
item.applyToVisibleCell(with: self.environment)
return
}
coordinator.animate(alongsideTransition: { _ in
item.set(isSelected: false, performCallbacks: true)
self.collectionView.deselectItem(at: indexPath, animated: true)
item.applyToVisibleCell(with: self.environment)
}, completion: { context in
if context.isCancelled {
item.set(isSelected: true, performCallbacks: false)
self.collectionView.selectItem(at: indexPath, animated: true, scrollPosition: [])
item.applyToVisibleCell(with: self.environment)
}
})
}
}