Repository: rlopezdiez/RLDTableViewSwift Branch: master Commit: 1bad87913779 Files: 61 Total size: 221.2 KB Directory structure: gitextract_uxx7488s/ ├── .gitignore ├── Classes/ │ ├── RLDHandledViewProtocol.swift │ ├── RLDTableViewController.swift │ ├── RLDTableViewDataSource.swift │ ├── RLDTableViewDelegate.swift │ ├── RLDTableViewEventHandler.swift │ ├── RLDTableViewEventHandlerProvider.swift │ └── RLDTableViewModel.swift ├── LICENSE ├── README.md ├── RLDTableViewSwift.podspec ├── Sample app/ │ ├── TableViewPrototype/ │ │ ├── AppDelegate.swift │ │ ├── Event handlers/ │ │ │ ├── RLDGenericTableViewCellEventHandler.swift │ │ │ └── RLDTableViewHeaderViewEventHandler.swift │ │ ├── Info.plist │ │ ├── Models/ │ │ │ ├── RLDBigPictureTableViewCellModel.swift │ │ │ ├── RLDCommentTableViewCellModel.swift │ │ │ ├── RLDGenericTableViewCellModel.swift │ │ │ ├── RLDSimpleTableViewCellModel.swift │ │ │ ├── RLDTableViewHeaderViewModel.swift │ │ │ └── RLDTableViewModelProvider.swift │ │ ├── RLDTableViewSwift-Info.plist │ │ ├── Resources/ │ │ │ ├── Images.xcassets/ │ │ │ │ ├── 0_small.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 1_small.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 2_big.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 2_small.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 3_small.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 4_big.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 5_small.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 6_big.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 7_big.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 8_small.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ ├── 9_small.imageset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── LaunchScreen.xib │ │ │ ├── Main.storyboard │ │ │ ├── RLDTableViewHeaderView.xib │ │ │ └── viewModelData.plist │ │ ├── View controllers/ │ │ │ ├── RLDMasterViewController.swift │ │ │ └── RLDWebViewController.swift │ │ └── Views/ │ │ ├── RLDBigPictureTableViewCell.swift │ │ ├── RLDCommentTableViewCell.swift │ │ ├── RLDGenericTableViewCell.swift │ │ ├── RLDSimpleTableViewCell.swift │ │ └── RLDTableViewHeaderView.swift │ └── TableViewPrototype.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── TableViewPrototype.xccheckout │ └── xcuserdata/ │ └── rhocassiopeiae.xcuserdatad/ │ └── xcschemes/ │ ├── RLDTableViewSwift.xcscheme │ ├── TableViewPrototype.xcscheme │ └── xcschememanagement.plist └── Tests/ ├── Tests/ │ ├── Info.plist │ ├── RLDTableViewDataSourceTests.swift │ ├── RLDTableViewDelegateTests.swift │ ├── RLDTableViewEventHandlerProviderTests.swift │ ├── RLDTableViewModelTests.swift │ └── TestHelpers.swift └── Tests.xcodeproj/ ├── project.pbxproj ├── project.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── Tests.xccheckout └── xcshareddata/ └── xcschemes/ └── Tests.xcscheme ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ Sample app/TableViewPrototype.xcodeproj/project.xcworkspace/xcuserdata ================================================ FILE: Classes/RLDHandledViewProtocol.swift ================================================ import UIKit public protocol RLDHandledViewProtocol:class { var eventHandler:RLDTableViewEventHandler? {get set} } ================================================ FILE: Classes/RLDTableViewController.swift ================================================ import UIKit import RLDTableViewSwift // MARK: UIView extension to find the first responder private extension UIView { private weak static var _firstResponder:UIView? class func rld_firstResponder() -> UIView? { UIApplication.sharedApplication().sendAction(Selector("setFirstResponder"), to:nil, from:nil, forEvent:nil) return _firstResponder } func setFirstResponder() { UIView._firstResponder = self } } // MARK: UITableView extension to scroll to the first responder cell private extension UITableView { func scrollToFirstResponder(animated:Bool) { if let firstResponder = UIView.rld_firstResponder() { let firstResponderFrame = firstResponder.convertRect(firstResponder.bounds, toView:self) self.scrollRectToVisible(firstResponderFrame, animated:animated) } } } // MARK: UITableView extension to know wether multiple selection is enabled private extension UITableView { var multipleSelectionModeEnabled: Bool { return (self.editing ? self.allowsMultipleSelectionDuringEditing : self.allowsMultipleSelection) } } // MARK: RLDTableViewController class public class RLDTableViewController:UIViewController { // MARK: Initialization required public init(style: UITableViewStyle) { super.init(nibName:nil, bundle:nil) tableView = UITableView(frame:CGRectZero, style:style) } required public init?(coder aDecoder: NSCoder) { super.init(coder:aDecoder) } // MARK: Data source and delegate configuration private var tableViewDataSource:RLDTableViewDataSource? private var tableViewDelegate:RLDTableViewDelegate? public var tableViewModel:RLDTableViewModel? { didSet { if let tableViewModel = tableViewModel { tableViewDataSource = RLDTableViewDataSource(tableViewModel:tableViewModel) tableViewDelegate = RLDTableViewDelegate(tableViewModel:tableViewModel) tableView?.dataSource = tableViewDataSource tableView?.delegate = tableViewDelegate tableView?.reloadData() } } } // MARK: View management lazy public var tableView: UITableView? = { return UITableView(frame:CGRectZero) }() override public var view: UIView! { get { return super.view } set { if let tableView = newValue as? UITableView { self.tableView = tableView super.view = tableView } else { fatalError("The view must be an UITableView") } } } var clearsSelectionOnViewWillAppear: Bool = true var refreshControl: UIRefreshControl? { didSet { if let refreshControl = refreshControl { self.tableView!.insertSubview(refreshControl, atIndex:0) } } } override public func viewWillAppear(animated:Bool) { startObservingKeyboardNotifications() if clearsSelectionOnViewWillAppear { clearTableViewSelection(animated) } super.viewWillAppear(animated) } override public func viewWillDisappear(animated:Bool) { super.viewWillDisappear(animated) stopObservingKeyboardNotifications() } override public func viewDidAppear(animated:Bool) { super.viewDidAppear(animated) tableView!.flashScrollIndicators() } override public func setEditing(editing:Bool, animated:Bool) { super.setEditing(editing, animated:animated) tableView!.setEditing(editing, animated:animated) } // MARK: Keyboard notifications handling private func startObservingKeyboardNotifications() { NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(RLDTableViewController.keyboardWillShowWithKeyboardChangeNotification(_:)), name:UIKeyboardWillShowNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(RLDTableViewController.keyboardWillHideWithKeyboardChangeNotification(_:)), name:UIKeyboardWillHideNotification, object: nil) } private func stopObservingKeyboardNotifications() { NSNotificationCenter.defaultCenter().removeObserver(self) } func keyboardWillShowWithKeyboardChangeNotification(notification:NSNotification) { synchronizeAnimationWithKeyboardChangeNotification(notification, animations: { () -> Void in self.tableView!.frame = CGRect(x:self.tableView!.frame.origin.x, y:self.tableView!.frame.origin.y, width:self.tableView!.frame.size.width, height:self.tableView!.superview!.bounds.size.height - self.keyboardHeightWithKeyboardNotification(notification)) self.tableView?.scrollToFirstResponder(false) }) } func keyboardWillHideWithKeyboardChangeNotification(notification:NSNotification) { synchronizeAnimationWithKeyboardChangeNotification(notification, animations: { () -> Void in self.tableView!.frame = self.tableView!.superview!.bounds }) } private func synchronizeAnimationWithKeyboardChangeNotification(notification:NSNotification, animations:(()->Void)?) { if let animations = animations, let userInfo = notification.userInfo as? [String:AnyObject] { let animationDuration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue if (animationDuration == 0) { animations() } else { let animationCurve = UIViewAnimationCurve(rawValue:(userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).integerValue)! UIView.beginAnimations(nil, context:nil) UIView.setAnimationDuration(animationDuration) UIView.setAnimationCurve(animationCurve) UIView.setAnimationBeginsFromCurrentState(true) animations() UIView.commitAnimations() } } } private func keyboardHeightWithKeyboardNotification(notification:NSNotification) -> CGFloat { let keyboardBounds = keyboardBoundsWithKeyboardNotification(notification) return keyboardBounds.size.height } private func keyboardBoundsWithKeyboardNotification(notification:NSNotification) -> CGRect { if let userInfo = notification.userInfo as? [String:NSValue], let keyboardFrameValue = userInfo[UIKeyboardFrameEndUserInfoKey] { return keyboardFrameValue.CGRectValue() } return CGRectZero } // MARK: Selection clearing private func clearTableViewSelection(animated:Bool) { if !tableView!.multipleSelectionModeEnabled { if let indexPathForSelectedRow = tableView?.indexPathForSelectedRow { synchronizeDeselectionAnimationOfRow(indexPathForSelectedRow, animated:animated) } } } private func synchronizeDeselectionAnimationOfRow(indexPathForSelectedRow:NSIndexPath, animated:Bool) { if let transitionCoordinator = transitionCoordinator() { transitionCoordinator.animateAlongsideTransitionInView(tableView, animation: { (context:UIViewControllerTransitionCoordinatorContext!) -> Void in self.tableView!.deselectRowAtIndexPath(indexPathForSelectedRow, animated:animated) }, completion: { (context:UIViewControllerTransitionCoordinatorContext!) -> Void in if context.isCancelled() { self.tableView!.selectRowAtIndexPath(indexPathForSelectedRow, animated:false, scrollPosition:UITableViewScrollPosition.None) } }) } } } ================================================ FILE: Classes/RLDTableViewDataSource.swift ================================================ import UIKit public class RLDTableViewDataSource:NSObject, UITableViewDataSource { // MARK: Initialization private(set) var tableViewModel:RLDTableViewModel? required public init(tableViewModel:RLDTableViewModel) { self.tableViewModel = tableViewModel } // MARK: Cell generation public func tableView(tableView:UITableView, cellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell { let cellModel = self.cellModel(indexPath) return tableView.dequeueReusableCellWithIdentifier(cellModel.reuseIdentifier, forIndexPath:indexPath) } // MARK: Sections public func numberOfSectionsInTableView(tableView:UITableView) -> Int { return tableViewModel!.sectionModels.count } public func tableView(tableView:UITableView, numberOfRowsInSection section:Int) -> Int { let sectionModel = tableViewModel!.sectionModels[section] return sectionModel.cellModels.count } public func tableView(tableView:UITableView, titleForHeaderInSection section:Int) -> String? { return title(forSection:section, sectionAccessoryViewModel:{ (sectionModel) -> RLDTableViewSectionAccessoryViewModel? in return sectionModel.header }) } public func tableView(tableView:UITableView, titleForFooterInSection section:Int) -> String? { return title(forSection:section, sectionAccessoryViewModel:{ (sectionModel) -> RLDTableViewSectionAccessoryViewModel? in return sectionModel.footer }) } private func title(forSection section:Int, sectionAccessoryViewModel:((sectionModel:RLDTableViewSectionModel)->RLDTableViewSectionAccessoryViewModel?)) -> String? { let sectionModel = tableViewModel!.sectionModels[section] if let sectionAccessoryViewModel = sectionAccessoryViewModel(sectionModel:sectionModel) { return sectionAccessoryViewModel.title } return nil } // MARK: Sections index titles public func sectionIndexTitlesForTableView(tableView:UITableView) -> [String]? { return tableViewModel!.sectionIndexTitles } public func tableView(tableView:UITableView, sectionForSectionIndexTitle title:String, atIndex index:Int) -> Int { for (index, sectionModel) in tableViewModel!.sectionModels.enumerate() { if let indexTitle = sectionModel.indexTitle { if indexTitle == title { return index } } } return 0 } // MARK: Data source edition public func tableView(tableView:UITableView, canEditRowAtIndexPath indexPath:NSIndexPath) -> Bool { let cellModel = self.cellModel(indexPath) if let editable = cellModel.editable { return editable } return false } public func tableView(tableView:UITableView, canMoveRowAtIndexPath indexPath:NSIndexPath) -> Bool { let cellModel = self.cellModel(indexPath) if let movable = cellModel.movable { return movable } return false } public func tableView(tableView:UITableView, commitEditingStyle editingStyle:UITableViewCellEditingStyle, forRowAtIndexPath indexPath:NSIndexPath) { switch editingStyle { case .Insert: self.tableView(tableView, commitInsertionForRowAtIndexPath:indexPath) case .Delete: self.tableView(tableView, commitDeletionForRowAtIndexPath:indexPath) case .None: break } } public func tableView(tableView:UITableView, moveRowAtIndexPath sourceIndexPath:NSIndexPath, toIndexPath destinationIndexPath:NSIndexPath) { let sourceSectionModel = tableViewModel!.sectionModels[sourceIndexPath.section] let destinationSectionModel = tableViewModel!.sectionModels[destinationIndexPath.section] let cellModel = sourceSectionModel.cellModels[sourceIndexPath.row] sourceSectionModel.remove(cellModel) destinationSectionModel.insert(cellModel, atIndex:destinationIndexPath.row) } private func tableView(tableView:UITableView, commitInsertionForRowAtIndexPath indexPath:NSIndexPath) { let sectionModel = tableViewModel!.sectionModels[indexPath.section] if let defaultCellModelClassForInsertions = sectionModel.defaultCellModelClassForInsertions { let cellModelType = NSClassFromString(defaultCellModelClassForInsertions) as! RLDTableViewCellModel.Type let cellModel = cellModelType.init() sectionModel.insert(cellModel, atIndex:indexPath.row) tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation:UITableViewRowAnimation.Automatic) } } private func tableView(tableView:UITableView, commitDeletionForRowAtIndexPath indexPath:NSIndexPath) { let sectionModel = tableViewModel!.sectionModels[indexPath.section] let cellModel = sectionModel.cellModels[indexPath.row] sectionModel.remove(cellModel) tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation:UITableViewRowAnimation.Automatic) } // MARK: Model accessors private func cellModel(indexPath:NSIndexPath) -> RLDTableViewCellModel { let sectionModel = tableViewModel!.sectionModels[indexPath.section] return sectionModel.cellModels[indexPath.row] } } ================================================ FILE: Classes/RLDTableViewDelegate.swift ================================================ import UIKit public class RLDTableViewDelegate:NSObject, UITableViewDelegate { // Initialization private var tableViewModel:RLDTableViewModel required public init(tableViewModel:RLDTableViewModel) { self.tableViewModel = tableViewModel } // MARK: Display customization public func tableView(tableView:UITableView, willDisplayCell cell:UITableViewCell, forRowAtIndexPath indexPath:NSIndexPath) { link(eventHandler(tableView, indexPath:indexPath, cell:cell), view:cell, andExecute:{ (eventHandler) -> Void in eventHandler.willDisplayView() }) } public func tableView(tableView:UITableView, willDisplayHeaderView view:UIView, forSection section:Int) { self.tableView(tableView, willDisplayHeaderFooterView:view, model:tableViewModel.sectionModels[section].header) } public func tableView(tableView:UITableView, willDisplayFooterView view:UIView, forSection section:Int) { self.tableView(tableView, willDisplayHeaderFooterView:view, model:tableViewModel.sectionModels[section].footer) } private func tableView(tableView:UITableView, willDisplayHeaderFooterView view:UIView, model:RLDTableViewSectionAccessoryViewModel?) { if let model = model { link(eventHandler(tableView, viewModel:model, view:view), view:view, andExecute:{ (eventHandler) -> Void in eventHandler.willDisplayView() }) } } private func link(eventHandler:RLDTableViewEventHandler, view:UIView, andExecute closure:(eventHandler:RLDTableViewEventHandler)->Void) { if let view = view as? RLDHandledViewProtocol { view.eventHandler = eventHandler } closure(eventHandler:eventHandler) } public func tableView(tableView:UITableView, didEndDisplayingCell cell:UITableViewCell, forRowAtIndexPath indexPath:NSIndexPath) { eventHandler(tableView, indexPath:indexPath, cell:cell).didEndDisplayingView() } public func tableView(tableView:UITableView, didEndDisplayingHeaderView view:UIView, forSection section:Int) { if let headerViewModel = tableViewModel.sectionModels[section].header { eventHandler(tableView, viewModel:headerViewModel, view:view).didEndDisplayingView() } } public func tableView(tableView:UITableView, didEndDisplayingFooterView view:UIView, forSection section:Int) { if let footerViewModel = tableViewModel.sectionModels[section].footer { eventHandler(tableView, viewModel:footerViewModel, view:view).didEndDisplayingView() } } // MARK: Variable height support public func tableView(tableView:UITableView, heightForRowAtIndexPath indexPath:NSIndexPath) -> CGFloat { if let height = cellModel(indexPath).height { return height } return UITableViewAutomaticDimension } public func tableView(tableView:UITableView, heightForHeaderInSection section:Int) -> CGFloat { if let height = tableViewModel.sectionModels[section].header?.height { return height } return UITableViewAutomaticDimension } public func tableView(tableView:UITableView, heightForFooterInSection section:Int) -> CGFloat { if let height = tableViewModel.sectionModels[section].footer?.height { return height } return UITableViewAutomaticDimension } public func tableView(tableView:UITableView, estimatedHeightForRowAtIndexPath indexPath:NSIndexPath) -> CGFloat { if let estimatedHeight = cellModel(indexPath).estimatedHeight { return estimatedHeight } return UITableViewAutomaticDimension } public func tableView(tableView:UITableView, estimatedHeightForHeaderInSection section:Int) -> CGFloat { if let estimatedHeight = tableViewModel.sectionModels[section].header?.estimatedHeight { return estimatedHeight } return UITableViewAutomaticDimension } public func tableView(tableView:UITableView, estimatedHeightForFooterInSection section:Int) -> CGFloat { if let estimatedHeight = tableViewModel.sectionModels[section].footer?.estimatedHeight { return estimatedHeight } return UITableViewAutomaticDimension } // MARK: Section header and footer public func tableView(tableView:UITableView, viewForHeaderInSection section:Int) -> UIView? { if let reuseIdentifier = tableViewModel.sectionModels[section].header?.reuseIdentifier { return tableView.dequeueReusableHeaderFooterViewWithIdentifier(reuseIdentifier) } return nil } public func tableView(tableView:UITableView, viewForFooterInSection section:Int) -> UIView? { if let reuseIdentifier = tableViewModel.sectionModels[section].footer?.reuseIdentifier { return tableView.dequeueReusableHeaderFooterViewWithIdentifier(reuseIdentifier) } return nil } // MARK: Accessories (disclosures) public func tableView(tableView:UITableView, accessoryButtonTappedForRowWithIndexPath indexPath:NSIndexPath) { eventHandler(tableView, indexPath:indexPath).accessoryButtonTapped() } // MARK: Selection public func tableView(tableView:UITableView, shouldHighlightRowAtIndexPath indexPath:NSIndexPath) -> Bool { return eventHandler(tableView, indexPath:indexPath).shouldHighlightView() } public func tableView(tableView:UITableView, didHighlightRowAtIndexPath indexPath:NSIndexPath) { eventHandler(tableView, indexPath:indexPath).didHighlightView() } public func tableView(tableView:UITableView, didUnhighlightRowAtIndexPath indexPath:NSIndexPath) { eventHandler(tableView, indexPath:indexPath).didUnhighlightView() } public func tableView(tableView:UITableView, willSelectRowAtIndexPath indexPath:NSIndexPath) -> NSIndexPath? { let selectedIndexPath = eventHandler(tableView, indexPath:indexPath).willSelectView() return (selectedIndexPath != nil ? selectedIndexPath : indexPath) } public func tableView(tableView:UITableView, willDeselectRowAtIndexPath indexPath:NSIndexPath) -> NSIndexPath? { let deselectedIndexPath = eventHandler(tableView, indexPath:indexPath).willDeselectView() return (deselectedIndexPath != nil ? deselectedIndexPath : indexPath) } public func tableView(tableView:UITableView, didSelectRowAtIndexPath indexPath:NSIndexPath) { eventHandler(tableView, indexPath:indexPath).didSelectView() } public func tableView(tableView:UITableView, didDeselectRowAtIndexPath indexPath:NSIndexPath) { eventHandler(tableView, indexPath:indexPath).didDeselectView() } // MARK: Editing public func tableView(tableView:UITableView, editingStyleForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCellEditingStyle { if let editingStyle = cellModel(indexPath).editingStyle { return editingStyle } return UITableViewCellEditingStyle.None } public func tableView(tableView:UITableView, titleForDeleteConfirmationButtonForRowAtIndexPath indexPath:NSIndexPath) -> String? { if let titleForDeleteConfirmationButton = cellModel(indexPath).titleForDeleteConfirmationButton { return titleForDeleteConfirmationButton } return "" } public func tableView(tableView:UITableView, editActionsForRowAtIndexPath indexPath:NSIndexPath) -> [UITableViewRowAction]? { return eventHandler(tableView, indexPath:indexPath).editActions() } public func tableView(tableView:UITableView, shouldIndentWhileEditingRowAtIndexPath indexPath:NSIndexPath) -> Bool { if let shouldIndentWhileEditing = cellModel(indexPath).shouldIndentWhileEditing { return shouldIndentWhileEditing } return false } public func tableView(tableView:UITableView, willBeginEditingRowAtIndexPath indexPath:NSIndexPath) { eventHandler(tableView, indexPath:indexPath).willBeginEditing() } public func tableView(tableView:UITableView, didEndEditingRowAtIndexPath indexPath:NSIndexPath) { eventHandler(tableView, indexPath:indexPath).didEndEditing() } // MARK: Moving and reordering public func tableView(tableView:UITableView, targetIndexPathForMoveFromRowAtIndexPath sourceIndexPath:NSIndexPath, toProposedIndexPath proposedDestinationIndexPath:NSIndexPath) -> NSIndexPath { return proposedDestinationIndexPath } // MARK: Indentation public func tableView(tableView:UITableView, indentationLevelForRowAtIndexPath indexPath:NSIndexPath) -> Int { if let indentationLevel = cellModel(indexPath).indentationLevel { return indentationLevel } return 0 } // MARK: Copy and Paste public func tableView(tableView:UITableView, shouldShowMenuForRowAtIndexPath indexPath:NSIndexPath) -> Bool { if let shouldShowMenu = cellModel(indexPath).shouldShowMenu { return shouldShowMenu } return false } public func tableView(tableView:UITableView, canPerformAction action:Selector, forRowAtIndexPath indexPath:NSIndexPath, withSender sender:AnyObject?) -> Bool { return eventHandler(tableView, indexPath:indexPath).canPerform(action, withSender:sender!) } public func tableView(tableView:UITableView, performAction action:Selector, forRowAtIndexPath indexPath:NSIndexPath, withSender sender:AnyObject?) { return eventHandler(tableView, indexPath:indexPath).performAction(action, withSender:sender!) } // MARK: Event handler generation private func eventHandler(tableView:UITableView, indexPath:NSIndexPath) -> RLDTableViewCellEventHandler { let cellModel = self.cellModel(indexPath) tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.None, animated:false) let cell = tableView.cellForRowAtIndexPath(indexPath)! return eventHandler(tableView, indexPath:indexPath, cell:cell) } private func eventHandler(tableView:UITableView, indexPath:NSIndexPath, cell:UITableViewCell) -> RLDTableViewCellEventHandler { let cellModel = self.cellModel(indexPath) return eventHandler(tableView, viewModel:cellModel, view:cell) as! RLDTableViewCellEventHandler } private func eventHandler(tableView:UITableView, viewModel:RLDTableViewReusableViewModel, view:UIView) -> RLDTableViewEventHandler { if let eventHandler = reusableEventHandler(tableView, viewModel:viewModel, view:view) { eventHandler.tableView = tableView eventHandler.viewModel = viewModel eventHandler.view = view return eventHandler } else { let eventHandler = RLDTableViewEventHandlerProvider.eventHandler(tableView, viewModel:viewModel, view:view) assert(eventHandler != nil, "Unable to find suitable event hander for \ntableView: \(tableView)\nviewModel: \(viewModel)\nview: \(view)") return eventHandler! } } private func reusableEventHandler(tableView:UITableView, viewModel:RLDTableViewReusableViewModel, view:UIView) -> RLDTableViewEventHandler? { if let handledView = view as? RLDHandledViewProtocol, let eventHandler = handledView.eventHandler { let eventHandlerClass = eventHandler.dynamicType if eventHandlerClass.canHandle(tableView, viewModel:viewModel, view:view) { return eventHandler } } return nil } // MARK: Model accessors private func cellModel(indexPath:NSIndexPath) -> RLDTableViewCellModel { let sectionModel = tableViewModel.sectionModels[indexPath.section] return sectionModel.cellModels[indexPath.row] } } ================================================ FILE: Classes/RLDTableViewEventHandler.swift ================================================ import UIKit // MARK: RLDTableViewEventHandler class public class RLDTableViewEventHandler { // MARK: Event handler registration public class func register() { RLDTableViewEventHandlerProvider.register(self) } // MARK: Suitability checking public class func canHandle(tableView:UITableView, viewModel:RLDTableViewReusableViewModel, view:UIView) -> Bool { return false } // MARK: Initialization and dependencies public var tableView:UITableView public var viewModel:RLDTableViewReusableViewModel public var view:UIView required public init(tableView:UITableView, viewModel:RLDTableViewReusableViewModel, view:UIView) { self.tableView = tableView self.viewModel = viewModel self.view = view } // MARK: Display customization public func willReuseView() {} public func willDisplayView() {} public func didEndDisplayingView() {} } // MARK: RLDTableViewCellEventHandler class public class RLDTableViewCellEventHandler:RLDTableViewEventHandler { // MARK: Accessories (disclosures) public func accessoryButtonTapped() {} // MARK: Selection public func shouldHighlightView() -> Bool { return false } public func didHighlightView() {} public func didUnhighlightView() {} public func willSelectView() -> NSIndexPath? { return nil } public func willDeselectView() -> NSIndexPath? { return nil } public func didSelectView() {} public func didDeselectView() {} // MARK: Editing public func willBeginEditing() {} public func didEndEditing() {} public func editActions() -> [UITableViewRowAction]? { return nil } // MARK: Copy and Paste public func canPerform(action:Selector, withSender sender:AnyObject) -> Bool { return false } public func performAction(action:Selector, withSender sender:AnyObject) {} } // MARK: RLDTableViewSectionAccessoryViewEventHandler class public class RLDTableViewSectionAccessoryViewEventHandler:RLDTableViewEventHandler { } ================================================ FILE: Classes/RLDTableViewEventHandlerProvider.swift ================================================ import UIKit class RLDTableViewEventHandlerProvider { private static var availableEventHanlderClasses:[RLDTableViewEventHandler.Type] = [] class func register(eventHandlerClass:RLDTableViewEventHandler.Type) { availableEventHanlderClasses.append(eventHandlerClass) } class func eventHandler(tableView:UITableView, viewModel:RLDTableViewReusableViewModel, view:UIView) -> RLDTableViewEventHandler? { for eventHandler in availableEventHanlderClasses { if eventHandler.canHandle(tableView, viewModel:viewModel, view:view) { return eventHandler.init(tableView:tableView, viewModel:viewModel, view:view) } } return nil } } ================================================ FILE: Classes/RLDTableViewModel.swift ================================================ // // RLDNavigation // // Copyright (c) 2015 Rafael Lopez Diez. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import UIKit // MARK: RLDTableViewReusableViewModel class public class RLDTableViewReusableViewModel { // MARK: Initialization required public init() { reuseIdentifier = NSStringFromClass(self.dynamicType) } // MARK: Parent linking weak private(set) var sectionModel:RLDTableViewSectionModel? public func set(sectionModel newSectionModel:RLDTableViewSectionModel) { sectionModel = newSectionModel } // MARK: Reuse identifier public var reuseIdentifier:String // MARK: Variable height support public var height:CGFloat? public var estimatedHeight:CGFloat? } // MARK: RLDTableViewCellModel class public class RLDTableViewCellModel:RLDTableViewReusableViewModel { // MARK: Indentation public var indentationLevel:NSInteger? // MARK: Editing public var editable:Bool? public var movable:Bool? public var shouldIndentWhileEditing:Bool? public var editingStyle:UITableViewCellEditingStyle? public var titleForDeleteConfirmationButton:String? // MARK: Copy and Paste public var shouldShowMenu:Bool? } // MARK: RLDTableViewSectionAccessoryViewModel class public class RLDTableViewSectionAccessoryViewModel:RLDTableViewReusableViewModel { // MARK: Title public var title:String? } // MARK: RLDTableViewSectionModel class public class RLDTableViewSectionModel:Equatable { // MARK: Parent linking weak private(set) var tableModel:RLDTableViewModel? public func set(tableModel newTableModel:RLDTableViewModel) { tableModel = newTableModel } // Listing cell models private(set) var cellModels:[RLDTableViewCellModel] = [] // MARK: Insertions public var defaultCellModelClassForInsertions:String? public func add(cellModel:RLDTableViewCellModel) { cellModel.set(sectionModel:self) cellModels.append(cellModel) } public func insert(cellModel:RLDTableViewCellModel, atIndex index:Int) { cellModel.set(sectionModel:self) cellModels.insert(cellModel, atIndex:index) } // MARK: Deletions public func remove(cellModel:RLDTableViewCellModel) { cellModel.set(sectionModel:self) cellModels = cellModels.filter( {!($0 === cellModel)} ) } // MARK: Index title public var indexTitle:String? // MARK: Section header and footer public var header:RLDTableViewSectionAccessoryViewModel? { didSet { header?.set(sectionModel:self) } } public var footer:RLDTableViewSectionAccessoryViewModel? { didSet { footer?.set(sectionModel:self) } } } // MARK: RLDTableViewSectionModel equality operator public func == (lhs:RLDTableViewSectionModel, rhs:RLDTableViewSectionModel) -> Bool { return lhs === rhs } // MARK: RLDTableViewModel class public class RLDTableViewModel { public init() { // XCode Version 6.3 (6D570) forces me to add this initializer } // MARK: Listing section models private(set) var sectionModels:[RLDTableViewSectionModel] = [] // MARK: Adding section models public func addNewSectionModel() -> RLDTableViewSectionModel { let newSectionModel = RLDTableViewSectionModel() add(newSectionModel) return newSectionModel } private func add(sectionModel:RLDTableViewSectionModel) { sectionModel.set(tableModel:self) sectionModels.append(sectionModel) } // MARK: Adding cell models public func add(cellModel:RLDTableViewCellModel) { if sectionModels.count == 0 { addNewSectionModel() } add(cellModel, toSectionModel:sectionModels.last!) } public func add(cellModel:RLDTableViewCellModel, toSectionModel sectionModel:RLDTableViewSectionModel) { if !sectionModels.contains(sectionModel) { add(sectionModel) } sectionModel.add(cellModel) } // MARK: Section index titles private var _sectionIndexTitles:[String]? public var sectionIndexTitles:[String]? { set { _sectionIndexTitles = newValue } get { if (_sectionIndexTitles == nil) { var indexTitles:[String] = [] for sectionModel in sectionModels { if let indexTitle = sectionModel.indexTitle { indexTitles.append(indexTitle) } } if indexTitles.count > 0 { _sectionIndexTitles = indexTitles } } return _sectionIndexTitles } } } ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: README.md ================================================ # RLDTableViewSwift The ubiquitous `UITableView`, with its required data source and delegate, is one of the main sources of badly architected solutions in the iOS platform. SDK classes, as `UITableViewController`, enforce bad design practices, and lead you to the problem of the Massive View Controller, where the single responsibility principle is completely missed. `RLDTableViewSwift` is a set of ready-to-use Swift classes that will get you back in track, helping you to refine the shape of your app. They enforce the SOLID principles, with an adaptation of the Model-View-Presenter pattern. ![RLDTableViewSwift sample app](https://raw.githubusercontent.com/rlopezdiez/RLDTableViewSwift/master/README.jpg) > [Objective-C version](https://github.com/rlopezdiez/RLDTableViewSuite) also available. ## Foundations ### RLDTableViewDataSource and RLDTableViewDelegate These are reusable components for the data source and delegate of all your table views. With them, you won't need to write the same boilerplate code again and again –they will just use your table view model and event handlers to be able to manage any `UITableView`. They fully implement the `UITableViewDataSource` and `UITableViewDelegate` protocols, and keep synchronized between themselves, giving you all the `UITableView` features with little effort from your side: - Display customization - Variable height support - Sections, with headers and footers - Sections index titles - Cell accessories (disclosures) - Highlighting and selection of cells - Editing, moving and reordering cells - Indentation - Copy and Paste ### Table view model The table view model defines the state, look and feel of the views in your table view, and the relationships between them. You can use the provided generic implementations, or subclass them to tailor them to yor needs: - `RLDTableViewCellModel`, for cells, - `RLDTableViewSectionModel`, for table sections, - `RLDTableViewSectionAccessoryViewModel`, for section headers and footers, and - `RLDTableViewModel`, for the table view itself. ### Event handlers Every view in your table view (like cells, section headers and section footers) should be managed by an event handler. It will receive all the view related actions from the table view delegate and react to user generated events on the view. Event handlers are short-lived objects that will be instantiated on demand and destroyed once the event that caused its creation has been handled or when the view they manage is deallocated. They should not store state *(because we have a model, right?)* and they can either configure the look of the view by themselves, or pass the view model to the view, so it can autoconfigure. The provided sample app uses the latest approach. You have two event handlers classes that you can subclass: - `RLDTableViewCellEventHandler`, for table view cells, and - `RLDTableViewSectionAccessoryViewEventHandler`, for section headers and footers. Although it is not mandatory, if the handled view conforms to the `RLDHandledViewProtocol` it will receive an event handler just before the view is displayed for the first time. The view will retain this event handler during all its lifecycle to be able to react to the user generated events on the view. This retained event handler will be reused together with the cell, improving the performance. ### Event handler provider In order to create the best suited event handler on demand, `RLDTableViewDelegate` will need an event handler provider to find the first event handler which supports a certain combination of table view, view and view model. To make your event handlers available to the provider, you must register them using their `register()` class method. The best way to make sure all your classes are ready when needed is registering them in the same place. You can use a registar class with a function that is called once when the application has finished launching, or register them just before their first use. The included sample app uses this approach. ### RLDTableViewController This class is a drop-in replacement of `UITableViewController`. It just requires a table view model to be able to configure your table view. Internally, it uses the default implementations of `RLDTableViewDataSource`, `RLDTableViewDelegate` and `RLDTableViewEventHandlerProvider`, so it's the easiest way to use `RLDTableViewSwift` without worrying about its internals, while getting the most of having a proper architecture. ## Installing ### Using CocoaPods To use the latest stable release of `RLDTableViewSwift`, just add the following to your project `Podfile`: ``` pod 'RLDTableViewSwift', '~> 0.2.1' ``` If you like to live on the bleeding edge, you can use the `master` branch with: ``` pod 'RLDTableViewSwift', :git => 'https://github.com/rlopezdiez/RLDTableViewSwift' ``` ### Manually 1. Clone, add as a submodule or [download.](https://github.com/rlopezdiez/RLDTableViewSwift/zipball/master) 2. Add all the files under `Classes` to your project. 3. Enjoy. ## License `RLDTableViewSwift` is available under the Apache License, Version 2.0. See LICENSE file for more info. This README has been made with [(GitHub-Flavored) Markdown Editor](http://jbt.github.io/markdown-editor) ================================================ FILE: RLDTableViewSwift.podspec ================================================ Pod::Spec.new do |s| s.name = 'RLDTableViewSwift' s.version = '0.2.1' s.homepage = 'https://github.com/rlopezdiez/RLDTableViewSwift.git' s.summary = 'Reusable table view controller, data source and delegate for all your UITableView needs in Swift' s.authors = { 'Rafael Lopez Diez' => 'https://www.linkedin.com/in/rafalopezdiez' } s.source = { :git => 'https://github.com/rlopezdiez/RLDTableViewSwift.git', :tag => s.version.to_s } s.source_files = 'Classes/*.swift' s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } s.platform = :ios, '8.0' s.requires_arc = true end ================================================ FILE: Sample app/TableViewPrototype/AppDelegate.swift ================================================ import UIKit @UIApplicationMain class AppDelegate:UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application:UIApplication, didFinishLaunchingWithOptions launchOptions:[NSObject:AnyObject]?) -> Bool { return true } } ================================================ FILE: Sample app/TableViewPrototype/Event handlers/RLDGenericTableViewCellEventHandler.swift ================================================ import UIKit import RLDTableViewSwift class RLDGenericTableViewCellEventHandler:RLDTableViewCellEventHandler { // MARK: Suitability checking override class func canHandle(tableView:UITableView, viewModel:RLDTableViewReusableViewModel, view:UIView) -> Bool { if let viewModel = viewModel as? RLDGenericTableViewCellModel, let view = view as? RLDGenericTableViewCell { return true } return false } // MARK: Cell customization override func willDisplayView() { if let view = view as? RLDGenericTableViewCell { view.model = (viewModel as! RLDGenericTableViewCellModel) } } // MARK: View highlighting override func shouldHighlightView() -> Bool { return true } override func didHighlightView() { if let view = view as? RLDGenericTableViewCell { view.viewToHighlight.backgroundColor = UIColor(red:0.8, green:0.92, blue:1, alpha:1) } view.alpha = 0.75 } override func didUnhighlightView() { if let view = view as? RLDGenericTableViewCell { view.viewToHighlight.backgroundColor = UIColor.whiteColor() } view.alpha = 1 } // MARK: Cell interactions override func didSelectView() { if let viewModel = viewModel as? RLDGenericTableViewCellModel { open(viewModel.imageURL, title: viewModel.title) } } func didTapCategoryButton() { if let viewModel = viewModel as? RLDGenericTableViewCellModel { open(viewModel.categoryURL, title: viewModel.category) } } private func open(url:String, title:String) { /* This way of navigating to another view controller is good enough for a sample app, but in a real life situation you should consider moving navigation code elsewhere. You can use flow controllers, routers or some kind of view model propagation. You might want to use my navigation library, which is a good complement to this one: https://github.com/rlopezdiez/RLDNavigationSwift */ if let navigationController = navigationController() { let storyboard = UIStoryboard(name:"Main", bundle:nil) let detailViewController = storyboard.instantiateViewControllerWithIdentifier("RLDWebViewController") as! RLDWebViewController detailViewController.title = title detailViewController.url = url navigationController.pushViewController(detailViewController, animated:true) } } private func navigationController() -> UINavigationController? { for var nextView:UIView? = view.superview; nextView != nil; nextView = nextView!.superview { if let nextResponder = nextView?.nextResponder() as? UINavigationController { return nextResponder } } return nil } } ================================================ FILE: Sample app/TableViewPrototype/Event handlers/RLDTableViewHeaderViewEventHandler.swift ================================================ import UIKit import RLDTableViewSwift class RLDTableViewHeaderViewEventHandler:RLDTableViewSectionAccessoryViewEventHandler { // MARK: Suitability checking override class func canHandle(tableView:UITableView, viewModel:RLDTableViewReusableViewModel, view:UIView) -> Bool { if let viewModel = viewModel as? RLDTableViewHeaderViewModel, let view = view as? RLDTableViewHeaderView { return true } return false } // MARK: View customization override func willDisplayView() { if let view = view as? RLDTableViewHeaderView { view.model = (viewModel as! RLDTableViewHeaderViewModel) } } } ================================================ FILE: Sample app/TableViewPrototype/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Sample app/TableViewPrototype/Models/RLDBigPictureTableViewCellModel.swift ================================================ class RLDBigPictureTableViewCellModel:RLDGenericTableViewCellModel { required init() { super.init() reuseIdentifier = "RLDBigPictureTableViewCell" } } ================================================ FILE: Sample app/TableViewPrototype/Models/RLDCommentTableViewCellModel.swift ================================================ class RLDCommentTableViewCellModel:RLDGenericTableViewCellModel { var comment:String = "" required init() { super.init() reuseIdentifier = "RLDCommentTableViewCell" } } ================================================ FILE: Sample app/TableViewPrototype/Models/RLDGenericTableViewCellModel.swift ================================================ import RLDTableViewSwift class RLDGenericTableViewCellModel:RLDTableViewCellModel { var title:String = "" var date:String = "" var category:String = "" var categoryURL:String = "" var imageName:String = "" var imageURL:String = "" } ================================================ FILE: Sample app/TableViewPrototype/Models/RLDSimpleTableViewCellModel.swift ================================================ class RLDSimpleTableViewCellModel:RLDGenericTableViewCellModel { required init() { super.init() reuseIdentifier = "RLDSimpleTableViewCell" } } ================================================ FILE: Sample app/TableViewPrototype/Models/RLDTableViewHeaderViewModel.swift ================================================ import RLDTableViewSwift class RLDTableViewHeaderViewModel:RLDTableViewSectionAccessoryViewModel { required init() { super.init() reuseIdentifier = "RLDTableViewHeaderView" height = 60 estimatedHeight = height } } ================================================ FILE: Sample app/TableViewPrototype/Models/RLDTableViewModelProvider.swift ================================================ import RLDTableViewSwift import UIKit class RLDTableViewModelProvider { static private let DataPlistFileName = "viewModelData" static private let DictionaryTitleKey = "title" static private let DictionaryCellsKey = "cells" static private let DictionaryClassKey = "class" static private let TableViewCellTitleForDeleteConfirmationButton = "Delete" static private let TableViewHeaderViewReuseIdentifier = "RLDTableViewHeaderView" var headerFooterReuseIdentifiersToNibNames = [RLDTableViewModelProvider.TableViewHeaderViewReuseIdentifier:RLDTableViewModelProvider.TableViewHeaderViewReuseIdentifier] lazy var tableViewModel:RLDTableViewModel = { let modelArray = self.modelArray() let tableViewModel = RLDTableViewModel() for sectionDictionary in modelArray { self.tableViewModel(tableViewModel, addSectionWithSectionDictionary:sectionDictionary) } return tableViewModel }() private func modelArray() -> [[String:AnyObject]] { return NSArray(contentsOfFile:NSBundle.mainBundle().pathForResource(RLDTableViewModelProvider.DataPlistFileName, ofType:"plist")!)! as! [[String:AnyObject]] } private func tableViewModel(tableViewModel:RLDTableViewModel, addSectionWithSectionDictionary sectionDictionary:[String:AnyObject]) { let sectionModel = tableViewModel.addNewSectionModel() if let headerTitle = sectionDictionary[RLDTableViewModelProvider.DictionaryTitleKey] as? String { let headerModel = RLDTableViewHeaderViewModel() headerModel.title = headerTitle sectionModel.header = headerModel } for cellDictionary in sectionDictionary[RLDTableViewModelProvider.DictionaryCellsKey] as! [[String:AnyObject]] { self.tableViewModel(tableViewModel, addCellWithCellDictionary:cellDictionary) } } private func tableViewModel(tableViewModel:RLDTableViewModel, addCellWithCellDictionary cellDictionary:[String:AnyObject]) { let cellModel = RLDGenericTableViewCellModel.cellModelForClass(cellDictionary[RLDTableViewModelProvider.DictionaryClassKey] as! String) for (key, value) in cellDictionary { cellModel.setValue(value, forKey:key) } cellModel.titleForDeleteConfirmationButton = RLDTableViewModelProvider.TableViewCellTitleForDeleteConfirmationButton tableViewModel.add(cellModel) } } extension RLDGenericTableViewCellModel { class func cellModelForClass(cellModelClass:String) -> RLDGenericTableViewCellModel { switch cellModelClass { case "RLDBigPictureTableViewCellModel": return RLDBigPictureTableViewCellModel() case "RLDCommentTableViewCellModel": return RLDCommentTableViewCellModel() case "RLDSimpleTableViewCellModel": return RLDSimpleTableViewCellModel() default: fatalError("Unable to find class for \(cellModelClass)") } } func setValue(value:AnyObject, forKey key:String) { switch key { case "category": category = value as! String case "categoryURL": categoryURL = value as! String case "comment": (self as! RLDCommentTableViewCellModel).comment = value as! String case "date": date = value as! String case "editable": editable = (value as! Bool) case "editingStyle": editingStyle = (value as! Int == 1 ? UITableViewCellEditingStyle.Delete : UITableViewCellEditingStyle.None) case "imageName": imageName = value as! String case "imageURL": imageURL = value as! String case "movable": movable = (value as! Bool) case "title": title = value as! String case "class": break default: fatalError("Unable to set the value for \(key)") } } } ================================================ FILE: Sample app/TableViewPrototype/RLDTableViewSwift-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString 0.2.0 CFBundleSignature ???? CFBundleVersion 1 NSPrincipalClass ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/0_small.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "0_small.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/1_small.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "1_small.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/2_big.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "2_big.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/2_small.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "2_small.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/3_small.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "3_small.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/4_big.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "4_big.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/5_small.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "5_small.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/6_big.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "6_big.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/7_big.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "7_big.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/8_small.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "8_small.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/9_small.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x", "filename" : "9_small.jpg" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Sample app/TableViewPrototype/Resources/LaunchScreen.xib ================================================ ================================================ FILE: Sample app/TableViewPrototype/Resources/Main.storyboard ================================================ Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam. ================================================ FILE: Sample app/TableViewPrototype/Resources/RLDTableViewHeaderView.xib ================================================ ================================================ FILE: Sample app/TableViewPrototype/Resources/viewModelData.plist ================================================ cells category Moscow categoryURL https://www.flickr.com/photos/keoki/sets/72157602083268883/ class RLDBigPictureTableViewCellModel date 2 years ago imageName 6_big.jpg imageURL https://www.flickr.com/photos/keoki/1406925773/in/set-72157602083268883 title The colorful Cathedral of Vasily the Blessed category Amsterdam categoryURL https://www.flickr.com/photos/keoki/sets/72157603544030165/ class RLDSimpleTableViewCellModel date 4 years ago imageName 3_small.jpg imageURL https://www.flickr.com/photos/keoki/2135948414/in/set-72157603544030165 title Quiet canals in the “Venice of the North” editable editingStyle 1 category London categoryURL https://www.flickr.com/photos/keoki/sets/72157594446615513/ class RLDSimpleTableViewCellModel date Today imageName 9_small.jpg imageURL https://www.flickr.com/photos/keoki/337540114/in/set-72157594446615513 title Tower Bridge, one of the most iconic bridges in the world editable editingStyle 1 cells category Kuramathi categoryURL https://www.flickr.com/photos/keoki/sets/72157627990642402/ class RLDBigPictureTableViewCellModel date 3 weeks ago imageName 4_big.jpg imageURL https://www.flickr.com/photos/keoki/6286531716/in/set-72157627990642402 title Sea Salt's crab unwinding in the beach category Rasdhoo categoryURL https://www.flickr.com/photos/keoki/sets/72157627991759766/ class RLDSimpleTableViewCellModel date Today imageName 1_small.jpg imageURL https://www.flickr.com/photos/keoki/6286903694/in/set-72157627990642402 title The Maldives, home of beautiful beaches editable movable category Swiss Alps categoryURL https://www.flickr.com/photos/keoki/sets/72157625889560426/ class RLDSimpleTableViewCellModel date 7 years ago imageName 0_small.jpg imageURL https://www.flickr.com/photos/keoki/5384467483/in/set-72157625889560426 title Picturesque peak covered with snow editable movable category Tenerife categoryURL https://www.flickr.com/photos/keoki/sets/72157594446322517/ class RLDSimpleTableViewCellModel date Yesterday imageName 8_small.jpg imageURL https://www.flickr.com/photos/keoki/3556768497/in/set-72157594446322517 title Mount Teide, a 3,718 meter high vulcano editable movable title Magnificent scenery cells class RLDCommentTableViewCellModel comment This building is the seat of the National Assembly of Hungary, and one of the most awesome constructions in Budapest imageName 7_big.jpg imageURL https://www.flickr.com/photos/keoki/336410485/in/set-72157594444731896 title The Hungarian Parliament category Rome categoryURL https://www.flickr.com/photos/keoki/sets/72157594444449793/ class RLDSimpleTableViewCellModel date 3 weeks ago imageName 5_small.jpg imageURL https://www.flickr.com/photos/keoki/188286231/in/set-72157594444449793 title Coliseum, or Flavian Amphitheatre category Paris categoryURL https://www.flickr.com/photos/keoki/sets/72157594444442779/ class RLDSimpleTableViewCellModel date 1 month ago imageName 2_small.jpg imageURL https://www.flickr.com/photos/keoki/188266460/in/set-72157594444442779 title The Basilica of the Sacré-Cœur of Paris title Singular buildings ================================================ FILE: Sample app/TableViewPrototype/View controllers/RLDMasterViewController.swift ================================================ import UIKit class RLDMasterViewController: RLDTableViewController { let modelProvider = RLDTableViewModelProvider() override func viewDidLoad() { super.viewDidLoad() registerNibs() registerEventHandlers() setTableViewModel() attachEditButton() } private func registerNibs() { for (reuseIdentifier, nibName) in modelProvider.headerFooterReuseIdentifiersToNibNames { let nib = UINib(nibName:reuseIdentifier, bundle:nil) tableView?.registerNib(nib, forHeaderFooterViewReuseIdentifier:nibName) } } private func registerEventHandlers() { RLDGenericTableViewCellEventHandler.register() RLDTableViewHeaderViewEventHandler.register() } private func setTableViewModel() { tableViewModel = modelProvider.tableViewModel } private func attachEditButton() { navigationItem.rightBarButtonItem = self.editButtonItem() } override func setEditing(editing:Bool, animated:Bool) { super.setEditing(editing, animated:animated) navigationController?.hidesBarsOnSwipe = !editing navigationController?.hidesBarsWhenVerticallyCompact = !editing } override func prefersStatusBarHidden() -> Bool { return true } } ================================================ FILE: Sample app/TableViewPrototype/View controllers/RLDWebViewController.swift ================================================ import UIKit class RLDWebViewController: UIViewController { var url:String = "" { didSet { if let view = view as? UIWebView { let urlRequest = NSURLRequest(URL:NSURL(string:url)!) view.loadRequest(urlRequest) } } } } ================================================ FILE: Sample app/TableViewPrototype/Views/RLDBigPictureTableViewCell.swift ================================================ class RLDBigPictureTableViewCell:RLDGenericTableViewCell { } ================================================ FILE: Sample app/TableViewPrototype/Views/RLDCommentTableViewCell.swift ================================================ import UIKit class RLDCommentTableViewCell:RLDGenericTableViewCell { @IBOutlet weak var comment:UITextView! override var model:RLDGenericTableViewCellModel? { didSet { if let model = model as? RLDCommentTableViewCellModel { comment.text = model.comment } } } } ================================================ FILE: Sample app/TableViewPrototype/Views/RLDGenericTableViewCell.swift ================================================ import UIKit import RLDTableViewSwift class RLDGenericTableViewCell:UITableViewCell, RLDHandledViewProtocol { @IBOutlet weak var title:UILabel! @IBOutlet weak var date:UILabel? @IBOutlet weak var category:UIButton? @IBOutlet weak var picture:UIImageView! @IBOutlet weak var viewToHighlight:UIView! var eventHandler:RLDTableViewEventHandler? = nil var model:RLDGenericTableViewCellModel? { didSet { if let model = model { title.text = model.title picture.image = UIImage(named:model.imageName) if let date = date { date.text = model.date } if let category = category { category.setTitle(model.category, forState:UIControlState.Normal) } } } } @IBAction func categoryButtonTapped() { if let eventHandler = eventHandler as? RLDGenericTableViewCellEventHandler { eventHandler.didTapCategoryButton() } } } ================================================ FILE: Sample app/TableViewPrototype/Views/RLDSimpleTableViewCell.swift ================================================ class RLDSimpleTableViewCell:RLDGenericTableViewCell { } ================================================ FILE: Sample app/TableViewPrototype/Views/RLDTableViewHeaderView.swift ================================================ import UIKit import RLDTableViewSwift class RLDTableViewHeaderView:UITableViewHeaderFooterView, RLDHandledViewProtocol { @IBOutlet weak var text:UILabel! var eventHandler:RLDTableViewEventHandler? = nil var model:RLDTableViewHeaderViewModel? { didSet { text.text = model!.title } } } ================================================ FILE: Sample app/TableViewPrototype.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 454E32001AF008BF00AB44BC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454E31FF1AF008BF00AB44BC /* AppDelegate.swift */; }; 457D00661AF0104E00AE3D52 /* RLDTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 457D00651AF0104E00AE3D52 /* RLDTableViewController.swift */; }; 457D7CF81AF009EA00388423 /* RLDMasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 457D7CF71AF009EA00388423 /* RLDMasterViewController.swift */; }; 457D7CFF1AF009F300388423 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 457D7CFA1AF009F300388423 /* Images.xcassets */; }; 457D7D001AF009F300388423 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 457D7CFB1AF009F300388423 /* LaunchScreen.xib */; }; 457D7D011AF009F300388423 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 457D7CFC1AF009F300388423 /* Main.storyboard */; }; 457D7D021AF009F300388423 /* RLDTableViewHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 457D7CFD1AF009F300388423 /* RLDTableViewHeaderView.xib */; }; 457D7D031AF009F300388423 /* viewModelData.plist in Resources */ = {isa = PBXBuildFile; fileRef = 457D7CFE1AF009F300388423 /* viewModelData.plist */; }; 457D7D451AF00AB700388423 /* RLDTableViewSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 457D7D2E1AF00AB700388423 /* RLDTableViewSwift.framework */; }; 457D7D461AF00AB700388423 /* RLDTableViewSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 457D7D2E1AF00AB700388423 /* RLDTableViewSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 45886C1A1AF177860085F7A4 /* RLDWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45886C191AF177860085F7A4 /* RLDWebViewController.swift */; }; 4596815E1AF031F300EC4B52 /* RLDTableViewModelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4596815D1AF031F300EC4B52 /* RLDTableViewModelProvider.swift */; }; 459681601AF0321000EC4B52 /* RLDGenericTableViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4596815F1AF0321000EC4B52 /* RLDGenericTableViewCellModel.swift */; }; 459681621AF0324E00EC4B52 /* RLDSimpleTableViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459681611AF0324E00EC4B52 /* RLDSimpleTableViewCellModel.swift */; }; 459681641AF0327000EC4B52 /* RLDBigPictureTableViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459681631AF0327000EC4B52 /* RLDBigPictureTableViewCellModel.swift */; }; 459681661AF0328D00EC4B52 /* RLDCommentTableViewCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459681651AF0328D00EC4B52 /* RLDCommentTableViewCellModel.swift */; }; 459681681AF032AE00EC4B52 /* RLDTableViewHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459681671AF032AE00EC4B52 /* RLDTableViewHeaderViewModel.swift */; }; 45AC03931AF16D2900944DB7 /* RLDGenericTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AC03921AF16D2900944DB7 /* RLDGenericTableViewCell.swift */; }; 45AC03951AF170FD00944DB7 /* RLDSimpleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AC03941AF170FD00944DB7 /* RLDSimpleTableViewCell.swift */; }; 45AC03971AF1711D00944DB7 /* RLDBigPictureTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AC03961AF1711D00944DB7 /* RLDBigPictureTableViewCell.swift */; }; 45AC03991AF1713800944DB7 /* RLDCommentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AC03981AF1713800944DB7 /* RLDCommentTableViewCell.swift */; }; 45AC039B1AF1715700944DB7 /* RLDTableViewHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AC039A1AF1715700944DB7 /* RLDTableViewHeaderView.swift */; }; 45AC039D1AF172D900944DB7 /* RLDGenericTableViewCellEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AC039C1AF172D900944DB7 /* RLDGenericTableViewCellEventHandler.swift */; }; 45AC039F1AF1731E00944DB7 /* RLDTableViewHeaderViewEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AC039E1AF1731E00944DB7 /* RLDTableViewHeaderViewEventHandler.swift */; }; 45F6AEC11AF00DEB007F7AF5 /* RLDHandledViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F6AEBB1AF00DEB007F7AF5 /* RLDHandledViewProtocol.swift */; }; 45F6AEC21AF00DEB007F7AF5 /* RLDTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F6AEBC1AF00DEB007F7AF5 /* RLDTableViewDataSource.swift */; }; 45F6AEC31AF00DEB007F7AF5 /* RLDTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F6AEBD1AF00DEB007F7AF5 /* RLDTableViewDelegate.swift */; }; 45F6AEC41AF00DEB007F7AF5 /* RLDTableViewEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F6AEBE1AF00DEB007F7AF5 /* RLDTableViewEventHandler.swift */; }; 45F6AEC51AF00DEB007F7AF5 /* RLDTableViewEventHandlerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F6AEBF1AF00DEB007F7AF5 /* RLDTableViewEventHandlerProvider.swift */; }; 45F6AEC61AF00DEB007F7AF5 /* RLDTableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F6AEC01AF00DEB007F7AF5 /* RLDTableViewModel.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 457D7D431AF00AB700388423 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 454E31F21AF008BF00AB44BC /* Project object */; proxyType = 1; remoteGlobalIDString = 457D7D2D1AF00AB700388423; remoteInfo = RLDTableViewSwift; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 457D7D251AF00A2A00388423 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 457D7D461AF00AB700388423 /* RLDTableViewSwift.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 454E31FA1AF008BF00AB44BC /* TableViewPrototype.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TableViewPrototype.app; sourceTree = BUILT_PRODUCTS_DIR; }; 454E31FE1AF008BF00AB44BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 454E31FF1AF008BF00AB44BC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 457D00651AF0104E00AE3D52 /* RLDTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewController.swift; sourceTree = ""; }; 457D7CF71AF009EA00388423 /* RLDMasterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDMasterViewController.swift; sourceTree = ""; }; 457D7CFA1AF009F300388423 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 457D7CFB1AF009F300388423 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 457D7CFC1AF009F300388423 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 457D7CFD1AF009F300388423 /* RLDTableViewHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RLDTableViewHeaderView.xib; sourceTree = ""; }; 457D7CFE1AF009F300388423 /* viewModelData.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = viewModelData.plist; sourceTree = ""; }; 457D7D2E1AF00AB700388423 /* RLDTableViewSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RLDTableViewSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 457D7D311AF00AB700388423 /* RLDTableViewSwift-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "RLDTableViewSwift-Info.plist"; path = "../Sample app/TableViewPrototype/RLDTableViewSwift-Info.plist"; sourceTree = ""; }; 45886C191AF177860085F7A4 /* RLDWebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDWebViewController.swift; sourceTree = ""; }; 4596815D1AF031F300EC4B52 /* RLDTableViewModelProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewModelProvider.swift; sourceTree = ""; }; 4596815F1AF0321000EC4B52 /* RLDGenericTableViewCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDGenericTableViewCellModel.swift; sourceTree = ""; }; 459681611AF0324E00EC4B52 /* RLDSimpleTableViewCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDSimpleTableViewCellModel.swift; sourceTree = ""; }; 459681631AF0327000EC4B52 /* RLDBigPictureTableViewCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDBigPictureTableViewCellModel.swift; sourceTree = ""; }; 459681651AF0328D00EC4B52 /* RLDCommentTableViewCellModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDCommentTableViewCellModel.swift; sourceTree = ""; }; 459681671AF032AE00EC4B52 /* RLDTableViewHeaderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewHeaderViewModel.swift; sourceTree = ""; }; 45AC03921AF16D2900944DB7 /* RLDGenericTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDGenericTableViewCell.swift; sourceTree = ""; }; 45AC03941AF170FD00944DB7 /* RLDSimpleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDSimpleTableViewCell.swift; sourceTree = ""; }; 45AC03961AF1711D00944DB7 /* RLDBigPictureTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDBigPictureTableViewCell.swift; sourceTree = ""; }; 45AC03981AF1713800944DB7 /* RLDCommentTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDCommentTableViewCell.swift; sourceTree = ""; }; 45AC039A1AF1715700944DB7 /* RLDTableViewHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewHeaderView.swift; sourceTree = ""; }; 45AC039C1AF172D900944DB7 /* RLDGenericTableViewCellEventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDGenericTableViewCellEventHandler.swift; sourceTree = ""; }; 45AC039E1AF1731E00944DB7 /* RLDTableViewHeaderViewEventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewHeaderViewEventHandler.swift; sourceTree = ""; }; 45F6AEBB1AF00DEB007F7AF5 /* RLDHandledViewProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDHandledViewProtocol.swift; sourceTree = ""; }; 45F6AEBC1AF00DEB007F7AF5 /* RLDTableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewDataSource.swift; sourceTree = ""; }; 45F6AEBD1AF00DEB007F7AF5 /* RLDTableViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewDelegate.swift; sourceTree = ""; }; 45F6AEBE1AF00DEB007F7AF5 /* RLDTableViewEventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewEventHandler.swift; sourceTree = ""; }; 45F6AEBF1AF00DEB007F7AF5 /* RLDTableViewEventHandlerProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewEventHandlerProvider.swift; sourceTree = ""; }; 45F6AEC01AF00DEB007F7AF5 /* RLDTableViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewModel.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 454E31F71AF008BF00AB44BC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 457D7D451AF00AB700388423 /* RLDTableViewSwift.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 457D7D2A1AF00AB700388423 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 454E31F11AF008BF00AB44BC = { isa = PBXGroup; children = ( 457D7D2F1AF00AB700388423 /* RLDTableViewSwift */, 454E31FC1AF008BF00AB44BC /* TableViewPrototype */, 454E31FB1AF008BF00AB44BC /* Products */, ); sourceTree = ""; }; 454E31FB1AF008BF00AB44BC /* Products */ = { isa = PBXGroup; children = ( 454E31FA1AF008BF00AB44BC /* TableViewPrototype.app */, 457D7D2E1AF00AB700388423 /* RLDTableViewSwift.framework */, ); name = Products; sourceTree = ""; }; 454E31FC1AF008BF00AB44BC /* TableViewPrototype */ = { isa = PBXGroup; children = ( 457D7CF31AF009D200388423 /* Models */, 457D7CF41AF009D700388423 /* Views */, 457D7CF51AF009DD00388423 /* Event handlers */, 457D7CF61AF009EA00388423 /* View controllers */, 457D7CF91AF009F300388423 /* Resources */, 454E31FD1AF008BF00AB44BC /* Supporting Files */, ); path = TableViewPrototype; sourceTree = ""; }; 454E31FD1AF008BF00AB44BC /* Supporting Files */ = { isa = PBXGroup; children = ( 454E31FF1AF008BF00AB44BC /* AppDelegate.swift */, 454E31FE1AF008BF00AB44BC /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; 457D7CF31AF009D200388423 /* Models */ = { isa = PBXGroup; children = ( 4596815D1AF031F300EC4B52 /* RLDTableViewModelProvider.swift */, 4596815F1AF0321000EC4B52 /* RLDGenericTableViewCellModel.swift */, 459681611AF0324E00EC4B52 /* RLDSimpleTableViewCellModel.swift */, 459681631AF0327000EC4B52 /* RLDBigPictureTableViewCellModel.swift */, 459681651AF0328D00EC4B52 /* RLDCommentTableViewCellModel.swift */, 459681671AF032AE00EC4B52 /* RLDTableViewHeaderViewModel.swift */, ); path = Models; sourceTree = ""; }; 457D7CF41AF009D700388423 /* Views */ = { isa = PBXGroup; children = ( 45AC03921AF16D2900944DB7 /* RLDGenericTableViewCell.swift */, 45AC03941AF170FD00944DB7 /* RLDSimpleTableViewCell.swift */, 45AC03961AF1711D00944DB7 /* RLDBigPictureTableViewCell.swift */, 45AC03981AF1713800944DB7 /* RLDCommentTableViewCell.swift */, 45AC039A1AF1715700944DB7 /* RLDTableViewHeaderView.swift */, ); path = Views; sourceTree = ""; }; 457D7CF51AF009DD00388423 /* Event handlers */ = { isa = PBXGroup; children = ( 45AC039C1AF172D900944DB7 /* RLDGenericTableViewCellEventHandler.swift */, 45AC039E1AF1731E00944DB7 /* RLDTableViewHeaderViewEventHandler.swift */, ); name = "Event handlers"; path = "TableViewPrototype/Event handlers"; sourceTree = SOURCE_ROOT; }; 457D7CF61AF009EA00388423 /* View controllers */ = { isa = PBXGroup; children = ( 457D7CF71AF009EA00388423 /* RLDMasterViewController.swift */, 45886C191AF177860085F7A4 /* RLDWebViewController.swift */, ); path = "View controllers"; sourceTree = ""; }; 457D7CF91AF009F300388423 /* Resources */ = { isa = PBXGroup; children = ( 457D7CFA1AF009F300388423 /* Images.xcassets */, 457D7CFB1AF009F300388423 /* LaunchScreen.xib */, 457D7CFC1AF009F300388423 /* Main.storyboard */, 457D7CFD1AF009F300388423 /* RLDTableViewHeaderView.xib */, 457D7CFE1AF009F300388423 /* viewModelData.plist */, ); path = Resources; sourceTree = ""; }; 457D7D2F1AF00AB700388423 /* RLDTableViewSwift */ = { isa = PBXGroup; children = ( 45F6AEC01AF00DEB007F7AF5 /* RLDTableViewModel.swift */, 45F6AEBC1AF00DEB007F7AF5 /* RLDTableViewDataSource.swift */, 45F6AEBD1AF00DEB007F7AF5 /* RLDTableViewDelegate.swift */, 45F6AEBE1AF00DEB007F7AF5 /* RLDTableViewEventHandler.swift */, 45F6AEBF1AF00DEB007F7AF5 /* RLDTableViewEventHandlerProvider.swift */, 45F6AEBB1AF00DEB007F7AF5 /* RLDHandledViewProtocol.swift */, 457D00651AF0104E00AE3D52 /* RLDTableViewController.swift */, 457D7D301AF00AB700388423 /* Supporting Files */, ); name = RLDTableViewSwift; path = ../Classes; sourceTree = ""; }; 457D7D301AF00AB700388423 /* Supporting Files */ = { isa = PBXGroup; children = ( 457D7D311AF00AB700388423 /* RLDTableViewSwift-Info.plist */, ); name = "Supporting Files"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 457D7D2B1AF00AB700388423 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 454E31F91AF008BF00AB44BC /* TableViewPrototype */ = { isa = PBXNativeTarget; buildConfigurationList = 454E32191AF008BF00AB44BC /* Build configuration list for PBXNativeTarget "TableViewPrototype" */; buildPhases = ( 454E31F61AF008BF00AB44BC /* Sources */, 454E31F71AF008BF00AB44BC /* Frameworks */, 454E31F81AF008BF00AB44BC /* Resources */, 457D7D251AF00A2A00388423 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( 457D7D441AF00AB700388423 /* PBXTargetDependency */, ); name = TableViewPrototype; productName = TableViewPrototype; productReference = 454E31FA1AF008BF00AB44BC /* TableViewPrototype.app */; productType = "com.apple.product-type.application"; }; 457D7D2D1AF00AB700388423 /* RLDTableViewSwift */ = { isa = PBXNativeTarget; buildConfigurationList = 457D7D471AF00AB700388423 /* Build configuration list for PBXNativeTarget "RLDTableViewSwift" */; buildPhases = ( 457D7D291AF00AB700388423 /* Sources */, 457D7D2A1AF00AB700388423 /* Frameworks */, 457D7D2B1AF00AB700388423 /* Headers */, 457D7D2C1AF00AB700388423 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = RLDTableViewSwift; productName = RLDTableViewSwift; productReference = 457D7D2E1AF00AB700388423 /* RLDTableViewSwift.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 454E31F21AF008BF00AB44BC /* Project object */ = { isa = PBXProject; attributes = { LastSwiftMigration = 0730; LastSwiftUpdateCheck = 0730; LastUpgradeCheck = 0730; ORGANIZATIONNAME = "Rafael Lopez Diez"; TargetAttributes = { 454E31F91AF008BF00AB44BC = { CreatedOnToolsVersion = 6.3; }; 457D7D2D1AF00AB700388423 = { CreatedOnToolsVersion = 6.3; }; }; }; buildConfigurationList = 454E31F51AF008BF00AB44BC /* Build configuration list for PBXProject "TableViewPrototype" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 454E31F11AF008BF00AB44BC; productRefGroup = 454E31FB1AF008BF00AB44BC /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 454E31F91AF008BF00AB44BC /* TableViewPrototype */, 457D7D2D1AF00AB700388423 /* RLDTableViewSwift */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 454E31F81AF008BF00AB44BC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 457D7D021AF009F300388423 /* RLDTableViewHeaderView.xib in Resources */, 457D7D011AF009F300388423 /* Main.storyboard in Resources */, 457D7D031AF009F300388423 /* viewModelData.plist in Resources */, 457D7D001AF009F300388423 /* LaunchScreen.xib in Resources */, 457D7CFF1AF009F300388423 /* Images.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 457D7D2C1AF00AB700388423 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 454E31F61AF008BF00AB44BC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 45AC039B1AF1715700944DB7 /* RLDTableViewHeaderView.swift in Sources */, 459681641AF0327000EC4B52 /* RLDBigPictureTableViewCellModel.swift in Sources */, 45AC039D1AF172D900944DB7 /* RLDGenericTableViewCellEventHandler.swift in Sources */, 45AC03991AF1713800944DB7 /* RLDCommentTableViewCell.swift in Sources */, 459681621AF0324E00EC4B52 /* RLDSimpleTableViewCellModel.swift in Sources */, 45AC03931AF16D2900944DB7 /* RLDGenericTableViewCell.swift in Sources */, 457D00661AF0104E00AE3D52 /* RLDTableViewController.swift in Sources */, 457D7CF81AF009EA00388423 /* RLDMasterViewController.swift in Sources */, 45AC03951AF170FD00944DB7 /* RLDSimpleTableViewCell.swift in Sources */, 459681601AF0321000EC4B52 /* RLDGenericTableViewCellModel.swift in Sources */, 45886C1A1AF177860085F7A4 /* RLDWebViewController.swift in Sources */, 459681681AF032AE00EC4B52 /* RLDTableViewHeaderViewModel.swift in Sources */, 45AC03971AF1711D00944DB7 /* RLDBigPictureTableViewCell.swift in Sources */, 45AC039F1AF1731E00944DB7 /* RLDTableViewHeaderViewEventHandler.swift in Sources */, 454E32001AF008BF00AB44BC /* AppDelegate.swift in Sources */, 459681661AF0328D00EC4B52 /* RLDCommentTableViewCellModel.swift in Sources */, 4596815E1AF031F300EC4B52 /* RLDTableViewModelProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 457D7D291AF00AB700388423 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 45F6AEC11AF00DEB007F7AF5 /* RLDHandledViewProtocol.swift in Sources */, 45F6AEC21AF00DEB007F7AF5 /* RLDTableViewDataSource.swift in Sources */, 45F6AEC61AF00DEB007F7AF5 /* RLDTableViewModel.swift in Sources */, 45F6AEC51AF00DEB007F7AF5 /* RLDTableViewEventHandlerProvider.swift in Sources */, 45F6AEC41AF00DEB007F7AF5 /* RLDTableViewEventHandler.swift in Sources */, 45F6AEC31AF00DEB007F7AF5 /* RLDTableViewDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 457D7D441AF00AB700388423 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 457D7D2D1AF00AB700388423 /* RLDTableViewSwift */; targetProxy = 457D7D431AF00AB700388423 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 454E32171AF008BF00AB44BC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 454E32181AF008BF00AB44BC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 454E321A1AF008BF00AB44BC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = TableViewPrototype/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.rld.TableViewPrototype.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 454E321B1AF008BF00AB44BC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = TableViewPrototype/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.rld.TableViewPrototype.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 457D7D481AF00AB700388423 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = "$(SRCROOT)/TableViewPrototype/RLDTableViewSwift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.rld.RLDTableViewSwift; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 457D7D491AF00AB700388423 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/TableViewPrototype/RLDTableViewSwift-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.rld.RLDTableViewSwift; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 454E31F51AF008BF00AB44BC /* Build configuration list for PBXProject "TableViewPrototype" */ = { isa = XCConfigurationList; buildConfigurations = ( 454E32171AF008BF00AB44BC /* Debug */, 454E32181AF008BF00AB44BC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 454E32191AF008BF00AB44BC /* Build configuration list for PBXNativeTarget "TableViewPrototype" */ = { isa = XCConfigurationList; buildConfigurations = ( 454E321A1AF008BF00AB44BC /* Debug */, 454E321B1AF008BF00AB44BC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 457D7D471AF00AB700388423 /* Build configuration list for PBXNativeTarget "RLDTableViewSwift" */ = { isa = XCConfigurationList; buildConfigurations = ( 457D7D481AF00AB700388423 /* Debug */, 457D7D491AF00AB700388423 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 454E31F21AF008BF00AB44BC /* Project object */; } ================================================ FILE: Sample app/TableViewPrototype.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Sample app/TableViewPrototype.xcodeproj/project.xcworkspace/xcshareddata/TableViewPrototype.xccheckout ================================================ IDESourceControlProjectFavoriteDictionaryKey IDESourceControlProjectIdentifier 4E446F4C-7F72-491A-AB8F-295366F8824F IDESourceControlProjectName TableViewPrototype IDESourceControlProjectOriginsDictionary B6CC70124526FB1C79E8927A505A5CB9A7F81823 https://github.com/rlopezdiez/RLDNavigationSwift.git IDESourceControlProjectPath Sample app/TableViewPrototype.xcodeproj IDESourceControlProjectRelativeInstallPathDictionary B6CC70124526FB1C79E8927A505A5CB9A7F81823 ../../.. IDESourceControlProjectURL https://github.com/rlopezdiez/RLDNavigationSwift.git IDESourceControlProjectVersion 111 IDESourceControlProjectWCCIdentifier B6CC70124526FB1C79E8927A505A5CB9A7F81823 IDESourceControlProjectWCConfigurations IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey B6CC70124526FB1C79E8927A505A5CB9A7F81823 IDESourceControlWCCName RLDTableViewSwift ================================================ FILE: Sample app/TableViewPrototype.xcodeproj/xcuserdata/rhocassiopeiae.xcuserdatad/xcschemes/RLDTableViewSwift.xcscheme ================================================ ================================================ FILE: Sample app/TableViewPrototype.xcodeproj/xcuserdata/rhocassiopeiae.xcuserdatad/xcschemes/TableViewPrototype.xcscheme ================================================ ================================================ FILE: Sample app/TableViewPrototype.xcodeproj/xcuserdata/rhocassiopeiae.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState RLDTableViewSwift.xcscheme orderHint 1 TableViewPrototype.xcscheme orderHint 0 SuppressBuildableAutocreation 454E31F91AF008BF00AB44BC primary 457D7D2D1AF00AB700388423 primary ================================================ FILE: Tests/Tests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier com.rld.RLDTableViewSwift CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: Tests/Tests/RLDTableViewDataSourceTests.swift ================================================ import XCTest import UIKit class RLDTableViewDataSourceTests:XCTestCase { // SUT let dataModel = RLDTableViewModel() // Collaborators lazy var dataSource:RLDTableViewDataSource = { return RLDTableViewDataSource(tableViewModel:self.dataModel) }() var tableView = UITableView() // MARK: Sections test cases func testAutomaticSectionCreation() { // GIVEN: // A cell model let cellModel = RLDTableViewCellModel() // WHEN: // We add the cell model to the table view model dataModel.add(cellModel:cellModel) // THEN: // A section is automatically created in the data model // The data source should return one row for the first section XCTAssertEqual(count(dataModel.sectionModels), 1) XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:0), 1) } func testAutomaticSectionConfiguration() { // GIVEN: // A cell model let cellModel = RLDTableViewCellModel() // WHEN: // We add the cell model to the table view model // We set up the last section in the model // setting up its index title // setting up its header // setting up its footer dataModel.add(cellModel:cellModel) let sectionModel = dataModel.sectionModels.last! sectionModel.indexTitle = "~" let header = RLDTableViewSectionAccessoryViewModel() header.title = "Header" sectionModel.header = header let footer = RLDTableViewSectionAccessoryViewModel() footer.title = "Footer" sectionModel.footer = footer // THEN: // The data source should return one section // The data source should return one row for the first section // The first section header title must be equal to the title of the header // The first section footer title must be equal to the title of the footer XCTAssertEqual(dataSource.numberOfSectionsInTableView(tableView), 1) XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:0), 1) XCTAssertEqual(dataSource.tableView(tableView, titleForHeaderInSection:0)!, header.title!) XCTAssertEqual(dataSource.tableView(tableView, titleForFooterInSection:0)!, footer.title!) } func testManualSectionAdding() { // GIVEN: // A cell model // A section created manually // with its index title set up // with its header set up // with its footer set up let cellModel = RLDTableViewCellModel() let sectionModel = RLDTableViewSectionModel() sectionModel.indexTitle = "~" let header = RLDTableViewSectionAccessoryViewModel() header.title = "Header" sectionModel.header = header let footer = RLDTableViewSectionAccessoryViewModel() footer.title = "Footer" sectionModel.footer = footer // WHEN: // We add the cell model to the table view model // specifying it should be added to the manually created section dataModel.add(cellModel:cellModel, toSectionModel:sectionModel) // THEN: // The data source should return one section // The data source should return one row for the first section // The first section header title must be equal to the title of the header // The first section footer title must be equal to the title of the footer // The fist section in the model must be equal to the manually created section XCTAssertEqual(dataSource.numberOfSectionsInTableView(tableView), 1) XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:0), 1) XCTAssertEqual(dataSource.tableView(tableView, titleForHeaderInSection:0)!, header.title!) XCTAssertEqual(dataSource.tableView(tableView, titleForFooterInSection:0)!, footer.title!) XCTAssertEqual(dataModel.sectionModels[0], sectionModel) } func testManualSectionAddingAfterAutomaticSectionAdded() { // GIVEN: // A cell model // A section created manually let cellModel = RLDTableViewCellModel() let sectionModel = RLDTableViewSectionModel() // WHEN: // We add the cell model to the table view model twice // We add the cell model to the table view model three times // specifying it should be added to the manually created section repeat(2, closure:{ self.dataModel.add(cellModel:cellModel) }) repeat(3, closure:{ self.dataModel.add(cellModel:cellModel, toSectionModel:sectionModel) }) // THEN: // The data source should return two sections // The data source should return two rows for the first section // The data source should return three rows for the second section // The second section in the model must be equal to the manually created section XCTAssertEqual(dataSource.numberOfSectionsInTableView(tableView), 2) XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:0), 2) XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:1), 3) XCTAssertEqual(dataModel.sectionModels[1], sectionModel) } func testMultipleManualSectionAdding() { // GIVEN: // A cell model // Two sections created manually let cellModel = RLDTableViewCellModel() let firstSectionModel = RLDTableViewSectionModel() let secondSectionModel = RLDTableViewSectionModel() // WHEN: // We add the cell model to the table view model three times // specifying it should be added to the first manually created section // We add the cell model to the table view model three times // specifying it should be added to the second manually created section repeat(2, closure:{ self.dataModel.add(cellModel:cellModel, toSectionModel:firstSectionModel) }) repeat(3, closure:{ self.dataModel.add(cellModel:cellModel, toSectionModel:secondSectionModel) }) // THEN: // The data source should return two sections // The data source should return two rows for the first section // The data source should return three rows for the second section // The first section in the model must be equal to the first manually created section // The second section in the model must be equal to the second manually created section XCTAssertEqual(dataSource.numberOfSectionsInTableView(tableView), 2) XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:0), 2) XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:1), 3) XCTAssertEqual(dataModel.sectionModels[0], firstSectionModel) XCTAssertEqual(dataModel.sectionModels[1], secondSectionModel) } // MARK: Section index titles test cases func testAutomaticSectionIndexTitles() { // GIVEN: // A cell model // Two sections created manually // with its indexes titles set up let cellModel = RLDTableViewCellModel() let firstSectionModel = RLDTableViewSectionModel() firstSectionModel.indexTitle = "1" let secondSectionModel = RLDTableViewSectionModel() secondSectionModel.indexTitle = "2" // WHEN: // We add the cell model to the table view model // specifying it should be added to the first manually created section // We add the cell model to the table view // specifying it should be added to the second manually created section dataModel.add(cellModel:cellModel, toSectionModel:firstSectionModel) dataModel.add(cellModel:cellModel, toSectionModel:secondSectionModel) // THEN: // The data source should return and array with two section index titles // The first one must be equal to the section index title of the first manually created section // The second one must be equal to the section index title of the second manually created section let sectionIndexTitles = dataSource.sectionIndexTitlesForTableView(tableView) as! [String] XCTAssertEqual(count(sectionIndexTitles), 2) XCTAssertEqual(sectionIndexTitles[0], firstSectionModel.indexTitle!) XCTAssertEqual(sectionIndexTitles[1], secondSectionModel.indexTitle!) } func testManualSectionIndexTitles() { // GIVEN: // A data model // with its section index titles set to numbers from 1 to 4 // A cell model // Two sections created manually // with its indexes titles set up to 1 and 2 dataModel.sectionIndexTitles = ["1", "2", "3", "4"] let cellModel = RLDTableViewCellModel() let firstSectionModel = RLDTableViewSectionModel() firstSectionModel.indexTitle = "1" let secondSectionModel = RLDTableViewSectionModel() secondSectionModel.indexTitle = "2" // WHEN: // We add the cell model to the table view model // specifying it should be added to the first manually created section // We add the cell model to the table view // specifying it should be added to the second manually created section dataModel.add(cellModel:cellModel, toSectionModel:firstSectionModel) dataModel.add(cellModel:cellModel, toSectionModel:secondSectionModel) // THEN: // The data source should return and array equal to the previously set up index title array of the data model let sectionIndexTitles = dataSource.sectionIndexTitlesForTableView(tableView) as! [String] XCTAssertEqual(sectionIndexTitles, dataModel.sectionIndexTitles!) } func testSectionIndexTitlesToSectionsRelationship() { // GIVEN: // An data model // with its section index titles set to numbers from 1 to 4 // A cell model // Two sections created manually // with its indexes titles set up to 1 and 4 dataModel.sectionIndexTitles = ["1", "2", "3", "4"] let cellModel = RLDTableViewCellModel() let firstSectionModel = RLDTableViewSectionModel() firstSectionModel.indexTitle = "1" let secondSectionModel = RLDTableViewSectionModel() secondSectionModel.indexTitle = "4" // WHEN: // We add the cell model to the table view model // specifying it should be added to the first manually created section // We add the cell model to the table view // specifying it should be added to the second manually created section dataModel.add(cellModel:cellModel, toSectionModel:firstSectionModel) dataModel.add(cellModel:cellModel, toSectionModel:secondSectionModel) // THEN: // The data source should return the second section when asked to provide an index for the fourth element of the section index titles array XCTAssertEqual(dataSource.tableView(tableView,sectionForSectionIndexTitle:"4", atIndex:3), 1) } // MARK: Data source edition test cases func testCellModelEditionPreferences() { // GIVEN: // A table view data source // A data model containing two cell models // the first one being non editable // the first one being editable let firstCellModel = RLDTableViewCellModel() firstCellModel.editable = true let secondCellModel = RLDTableViewCellModel() secondCellModel.editable = false dataModel.add(cellModel:firstCellModel) dataModel.add(cellModel:secondCellModel) // THEN: // The data source should identify the first cell in the first section as editable // The data source should identify the second cell in the first section as non editable XCTAssertEqual(dataSource.tableView(tableView, canEditRowAtIndexPath:NSIndexPath(forRow:0, inSection:0)), true) XCTAssertEqual(dataSource.tableView(tableView, canEditRowAtIndexPath:NSIndexPath(forRow:1, inSection:0)), false) } func testCellModelReorderingPreferences() { // GIVEN: // A table view data source // A data model containing two cell models // the first one being non movable // the first one being movable let firstCellModel = RLDTableViewCellModel() firstCellModel.movable = true let secondCellModel = RLDTableViewCellModel() secondCellModel.movable = false dataModel.add(cellModel:firstCellModel) dataModel.add(cellModel:secondCellModel) // THEN: // The data source should identify the first cell in the first section as movable // The data source should identify the second cell in the first section as non movable XCTAssertEqual(dataSource.tableView(tableView, canMoveRowAtIndexPath:NSIndexPath(forRow:0, inSection:0)), true) XCTAssertEqual(dataSource.tableView(tableView, canMoveRowAtIndexPath:NSIndexPath(forRow:1, inSection:0)), false) } func testCellModelInsertion() { // GIVEN: // A table view data source // A data model containing two cell models let sectionModel = RLDTableViewSectionModel() let expectedClassForInsertedCellModel = "Tests.RLDFirstTestCellModel" sectionModel.defaultCellModelClassForInsertions = expectedClassForInsertedCellModel var testCellModels:[RLDTableViewCellModel] = [] repeat(2, closure:{ let testCellModel = RLDSecondTestCellModel() testCellModels.append(testCellModel) self.dataModel.add(cellModel:testCellModel, toSectionModel:sectionModel) }) // WHEN: // We ask the data source to insert a new cell at the second row in the first section dataSource.tableView(tableView, commitEditingStyle:UITableViewCellEditingStyle.Insert, forRowAtIndexPath:NSIndexPath(forRow:1, inSection:0)) // THEN: // The data source must return three rows for the first section // The section model must contain three cell models // The second cell model must have the expected class // The first cell model must be the first one added // The third cell model must be the second one added XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:0), 3) XCTAssertEqual(count(sectionModel.cellModels), 3) XCTAssertEqual(NSStringFromClass(sectionModel.cellModels[1].dynamicType), expectedClassForInsertedCellModel) XCTAssertTrue(sectionModel.cellModels[0] === testCellModels[0]) XCTAssertTrue(sectionModel.cellModels[2] === testCellModels[1]) } func testCellModelDeletion() { // GIVEN: // A table view data source // with a cell provider // A data model // with a section with a default cell model class for insertions // containing three cell models var testCellModels:[RLDTableViewCellModel] = [] repeat(3, closure:{ let cellModel = RLDTableViewCellModel() testCellModels.append(cellModel) self.dataModel.add(cellModel:cellModel) }) let sectionModel = dataModel.sectionModels.last! // WHEN: // We ask the data source to delete the second row in the first section dataSource.tableView(tableView, commitEditingStyle:UITableViewCellEditingStyle.Delete, forRowAtIndexPath:NSIndexPath(forRow:1, inSection:0)) // THEN: // The data source should return two rows for the first section // The section model must contain two cell models // The first cell model must be the first one added // The second cell model must be the third one added XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:0), 2) XCTAssertEqual(count(sectionModel.cellModels), 2) XCTAssertTrue(sectionModel.cellModels[0] === testCellModels[0]) XCTAssertTrue(sectionModel.cellModels[1] === testCellModels[2]) } func testCellModelReordering() { // GIVEN: // A table view data source // with a cell provider // A data model // with a first section with a cell model // with a second section with a cell model var testCellModels:[RLDTableViewCellModel] = [] repeat(2, closure:{ let cellModel = RLDTableViewCellModel() testCellModels.append(cellModel) self.dataModel.add(cellModel:cellModel, toSectionModel:RLDTableViewSectionModel()) }) let firstSectionModel = dataModel.sectionModels[0] let secondSectionModel = dataModel.sectionModels[1] // WHEN: // We ask the data source to move the first row of the first section to the second row of the second section dataSource.tableView(tableView, moveRowAtIndexPath:NSIndexPath(forRow:0, inSection:0), toIndexPath:NSIndexPath(forRow:1, inSection:1)) // THEN: // The data source should return zero rows for the first section // The data source should return two rows for the second section // The data model must contain zero cell models in its first section // The data model must contain two cell models in its second section // The first cell model in the second section must be the second one added // The second cell model in the second section must be the first one added XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:0), 0) XCTAssertEqual(dataSource.tableView(tableView, numberOfRowsInSection:1), 2) XCTAssertEqual(count(firstSectionModel.cellModels), 0) XCTAssertEqual(count(secondSectionModel.cellModels), 2) XCTAssertTrue(secondSectionModel.cellModels[0] === testCellModels[1]) XCTAssertTrue(secondSectionModel.cellModels[1] === testCellModels[0]) } // MARK: Test cell generation test cases func testCellGeneration() { // GIVEN: // A table view // A table view data source // assigned to the table view // A data model // A cell model // added to the data model // A UITableViewCell subclass // registered with the table view for the cell model reuse identifier tableView.dataSource = dataSource let cellModel = RLDTableViewCellModel() dataModel.add(cellModel:cellModel) let expectedCellClass = "Tests.RLDTestTableViewCell" tableView.registerClass(NSClassFromString(expectedCellClass), forCellReuseIdentifier:cellModel.reuseIdentifier) // WHEN: // We ask the data source for a cell in the first row of the first section of the table let returnedCell = dataSource.tableView(tableView, cellForRowAtIndexPath:NSIndexPath(forRow:0, inSection:0)) // THEN: // The returned cell class must match the registered UITableViewCell subclass XCTAssertEqual(NSStringFromClass(returnedCell.dynamicType), expectedCellClass) } // MARK: Helper functions private func repeat(times:UInt, closure:(Void->Void)) { for var counter:UInt = 0; counter < times; counter++ { closure() } } } ================================================ FILE: Tests/Tests/RLDTableViewDelegateTests.swift ================================================ import UIKit import XCTest class RLDTableViewDelegateTests: XCTestCase { // SUT var tableViewDelegate:RLDTableViewDelegate? // Collaborators var tableDataSource:RLDTableViewDataSource? let tableView = UITableView(frame:CGRect(x:0, y:0, width: 1, height:1)) let cell = UITableViewCell() let view = UITableViewHeaderFooterView() let indexPath = NSIndexPath(forRow:0, inSection:0) override func setUp() { super.setUp() // GIVEN: // A table view delegate // An event handler // capable to handle any model, table view and view // A table view, a header/footer view, a cell and an index path let cellModel = RLDTableViewCellModel() let tableViewModel = RLDTableViewModel() tableViewModel.add(cellModel:cellModel) tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier:cellModel.reuseIdentifier) RLDTestEventHandler.register() tableViewDelegate = RLDTableViewDelegate(tableViewModel:tableViewModel) tableView.delegate = tableViewDelegate tableDataSource = RLDTableViewDataSource(tableViewModel:tableViewModel) tableView.dataSource = tableDataSource } override func tearDown() { RLDTestEventHandler.reset() super.tearDown() } // MARK : Display customization test cases func testWillDisplayCell() { // WHEN: // We inform the delegate that the table view will display the cell for a row at the index path tableViewDelegate!.tableView(tableView, willDisplayCell:cell, forRowAtIndexPath:indexPath) // THEN: // The willDisplayView method of the event handler will be called RLDTestEventHandler.isCallRegistered("willDisplayView") } func testWillDisplayHeaderView() { // WHEN: // We inform the delegate that the table view will display the header view of a section tableViewDelegate!.tableView(tableView, willDisplayHeaderView:view, forSection:indexPath.section) // THEN: // The willDisplayView method of the event handler will be called RLDTestEventHandler.isCallRegistered("willDisplayView") } func testWillDisplayFooterView() { // WHEN: // We inform the delegate that the table view will display the footer view of a section tableViewDelegate!.tableView(tableView, willDisplayFooterView:view, forSection:indexPath.section) // THEN: // The willDisplayView method of the event handler will be called RLDTestEventHandler.isCallRegistered("willDisplayView") } func testDidEndDisplayingCell() { // WHEN: // We inform the delegate that the table view did end displaying the cell for a row at the index path tableViewDelegate!.tableView(tableView, didEndDisplayingCell:cell, forRowAtIndexPath:indexPath) // THEN: // The didEndDisplayingView method of the event handler will be called RLDTestEventHandler.isCallRegistered("didEndDisplayingView") } func testDidEndDisplayingHeaderView() { // WHEN: // We inform the delegate that the table view did end displaying the header view of a section tableViewDelegate!.tableView(tableView, didEndDisplayingHeaderView:view, forSection:indexPath.section) // THEN: // The didEndDisplayingView method of the event handler will be called RLDTestEventHandler.isCallRegistered("didEndDisplayingView") } func testDidEndDisplayingFooterView() { // WHEN: // We inform the delegate that the table view did end displaying the footer view of a section tableViewDelegate!.tableView(tableView, didEndDisplayingFooterView:view, forSection:indexPath.section) // THEN: // The didEndDisplayingView method of the event handler will be called RLDTestEventHandler.isCallRegistered("didEndDisplayingView") } // MARK : Section header and footer reusing test cases func testHeaderReusing() { // GIVEN: // A table view data model // with an automatically created section model // with a cell model assigned to it // set as the model of the table view delegate // A header data model // with a certain reuse identifier // registered with the table view with a certain view class // assigned as the header of the section model let dataModel = RLDTableViewModel() dataModel.add(cellModel:RLDTableViewCellModel()) tableViewDelegate = RLDTableViewDelegate(tableViewModel:dataModel) tableView.delegate = tableViewDelegate let headerModel = RLDTableViewSectionAccessoryViewModel() headerModel.reuseIdentifier = "HeaderReuseIdentifier" tableView.registerClass(RLDTestAccessoryView.self, forHeaderFooterViewReuseIdentifier:headerModel.reuseIdentifier) dataModel.sectionModels.last!.header = headerModel // WHEN: // We ask the delegate for a view for the header of the section let sectionHeader = tableViewDelegate!.tableView(tableView, viewForHeaderInSection:indexPath.section) as? RLDTestAccessoryView // THEN: // The returned view class must be the same as the registered class // Its reuse identifier must match the one for the header model XCTAssertNotNil(sectionHeader) XCTAssertTrue(sectionHeader!.dynamicType === RLDTestAccessoryView.self) XCTAssertEqual(sectionHeader!.reuseIdentifier!, headerModel.reuseIdentifier) } func testFooterReusing() { // GIVEN: // A table view data model // with an automatically created section model // with a cell model assigned to it // set as the model of the table view delegate // A footer data model // with a certain reuse identifier // registered with the table view with a certain view class // assigned as the footer of the section model let dataModel = RLDTableViewModel() dataModel.add(cellModel:RLDTableViewCellModel()) tableViewDelegate = RLDTableViewDelegate(tableViewModel:dataModel) tableView.delegate = tableViewDelegate let footerModel = RLDTableViewSectionAccessoryViewModel() footerModel.reuseIdentifier = "FooterReuseIdentifier" tableView.registerClass(RLDTestAccessoryView.self, forHeaderFooterViewReuseIdentifier:footerModel.reuseIdentifier) dataModel.sectionModels.last!.footer = footerModel // WHEN: // We ask the delegate for a view for the footer of the section let sectionFooter = tableViewDelegate!.tableView(tableView, viewForFooterInSection:indexPath.section) as? RLDTestAccessoryView // THEN: // The returned view class must be the same as the registered class // Its reuse identifier must match the one for the footer model XCTAssertNotNil(sectionFooter) XCTAssertTrue(sectionFooter!.dynamicType === RLDTestAccessoryView.self) XCTAssertEqual(sectionFooter!.reuseIdentifier!, footerModel.reuseIdentifier) } // MARK : Variable height support test cases func testVariableHeightForRow() { // GIVEN: // A table view data model // set as the model of the table view delegate // A cell model // with a certain estimated height // with a certain height // assigned to the table model let cellModel = cellModelInTableViewDataModel() cellModel.estimatedHeight = 1 cellModel.height = 2 // WHEN: // We ask the delegate for // the estimated height of a given cell for a row at the index path // the height of a given cell for a row at the index path let estimatedHeight = tableViewDelegate!.tableView(tableView, estimatedHeightForRowAtIndexPath:indexPath) let height = tableViewDelegate!.tableView(tableView, heightForRowAtIndexPath:indexPath) // THEN: // The returned estimated height must be equal to the height of the cell model // The returned height must be equal to the height of the cell model XCTAssertEqual(estimatedHeight, cellModel.estimatedHeight!) XCTAssertEqual(height, cellModel.height!) } func testVariableHeightForHeader() { // GIVEN: // A table view data model // with a cell model assigned to it // set as the model of the table view delegate // An automatically created section model // A header data model // with a certain estimated height // with a certain height // assigned to the section model let dataModel = RLDTableViewModel() dataModel.add(cellModel:RLDTableViewCellModel()) tableViewDelegate = RLDTableViewDelegate(tableViewModel:dataModel) tableView.delegate = tableViewDelegate let sectionModel = dataModel.sectionModels.last! let headerModel = RLDTableViewSectionAccessoryViewModel() headerModel.estimatedHeight = 1 headerModel.height = 2 sectionModel.header = headerModel // WHEN: // We ask the delegate for // the estimated height of a given cell for a row at the index path // the height of a given cell for a row at the index path let estimatedHeight = tableViewDelegate!.tableView(tableView, estimatedHeightForHeaderInSection:indexPath.section) let height = tableViewDelegate!.tableView(tableView, heightForHeaderInSection:indexPath.section) // THEN: // The returned estimated height must be equal to the height of the cell model // The returned height must be equal to the height of the cell model XCTAssertEqual(estimatedHeight, headerModel.estimatedHeight!) XCTAssertEqual(height, headerModel.height!) } func testVariableHeightForFooter() { // GIVEN: // A table view data model // with a cell model assigned to it // set as the model of the table view delegate // An automatically created section model // A footer data model // with a certain estimated height // with a certain height // assigned to the section model let dataModel = RLDTableViewModel() dataModel.add(cellModel:RLDTableViewCellModel()) tableViewDelegate = RLDTableViewDelegate(tableViewModel:dataModel) tableView.delegate = tableViewDelegate let sectionModel = dataModel.sectionModels.last! let footerModel = RLDTableViewSectionAccessoryViewModel() footerModel.estimatedHeight = 1 footerModel.height = 2 sectionModel.footer = footerModel // WHEN: // We ask the delegate for // the estimated height of a given cell for a row at the index path // the height of a given cell for a row at the index path let estimatedHeight = tableViewDelegate!.tableView(tableView, estimatedHeightForFooterInSection:indexPath.section) let height = tableViewDelegate!.tableView(tableView, heightForFooterInSection:indexPath.section) // THEN: // The returned estimated height must be equal to the height of the cell model // The returned height must be equal to the height of the cell model XCTAssertEqual(estimatedHeight, footerModel.estimatedHeight!) XCTAssertEqual(height, footerModel.height!) } // MARK : Accessories (disclosures) func testAccessoryButtonTapped() { // WHEN: // We inform the delegate that the accessory button for a row with a certain index path has been tapped tableViewDelegate!.tableView(tableView, accessoryButtonTappedForRowWithIndexPath:indexPath) // THEN: // The accessoryButtonTapped method of the event handler will be called RLDTestEventHandler.isCallRegistered("accessoryButtonTapped") } // MARK : Selection func testShouldHighlightRow() { // WHEN: // We ask the delegate wether a row at a certain index path should be highlighted tableViewDelegate!.tableView(tableView, shouldHighlightRowAtIndexPath:indexPath) // THEN: // The shouldHighlightView method of the event handler will be called RLDTestEventHandler.isCallRegistered("shouldHighlightView") } func testDidHighlightRow() { // WHEN: // We inform the delegate that the table view did highlight a row at the index path tableViewDelegate!.tableView(tableView, didHighlightRowAtIndexPath:indexPath) // THEN: // The didHighlightView method of the event handler will be called RLDTestEventHandler.isCallRegistered("didHighlightView") } func testDidUnhighlightRow() { // WHEN: // We inform the delegate that the table view did unhighlight a row at the index path tableViewDelegate!.tableView(tableView, didUnhighlightRowAtIndexPath:indexPath) // THEN: // The didUnhighlightView method of the event handler will be called RLDTestEventHandler.isCallRegistered("didUnhighlightView") } func testWillSelectRowAtIndexPath() { // WHEN: // We inform the delegate that the table view will select a row at the index path tableViewDelegate!.tableView(tableView, willSelectRowAtIndexPath:indexPath) // THEN: // The willSelectView method of the event handler will be called RLDTestEventHandler.isCallRegistered("willSelectView") } func testWillDeselectRowAtIndexPath() { // WHEN: // We inform the delegate that the table view will deselect a row at the index path tableViewDelegate!.tableView(tableView, willDeselectRowAtIndexPath:indexPath) // THEN: // The willDeselectView method of the event handler will be called RLDTestEventHandler.isCallRegistered("willDeselectView") } func testDidSelectRowAtIndexPath() { // WHEN: // We inform the delegate that the table view selected a row at the index path tableViewDelegate!.tableView(tableView, didSelectRowAtIndexPath:indexPath) // THEN: // The didSelectView method of the event handler will be called RLDTestEventHandler.isCallRegistered("didSelectView") } func testDidDeselectRowAtIndexPath() { // WHEN: // We inform the delegate that the table view deselected a row at the index path tableViewDelegate!.tableView(tableView, didDeselectRowAtIndexPath:indexPath) // THEN: // The didDeselectView method of the event handler will be called RLDTestEventHandler.isCallRegistered("didDeselectView") } // MARK : Editing func testEditingStyleForRow() { // GIVEN: // A table view data model // set as the model of the table view delegate // A cell data model // with a certain editing style // added to the table model let cellModel = cellModelInTableViewDataModel() cellModel.editingStyle = UITableViewCellEditingStyle.Delete // WHEN: // We ask the delegate for the editing style of a row at a certain index path let editingStyle = tableViewDelegate!.tableView(tableView, editingStyleForRowAtIndexPath:indexPath) // THEN: // The returned value must be equal to the editing style in the cell model XCTAssertEqual(editingStyle, cellModel.editingStyle!) } func testEditActions() { // WHEN: // We ask the delegate for the edit actions of a row at a certain index path let editActions = tableViewDelegate!.tableView(tableView, editActionsForRowAtIndexPath:indexPath) // THEN: // The returned edit actions array must be equal to the one returned by the event handler RLDTestEventHandler.isCallRegistered("editActions") } func testTitleForDeleteConfirmationButton() { // GIVEN: // A table view data model // set as the model of the table view delegate // A cell data model // with a certain editing style // added to the table model let cellModel = cellModelInTableViewDataModel() cellModel.titleForDeleteConfirmationButton = "TestTitleForDeleteConfirmationButton" // WHEN: // We ask the delegate for the editing style of a row at a certain index path let titleForDeleteConfirmationButton = tableViewDelegate!.tableView(tableView, titleForDeleteConfirmationButtonForRowAtIndexPath:indexPath) // THEN: // The returned title must be equal to the one in the cell model XCTAssertEqual(titleForDeleteConfirmationButton, cellModel.titleForDeleteConfirmationButton!) } func testShouldIndentWhileEditing() { // GIVEN: // A table view data model // set as the model of the table view delegate // A cell data model // that states that the cell should indent while editing // added to the table model let cellModel = cellModelInTableViewDataModel() cellModel.shouldIndentWhileEditing = true // WHEN: // We ask the delegate wether a row at a certain index path should indent while editing let shouldIndentWhileEditing = tableViewDelegate!.tableView(tableView, shouldIndentWhileEditingRowAtIndexPath:indexPath) // THEN: // The returned value must be equal to the one set in the cell model XCTAssertEqual(shouldIndentWhileEditing, cellModel.shouldIndentWhileEditing!) } func testWillBeginEditing() { // WHEN: // We inform the delegate that the table view will beging editing a row at the index path tableViewDelegate!.tableView(tableView, willBeginEditingRowAtIndexPath:indexPath) // THEN: // The willBeginEditing method of the event handler will be called RLDTestEventHandler.isCallRegistered("willBeginEditing") } func testDidEndEditing() { // WHEN: // We inform the delegate that the table view ended editing a row at the index path tableViewDelegate!.tableView(tableView, didEndEditingRowAtIndexPath:indexPath) // THEN: // The didEndEditing method of the event handler will be called RLDTestEventHandler.isCallRegistered("didEndEditing") } // MARK : Indentation func testIndentationLevel() { // GIVEN: // A table view data model // set as the model of the table view delegate // A cell data model // with a certain indentation level // added to the table model let dataModel = RLDTableViewModel() tableViewDelegate = RLDTableViewDelegate(tableViewModel:dataModel) tableView.delegate = tableViewDelegate let cellModel = RLDTableViewCellModel() cellModel.indentationLevel = 2 dataModel.add(cellModel:cellModel) // WHEN: // We ask the delegate for the indentation level of a row at a certain index path let indentationLevel = tableViewDelegate!.tableView(tableView, indentationLevelForRowAtIndexPath:indexPath) // THEN: // The returned value must be equal to the one set in the cell model XCTAssertEqual(indentationLevel, cellModel.indentationLevel!) } // MARK : Copy and Paste func testShouldShowMenu() { // GIVEN: // A table view data model // set as the model of the table view delegate // A cell data model // that states that the cell should show the copy and paste menu // added to the table model let cellModel = cellModelInTableViewDataModel() cellModel.shouldShowMenu = true // WHEN: // We ask the delegate wether a row at a certain index path should show the copy and paste menu let shouldShowMenu = tableViewDelegate!.tableView(tableView, shouldShowMenuForRowAtIndexPath:indexPath) // THEN: // The returned value must be equal to the one set in the cell model XCTAssertEqual(shouldShowMenu, cellModel.shouldShowMenu!) } func testCanPerformAction() { // WHEN: // We ask the delegate wether a row at a certain index path can perform an action tableViewDelegate!.tableView(tableView, canPerformAction:nil, forRowAtIndexPath:indexPath, withSender:self) // THEN: // The canPerformAction:withSender: method of the event handler will be called RLDTestEventHandler.isCallRegistered("canPerformAction:withSender:") } func testPerformAction() { // WHEN: // We ask the delegate to perform an action for a row at a certain index path tableViewDelegate!.tableView(tableView, performAction:nil, forRowAtIndexPath:indexPath, withSender:self) // THEN: // The performAction:withSender: method of the event handler will be called RLDTestEventHandler.isCallRegistered("performAction:withSender:") } // MARK : Helper methods func cellModelInTableViewDataModel() -> RLDTableViewCellModel { let dataModel = RLDTableViewModel() tableViewDelegate! = RLDTableViewDelegate(tableViewModel:dataModel) let cellModel = RLDTableViewCellModel() dataModel.add(cellModel:cellModel) return cellModel } } ================================================ FILE: Tests/Tests/RLDTableViewEventHandlerProviderTests.swift ================================================ import XCTest import UIKit class RLDTableViewEventHandlerProviderTests: XCTestCase { func testFactoryMethod() { // GIVEN: // A table view // A view model // A view // An event hanlder // which is able to handle these three assets // registered with the event handler provider let testTableView = UITableView() let testViewModel = RLDTableViewReusableViewModel() let testView = UIView() RLDTableViewEventHandlerProvider.register(RLDTestEventHandler) RLDTestEventHandler.setCanHandleClosure { (tableView, viewModel, view) -> Bool in return tableView === testTableView && viewModel === testViewModel && view === testView } // WHEN: // We ask the event handler provider for an event handler with the three defined assets // We ask the event handler provider for an event handler with different assets let firstEventHandler = RLDTableViewEventHandlerProvider.eventHandler(tableView:testTableView, viewModel:testViewModel, view:testView) let secondEventHandler = RLDTableViewEventHandlerProvider.eventHandler(tableView:testTableView, viewModel:RLDTableViewReusableViewModel(), view:testView) // THEN: // It is able to provide an event handler for the first combination // It is not able to provide an event handler for the second combination XCTAssertNotNil(firstEventHandler) XCTAssertNil(secondEventHandler) } } ================================================ FILE: Tests/Tests/RLDTableViewModelTests.swift ================================================ import UIKit import XCTest class RLDTableViewModelTests:XCTestCase { // MARK: Section index titles test cases func testSectionIndexTitlesAreTakenFromSectionsWhenNotSet() { // GIVEN: // A table view data model // with a section with a certain index title let indexTitle = "~" let tableViewModel = tableViewModelWithSectionWithIndexTitle(indexTitle) // WHEN: // The data model is asked for its section index titles let sectionIndexTitles = tableViewModel.sectionIndexTitles! // THEN: // The section index title of its section is returned XCTAssertEqual(count(sectionIndexTitles), 1) XCTAssert(contains(sectionIndexTitles, indexTitle)) } func testSectionIndexTitlesReturnedWhenSet() { // GIVEN: // A table view data model // with a section with a certain index title let indexTitle = "~" let tableViewModel = tableViewModelWithSectionWithIndexTitle(indexTitle) // WHEN: // The sectionIndexTitles property of the data model is set let sectionIndexTitles = ["A"] tableViewModel.sectionIndexTitles = sectionIndexTitles // THEN: // The data model return the set object for its sectionIndexTitles property XCTAssertEqual(tableViewModel.sectionIndexTitles!, sectionIndexTitles) } // MARK: Helper methods func tableViewModelWithSectionWithIndexTitle(indexTitle:String) -> RLDTableViewModel { let cellModel = RLDTableViewCellModel() let sectionModel = RLDTableViewSectionModel() let tableViewModel = RLDTableViewModel() sectionModel.indexTitle = indexTitle tableViewModel.add(cellModel:cellModel, toSectionModel:sectionModel) return tableViewModel } } ================================================ FILE: Tests/Tests/TestHelpers.swift ================================================ import UIKit // MARK: UITableViewCell subclass class RLDTestTableViewCell:UITableViewCell { } // MARK: UITableViewHeaderFooterView subclass class RLDTestAccessoryView : UITableViewHeaderFooterView { } // MARK: RLDTableViewCellModel subclasses class RLDFirstTestCellModel:RLDTableViewCellModel { } class RLDSecondTestCellModel:RLDTableViewCellModel { } // MARK: Event handler for testing class RLDTestEventHandler:RLDTableViewCellEventHandler { typealias canHandleClosureType = (tableView:UITableView, viewModel:RLDTableViewReusableViewModel, view:UIView) -> Bool private static var canHandleClosure:canHandleClosureType = { canHandleClosureType in return true } private static var callRegister:[String:String] = [:] class func setCanHandleClosure(newCanHandleClosure:canHandleClosureType) { canHandleClosure = newCanHandleClosure } class func reset() { canHandleClosure = { canHandleClosureType in return true } } // MARK: Suitability checking override class func canHandle(#tableView:UITableView, viewModel:RLDTableViewReusableViewModel, view:UIView) -> Bool { return canHandleClosure(tableView:tableView, viewModel:viewModel, view:view) } // MARK: Call registering override func willReuseView() { registerCall("willReuseView") } override func willDisplayView() { registerCall("willDisplayView") } override func didEndDisplayingView() { registerCall("didEndDisplayingView") } override func accessoryButtonTapped() { registerCall("accessoryButtonTapped") } override func shouldHighlightView() -> Bool { registerCall("shouldHighlightView"); return false } override func didHighlightView() { registerCall("didHighlightView") } override func didUnhighlightView() { registerCall("didUnhighlightView") } override func willSelectView() -> NSIndexPath? { registerCall("willSelectView"); return nil } override func willDeselectView() -> NSIndexPath? { registerCall("willDeselectView"); return nil } override func didSelectView() { registerCall("didSelectView") } override func didDeselectView() { registerCall("didDeselectView") } override func willBeginEditing() { registerCall("willBeginEditing") } override func didEndEditing() { registerCall("didEndEditing") } override func editActions() -> [UITableViewRowAction]? { registerCall("editActions"); return nil } override func canPerform(#action:Selector, withSender sender:AnyObject) -> Bool { registerCall("canPerformAction:withSender:"); return false } override func performAction(#action:Selector, withSender sender:AnyObject) { registerCall("performAction:withSender:") } func registerCall(selector:String) { self.dynamicType.callRegister[NSStringFromClass(self.dynamicType)] = selector } class func isCallRegistered(selector:String) -> Bool { if let isCallRegistered = callRegister[selector] { return true } else { return false } } } ================================================ FILE: Tests/Tests.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 45054CEC1AEC6A75006DD0B9 /* RLDTableViewEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45054CEB1AEC6A75006DD0B9 /* RLDTableViewEventHandler.swift */; }; 4537E02F1AF003160051625F /* RLDTableViewEventHandlerProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4537E02E1AF003160051625F /* RLDTableViewEventHandlerProviderTests.swift */; }; 455CE3B81AEC65FD002EA10E /* RLDTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 455CE3B51AEC65FD002EA10E /* RLDTableViewDataSource.swift */; }; 455CE3B91AEC65FD002EA10E /* RLDTableViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 455CE3B61AEC65FD002EA10E /* RLDTableViewModel.swift */; }; 455CE3BE1AEC6606002EA10E /* RLDTableViewDataSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 455CE3BB1AEC6606002EA10E /* RLDTableViewDataSourceTests.swift */; }; 455CE3BF1AEC6606002EA10E /* RLDTableViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 455CE3BC1AEC6606002EA10E /* RLDTableViewModelTests.swift */; }; 455CE3C01AEC6606002EA10E /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 455CE3BD1AEC6606002EA10E /* TestHelpers.swift */; }; 45B3A15A1AED819200EECD76 /* RLDTableViewEventHandlerProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B3A1591AED819200EECD76 /* RLDTableViewEventHandlerProvider.swift */; }; 45CA62761AED0EBC00F46A6B /* RLDTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CA62751AED0EBC00F46A6B /* RLDTableViewDelegate.swift */; }; 45CA62781AED114600F46A6B /* RLDTableViewDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CA62771AED114600F46A6B /* RLDTableViewDelegateTests.swift */; }; 45CA627A1AED48B300F46A6B /* RLDHandledViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CA62791AED48B300F46A6B /* RLDHandledViewProtocol.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 45054CEB1AEC6A75006DD0B9 /* RLDTableViewEventHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewEventHandler.swift; sourceTree = ""; }; 4537E02E1AF003160051625F /* RLDTableViewEventHandlerProviderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewEventHandlerProviderTests.swift; sourceTree = ""; }; 455CE3B51AEC65FD002EA10E /* RLDTableViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewDataSource.swift; sourceTree = ""; }; 455CE3B61AEC65FD002EA10E /* RLDTableViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewModel.swift; sourceTree = ""; }; 455CE3BB1AEC6606002EA10E /* RLDTableViewDataSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewDataSourceTests.swift; sourceTree = ""; }; 455CE3BC1AEC6606002EA10E /* RLDTableViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewModelTests.swift; sourceTree = ""; }; 455CE3BD1AEC6606002EA10E /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; }; 45643EE21AB86EFC00FE7F17 /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 45A60ED41AB8700A00488EA9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45B3A1591AED819200EECD76 /* RLDTableViewEventHandlerProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewEventHandlerProvider.swift; sourceTree = ""; }; 45CA62751AED0EBC00F46A6B /* RLDTableViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewDelegate.swift; sourceTree = ""; }; 45CA62771AED114600F46A6B /* RLDTableViewDelegateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDTableViewDelegateTests.swift; sourceTree = ""; }; 45CA62791AED48B300F46A6B /* RLDHandledViewProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RLDHandledViewProtocol.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 45643EDF1AB86EFC00FE7F17 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 455CE3B41AEC65FD002EA10E /* Classes */ = { isa = PBXGroup; children = ( 455CE3B61AEC65FD002EA10E /* RLDTableViewModel.swift */, 455CE3B51AEC65FD002EA10E /* RLDTableViewDataSource.swift */, 45CA62791AED48B300F46A6B /* RLDHandledViewProtocol.swift */, 45054CEB1AEC6A75006DD0B9 /* RLDTableViewEventHandler.swift */, 45B3A1591AED819200EECD76 /* RLDTableViewEventHandlerProvider.swift */, 45CA62751AED0EBC00F46A6B /* RLDTableViewDelegate.swift */, ); name = Classes; path = ../Classes; sourceTree = ""; }; 45643EC41AB86EFC00FE7F17 = { isa = PBXGroup; children = ( 455CE3B41AEC65FD002EA10E /* Classes */, 45A60ED31AB8700A00488EA9 /* Tests */, 45643ECE1AB86EFC00FE7F17 /* Products */, ); sourceTree = ""; }; 45643ECE1AB86EFC00FE7F17 /* Products */ = { isa = PBXGroup; children = ( 45643EE21AB86EFC00FE7F17 /* Tests.xctest */, ); name = Products; sourceTree = ""; }; 45A60ED31AB8700A00488EA9 /* Tests */ = { isa = PBXGroup; children = ( 455CE3BC1AEC6606002EA10E /* RLDTableViewModelTests.swift */, 455CE3BB1AEC6606002EA10E /* RLDTableViewDataSourceTests.swift */, 4537E02E1AF003160051625F /* RLDTableViewEventHandlerProviderTests.swift */, 45CA62771AED114600F46A6B /* RLDTableViewDelegateTests.swift */, 455CE3BD1AEC6606002EA10E /* TestHelpers.swift */, 45A60ED41AB8700A00488EA9 /* Info.plist */, ); path = Tests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 45643EE11AB86EFC00FE7F17 /* Tests */ = { isa = PBXNativeTarget; buildConfigurationList = 45643EEF1AB86EFD00FE7F17 /* Build configuration list for PBXNativeTarget "Tests" */; buildPhases = ( 45643EDE1AB86EFC00FE7F17 /* Sources */, 45643EDF1AB86EFC00FE7F17 /* Frameworks */, 45643EE01AB86EFC00FE7F17 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Tests; productName = TestsTests; productReference = 45643EE21AB86EFC00FE7F17 /* Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 45643EC51AB86EFC00FE7F17 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0630; ORGANIZATIONNAME = "Rafael Lopez Diez"; TargetAttributes = { 45643EE11AB86EFC00FE7F17 = { CreatedOnToolsVersion = 6.3; TestTargetID = 45643ECC1AB86EFC00FE7F17; }; }; }; buildConfigurationList = 45643EC81AB86EFC00FE7F17 /* Build configuration list for PBXProject "Tests" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 45643EC41AB86EFC00FE7F17; productRefGroup = 45643ECE1AB86EFC00FE7F17 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 45643EE11AB86EFC00FE7F17 /* Tests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 45643EE01AB86EFC00FE7F17 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 45643EDE1AB86EFC00FE7F17 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 455CE3B91AEC65FD002EA10E /* RLDTableViewModel.swift in Sources */, 45CA627A1AED48B300F46A6B /* RLDHandledViewProtocol.swift in Sources */, 455CE3B81AEC65FD002EA10E /* RLDTableViewDataSource.swift in Sources */, 455CE3BE1AEC6606002EA10E /* RLDTableViewDataSourceTests.swift in Sources */, 455CE3BF1AEC6606002EA10E /* RLDTableViewModelTests.swift in Sources */, 45054CEC1AEC6A75006DD0B9 /* RLDTableViewEventHandler.swift in Sources */, 4537E02F1AF003160051625F /* RLDTableViewEventHandlerProviderTests.swift in Sources */, 45CA62781AED114600F46A6B /* RLDTableViewDelegateTests.swift in Sources */, 45B3A15A1AED819200EECD76 /* RLDTableViewEventHandlerProvider.swift in Sources */, 45CA62761AED0EBC00F46A6B /* RLDTableViewDelegate.swift in Sources */, 455CE3C01AEC6606002EA10E /* TestHelpers.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 45643EEA1AB86EFD00FE7F17 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 45643EEB1AB86EFD00FE7F17 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = ""; VALIDATE_PRODUCT = YES; }; name = Release; }; 45643EF01AB86EFD00FE7F17 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = ""; CLANG_ENABLE_MODULES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tests.app/Tests"; }; name = Debug; }; 45643EF11AB86EFD00FE7F17 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = ""; CLANG_ENABLE_MODULES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = ""; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tests.app/Tests"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 45643EC81AB86EFC00FE7F17 /* Build configuration list for PBXProject "Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( 45643EEA1AB86EFD00FE7F17 /* Debug */, 45643EEB1AB86EFD00FE7F17 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 45643EEF1AB86EFD00FE7F17 /* Build configuration list for PBXNativeTarget "Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( 45643EF01AB86EFD00FE7F17 /* Debug */, 45643EF11AB86EFD00FE7F17 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 45643EC51AB86EFC00FE7F17 /* Project object */; } ================================================ FILE: Tests/Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Tests/Tests.xcodeproj/project.xcworkspace/xcshareddata/Tests.xccheckout ================================================ IDESourceControlProjectFavoriteDictionaryKey IDESourceControlProjectIdentifier 48D7CAE4-FB03-41CB-9AA5-81ABA97ABAFE IDESourceControlProjectName Tests IDESourceControlProjectOriginsDictionary B6CC70124526FB1C79E8927A505A5CB9A7F81823 https://github.com/rlopezdiez/RLDNavigationSwift.git IDESourceControlProjectPath Tests/Tests.xcodeproj IDESourceControlProjectRelativeInstallPathDictionary B6CC70124526FB1C79E8927A505A5CB9A7F81823 ../../.. IDESourceControlProjectURL https://github.com/rlopezdiez/RLDNavigationSwift.git IDESourceControlProjectVersion 111 IDESourceControlProjectWCCIdentifier B6CC70124526FB1C79E8927A505A5CB9A7F81823 IDESourceControlProjectWCConfigurations IDESourceControlRepositoryExtensionIdentifierKey public.vcs.git IDESourceControlWCCIdentifierKey B6CC70124526FB1C79E8927A505A5CB9A7F81823 IDESourceControlWCCName RLDTableViewSwift ================================================ FILE: Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme ================================================