diff --git a/Examples/SampleApp/SampleApp/ActionBar/ActionBarManager.swift b/Examples/SampleApp/SampleApp/ActionBar/ActionBarManager.swift index 54c4724..c68f73d 100644 --- a/Examples/SampleApp/SampleApp/ActionBar/ActionBarManager.swift +++ b/Examples/SampleApp/SampleApp/ActionBar/ActionBarManager.swift @@ -2,24 +2,24 @@ import Foundation import TableViewKit class ActionBarManager: ActionBarDelegate { - + let manager: TableViewManager - + init(manager: TableViewManager) { self.manager = manager } - + public func actionBar(_ actionBar: ActionBar, direction: Direction) { guard let indexPath = indexPathForResponder(forDirection: direction) else { return } - + manager.tableView.scrollToRow(at: indexPath, at: .top, animated: true) manager.tableView.cellForRow(at: indexPath)?.becomeFirstResponder() } - + public func actionBar(_ actionBar: ActionBar, doneButtonPressed doneButtonItem: UIBarButtonItem) { } - + fileprivate func indexPathForResponder(forDirection direction: Direction) -> IndexPath? { - + func isFirstResponder(item: Item) -> Bool { if isResponder(item: item), let indexPath = item.indexPath(in: manager), @@ -28,26 +28,26 @@ class ActionBarManager: ActionBarDelegate { } return false } - + func isResponder(item: Item) -> Bool { return (item as? UIResponder)?.canBecomeFirstResponder ?? false } - + let array = manager.sections.flatMap { $0.items } guard let currentItem = array.first(where: isFirstResponder), let index = array.index(of: currentItem) else { return nil } - + let item: Item? - + switch direction { case .next: item = array.suffix(from: index).dropFirst().first(where: isResponder) case .previous: item = array.prefix(upTo: index).reversed().first(where: isResponder) } - + return item?.indexPath(in: manager) - + } } diff --git a/Examples/SampleApp/SampleApp/AppDelegate.swift b/Examples/SampleApp/SampleApp/AppDelegate.swift index 0c50554..31c25a6 100644 --- a/Examples/SampleApp/SampleApp/AppDelegate.swift +++ b/Examples/SampleApp/SampleApp/AppDelegate.swift @@ -7,7 +7,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: Any]?) -> Bool { // Override point for customization after application launch. - + return true } @@ -33,6 +33,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - } - diff --git a/Examples/SampleApp/SampleApp/CustomCell.swift b/Examples/SampleApp/SampleApp/CustomCell.swift index b90b3ee..bd19dbc 100644 --- a/Examples/SampleApp/SampleApp/CustomCell.swift +++ b/Examples/SampleApp/SampleApp/CustomCell.swift @@ -2,8 +2,8 @@ import UIKit import TableViewKit class CustomCell: TableViewCell { - + override func awakeFromNib() { super.awakeFromNib() } -} \ No newline at end of file +} diff --git a/Examples/SampleApp/SampleApp/CustomHeader.swift b/Examples/SampleApp/SampleApp/CustomHeader.swift index 8c69e0a..dc6036b 100644 --- a/Examples/SampleApp/SampleApp/CustomHeader.swift +++ b/Examples/SampleApp/SampleApp/CustomHeader.swift @@ -2,33 +2,28 @@ import UIKit import TableViewKit public class CustomHeaderFooterView: UITableViewHeaderFooterView { - + @IBOutlet weak var label: UILabel! } - public class CustomHeaderDrawer: HeaderFooterDrawer { - + public static let nib = UINib(nibName: String(describing: CustomHeaderFooterView.self), bundle: nil) static public var type = HeaderFooterType.nib(CustomHeaderDrawer.nib, CustomHeaderFooterView.self) - - static public func draw(_ view: UITableViewHeaderFooterView, with item: Any) { - let item = item as! CustomHeaderItem - let view = view as! CustomHeaderFooterView + + static public func draw(_ view: CustomHeaderFooterView, with item: CustomHeaderItem) { view.label.text = item.title } } - public class CustomHeaderItem: HeaderFooter { - + public static var drawer = AnyHeaderFooterDrawer(CustomHeaderDrawer.self) + public var title: String? public var height: Height? = .dynamic(44.0) - - public var drawer: HeaderFooterDrawer.Type = CustomHeaderDrawer.self - + public init() { } - + public convenience init(title: String) { self.init() self.title = title diff --git a/Examples/SampleApp/SampleApp/CustomItem.swift b/Examples/SampleApp/SampleApp/CustomItem.swift index efd548f..647416c 100644 --- a/Examples/SampleApp/SampleApp/CustomItem.swift +++ b/Examples/SampleApp/SampleApp/CustomItem.swift @@ -3,38 +3,35 @@ import UIKit import TableViewKit public class CustomDrawer: CellDrawer { - - static public var type = CellType.class(UITableViewCell.self) - - static public func draw(_ cell: UITableViewCell, with item: Any) { - let item = item as! CustomItem + + public static var type = CellType.class(UITableViewCell.self) + + public static func draw(_ cell: UITableViewCell, with item: CustomItem) { cell.accessoryType = item.accessoryType cell.accessoryView = item.accessoryView cell.textLabel?.text = item.title } } - public class CustomItem: Selectable, Item { - + public static var drawer = AnyCellDrawer(CustomDrawer.self) + public var title: String? - - public var onSelection: (Selectable) -> () = { _ in } - + + public var onSelection: (Selectable) -> Void = { _ in } + public var cellStyle: UITableViewCellStyle = .default public var accessoryType: UITableViewCellAccessoryType = .none public var accessoryView: UIView? public var cellHeight: CGFloat? = UITableViewAutomaticDimension - - public var drawer: CellDrawer.Type = CustomDrawer.self - + public init() { } - + public convenience init(title: String) { self.init() self.title = title } - + public func didSelect() { onSelection(self) } diff --git a/Examples/SampleApp/SampleApp/Example1.swift b/Examples/SampleApp/SampleApp/Example1.swift index 88d5a8b..a16559e 100644 --- a/Examples/SampleApp/SampleApp/Example1.swift +++ b/Examples/SampleApp/SampleApp/Example1.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import TableViewKit - public protocol TableViewManagerCompatible { var tableViewManager: TableViewManager! { get } } @@ -15,23 +14,23 @@ class Example1: UIViewController, TableViewManagerCompatible { fileprivate class FirstSection: Section, StaticStateful { var items: ObservableArray = [] var states: [State: [Item]] = [:] - + enum State: Int { case preParty case onParty case afterParty } - + var currentState: State = .preParty - + let vc: Example1 - + internal var header: HeaderFooterView = .view(CustomHeaderItem(title: "First Section")) internal var footer: HeaderFooterView = .view(CustomHeaderItem(title: "Section Footer\nHola")) - + required init(vc: Example1) { self.vc = vc - + let item = CustomItem(title: "Passengers") let item2 = CustomItem(title: "Testing") let item3 = CustomItem(title: "Testing 2") @@ -41,7 +40,7 @@ class Example1: UIViewController, TableViewManagerCompatible { textFieldItem.validation.add(rule: ExistRule()) let textFieldItem2 = TextFieldItem(placeHolder: "Surname", actionBarDelegate: vc.actionBarManager) textFieldItem2.validation.add(rule: ExistRule()) - + item.onSelection = { item in item.deselect(in: self.vc.tableViewManager, animated: true) self.vc.showPickerControl() @@ -61,21 +60,20 @@ class Example1: UIViewController, TableViewManagerCompatible { states[State.afterParty] = [item2] transition(to: currentState) } - - + } - + fileprivate class SecondSection: Section { - + var items: ObservableArray = [] - + internal var header: HeaderFooterView = .view(CustomHeaderItem(title: "Second Section")) - + let vc: Example1 - + required init(vc: Example1) { self.vc = vc - + let total: [Int] = Array(1...100) let items = total.map({ (index) -> Item in if (index % 2 == 0) { @@ -92,23 +90,23 @@ class Example1: UIViewController, TableViewManagerCompatible { self.items.insert(contentsOf: items, at: 0) } } - + fileprivate class ThirdSection: Section, Stateful { enum State { case all case selected(Item) } - + var items: ObservableArray = [] var allItems: [Item] = [] var currentState: State = .all - + let vc: TableViewManagerCompatible - + required init(vc: TableViewManagerCompatible) { self.vc = vc - + let total: [Int] = Array(1...10) self.allItems = total.map { (index) -> Item in let item = CustomItem(title: "Label \(index)") @@ -125,7 +123,7 @@ class Example1: UIViewController, TableViewManagerCompatible { } self.transition(to: currentState) } - + func items(for state: State) -> [Item] { switch state { case .all: @@ -136,16 +134,12 @@ class Example1: UIViewController, TableViewManagerCompatible { } } - - - - @IBOutlet weak var tableView: UITableView! { didSet { tableViewManager = TableViewManager(tableView: tableView) } } - + var tableViewManager: TableViewManager! { didSet { actionBarManager = ActionBarManager(manager: tableViewManager) @@ -153,72 +147,69 @@ class Example1: UIViewController, TableViewManagerCompatible { } var actionBarManager: ActionBarManager! var validator: ValidatorManager = ValidatorManager() - - + var pickerControl: PickerControl? - + fileprivate var firstSection: FirstSection! - + override func viewDidLoad() { - + super.viewDidLoad() - - self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension; - self.tableView.sectionFooterHeight = UITableViewAutomaticDimension; - self.tableView.estimatedSectionHeaderHeight = 100; - self.tableView.estimatedSectionFooterHeight = 100; - - + + self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension + self.tableView.sectionFooterHeight = UITableViewAutomaticDimension + self.tableView.estimatedSectionHeaderHeight = 100 + self.tableView.estimatedSectionFooterHeight = 100 + firstSection = FirstSection(vc: self) tableViewManager.sections.append(firstSection) tableViewManager.sections.append(ThirdSection(vc: self)) tableViewManager.sections.append(SecondSection(vc: self)) - + // TODO think about a better way to handle self registration // In this way we do not take in consideration when the items changes tableViewManager.sections .flatMap { $0.items } .flatMap { $0 as? Validationable } .forEach { validator.add(validation: $0.validation) } - + navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Validate", style: .plain, target: self, action: #selector(validationAction)) navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(done)) } - - + fileprivate func showPickerControl() { - + var elements = [PickerItem]() - + let options = ["Option 1", "Option 2", "Option 3"] options.forEach { elements.append(PickerItem(title: $0, value: $0)) } - + let pickerControl = PickerControl(elements: elements, selectCallback: { pickerControl, selectedElement in print(selectedElement) pickerControl.dismissPickerView() self.pickerControl = nil }) pickerControl.presentPickerOnView(view) - + self.pickerControl = pickerControl } - + fileprivate func showDatePickerControl() { - + let toDate = Calendar.current.date(byAdding: .year, value: 1, to: Date())! let pickerDateControl = PickerControl(datePickerMode: .date, fromDate: Date(), toDate: toDate, minuteInterval: 0, selectCallback: { pickerControl, date in - + pickerControl.dismissPickerView() print(date) - + self.pickerControl = nil - + }, cancelCallback: nil) pickerDateControl.presentPickerOnView(view) - + pickerControl = pickerDateControl } - + @objc fileprivate func validationAction() { firstSection.transition(to: firstSection.currentState == .preParty ? .onParty : .afterParty) guard let error = validator.errors.first else { @@ -227,9 +218,9 @@ class Example1: UIViewController, TableViewManagerCompatible { } print(error) } - + @objc fileprivate func done() { dismiss(animated: true, completion: nil) } - + } diff --git a/Examples/SampleApp/SampleApp/PickerControl.swift b/Examples/SampleApp/SampleApp/PickerControl.swift index 143ad3d..85fa1a2 100644 --- a/Examples/SampleApp/SampleApp/PickerControl.swift +++ b/Examples/SampleApp/SampleApp/PickerControl.swift @@ -3,26 +3,26 @@ import UIKit import TableViewKit struct Constants { - + enum Frame { - + static let PickerHeight = 260.0 static let PickerViewHeight = 216.0 static let NavigationBarHeight = 44.0 static let HeaderViewHeight = 44.0 } - + enum Animation { - + static let Duration = 0.3 } } public protocol PickerItemProtocol: class { - + var title: String { get } var value: Any { get } - + init(title: String, value: Any) } @@ -35,240 +35,236 @@ public enum PickerControlDismissType { } open class PickerItem: PickerItemProtocol, CustomStringConvertible { - + open var title: String open var value: Any - + public required init(title: String, value: Any) { self.title = title self.value = value } - + open var description: String { return title } } -public typealias SelectCallBack = (PickerControl, Any) -> () -public typealias CancelCallBack = (PickerControl) -> () +public typealias SelectCallBack = (PickerControl, Any) -> Void +public typealias CancelCallBack = (PickerControl) -> Void open class PickerControl: NSObject { - + fileprivate var type: PickerControlType fileprivate var dismissType: PickerControlDismissType - + // Single columns fileprivate var items: [PickerItemProtocol]! fileprivate var selection: PickerItemProtocol! - + // Multicolumn fileprivate var components: [[PickerItemProtocol]]! fileprivate var selections: [PickerItemProtocol?]! - + // Date fileprivate var dateSelection: Date? - + fileprivate var pickerView: UIPickerView? fileprivate var datePicker: UIDatePicker? - + fileprivate var overlayLayerView: UIView! fileprivate var pickerContainerView: UIView! fileprivate var navigationBar: UINavigationBar! - + open var headerView: UIView? open var cancelButtonItem: UIBarButtonItem! open var okButtonItem: UIBarButtonItem! - + open var title: String? open var selectCallback: SelectCallBack? open var cancelCallback: CancelCallBack? - + // MARK: Constructors - + public override init() { - + type = .single dismissType = .cancel - + items = [] components = [] - + overlayLayerView = UIView() overlayLayerView.isUserInteractionEnabled = true overlayLayerView.backgroundColor = UIColor(white: 0, alpha: 0.7) - + pickerContainerView = UIView() pickerContainerView.backgroundColor = UIColor.white - + super.init() - + pickerView = UIPickerView() pickerView?.dataSource = self pickerView?.delegate = self pickerView?.autoresizingMask = .flexibleWidth - + datePicker = UIDatePicker() datePicker?.autoresizingMask = .flexibleWidth datePicker?.addTarget(self, action: #selector(datePickerDidChangeValue), for: .valueChanged) - + navigationBar = UINavigationBar() - + cancelButtonItem = UIBarButtonItem(title: NSLocalizedString("Cancel", comment: ""), style: .plain, target: self, action: #selector(cancelButtonPressed)) okButtonItem = UIBarButtonItem(title: NSLocalizedString("OK", comment: ""), style: .plain, target: self, action: #selector(selectButtonPressed)) } - + public convenience init(elements: [AnyObject], selectCallback: SelectCallBack? = nil, cancelCallback: CancelCallBack? = nil) { - + self.init() - + for value in elements { - + // Each item is PickerItem if value is PickerItemProtocol { items.append(value as! PickerItemProtocol) } // We have a array else if let array = value as? [AnyObject] { - + type = .multiColumn - + // Column items var components: [PickerItemProtocol] = [] - + for element in array { - + if element is PickerItemProtocol { components.append(element as! PickerItemProtocol) - } - else { + } else { let item = PickerItem(title: String(describing: element), value: element) components.append(item) } } - + if components.count != 0 { self.components.append(components) self.selections = Array.init(repeating: nil, count: self.components.count) } - } - else { + } else { let item = PickerItem(title: String(describing: value), value: value) items.append(item) } } - + self.selectCallback = selectCallback self.cancelCallback = cancelCallback } - + public convenience init(datePickerMode: UIDatePickerMode, fromDate: Date, toDate: Date, minuteInterval: Int, selectCallback: SelectCallBack? = nil, cancelCallback: CancelCallBack? = nil) { - + self.init() - + type = .date - + datePicker?.minimumDate = fromDate datePicker?.maximumDate = toDate datePicker?.minuteInterval = minuteInterval datePicker?.datePickerMode = datePickerMode - + self.selectCallback = selectCallback self.cancelCallback = cancelCallback } - + // MARK: Public methods - + open func presentPickerOnView(_ view: UIView) { - + let hostFrame = view.window!.frame - + // Hide keyboard view.endEditing(true) - + overlayLayerView.alpha = 0 overlayLayerView.frame = hostFrame view.window?.addSubview(overlayLayerView) - + // Add gesture if dismissType == .select { let layerTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(selectButtonPressed)) overlayLayerView.addGestureRecognizer(layerTapRecognizer) - } - else if dismissType == .cancel { + } else if dismissType == .cancel { let layerTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(cancelButtonPressed)) overlayLayerView.addGestureRecognizer(layerTapRecognizer) } - + let extraHeight = headerView != nil ? CGFloat(Constants.Frame.HeaderViewHeight) : 0 let pickerContainerSourceFrame = CGRect(x: 0, y: hostFrame.height, width: hostFrame.width, height: CGFloat(Constants.Frame.PickerHeight) + extraHeight) pickerContainerView.frame = pickerContainerSourceFrame view.window?.addSubview(pickerContainerView) - + // Add toolbar navigationBar.frame = CGRect(x: 0, y: 0, width: hostFrame.width, height: CGFloat(Constants.Frame.NavigationBarHeight)) pickerContainerView.addSubview(navigationBar) - + let spacer = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) spacer.width = 15 - + let navigationItem = UINavigationItem() navigationItem.title = title navigationItem.leftBarButtonItems = [spacer, cancelButtonItem] navigationItem.rightBarButtonItems = [spacer, okButtonItem] navigationBar.pushItem(navigationItem, animated: false) - + if let headerView = headerView { - + headerView.frame = CGRect(x: 0, y: navigationBar.frame.maxY, width: hostFrame.width, height: CGFloat(Constants.Frame.NavigationBarHeight)) pickerContainerView.addSubview(headerView) } - + let pickerViewFrame = CGRect(x: 0, y: CGFloat(Constants.Frame.NavigationBarHeight) + extraHeight, width: hostFrame.width, height: CGFloat(Constants.Frame.PickerViewHeight)) if type == .single || type == .multiColumn { pickerView?.frame = pickerViewFrame pickerContainerView.addSubview(pickerView!) - } - else if type == .date { + } else if type == .date { datePicker?.frame = pickerViewFrame pickerContainerView.addSubview(datePicker!) } - + UIView.animate(withDuration: Constants.Animation.Duration, animations: { self.overlayLayerView.alpha = 1.0 }) - + let pickerContainerDestinationFrame = CGRect(x: 0, y: hostFrame.height - CGFloat(Constants.Frame.PickerHeight) - extraHeight, width: hostFrame.width, height: CGFloat(Constants.Frame.PickerHeight) + extraHeight) UIView.animate(withDuration: Constants.Animation.Duration, delay: 0, options: [.curveEaseOut], animations: { self.pickerContainerView.frame = pickerContainerDestinationFrame }, completion: nil) } - + open func dismissPickerView() { - + let pickerContainerDestinationFrame = CGRect(x: 0, y: pickerContainerView.frame.origin.y + CGFloat(Constants.Frame.PickerHeight), width: pickerContainerView.frame.size.width, height: CGFloat(Constants.Frame.PickerHeight)) - + UIView.animate(withDuration: Constants.Animation.Duration, animations: { self.overlayLayerView.alpha = 0 - }, completion: { finished in + }, completion: { _ in self.overlayLayerView.removeFromSuperview() if let gesture = self.overlayLayerView.gestureRecognizers?.first { self.overlayLayerView.removeGestureRecognizer(gesture) } }) - + UIView.animate(withDuration: Constants.Animation.Duration, delay: 0, options: [.curveEaseIn], animations: { self.pickerContainerView.frame = pickerContainerDestinationFrame - }, completion: { finished in + }, completion: { _ in self.pickerContainerView.removeFromSuperview() }) } - + open func selectValue(_ value: PickerItemProtocol) { - + if type != .single { return } - + for index in 0 ..< items.count { let item = items[index] if item === value { @@ -277,13 +273,13 @@ open class PickerControl: NSObject { } } } - + open func selectValue(_ value: PickerItemProtocol, component: Int) { - + if type != .multiColumn { return } - + let column = components[component] for index in 0 ..< column.count { let item = column[index] @@ -293,158 +289,149 @@ open class PickerControl: NSObject { } } } - + open func selectValue(_ index: Int) { - + if type != .single { return } - + pickerView?.selectRow(index, inComponent: 0, animated: false) } - + open func selectValue(_ index: Int, component: Int) { - + if type != .multiColumn { return } - + pickerView?.selectRow(index, inComponent: component, animated: false) } - + open func selectDate(_ date: Date) { - + if type != .date { return } - + datePicker?.date = date } - + // MARK: Private methods - + fileprivate func updateSelection() { - + if type == .single { - + if items.count == 0 { return } - + let index = pickerView!.selectedRow(inComponent: 0) let item = items[index] selection = item - } - else if type == .multiColumn { - + } else if type == .multiColumn { + if components.count == 0 { return } - + for columnIndex in 0 ..< pickerView!.numberOfComponents { let rowIndex = pickerView!.selectedRow(inComponent: columnIndex) let item = components[columnIndex][rowIndex] selections[columnIndex] = item } - } - else if type == .date { - + } else if type == .date { + dateSelection = datePicker?.date } } - + // MARK: Selectors - + @objc func datePickerDidChangeValue() { - + dateSelection = datePicker?.date } - + @objc func cancelButtonPressed() { - + dismissPickerView() } - + @objc func selectButtonPressed() { - + updateSelection() - + if type == .single { - + selectCallback?(self, selection) - } - else if type == .multiColumn { - + } else if type == .multiColumn { + selectCallback?(self, selections) - } - else if type == .date { - + } else if type == .date { + selectCallback?(self, dateSelection) } - + dismissPickerView() } } extension PickerControl: UIPickerViewDataSource { - + @objc public func numberOfComponents(in pickerView: UIPickerView) -> Int { - + if type == .single { return 1 - } - else if type == .multiColumn { + } else if type == .multiColumn { return components.count } - + return 0 } - + @objc public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - + if type == .single { return items.count - } - else if type == .multiColumn { + } else if type == .multiColumn { let column = components[component] return column.count } - + return 0 } - + @objc public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { - + if type == .single { - + let item = items[row] return item.title - } - else if type == .multiColumn { - + } else if type == .multiColumn { + let column = components[component] let item = column[row] return item.title } - + return nil } } extension PickerControl: UIPickerViewDelegate { - + public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - + updateSelection() } } extension PickerControl: UIToolbarDelegate { - + public func position(for bar: UIBarPositioning) -> UIBarPosition { - + return .top } } - - diff --git a/Examples/SampleApp/SampleApp/SelectionViewController.swift b/Examples/SampleApp/SampleApp/SelectionViewController.swift index 721a5d5..d7302b8 100644 --- a/Examples/SampleApp/SampleApp/SelectionViewController.swift +++ b/Examples/SampleApp/SampleApp/SelectionViewController.swift @@ -10,10 +10,10 @@ class SelectionSection: Section { } public protocol SelectionItemProtocol: Item { - + var value: Any { get } var selected: Bool { get set } - + init(title: String, value: Any, selected: Bool) } @@ -21,21 +21,20 @@ public enum SelectionType { case Single, Multiple } -public class SelectionItem: SelectionItemProtocol { +public class SelectionItem: SelectionItemProtocol { + public static var drawer = AnyCellDrawer(CustomDrawer.self) + public var title: String? - + public var value: Any public var selected: Bool - - public var onSelection: (Selectable) -> () = { _ in } - + + public var onSelection: (Selectable) -> Void = { _ in } + public var accessoryType: UITableViewCellAccessoryType = .none public var accessoryView: UIView? public var cellHeight: CGFloat? = UITableViewAutomaticDimension - - public var drawer: CellDrawer.Type = CustomDrawer.self - public required init(title: String, value: Any, selected: Bool = false) { self.value = value self.selected = selected @@ -44,101 +43,101 @@ public class SelectionItem: SelectionItemProtocol { } public class SelectionViewController: UITableViewController { - + private var tableViewManager: TableViewManager! private var selectionType: SelectionType! private var selectedItems: [SelectionItem]! - + public var items: [SelectionItem]! - public var onSelection: (([SelectionItem]) -> ())? - + public var onSelection: (([SelectionItem]) -> Void)? + private func commonInit() { - + selectionType = .Single items = [] selectedItems = [] } - + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) commonInit() } - + public init(style: UITableViewStyle, selectionType: SelectionType) { - + super.init(style: style) commonInit() self.selectionType = selectionType } - + required public init?(coder aDecoder: NSCoder) { - + super.init(coder: aDecoder) commonInit() } - + public override func awakeFromNib() { super.awakeFromNib() commonInit() } - + override public func viewDidLoad() { - + super.viewDidLoad() - + tableViewManager = TableViewManager(tableView: self.tableView) setupTaleViewItems() } - + public override func viewWillDisappear(_ animated: Bool) { - + super.viewWillDisappear(animated) - + onSelection?(selectedItems) } - + private func fillSelected() { - + selectedItems = items.filter { $0.selected == true } } - + private func setupTaleViewItems() { - + let section = SelectionSection() tableViewManager.sections.insert(section, at: 0) - + for element in items { - + element.onSelection = { item in self.toogleItemCheck(item: item as! SelectionItem) } element.accessoryType = element.selected ? .checkmark : .none section.items.append(element) } - + fillSelected() } - + private func toogleItemCheck(item: SelectionItem) { - + if selectionType == .Single { - + if let checkedItem = itemSelected() { checkedItem.selected = false checkedItem.accessoryType = .none checkedItem.reload(in: tableViewManager, with: .fade) } } - + item.selected = !item.selected item.accessoryType = item.accessoryType == .checkmark ? .none : .checkmark item.reload(in: tableViewManager, with: .fade) - + fillSelected() } - + private func itemSelected() -> SelectionItem? { - + // for section in tableViewManager.sections { // let checkedItems = section.items.filter { $0.accessoryType == .Checkmark } // if checkedItems.count != 0 { diff --git a/Examples/SampleApp/SampleApp/TextFieldItem.swift b/Examples/SampleApp/SampleApp/TextFieldItem.swift index 89105e0..fd051f4 100644 --- a/Examples/SampleApp/SampleApp/TextFieldItem.swift +++ b/Examples/SampleApp/SampleApp/TextFieldItem.swift @@ -2,45 +2,45 @@ import Foundation import TableViewKit public class TextFieldCell: UITableViewCell, ItemCompatible, ActionBarDelegate { - + public var item: Item? - + public var textFieldItem: TextFieldItem { get { return item as! TextFieldItem } } - + @IBOutlet var textField: UITextField! - + override public func awakeFromNib() { - + super.awakeFromNib() - + selectionStyle = .none - + textField.addTarget(self, action: #selector(onTextChange), for: .editingChanged) textField.inputAccessoryView = ActionBar(delegate: self) } - + public func onTextChange(textField: UITextField) { textFieldItem.value = textField.text } - - public func actionBar(_ actionBar: ActionBar, direction: Direction) { + + public func actionBar(_ actionBar: ActionBar, direction: Direction) { textFieldItem.actionBarDelegate.actionBar(actionBar, direction: direction) } public func actionBar(_ actionBar: ActionBar, doneButtonPressed doneButtonItem: UIBarButtonItem) { textField.resignFirstResponder() } - + override open var isFirstResponder: Bool { get { return textField.isFirstResponder } } - + override open func becomeFirstResponder() -> Bool { return textField.becomeFirstResponder() } @@ -48,44 +48,40 @@ public class TextFieldCell: UITableViewCell, ItemCompatible, ActionBarDelegate { } public class TextFieldDrawer: CellDrawer { - + public static let nib = UINib(nibName: String(describing: TextFieldCell.self), bundle: nil) public static let type = CellType.nib(TextFieldDrawer.nib, TextFieldCell.self) - - public static func draw(_ cell: UITableViewCell, with item: Any) { - - let textCell = cell as! TextFieldCell - let textItem = item as! TextFieldItem - - textCell.textField.placeholder = textItem.placeHolder - textCell.textField.text = textItem.value + + public static func draw(_ cell: TextFieldCell, with item: TextFieldItem) { + cell.textField.placeholder = item.placeHolder + cell.textField.text = item.value } } public class TextFieldItem: UIResponder, Item, ContentValidatable, Validationable { - - public var drawer: CellDrawer.Type = TextFieldDrawer.self - + + public static var drawer = AnyCellDrawer(TextFieldDrawer.self) + public lazy var validation: Validation = { return Validation(forInput: self, withIdentifier: self) }() - + public var placeHolder: String? public var value: String? - + fileprivate let actionBarDelegate: ActionBarDelegate - + public init(placeHolder: String?, actionBarDelegate: ActionBarDelegate) { self.placeHolder = placeHolder self.actionBarDelegate = actionBarDelegate } - + public var validationContent: String? { get { return value } } - + override open var canBecomeFirstResponder: Bool { get { return true diff --git a/Examples/SampleApp/SampleApp/Validator/Validator.swift b/Examples/SampleApp/SampleApp/Validator/Validator.swift index 25d6086..34a592e 100644 --- a/Examples/SampleApp/SampleApp/Validator/Validator.swift +++ b/Examples/SampleApp/SampleApp/Validator/Validator.swift @@ -16,7 +16,6 @@ public protocol Validatable { func test(_ validationContent: Input) -> Bool } - public protocol Regexable { var regex: String { get } } diff --git a/Examples/SampleApp/SampleApp/ValidatorTests.swift b/Examples/SampleApp/SampleApp/ValidatorTests.swift index d239d9e..3a0f436 100644 --- a/Examples/SampleApp/SampleApp/ValidatorTests.swift +++ b/Examples/SampleApp/SampleApp/ValidatorTests.swift @@ -3,7 +3,7 @@ import TableViewKit import Nimble extension String: ContentValidatable { - + public var validationContent: String? { get { return self @@ -12,7 +12,7 @@ extension String: ContentValidatable { } extension Int: ContentValidatable { - + public var validationContent: Int { get { return self @@ -21,51 +21,51 @@ extension Int: ContentValidatable { } class ValidatorTests: XCTestCase { - + override func setUp() { super.setUp() } - + override func tearDown() { super.tearDown() } - + func testExistRule() { - + let value = "Test" - + let validation = Validation(forInput: value, withIdentifier: value) validation.add(rule: ExistRule()) - + var validator = ValidatorManager() validator.add(validation: validation) - + expect(validator.errors.count).to(equal(0)) } - + func testCharactersLengthRule() { - + let value = "Odigeo" - + let validation = Validation(forInput: value, withIdentifier: value) validation.add(rule: CharactersLengthRule(min: 3, max: 6)) - + var validator = ValidatorManager() validator.add(validation: validation) - + expect(validator.errors.count).to(equal(0)) } - + func testNumberBetweenRule() { - + let value: Int = 9 - + let validation = Validation(forInput: value, withIdentifier: value) validation.add(rule: NumberBetweenRule(min: 5, max: 10)) - + var validator = ValidatorManager() validator.add(validation: validation) - + expect(validator.errors.count).to(equal(0)) } } diff --git a/Examples/SampleApp/SampleApp/ViewController.swift b/Examples/SampleApp/SampleApp/ViewController.swift index 95e7556..49dba19 100644 --- a/Examples/SampleApp/SampleApp/ViewController.swift +++ b/Examples/SampleApp/SampleApp/ViewController.swift @@ -1,20 +1,19 @@ import UIKit import TableViewKit - class ViewController: UIViewController, TableViewManagerCompatible { - + fileprivate class CustomSection: Section { var items: ObservableArray let vc: ViewController - + required init(vc: ViewController) { self.vc = vc self.items = [] - + let array: [UIViewController.Type] = [Example1.self] - let mappedItems = array.map({ (className) -> Item in + let mappedItems = array.map({ (className) -> Item in let viewController = className.init(nibName: String(describing: className), bundle: nil) let navigationController = UINavigationController.init(rootViewController: viewController) @@ -27,7 +26,7 @@ class ViewController: UIViewController, TableViewManagerCompatible { } return item }) - + self.items.insert(contentsOf: mappedItems, at: 0) } } @@ -37,13 +36,11 @@ class ViewController: UIViewController, TableViewManagerCompatible { tableViewManager = TableViewManager(tableView: tableView, sections: [CustomSection(vc: self)]) } } - + var tableViewManager: TableViewManager! - + override func viewDidLoad() { super.viewDidLoad() - + } } - - diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutModuleProtocols.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutModuleProtocols.swift index 621275e..8fe2c63 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutModuleProtocols.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutModuleProtocols.swift @@ -2,18 +2,18 @@ import Foundation import UIKit protocol AboutWireFrameProtocol: class { - + func presentAboutViewModule(_ navigationController: UINavigationController) func presentHelpCenter() } protocol AboutPresenterProtocol: class { - + var router: AboutWireFrameProtocol? { get set } var view: AboutViewControllerProtocol? { get set } - + func showHelpCenter() - + func showFaq() func showContactUs() func showTermsAndConditions() @@ -23,8 +23,8 @@ protocol AboutPresenterProtocol: class { } protocol AboutViewControllerProtocol: class { - + var presenter: AboutPresenterProtocol? { get set } - + func presentMessage(_ message: String, title: String) } diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutPresenter.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutPresenter.swift index c28b581..62c2d2b 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutPresenter.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutPresenter.swift @@ -1,41 +1,41 @@ import Foundation class AboutPresenter: AboutPresenterProtocol { - + var router: AboutWireFrameProtocol? weak var view: AboutViewControllerProtocol? - + func showHelpCenter() { router?.presentHelpCenter() } - + func showFaq() { let message = "FAQ not implemented" view?.presentMessage(message, title: "Error") } - + func showContactUs() { let message = "Contact Us not implemented" view?.presentMessage(message, title: "Error") } - + func showTermsAndConditions() { let message = "Terms and Conditions not implemented" view?.presentMessage(message, title: "Error") } - + func showFeedback() { let message = "Feedback not implemented" view?.presentMessage(message, title: "Error") } - + func showShareApp() { let message = "Share the app not implemented" view?.presentMessage(message, title: "Error") } - + func showRateApp() { let message = "Rate the app not implemented" view?.presentMessage(message, title: "Error") } -} \ No newline at end of file +} diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutViewController.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutViewController.swift index 868f285..722f511 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutViewController.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutViewController.swift @@ -3,53 +3,53 @@ import UIKit import TableViewKit class AboutViewController: UITableViewController, AboutViewControllerProtocol { - + var presenter: AboutPresenterProtocol? var tableViewManager: TableViewManager? - + private var insertSectionButtonItem: UIBarButtonItem? private var moveSectionButtonItem: UIBarButtonItem? - + override func viewDidLoad() { super.viewDidLoad() - + title = "Info" - + insertSectionButtonItem = UIBarButtonItem(title: "Insert", style: .plain, target: self, action: #selector(insertSection)) moveSectionButtonItem = UIBarButtonItem(title: "Move", style: .plain, target: self, action: #selector(moveSection)) - + let helpSection = HelpCenterSection(presenter: presenter) let moreAboutSection = MoreAboutSection(presenter: presenter, manager: tableViewManager) - + tableViewManager = TableViewManager(tableView: self.tableView) tableViewManager?.sections.replace(with: [helpSection, moreAboutSection]) - + navigationItem.rightBarButtonItem = insertSectionButtonItem } - + func presentMessage(_ message: String, title: String) { - + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) present(alertController, animated: true, completion: nil) } - + func insertSection() { - + let newSection = OtherSection() tableViewManager?.sections.insert(newSection, at: 1) - + navigationItem.rightBarButtonItem = moveSectionButtonItem } - + func moveSection() { - + guard let section1 = tableViewManager?.sections[0], let section2 = tableViewManager?.sections[1], let section3 = tableViewManager?.sections[2] else { return } - + tableViewManager?.sections.replace(with: [section2, section3, section1]) - + navigationItem.rightBarButtonItem = nil } } diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutWireFrame.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutWireFrame.swift index 735732b..86c8fbf 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutWireFrame.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/AboutWireFrame.swift @@ -3,25 +3,25 @@ import UIKit import SafariServices class AboutWireFrame: AboutWireFrameProtocol { - + fileprivate var viewController: AboutViewController? - + func presentAboutViewModule(_ navigationController: UINavigationController) { - + let presenter = AboutPresenter() let view = AboutViewController(style: .grouped) - + // Connections presenter.router = self presenter.view = view view.presenter = presenter - + navigationController.pushViewController(view, animated: true) viewController = view } - + func presentHelpCenter() { - + let webViewController = SFSafariViewController(url: URL(string: "https://www.edreams.com")!) viewController?.navigationController?.pushViewController(webViewController, animated: true) } diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterCell.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterCell.swift index 569e934..6cec625 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterCell.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterCell.swift @@ -2,28 +2,23 @@ import Foundation import TableViewKit class HelpCenterDrawer: CellDrawer { - - static var type: CellType = CellType.nib(UINib(nibName: String(describing: HelpCenterCell.self), bundle: Bundle.main), HelpCenterCell.self) - - static func draw(_ cell: UITableViewCell, with item: Any) { - - guard let helpCenterCell = cell as? HelpCenterCell, - let helpCenterItem = item as? HelpCenterItem - else { return } - - helpCenterCell.selectionStyle = .none - helpCenterCell.titleLabel.text = helpCenterItem.title + + static var type = CellType.nib(UINib(nibName: String(describing: HelpCenterCell.self), bundle: Bundle.main), HelpCenterCell.self) + + static func draw(_ cell: HelpCenterCell, with item: HelpCenterItem) { + cell.selectionStyle = .none + cell.titleLabel.text = item.title } } class HelpCenterCell: UITableViewCell, ItemCompatible { - + public var item: Item? - + @IBOutlet var titleLabel: UILabel! @IBOutlet var detailLabel: UILabel! @IBOutlet var helpCenterButton: UIButton! - + @IBAction func helpCenterButtonSelected() { guard let helpCenterItem = item as? HelpCenterItem else { return } helpCenterItem.onHelpCenterButtonSelected?() diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterItem.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterItem.swift index 82555ba..f79d883 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterItem.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterItem.swift @@ -2,14 +2,14 @@ import Foundation import TableViewKit class HelpCenterItem: Item { - - var drawer: CellDrawer.Type = HelpCenterDrawer.self + static var drawer = AnyCellDrawer(HelpCenterDrawer.self) + var height: Height? = .dynamic(44.0) - + var title: String? var subtitles: [String]? - var onHelpCenterButtonSelected: (() -> ())? - + var onHelpCenterButtonSelected: (() -> Void)? + init() { title = "Find the answer to your questions" subtitles = ["Check my booking status", "Change my flight", "Check my baggage allowance"] diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterSection.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterSection.swift index 2f79bc9..b746067 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterSection.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/HelpCenterSection.swift @@ -2,20 +2,20 @@ import Foundation import TableViewKit class HelpCenterSection: Section { - + var items: ObservableArray = [] var header: HeaderFooterView = .title("How can we help you today?") let presenter: AboutPresenterProtocol? - + required init(presenter: AboutPresenterProtocol?) { - + self.presenter = presenter - + let helpCenterItem = HelpCenterItem() helpCenterItem.onHelpCenterButtonSelected = { self.presenter?.showHelpCenter() } - + items.append(helpCenterItem) } } diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutCell.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutCell.swift index 0cc685c..198e369 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutCell.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutCell.swift @@ -2,13 +2,10 @@ import Foundation import TableViewKit class MoreAboutDrawer: CellDrawer { - + static open var type = CellType.class(UITableViewCell.self) - - static open func draw(_ cell: UITableViewCell, with item: Any) { - - let item = item as! MoreAboutItem - + + static open func draw(_ cell: UITableViewCell, with item: MoreAboutItem) { cell.accessoryType = .disclosureIndicator cell.textLabel?.text = item.title } diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutItem.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutItem.swift index 1a5e1d6..c6e6e8f 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutItem.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutItem.swift @@ -2,9 +2,9 @@ import Foundation import TableViewKit enum MoreAboutItemType { - + case faq, contact, terms, feedback, share, rate - + func title() -> String { switch self { case .faq: @@ -24,13 +24,13 @@ enum MoreAboutItemType { } class MoreAboutItem: Item, Selectable, Editable { + public static var drawer = AnyCellDrawer(MoreAboutDrawer.self) var type: MoreAboutItemType var title: String? - - var drawer: CellDrawer.Type = MoreAboutDrawer.self - var onSelection: (Selectable) -> () = { _ in } - + + var onSelection: (Selectable) -> Void = { _ in } + var actions: [UITableViewRowAction]? weak var manager: TableViewManager? let presenter: AboutPresenterProtocol? @@ -41,7 +41,7 @@ class MoreAboutItem: Item, Selectable, Editable { self.type = type self.title = type.title() } - + func didSelect() { switch type { case .faq: @@ -57,7 +57,7 @@ class MoreAboutItem: Item, Selectable, Editable { case .rate: presenter?.showRateApp() } - + if let manager = manager { deselect(in: manager, animated: true) } diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutSection.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutSection.swift index c1b50db..7fc9922 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutSection.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/MoreAboutSection.swift @@ -2,31 +2,31 @@ import Foundation import TableViewKit class MoreAboutSection: Section { - + var items: ObservableArray = [] var header: HeaderFooterView = .title("More about eDreams") let presenter: AboutPresenterProtocol? weak var manager: TableViewManager? - + required init(presenter: AboutPresenterProtocol?, manager: TableViewManager?) { - + self.presenter = presenter self.manager = manager - - let moreAction = UITableViewRowAction(style: .normal, title: "More", handler: { action, indexPath in + + let moreAction = UITableViewRowAction(style: .normal, title: "More", handler: { _, _ in print("MoreAction executed") }) - let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete", handler: { action, indexPath in + let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete", handler: { _, indexPath in self.items.remove(at: indexPath.row) }) - + let types: [MoreAboutItemType] = [.faq, .contact, .terms, .feedback, .share, .rate] let items: [Item] = types.map { let moreAboutItem = MoreAboutItem(type: $0, presenter: presenter, manager: manager) moreAboutItem.actions = [deleteAction, moreAction] return moreAboutItem } - + self.items.replace(with: items) } } diff --git a/Examples/Viper/TableViewKit+VIPER/AboutModule/OtherSection.swift b/Examples/Viper/TableViewKit+VIPER/AboutModule/OtherSection.swift index 6242daa..aba9df0 100644 --- a/Examples/Viper/TableViewKit+VIPER/AboutModule/OtherSection.swift +++ b/Examples/Viper/TableViewKit+VIPER/AboutModule/OtherSection.swift @@ -10,9 +10,9 @@ import Foundation import TableViewKit class OtherSection: Section { - + var items: ObservableArray = [] - + init() { let item1 = MoreAboutItem(type: .contact, presenter: nil, manager: nil) items.append(item1) diff --git a/Examples/Viper/TableViewKit+VIPER/AppDelegate.swift b/Examples/Viper/TableViewKit+VIPER/AppDelegate.swift index bbe85bb..5083a79 100644 --- a/Examples/Viper/TableViewKit+VIPER/AppDelegate.swift +++ b/Examples/Viper/TableViewKit+VIPER/AppDelegate.swift @@ -6,17 +6,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - + let navigationController = UINavigationController() - + window = UIWindow(frame: UIScreen.main.bounds) window?.backgroundColor = UIColor.white window?.rootViewController = navigationController window?.makeKeyAndVisible() - + let mainModule = AboutWireFrame() mainModule.presentAboutViewModule(navigationController) - + return true } @@ -42,6 +42,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } - } - diff --git a/README.md b/README.md index f225626..2417e8b 100644 --- a/README.md +++ b/README.md @@ -35,16 +35,16 @@ Create an `Item` with a `UITableViewCell` and `CellDrawer`. An item may have a m class YourDrawer: CellDrawer { // The type could be a custom UITableViewCell class, with or without a Nib - static public var type = CellType.class(UITableViewCell.self) + static var type = CellType.class(YourCustomCell.self) - static public func draw(_ cell: UITableViewCell, with item: Any) { + static func draw(_ cell: YourCustomCell, with item: YourItem) { // Draw by setting properties of your cell from the item } } class YourItem: Item { - public var drawer: CellDrawer.Type = YourDrawer.self + var drawer = AnyCellDrawer(YourDrawer.self) // Your properties and methods diff --git a/TableViewKit.podspec b/TableViewKit.podspec index f58ebca..9fa19de 100644 --- a/TableViewKit.podspec +++ b/TableViewKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "TableViewKit" - s.version = "0.9.6" + s.version = "1.0.0" s.summary = "Empowering UITableView with painless multi-type cell support and build-in automatic state transition animations" s.homepage = "http://github.com/odigeoteam/TableViewKit/" s.license = "MIT" diff --git a/TableViewKit.xcodeproj/project.pbxproj b/TableViewKit.xcodeproj/project.pbxproj index 76a4fe2..1750484 100644 --- a/TableViewKit.xcodeproj/project.pbxproj +++ b/TableViewKit.xcodeproj/project.pbxproj @@ -117,11 +117,11 @@ CCFD05CB1D95BD3C0063A002 /* Stateful.swift */, CCCAC1251D7D9EE00001FC1D /* HeaderFooter.swift */, CCEF387F1D8D5A7A00F5893F /* HeaderFooterDrawer.swift */, + CCBE458A1D72F79C00414A64 /* CellDrawer.swift */, CCBE458C1D72F79C00414A64 /* Item.swift */, CC97D76C1D741DC4009CDF9D /* Selectable.swift */, 25B0B7531DC74F6C00591467 /* Editable.swift */, 25837C771D87F819001EF4B8 /* ItemCompatible.swift */, - CCBE458A1D72F79C00414A64 /* CellDrawer.swift */, CC0DE2C71DE33B9C00E2EDE5 /* AnyEquatable.swift */, ); name = Protocols; diff --git a/TableViewKit/ArrayDiff.swift b/TableViewKit/ArrayDiff.swift index 848977d..9466d99 100644 --- a/TableViewKit/ArrayDiff.swift +++ b/TableViewKit/ArrayDiff.swift @@ -4,33 +4,32 @@ struct Diff { var inserts: [Int] var deletes: [Int] var moves: [(Int, Int)] - + var isEmpty: Bool { return (inserts.count + deletes.count + moves.count) == 0 } - + init(inserts: [Int] = [], deletes: [Int] = [], moves: [(Int, Int)] = []) { self.inserts = inserts self.deletes = deletes self.moves = moves } - - + } -class DiffIterator : IteratorProtocol { +class DiffIterator: IteratorProtocol { struct Coordinates { var x: Int var y: Int } var last: Coordinates private let matrix: Matrix - - init(matrix: Matrix){ + + init(matrix: Matrix) { self.matrix = matrix self.last = Coordinates(x: matrix.rows-1, y: matrix.columns-1) } - + func next() -> ArrayChanges? { while(last.x > 0 || last.y > 0) { if last.x == 0 { @@ -54,30 +53,30 @@ class DiffIterator : IteratorProtocol { } } -class DiffSequence : Sequence { +class DiffSequence: Sequence { private let matrix: Matrix - - init(matrix: Matrix){ + + init(matrix: Matrix) { self.matrix = matrix } - + func makeIterator() -> DiffIterator { return DiffIterator(matrix: matrix) } } struct Matrix { - + let rows: Int let columns: Int var grid: [Element] - + init(rows: Int, columns: Int, repeatedValue: Element) { self.rows = rows self.columns = columns self.grid = [Element](repeating: repeatedValue, count: rows * columns) } - + subscript(row: Int, column: Int) -> Element { get { return grid[(row * columns) + column] @@ -89,11 +88,11 @@ struct Matrix { } extension Array { - + typealias Predicate = (Element, Element) -> Bool static func diff(between x: [Element], and y: [Element], where predicate: Predicate) -> Diff { - + var matrix = Matrix(rows: x.count+1, columns: y.count+1, repeatedValue: 0) for (i, xElem) in x.enumerated() { for (j, yElem) in y.enumerated() { @@ -104,14 +103,13 @@ extension Array { } } } - + let changes = [ArrayChanges](DiffSequence(matrix: matrix)) var inserts: [Int] = changes.flatMap { change -> [Int] in guard case .inserts(let array) = change else { return [] } return array }.sorted { $0 > $1 } - var deletes: [Int] = changes.flatMap { change -> [Int] in guard case .deletes(let array) = change else { return [] } return array @@ -124,7 +122,7 @@ extension Array { let elem = x[deleteAtIndex] let temp = inserts.index { predicate(y[$0], elem) } guard let insertIndex = temp else { continue } - + let insertAtIndex = inserts[insertIndex] deletes.remove(at: deleteIndex - deleted) inserts.remove(at: insertIndex) @@ -133,7 +131,6 @@ extension Array { deleted += 1 } - return Diff(inserts: inserts, deletes: deletes, moves: moves) } diff --git a/TableViewKit/Extensions/UITableView+Moves.swift b/TableViewKit/Extensions/UITableView+Moves.swift index 53ce4ce..b1677e3 100644 --- a/TableViewKit/Extensions/UITableView+Moves.swift +++ b/TableViewKit/Extensions/UITableView+Moves.swift @@ -2,13 +2,13 @@ import Foundation import UIKit extension UITableView { - + func moveRows(at indexPaths: [IndexPath], to newIndexPaths: [IndexPath]) { for (index, _) in indexPaths.enumerated() { moveRow(at: indexPaths[index], to: newIndexPaths[index]) } } - + func moveSections(from: [Int], to: [Int]) { for (index, _) in from.enumerated() { moveSection(from[index], toSection: to[index]) diff --git a/TableViewKit/Extensions/UITableView+Register.swift b/TableViewKit/Extensions/UITableView+Register.swift index 2c9517e..60824eb 100644 --- a/TableViewKit/Extensions/UITableView+Register.swift +++ b/TableViewKit/Extensions/UITableView+Register.swift @@ -1,11 +1,11 @@ import UIKit extension UITableView { - + /// Register a cell type for reuse /// /// - parameter type: The type of cell that must be registered - func register(_ type: CellType) { + func register(_ type: CellType) { switch type { case .class(let cellClass): self.register(cellClass, forCellReuseIdentifier: type.reusableIdentifier) @@ -17,7 +17,7 @@ extension UITableView { /// Register a header/footer type for reuse /// /// - parameter type: The type of header/footer that must be registered - func register(_ type: HeaderFooterType) { + func register(_ type: HeaderFooterType) { switch type { case .class(let cellClass): self.register(cellClass, forHeaderFooterViewReuseIdentifier: type.reusableIdentifier) diff --git a/TableViewKit/Height.swift b/TableViewKit/Height.swift index 6280590..c6577f9 100644 --- a/TableViewKit/Height.swift +++ b/TableViewKit/Height.swift @@ -1,15 +1,14 @@ import Foundation - /// Defines either a dynamic or static height public enum Height { - + /// A dynamic height, calculated with autolayout with an estimated value case dynamic(CGFloat) - + /// A static height case `static`(CGFloat) - + /// Returns the estimated value of the height internal var estimated: CGFloat { switch self { @@ -19,7 +18,7 @@ public enum Height { return value } } - + /// Returns the height value internal var height: CGFloat { switch self { diff --git a/TableViewKit/NibClassType.swift b/TableViewKit/NibClassType.swift index 9ee3ea1..623a372 100644 --- a/TableViewKit/NibClassType.swift +++ b/TableViewKit/NibClassType.swift @@ -2,17 +2,17 @@ import Foundation /// A Nib/Class loadable type public enum NibClassType { - + /// If it must be loaded from a UINib case nib(UINib, T.Type) /// If it must be loaded from a Class case `class`(T.Type) - + /// The reusable identifier for the type public var reusableIdentifier: String { return String(describing: typeClass) } - + /// The type class public var typeClass: T.Type { switch self { @@ -23,3 +23,25 @@ public enum NibClassType { } } } + +extension NibClassType where T: UITableViewCell { + var cellType: NibClassType { + switch self { + case .class(let type): + return NibClassType.class(type) + case .nib(let nib, let type): + return NibClassType.nib(nib, type) + } + } +} + +extension NibClassType where T: UITableViewHeaderFooterView { + var headerFooterType: NibClassType { + switch self { + case .class(let type): + return NibClassType.class(type) + case .nib(let nib, let type): + return NibClassType.nib(nib, type) + } + } +} diff --git a/TableViewKit/ObservableArray.swift b/TableViewKit/ObservableArray.swift index f2a8bd6..6352ec0 100644 --- a/TableViewKit/ObservableArray.swift +++ b/TableViewKit/ObservableArray.swift @@ -11,12 +11,12 @@ enum ArrayChanges { /// An observable array. It will notify any kind of changes. public struct ObservableArray: ExpressibleByArrayLiteral, Collection, MutableCollection, RangeReplaceableCollection { - + /// The type of the elements of an array literal. public typealias Element = T - + private var diff = Diff() - + private var array: [T] { willSet { diff = Array.diff(between: array, and: newValue, where: compare) @@ -29,43 +29,43 @@ public struct ObservableArray: ExpressibleByArrayLiteral, Collection, Mutable callback?(.endUpdates) } } - - var callback: ((ArrayChanges) -> ())? - + + var callback: ((ArrayChanges) -> Void)? + /// Creates an empty `ObservableArray` public init() { self.array = [] } - + /// Creates an `ObservableArray` with the contents of `array` /// /// - parameter array: The initial content public init(array: [T]) { self.array = array } - + /// Creates an instance initialized with the given elements. /// /// - parameter elements: An array of elements public init(arrayLiteral elements: Element...) { self.array = elements } - + /// Returns an iterator over the elements of the collection. public func makeIterator() -> Array.Iterator { return array.makeIterator() } - + /// The position of the first element in a nonempty collection. public var startIndex: Int { return array.startIndex } - + /// The position of the last element in a nonempty collection. public var endIndex: Int { return array.endIndex } - + /// Returns the position immediately after the given index. /// /// - parameter i: A valid index of the collection. i must be less than endIndex. @@ -74,7 +74,7 @@ public struct ObservableArray: ExpressibleByArrayLiteral, Collection, Mutable public func index(after i: Int) -> Int { return array.index(after: i) } - + /// A Boolean value indicating whether the collection is empty. public var isEmpty: Bool { return array.isEmpty @@ -84,8 +84,7 @@ public struct ObservableArray: ExpressibleByArrayLiteral, Collection, Mutable public var count: Int { return array.count } - - + /// Accesses the element at the specified position. /// /// - parameter index: @@ -97,7 +96,7 @@ public struct ObservableArray: ExpressibleByArrayLiteral, Collection, Mutable array[index] = newValue } } - + /// Replaces the specified subrange of elements with the given collection. /// /// - parameter subrange: The subrange that must be replaced @@ -105,14 +104,14 @@ public struct ObservableArray: ExpressibleByArrayLiteral, Collection, Mutable public mutating func replaceSubrange(_ subrange: Range, with newElements: C) where C : Collection, C.Iterator.Element == T { array.replaceSubrange(subrange, with: newElements) } - + /// Replace its content with a new array /// /// - parameter array: The new array public mutating func replace(with array: [T]) { self.array = array } - + private func compare(lhs: T, rhs: T) -> Bool { if let lhs = lhs as? AnyEquatable { return lhs.equals(rhs) diff --git a/TableViewKit/Protocols/CellDrawer.swift b/TableViewKit/Protocols/CellDrawer.swift index 3abcac1..3b7593a 100644 --- a/TableViewKit/Protocols/CellDrawer.swift +++ b/TableViewKit/Protocols/CellDrawer.swift @@ -2,14 +2,16 @@ import Foundation import UIKit /// The type of a cell (nib, class) -public typealias CellType = NibClassType +public typealias CellType = NibClassType /// A type that can draw a cell public protocol CellDrawer { + associatedtype Cell: UITableViewCell + associatedtype GenericItem /// Define the `type` of the cell - static var type: CellType { get } - + static var type: CellType { get } + /// Returns the cell from the `manager` /// /// - parameter manager: The `manager` where the cell came from @@ -17,28 +19,51 @@ public protocol CellDrawer { /// - parameter indexPath: Where the cell is located /// /// - returns: The the cell - static func cell(in manager: TableViewManager, with item: Item, for indexPath: IndexPath) -> UITableViewCell - + static func cell(in manager: TableViewManager, with item: GenericItem, for indexPath: IndexPath) -> Cell + /// Draw the `cell` using the `item` /// /// - parameter cell: The `cell` that must be drawn /// - parameter item: The cell `item` that generated the drawer - static func draw(_ cell: UITableViewCell, with item: Any) + static func draw(_ cell: Cell, with item: GenericItem) } public extension CellDrawer { - + /// Returns a dequeued cell and set the item property if the cell conforms to ItemCompatible - static func cell(in manager: TableViewManager, with item: Item, for indexPath: IndexPath) -> UITableViewCell { - + static func cell(in manager: TableViewManager, with item: GenericItem, for indexPath: IndexPath) -> Cell { + let cell = manager.tableView.dequeueReusableCell(withIdentifier: self.type.reusableIdentifier, for: indexPath) cell.selectionStyle = item is Selectable ? .default : .none - + if let cell = cell as? ItemCompatible { - cell.item = item + cell.item = item as? Item } - - return cell + + return cell as! Cell } } + +/// A type-erased wrapper over any cell drawer +public struct AnyCellDrawer { + let type: CellType + let cell: (TableViewManager, Item, IndexPath) -> UITableViewCell + let draw: (UITableViewCell, Item) -> Void + + /// Creates a type-erased drawer that wraps the given cell drawer + public init(_ drawer: Drawer.Type) where Drawer.GenericItem == GenericItem, Drawer.Cell == Cell { + self.type = drawer.type.cellType + self.cell = { manager, item, indexPath in drawer.cell(in: manager, with: item as! GenericItem, for: indexPath) } + self.draw = { cell, item in drawer.draw(cell as! Cell, with: item as! GenericItem) } + } + + public func cell(in manager: TableViewManager, with item: Item, for indexPath: IndexPath) -> UITableViewCell { + return cell(manager, item, indexPath) + } + + public func draw(_ cell: UITableViewCell, with item: Item) { + draw(cell, item) + } + +} diff --git a/TableViewKit/Protocols/Editable.swift b/TableViewKit/Protocols/Editable.swift index a3b1790..8cb9392 100644 --- a/TableViewKit/Protocols/Editable.swift +++ b/TableViewKit/Protocols/Editable.swift @@ -2,7 +2,7 @@ import Foundation /// A type that represent an item that can be edited public protocol Editable: Item { - + /// The associated actions var actions: [UITableViewRowAction]? { get set } } diff --git a/TableViewKit/Protocols/HeaderFooter.swift b/TableViewKit/Protocols/HeaderFooter.swift index b3eac0d..2252d02 100644 --- a/TableViewKit/Protocols/HeaderFooter.swift +++ b/TableViewKit/Protocols/HeaderFooter.swift @@ -20,7 +20,7 @@ public enum HeaderFooterView: ExpressibleByNilLiteral { public protocol HeaderFooter: class { /// The `drawer` of the header/footer - var drawer: HeaderFooterDrawer.Type { get } + static var drawer: AnyHeaderFooterDrawer { get } /// The `height` of the header/footer var height: Height? { get } diff --git a/TableViewKit/Protocols/HeaderFooterDrawer.swift b/TableViewKit/Protocols/HeaderFooterDrawer.swift index d73aaa0..8b5be49 100644 --- a/TableViewKit/Protocols/HeaderFooterDrawer.swift +++ b/TableViewKit/Protocols/HeaderFooterDrawer.swift @@ -2,34 +2,58 @@ import Foundation import UIKit /// The type of a header/footer (nib, class) -public typealias HeaderFooterType = NibClassType +public typealias HeaderFooterType = NibClassType /// A type that can draw either a header or a footer public protocol HeaderFooterDrawer { - + associatedtype View: UITableViewHeaderFooterView + associatedtype GenericItem + /// Define the `type` of the header/footer - static var type: HeaderFooterType { get } - + static var type: HeaderFooterType { get } + /// Draw the `view` using the `item` /// /// - parameter view: The header/footer `view` that must be drawn /// - parameter item: The header/footer `item` that generated the drawer - static func draw(_ view: UITableViewHeaderFooterView, with item: Any) - - + static func draw(_ view: View, with item: GenericItem) + /// Returns the header/footer view from the `manager` /// /// - parameter manager: The `manager` where the header/footer came from /// - parameter item: The header/footer `item` /// /// - returns: The header/footer view - static func view(in manager: TableViewManager, with item: HeaderFooter) -> UITableViewHeaderFooterView + static func view(in manager: TableViewManager, with item: GenericItem) -> View } public extension HeaderFooterDrawer { - + /// Returns a dequeued header/footer - static func view(in manager: TableViewManager, with item: HeaderFooter) -> UITableViewHeaderFooterView { - return manager.tableView.dequeueReusableHeaderFooterView(withIdentifier: self.type.reusableIdentifier)! + static func view(in manager: TableViewManager, with item: GenericItem) -> View { + return manager.tableView.dequeueReusableHeaderFooterView(withIdentifier: self.type.reusableIdentifier) as! View } } + +/// A type-erased wrapper over any header or footer drawer +public struct AnyHeaderFooterDrawer { + let type: HeaderFooterType + let view: (TableViewManager, HeaderFooter) -> UITableViewHeaderFooterView + let draw: (UITableViewHeaderFooterView, HeaderFooter) -> Void + + /// Creates a type-erased drawer that wraps the given header or footer drawer + public init(_ drawer: Drawer.Type) where Drawer.GenericItem == GenericItem, Drawer.View == View { + self.type = drawer.type.headerFooterType + self.view = { manager, item in drawer.view(in: manager, with: item as! GenericItem) } + self.draw = { cell, item in drawer.draw(cell as! View, with: item as! GenericItem) } + } + + public func view(in manager: TableViewManager, with item: HeaderFooter) -> UITableViewHeaderFooterView { + return view(manager, item) + } + + public func draw(_ view: UITableViewHeaderFooterView, with item: HeaderFooter) { + draw(view, item) + } + +} diff --git a/TableViewKit/Protocols/Item.swift b/TableViewKit/Protocols/Item.swift index 16521b9..48f4b87 100644 --- a/TableViewKit/Protocols/Item.swift +++ b/TableViewKit/Protocols/Item.swift @@ -1,13 +1,12 @@ import Foundation - /// A type that represent an item to be displayed /// defining the `drawer` and the `height` public protocol Item: class, AnyEquatable { - + /// The `drawer` of the item - var drawer: CellDrawer.Type { get } - + static var drawer: AnyCellDrawer { get } + /// The `height` of the item var height: Height? { get } } @@ -22,7 +21,7 @@ public extension Item where Self: Equatable { } extension Item { - + public func equals(_ other: Any?) -> Bool { if let other = other as AnyObject? { return other === self diff --git a/TableViewKit/Protocols/ItemCompatible.swift b/TableViewKit/Protocols/ItemCompatible.swift index 599bc82..2b743dc 100644 --- a/TableViewKit/Protocols/ItemCompatible.swift +++ b/TableViewKit/Protocols/ItemCompatible.swift @@ -5,7 +5,7 @@ import Foundation /// if the cell is initialiated by the TableViewManager, /// the item will be setted automatically. public protocol ItemCompatible: class { - + /// The associated item var item: Item? { get set } } diff --git a/TableViewKit/Protocols/Selectable.swift b/TableViewKit/Protocols/Selectable.swift index 0c1010b..0d1b173 100644 --- a/TableViewKit/Protocols/Selectable.swift +++ b/TableViewKit/Protocols/Selectable.swift @@ -2,7 +2,7 @@ import Foundation /// A type that represent an item that can be selected public protocol Selectable: Item { - + /// Method called once an item is selected by a selectRow. func didSelect() } diff --git a/TableViewKit/Protocols/Stateful.swift b/TableViewKit/Protocols/Stateful.swift index 2627159..87b30bb 100644 --- a/TableViewKit/Protocols/Stateful.swift +++ b/TableViewKit/Protocols/Stateful.swift @@ -8,22 +8,20 @@ import Foundation /// You must implement `items(for:)`, that will be used to know /// which items belong to which `State`. public protocol Stateful: Section { - + /// A type that represent a state, such as an enum. associatedtype State - + /// The current state var currentState: State { get set } - - + /// Returns the `items` belonging to a `state` /// /// - parameter state: A concrete `state` /// /// - returns: The `items` belonging to a `state` func items(for state: State) -> [Item] - - + /// Performs a transition from the `currentState` to a `newState` /// /// - parameter newState: The `newState` @@ -39,12 +37,12 @@ public extension Stateful { public protocol StaticStateful: Stateful { associatedtype State: Hashable - + var states: [State: [Item]] { get } } public extension StaticStateful { - + func items(for state: State) -> [Item] { return states[state]! } diff --git a/TableViewKit/Section.swift b/TableViewKit/Section.swift index 9002def..6af9e89 100644 --- a/TableViewKit/Section.swift +++ b/TableViewKit/Section.swift @@ -13,18 +13,18 @@ public protocol Section: class, AnyEquatable { var header: HeaderFooterView { get } /// The `footer` of the section, none if not defined var footer: HeaderFooterView { get } - + func index(in manager: TableViewManager) -> Int? } public extension Section where Self: Equatable { - + func equals(_ other: Any?) -> Bool { if let other = other as? Self { return other == self } return false - } +} } extension Section { @@ -36,7 +36,7 @@ extension Section { } extension Section { - + public func equals(_ other: Any?) -> Bool { if let other = other as AnyObject? { return other === self @@ -58,13 +58,13 @@ extension Section { /// - parameter manager: A manager where the section may have been added internal func register(in manager: TableViewManager) { if case .view(let header) = header { - manager.register(header.drawer.type) + manager.tableView.register(type(of: header).drawer.type) } if case .view(let footer) = footer { - manager.register(footer.drawer.type) + manager.tableView.register(type(of: footer).drawer.type) } items.forEach { - manager.register($0.drawer.type) + manager.tableView.register(type(of: $0).drawer.type) } } @@ -78,14 +78,14 @@ extension Section { } } } - + private func onItemsUpdate(withChanges changes: ArrayChanges, in manager: TableViewManager) { - + guard let sectionIndex = index(in: manager) else { return } let tableView = manager.tableView if case .inserts(let array) = changes { - array.forEach { manager.register(items[$0].drawer.type) } + array.forEach { manager.register(type(of: items[$0]).drawer.type) } } switch changes { diff --git a/TableViewKit/TableViewManager.swift b/TableViewKit/TableViewManager.swift index d4a7e73..dc004f2 100644 --- a/TableViewKit/TableViewManager.swift +++ b/TableViewKit/TableViewManager.swift @@ -6,17 +6,17 @@ import UIKit /// It automatically registers any related initial or new views/cells for reusability. /// Any changes of the sections will be automatically animated and reflected. open class TableViewManager: NSObject { - + /// The `tableView` linked to this manager open let tableView: UITableView - + /// An array of sections open var sections: ObservableArray
- + open var animation: UITableViewRowAnimation = .automatic - + var reusableIdentifiers: Set = [] - + /// Initialize a `TableViewManager` with a `tableView`. /// /// - parameter tableView: A `tableView` that will be controlled by the `TableViewManager` @@ -27,7 +27,7 @@ open class TableViewManager: NSObject { self.tableView.dataSource = self self.tableView.delegate = self self.setupSections() - + } /// Initialize a `TableViewManager` with a `tableView` and an initial array of sections @@ -42,7 +42,7 @@ open class TableViewManager: NSObject { self.tableView.delegate = self self.setupSections() } - + private func setupSections() { sections.forEach { section in section.setup(in: self) @@ -50,8 +50,8 @@ open class TableViewManager: NSObject { } sections.callback = { [weak self] in self?.onSectionsUpdate(withChanges: $0) } } - - private func onSectionsUpdate(withChanges changes: ArrayChanges){ + + private func onSectionsUpdate(withChanges changes: ArrayChanges) { switch changes { case .inserts(let array): array.forEach { index in @@ -79,18 +79,18 @@ open class TableViewManager: NSObject { } } } - + } extension TableViewManager { - - func register(_ type: CellType) { + + func register(_ type: CellType) { if !reusableIdentifiers.contains(type.reusableIdentifier) { tableView.register(type) reusableIdentifiers.insert(type.reusableIdentifier) } } - func register(_ type: HeaderFooterType) { + func register(_ type: HeaderFooterType) { if !reusableIdentifiers.contains(type.reusableIdentifier) { tableView.register(type) reusableIdentifiers.insert(type.reusableIdentifier) @@ -99,29 +99,29 @@ extension TableViewManager { } extension TableViewManager { - + fileprivate func item(at indexPath: IndexPath) -> Item { return sections[indexPath.section].items[indexPath.row] } - + fileprivate func view(for key: (Section) -> HeaderFooterView, inSection section: Int) -> UIView? { guard case .view(let item) = key(sections[section]) else { return nil } - - let drawer = item.drawer + + let drawer = type(of: item).drawer let view = drawer.view(in: self, with: item) drawer.draw(view, with: item) - + return view } - + fileprivate func title(for key: (Section) -> HeaderFooterView, inSection section: Int) -> String? { if case .title(let value) = key(sections[section]) { return value } return nil - + } - + fileprivate func estimatedHeight(for key: (Section) -> HeaderFooterView, inSection section: Int) -> CGFloat? { let item = key(sections[section]) switch item { @@ -134,61 +134,59 @@ extension TableViewManager { return nil } } - + fileprivate func estimatedHeight(at indexPath: IndexPath) -> CGFloat? { guard let height = item(at: indexPath).height else { return nil } return height.estimated } - - fileprivate func height(for key: (Section) -> HeaderFooterView, inSection section: Int) -> CGFloat? { guard case .view(let view) = key(sections[section]), let value = view.height else { return nil } return value.height } - + fileprivate func height(at indexPath: IndexPath) -> CGFloat? { guard let value = item(at: indexPath).height else { return nil } return value.height } - + } extension TableViewManager: UITableViewDataSource { - + /// Implementation of UITableViewDataSource open func numberOfSections(in tableView: UITableView) -> Int { return sections.count } - + /// Implementation of UITableViewDataSource open func tableView(_ tableView: UITableView, numberOfRowsInSection sectionIndex: Int) -> Int { let section = sections[sectionIndex] return section.items.count } - + /// Implementation of UITableViewDataSource open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let currentItem = item(at: indexPath) - let drawer = currentItem.drawer - + let drawer = type(of: currentItem).drawer + let cell = drawer.cell(in: self, with: currentItem, for: indexPath) drawer.draw(cell, with: currentItem) - + return cell } - + /// Implementation of UITableViewDataSource open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return title(for: {$0.header}, inSection: section) } - + /// Implementation of UITableViewDataSource open func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { return title(for: {$0.footer}, inSection: section) } - + /// Implementation of UITableViewDataSource open func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { // Intentionally blank. Required to use UITableViewRowActions @@ -196,53 +194,57 @@ extension TableViewManager: UITableViewDataSource { } extension TableViewManager: UITableViewDelegate { - + /// Implementation of UITableViewDelegate open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let currentItem = item(at: indexPath) as? Selectable else { return } currentItem.didSelect() } - + /// Implementation of UITableViewDelegate open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return height(at: indexPath) ?? tableView.rowHeight } - + /// Implementation of UITableViewDelegate open func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return height(for: {$0.header}, inSection: section) ?? tableView.sectionHeaderHeight } - + /// Implementation of UITableViewDelegate open func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return height(for: {$0.footer}, inSection: section) ?? tableView.sectionFooterHeight } - + /// Implementation of UITableViewDelegate open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return estimatedHeight(at: indexPath) ?? tableView.estimatedRowHeight } - + /// Implementation of UITableViewDelegate open func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { return estimatedHeight(for: {$0.header}, inSection: section) ?? tableView.estimatedSectionHeaderHeight } - + /// Implementation of UITableViewDelegate open func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat { return estimatedHeight(for: {$0.footer}, inSection: section) ?? tableView.estimatedSectionHeaderHeight } - + /// Implementation of UITableViewDelegate open func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return view(for: {$0.header}, inSection: section) } - + /// Implementation of UITableViewDelegate open func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return view(for: {$0.footer}, inSection: section) } - + + open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + return item(at: indexPath) is Editable + } + /// Implementation of UITableViewDelegate open func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { guard let item = item(at: indexPath) as? Editable else { return nil } diff --git a/TableViewKitTests/ObservableArrayTests.swift b/TableViewKitTests/ObservableArrayTests.swift index 8fe86ef..4926c82 100644 --- a/TableViewKitTests/ObservableArrayTests.swift +++ b/TableViewKitTests/ObservableArrayTests.swift @@ -2,21 +2,21 @@ import XCTest import TableViewKit class ObservableArrayTests: XCTestCase { - + override func setUp() { super.setUp() } - + override func tearDown() { super.tearDown() } - + func testInsert() { - + var numbers: ObservableArray = ObservableArray() numbers.replace(with: [4, 2, 1]) numbers[0] = 3 - + XCTAssert(numbers[0] == 3) } } diff --git a/TableViewKitTests/TableViewDataSourceTests.swift b/TableViewKitTests/TableViewDataSourceTests.swift index f8d523b..895072c 100644 --- a/TableViewKitTests/TableViewDataSourceTests.swift +++ b/TableViewKitTests/TableViewDataSourceTests.swift @@ -1,5 +1,5 @@ import XCTest -import TableViewKit +@testable import TableViewKit import Nimble extension HeaderFooterView: Equatable { @@ -20,7 +20,7 @@ extension HeaderFooterView: Equatable { class HeaderFooterTitleSection: Section { var items: ObservableArray = [] weak var tableViewManager: TableViewManager! - + internal var header: HeaderFooterView { return .title("Header") } internal var footer: HeaderFooterView { return .title("Footer") } @@ -31,18 +31,17 @@ class HeaderFooterTitleSection: Section { } class TestDrawer: CellDrawer { - static internal var type = CellType.class(TestCell.self) - static internal func draw(_ cell: UITableViewCell, with item: Any) { } + static internal func draw(_ cell: TestCell, with item: Item) { } } class TestItem: Item, Selectable { + static internal var drawer = AnyCellDrawer(TestDrawer.self) public func didSelect() { print("didSelect called") } - internal var drawer: CellDrawer.Type = TestDrawer.self } class TestCell: UITableViewCell, ItemCompatible { @@ -50,28 +49,27 @@ class TestCell: UITableViewCell, ItemCompatible { } class DifferentItem: Item { - - var drawer: CellDrawer.Type = DifferentDrawer.self + + static var drawer = AnyCellDrawer(DifferentDrawer.self) } class DifferentDrawer: CellDrawer { - - static var type: CellType = CellType.class(DifferentCell.self) - static func draw(_ cell: UITableViewCell, with item: Any) {} + static var type: NibClassType = CellType.class(DifferentCell.self) + static func draw(_ cell: DifferentCell, with item: DifferentItem) {} } class DifferentCell: UITableViewCell { } class TableViewDataSourceTests: XCTestCase { - + fileprivate var tableViewManager: TableViewManager! - + override func setUp() { super.setUp() - + let section1 = HeaderFooterTitleSection(items: [TestItem()]) let section2 = ViewHeaderFooterSection(items: [NoHeigthItem(), StaticHeigthItem()]) - + tableViewManager = TableViewManager(tableView: UITableView(), sections: [section1, section2]) } @@ -79,68 +77,69 @@ class TableViewDataSourceTests: XCTestCase { tableViewManager = nil super.tearDown() } - + func testCellForRow() { let indexPath = IndexPath(row: 0, section: 0) let cell = self.tableViewManager.tableView(self.tableViewManager.tableView, cellForRowAt: indexPath) - + expect(cell).to(beAnInstanceOf(TestCell.self)) } - + func testDequeueCellAfterRegisterSection() { - + guard let section = tableViewManager.sections.first else { fatalError("Couldn't get the first section") } - + let otherItem = DifferentItem() section.items.append(otherItem) - + let indexPath = otherItem.indexPath(in: tableViewManager)! - let cell = otherItem.drawer.cell(in: tableViewManager, with: otherItem, for: indexPath) - + let drawer = type(of: otherItem).drawer + let cell = drawer.cell(in: tableViewManager, with: otherItem as Item, for: indexPath) + XCTAssertTrue(cell is DifferentCell) } - + func testNumberOfSections() { let count = self.tableViewManager.numberOfSections(in: self.tableViewManager.tableView) expect(count).to(equal(2)) } - + func testNumberOfRowsInSection() { let count = self.tableViewManager.tableView(self.tableViewManager.tableView, numberOfRowsInSection: 0) expect(count) == 1 } - + func testTitleForHeaderInSection() { let title = self.tableViewManager.tableView(self.tableViewManager.tableView, titleForHeaderInSection: 0)! let section = tableViewManager.sections.first! - + expect(HeaderFooterView.title(title)).to(equal(section.header)) } - + func testTitleForFooterInSection() { var title: String? - + title = self.tableViewManager.tableView(self.tableViewManager.tableView, titleForFooterInSection: 0) - + let section = tableViewManager.sections.first! expect(HeaderFooterView.title(title!)).to(equal(section.footer)) - + title = self.tableViewManager.tableView(self.tableViewManager.tableView, titleForFooterInSection: 1) expect(title).to(beNil()) } - + func testViewForHeaderInSection() { let view = self.tableViewManager.tableView(self.tableViewManager.tableView, viewForHeaderInSection: 0) expect(view).to(beNil()) } - + func testViewForFooterInSection() { var view: UIView? view = self.tableViewManager.tableView(self.tableViewManager.tableView, viewForFooterInSection: 0) expect(view).to(beNil()) - + view = self.tableViewManager.tableView(self.tableViewManager.tableView, viewForFooterInSection: 1) expect(view).notTo(beNil()) } diff --git a/TableViewKitTests/TableViewDelegateTests.swift b/TableViewKitTests/TableViewDelegateTests.swift index 44ccfd2..c249f56 100644 --- a/TableViewKitTests/TableViewDelegateTests.swift +++ b/TableViewKitTests/TableViewDelegateTests.swift @@ -4,7 +4,7 @@ import Nimble class NoHeaderFooterSection: Section { var items: ObservableArray = [] - + convenience init(items: [Item]) { self.init() self.items.insert(contentsOf: items, at: 0) @@ -13,37 +13,33 @@ class NoHeaderFooterSection: Section { class CustomHeaderFooterView: UITableViewHeaderFooterView { var label: UILabel = UILabel() - + override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) } - + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } - class CustomHeaderDrawer: HeaderFooterDrawer { - + static public var type = HeaderFooterType.class(CustomHeaderFooterView.self) - - static public func draw(_ view: UITableViewHeaderFooterView, with item: Any) { - let item = item as! ViewHeaderFooter - let view = view as! CustomHeaderFooterView + + static public func draw(_ view: CustomHeaderFooterView, with item: ViewHeaderFooter) { view.label.text = item.title } } - class ViewHeaderFooter: HeaderFooter { - + public var title: String? public var height: Height? = .dynamic(44.0) - public var drawer: HeaderFooterDrawer.Type = CustomHeaderDrawer.self - + static public var drawer = AnyHeaderFooterDrawer(CustomHeaderDrawer.self) + public init() { } - + public convenience init(title: String) { self.init() self.title = title @@ -63,26 +59,25 @@ class ViewHeaderFooterSection: Section { } class NoHeigthItem: Item { - internal var drawer: CellDrawer.Type = TestDrawer.self - - internal var height: Height? = nil + static internal var drawer = AnyCellDrawer(TestDrawer.self) + + internal var height: Height? } class StaticHeigthItem: Item { static let testStaticHeightValue: CGFloat = 20.0 - - internal var drawer: CellDrawer.Type = TestDrawer.self - + static internal var drawer = AnyCellDrawer(TestDrawer.self) + internal var height: Height? = .static(20.0) } class SelectableItem: Selectable, Item { + static internal var drawer = AnyCellDrawer(TestDrawer.self) + public var check: Int = 0 - - internal var drawer: CellDrawer.Type = TestDrawer.self - + public init() {} - + func didSelect() { check += 1 } @@ -92,71 +87,69 @@ class EditableItem: SelectableItem, Editable { public var actions: [UITableViewRowAction]? } - class TableViewDelegateTests: XCTestCase { - + private var tableViewManager: TableViewManager! - + override func setUp() { super.setUp() tableViewManager = TableViewManager(tableView: UITableView()) - + tableViewManager.sections.append(HeaderFooterTitleSection(items: [TestItem()])) tableViewManager.sections.append(NoHeaderFooterSection(items: [NoHeigthItem(), StaticHeigthItem()])) tableViewManager.sections.append(ViewHeaderFooterSection(items: [NoHeigthItem(), StaticHeigthItem()])) - + } - + override func tearDown() { tableViewManager = nil super.tearDown() } - + func testEstimatedHeightForHeader() { var height: CGFloat - + height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForHeaderInSection: 0) expect(height).to(beGreaterThan(0.0)) - + height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForHeaderInSection: 1) expect(height).to(equal(tableViewManager.tableView.estimatedSectionHeaderHeight)) } - - + func testHeightForHeader() { var height: CGFloat - + height = tableViewManager.tableView(tableViewManager.tableView, heightForHeaderInSection: 0) expect(height).to(equal(UITableViewAutomaticDimension)) } - + func testEstimatedHeightForFooter() { var height: CGFloat - + height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForFooterInSection: 0) expect(height).to(beGreaterThan(0.0)) - + height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForFooterInSection: 1) expect(height).to(equal(tableViewManager.tableView.estimatedSectionFooterHeight)) - + height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForFooterInSection: 2) expect(height).to(equal(44.0)) } - + func testHeightForFooter() { var height: CGFloat - + height = tableViewManager.tableView(tableViewManager.tableView, heightForFooterInSection: 0) expect(height).to(equal(UITableViewAutomaticDimension)) - + height = tableViewManager.tableView(tableViewManager.tableView, heightForFooterInSection: 2) expect(height).to(equal(UITableViewAutomaticDimension)) } - + func testEstimatedHeightForRowAtIndexPath() { var height: CGFloat var indexPath: IndexPath - + indexPath = IndexPath(row: 0, section: 0) height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForRowAt: indexPath) expect(height).to(equal(44.0)) @@ -169,59 +162,58 @@ class TableViewDelegateTests: XCTestCase { height = tableViewManager.tableView(tableViewManager.tableView, estimatedHeightForRowAt: indexPath) expect(height).to(equal(20.0)) } - + func testHeightForRowAtIndexPath() { var height: CGFloat var indexPath: IndexPath - + indexPath = IndexPath(row: 0, section: 0) height = tableViewManager.tableView(tableViewManager.tableView, heightForRowAt: indexPath) expect(height).to(equal(UITableViewAutomaticDimension)) - + indexPath = IndexPath(row: 0, section: 1) height = tableViewManager.tableView(tableViewManager.tableView, heightForRowAt: indexPath) expect(height).to(equal(tableViewManager.tableView.rowHeight)) - + indexPath = IndexPath(row: 1, section: 1) height = tableViewManager.tableView(tableViewManager.tableView, heightForRowAt: indexPath) expect(height).to(equal(StaticHeigthItem.testStaticHeightValue)) } - + func testSelectRow() { var indexPath: IndexPath - + indexPath = IndexPath(row: 0, section: 0) tableViewManager.tableView(tableViewManager.tableView, didSelectRowAt: indexPath) - let section = tableViewManager.sections[0] indexPath = IndexPath(row: section.items.count, section: 0) let item = SelectableItem() section.items.append(item) - + tableViewManager.tableView(tableViewManager.tableView, didSelectRowAt: indexPath) expect(item.check).to(equal(1)) item.select(in: tableViewManager, animated: true) expect(item.check).to(equal(2)) - + item.deselect(in: tableViewManager, animated: true) expect(item.check).to(equal(2)) } - + func testEditableRows() { let section = tableViewManager.sections.first! - let deleteAction = UITableViewRowAction(style: .normal, title: "Delete", handler: { action, indexPath in + let deleteAction = UITableViewRowAction(style: .normal, title: "Delete", handler: { _, _ in print("DeleteAction") }) let editableItem = EditableItem() editableItem.actions = [deleteAction] section.items.append(editableItem) - + let sectionIndex = section.index(in: tableViewManager)! let rows = tableViewManager.tableView(tableViewManager.tableView, numberOfRowsInSection: sectionIndex) XCTAssert(rows == 2) - + let indexPath = editableItem.indexPath(in: tableViewManager)! let actions = tableViewManager.tableView(tableViewManager.tableView, editActionsForRowAt: indexPath) XCTAssertNotNil(actions) diff --git a/TableViewKitTests/TableViewKitTests.swift b/TableViewKitTests/TableViewKitTests.swift index 3428d2c..f08e325 100644 --- a/TableViewKitTests/TableViewKitTests.swift +++ b/TableViewKitTests/TableViewKitTests.swift @@ -5,26 +5,27 @@ import Nimble @testable import TableViewKit class TestReloadDrawer: CellDrawer { - + static internal var type = CellType.class(UITableViewCell.self) - + static internal func draw(_ cell: UITableViewCell, with item: Any) { cell.textLabel?.text = (item as! TestReloadItem).title } } class TestReloadItem: Item { - internal var drawer: CellDrawer.Type = TestReloadDrawer.self + static internal var drawer = AnyCellDrawer(TestReloadDrawer.self) + internal var title: String? } class EqualableItem: TestReloadItem, Equatable { - + init(title: String) { super.init() self.title = title } - + public static func ==(lhs: EqualableItem, rhs: EqualableItem) -> Bool { return lhs.title == rhs.title } @@ -38,25 +39,25 @@ class EquatableSection: NoHeaderFooterSection, Equatable { } class StatefulSection: HeaderFooterTitleSection, StaticStateful { - + enum State { case login, register } - + var currentState: StatefulSection.State = .login var states: [StatefulSection.State : [Item]] = [:] - + override init() { super.init() - + let loginItem = TestReloadItem() loginItem.title = "Login" states[.login] = [loginItem] - + let registerItem = TestReloadItem() registerItem.title = "Register" states[.register] = [registerItem] - + transition(to: currentState) } } @@ -93,53 +94,53 @@ class TableViewKitTests: XCTestCase { section.items.append(item) tableViewManager.sections.insert(section, at: 0) - + expect(section.items.count).to(equal(1)) expect(item.section(in: tableViewManager)).notTo(beNil()) - + section.items.remove(at: 0) section.items.append(item) - + section.items.replace(with: [TestItem(), item]) } - + func testEqualableItem() { - + let item1 = EqualableItem(title: "Item1") let item2 = EqualableItem(title: "Item1") - + XCTAssert(item1.equals(item2)) XCTAssert(item1.equals("Item3") == false) } - + func testEqualableSection() { - + let section1 = EquatableSection() let section2 = EquatableSection() - + XCTAssert(section1.equals(section1)) XCTAssert(section1.equals(section2) == false) XCTAssert(section2.equals(nil) == false) } - + func testEqualItem() { - + let item1 = TestItem() let item2 = TestItem() - + XCTAssert(item1.equals(item2) == false) XCTAssert(item1.equals(nil) == false) } - + func testEqualSection() { - + let section1 = NoHeaderFooterSection() let section2 = NoHeaderFooterSection() - + XCTAssert(section1.equals(section2) == false) XCTAssert(section1.equals(nil) == false) } - + func testRetainCycle() { let tableViewManager = TableViewManager(tableView: UITableView()) tableViewManager.sections.insert(HeaderFooterTitleSection(items: [TestItem()]), at: 0) @@ -152,73 +153,73 @@ class TableViewKitTests: XCTestCase { expect(section).to(beNil()) expect(item).to(beNil()) } - + func testConvenienceInit() { let tableViewManager = TableViewManager(tableView: UITableView(), sections: [HeaderFooterTitleSection()]) - + expect(tableViewManager.sections.count).to(equal(1)) } - + func testUpdateRow() { - + let item = TestReloadItem() item.title = "Before" - + let section = HeaderFooterTitleSection(items: [item]) let tableViewManager = TableViewManager(tableView: UITableView(), sections: [section]) - + guard let indexPath = item.indexPath(in: tableViewManager) else { return } var cell = tableViewManager.tableView(tableViewManager.tableView, cellForRowAt: indexPath) - + expect(cell.textLabel?.text).to(equal(item.title)) - + item.title = "After" item.reload(in: tableViewManager) - + cell = tableViewManager.tableView(tableViewManager.tableView, cellForRowAt: indexPath) - + expect(cell.textLabel?.text).to(equal(item.title)) } - + func testMoveRows() { - + let tableView = UITableView() - + let item1 = TestItem() let item2 = TestItem() - + let section = NoHeaderFooterSection(items: [item1, item2]) let tableViewManager = TableViewManager(tableView: tableView, sections: [section]) - + var indexPathItem1 = item1.indexPath(in: tableViewManager) var indexPathItem2 = item2.indexPath(in: tableViewManager) - + XCTAssertNotNil(indexPathItem1) XCTAssertNotNil(indexPathItem2) - + section.items.replace(with: [item2, item1]) - + indexPathItem1 = item1.indexPath(in: tableViewManager) indexPathItem2 = item2.indexPath(in: tableViewManager) - + XCTAssert(indexPathItem2?.item == 0) XCTAssert(indexPathItem1?.item == 1) } - + func testMoveSections() { - + let tableView = UITableView() - + let section1 = NoHeaderFooterSection() let section2 = NoHeaderFooterSection() - + let tableViewManager = TableViewManager(tableView: tableView, sections: [section1, section2]) - + XCTAssert(section1.index(in: tableViewManager) == 0) XCTAssert(section2.index(in: tableViewManager) == 1) - + tableViewManager.sections.replace(with: [section2, section1]) - + XCTAssert(section1.index(in: tableViewManager) == 1) XCTAssert(section2.index(in: tableViewManager) == 0) } @@ -228,52 +229,64 @@ class TableViewKitTests: XCTestCase { let item: Item = TestReloadItem() item.reload(in: tableViewManager, with: .automatic) - + let section = item.section(in: tableViewManager) expect(section).to(beNil()) } - + func testRegisterNibCells() { - + let testBundle = Bundle(for: TableViewKitTests.self) - let cellType = CellType.nib(UINib(nibName: String(describing: TestRegisterNibCell.self), bundle: testBundle), TestRegisterNibCell.self) - + let cellType = CellType.nib(UINib(nibName: String(describing: TestRegisterNibCell.self), bundle: testBundle), TestRegisterNibCell.self) + let tableView = UITableView() tableView.register(cellType) - + let cell = tableView.dequeueReusableCell(withIdentifier: cellType.reusableIdentifier) - + expect(cell).toNot(equal(nil)) } - + func testRegisterNibHeaderFooter() { - + let testBundle = Bundle(for: TableViewKitTests.self) - let headerFooterType = HeaderFooterType.nib(UINib(nibName: String(describing: TestRegisterHeaderFooterView.self), bundle: testBundle), TestRegisterHeaderFooterView.self) - + let headerFooterType = HeaderFooterType.nib(UINib(nibName: String(describing: TestRegisterHeaderFooterView.self), bundle: testBundle), TestRegisterHeaderFooterView.self) + let tableView = UITableView() tableView.register(headerFooterType) - + let headerFooterView = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerFooterType.reusableIdentifier) expect(headerFooterView).toNot(equal(nil)) } - + + func testNibClassTypeCells() { + let testBundle = Bundle(for: TableViewKitTests.self) + let type = CellType.nib(UINib(nibName: String(describing: TestRegisterNibCell.self), bundle: testBundle), TestRegisterNibCell.self) + _ = type.cellType + } + + func testNibClassTypeHeaderFooter() { + let testBundle = Bundle(for: TableViewKitTests.self) + let type = HeaderFooterType.nib(UINib(nibName: String(describing: TestRegisterHeaderFooterView.self), bundle: testBundle), TestRegisterHeaderFooterView.self) + _ = type.headerFooterType + } + func testLoginState() { - + let section = StatefulSection() XCTAssert(section.items.count == 1) - + let loginItem = section.items.first as? TestReloadItem XCTAssert(loginItem?.title == "Login") } - + func testRegisterState() { - + let section = StatefulSection() section.transition(to: .register) - + XCTAssert(section.items.count == 1) - + let registerItem = section.items.first as? TestReloadItem XCTAssert(registerItem?.title == "Register") }