Skip to content
This repository was archived by the owner on Apr 15, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
55e9a0c
update to swift 2.3, #53
jessesquires Jul 17, 2016
56af816
update travis for swift 2.3, #53
jessesquires Jul 17, 2016
f6029a2
initial migration to swift 3.0, #54
jessesquires Jul 18, 2016
2f442d4
update xcode 8 beta 3, #54
jessesquires Jul 19, 2016
77540a7
Merge branch 'develop' into swift2.3
jessesquires Jul 28, 2016
63ac07f
proj settings
jessesquires Jul 28, 2016
4912ecc
Merge branch 'swift2.3' into swift3.0
dcaunt Jul 28, 2016
4ee1a40
Update mixed cell type example for Swift 3.0
dcaunt Jul 28, 2016
8d680f1
Merge pull request #60 from dcaunt/example-mixed-swift3.0
jessesquires Jul 29, 2016
5be49cf
minor demo updates
jessesquires Jul 29, 2016
04cb5de
update Xcode 8 beta 4, #54
jessesquires Aug 3, 2016
69bb4e2
Merge branch 'develop' into swift2.3
jessesquires Aug 9, 2016
fbba40d
Merge branch 'swift2.3' into swift3.0
jessesquires Aug 9, 2016
96c97c5
update for Xcode8 beta6, #54
jessesquires Aug 16, 2016
5e12e27
update for xcode8 GM, #54
jessesquires Sep 8, 2016
a51bfe6
name closure params
jessesquires Sep 9, 2016
386e5dc
edit functionality support for UITableViewDataSource
Sep 14, 2016
923f174
Merge branch 'swift3.0' into issue_29_tableViewDataSource_editing
Sep 14, 2016
22264e8
Merge remote-tracking branch 'jessesquires/develop' into issue_29_tab…
Sep 17, 2016
78de1c1
Merge branch 'develop' into issue_29_tableViewDataSource_editing
Sep 18, 2016
3b63d1c
Merge remote-tracking branch 'jessesquires/develop' into issue_29_tab…
Sep 18, 2016
311488a
Merge remote-tracking branch 'origin/issue_29_tableViewDataSource_edi…
Sep 18, 2016
3e6a9ac
updates to match existing coding style
Sep 20, 2016
5f5aae8
Merge remote-tracking branch 'jessesquires/develop' into issue_29_tab…
Oct 1, 2016
025e7c7
Merge branch 'develop' into issue_29_tableViewDataSource_editing
Oct 1, 2016
a971fa1
minor format adjustments, more tests cases added
Oct 1, 2016
9f8afe8
Merge remote-tracking branch 'origin/issue_29_tableViewDataSource_edi…
Oct 1, 2016
656d4cc
initial migration to swift 3.0, #54
jessesquires Jul 18, 2016
0055fd0
rebase commit 1
Oct 1, 2016
3a45779
rebase commit 2
Oct 1, 2016
779aeee
updates to match existing coding style
Sep 20, 2016
3b2d28a
minor format adjustments, more tests cases added
Oct 1, 2016
9f07521
Merge remote-tracking branch 'origin/issue_29_tableViewDataSource_edi…
Oct 1, 2016
3ba8580
redundant void removed
Oct 1, 2016
d98fc67
Merge branch 'develop' into issue_29_tableViewDataSource_editing
jessesquires Oct 28, 2016
356f15e
Merge branch 'develop' into issue_29_tableViewDataSource_editing
jessesquires Oct 28, 2016
250783f
minor updates
Nov 9, 2016
b94065b
Merge remote-tracking branch 'jessesquires/develop' into issue_29_tab…
Dec 12, 2016
c62032a
review updates
Dec 12, 2016
7305f83
Merge branch 'develop' into issue_29_tableViewDataSource_editing
jessesquires Dec 13, 2016
97f94dc
minor update
Dec 13, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions Example/Sources/TableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,26 @@ final class TableViewController: UITableViewController {
cell.accessibilityIdentifier = "\(indexPath.section), \(indexPath.row)"
return cell
}

