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.

> [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
================================================
CFBundleDevelopmentRegionenCFBundleExecutable$(EXECUTABLE_NAME)CFBundleIdentifier$(PRODUCT_BUNDLE_IDENTIFIER)CFBundleInfoDictionaryVersion6.0CFBundleName$(PRODUCT_NAME)CFBundlePackageTypeAPPLCFBundleShortVersionString1.0CFBundleSignature????CFBundleVersion1LSRequiresIPhoneOSUILaunchStoryboardNameLaunchScreenUIMainStoryboardFileMainUIRequiredDeviceCapabilitiesarmv7UISupportedInterfaceOrientationsUIInterfaceOrientationPortraitUIInterfaceOrientationLandscapeLeftUIInterfaceOrientationLandscapeRight
================================================
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
================================================
CFBundleDevelopmentRegionenCFBundleExecutable$(EXECUTABLE_NAME)CFBundleIdentifier$(PRODUCT_BUNDLE_IDENTIFIER)CFBundleInfoDictionaryVersion6.0CFBundleName$(PRODUCT_NAME)CFBundlePackageTypeFMWKCFBundleShortVersionString0.2.0CFBundleSignature????CFBundleVersion1NSPrincipalClass
================================================
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
================================================
cellscategoryMoscowcategoryURLhttps://www.flickr.com/photos/keoki/sets/72157602083268883/classRLDBigPictureTableViewCellModeldate2 years agoimageName6_big.jpgimageURLhttps://www.flickr.com/photos/keoki/1406925773/in/set-72157602083268883titleThe colorful Cathedral of Vasily the BlessedcategoryAmsterdamcategoryURLhttps://www.flickr.com/photos/keoki/sets/72157603544030165/classRLDSimpleTableViewCellModeldate4 years agoimageName3_small.jpgimageURLhttps://www.flickr.com/photos/keoki/2135948414/in/set-72157603544030165titleQuiet canals in the “Venice of the North”editableeditingStyle1categoryLondoncategoryURLhttps://www.flickr.com/photos/keoki/sets/72157594446615513/classRLDSimpleTableViewCellModeldateTodayimageName9_small.jpgimageURLhttps://www.flickr.com/photos/keoki/337540114/in/set-72157594446615513titleTower Bridge, one of the most iconic bridges in the worldeditableeditingStyle1cellscategoryKuramathicategoryURLhttps://www.flickr.com/photos/keoki/sets/72157627990642402/classRLDBigPictureTableViewCellModeldate3 weeks agoimageName4_big.jpgimageURLhttps://www.flickr.com/photos/keoki/6286531716/in/set-72157627990642402titleSea Salt's crab unwinding in the beachcategoryRasdhoocategoryURLhttps://www.flickr.com/photos/keoki/sets/72157627991759766/classRLDSimpleTableViewCellModeldateTodayimageName1_small.jpgimageURLhttps://www.flickr.com/photos/keoki/6286903694/in/set-72157627990642402titleThe Maldives, home of beautiful beacheseditablemovablecategorySwiss AlpscategoryURLhttps://www.flickr.com/photos/keoki/sets/72157625889560426/classRLDSimpleTableViewCellModeldate7 years agoimageName0_small.jpgimageURLhttps://www.flickr.com/photos/keoki/5384467483/in/set-72157625889560426titlePicturesque peak covered with snoweditablemovablecategoryTenerifecategoryURLhttps://www.flickr.com/photos/keoki/sets/72157594446322517/classRLDSimpleTableViewCellModeldateYesterdayimageName8_small.jpgimageURLhttps://www.flickr.com/photos/keoki/3556768497/in/set-72157594446322517titleMount Teide, a 3,718 meter high vulcanoeditablemovabletitleMagnificent scenerycellsclassRLDCommentTableViewCellModelcommentThis building is the seat of the National Assembly of Hungary, and one of the most awesome constructions in BudapestimageName7_big.jpgimageURLhttps://www.flickr.com/photos/keoki/336410485/in/set-72157594444731896titleThe Hungarian ParliamentcategoryRomecategoryURLhttps://www.flickr.com/photos/keoki/sets/72157594444449793/classRLDSimpleTableViewCellModeldate3 weeks agoimageName5_small.jpgimageURLhttps://www.flickr.com/photos/keoki/188286231/in/set-72157594444449793titleColiseum, or Flavian AmphitheatrecategoryPariscategoryURLhttps://www.flickr.com/photos/keoki/sets/72157594444442779/classRLDSimpleTableViewCellModeldate1 month agoimageName2_small.jpgimageURLhttps://www.flickr.com/photos/keoki/188266460/in/set-72157594444442779titleThe Basilica of the Sacré-Cœur of ParistitleSingular 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
================================================
IDESourceControlProjectFavoriteDictionaryKeyIDESourceControlProjectIdentifier4E446F4C-7F72-491A-AB8F-295366F8824FIDESourceControlProjectNameTableViewPrototypeIDESourceControlProjectOriginsDictionaryB6CC70124526FB1C79E8927A505A5CB9A7F81823https://github.com/rlopezdiez/RLDNavigationSwift.gitIDESourceControlProjectPathSample app/TableViewPrototype.xcodeprojIDESourceControlProjectRelativeInstallPathDictionaryB6CC70124526FB1C79E8927A505A5CB9A7F81823../../..IDESourceControlProjectURLhttps://github.com/rlopezdiez/RLDNavigationSwift.gitIDESourceControlProjectVersion111IDESourceControlProjectWCCIdentifierB6CC70124526FB1C79E8927A505A5CB9A7F81823IDESourceControlProjectWCConfigurationsIDESourceControlRepositoryExtensionIdentifierKeypublic.vcs.gitIDESourceControlWCCIdentifierKeyB6CC70124526FB1C79E8927A505A5CB9A7F81823IDESourceControlWCCNameRLDTableViewSwift
================================================
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
================================================
SchemeUserStateRLDTableViewSwift.xcschemeorderHint1TableViewPrototype.xcschemeorderHint0SuppressBuildableAutocreation454E31F91AF008BF00AB44BCprimary457D7D2D1AF00AB700388423primary
================================================
FILE: Tests/Tests/Info.plist
================================================
CFBundleDevelopmentRegionenCFBundleExecutable$(EXECUTABLE_NAME)CFBundleIdentifiercom.rld.RLDTableViewSwiftCFBundleInfoDictionaryVersion6.0CFBundleName$(PRODUCT_NAME)CFBundlePackageTypeBNDLCFBundleShortVersionString1.0CFBundleSignature????CFBundleVersion1
================================================
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
================================================
IDESourceControlProjectFavoriteDictionaryKeyIDESourceControlProjectIdentifier48D7CAE4-FB03-41CB-9AA5-81ABA97ABAFEIDESourceControlProjectNameTestsIDESourceControlProjectOriginsDictionaryB6CC70124526FB1C79E8927A505A5CB9A7F81823https://github.com/rlopezdiez/RLDNavigationSwift.gitIDESourceControlProjectPathTests/Tests.xcodeprojIDESourceControlProjectRelativeInstallPathDictionaryB6CC70124526FB1C79E8927A505A5CB9A7F81823../../..IDESourceControlProjectURLhttps://github.com/rlopezdiez/RLDNavigationSwift.gitIDESourceControlProjectVersion111IDESourceControlProjectWCCIdentifierB6CC70124526FB1C79E8927A505A5CB9A7F81823IDESourceControlProjectWCConfigurationsIDESourceControlRepositoryExtensionIdentifierKeypublic.vcs.gitIDESourceControlWCCIdentifierKeyB6CC70124526FB1C79E8927A505A5CB9A7F81823IDESourceControlWCCNameRLDTableViewSwift
================================================
FILE: Tests/Tests.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme
================================================