BaseRow.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. // BaseRow.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. open class BaseRow: BaseRowType {
  27. var callbackOnChange: (() -> Void)?
  28. var callbackCellUpdate: (() -> Void)?
  29. var callbackCellSetup: Any?
  30. var callbackCellOnSelection: (() -> Void)?
  31. var callbackOnExpandInlineRow: Any?
  32. var callbackOnCollapseInlineRow: Any?
  33. var callbackOnCellHighlightChanged: (() -> Void)?
  34. var callbackOnRowValidationChanged: (() -> Void)?
  35. var _inlineRow: BaseRow?
  36. var _cachedOptionsData: Any?
  37. public var validationOptions: ValidationOptions = .validatesOnBlur
  38. // validation state
  39. public internal(set) var validationErrors = [ValidationError]() {
  40. didSet {
  41. guard validationErrors != oldValue else { return }
  42. RowDefaults.onRowValidationChanged["\(type(of: self))"]?(baseCell, self)
  43. callbackOnRowValidationChanged?()
  44. updateCell()
  45. }
  46. }
  47. public internal(set) var wasBlurred = false
  48. public internal(set) var wasChanged = false
  49. public var isValid: Bool { return validationErrors.isEmpty }
  50. public var isHighlighted: Bool = false
  51. /// The title will be displayed in the textLabel of the row.
  52. public var title: String?
  53. /// Parameter used when creating the cell for this row.
  54. public var cellStyle = UITableViewCell.CellStyle.value1
  55. /// String that uniquely identifies a row. Must be unique among rows and sections.
  56. public var tag: String?
  57. /// The untyped cell associated to this row.
  58. public var baseCell: BaseCell! { return nil }
  59. /// The untyped value of this row.
  60. public var baseValue: Any? {
  61. set {}
  62. get { return nil }
  63. }
  64. public func validate(quietly: Bool = false) -> [ValidationError] {
  65. return []
  66. }
  67. public static var estimatedRowHeight: CGFloat = 44.0
  68. /// Condition that determines if the row should be disabled or not.
  69. public var disabled: Condition? {
  70. willSet { removeFromDisabledRowObservers() }
  71. didSet { addToDisabledRowObservers() }
  72. }
  73. /// Condition that determines if the row should be hidden or not.
  74. public var hidden: Condition? {
  75. willSet { removeFromHiddenRowObservers() }
  76. didSet { addToHiddenRowObservers() }
  77. }
  78. /// Returns if this row is currently disabled or not
  79. public var isDisabled: Bool { return disabledCache }
  80. /// Returns if this row is currently hidden or not
  81. public var isHidden: Bool { return hiddenCache }
  82. /// The section to which this row belongs.
  83. open weak var section: Section?
  84. public lazy var trailingSwipe = {[unowned self] in SwipeConfiguration(self)}()
  85. //needs the accessor because if marked directly this throws "Stored properties cannot be marked potentially unavailable with '@available'"
  86. private lazy var _leadingSwipe = {[unowned self] in SwipeConfiguration(self)}()
  87. @available(iOS 11,*)
  88. public var leadingSwipe: SwipeConfiguration{
  89. get { return self._leadingSwipe }
  90. set { self._leadingSwipe = newValue }
  91. }
  92. public required init(tag: String? = nil) {
  93. self.tag = tag
  94. }
  95. /**
  96. Method that reloads the cell
  97. */
  98. open func updateCell() {}
  99. /**
  100. Method called when the cell belonging to this row was selected. Must call the corresponding method in its cell.
  101. */
  102. open func didSelect() {}
  103. open func prepare(for segue: UIStoryboardSegue) {}
  104. /**
  105. Helps to pick destination part of the cell after scrolling
  106. */
  107. open var destinationScrollPosition: UITableView.ScrollPosition? = UITableView.ScrollPosition.bottom
  108. /**
  109. Returns the IndexPath where this row is in the current form.
  110. */
  111. public final var indexPath: IndexPath? {
  112. guard let sectionIndex = section?.index, let rowIndex = section?.firstIndex(of: self) else { return nil }
  113. return IndexPath(row: rowIndex, section: sectionIndex)
  114. }
  115. var hiddenCache = false
  116. var disabledCache = false {
  117. willSet {
  118. if newValue && !disabledCache {
  119. baseCell.cellResignFirstResponder()
  120. }
  121. }
  122. }
  123. }
  124. extension BaseRow {
  125. // Reset validation
  126. public func cleanValidationErrors(){
  127. validationErrors = []
  128. }
  129. }
  130. extension BaseRow {
  131. /**
  132. Evaluates if the row should be hidden or not and updates the form accordingly
  133. */
  134. public final func evaluateHidden() {
  135. guard let h = hidden, let form = section?.form else { return }
  136. switch h {
  137. case .function(_, let callback):
  138. hiddenCache = callback(form)
  139. case .predicate(let predicate):
  140. hiddenCache = predicate.evaluate(with: self, substitutionVariables: form.dictionaryValuesToEvaluatePredicate())
  141. }
  142. if hiddenCache {
  143. section?.hide(row: self)
  144. } else {
  145. section?.show(row: self)
  146. }
  147. }
  148. /**
  149. Evaluates if the row should be disabled or not and updates it accordingly
  150. */
  151. public final func evaluateDisabled() {
  152. guard let d = disabled, let form = section?.form else { return }
  153. switch d {
  154. case .function(_, let callback):
  155. disabledCache = callback(form)
  156. case .predicate(let predicate):
  157. disabledCache = predicate.evaluate(with: self, substitutionVariables: form.dictionaryValuesToEvaluatePredicate())
  158. }
  159. updateCell()
  160. }
  161. final func wasAddedTo(section: Section) {
  162. self.section = section
  163. if let t = tag {
  164. assert(section.form?.rowsByTag[t] == nil, "Duplicate tag \(t)")
  165. self.section?.form?.rowsByTag[t] = self
  166. self.section?.form?.tagToValues[t] = baseValue != nil ? baseValue! : NSNull()
  167. }
  168. addToRowObservers()
  169. evaluateHidden()
  170. evaluateDisabled()
  171. }
  172. final func addToHiddenRowObservers() {
  173. guard let h = hidden else { return }
  174. switch h {
  175. case .function(let tags, _):
  176. section?.form?.addRowObservers(to: self, rowTags: tags, type: .hidden)
  177. case .predicate(let predicate):
  178. section?.form?.addRowObservers(to: self, rowTags: predicate.predicateVars, type: .hidden)
  179. }
  180. }
  181. final func addToDisabledRowObservers() {
  182. guard let d = disabled else { return }
  183. switch d {
  184. case .function(let tags, _):
  185. section?.form?.addRowObservers(to: self, rowTags: tags, type: .disabled)
  186. case .predicate(let predicate):
  187. section?.form?.addRowObservers(to: self, rowTags: predicate.predicateVars, type: .disabled)
  188. }
  189. }
  190. final func addToRowObservers() {
  191. addToHiddenRowObservers()
  192. addToDisabledRowObservers()
  193. }
  194. final func willBeRemovedFromForm() {
  195. (self as? BaseInlineRowType)?.collapseInlineRow()
  196. if let t = tag {
  197. section?.form?.rowsByTag[t] = nil
  198. section?.form?.tagToValues[t] = nil
  199. }
  200. removeFromRowObservers()
  201. }
  202. final func willBeRemovedFromSection() {
  203. willBeRemovedFromForm()
  204. section = nil
  205. }
  206. final func removeFromHiddenRowObservers() {
  207. guard let h = hidden else { return }
  208. switch h {
  209. case .function(let tags, _):
  210. section?.form?.removeRowObservers(from: self, rowTags: tags, type: .hidden)
  211. case .predicate(let predicate):
  212. section?.form?.removeRowObservers(from: self, rowTags: predicate.predicateVars, type: .hidden)
  213. }
  214. }
  215. final func removeFromDisabledRowObservers() {
  216. guard let d = disabled else { return }
  217. switch d {
  218. case .function(let tags, _):
  219. section?.form?.removeRowObservers(from: self, rowTags: tags, type: .disabled)
  220. case .predicate(let predicate):
  221. section?.form?.removeRowObservers(from: self, rowTags: predicate.predicateVars, type: .disabled)
  222. }
  223. }
  224. final func removeFromRowObservers() {
  225. removeFromHiddenRowObservers()
  226. removeFromDisabledRowObservers()
  227. }
  228. }
  229. extension BaseRow: Equatable, Hidable, Disableable {}
  230. extension BaseRow {
  231. public func reload(with rowAnimation: UITableView.RowAnimation = .none) {
  232. guard let tableView = baseCell?.formViewController()?.tableView ?? (section?.form?.delegate as? FormViewController)?.tableView, let indexPath = indexPath else { return }
  233. tableView.reloadRows(at: [indexPath], with: rowAnimation)
  234. }
  235. public func deselect(animated: Bool = true) {
  236. guard let indexPath = indexPath,
  237. let tableView = baseCell?.formViewController()?.tableView ?? (section?.form?.delegate as? FormViewController)?.tableView else { return }
  238. tableView.deselectRow(at: indexPath, animated: animated)
  239. }
  240. public func select(animated: Bool = false, scrollPosition: UITableView.ScrollPosition = .none) {
  241. guard let indexPath = indexPath,
  242. let tableView = baseCell?.formViewController()?.tableView ?? (section?.form?.delegate as? FormViewController)?.tableView else { return }
  243. tableView.selectRow(at: indexPath, animated: animated, scrollPosition: scrollPosition)
  244. }
  245. }
  246. public func == (lhs: BaseRow, rhs: BaseRow) -> Bool {
  247. return lhs === rhs
  248. }