BaseRow.swift 10 KB

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