// 3. create data source provider
dataSourceProvider = DataSourceProvider(dataSource: dataSource, cellFactory: factory, supplementaryFactory: factory)

// 4. set data source
// 4. Optional - create if neccessary a datasourceEditingController to enable the editing functionality on the tableView
let tableDataSourceEditingController = TableDataSourceEditingController(
canEditConfigurator: { (indexPath, tableView) -> Bool in
return indexPath.row % 2 == 0
},
commitEditingStyle:{ (tableView, editingStyle, indexPath) in
if editingStyle == .delete {
if let _ = self.dataSourceProvider?.dataSource.remove(at: indexPath) {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
})

dataSourceProvider?.tableEditingController = tableDataSourceEditingController

// 5. set data source
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

tableView.dataSource = dataSourceProvider?.tableViewDataSource
}

Expand Down
6 changes: 6 additions & 0 deletions JSQDataSourcesKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
1D68ECA31DFFEF4600A1AFB7 /* TableDataSourceEditingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DD7AC5C1D86C4BC00B676A6 /* TableDataSourceEditingController.swift */; };
1DD7AC5D1D86C4BC00B676A6 /* TableDataSourceEditingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DD7AC5C1D86C4BC00B676A6 /* TableDataSourceEditingController.swift */; };
881A92DB1CB881550080BC5C /* FetchedResultsDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DCAD2C1CB87CB400C018AF /* FetchedResultsDelegate.swift */; };
881A92DD1CB881550080BC5C /* JSQDataSourcesKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 88DCAD2F1CB87CB400C018AF /* JSQDataSourcesKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
881A92E11CB881550080BC5C /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DCAD331CB87CB400C018AF /* Section.swift */; };
Expand Down Expand Up @@ -84,6 +86,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
1DD7AC5C1D86C4BC00B676A6 /* TableDataSourceEditingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableDataSourceEditingController.swift; sourceTree = "<group>"; };
881A92CC1CB881270080BC5C /* JSQDataSourcesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JSQDataSourcesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
882CA4531D1D7D48006112B9 /* BridgedFetchedResultsDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BridgedFetchedResultsDelegate.swift; sourceTree = "<group>"; };
882CA4571D1D82E1006112B9 /* TestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCase.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -178,6 +181,7 @@
88DCAD2C1CB87CB400C018AF /* FetchedResultsDelegate.swift */,
88DCAD2D1CB87CB400C018AF /* Info.plist */,
88DCAD2F1CB87CB400C018AF /* JSQDataSourcesKit.h */,
1DD7AC5C1D86C4BC00B676A6 /* TableDataSourceEditingController.swift */,
88DCAD331CB87CB400C018AF /* Section.swift */,
88DCAD351CB87CB400C018AF /* TitledSupplementaryView.swift */,
88DCAD371CB87CB400C018AF /* TitledSupplementaryViewFactory.swift */,
Expand Down Expand Up @@ -388,6 +392,7 @@
88B80CD91CBBF62A00EDF9D5 /* DataSourceProvider.swift in Sources */,
882CA4551D1D7DE1006112B9 /* BridgedFetchedResultsDelegate.swift in Sources */,
882CA4561D1D7DE1006112B9 /* DataSource.swift in Sources */,
1D68ECA31DFFEF4600A1AFB7 /* TableDataSourceEditingController.swift in Sources */,
881A92DB1CB881550080BC5C /* FetchedResultsDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -403,6 +408,7 @@
88DCAD761CB87CC000C018AF /* TitledSupplementaryView.swift in Sources */,
88DCAD741CB87CC000C018AF /* Section.swift in Sources */,
88B80CD81CBBF62A00EDF9D5 /* DataSourceProvider.swift in Sources */,
1DD7AC5D1D86C4BC00B676A6 /* TableDataSourceEditingController.swift in Sources */,
888799491D0E2D1700BBCCBC /* DataSource.swift in Sources */,
88DCAD781CB87CC000C018AF /* TitledSupplementaryViewFactory.swift in Sources */,
);
Expand Down
17 changes: 17 additions & 0 deletions Source/BridgedDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ internal typealias TableCellForRowAtIndexPathHandler = (UITableView, IndexPath)
internal typealias TableTitleForHeaderInSectionHandler = (Int) -> String?
internal typealias TableTitleForFooterInSectionHandler = (Int) -> String?

