Core.swift 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
  1. // Core.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. // MARK: Row
  27. internal class RowDefaults {
  28. static var cellUpdate = [String: (BaseCell, BaseRow) -> Void]()
  29. static var cellSetup = [String: (BaseCell, BaseRow) -> Void]()
  30. static var onCellHighlightChanged = [String: (BaseCell, BaseRow) -> Void]()
  31. static var rowInitialization = [String: (BaseRow) -> Void]()
  32. static var onRowValidationChanged = [String: (BaseCell, BaseRow) -> Void]()
  33. static var rawCellUpdate = [String: Any]()
  34. static var rawCellSetup = [String: Any]()
  35. static var rawOnCellHighlightChanged = [String: Any]()
  36. static var rawRowInitialization = [String: Any]()
  37. static var rawOnRowValidationChanged = [String: Any]()
  38. }
  39. // MARK: FormCells
  40. public struct CellProvider<Cell: BaseCell> where Cell: CellType {
  41. /// Nibname of the cell that will be created.
  42. public private (set) var nibName: String?
  43. /// Bundle from which to get the nib file.
  44. public private (set) var bundle: Bundle!
  45. public init() {}
  46. public init(nibName: String, bundle: Bundle? = nil) {
  47. self.nibName = nibName
  48. self.bundle = bundle ?? Bundle(for: Cell.self)
  49. }
  50. /**
  51. Creates the cell with the specified style.
  52. - parameter cellStyle: The style with which the cell will be created.
  53. - returns: the cell
  54. */
  55. func makeCell(style: UITableViewCell.CellStyle) -> Cell {
  56. if let nibName = self.nibName {
  57. return bundle.loadNibNamed(nibName, owner: nil, options: nil)!.first as! Cell
  58. }
  59. return Cell.init(style: style, reuseIdentifier: nil)
  60. }
  61. }
  62. /**
  63. Enumeration that defines how a controller should be created.
  64. - Callback->VCType: Creates the controller inside the specified block
  65. - NibFile: Loads a controller from a nib file in some bundle
  66. - StoryBoard: Loads the controller from a Storyboard by its storyboard id
  67. */
  68. public enum ControllerProvider<VCType: UIViewController> {
  69. /**
  70. * Creates the controller inside the specified block
  71. */
  72. case callback(builder: (() -> VCType))
  73. /**
  74. * Loads a controller from a nib file in some bundle
  75. */
  76. case nibFile(name: String, bundle: Bundle?)
  77. /**
  78. * Loads the controller from a Storyboard by its storyboard id
  79. */
  80. case storyBoard(storyboardId: String, storyboardName: String, bundle: Bundle?)
  81. func makeController() -> VCType {
  82. switch self {
  83. case .callback(let builder):
  84. return builder()
  85. case .nibFile(let nibName, let bundle):
  86. return VCType.init(nibName: nibName, bundle:bundle ?? Bundle(for: VCType.self))
  87. case .storyBoard(let storyboardId, let storyboardName, let bundle):
  88. let sb = UIStoryboard(name: storyboardName, bundle: bundle ?? Bundle(for: VCType.self))
  89. return sb.instantiateViewController(withIdentifier: storyboardId) as! VCType
  90. }
  91. }
  92. }
  93. /**
  94. Defines how a controller should be presented.
  95. - Show?: Shows the controller with `showViewController(...)`.
  96. - PresentModally?: Presents the controller modally.
  97. - SegueName?: Performs the segue with the specified identifier (name).
  98. - SegueClass?: Performs a segue from a segue class.
  99. */
  100. public enum PresentationMode<VCType: UIViewController> {
  101. /**
  102. * Shows the controller, created by the specified provider, with `showViewController(...)`.
  103. */
  104. case show(controllerProvider: ControllerProvider<VCType>, onDismiss: ((UIViewController) -> Void)?)
  105. /**
  106. * Presents the controller, created by the specified provider, modally.
  107. */
  108. case presentModally(controllerProvider: ControllerProvider<VCType>, onDismiss: ((UIViewController) -> Void)?)
  109. /**
  110. * Performs the segue with the specified identifier (name).
  111. */
  112. case segueName(segueName: String, onDismiss: ((UIViewController) -> Void)?)
  113. /**
  114. * Performs a segue from a segue class.
  115. */
  116. case segueClass(segueClass: UIStoryboardSegue.Type, onDismiss: ((UIViewController) -> Void)?)
  117. case popover(controllerProvider: ControllerProvider<VCType>, onDismiss: ((UIViewController) -> Void)?)
  118. public var onDismissCallback: ((UIViewController) -> Void)? {
  119. switch self {
  120. case .show(_, let completion):
  121. return completion
  122. case .presentModally(_, let completion):
  123. return completion
  124. case .segueName(_, let completion):
  125. return completion
  126. case .segueClass(_, let completion):
  127. return completion
  128. case .popover(_, let completion):
  129. return completion
  130. }
  131. }
  132. /**
  133. Present the view controller provided by PresentationMode. Should only be used from custom row implementation.
  134. - parameter viewController: viewController to present if it makes sense (normally provided by makeController method)
  135. - parameter row: associated row
  136. - parameter presentingViewController: form view controller
  137. */
  138. public func present(_ viewController: VCType!, row: BaseRow, presentingController: FormViewController) {
  139. switch self {
  140. case .show(_, _):
  141. presentingController.show(viewController, sender: row)
  142. case .presentModally(_, _):
  143. presentingController.present(viewController, animated: true)
  144. case .segueName(let segueName, _):
  145. presentingController.performSegue(withIdentifier: segueName, sender: row)
  146. case .segueClass(let segueClass, _):
  147. let segue = segueClass.init(identifier: row.tag, source: presentingController, destination: viewController)
  148. presentingController.prepare(for: segue, sender: row)
  149. segue.perform()
  150. case .popover(_, _):
  151. guard let porpoverController = viewController.popoverPresentationController else {
  152. fatalError()
  153. }
  154. porpoverController.sourceView = porpoverController.sourceView ?? presentingController.tableView
  155. presentingController.present(viewController, animated: true)
  156. }
  157. }
  158. /**
  159. Creates the view controller specified by presentation mode. Should only be used from custom row implementation.
  160. - returns: the created view controller or nil depending on the PresentationMode type.
  161. */
  162. public func makeController() -> VCType? {
  163. switch self {
  164. case .show(let controllerProvider, let completionCallback):
  165. let controller = controllerProvider.makeController()
  166. let completionController = controller as? RowControllerType
  167. if let callback = completionCallback {
  168. completionController?.onDismissCallback = callback
  169. }
  170. return controller
  171. case .presentModally(let controllerProvider, let completionCallback):
  172. let controller = controllerProvider.makeController()
  173. let completionController = controller as? RowControllerType
  174. if let callback = completionCallback {
  175. completionController?.onDismissCallback = callback
  176. }
  177. return controller
  178. case .popover(let controllerProvider, let completionCallback):
  179. let controller = controllerProvider.makeController()
  180. controller.modalPresentationStyle = .popover
  181. let completionController = controller as? RowControllerType
  182. if let callback = completionCallback {
  183. completionController?.onDismissCallback = callback
  184. }
  185. return controller
  186. default:
  187. return nil
  188. }
  189. }
  190. }
  191. /**
  192. * Protocol to be implemented by custom formatters.
  193. */
  194. public protocol FormatterProtocol {
  195. func getNewPosition(forPosition: UITextPosition, inTextInput textInput: UITextInput, oldValue: String?, newValue: String?) -> UITextPosition
  196. }
  197. // MARK: Predicate Machine
  198. enum ConditionType {
  199. case hidden, disabled
  200. }
  201. /**
  202. Enumeration that are used to specify the disbaled and hidden conditions of rows
  203. - Function: A function that calculates the result
  204. - Predicate: A predicate that returns the result
  205. */
  206. public enum Condition {
  207. /**
  208. * Calculate the condition inside a block
  209. *
  210. * @param Array of tags of the rows this function depends on
  211. * @param Form->Bool The block that calculates the result
  212. *
  213. * @return If the condition is true or false
  214. */
  215. case function([String], (Form)->Bool)
  216. /**
  217. * Calculate the condition using a NSPredicate
  218. *
  219. * @param NSPredicate The predicate that will be evaluated
  220. *
  221. * @return If the condition is true or false
  222. */
  223. case predicate(NSPredicate)
  224. }
  225. extension Condition : ExpressibleByBooleanLiteral {
  226. /**
  227. Initialize a condition to return afixed boolean value always
  228. */
  229. public init(booleanLiteral value: Bool) {
  230. self = Condition.function([]) { _ in return value }
  231. }
  232. }
  233. extension Condition : ExpressibleByStringLiteral {
  234. /**
  235. Initialize a Condition with a string that will be converted to a NSPredicate
  236. */
  237. public init(stringLiteral value: String) {
  238. self = .predicate(NSPredicate(format: value))
  239. }
  240. /**
  241. Initialize a Condition with a string that will be converted to a NSPredicate
  242. */
  243. public init(unicodeScalarLiteral value: String) {
  244. self = .predicate(NSPredicate(format: value))
  245. }
  246. /**
  247. Initialize a Condition with a string that will be converted to a NSPredicate
  248. */
  249. public init(extendedGraphemeClusterLiteral value: String) {
  250. self = .predicate(NSPredicate(format: value))
  251. }
  252. }
  253. // MARK: Errors
  254. /**
  255. Errors thrown by Eureka
  256. - duplicatedTag: When a section or row is inserted whose tag dows already exist
  257. - rowNotInSection: When a row was expected to be in a Section, but is not.
  258. */
  259. public enum EurekaError: Error {
  260. case duplicatedTag(tag: String)
  261. case rowNotInSection(row: BaseRow)
  262. }
  263. //Mark: FormViewController
  264. /**
  265. * A protocol implemented by FormViewController
  266. */
  267. public protocol FormViewControllerProtocol {
  268. var tableView: UITableView! { get }
  269. func beginEditing<T>(of: Cell<T>)
  270. func endEditing<T>(of: Cell<T>)
  271. func insertAnimation(forRows rows: [BaseRow]) -> UITableView.RowAnimation
  272. func deleteAnimation(forRows rows: [BaseRow]) -> UITableView.RowAnimation
  273. func reloadAnimation(oldRows: [BaseRow], newRows: [BaseRow]) -> UITableView.RowAnimation
  274. func insertAnimation(forSections sections: [Section]) -> UITableView.RowAnimation
  275. func deleteAnimation(forSections sections: [Section]) -> UITableView.RowAnimation
  276. func reloadAnimation(oldSections: [Section], newSections: [Section]) -> UITableView.RowAnimation
  277. }
  278. /**
  279. * Navigation options for a form view controller.
  280. */
  281. public struct RowNavigationOptions: OptionSet {
  282. private enum NavigationOptions: Int {
  283. case disabled = 0, enabled = 1, stopDisabledRow = 2, skipCanNotBecomeFirstResponderRow = 4
  284. }
  285. public let rawValue: Int
  286. public init(rawValue: Int) { self.rawValue = rawValue}
  287. private init(_ options: NavigationOptions ) { self.rawValue = options.rawValue }
  288. /// No navigation.
  289. public static let Disabled = RowNavigationOptions(.disabled)
  290. /// Full navigation.
  291. public static let Enabled = RowNavigationOptions(.enabled)
  292. /// Break navigation when next row is disabled.
  293. public static let StopDisabledRow = RowNavigationOptions(.stopDisabledRow)
  294. /// Break navigation when next row cannot become first responder.
  295. public static let SkipCanNotBecomeFirstResponderRow = RowNavigationOptions(.skipCanNotBecomeFirstResponderRow)
  296. }
  297. /**
  298. * Defines the configuration for the keyboardType of FieldRows.
  299. */
  300. public struct KeyboardReturnTypeConfiguration {
  301. /// Used when the next row is available.
  302. public var nextKeyboardType = UIReturnKeyType.next
  303. /// Used if next row is not available.
  304. public var defaultKeyboardType = UIReturnKeyType.default
  305. public init() {}
  306. public init(nextKeyboardType: UIReturnKeyType, defaultKeyboardType: UIReturnKeyType) {
  307. self.nextKeyboardType = nextKeyboardType
  308. self.defaultKeyboardType = defaultKeyboardType
  309. }
  310. }
  311. /**
  312. * Options that define when an inline row should collapse.
  313. */
  314. public struct InlineRowHideOptions: OptionSet {
  315. private enum _InlineRowHideOptions: Int {
  316. case never = 0, anotherInlineRowIsShown = 1, firstResponderChanges = 2
  317. }
  318. public let rawValue: Int
  319. public init(rawValue: Int) { self.rawValue = rawValue}
  320. private init(_ options: _InlineRowHideOptions ) { self.rawValue = options.rawValue }
  321. /// Never collapse automatically. Only when user taps inline row.
  322. public static let Never = InlineRowHideOptions(.never)
  323. /// Collapse qhen another inline row expands. Just one inline row will be expanded at a time.
  324. public static let AnotherInlineRowIsShown = InlineRowHideOptions(.anotherInlineRowIsShown)
  325. /// Collapse when first responder changes.
  326. public static let FirstResponderChanges = InlineRowHideOptions(.firstResponderChanges)
  327. }
  328. /// View controller that shows a form.
  329. open class FormViewController: UIViewController, FormViewControllerProtocol, FormDelegate {
  330. @IBOutlet public var tableView: UITableView!
  331. private lazy var _form: Form = { [weak self] in
  332. let form = Form()
  333. form.delegate = self
  334. return form
  335. }()
  336. public var form: Form {
  337. get { return _form }
  338. set {
  339. guard form !== newValue else { return }
  340. _form.delegate = nil
  341. tableView?.endEditing(false)
  342. _form = newValue
  343. _form.delegate = self
  344. if isViewLoaded {
  345. tableView?.reloadData()
  346. }
  347. }
  348. }
  349. /// Extra space to leave between between the row in focus and the keyboard
  350. open var rowKeyboardSpacing: CGFloat = 0
  351. /// Enables animated scrolling on row navigation
  352. open var animateScroll = false
  353. /// Accessory view that is responsible for the navigation between rows
  354. private var navigationAccessoryView: (UIView & NavigationAccessory)!
  355. /// Custom Accesory View to be used as a replacement
  356. open var customNavigationAccessoryView: (UIView & NavigationAccessory)? {
  357. return nil
  358. }
  359. /// Defines the behaviour of the navigation between rows
  360. public var navigationOptions: RowNavigationOptions?
  361. private var tableViewStyle: UITableView.Style = .grouped
  362. public init(style: UITableView.Style) {
  363. super.init(nibName: nil, bundle: nil)
  364. tableViewStyle = style
  365. }
  366. public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
  367. super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
  368. }
  369. public required init?(coder aDecoder: NSCoder) {
  370. super.init(coder: aDecoder)
  371. }
  372. open override func viewDidLoad() {
  373. super.viewDidLoad()
  374. navigationAccessoryView = customNavigationAccessoryView ?? NavigationAccessoryView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 44.0))
  375. navigationAccessoryView.autoresizingMask = .flexibleWidth
  376. if tableView == nil {
  377. tableView = UITableView(frame: view.bounds, style: tableViewStyle)
  378. tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  379. tableView.cellLayoutMarginsFollowReadableWidth = false
  380. }
  381. if tableView.superview == nil {
  382. view.addSubview(tableView)
  383. }
  384. if tableView.delegate == nil {
  385. tableView.delegate = self
  386. }
  387. if tableView.dataSource == nil {
  388. tableView.dataSource = self
  389. }
  390. tableView.rowHeight = UITableView.automaticDimension
  391. tableView.estimatedRowHeight = BaseRow.estimatedRowHeight
  392. tableView.allowsSelectionDuringEditing = true
  393. }
  394. open override func viewWillAppear(_ animated: Bool) {
  395. super.viewWillAppear(animated)
  396. animateTableView = true
  397. let selectedIndexPaths = tableView.indexPathsForSelectedRows ?? []
  398. if !selectedIndexPaths.isEmpty, tableView.window != nil {
  399. tableView.reloadRows(at: selectedIndexPaths, with: .none)
  400. }
  401. selectedIndexPaths.forEach {
  402. tableView.selectRow(at: $0, animated: false, scrollPosition: .none)
  403. }
  404. let deselectionAnimation = { [weak self] (context: UIViewControllerTransitionCoordinatorContext) in
  405. selectedIndexPaths.forEach {
  406. self?.tableView.deselectRow(at: $0, animated: context.isAnimated)
  407. }
  408. }
  409. let reselection = { [weak self] (context: UIViewControllerTransitionCoordinatorContext) in
  410. if context.isCancelled {
  411. selectedIndexPaths.forEach {
  412. self?.tableView.selectRow(at: $0, animated: false, scrollPosition: .none)
  413. }
  414. }
  415. }
  416. if let coordinator = transitionCoordinator {
  417. coordinator.animate(alongsideTransition: deselectionAnimation, completion: reselection)
  418. } else {
  419. selectedIndexPaths.forEach {
  420. tableView.deselectRow(at: $0, animated: false)
  421. }
  422. }
  423. NotificationCenter.default.addObserver(self, selector: #selector(FormViewController.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  424. NotificationCenter.default.addObserver(self, selector: #selector(FormViewController.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  425. if form.containsMultivaluedSection && (isBeingPresented || isMovingToParent) {
  426. tableView.setEditing(true, animated: false)
  427. }
  428. }
  429. open override func viewWillDisappear(_ animated: Bool) {
  430. super.viewWillDisappear(animated)
  431. NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
  432. NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
  433. }
  434. open override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  435. super.prepare(for: segue, sender: sender)
  436. let baseRow = sender as? BaseRow
  437. baseRow?.prepare(for: segue)
  438. }
  439. /**
  440. Returns the navigation accessory view if it is enabled. Returns nil otherwise.
  441. */
  442. open func inputAccessoryView(for row: BaseRow) -> UIView? {
  443. let options = navigationOptions ?? Form.defaultNavigationOptions
  444. guard options.contains(.Enabled) else { return nil }
  445. guard row.baseCell.cellCanBecomeFirstResponder() else { return nil}
  446. navigationAccessoryView.previousEnabled = nextRow(for: row, withDirection: .up) != nil
  447. navigationAccessoryView.doneClosure = { [weak self] in
  448. self?.navigationDone()
  449. }
  450. navigationAccessoryView.previousClosure = { [weak self] in
  451. self?.navigationPrevious()
  452. }
  453. navigationAccessoryView.nextClosure = { [weak self] in
  454. self?.navigationNext()
  455. }
  456. navigationAccessoryView.nextEnabled = nextRow(for: row, withDirection: .down) != nil
  457. return navigationAccessoryView
  458. }
  459. // MARK: FormViewControllerProtocol
  460. /**
  461. Called when a cell becomes first responder
  462. */
  463. public final func beginEditing<T>(of cell: Cell<T>) {
  464. cell.row.isHighlighted = true
  465. cell.row.updateCell()
  466. RowDefaults.onCellHighlightChanged["\(type(of: cell.row!))"]?(cell, cell.row)
  467. cell.row.callbackOnCellHighlightChanged?()
  468. guard let _ = tableView, (form.inlineRowHideOptions ?? Form.defaultInlineRowHideOptions).contains(.FirstResponderChanges) else { return }
  469. let row = cell.baseRow
  470. let inlineRow = row?._inlineRow
  471. for row in form.allRows.filter({ $0 !== row && $0 !== inlineRow && $0._inlineRow != nil }) {
  472. if let inlineRow = row as? BaseInlineRowType {
  473. inlineRow.collapseInlineRow()
  474. }
  475. }
  476. }
  477. /**
  478. Called when a cell resigns first responder
  479. */
  480. public final func endEditing<T>(of cell: Cell<T>) {
  481. cell.row.isHighlighted = false
  482. cell.row.wasBlurred = true
  483. RowDefaults.onCellHighlightChanged["\(type(of: cell.row!))"]?(cell, cell.row)
  484. cell.row.callbackOnCellHighlightChanged?()
  485. if cell.row.validationOptions.contains(.validatesOnBlur) || (cell.row.wasChanged && cell.row.validationOptions.contains(.validatesOnChangeAfterBlurred)) {
  486. cell.row.validate()
  487. }
  488. cell.row.updateCell()
  489. }
  490. /**
  491. Returns the animation for the insertion of the given rows.
  492. */
  493. open func insertAnimation(forRows rows: [BaseRow]) -> UITableView.RowAnimation {
  494. return .fade
  495. }
  496. /**
  497. Returns the animation for the deletion of the given rows.
  498. */
  499. open func deleteAnimation(forRows rows: [BaseRow]) -> UITableView.RowAnimation {
  500. return .fade
  501. }
  502. /**
  503. Returns the animation for the reloading of the given rows.
  504. */
  505. open func reloadAnimation(oldRows: [BaseRow], newRows: [BaseRow]) -> UITableView.RowAnimation {
  506. return .automatic
  507. }
  508. /**
  509. Returns the animation for the insertion of the given sections.
  510. */
  511. open func insertAnimation(forSections sections: [Section]) -> UITableView.RowAnimation {
  512. return .automatic
  513. }
  514. /**
  515. Returns the animation for the deletion of the given sections.
  516. */
  517. open func deleteAnimation(forSections sections: [Section]) -> UITableView.RowAnimation {
  518. return .automatic
  519. }
  520. /**
  521. Returns the animation for the reloading of the given sections.
  522. */
  523. open func reloadAnimation(oldSections: [Section], newSections: [Section]) -> UITableView.RowAnimation {
  524. return .automatic
  525. }
  526. // MARK: TextField and TextView Delegate
  527. open func textInputShouldBeginEditing<T>(_ textInput: UITextInput, cell: Cell<T>) -> Bool {
  528. return true
  529. }
  530. open func textInputDidBeginEditing<T>(_ textInput: UITextInput, cell: Cell<T>) {
  531. if let row = cell.row as? KeyboardReturnHandler {
  532. let next = nextRow(for: cell.row, withDirection: .down)
  533. if let textField = textInput as? UITextField {
  534. textField.returnKeyType = next != nil ? (row.keyboardReturnType?.nextKeyboardType ??
  535. (form.keyboardReturnType?.nextKeyboardType ?? Form.defaultKeyboardReturnType.nextKeyboardType )) :
  536. (row.keyboardReturnType?.defaultKeyboardType ?? (form.keyboardReturnType?.defaultKeyboardType ??
  537. Form.defaultKeyboardReturnType.defaultKeyboardType))
  538. } else if let textView = textInput as? UITextView {
  539. textView.returnKeyType = next != nil ? (row.keyboardReturnType?.nextKeyboardType ??
  540. (form.keyboardReturnType?.nextKeyboardType ?? Form.defaultKeyboardReturnType.nextKeyboardType )) :
  541. (row.keyboardReturnType?.defaultKeyboardType ?? (form.keyboardReturnType?.defaultKeyboardType ??
  542. Form.defaultKeyboardReturnType.defaultKeyboardType))
  543. }
  544. }
  545. }
  546. open func textInputShouldEndEditing<T>(_ textInput: UITextInput, cell: Cell<T>) -> Bool {
  547. return true
  548. }
  549. open func textInputDidEndEditing<T>(_ textInput: UITextInput, cell: Cell<T>) {
  550. }
  551. open func textInput<T>(_ textInput: UITextInput, shouldChangeCharactersInRange range: NSRange, replacementString string: String, cell: Cell<T>) -> Bool {
  552. return true
  553. }
  554. open func textInputShouldClear<T>(_ textInput: UITextInput, cell: Cell<T>) -> Bool {
  555. return true
  556. }
  557. open func textInputShouldReturn<T>(_ textInput: UITextInput, cell: Cell<T>) -> Bool {
  558. if let nextRow = nextRow(for: cell.row, withDirection: .down) {
  559. if nextRow.baseCell.cellCanBecomeFirstResponder() {
  560. nextRow.baseCell.cellBecomeFirstResponder()
  561. return true
  562. }
  563. }
  564. tableView?.endEditing(true)
  565. return true
  566. }
  567. // MARK: FormDelegate
  568. open func valueHasBeenChanged(for: BaseRow, oldValue: Any?, newValue: Any?) {}
  569. // MARK: UITableViewDelegate
  570. @objc open func tableView(_ tableView: UITableView, willBeginReorderingRowAtIndexPath indexPath: IndexPath) {
  571. // end editing if inline cell is first responder
  572. let row = form[indexPath]
  573. if let inlineRow = row as? BaseInlineRowType, row._inlineRow != nil {
  574. inlineRow.collapseInlineRow()
  575. }
  576. }
  577. // MARK: FormDelegate
  578. open func sectionsHaveBeenAdded(_ sections: [Section], at indexes: IndexSet) {
  579. guard animateTableView else { return }
  580. tableView?.beginUpdates()
  581. tableView?.insertSections(indexes, with: insertAnimation(forSections: sections))
  582. tableView?.endUpdates()
  583. }
  584. open func sectionsHaveBeenRemoved(_ sections: [Section], at indexes: IndexSet) {
  585. guard animateTableView else { return }
  586. tableView?.beginUpdates()
  587. tableView?.deleteSections(indexes, with: deleteAnimation(forSections: sections))
  588. tableView?.endUpdates()
  589. }
  590. open func sectionsHaveBeenReplaced(oldSections: [Section], newSections: [Section], at indexes: IndexSet) {
  591. guard animateTableView else { return }
  592. tableView?.beginUpdates()
  593. tableView?.reloadSections(indexes, with: reloadAnimation(oldSections: oldSections, newSections: newSections))
  594. tableView?.endUpdates()
  595. }
  596. open func rowsHaveBeenAdded(_ rows: [BaseRow], at indexes: [IndexPath]) {
  597. guard animateTableView else { return }
  598. tableView?.beginUpdates()
  599. tableView?.insertRows(at: indexes, with: insertAnimation(forRows: rows))
  600. tableView?.endUpdates()
  601. }
  602. open func rowsHaveBeenRemoved(_ rows: [BaseRow], at indexes: [IndexPath]) {
  603. guard animateTableView else { return }
  604. tableView?.beginUpdates()
  605. tableView?.deleteRows(at: indexes, with: deleteAnimation(forRows: rows))
  606. tableView?.endUpdates()
  607. }
  608. open func rowsHaveBeenReplaced(oldRows: [BaseRow], newRows: [BaseRow], at indexes: [IndexPath]) {
  609. guard animateTableView else { return }
  610. tableView?.beginUpdates()
  611. tableView?.reloadRows(at: indexes, with: reloadAnimation(oldRows: oldRows, newRows: newRows))
  612. tableView?.endUpdates()
  613. }
  614. // MARK: Private
  615. var oldBottomInset: CGFloat?
  616. var animateTableView = false
  617. /** Calculates the height needed for a header or footer. */
  618. fileprivate func height(specifiedHeight: (() -> CGFloat)?, sectionView: UIView?, sectionTitle: String?) -> CGFloat {
  619. if let height = specifiedHeight {
  620. return height()
  621. }
  622. if let sectionView = sectionView {
  623. let height = sectionView.bounds.height
  624. if height == 0 {
  625. return UITableView.automaticDimension
  626. }
  627. return height
  628. }
  629. if let sectionTitle = sectionTitle,
  630. sectionTitle != "" {
  631. return UITableView.automaticDimension
  632. }
  633. // Fix for iOS 11+. By returning 0, we ensure that no section header or
  634. // footer is shown when self-sizing is enabled (i.e. when
  635. // tableView.estimatedSectionHeaderHeight or tableView.estimatedSectionFooterHeight
  636. // == UITableView.automaticDimension).
  637. if tableView.style == .plain {
  638. return 0
  639. }
  640. return UITableView.automaticDimension
  641. }
  642. }
  643. extension FormViewController : UITableViewDelegate {
  644. // MARK: UITableViewDelegate
  645. open func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
  646. return indexPath
  647. }
  648. open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  649. guard tableView == self.tableView else { return }
  650. let row = form[indexPath]
  651. // row.baseCell.cellBecomeFirstResponder() may be cause InlineRow collapsed then section count will be changed. Use orignal indexPath will out of section's bounds.
  652. if !row.baseCell.cellCanBecomeFirstResponder() || !row.baseCell.cellBecomeFirstResponder() {
  653. self.tableView?.endEditing(true)
  654. }
  655. row.didSelect()
  656. }
  657. open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  658. guard tableView == self.tableView else { return tableView.rowHeight }
  659. let row = form[indexPath.section][indexPath.row]
  660. return row.baseCell.height?() ?? tableView.rowHeight
  661. }
  662. open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
  663. guard tableView == self.tableView else { return tableView.estimatedRowHeight }
  664. let row = form[indexPath.section][indexPath.row]
  665. return row.baseCell.height?() ?? tableView.estimatedRowHeight
  666. }
  667. open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
  668. return form[section].header?.viewForSection(form[section], type: .header)
  669. }
  670. open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
  671. return form[section].footer?.viewForSection(form[section], type:.footer)
  672. }
  673. open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
  674. return height(specifiedHeight: form[section].header?.height,
  675. sectionView: self.tableView(tableView, viewForHeaderInSection: section),
  676. sectionTitle: self.tableView(tableView, titleForHeaderInSection: section))
  677. }
  678. open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
  679. return height(specifiedHeight: form[section].footer?.height,
  680. sectionView: self.tableView(tableView, viewForFooterInSection: section),
  681. sectionTitle: self.tableView(tableView, titleForFooterInSection: section))
  682. }
  683. open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
  684. let row = form[indexPath]
  685. guard !row.isDisabled else { return false }
  686. if row.trailingSwipe.actions.count > 0 { return true }
  687. if #available(iOS 11,*), row.leadingSwipe.actions.count > 0 { return true }
  688. guard let section = form[indexPath.section] as? BaseMultivaluedSection else { return false }
  689. guard !(indexPath.row == section.count - 1 && section.multivaluedOptions.contains(.Insert) && section.showInsertIconInAddButton) else {
  690. return true
  691. }
  692. if indexPath.row > 0 && section[indexPath.row - 1] is BaseInlineRowType && section[indexPath.row - 1]._inlineRow != nil {
  693. return false
  694. }
  695. return true
  696. }
  697. open func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
  698. if editingStyle == .delete {
  699. let row = form[indexPath]
  700. let section = row.section!
  701. if let _ = row.baseCell.findFirstResponder() {
  702. tableView.endEditing(true)
  703. }
  704. section.remove(at: indexPath.row)
  705. } else if editingStyle == .insert {
  706. guard var section = form[indexPath.section] as? BaseMultivaluedSection else { return }
  707. guard let multivaluedRowToInsertAt = section.multivaluedRowToInsertAt else {
  708. fatalError("Multivalued section multivaluedRowToInsertAt property must be set up")
  709. }
  710. let newRow = multivaluedRowToInsertAt(max(0, section.count - 1))
  711. section.insert(newRow, at: max(0, section.count - 1))
  712. DispatchQueue.main.async {
  713. tableView.isEditing = !tableView.isEditing
  714. tableView.isEditing = !tableView.isEditing
  715. }
  716. tableView.scrollToRow(at: IndexPath(row: section.count - 1, section: indexPath.section), at: .bottom, animated: true)
  717. if newRow.baseCell.cellCanBecomeFirstResponder() {
  718. newRow.baseCell.cellBecomeFirstResponder()
  719. } else if let inlineRow = newRow as? BaseInlineRowType {
  720. inlineRow.expandInlineRow()
  721. }
  722. }
  723. }
  724. open func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
  725. guard let section = form[indexPath.section] as? BaseMultivaluedSection, section.multivaluedOptions.contains(.Reorder) && section.count > 1 else {
  726. return false
  727. }
  728. if section.multivaluedOptions.contains(.Insert) && (section.count <= 2 || indexPath.row == (section.count - 1)) {
  729. return false
  730. }
  731. if indexPath.row > 0 && section[indexPath.row - 1] is BaseInlineRowType && section[indexPath.row - 1]._inlineRow != nil {
  732. return false
  733. }
  734. return true
  735. }
  736. open func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
  737. guard let section = form[sourceIndexPath.section] as? BaseMultivaluedSection else { return sourceIndexPath }
  738. guard sourceIndexPath.section == proposedDestinationIndexPath.section else { return sourceIndexPath }
  739. let destRow = form[proposedDestinationIndexPath]
  740. if destRow is BaseInlineRowType && destRow._inlineRow != nil {
  741. return IndexPath(row: proposedDestinationIndexPath.row + (sourceIndexPath.row < proposedDestinationIndexPath.row ? 1 : -1), section:sourceIndexPath.section)
  742. }
  743. if proposedDestinationIndexPath.row > 0 {
  744. let previousRow = form[IndexPath(row: proposedDestinationIndexPath.row - 1, section: proposedDestinationIndexPath.section)]
  745. if previousRow is BaseInlineRowType && previousRow._inlineRow != nil {
  746. return IndexPath(row: proposedDestinationIndexPath.row + (sourceIndexPath.row < proposedDestinationIndexPath.row ? 1 : -1), section:sourceIndexPath.section)
  747. }
  748. }
  749. if section.multivaluedOptions.contains(.Insert) && proposedDestinationIndexPath.row == section.count - 1 {
  750. return IndexPath(row: section.count - 2, section: sourceIndexPath.section)
  751. }
  752. return proposedDestinationIndexPath
  753. }
  754. open func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
  755. guard var section = form[sourceIndexPath.section] as? BaseMultivaluedSection else { return }
  756. if sourceIndexPath.row < section.count && destinationIndexPath.row < section.count && sourceIndexPath.row != destinationIndexPath.row {
  757. let sourceRow = form[sourceIndexPath]
  758. animateTableView = false
  759. section.remove(at: sourceIndexPath.row)
  760. section.insert(sourceRow, at: destinationIndexPath.row)
  761. animateTableView = true
  762. // update the accessory view
  763. let _ = inputAccessoryView(for: sourceRow)
  764. }
  765. }
  766. open func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
  767. guard let section = form[indexPath.section] as? BaseMultivaluedSection else {
  768. if form[indexPath].trailingSwipe.actions.count > 0 {
  769. return .delete
  770. }
  771. return .none
  772. }
  773. if section.multivaluedOptions.contains(.Insert) && indexPath.row == section.count - 1 {
  774. return section.showInsertIconInAddButton ? .insert : .none
  775. }
  776. if section.multivaluedOptions.contains(.Delete) {
  777. return .delete
  778. }
  779. return .none
  780. }
  781. open func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
  782. return self.tableView(tableView, editingStyleForRowAt: indexPath) != .none
  783. }
  784. @available(iOS 11,*)
  785. open func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  786. guard !form[indexPath].leadingSwipe.actions.isEmpty else {
  787. return nil
  788. }
  789. return form[indexPath].leadingSwipe.contextualConfiguration
  790. }
  791. @available(iOS 11,*)
  792. open func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  793. guard !form[indexPath].trailingSwipe.actions.isEmpty else {
  794. return nil
  795. }
  796. return form[indexPath].trailingSwipe.contextualConfiguration
  797. }
  798. @available(iOS, deprecated: 13, message: "UITableViewRowAction is deprecated, use leading/trailingSwipe actions instead")
  799. open func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?{
  800. guard let actions = form[indexPath].trailingSwipe.contextualActions as? [UITableViewRowAction], !actions.isEmpty else {
  801. return nil
  802. }
  803. return actions
  804. }
  805. }
  806. extension FormViewController : UITableViewDataSource {
  807. // MARK: UITableViewDataSource
  808. open func numberOfSections(in tableView: UITableView) -> Int {
  809. return form.count
  810. }
  811. open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  812. return form[section].count
  813. }
  814. open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  815. form[indexPath].updateCell()
  816. return form[indexPath].baseCell
  817. }
  818. open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  819. return form[section].header?.title
  820. }
  821. open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
  822. return form[section].footer?.title
  823. }
  824. open func sectionIndexTitles(for tableView: UITableView) -> [String]? {
  825. return nil
  826. }
  827. open func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
  828. return 0
  829. }
  830. }
  831. extension FormViewController : UIScrollViewDelegate {
  832. // MARK: UIScrollViewDelegate
  833. open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
  834. guard let tableView = tableView, scrollView === tableView else { return }
  835. tableView.endEditing(true)
  836. }
  837. }
  838. extension FormViewController {
  839. // MARK: KeyBoard Notifications
  840. /**
  841. Called when the keyboard will appear. Adjusts insets of the tableView and scrolls it if necessary.
  842. */
  843. @objc open func keyboardWillShow(_ notification: Notification) {
  844. guard let table = tableView, let cell = table.findFirstResponder()?.formCell() else { return }
  845. let keyBoardInfo = notification.userInfo!
  846. let endFrame = keyBoardInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue
  847. let keyBoardFrame = table.window!.convert(endFrame.cgRectValue, to: table.superview)
  848. var newBottomInset = table.frame.origin.y + table.frame.size.height - keyBoardFrame.origin.y + rowKeyboardSpacing
  849. if #available(iOS 11.0, *) {
  850. newBottomInset = newBottomInset - tableView.safeAreaInsets.bottom
  851. }
  852. var tableInsets = table.contentInset
  853. var scrollIndicatorInsets = table.scrollIndicatorInsets
  854. oldBottomInset = oldBottomInset ?? tableInsets.bottom
  855. if newBottomInset > oldBottomInset! {
  856. tableInsets.bottom = newBottomInset
  857. scrollIndicatorInsets.bottom = tableInsets.bottom
  858. UIView.beginAnimations(nil, context: nil)
  859. UIView.setAnimationDuration((keyBoardInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double))
  860. UIView.setAnimationCurve(UIView.AnimationCurve(rawValue: (keyBoardInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as! Int))!)
  861. table.contentInset = tableInsets
  862. table.scrollIndicatorInsets = scrollIndicatorInsets
  863. if let selectedRow = table.indexPath(for: cell) {
  864. if ProcessInfo.processInfo.operatingSystemVersion.majorVersion == 11 {
  865. let rect = table.rectForRow(at: selectedRow)
  866. table.scrollRectToVisible(rect, animated: animateScroll)
  867. } else {
  868. table.scrollToRow(at: selectedRow, at: .none, animated: animateScroll)
  869. }
  870. }
  871. UIView.commitAnimations()
  872. }
  873. }
  874. /**
  875. Called when the keyboard will disappear. Adjusts insets of the tableView.
  876. */
  877. @objc open func keyboardWillHide(_ notification: Notification) {
  878. guard let table = tableView, let oldBottom = oldBottomInset else { return }
  879. let keyBoardInfo = notification.userInfo!
  880. var tableInsets = table.contentInset
  881. var scrollIndicatorInsets = table.scrollIndicatorInsets
  882. tableInsets.bottom = oldBottom
  883. scrollIndicatorInsets.bottom = tableInsets.bottom
  884. oldBottomInset = nil
  885. UIView.beginAnimations(nil, context: nil)
  886. UIView.setAnimationDuration((keyBoardInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as! Double))
  887. UIView.setAnimationCurve(UIView.AnimationCurve(rawValue: (keyBoardInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as! Int))!)
  888. table.contentInset = tableInsets
  889. table.scrollIndicatorInsets = scrollIndicatorInsets
  890. UIView.commitAnimations()
  891. }
  892. }
  893. public enum Direction { case up, down }
  894. extension FormViewController {
  895. // MARK: Navigation Methods
  896. @objc func navigationDone() {
  897. tableView?.endEditing(true)
  898. }
  899. @objc func navigationPrevious() {
  900. navigateTo(direction: .up)
  901. }
  902. @objc func navigationNext() {
  903. navigateTo(direction: .down)
  904. }
  905. public func navigateTo(direction: Direction) {
  906. guard let currentCell = tableView?.findFirstResponder()?.formCell() else { return }
  907. guard let currentIndexPath = tableView?.indexPath(for: currentCell) else { return }
  908. guard let nextRow = nextRow(for: form[currentIndexPath], withDirection: direction) else { return }
  909. if nextRow.baseCell.cellCanBecomeFirstResponder() {
  910. tableView?.scrollToRow(at: nextRow.indexPath!, at: .none, animated: animateScroll)
  911. nextRow.baseCell.cellBecomeFirstResponder(withDirection: direction)
  912. }
  913. }
  914. func nextRow(for currentRow: BaseRow, withDirection direction: Direction) -> BaseRow? {
  915. let options = navigationOptions ?? Form.defaultNavigationOptions
  916. guard options.contains(.Enabled) else { return nil }
  917. guard let next = direction == .down ? form.nextRow(for: currentRow) : form.previousRow(for: currentRow) else { return nil }
  918. if next.isDisabled && options.contains(.StopDisabledRow) {
  919. return nil
  920. }
  921. if !next.baseCell.cellCanBecomeFirstResponder() && !next.isDisabled && !options.contains(.SkipCanNotBecomeFirstResponderRow) {
  922. return nil
  923. }
  924. if !next.isDisabled && next.baseCell.cellCanBecomeFirstResponder() {
  925. return next
  926. }
  927. return nextRow(for: next, withDirection:direction)
  928. }
  929. }
  930. extension FormViewControllerProtocol {
  931. // MARK: Helpers
  932. func makeRowVisible(_ row: BaseRow, destinationScrollPosition: UITableView.ScrollPosition? = .bottom) {
  933. guard let destinationScrollPosition = destinationScrollPosition else { return }
  934. guard let cell = row.baseCell, let indexPath = row.indexPath, let tableView = tableView else { return }
  935. if cell.window == nil || (tableView.contentOffset.y + tableView.frame.size.height <= cell.frame.origin.y + cell.frame.size.height) {
  936. tableView.scrollToRow(at: indexPath, at: destinationScrollPosition, animated: true)
  937. }
  938. }
  939. }