Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to receive UITableViewDelegate & UIScrollViewDelegate messages from UITableView #119

Merged
merged 1 commit into from
Dec 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class ViewController: TableViewController {
tableView.estimatedSectionHeaderHeight = 13.5
tableView.estimatedSectionFooterHeight = 13.5

dataSource = DataSource(tableViewDelegate: self)
dataSource.sections = [
Section(header: "Styles", rows: [
Row(text: "Value 1", detailText: "Detail", cellClass: Value1Cell.self),
Expand Down Expand Up @@ -89,6 +90,25 @@ class ViewController: TableViewController {
}
}

extension TableViewController: UITableViewDelegate {
// MARK: - UIScrollViewDelegate example functions
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// You can get UIScrollViewDelegate functions forwarded, even though the `DataSource` instance is the true delegate
// ...
}

// MARK: - UITableViewDelegate example functions
public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I can see this item being useful...

But didSelect is covered as part of Static closure handling, and scrollViewWillBeginDragging can be observed with a observer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The functions here are not comprehensive regarding what methods will get forwarded, they are just meant to demonstrate this functionality. They all get forwarded, including any potential future UIScrollViewDelegate functions. The only ones that do not are the ones that Static handles completely where it doesn't make sense to forward.

I don't think all UIScrollViewDelegate methods can be handled with observers. Developers are more familiar with UIScrollViewDelegate & UITableViewDelegate methods than they are with observing those same changes. This is why Static has at least 3 tickets around requesting this functionality. If the code only forwarded the calls that can't be done with observers, that would obviously create more confusion.

Copy link
Contributor

Choose a reason for hiding this comment

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

understood

// You can get UITableViewDelegate functions forwarded, even though the `DataSource` instance is the true delegate
// ...
}

public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// The Row object's `selection` property will handle most of your use cases, but
// if you need to do something additional you can still implement this function.
}
}

class LargeAutoSizedExtremityView: UIView {
lazy var label: UILabel = {
let label = UILabel()
Expand Down
2 changes: 1 addition & 1 deletion Static.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = 'Static'
spec.version = '2.2.0'
spec.version = '2.3.0'

Choose a reason for hiding this comment

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

is this a breaking change? 🤔

Copy link
Contributor Author

@jaredegan jaredegan Nov 21, 2017

Choose a reason for hiding this comment

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

nah, bro. I don't know what Venmo uses for CocoaPods, but I assumed Semantic Versioning: https://semver.org/

Given a version number MAJOR.MINOR.PATCH, increment the:

MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards-compatible manner, and
PATCH version when you make backwards-compatible bug fixes.

Choose a reason for hiding this comment

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

ahhh, you right you right.

spec.summary = 'Simple static table views for iOS in Swift.'
spec.description = 'Static provides simple static table views for iOS in Swift.'
spec.homepage = 'https://github.com/venmo/static'
Expand Down
26 changes: 24 additions & 2 deletions Static/DataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,13 @@ public class DataSource: NSObject {

// MARK: - Initializers

/// Initialize with optional `tableView` and `sections`.
public init(tableView: UITableView? = nil, sections: [Section]? = nil) {
/// Initialize with optional `tableView`, `sections` and `tableViewDelegate`.
public init(tableView: UITableView? = nil, sections: [Section]? = nil, tableViewDelegate: UITableViewDelegate? = nil) {
assert(Thread.isMainThread, "You must access Static.DataSource from the main thread.")

self.tableView = tableView
self.sections = sections ?? []
self.tableViewDelegate = tableViewDelegate

super.init()

Expand All @@ -73,6 +74,23 @@ public class DataSource: NSObject {
return row(at: indexPath)
}

// MARK: - Forwarding UITableViewDelegate messages

/// If you have a use for `UITableViewDelegate` or `UIScrollViewDelegate` messages, you can use this property to receive those messages. `DataSource` needs to be the `UITableView` instance's true `delegate`, but will forward messages to this property.
/// You must pass this in the `init` function.
weak public private(set) var tableViewDelegate: UITableViewDelegate?

override public func forwardingTarget(for aSelector: Selector!) -> Any? {
if let forwardDelegate = tableViewDelegate, forwardDelegate.responds(to: aSelector) {
return forwardDelegate
} else {
return super.forwardingTarget(for: aSelector)
}
}

override public func responds(to aSelector: Selector!) -> Bool {
return super.responds(to: aSelector) || tableViewDelegate?.responds(to: aSelector) == true
}

// MARK: - Private

Expand Down Expand Up @@ -275,12 +293,16 @@ extension DataSource: UITableViewDelegate {
if let row = row(at: indexPath) {
row.selection?()
}

tableViewDelegate?.tableView?(tableView, didSelectRowAt: indexPath)

Choose a reason for hiding this comment

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

why is this needed if we have the forwarding target?.. wouldn't it send this there anyway?.. I may be misunderstanding forwardingTarget...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since DataSource implements this method, it gets the message sent to it, and thus it doesn't go to the forwardingTarget.

Messages only get forwarded to the forwardingTarget if this object doesn't implement.

}

public func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
if let row = row(at: indexPath) {
row.accessory.selection?()
}

tableViewDelegate?.tableView?(tableView, accessoryButtonTappedForRowWith: indexPath)
}
}

Expand Down
27 changes: 27 additions & 0 deletions Static/Tests/DataSourceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,31 @@ class DataSourceTests: XCTestCase {
XCTAssertEqual(dataSource, tableView2.dataSource as? DataSource)
XCTAssertEqual(dataSource, tableView2.delegate as? DataSource)
}

func testTableViewDelegateForwarding() {
// Sample object that conforms to `UITableViewDelegate` protocol
class TestTableViewDelegate: NSObject, UITableViewDelegate {
static var delegateDidForward = false

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
TestTableViewDelegate.delegateDidForward = true
}
}

let sampleDelegate = TestTableViewDelegate()
let dataSource2 = DataSource(tableViewDelegate: sampleDelegate)

XCTAssertNotNil(dataSource2.tableViewDelegate)

let forwardingTarget = dataSource2.forwardingTarget(for: #selector(UITableViewDelegate.tableView(_:willDisplay:forRowAt:)))
XCTAssertNotNil(forwardingTarget)
XCTAssertNotNil(forwardingTarget as? TestTableViewDelegate)

// Test actual message
TestTableViewDelegate.delegateDidForward = false

(dataSource2 as UITableViewDelegate).tableView!(UITableView(), willDisplay: UITableViewCell(), forRowAt: IndexPath())
XCTAssertTrue(TestTableViewDelegate.delegateDidForward)

}
}