-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathSelectorViewController.swift
232 lines (192 loc) · 8.82 KB
/
SelectorViewController.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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// SelectorViewController.swift
// Eureka ( https://github.com/xmartlabs/Eureka )
//
// Copyright (c) 2016 Xmartlabs SRL ( http://xmartlabs.com )
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import UIKit
/**
* Responsible for the options passed to a selector view controller
*/
public protocol OptionsProviderRow: TypedRowType {
associatedtype OptionsProviderType: OptionsProviderConformance
var optionsProvider: OptionsProviderType? { get set }
var cachedOptionsData: [OptionsProviderType.Option]? { get set }
}
extension OptionsProviderRow where Self: BaseRow {
public var options: [OptionsProviderType.Option]? {
set (newValue){
let optProvider = OptionsProviderType.init(array: newValue)
optionsProvider = optProvider
}
get {
return self.cachedOptionsData ?? optionsProvider?.optionsArray
}
}
public var cachedOptionsData: [OptionsProviderType.Option]? {
get {
return self._cachedOptionsData as? [OptionsProviderType.Option]
}
set {
self._cachedOptionsData = newValue
}
}
}
public protocol OptionsProviderConformance: ExpressibleByArrayLiteral {
associatedtype Option: Equatable
init(array: [Option]?)
func options(for selectorViewController: FormViewController, completion: @escaping ([Option]?) -> Void)
var optionsArray: [Option]? { get }
}
/// Provider of selectable options.
public enum OptionsProvider<T: Equatable>: OptionsProviderConformance {
/// Synchronous provider that provides array of options it was initialized with
case array([T]?)
/// Provider that uses closure it was initialized with to provide options. Can be synchronous or asynchronous.
case lazy((FormViewController, @escaping ([T]?) -> Void) -> Void)
public init(array: [T]?) {
self = .array(array)
}
public init(arrayLiteral elements: T...) {
self = .array(elements)
}
public func options(for selectorViewController: FormViewController, completion: @escaping ([T]?) -> Void) {
switch self {
case let .array(array):
completion(array)
case let .lazy(fetch):
fetch(selectorViewController, completion)
}
}
public var optionsArray: [T]?{
switch self {
case let .array(arrayData):
return arrayData
default:
return nil
}
}
}
open class _SelectorViewController<Row: SelectableRowType, OptionsRow: OptionsProviderRow>: FormViewController, TypedRowControllerType where Row: BaseRow, Row.Cell.Value == OptionsRow.OptionsProviderType.Option {
/// The row that pushed or presented this controller
public var row: RowOf<Row.Cell.Value>!
public var enableDeselection = true
public var dismissOnSelection = true
public var dismissOnChange = true
public var selectableRowSetup: ((_ row: Row) -> Void)?
public var selectableRowCellUpdate: ((_ cell: Row.Cell, _ row: Row) -> Void)?
public var selectableRowCellSetup: ((_ cell: Row.Cell, _ row: Row) -> Void)?
/// A closure to be called when the controller disappears.
public var onDismissCallback: ((UIViewController) -> Void)?
/// A closure that should return key for particular row value.
/// This key is later used to break options by sections.
public var sectionKeyForValue: ((Row.Cell.Value) -> (String))?
/// A closure that returns header title for a section for particular key.
/// By default returns the key itself.
public var sectionHeaderTitleForKey: ((String) -> String?)? = { $0 }
/// A closure that returns footer title for a section for particular key.
public var sectionFooterTitleForKey: ((String) -> String?)?
public var optionsProviderRow: OptionsRow {
return row as! OptionsRow
}
override public init(style: UITableView.Style) {
super.init(style: style)
}
override public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
convenience public init(_ callback: ((UIViewController) -> Void)?) {
self.init(nibName: nil, bundle: nil)
onDismissCallback = callback
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
open override func viewDidLoad() {
super.viewDidLoad()
setupForm()
}
open func setupForm() {
let optProvider = optionsProviderRow.optionsProvider
optProvider?.options(for: self) { [weak self] (options: [Row.Cell.Value]?) in
guard let strongSelf = self, let options = options else { return }
strongSelf.optionsProviderRow.cachedOptionsData = options
strongSelf.setupForm(with: options)
}
}
open func setupForm(with options: [Row.Cell.Value]) {
if let optionsBySections = optionsBySections(with: options) {
for (sectionKey, options) in optionsBySections {
form +++ section(with: options,
header: sectionHeaderTitleForKey?(sectionKey),
footer: sectionFooterTitleForKey?(sectionKey))
}
} else {
form +++ section(with: options, header: row.title, footer: nil)
}
}
func optionsBySections(with options: [Row.Cell.Value]) -> [(String, [Row.Cell.Value])]? {
guard let sectionKeyForValue = sectionKeyForValue else { return nil }
let sections = options.reduce([:]) { (reduced, option) -> [String: [Row.Cell.Value]] in
var reduced = reduced
let key = sectionKeyForValue(option)
reduced[key] = (reduced[key] ?? []) + [option]
return reduced
}
return sections.sorted(by: { (lhs, rhs) in lhs.0 < rhs.0 })
}
func section(with options: [Row.Cell.Value], header: String?, footer: String?) -> SelectableSection<Row> {
let section = SelectableSection<Row>(header: header, footer: footer, selectionType: .singleSelection(enableDeselection: enableDeselection)) { section in
section.onSelectSelectableRow = { [weak self] _, row in
let changed = self?.row.value != row.value
self?.row.value = row.value
if let form = row.section?.form {
for section in form where section !== row.section && section is SelectableSection<Row> {
let section = section as Any as! SelectableSection<Row>
if let selectedRow = section.selectedRow(), selectedRow !== row {
selectedRow.value = nil
selectedRow.updateCell()
}
}
}
if self?.dismissOnSelection == true || (changed && self?.dismissOnChange == true) {
self?.onDismissCallback?(self!)
}
}
}
for (index, option) in options.enumerated() {
section <<< Row.init(String(index)) { lrow in
lrow.title = self.row.displayValueFor?(option)
lrow.selectableValue = option
lrow.value = self.row.value == option ? option : nil
self.selectableRowSetup?(lrow)
}.cellSetup { [weak self] cell, row in
self?.selectableRowCellSetup?(cell, row)
}.cellUpdate { [weak self] cell, row in
self?.selectableRowCellUpdate?(cell, row)
}
}
return section
}
}
/// Selector Controller (used to select one option among a list)
open class SelectorViewController<OptionsRow: OptionsProviderRow>: _SelectorViewController<ListCheckRow<OptionsRow.OptionsProviderType.Option>, OptionsRow> {
}