Section.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. // Section.swift
  2. // Eureka ( https://github.com/xmartlabs/Eureka )
  3. //
  4. // Copyright (c) 2016 Xmartlabs ( http://xmartlabs.com )
  5. //
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. import Foundation
  25. import UIKit
  26. /// The delegate of the Eureka sections.
  27. public protocol SectionDelegate: class {
  28. func rowsHaveBeenAdded(_ rows: [BaseRow], at: IndexSet)
  29. func rowsHaveBeenRemoved(_ rows: [BaseRow], at: IndexSet)
  30. func rowsHaveBeenReplaced(oldRows: [BaseRow], newRows: [BaseRow], at: IndexSet)
  31. }
  32. // MARK: Section
  33. extension Section : Equatable {}
  34. public func == (lhs: Section, rhs: Section) -> Bool {
  35. return lhs === rhs
  36. }
  37. extension Section : Hidable, SectionDelegate {}
  38. extension Section {
  39. public func reload(with rowAnimation: UITableView.RowAnimation = .none) {
  40. guard let tableView = (form?.delegate as? FormViewController)?.tableView, let index = index, index < tableView.numberOfSections else { return }
  41. tableView.reloadSections(IndexSet(integer: index), with: rowAnimation)
  42. }
  43. }
  44. extension Section {
  45. internal class KVOWrapper: NSObject {
  46. @objc dynamic private var _rows = NSMutableArray()
  47. var rows: NSMutableArray {
  48. return mutableArrayValue(forKey: "_rows")
  49. }
  50. var _allRows = [BaseRow]()
  51. private weak var section: Section?
  52. init(section: Section) {
  53. self.section = section
  54. super.init()
  55. addObserver(self, forKeyPath: "_rows", options: [.new, .old], context:nil)
  56. }
  57. deinit {
  58. removeObserver(self, forKeyPath: "_rows")
  59. _rows.removeAllObjects()
  60. _allRows.removeAll()
  61. }
  62. func removeAllRows() {
  63. _rows = []
  64. _allRows.removeAll()
  65. }
  66. public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  67. let newRows = change![NSKeyValueChangeKey.newKey] as? [BaseRow] ?? []
  68. let oldRows = change![NSKeyValueChangeKey.oldKey] as? [BaseRow] ?? []
  69. guard let keyPathValue = keyPath, let changeType = change?[NSKeyValueChangeKey.kindKey] else { return }
  70. let delegateValue = section?.form?.delegate
  71. guard keyPathValue == "_rows" else { return }
  72. switch (changeType as! NSNumber).uintValue {
  73. case NSKeyValueChange.setting.rawValue:
  74. if newRows.count == 0 {
  75. let indexSet = IndexSet(integersIn: 0..<oldRows.count)
  76. section?.rowsHaveBeenRemoved(oldRows, at: indexSet)
  77. if let _index = section?.index {
  78. delegateValue?.rowsHaveBeenRemoved(oldRows, at: (0..<oldRows.count).map { IndexPath(row: $0, section: _index) })
  79. }
  80. } else {
  81. let indexSet = IndexSet(integersIn: 0..<newRows.count)
  82. section?.rowsHaveBeenAdded(newRows, at: indexSet)
  83. if let _index = section?.index {
  84. delegateValue?.rowsHaveBeenAdded(newRows, at: indexSet.map { IndexPath(row: $0, section: _index) })
  85. }
  86. }
  87. case NSKeyValueChange.insertion.rawValue:
  88. let indexSet = change![NSKeyValueChangeKey.indexesKey] as! IndexSet
  89. section?.rowsHaveBeenAdded(newRows, at: indexSet)
  90. if let _index = section?.index {
  91. delegateValue?.rowsHaveBeenAdded(newRows, at: indexSet.map { IndexPath(row: $0, section: _index ) })
  92. }
  93. case NSKeyValueChange.removal.rawValue:
  94. let indexSet = change![NSKeyValueChangeKey.indexesKey] as! IndexSet
  95. section?.rowsHaveBeenRemoved(oldRows, at: indexSet)
  96. if let _index = section?.index {
  97. delegateValue?.rowsHaveBeenRemoved(oldRows, at: indexSet.map { IndexPath(row: $0, section: _index ) })
  98. }
  99. case NSKeyValueChange.replacement.rawValue:
  100. let indexSet = change![NSKeyValueChangeKey.indexesKey] as! IndexSet
  101. section?.rowsHaveBeenReplaced(oldRows: oldRows, newRows: newRows, at: indexSet)
  102. if let _index = section?.index {
  103. delegateValue?.rowsHaveBeenReplaced(oldRows: oldRows, newRows: newRows, at: indexSet.map { IndexPath(row: $0, section: _index)})
  104. }
  105. default:
  106. assertionFailure()
  107. }
  108. }
  109. }
  110. /**
  111. * If this section contains a row (hidden or not) with the passed parameter as tag then that row will be returned.
  112. * If not, it returns nil.
  113. */
  114. public func rowBy<Row: RowType>(tag: String) -> Row? {
  115. guard let index = kvoWrapper._allRows.firstIndex(where: { $0.tag == tag }) else { return nil }
  116. return kvoWrapper._allRows[index] as? Row
  117. }
  118. }
  119. /// The class representing the sections in a Eureka form.
  120. open class Section {
  121. /// The tag is used to uniquely identify a Section. Must be unique among sections and rows.
  122. public var tag: String?
  123. /// The form that contains this section
  124. public internal(set) weak var form: Form?
  125. /// The header of this section.
  126. public var header: HeaderFooterViewRepresentable? {
  127. willSet {
  128. headerView = nil
  129. }
  130. }
  131. /// The footer of this section
  132. public var footer: HeaderFooterViewRepresentable? {
  133. willSet {
  134. footerView = nil
  135. }
  136. }
  137. /// Index of this section in the form it belongs to.
  138. public var index: Int? { return form?.firstIndex(of: self) }
  139. /// Condition that determines if the section should be hidden or not.
  140. public var hidden: Condition? {
  141. willSet { removeFromRowObservers() }
  142. didSet { addToRowObservers() }
  143. }
  144. /// Returns if the section is currently hidden or not.
  145. public var isHidden: Bool { return hiddenCache }
  146. /// Returns all the rows in this section, including hidden rows.
  147. public var allRows: [BaseRow] {
  148. return kvoWrapper._allRows
  149. }
  150. public required init() {}
  151. #if swift(>=4.1)
  152. public required init<S>(_ elements: S) where S: Sequence, S.Element == BaseRow {
  153. self.append(contentsOf: elements)
  154. }
  155. #endif
  156. public init(_ initializer: @escaping (Section) -> Void) {
  157. initializer(self)
  158. }
  159. public init(_ header: String?, _ initializer: @escaping (Section) -> Void = { _ in }) {
  160. if let header = header {
  161. self.header = HeaderFooterView(stringLiteral: header)
  162. }
  163. initializer(self)
  164. }
  165. public init(header: String?, footer: String?, _ initializer: (Section) -> Void = { _ in }) {
  166. if let header = header {
  167. self.header = HeaderFooterView(stringLiteral: header)
  168. }
  169. if let footer = footer {
  170. self.footer = HeaderFooterView(stringLiteral: footer)
  171. }
  172. initializer(self)
  173. }
  174. public init(footer: String?, _ initializer: (Section) -> Void = { _ in }) {
  175. if let footer = footer {
  176. self.footer = HeaderFooterView(stringLiteral: footer)
  177. }
  178. initializer(self)
  179. }
  180. // MARK: SectionDelegate
  181. /**
  182. * Delegate method called by the framework when one or more rows have been added to the section.
  183. */
  184. open func rowsHaveBeenAdded(_ rows: [BaseRow], at: IndexSet) {}
  185. /**
  186. * Delegate method called by the framework when one or more rows have been removed from the section.
  187. */
  188. open func rowsHaveBeenRemoved(_ rows: [BaseRow], at: IndexSet) {}
  189. /**
  190. * Delegate method called by the framework when one or more rows have been replaced in the section.
  191. */
  192. open func rowsHaveBeenReplaced(oldRows: [BaseRow], newRows: [BaseRow], at: IndexSet) {}
  193. // MARK: Private
  194. lazy var kvoWrapper: KVOWrapper = { [unowned self] in return KVOWrapper(section: self) }()
  195. var headerView: UIView?
  196. var footerView: UIView?
  197. var hiddenCache = false
  198. }
  199. extension Section: MutableCollection, BidirectionalCollection {
  200. // MARK: MutableCollectionType
  201. public var startIndex: Int { return 0 }
  202. public var endIndex: Int { return kvoWrapper.rows.count }
  203. public subscript (position: Int) -> BaseRow {
  204. get {
  205. if position >= kvoWrapper.rows.count {
  206. assertionFailure("Section: Index out of bounds")
  207. }
  208. return kvoWrapper.rows[position] as! BaseRow
  209. }
  210. set {
  211. if position > kvoWrapper.rows.count {
  212. assertionFailure("Section: Index out of bounds")
  213. }
  214. if position < kvoWrapper.rows.count {
  215. let oldRow = kvoWrapper.rows[position]
  216. let oldRowIndex = kvoWrapper._allRows.firstIndex(of: oldRow as! BaseRow)!
  217. // Remove the previous row from the form
  218. kvoWrapper._allRows[oldRowIndex].willBeRemovedFromSection()
  219. kvoWrapper._allRows[oldRowIndex] = newValue
  220. } else {
  221. kvoWrapper._allRows.append(newValue)
  222. }
  223. kvoWrapper.rows[position] = newValue
  224. newValue.wasAddedTo(section: self)
  225. }
  226. }
  227. public subscript (range: Range<Int>) -> ArraySlice<BaseRow> {
  228. get { return kvoWrapper.rows.map { $0 as! BaseRow }[range] }
  229. set { replaceSubrange(range, with: newValue) }
  230. }
  231. public func index(after i: Int) -> Int { return i + 1 }
  232. public func index(before i: Int) -> Int { return i - 1 }
  233. }
  234. extension Section: RangeReplaceableCollection {
  235. // MARK: RangeReplaceableCollectionType
  236. public func append(_ formRow: BaseRow) {
  237. kvoWrapper.rows.insert(formRow, at: kvoWrapper.rows.count)
  238. kvoWrapper._allRows.append(formRow)
  239. formRow.wasAddedTo(section: self)
  240. }
  241. public func append<S: Sequence>(contentsOf newElements: S) where S.Iterator.Element == BaseRow {
  242. kvoWrapper.rows.addObjects(from: newElements.map { $0 })
  243. kvoWrapper._allRows.append(contentsOf: newElements)
  244. for row in newElements {
  245. row.wasAddedTo(section: self)
  246. }
  247. }
  248. public func replaceSubrange<C>(_ subrange: Range<Int>, with newElements: C) where C : Collection, C.Element == BaseRow {
  249. for i in subrange.lowerBound..<subrange.upperBound {
  250. if let row = kvoWrapper.rows.object(at: i) as? BaseRow {
  251. row.willBeRemovedFromSection()
  252. kvoWrapper._allRows.remove(at: kvoWrapper._allRows.firstIndex(of: row)!)
  253. }
  254. }
  255. kvoWrapper.rows.replaceObjects(in: NSRange(location: subrange.lowerBound, length: subrange.upperBound - subrange.lowerBound),
  256. withObjectsFrom: newElements.map { $0 })
  257. kvoWrapper._allRows.insert(contentsOf: newElements, at: indexForInsertion(at: subrange.lowerBound))
  258. for row in newElements {
  259. row.wasAddedTo(section: self)
  260. }
  261. }
  262. public func removeAll(keepingCapacity keepCapacity: Bool = false) {
  263. // not doing anything with capacity
  264. let rows = kvoWrapper._allRows
  265. kvoWrapper.removeAllRows()
  266. for row in rows {
  267. row.willBeRemovedFromSection()
  268. }
  269. }
  270. @discardableResult
  271. public func remove(at position: Int) -> BaseRow {
  272. let row = kvoWrapper.rows.object(at: position) as! BaseRow
  273. row.willBeRemovedFromSection()
  274. kvoWrapper.rows.removeObject(at: position)
  275. if let index = kvoWrapper._allRows.firstIndex(of: row) {
  276. kvoWrapper._allRows.remove(at: index)
  277. }
  278. return row
  279. }
  280. private func indexForInsertion(at index: Int) -> Int {
  281. guard index != 0 else { return 0 }
  282. let row = kvoWrapper.rows[index-1]
  283. if let i = kvoWrapper._allRows.firstIndex(of: row as! BaseRow) {
  284. return i + 1
  285. }
  286. return kvoWrapper._allRows.count
  287. }
  288. }
  289. extension Section /* Condition */ {
  290. // MARK: Hidden/Disable Engine
  291. /**
  292. Function that evaluates if the section should be hidden and updates it accordingly.
  293. */
  294. public final func evaluateHidden() {
  295. if let h = hidden, let f = form {
  296. switch h {
  297. case .function(_, let callback):
  298. hiddenCache = callback(f)
  299. case .predicate(let predicate):
  300. hiddenCache = predicate.evaluate(with: self, substitutionVariables: f.dictionaryValuesToEvaluatePredicate())
  301. }
  302. if hiddenCache {
  303. form?.hideSection(self)
  304. } else {
  305. form?.showSection(self)
  306. }
  307. }
  308. }
  309. /**
  310. Internal function called when this section was added to a form.
  311. */
  312. func wasAddedTo(form: Form) {
  313. self.form = form
  314. addToRowObservers()
  315. evaluateHidden()
  316. for row in kvoWrapper._allRows {
  317. row.wasAddedTo(section: self)
  318. }
  319. }
  320. /**
  321. Internal function called to add this section to the observers of certain rows. Called when the hidden variable is set and depends on other rows.
  322. */
  323. func addToRowObservers() {
  324. guard let h = hidden else { return }
  325. switch h {
  326. case .function(let tags, _):
  327. form?.addRowObservers(to: self, rowTags: tags, type: .hidden)
  328. case .predicate(let predicate):
  329. form?.addRowObservers(to: self, rowTags: predicate.predicateVars, type: .hidden)
  330. }
  331. }
  332. /**
  333. Internal function called when this section was removed from a form.
  334. */
  335. func willBeRemovedFromForm() {
  336. for row in kvoWrapper._allRows {
  337. row.willBeRemovedFromForm()
  338. }
  339. removeFromRowObservers()
  340. self.form = nil
  341. }
  342. /**
  343. Internal function called to remove this section from the observers of certain rows. Called when the hidden variable is changed.
  344. */
  345. func removeFromRowObservers() {
  346. guard let h = hidden else { return }
  347. switch h {
  348. case .function(let tags, _):
  349. form?.removeRowObservers(from: self, rowTags: tags, type: .hidden)
  350. case .predicate(let predicate):
  351. form?.removeRowObservers(from: self, rowTags: predicate.predicateVars, type: .hidden)
  352. }
  353. }
  354. func hide(row: BaseRow) {
  355. row.baseCell.cellResignFirstResponder()
  356. (row as? BaseInlineRowType)?.collapseInlineRow()
  357. kvoWrapper.rows.remove(row)
  358. }
  359. func show(row: BaseRow) {
  360. guard !kvoWrapper.rows.contains(row) else { return }
  361. guard var index = kvoWrapper._allRows.firstIndex(of: row) else { return }
  362. var formIndex = NSNotFound
  363. while formIndex == NSNotFound && index > 0 {
  364. index = index - 1
  365. let previous = kvoWrapper._allRows[index]
  366. formIndex = kvoWrapper.rows.index(of: previous)
  367. }
  368. kvoWrapper.rows.insert(row, at: formIndex == NSNotFound ? 0 : formIndex + 1)
  369. }
  370. }
  371. extension Section /* Helpers */ {
  372. /**
  373. * This method inserts a row after another row.
  374. * It is useful if you want to insert a row after a row that is currently hidden. Otherwise use `insert(at: Int)`.
  375. * It throws an error if the old row is not in this section.
  376. */
  377. public func insert(row newRow: BaseRow, after previousRow: BaseRow) throws {
  378. guard let rowIndex = (kvoWrapper._allRows as [BaseRow]).firstIndex(of: previousRow) else {
  379. throw EurekaError.rowNotInSection(row: previousRow)
  380. }
  381. kvoWrapper._allRows.insert(newRow, at: index(after: rowIndex))
  382. show(row: newRow)
  383. newRow.wasAddedTo(section: self)
  384. }
  385. }
  386. /**
  387. * Navigation options for a form view controller.
  388. */
  389. public struct MultivaluedOptions: OptionSet {
  390. private enum Options: Int {
  391. case none = 0, insert = 1, delete = 2, reorder = 4
  392. }
  393. public let rawValue: Int
  394. public init(rawValue: Int) { self.rawValue = rawValue}
  395. private init(_ options: Options) { self.rawValue = options.rawValue }
  396. /// No multivalued.
  397. public static let None = MultivaluedOptions(.none)
  398. /// Allows user to insert rows.
  399. public static let Insert = MultivaluedOptions(.insert)
  400. /// Allows user to delete rows.
  401. public static let Delete = MultivaluedOptions(.delete)
  402. /// Allows user to reorder rows
  403. public static let Reorder = MultivaluedOptions(.reorder)
  404. }
  405. /// Base class for multivalued sections. Use one of the subclasses.
  406. open class BaseMultivaluedSection: Section {
  407. public var multivaluedOptions: MultivaluedOptions
  408. public var showInsertIconInAddButton = true
  409. public var multivaluedRowToInsertAt: ((Int) -> BaseRow)?
  410. public required init(multivaluedOptions: MultivaluedOptions = MultivaluedOptions.Insert.union(.Delete),
  411. header: String? = nil,
  412. footer: String? = nil,
  413. _ initializer: (BaseMultivaluedSection) -> Void = { _ in }) {
  414. self.multivaluedOptions = multivaluedOptions
  415. super.init(header: header, footer: footer, {section in initializer(section as! BaseMultivaluedSection) })
  416. guard multivaluedOptions.contains(.Insert) else { return }
  417. initialize()
  418. }
  419. public required init() {
  420. self.multivaluedOptions = MultivaluedOptions.Insert.union(.Delete)
  421. super.init()
  422. initialize()
  423. }
  424. #if swift(>=4.1)
  425. public required init<S>(_ elements: S) where S : Sequence, S.Element == BaseRow {
  426. self.multivaluedOptions = MultivaluedOptions.Insert.union(.Delete)
  427. super.init(elements)
  428. initialize()
  429. }
  430. #endif
  431. func initialize() {
  432. // Overridden by subclasses
  433. }
  434. /**
  435. Method used to get all the values of the section.
  436. - returns: An Array mapping the row values. [value]
  437. */
  438. public func values() -> [Any?] {
  439. return kvoWrapper._allRows.filter({ $0.baseValue != nil }).map({ $0.baseValue })
  440. }
  441. }
  442. /// Generic multivalued section. Pass the type of the add button row as generic parameter.
  443. open class GenericMultivaluedSection<AddButtonType: RowType>: BaseMultivaluedSection where AddButtonType: BaseRow {
  444. public var addButtonProvider: ((GenericMultivaluedSection<AddButtonType>) -> AddButtonType)!
  445. public required init(multivaluedOptions: MultivaluedOptions = MultivaluedOptions.Insert.union(.Delete),
  446. header: String? = nil,
  447. footer: String? = nil,
  448. _ initializer: (GenericMultivaluedSection<AddButtonType>) -> Void = { _ in }) {
  449. super.init(multivaluedOptions: multivaluedOptions, header: header, footer: footer, {section in initializer(section as! GenericMultivaluedSection<AddButtonType>) })
  450. }
  451. public required init() {
  452. super.init()
  453. }
  454. #if swift(>=4.1)
  455. public required init<S>(_ elements: S) where S : Sequence, S.Element == BaseRow {
  456. super.init(elements)
  457. }
  458. #endif
  459. override func initialize() {
  460. let addRow = addButtonProvider(self)
  461. addRow.onCellSelection { cell, row in
  462. guard !row.isDisabled else { return }
  463. guard let tableView = cell.formViewController()?.tableView, let indexPath = row.indexPath else { return }
  464. cell.formViewController()?.tableView(tableView, commit: .insert, forRowAt: indexPath)
  465. }
  466. self <<< addRow
  467. }
  468. }
  469. /**
  470. * Multivalued sections allows us to easily create insertable, deletable and reorderable sections. By using a multivalued section we can add multiple values for a certain field, such as telephone numbers in a contact.
  471. */
  472. open class MultivaluedSection: GenericMultivaluedSection<ButtonRow> {
  473. override func initialize() {
  474. if addButtonProvider == nil {
  475. addButtonProvider = { _ in
  476. return ButtonRow {
  477. $0.title = "Add"
  478. $0.cellStyle = .value1
  479. }.cellUpdate { cell, _ in
  480. cell.textLabel?.textAlignment = .left
  481. }
  482. }
  483. }
  484. super.initialize()
  485. }
  486. }