internal typealias TableCanEditHandler = (UITableView, IndexPath) -> Bool
internal typealias TableCommitEditingStyleHandler = (UITableView, UITableViewCellEditingStyle, IndexPath) -> Void


/*
This class is responsible for implementing the `UICollectionViewDataSource` and `UITableViewDataSource` protocols.
Expand All @@ -46,6 +49,9 @@ internal typealias TableTitleForFooterInSectionHandler = (Int) -> String?
var tableCellForRowAtIndexPath: TableCellForRowAtIndexPathHandler?
var tableTitleForHeaderInSection: TableTitleForHeaderInSectionHandler?
var tableTitleForFooterInSection: TableTitleForFooterInSectionHandler?

var tableCanEditRow: TableCanEditHandler?
var tableCommitEditingStyleForRow: TableCommitEditingStyleHandler?

init(numberOfSections: @escaping NumberOfSectionsHandler,
numberOfItemsInSection: @escaping NumberOfItemsInSectionHandler) {
Expand Down Expand Up @@ -103,4 +109,15 @@ extension BridgedDataSource: UITableViewDataSource {
}
return nil
}

@objc func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if let closure = tableCanEditRow {
return closure(tableView,indexPath)
}
return false
}

@objc func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
tableCommitEditingStyleForRow?(tableView,editingStyle,indexPath)
}
}
17 changes: 15 additions & 2 deletions Source/DataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ extension DataSourceProtocol {

- returns: The item specified by indexPath, or `nil`.
*/
func item(atIndexPath indexPath: IndexPath) -> Item? {
public func item(atIndexPath indexPath: IndexPath) -> Item? {
return item(atRow: indexPath.row, inSection: indexPath.section)
}
}
Expand Down Expand Up @@ -159,7 +159,20 @@ public struct DataSource<S: SectionInfoProtocol>: DataSourceProtocol {
guard section < sections.count else { return nil }
return sections[section].footerTitle
}


/**
Removes an item at the specified index path.

- parameter indexPath: The index path specifying the location of the item.
- returns: The item at `indexPath`, or `nil` if it does not exist.
*/
@discardableResult
public mutating func remove(at indexPath: IndexPath) -> S.Item? {
guard item(atIndexPath: indexPath) != nil else { return nil }
let section = indexPath.section
let row = indexPath.row
return sections[section].items.remove(at: row)
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


// MARK: Subscripts

Expand Down
24 changes: 21 additions & 3 deletions Source/DataSourceProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ where CellFactory.Item == DataSource.Item, SupplementaryFactory.Item == DataSour
public let supplementaryFactory: SupplementaryFactory

fileprivate var bridgedDataSource: BridgedDataSource?


fileprivate var _tableEditingController: TableDataSourceEditingController?

// MARK: Initialization

Expand All @@ -61,7 +62,6 @@ where CellFactory.Item == DataSource.Item, SupplementaryFactory.Item == DataSour
}
}


public extension DataSourceProvider where CellFactory.View: UITableViewCell {

// MARK: UITableViewDataSource
Expand All @@ -73,7 +73,16 @@ public extension DataSourceProvider where CellFactory.View: UITableViewCell {
}
return bridgedDataSource!
}


public var tableEditingController: TableDataSourceEditingController? {
set {
_tableEditingController = newValue
}
get {
return _tableEditingController
}
}

