diff --git a/Example.xcodeproj/project.pbxproj b/Example.xcodeproj/project.pbxproj index ee26e7ba2..ada6478ac 100644 --- a/Example.xcodeproj/project.pbxproj +++ b/Example.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 082E31F21DF8DA1D005CFC85 /* PDFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082E31F11DF8DA1D005CFC85 /* PDFormViewModel.swift */; }; 2837E2A01BA3A5A800A1952C /* FloatLabelTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2837E29E1BA3A5A800A1952C /* FloatLabelTextField.swift */; }; 285F2B441BB79B86005991BA /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 285F2B431BB79B86005991BA /* MapKit.framework */; }; 287A143F1D99A96100FFE6EB /* ImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287A143D1D99A96100FFE6EB /* ImagePickerController.swift */; }; @@ -77,6 +78,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 082E31F11DF8DA1D005CFC85 /* PDFormViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PDFormViewModel.swift; sourceTree = ""; }; 2837E29E1BA3A5A800A1952C /* FloatLabelTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = FloatLabelTextField.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 285F2B431BB79B86005991BA /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 287A143D1D99A96100FFE6EB /* ImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImagePickerController.swift; path = Example/CustomRows/ImageRow/ImagePickerController.swift; sourceTree = ""; }; @@ -178,6 +180,7 @@ 51729E6A1B9A657E004A00EB /* Custom Row and Cell */, 51729E1D1B9A54F1004A00EB /* AppDelegate.swift */, 51729E1F1B9A54F1004A00EB /* ViewController.swift */, + 082E31F11DF8DA1D005CFC85 /* PDFormViewModel.swift */, 51729E211B9A54F1004A00EB /* Main.storyboard */, 51729E241B9A54F1004A00EB /* Assets.xcassets */, 51729E261B9A54F1004A00EB /* LaunchScreen.storyboard */, @@ -353,6 +356,7 @@ 287A143F1D99A96100FFE6EB /* ImagePickerController.swift in Sources */, 51729E1E1B9A54F1004A00EB /* AppDelegate.swift in Sources */, 2837E2A01BA3A5A800A1952C /* FloatLabelTextField.swift in Sources */, + 082E31F21DF8DA1D005CFC85 /* PDFormViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/Example/Base.lproj/Main.storyboard b/Example/Example/Base.lproj/Main.storyboard index 8969e6e40..687aadd2a 100644 --- a/Example/Example/Base.lproj/Main.storyboard +++ b/Example/Example/Base.lproj/Main.storyboard @@ -1,8 +1,11 @@ - - + + + + + - + @@ -19,6 +22,7 @@ + @@ -37,7 +41,7 @@ - + @@ -52,6 +56,7 @@ + @@ -70,7 +75,7 @@ - + @@ -85,6 +90,7 @@ + @@ -138,6 +144,7 @@ + @@ -182,6 +189,7 @@ + @@ -233,6 +241,7 @@ + @@ -266,6 +275,7 @@ + @@ -299,6 +309,7 @@ + @@ -332,6 +343,7 @@ + @@ -365,6 +377,7 @@ + @@ -398,6 +411,7 @@ + @@ -431,6 +445,7 @@ + diff --git a/Example/Example/CustomCells.swift b/Example/Example/CustomCells.swift index a678c3613..869c6bfa9 100644 --- a/Example/Example/CustomCells.swift +++ b/Example/Example/CustomCells.swift @@ -253,7 +253,7 @@ public class _FloatLabelCell: Cell, UITextFieldDelegate, TextFieldCell whe //MARK: TextFieldDelegate public func textFieldDidBeginEditing(_ textField: UITextField) { - formViewController()?.beginEditing(of: self) + formViewDelegate?.beginEditing(of: self) if let fieldRowConformance = row as? FormatterConformance, let _ = fieldRowConformance.formatter, fieldRowConformance.useFormatterOnDidBeginEditing ?? fieldRowConformance.useFormatterDuringInput { textField.text = displayValue(useFormatter: true) } else { @@ -262,8 +262,8 @@ public class _FloatLabelCell: Cell, UITextFieldDelegate, TextFieldCell whe } public func textFieldDidEndEditing(_ textField: UITextField) { - formViewController()?.endEditing(of: self) - formViewController()?.textInputDidEndEditing(textField, cell: self) + formViewDelegate?.endEditing(of: self) + formViewDelegate?.textInputDidEndEditing(textField, cell: self) textFieldDidChange(textField) textField.text = displayValue(useFormatter: (row as? FormatterConformance)?.formatter != nil) } diff --git a/Example/Example/CustomRows/ImageRow/ImageRow.swift b/Example/Example/CustomRows/ImageRow/ImageRow.swift index 78f2fa96f..9c2565c53 100644 --- a/Example/Example/CustomRows/ImageRow/ImageRow.swift +++ b/Example/Example/CustomRows/ImageRow/ImageRow.swift @@ -91,12 +91,12 @@ open class _ImageRow: SelectorRow w if let controller = presentationMode.makeController(){ controller.row = self controller.sourceType = sourceType - onPresentCallback?(cell.formViewController()!, controller) - presentationMode.present(controller, row: self, presentingController: cell.formViewController()!) + onPresentCallback?(cell.viewController()!, controller) + presentationMode.present(controller, row: self, presentingController: cell.viewController()!) } else{ _sourceType = sourceType - presentationMode.present(nil, row: self, presentingController: cell.formViewController()!) + presentationMode.present(nil, row: self, presentingController: cell.viewController()!) } } } @@ -128,7 +128,7 @@ open class _ImageRow: SelectorRow w // now that we know the number of actions aren't empty let sourceActionSheet = UIAlertController(title: nil, message: selectorTitle, preferredStyle: .actionSheet) - guard let tableView = cell.formViewController()?.tableView else { fatalError() } + guard let tableView = cell.parentTableView() else { fatalError() } if let popView = sourceActionSheet.popoverPresentationController { popView.sourceView = tableView popView.sourceRect = tableView.convert(cell.accessoryView?.frame ?? cell.contentView.frame, from: cell) @@ -151,7 +151,7 @@ open class _ImageRow: SelectorRow w let cancelOption = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler:nil) sourceActionSheet.addAction(cancelOption) - if let presentingViewController = cell.formViewController() { + if let presentingViewController = cell.viewController() { presentingViewController.present(sourceActionSheet, animated: true) } } diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index 396b5bdb1..e089e19b8 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -48,7 +48,15 @@ class HomeViewController : FormViewController { $0.title = $0.tag $0.presentationMode = .segueName(segueName: "RowsExampleViewControllerSegue", onDismiss: nil) } - + + <<< ButtonRow("PD TEST") { + $0.title = $0.tag + }.onCellSelection { [unowned self] _ in + let viewController = PDTESTExampleViewController() + viewController.view.backgroundColor = UIColor.yellow + self.navigationController?.pushViewController(viewController, animated: true) + } + <<< ButtonRow("Native iOS Event Form") { row in row.title = row.tag row.presentationMode = .segueName(segueName: "NativeEventsFormNavigationControllerSegue", onDismiss:{ vc in vc.dismiss(animated: true) }) @@ -120,6 +128,59 @@ class HomeViewController : FormViewController { typealias Emoji = String let πŸ‘¦πŸΌ = "πŸ‘¦πŸΌ", 🍐 = "🍐", πŸ’πŸ» = "πŸ’πŸ»", πŸ— = "πŸ—", 🐼 = "🐼", 🐻 = "🐻", πŸ– = "πŸ–", 🐑 = "🐑" + +class PDTESTViewModel: PDFormViewModel { + + override init() { + super.init() + + // setup sections and rows using eureka + + let section = Section() + + section + <<< ButtonRow("Push ViewController") { + $0.title = $0.tag + }.onCellSelection({ _ in + let viewController = UIViewController() + viewController.view.backgroundColor = UIColor.cyan + self.linkedViewController?.navigationController?.pushViewController(viewController, animated: true) + }) + <<< LabelRow().cellUpdate { cell, row in + cell.textLabel?.text = "Hello World" + }.onCellSelection() { row in + print("I'm working") + } + <<< ActionSheetRow() { + $0.title = "ActionSheetRow" + $0.selectorTitle = "Your favourite player?" + $0.options = ["Diego ForlΓ‘n", "Edinson Cavani", "Diego Lugano", "Luis Suarez"] + $0.value = "Luis Suarez" + } + + tableContent.append(section) + } + +} + +class PDTESTExampleViewController: UIViewController { + + var formViewController: PDFormViewController? + + override func viewDidLoad() { + super.viewDidLoad() + + let topOffset : CGFloat = 64 + let bottomOffset : CGFloat = 0 + let contentInset = UIEdgeInsetsMake(topOffset, 0.0, bottomOffset, 0.0) + formViewController = configureFormViewController(insets: contentInset) + formViewController!.viewModel = PDTESTViewModel() + + formViewController!.tableView?.reloadData() + } + +} + //Mark: RowsExampleViewController class RowsExampleViewController: FormViewController { diff --git a/Example/PDFormViewModel.swift b/Example/PDFormViewModel.swift new file mode 100644 index 000000000..d98c3964b --- /dev/null +++ b/Example/PDFormViewModel.swift @@ -0,0 +1,342 @@ +// +// Created by pual on 07.12.16. +// Copyright (c) 2016 Jommi. All rights reserved. +// + +import Foundation +import Eureka +// import CocoaLumberjackSwift + +open class PDTableViewController: UIViewController { + + public var tableViewContentInsets: UIEdgeInsets = UIEdgeInsets.zero + + public var tableView: UITableView? + + open override func loadView() { + super.loadView() + let _tableView = UITableView(frame: CGRect.zero, style: UITableViewStyle.plain) + _tableView.separatorStyle = .none + view.addSubview(_tableView) + _tableView.frame = view.bounds +// _tableView.snp.makeConstraints { make in +// make.edges.equalToSuperview() +// } + _tableView.setContentOffset(CGPoint(x: 0,y: -tableViewContentInsets.top), animated: false) + + tableView = _tableView + } + + var didSetupInsetsOnce: Bool = false + + open override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + tableView?.contentInset = tableViewContentInsets + tableView?.scrollIndicatorInsets = tableViewContentInsets + + if (!didSetupInsetsOnce) { + tableView?.setContentOffset(CGPoint(x: 0,y: -tableViewContentInsets.top), animated: false) + didSetupInsetsOnce = true + } + + } + + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if let selectedIndexPath = tableView?.indexPathForSelectedRow { + tableView?.deselectRow(at: selectedIndexPath, animated: true) + } + + } + +} + +extension UIViewController { + + func configureFormViewController(insets: UIEdgeInsets = UIEdgeInsets.zero) -> PDFormViewController { + + let childViewController = PDFormViewController() + childViewController.tableViewContentInsets = insets + + addChildViewController(childViewController) + childViewController.didMove(toParentViewController: self) + childViewController.view.translatesAutoresizingMaskIntoConstraints = false + childViewController.view.frame = view.bounds + view.addSubview(childViewController.view) +// childViewController.view.snp.makeConstraints { make in +// make.edges.equalToSuperview() +// } + return childViewController + + } + +} + +open class PDFormViewController: PDTableViewController { + var viewModel: PDFormViewModel? { + didSet { + viewModel?.linkedViewController = self + tableView?.dataSource = viewModel + tableView?.delegate = viewModel + } + } +} + +open class PDFormViewModel : NSObject { + + weak var linkedViewController: PDFormViewController? + + private lazy var _tableContent : Form = { [weak self] in + let form = Form() + form.delegate = self + return form + }() + + public var tableContent : Form { + get { return _tableContent } + set { + guard tableContent !== newValue else { return } + _tableContent.delegate = nil + tableView?.endEditing(false) + _tableContent = newValue + _tableContent.delegate = self + } + } + +} + +extension PDFormViewModel : UITableViewDataSource { + + //MARK: UITableViewDataSource + + open func numberOfSections(in tableView: UITableView) -> Int { + return tableContent.count + } + + open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return tableContent[section].count + } + + open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + tableContent[indexPath.section][indexPath.row].updateCell() + return tableContent[indexPath.section][indexPath.row].baseCell + } + + open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return tableContent[section].header?.title + } + + open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + return tableContent[section].footer?.title + } + +} + +extension PDFormViewModel : UITableViewDelegate { + + open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // guard tableView == self.tableView else { DDLogError(""); return } + let row = tableContent[indexPath.section][indexPath.row] + // row.baseCell.cellBecomeFirstResponder() may be cause InlineRow collapsed then section count will be changed. Use orignal indexPath will out of section's bounds. + if !row.baseCell.cellCanBecomeFirstResponder() || !row.baseCell.cellBecomeFirstResponder() { + self.tableView?.endEditing(true) + } + row.didSelect() + } + + open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + // guard tableView == self.tableView else { DDLogError(""); return tableView.rowHeight } + let row = tableContent[indexPath.section][indexPath.row] + return row.baseCell.height?() ?? tableView.rowHeight + } + + open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { + // guard tableView == self.tableView else { DDLogError(""); return tableView.rowHeight } + let row = tableContent[indexPath.section][indexPath.row] + return row.baseCell.height?() ?? tableView.estimatedRowHeight + } + + open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return tableContent[section].header?.viewForSection(tableContent[section], type: .header) + } + + open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + return tableContent[section].footer?.viewForSection(tableContent[section], type:.footer) + } + + open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + if let height = tableContent[section].header?.height { + return height() + } + guard let view = tableContent[section].header?.viewForSection(tableContent[section], type: .header) else{ + return UITableViewAutomaticDimension + } + guard view.bounds.height != 0 else { + return UITableViewAutomaticDimension + } + return view.bounds.height + } + + open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + if let height = tableContent[section].footer?.height { + return height() + } + guard let view = tableContent[section].footer?.viewForSection(tableContent[section], type: .footer) else{ + return UITableViewAutomaticDimension + } + guard view.bounds.height != 0 else { + return UITableViewAutomaticDimension + } + return view.bounds.height + } + +} + + +extension PDFormViewModel: FormDelegate { + + //MARK: FormDelegate + + open func sectionsHaveBeenAdded(_ sections: [Section], at indexes: IndexSet){ + tableView?.beginUpdates() + tableView?.insertSections(indexes, with: insertAnimation(forSections: sections)) + tableView?.endUpdates() + } + + open func sectionsHaveBeenRemoved(_ sections: [Section], at indexes: IndexSet){ + tableView?.beginUpdates() + tableView?.deleteSections(indexes, with: deleteAnimation(forSections: sections)) + tableView?.endUpdates() + } + + open func sectionsHaveBeenReplaced(oldSections:[Section], newSections: [Section], at indexes: IndexSet){ + tableView?.beginUpdates() + tableView?.reloadSections(indexes, with: reloadAnimation(oldSections: oldSections, newSections: newSections)) + tableView?.endUpdates() + } + + + open func rowsHaveBeenAdded(_ rows: [BaseRow], at indexes: [IndexPath]) { + tableView?.beginUpdates() + tableView?.insertRows(at: indexes, with: insertAnimation(forRows: rows)) + tableView?.endUpdates() + } + + open func rowsHaveBeenRemoved(_ rows: [BaseRow], at indexes: [IndexPath]) { + tableView?.beginUpdates() + tableView?.deleteRows(at: indexes, with: deleteAnimation(forRows: rows)) + tableView?.endUpdates() + } + + open func rowsHaveBeenReplaced(oldRows:[BaseRow], newRows: [BaseRow], at indexes: [IndexPath]){ + tableView?.beginUpdates() + tableView?.reloadRows(at: indexes, with: reloadAnimation(oldRows: oldRows, newRows: newRows)) + tableView?.endUpdates() + } + + open func valueHasBeenChanged(for: BaseRow, oldValue: Any?, newValue: Any?) { + print("oldValue: \(oldValue), newValue: \(newValue)") + } + +} + +extension PDFormViewModel : FormViewControllerProtocol { + + + open func inputAccessoryView(for row: BaseRow) -> UIView? { return nil } + open func textInputShouldBeginEditing(_ textInput: UITextInput, cell: Cell) -> Bool { return false } + open func textInputDidBeginEditing(_ textInput: UITextInput, cell: Cell) { } + open func textInputShouldEndEditing(_ textInput: UITextInput, cell: Cell) -> Bool { return false } + open func textInputDidEndEditing(_ textInput: UITextInput, cell: Cell) { return } + open func textInput(_ textInput: UITextInput, shouldChangeCharactersInRange range: NSRange, replacementString string: String, cell: Cell) -> Bool { return false } + open func textInputShouldClear(_ textInput: UITextInput, cell: Cell) -> Bool { return false } + open func textInputShouldReturn(_ textInput: UITextInput, cell: Cell) -> Bool { return false } + + //MARK: FormViewControllerProtocol + + public var tableView: UITableView? { + let result = linkedViewController?.tableView + if result == nil { + print("is nil") + } + return result + } + + /** + Called when a cell becomes first responder + */ + public final func beginEditing(of cell: Cell) { + cell.row.isHighlighted = true + cell.row.updateCell() + // RowDefaults.onCellHighlightChanged["\(type(of: cell.row!))"]?(cell, cell.row) + // cell.row.callbackOnCellHighlightChanged?() + // guard let _ = tableView, (tableContent.inlineRowHideOptions ?? tableContent.defaultInlineRowHideOptions).contains(.FirstResponderChanges) else { return } + // let row = cell.baseRow + // let inlineRow = row?._inlineRow + // for row in tableContent.allRows.filter({ $0 !== row && $0 !== inlineRow && $0._inlineRow != nil }) { + // if let inlineRow = row as? BaseInlineRowType { + // inlineRow.collapseInlineRow() + // } + // } + } + + /** + Called when a cell resigns first responder + */ + public final func endEditing(of cell: Cell) { + print("") + cell.row.isHighlighted = false + // cell.row.wasBlurred = true + // RowDefaults.onCellHighlightChanged["\(type(of: self))"]?(cell, cell.row) + // cell.row.callbackOnCellHighlightChanged?() + if cell.row.validationOptions.contains(.validatesOnBlur) || cell.row.validationOptions.contains(.validatesOnChangeAfterBlurred) { + cell.row.validate() + } + cell.row.updateCell() + } + + /** + Returns the animation for the insertion of the given rows. + */ + open func insertAnimation(forRows rows: [BaseRow]) -> UITableViewRowAnimation { + return .fade + } + + /** + Returns the animation for the deletion of the given rows. + */ + open func deleteAnimation(forRows rows: [BaseRow]) -> UITableViewRowAnimation { + return .fade + } + + /** + Returns the animation for the reloading of the given rows. + */ + open func reloadAnimation(oldRows: [BaseRow], newRows: [BaseRow]) -> UITableViewRowAnimation { + return .automatic + } + + /** + Returns the animation for the insertion of the given sections. + */ + open func insertAnimation(forSections sections: [Section]) -> UITableViewRowAnimation { + return .automatic + } + + /** + Returns the animation for the deletion of the given sections. + */ + open func deleteAnimation(forSections sections: [Section]) -> UITableViewRowAnimation { + return .automatic + } + + /** + Returns the animation for the reloading of the given sections. + */ + open func reloadAnimation(oldSections: [Section], newSections: [Section]) -> UITableViewRowAnimation { + return .automatic + } + +} diff --git a/Source/Core/BaseRow.swift b/Source/Core/BaseRow.swift index b31b05126..16a59811d 100644 --- a/Source/Core/BaseRow.swift +++ b/Source/Core/BaseRow.swift @@ -56,7 +56,10 @@ open class BaseRow : BaseRowType { /// The title will be displayed in the textLabel of the row. public var title: String? - + + /// The title will be displayed in the textLabel of the row. + public var attributedTitle: NSAttributedString? + /// Parameter used when creating the cell for this row. public var cellStyle = UITableViewCellStyle.value1 @@ -247,19 +250,17 @@ extension BaseRow: Equatable, Hidable, Disableable {} extension BaseRow { public func reload(with rowAnimation: UITableViewRowAnimation = .none) { - guard let tableView = baseCell?.formViewController()?.tableView ?? (section?.form?.delegate as? FormViewController)?.tableView, let indexPath = indexPath else { return } + guard let tableView = baseCell?.parentTableView(), let indexPath = indexPath else { return } tableView.reloadRows(at: [indexPath], with: rowAnimation) } public func deselect(animated: Bool = true) { - guard let indexPath = indexPath, - let tableView = baseCell?.formViewController()?.tableView ?? (section?.form?.delegate as? FormViewController)?.tableView else { return } + guard let indexPath = indexPath, let tableView = baseCell?.parentTableView() else { return } tableView.deselectRow(at: indexPath, animated: animated) } public func select(animated: Bool = false, scrollPosition: UITableViewScrollPosition = .none) { - guard let indexPath = indexPath, - let tableView = baseCell?.formViewController()?.tableView ?? (section?.form?.delegate as? FormViewController)?.tableView else { return } + guard let indexPath = indexPath, let tableView = baseCell?.parentTableView() else { return } tableView.selectRow(at: indexPath, animated: animated, scrollPosition: scrollPosition) } } diff --git a/Source/Core/Cell.swift b/Source/Core/Cell.swift index 16a2c3451..3bd6e59b2 100644 --- a/Source/Core/Cell.swift +++ b/Source/Core/Cell.swift @@ -44,18 +44,25 @@ open class BaseCell : UITableViewCell, BaseCellType { /** Function that returns the FormViewController this cell belongs to. + Will be set by the controller instantiating the cell. */ - public func formViewController() -> FormViewController? { + public var formViewDelegate: FormViewControllerProtocol? + + /** + Function that returns the ViewController this cell belongs to. + */ + public func viewController() -> UIViewController? { var responder : AnyObject? = self while responder != nil { - if responder! is FormViewController { - return responder as? FormViewController + if responder! is UIViewController { + return responder as? UIViewController } responder = responder?.next } return nil } + open func setup(){} open func update() {} @@ -85,6 +92,24 @@ open class BaseCell : UITableViewCell, BaseCellType { } } +extension BaseCell { + + /** + Will iterate through the superviews to find the tableView the cell belongs to. + */ + public func parentTableView() -> UITableView? { + var view: UIView? = self + while (view != nil) { + if view?.isKind(of: UITableView.self) ?? false { + return view as? UITableView + } + view = view?.superview ?? nil + } + return nil + } + +} + /// Generic class that represents the Eureka cells. open class Cell : BaseCell, TypedCellType { @@ -95,7 +120,7 @@ open class Cell : BaseCell, TypedCellType { /// Returns the navigationAccessoryView if it is defined or calls super if not. override open var inputAccessoryView: UIView? { - if let v = formViewController()?.inputAccessoryView(for: row){ + if let v = formViewDelegate?.inputAccessoryView(for: row){ return v } return super.inputAccessoryView @@ -122,8 +147,12 @@ open class Cell : BaseCell, TypedCellType { */ open override func update(){ super.update() - textLabel?.text = row.title - textLabel?.textColor = row.isDisabled ? .gray : .black + if row.attributedTitle != nil { + textLabel?.attributedText = row.attributedTitle + } else if row.title != nil { + textLabel?.text = row.title + textLabel?.textColor = row.isDisabled ? .gray : .black + } detailTextLabel?.text = row.displayValueFor?(row.value) ?? (row as? NoValueDisplayTextConformance)?.noValueDisplayText } @@ -139,7 +168,7 @@ open class Cell : BaseCell, TypedCellType { open override func becomeFirstResponder() -> Bool { let result = super.becomeFirstResponder() if result { - formViewController()?.beginEditing(of: self) + formViewDelegate?.beginEditing(of: self) } return result } @@ -147,7 +176,7 @@ open class Cell : BaseCell, TypedCellType { open override func resignFirstResponder() -> Bool { let result = super.resignFirstResponder() if result { - formViewController()?.endEditing(of: self) + formViewDelegate?.endEditing(of: self) } return result } diff --git a/Source/Core/CellType.swift b/Source/Core/CellType.swift index e9899741a..f47bb282e 100644 --- a/Source/Core/CellType.swift +++ b/Source/Core/CellType.swift @@ -63,11 +63,12 @@ public protocol BaseCellType : class { Method called when the cell resigns first responder */ func cellResignFirstResponder() -> Bool - + /** A reference to the controller in which the cell is displayed. */ - func formViewController () -> FormViewController? + var formViewDelegate: FormViewControllerProtocol? { get set } + } diff --git a/Source/Core/Core.swift b/Source/Core/Core.swift index 6d05cf026..f8eed772b 100644 --- a/Source/Core/Core.swift +++ b/Source/Core/Core.swift @@ -180,7 +180,7 @@ public enum PresentationMode { - parameter row: associated row - parameter presentingViewController: form view controller */ - public func present(_ viewController: VCType!, row: BaseRow, presentingController: FormViewController){ + public func present(_ viewController: VCType!, row: BaseRow, presentingController: UIViewController){ switch self { case .show(_, _): presentingController.show(viewController, sender: row) @@ -193,13 +193,15 @@ public enum PresentationMode { presentingController.prepare(for: segue, sender: row) segue.perform() case .popover(_, _): - guard let porpoverController = viewController.popoverPresentationController else { + guard let formViewController = presentingController as? FormViewController else { + fatalError("not supported for custom ViewController") + } + guard let popoverController = viewController.popoverPresentationController else { fatalError() } - porpoverController.sourceView = porpoverController.sourceView ?? presentingController.tableView + popoverController.sourceView = popoverController.sourceView ?? formViewController.tableView presentingController.present(viewController, animated: true) } - } /** @@ -332,13 +334,23 @@ public protocol FormViewControllerProtocol { func beginEditing(of: Cell) func endEditing(of: Cell) - + func insertAnimation(forRows rows: [BaseRow]) -> UITableViewRowAnimation func deleteAnimation(forRows rows: [BaseRow]) -> UITableViewRowAnimation func reloadAnimation(oldRows: [BaseRow], newRows: [BaseRow]) -> UITableViewRowAnimation func insertAnimation(forSections sections : [Section]) -> UITableViewRowAnimation func deleteAnimation(forSections sections : [Section]) -> UITableViewRowAnimation func reloadAnimation(oldSections: [Section], newSections:[Section]) -> UITableViewRowAnimation + + // UITextField delegate methods + func inputAccessoryView(for row: BaseRow) -> UIView? + func textInputShouldBeginEditing(_ textInput: UITextInput, cell: Cell) -> Bool + func textInputDidBeginEditing(_ textInput: UITextInput, cell: Cell) + func textInputShouldEndEditing(_ textInput: UITextInput, cell: Cell) -> Bool + func textInputDidEndEditing(_ textInput: UITextInput, cell: Cell) + func textInput(_ textInput: UITextInput, shouldChangeCharactersInRange range: NSRange, replacementString string: String, cell: Cell) -> Bool + func textInputShouldClear(_ textInput: UITextInput, cell: Cell) -> Bool + func textInputShouldReturn(_ textInput: UITextInput, cell: Cell) -> Bool } /** @@ -756,6 +768,7 @@ extension FormViewController : UITableViewDataSource { } open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + form[indexPath].baseCell.formViewDelegate = self form[indexPath].updateCell() return form[indexPath].baseCell } diff --git a/Source/Core/InlineRowType.swift b/Source/Core/InlineRowType.swift index 55914ac36..c116660a6 100644 --- a/Source/Core/InlineRowType.swift +++ b/Source/Core/InlineRowType.swift @@ -87,7 +87,7 @@ extension InlineRowType where Self: BaseRow, Self.InlineRow : BaseRow, Self.Cell if let indexPath = indexPath { section.insert(inline, at: indexPath.row + 1) _inlineRow = inline - cell.formViewController()?.makeRowVisible(inline) + cell.formViewDelegate?.makeRowVisible(inline) } } } diff --git a/Source/Core/PresenterRowType.swift b/Source/Core/PresenterRowType.swift index 77ad0263e..d22dab3c4 100644 --- a/Source/Core/PresenterRowType.swift +++ b/Source/Core/PresenterRowType.swift @@ -37,7 +37,7 @@ public protocol PresenterRowType: TypedRowType { var presentationMode: PresentationMode? { get set } /// Will be called before the presentation occurs. - var onPresentCallback: ((FormViewController, ProviderType)->())? { get set } + var onPresentCallback: ((UIViewController, ProviderType)->())? { get set } } extension PresenterRowType { @@ -49,7 +49,7 @@ extension PresenterRowType { - returns: this row */ - public func onPresent(_ callback: ((FormViewController, ProviderType)->())?) -> Self { + public func onPresent(_ callback: ((UIViewController, ProviderType)->())?) -> Self { onPresentCallback = callback return self } diff --git a/Source/Core/RowType.swift b/Source/Core/RowType.swift index 320719ff1..185790b24 100644 --- a/Source/Core/RowType.swift +++ b/Source/Core/RowType.swift @@ -59,6 +59,9 @@ public protocol BaseRowType: Taggable { /// The title will be displayed in the textLabel of the row. var title: String? { get set } + /// The title will be displayed in the textLabel of the row. + var attributedTitle: NSAttributedString? { get set } + /** Method that should re-display the cell */ diff --git a/Source/Rows/ActionSheetRow.swift b/Source/Rows/ActionSheetRow.swift index 5354e48bd..32eed097d 100644 --- a/Source/Rows/ActionSheetRow.swift +++ b/Source/Rows/ActionSheetRow.swift @@ -50,12 +50,12 @@ open class AlertSelectorCell : Cell, CellType { public class _ActionSheetRow: OptionsRow, PresenterRowType where Cell: BaseCell { - public var onPresentCallback : ((FormViewController, SelectorAlertController)->())? + public var onPresentCallback : ((UIViewController, SelectorAlertController)->())? lazy public var presentationMode: PresentationMode>? = { return .presentModally(controllerProvider: ControllerProvider.callback { [weak self] in let vc = SelectorAlertController(title: self?.selectorTitle, message: nil, preferredStyle: .actionSheet) if let popView = vc.popoverPresentationController { - guard let cell = self?.cell, let tableView = cell.formViewController()?.tableView else { fatalError() } + guard let cell = self?.cell, let tableView = cell.parentTableView() else { fatalError() } popView.sourceView = tableView popView.sourceRect = tableView.convert(cell.detailTextLabel?.frame ?? cell.textLabel?.frame ?? cell.contentView.frame, from: cell) } @@ -64,7 +64,7 @@ public class _ActionSheetRow: OptionsRow, PresenterRowType }, onDismiss: { [weak self] in $0.dismiss(animated: true) - self?.cell?.formViewController()?.tableView?.reloadData() + self?.cell?.parentTableView()?.reloadData() }) }() @@ -75,13 +75,14 @@ public class _ActionSheetRow: OptionsRow, PresenterRowType public override func customDidSelect() { super.customDidSelect() if let presentationMode = presentationMode, !isDisabled { + guard let viewController = cell.viewController() else { return } if let controller = presentationMode.makeController(){ controller.row = self - onPresentCallback?(cell.formViewController()!, controller) - presentationMode.present(controller, row: self, presentingController: cell.formViewController()!) + onPresentCallback?(viewController, controller) + presentationMode.present(controller, row: self, presentingController: viewController) } else{ - presentationMode.present(nil, row: self, presentingController: cell.formViewController()!) + presentationMode.present(nil, row: self, presentingController: viewController) } } } diff --git a/Source/Rows/AlertRow.swift b/Source/Rows/AlertRow.swift index 724f5fff5..c94e4a874 100644 --- a/Source/Rows/AlertRow.swift +++ b/Source/Rows/AlertRow.swift @@ -26,7 +26,7 @@ import Foundation open class _AlertRow: OptionsRow, PresenterRowType where Cell: BaseCell { - open var onPresentCallback : ((FormViewController, SelectorAlertController)->())? + open var onPresentCallback : ((UIViewController, SelectorAlertController)->())? lazy open var presentationMode: PresentationMode>? = { return .presentModally(controllerProvider: ControllerProvider.callback { [weak self] in let vc = SelectorAlertController(title: self?.selectorTitle, message: nil, preferredStyle: .alert) @@ -34,7 +34,7 @@ open class _AlertRow: OptionsRow, PresenterRowType where C return vc }, onDismiss: { [weak self] in $0.dismiss(animated: true) - self?.cell?.formViewController()?.tableView?.reloadData() + self?.cell?.parentTableView()?.reloadData() } ) }() @@ -48,11 +48,11 @@ open class _AlertRow: OptionsRow, PresenterRowType where C if let presentationMode = presentationMode, !isDisabled { if let controller = presentationMode.makeController(){ controller.row = self - onPresentCallback?(cell.formViewController()!, controller) - presentationMode.present(controller, row: self, presentingController: cell.formViewController()!) + onPresentCallback?(cell.viewController()!, controller) + presentationMode.present(controller, row: self, presentingController: cell.viewController()!) } else{ - presentationMode.present(nil, row: self, presentingController: cell.formViewController()!) + presentationMode.present(nil, row: self, presentingController: cell.viewController()!) } } } diff --git a/Source/Rows/ButtonRow.swift b/Source/Rows/ButtonRow.swift index df6a9c879..18a56cced 100644 --- a/Source/Rows/ButtonRow.swift +++ b/Source/Rows/ButtonRow.swift @@ -73,10 +73,10 @@ open class _ButtonRowOf : Row> { if !isDisabled { if let presentationMode = presentationMode { if let controller = presentationMode.makeController(){ - presentationMode.present(controller, row: self, presentingController: self.cell.formViewController()!) + presentationMode.present(controller, row: self, presentingController: self.cell.viewController()!) } else{ - presentationMode.present(nil, row: self, presentingController: self.cell.formViewController()!) + presentationMode.present(nil, row: self, presentingController: self.cell.viewController()!) } } } diff --git a/Source/Rows/ButtonRowWithPresent.swift b/Source/Rows/ButtonRowWithPresent.swift index 2c849cb6a..662ceb478 100644 --- a/Source/Rows/ButtonRowWithPresent.swift +++ b/Source/Rows/ButtonRowWithPresent.swift @@ -27,7 +27,7 @@ import Foundation open class _ButtonRowWithPresent: Row>, PresenterRowType where VCType: UIViewController { open var presentationMode: PresentationMode? - open var onPresentCallback : ((FormViewController, VCType)->())? + open var onPresentCallback : ((UIViewController, VCType)->())? required public init(tag: String?) { super.init(tag: tag) @@ -56,11 +56,11 @@ open class _ButtonRowWithPresent: Row: Row : Cell, UITextFieldDelegate, TextFieldCell where T: NotificationCenter.default.addObserver(forName: Notification.Name.UIApplicationDidBecomeActive, object: nil, queue: nil){ [weak self] notification in self?.titleLabel?.addObserver(self!, forKeyPath: "text", options: NSKeyValueObservingOptions.old.union(.new), context: nil) } - + + + NotificationCenter.default.addObserver(forName: Notification.Name.UIApplicationWillResignActive, object: nil, queue: nil){ [weak self] notification in + guard let me = self else { return } + me.titleLabel?.removeObserver(me, forKeyPath: "attributedText") + } + NotificationCenter.default.addObserver(forName: Notification.Name.UIApplicationDidBecomeActive, object: nil, queue: nil){ [weak self] notification in + self?.titleLabel?.addObserver(self!, forKeyPath: "attributedText", options: NSKeyValueObservingOptions.old.union(.new), context: nil) + } + + NotificationCenter.default.addObserver(forName: Notification.Name.UIContentSizeCategoryDidChange, object: nil, queue: nil){ [weak self] notification in self?.setNeedsUpdateConstraints() } @@ -162,6 +172,7 @@ open class _FieldCell : Cell, UITextFieldDelegate, TextFieldCell where T: textField.delegate = nil textField.removeTarget(self, action: nil, for: .allEvents) titleLabel?.removeObserver(self, forKeyPath: "text") + titleLabel?.removeObserver(self, forKeyPath: "attributedText") imageView?.removeObserver(self, forKeyPath: "image") NotificationCenter.default.removeObserver(self, name: Notification.Name.UIApplicationWillResignActive, object: nil) NotificationCenter.default.removeObserver(self, name: Notification.Name.UIApplicationDidBecomeActive, object: nil) @@ -175,9 +186,9 @@ open class _FieldCell : Cell, UITextFieldDelegate, TextFieldCell where T: contentView.addSubview(textField) titleLabel?.addObserver(self, forKeyPath: "text", options: NSKeyValueObservingOptions.old.union(.new), context: nil) + titleLabel?.addObserver(self, forKeyPath: "attributedText", options: NSKeyValueObservingOptions.old.union(.new), context: nil) imageView?.addObserver(self, forKeyPath: "image", options: NSKeyValueObservingOptions.old.union(.new), context: nil) textField.addTarget(self, action: #selector(_FieldCell.textFieldDidChange(_:)), for: .editingChanged) - } open override func update() { @@ -187,6 +198,10 @@ open class _FieldCell : Cell, UITextFieldDelegate, TextFieldCell where T: textField.textAlignment = title.isEmpty ? .left : .right textField.clearButtonMode = title.isEmpty ? .whileEditing : .never } + else if let title = row.attributedTitle { + textField.textAlignment = title.string.isEmpty ? .left : .right + textField.clearButtonMode = title.string.isEmpty ? .whileEditing : .never + } else{ textField.textAlignment = .left textField.clearButtonMode = .whileEditing @@ -224,7 +239,7 @@ open class _FieldCell : Cell, UITextFieldDelegate, TextFieldCell where T: open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { let obj = object as AnyObject? - if let keyPathValue = keyPath, let changeType = change?[NSKeyValueChangeKey.kindKey], ((obj === titleLabel && keyPathValue == "text") || (obj === imageView && keyPathValue == "image")) && (changeType as? NSNumber)?.uintValue == NSKeyValueChange.setting.rawValue { + if let keyPathValue = keyPath, let changeType = change?[NSKeyValueChangeKey.kindKey], ((obj === titleLabel && keyPathValue == "text") || (obj === titleLabel && keyPathValue == "attributedText") || (obj === imageView && keyPathValue == "image")) && (changeType as? NSNumber)?.uintValue == NSKeyValueChange.setting.rawValue { setNeedsUpdateConstraints() updateConstraintsIfNeeded() } @@ -308,7 +323,7 @@ open class _FieldCell : Cell, UITextFieldDelegate, TextFieldCell where T: //Mark: Helpers - private func displayValue(useFormatter: Bool) -> String? { + open func displayValue(useFormatter: Bool) -> String? { guard let v = row.value else { return nil } if let formatter = (row as? FormatterConformance)?.formatter, useFormatter { return textField.isFirstResponder ? formatter.editingString(for: v) : formatter.string(for: v) @@ -319,8 +334,8 @@ open class _FieldCell : Cell, UITextFieldDelegate, TextFieldCell where T: //MARK: TextFieldDelegate open func textFieldDidBeginEditing(_ textField: UITextField) { - formViewController()?.beginEditing(of: self) - formViewController()?.textInputDidBeginEditing(textField, cell: self) + formViewDelegate?.beginEditing(of: self) + formViewDelegate?.textInputDidBeginEditing(textField, cell: self) if let fieldRowConformance = row as? FormatterConformance, let _ = fieldRowConformance.formatter, fieldRowConformance.useFormatterOnDidBeginEditing ?? fieldRowConformance.useFormatterDuringInput { textField.text = displayValue(useFormatter: true) } else { @@ -329,30 +344,30 @@ open class _FieldCell : Cell, UITextFieldDelegate, TextFieldCell where T: } open func textFieldDidEndEditing(_ textField: UITextField) { - formViewController()?.endEditing(of: self) - formViewController()?.textInputDidEndEditing(textField, cell: self) + formViewDelegate?.endEditing(of: self) + formViewDelegate?.textInputDidEndEditing(textField, cell: self) textFieldDidChange(textField) textField.text = displayValue(useFormatter: (row as? FormatterConformance)?.formatter != nil) } open func textFieldShouldReturn(_ textField: UITextField) -> Bool { - return formViewController()?.textInputShouldReturn(textField, cell: self) ?? true + return formViewDelegate?.textInputShouldReturn(textField, cell: self) ?? true } open func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - return formViewController()?.textInput(textField, shouldChangeCharactersInRange:range, replacementString:string, cell: self) ?? true + return formViewDelegate?.textInput(textField, shouldChangeCharactersInRange:range, replacementString:string, cell: self) ?? true } open func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - return formViewController()?.textInputShouldBeginEditing(textField, cell: self) ?? true + return formViewDelegate?.textInputShouldBeginEditing(textField, cell: self) ?? true } open func textFieldShouldClear(_ textField: UITextField) -> Bool { - return formViewController()?.textInputShouldClear(textField, cell: self) ?? true + return formViewDelegate?.textInputShouldClear(textField, cell: self) ?? true } open func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { - return formViewController()?.textInputShouldEndEditing(textField, cell: self) ?? true + return formViewDelegate?.textInputShouldEndEditing(textField, cell: self) ?? true } diff --git a/Source/Rows/Common/GenericMultipleSelectorRow.swift b/Source/Rows/Common/GenericMultipleSelectorRow.swift index e2695d442..da0c1b87c 100644 --- a/Source/Rows/Common/GenericMultipleSelectorRow.swift +++ b/Source/Rows/Common/GenericMultipleSelectorRow.swift @@ -33,7 +33,7 @@ open class GenericMultipleSelectorRow? /// Will be called before the presentation occurs. - open var onPresentCallback : ((FormViewController, VCType)->())? + open var onPresentCallback : ((UIViewController, VCType)->())? /// Title to be displayed for the options open var selectorTitle: String? @@ -64,11 +64,11 @@ open class GenericMultipleSelectorRow: OptionsR open var presentationMode: PresentationMode? /// Will be called before the presentation occurs. - open var onPresentCallback : ((FormViewController, VCType)->())? + open var onPresentCallback : ((UIViewController, VCType)->())? required public init(tag: String?) { super.init(tag: tag) @@ -64,11 +64,11 @@ open class SelectorRow: OptionsR if let controller = presentationMode.makeController(){ controller.row = self controller.title = selectorTitle ?? controller.title - onPresentCallback?(cell.formViewController()!, controller) - presentationMode.present(controller, row: self, presentingController: self.cell.formViewController()!) + onPresentCallback?(cell.viewController()!, controller) + presentationMode.present(controller, row: self, presentingController: self.cell.viewController()!) } else{ - presentationMode.present(nil, row: self, presentingController: self.cell.formViewController()!) + presentationMode.present(nil, row: self, presentingController: self.cell.viewController()!) } } @@ -80,7 +80,7 @@ open class SelectorRow: OptionsR guard let rowVC = segue.destination as? VCType else { return } rowVC.title = selectorTitle ?? rowVC.title rowVC.onDismissCallback = presentationMode?.onDismissCallback ?? rowVC.onDismissCallback - onPresentCallback?(cell.formViewController()!, rowVC) + onPresentCallback?(cell.viewController()!, rowVC) rowVC.row = self } } diff --git a/Source/Rows/PopoverSelectorRow.swift b/Source/Rows/PopoverSelectorRow.swift index c73c6baf5..6544507db 100644 --- a/Source/Rows/PopoverSelectorRow.swift +++ b/Source/Rows/PopoverSelectorRow.swift @@ -29,7 +29,7 @@ open class _PopoverSelectorRow : SelectorRow Void in - guard let porpoverController = viewController.popoverPresentationController, let tableView = self?.baseCell.formViewController()?.tableView, let cell = self?.cell else { + guard let porpoverController = viewController.popoverPresentationController, let tableView = self?.baseCell.parentTableView(), let cell = self?.cell else { fatalError() } porpoverController.sourceView = tableView diff --git a/Source/Rows/TextAreaRow.swift b/Source/Rows/TextAreaRow.swift index dcf59ce51..771e10367 100644 --- a/Source/Rows/TextAreaRow.swift +++ b/Source/Rows/TextAreaRow.swift @@ -149,8 +149,8 @@ open class _TextAreaCell : Cell, UITextViewDelegate, AreaCell where T: Equ open func textViewDidBeginEditing(_ textView: UITextView) { - formViewController()?.beginEditing(of: self) - formViewController()?.textInputDidBeginEditing(textView, cell: self) + formViewDelegate?.beginEditing(of: self) + formViewDelegate?.textInputDidBeginEditing(textView, cell: self) if let textAreaConformance = (row as? TextAreaConformance), let _ = textAreaConformance.formatter, textAreaConformance.useFormatterOnDidBeginEditing ?? textAreaConformance.useFormatterDuringInput { textView.text = self.displayValue(useFormatter: true) } @@ -160,15 +160,15 @@ open class _TextAreaCell : Cell, UITextViewDelegate, AreaCell where T: Equ } open func textViewDidEndEditing(_ textView: UITextView) { - formViewController()?.endEditing(of: self) - formViewController()?.textInputDidEndEditing(textView, cell: self) + formViewDelegate?.endEditing(of: self) + formViewDelegate?.textInputDidEndEditing(textView, cell: self) textViewDidChange(textView) textView.text = displayValue(useFormatter: (row as? FormatterConformance)?.formatter != nil) } open func textViewDidChange(_ textView: UITextView) { - if let textAreaConformance = row as? TextAreaConformance, case .dynamic = textAreaConformance.textAreaHeight, let tableView = formViewController()?.tableView { + if let textAreaConformance = row as? TextAreaConformance, case .dynamic = textAreaConformance.textAreaHeight, let tableView = parentTableView() { let currentOffset = tableView.contentOffset UIView.setAnimationsEnabled(false) tableView.beginUpdates() @@ -208,15 +208,15 @@ open class _TextAreaCell : Cell, UITextViewDelegate, AreaCell where T: Equ } open func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - return formViewController()?.textInput(textView, shouldChangeCharactersInRange: range, replacementString: text, cell: self) ?? true + return formViewDelegate?.textInput(textView, shouldChangeCharactersInRange: range, replacementString: text, cell: self) ?? true } open func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { - return formViewController()?.textInputShouldBeginEditing(textView, cell: self) ?? true + return formViewDelegate?.textInputShouldBeginEditing(textView, cell: self) ?? true } open func textViewShouldEndEditing(_ textView: UITextView) -> Bool { - return formViewController()?.textInputShouldEndEditing(textView, cell: self) ?? true + return formViewDelegate?.textInputShouldEndEditing(textView, cell: self) ?? true } open override func updateConstraints(){