private func tableViewBridgedDataSource() -> BridgedDataSource {
let dataSource = BridgedDataSource(
numberOfSections: { [unowned self] () -> Int in
Expand All @@ -95,6 +104,15 @@ public extension DataSourceProvider where CellFactory.View: UITableViewCell {
dataSource.tableTitleForFooterInSection = { [unowned self] (section) -> String? in
return self.dataSource.footerTitle(inSection: section)
}

dataSource.tableCanEditRow = { [unowned self] (tableView, indexPath) -> Bool in
guard let editDataSource = self.tableEditingController else { return false }
return editDataSource.canEditRowAt(indexPath: indexPath, in: tableView)
}

dataSource.tableCommitEditingStyleForRow = { [unowned self] (tableView, editingStyle,indexPath) in
self.tableEditingController?.commitEditStyleForRow(in: tableView, editingStyle: editingStyle, at: indexPath)
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


return dataSource
}
Expand Down
45 changes: 45 additions & 0 deletions Source/TableDataSourceEditingController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://jessesquires.com/JSQDataSourcesKit
//
//
// GitHub
// https://github.com/jessesquires/JSQDataSourcesKit
//
//
// License
// Copyright © 2015 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//

import Foundation
import UIKit

public struct TableDataSourceEditingController {

public typealias CanEditRowConfigurator = (IndexPath, UITableView) -> Bool
public typealias CommitEditingStyleConfigurator = (UITableView, UITableViewCellEditingStyle, IndexPath) -> Void

public let canEditConfigurator: CanEditRowConfigurator
public let commitEditingStyle: CommitEditingStyleConfigurator

public init(canEditConfigurator: @escaping CanEditRowConfigurator,
commitEditingStyle: @escaping CommitEditingStyleConfigurator) {

self.canEditConfigurator = canEditConfigurator
self.commitEditingStyle = commitEditingStyle
}

public func canEditRowAt(indexPath: IndexPath, in tableView: UITableView) -> Bool {
return canEditConfigurator(indexPath, tableView)
}

public func commitEditStyleForRow(in tableView: UITableView, editingStyle: UITableViewCellEditingStyle, at indexPath: IndexPath) {
return commitEditingStyle(tableView, editingStyle, indexPath)
}

}
89 changes: 89 additions & 0 deletions Tests/DataSourceProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -369,4 +369,93 @@ final class DataSourceProviderTests: TestCase {
}
}

func test_thatDataSourceProvider_forTableView_returnsExpectedData_afterRemovingRowFromTableView() {
// GIVEN: a single section with data items
let expectedModel = FakeViewModel()
let expectedIndexPath = IndexPath(row: 2, section: 0)

let section0 = Section(items: FakeViewModel(), FakeViewModel(), expectedModel, FakeViewModel(), FakeViewModel(),
headerTitle: "Header",
footerTitle: "Footer")
let dataSource = DataSource([section0])

let oldItemForExpectedIndexPath = dataSource.item(atRow: expectedIndexPath.row, inSection: expectedIndexPath.section)
let oldCount = dataSource.numberOfItems(inSection: expectedIndexPath.section)

let factoryExpectation = expectation(description: #function)
tableView.dequeueCellExpectation = expectation(description: dequeueCellExpectationName + #function)

typealias TableCellFactory = ViewFactory<FakeViewModel, FakeTableCell>
var dataSourceProvider: DataSourceProvider<DataSource<Section<FakeViewModel>>, TableCellFactory, TableCellFactory>!

// GIVEN: a cell factory
let factory = ViewFactory(reuseIdentifier: cellReuseId) { (cell, model: FakeViewModel?, type, tableView, indexPath) -> FakeTableCell in

XCTAssertEqual(cell.reuseIdentifier!, self.cellReuseId, "Dequeued cell should have expected identifier")
XCTAssertEqual(tableView, self.tableView, "TableView should equal the tableView for the data source")

factoryExpectation.fulfill()
return cell
}

//GIVEN: a data source editing controller
let tableDataSourceEditingController = TableDataSourceEditingController(
canEditConfigurator: { (indexPath, tableView) -> Bool in
return indexPath == expectedIndexPath
},
commitEditingStyle:{ (tableView, editingStyle, indexPath) in
if editingStyle == .delete {
if let _ = dataSourceProvider.dataSource.remove(at: indexPath) {
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
})

// GIVEN: a data source provider
dataSourceProvider = DataSourceProvider(dataSource: dataSource, cellFactory: factory, supplementaryFactory: factory)
dataSourceProvider.tableEditingController = tableDataSourceEditingController

let tableViewDataSource = dataSourceProvider.tableViewDataSource
tableView.dataSource = tableViewDataSource

// WHEN: we call the table view data source methods
let canEditRow = tableViewDataSource.tableView?(tableView, canEditRowAt: expectedIndexPath)
tableViewDataSource.tableView?(tableView, commit: .delete, forRowAt: expectedIndexPath)
let newItemForExpectedIndexPath = dataSourceProvider.dataSource.item(atRow: expectedIndexPath.row, inSection: expectedIndexPath.section)
let newCount = dataSourceProvider.dataSource.numberOfItems(inSection: expectedIndexPath.section)

let numSections = tableViewDataSource.numberOfSections?(in: tableView)
let cell = tableViewDataSource.tableView(tableView, cellForRowAt: expectedIndexPath)
let header = tableViewDataSource.tableView?(tableView, titleForHeaderInSection: 0)
let footer = tableViewDataSource.tableView?(tableView, titleForFooterInSection: 0)

// THEN: we receive the expected return values
XCTAssertNotNil(canEditRow, "canEditRow should not be nil")
XCTAssert(canEditRow!, "expectedIndexpath should be able to be removed from tableview")

XCTAssertNotEqual(oldCount, newCount, "Number of items for \(expectedIndexPath.section) should not be equal after removing the expected row")
XCTAssertEqual(newCount, oldCount - 1, "Number of items for \(expectedIndexPath.section) should be less by one")
XCTAssertNotEqual(newItemForExpectedIndexPath, oldItemForExpectedIndexPath, "old item at row \(expectedIndexPath.row), section \(expectedIndexPath.section) shouldn't exist any more")

XCTAssertNotNil(numSections, "Number of sections should not be nil")
XCTAssertEqual(numSections!, dataSourceProvider.dataSource.sections.count, "Data source should return expected number of sections")

XCTAssertNotNil(cell.reuseIdentifier, "Cell reuse identifier should not be nil")
XCTAssertEqual(cell.reuseIdentifier!, cellReuseId, "Data source should return cells with the expected identifier")

XCTAssertNotNil(header, "Header should not be nil")
XCTAssertNotNil(section0.headerTitle, "Section 0 header title should not be nil")
XCTAssertEqual(header!, section0.headerTitle!, "Data source should return expected header title")

XCTAssertNotNil(footer, "Footer should not be nil")
XCTAssertNotNil(section0.footerTitle, "Section 0 footer title should not be nil")
XCTAssertEqual(footer!, section0.footerTitle!, "Data source should return expected footer title")

// THEN: the tableView calls `dequeueReusableCellWithIdentifier`
// THEN: the cell factory calls its `ConfigurationHandler`
waitForExpectations(timeout: defaultTimeout, handler: { (error) -> Void in
XCTAssertNil(error, "Expectations should not error")
})
}

}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 🙌

19 changes: 18 additions & 1 deletion Tests/DataSourceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ final class DataSourceTests: XCTestCase {
let ip = IndexPath(item: 2, section: 2)
let item = dataSource[ip]

// THEN: we receive the exepected data
// THEN: we receive the expected data
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😊

XCTAssertEqual(item, model)
}

Expand All @@ -219,4 +219,21 @@ final class DataSourceTests: XCTestCase {
// THEN: the item is replaced
XCTAssertEqual(dataSource[ip], item)
}

func test_thatDataSource_removesExpectedData_atIndexPath() {
// GIVEN: a data source
let sectionA = Section(items: FakeViewModel(), FakeViewModel(), headerTitle: "Header")
let sectionB = Section(items: FakeViewModel(), FakeViewModel(), footerTitle: "Footer")
var dataSource = DataSource(sections: sectionA, sectionB)

// WHEN: we set an item at a specific index path
let ip = IndexPath(item: 1, section: 0)
let itemToRemove = dataSource.item(atIndexPath: ip)

// THEN: Check if an item exists at the specified indexPath .Then check if the removedItem is the expected item
XCTAssertNotNil(itemToRemove)

let removedItem = dataSource.remove(at: ip)
XCTAssertEqual(removedItem, itemToRemove)
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should test out-of-bounds indexPaths, too 😄

}