From 3d7607c77e3f8417e8ba1ab5954e8c5d5605b4cc Mon Sep 17 00:00:00 2001 From: Tom Kraina Date: Wed, 4 May 2016 18:14:23 +0200 Subject: [PATCH 1/9] Add support for copy action Closes https://github.com/venmo/Static/issues/80 --- Example/ViewController.swift | 5 +++++ Static/DataSource.swift | 14 ++++++++++++++ Static/Row.swift | 13 ++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 629d92d..78cf0f8 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -69,6 +69,11 @@ class ViewController: TableViewController { self.showAlert(title: "Deleted.") }) ]) + ]), + Section(header: "Copying", rows: [ + Row(text: "Tap and hold this row", copyAction: { [unowned self] row in + self.showAlert(title: "Copied.") + }) ]) ] } diff --git a/Static/DataSource.swift b/Static/DataSource.swift index c56d95f..841b3a4 100644 --- a/Static/DataSource.swift +++ b/Static/DataSource.swift @@ -277,4 +277,18 @@ extension DataSource: UITableViewDelegate { row.accessory.selection?() } } + + public func tableView(tableView: UITableView, shouldShowMenuForRowAtIndexPath indexPath: NSIndexPath) -> Bool { + return rowForIndexPath(indexPath)?.canCopy ?? false + } + + public func tableView(tableView: UITableView, canPerformAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) -> Bool { + return action == #selector(NSObject.copy(_:)) && (rowForIndexPath(indexPath)?.canCopy ?? false) + } + + public func tableView(tableView: UITableView, performAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) { + if let row = rowForIndexPath(indexPath) where action == #selector(NSObject.copy(_:)) { + row.copyAction?(row) + } + } } diff --git a/Static/Row.swift b/Static/Row.swift index 701e530..4712e51 100644 --- a/Static/Row.swift +++ b/Static/Row.swift @@ -3,6 +3,9 @@ import UIKit /// Row or Accessory selection callback. public typealias Selection = () -> Void +/// Row copy callback +public typealias CopyAction = (Row) -> Void + /// Representation of a table row. public struct Row: Hashable, Equatable { @@ -113,7 +116,14 @@ public struct Row: Hashable, Equatable { /// Actions to show when swiping the cell, such as Delete. public var editActions: [EditAction] + + /// Action to run when the row is selected to copy + public var copyAction: CopyAction? + var canCopy: Bool { + return copyAction != nil + } + var canEdit: Bool { return editActions.count > 0 } @@ -134,7 +144,7 @@ public struct Row: Hashable, Equatable { // MARK: - Initializers public init(text: String? = nil, detailText: String? = nil, selection: Selection? = nil, - image: UIImage? = nil, accessory: Accessory = .None, cellClass: CellType.Type? = nil, context: Context? = nil, editActions: [EditAction] = [], UUID: String = NSUUID().UUIDString) { + image: UIImage? = nil, accessory: Accessory = .None, cellClass: CellType.Type? = nil, context: Context? = nil, editActions: [EditAction] = [], copyAction: CopyAction? = nil, UUID: String = NSUUID().UUIDString) { self.UUID = UUID self.text = text @@ -145,6 +155,7 @@ public struct Row: Hashable, Equatable { self.cellClass = cellClass ?? Value1Cell.self self.context = context self.editActions = editActions + self.copyAction = copyAction } } From f094557d96c2b73c5744cf1e2943f58187796163 Mon Sep 17 00:00:00 2001 From: Tom Kraina Date: Fri, 23 Sep 2016 13:11:43 +0200 Subject: [PATCH 2/9] Migrate to Swift 2.3 --- Static/DataSource.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Static/DataSource.swift b/Static/DataSource.swift index 841b3a4..f473572 100644 --- a/Static/DataSource.swift +++ b/Static/DataSource.swift @@ -283,11 +283,11 @@ extension DataSource: UITableViewDelegate { } public func tableView(tableView: UITableView, canPerformAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) -> Bool { - return action == #selector(NSObject.copy(_:)) && (rowForIndexPath(indexPath)?.canCopy ?? false) + return action == #selector(NSObject.copy) && (rowForIndexPath(indexPath)?.canCopy ?? false) } public func tableView(tableView: UITableView, performAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) { - if let row = rowForIndexPath(indexPath) where action == #selector(NSObject.copy(_:)) { + if let row = rowForIndexPath(indexPath) where action == #selector(NSObject.copy) { row.copyAction?(row) } } From a0ab5658509c195e83fcd1ec0358f77508144639 Mon Sep 17 00:00:00 2001 From: Tom Kraina Date: Fri, 23 Sep 2016 13:15:23 +0200 Subject: [PATCH 3/9] Update podspec to 1.2.0 --- Static.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Static.podspec b/Static.podspec index 5b7c6ba..f2aa050 100644 --- a/Static.podspec +++ b/Static.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Static' - spec.version = '1.1.1' + spec.version = '1.2.0' 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' From aa0c12d2c32c39d8a6675d5a14301a68c8cdcd94 Mon Sep 17 00:00:00 2001 From: Tom Kraina Date: Tue, 7 Feb 2017 14:16:25 +0100 Subject: [PATCH 4/9] Fix wrong copy selectors --- Static/DataSource.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Static/DataSource.swift b/Static/DataSource.swift index f473572..4af5eac 100644 --- a/Static/DataSource.swift +++ b/Static/DataSource.swift @@ -283,11 +283,11 @@ extension DataSource: UITableViewDelegate { } public func tableView(tableView: UITableView, canPerformAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) -> Bool { - return action == #selector(NSObject.copy) && (rowForIndexPath(indexPath)?.canCopy ?? false) + return action == #selector(UIResponder.copy(_:)) && (rowForIndexPath(indexPath)?.canCopy ?? false) } public func tableView(tableView: UITableView, performAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) { - if let row = rowForIndexPath(indexPath) where action == #selector(NSObject.copy) { + if let row = rowForIndexPath(indexPath) where action == #selector(UIResponder.copy(_:)) { row.copyAction?(row) } } From ca122b6900a4fff014b4179f75a4187d25c2c57b Mon Sep 17 00:00:00 2001 From: Tom Kraina Date: Tue, 7 Feb 2017 14:18:18 +0100 Subject: [PATCH 5/9] Version bump to 1.2.0.2 --- Static.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Static.podspec b/Static.podspec index f2aa050..5ce901a 100644 --- a/Static.podspec +++ b/Static.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Static' - spec.version = '1.2.0' + spec.version = '1.2.0.2' 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' From 3e4ed41fdf4c5772515d4604917ef4e1985a7876 Mon Sep 17 00:00:00 2001 From: Tom Kraina Date: Tue, 7 Mar 2017 16:27:42 +0100 Subject: [PATCH 6/9] Migrate to Swift 3.0 --- Example/CustomTableViewCell.swift | 16 ++--- Example/NibTableViewCell.swift | 6 +- Example/ViewController.swift | 32 +++++----- Example/WindowController.swift | 4 +- Static.xcodeproj/project.pbxproj | 12 ++-- Static/ButtonCell.swift | 8 +-- Static/CellType.swift | 4 +- Static/DataSource.swift | 100 +++++++++++++++--------------- Static/Row.swift | 40 ++++++------ Static/Section.swift | 4 +- Static/SubtitleCell.swift | 4 +- Static/TableViewController.swift | 58 ++++++++--------- Static/Value1Cell.swift | 4 +- Static/Value2Cell.swift | 4 +- 14 files changed, 149 insertions(+), 147 deletions(-) diff --git a/Example/CustomTableViewCell.swift b/Example/CustomTableViewCell.swift index 4e8277f..c25e94c 100644 --- a/Example/CustomTableViewCell.swift +++ b/Example/CustomTableViewCell.swift @@ -5,10 +5,10 @@ final class CustomTableViewCell: UITableViewCell, CellType { // MARK: - Properties - private lazy var centeredLabel: UILabel = { + fileprivate lazy var centeredLabel: UILabel = { let label = UILabel() - label.textAlignment = .Center - label.textColor = .whiteColor() + label.textAlignment = .center + label.textColor = .white label.translatesAutoresizingMaskIntoConstraints = false return label }() @@ -18,14 +18,14 @@ final class CustomTableViewCell: UITableViewCell, CellType { override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - contentView.backgroundColor = .grayColor() + contentView.backgroundColor = .gray contentView.addSubview(centeredLabel) let views = ["centeredLabel": centeredLabel] - var constraints: [NSLayoutConstraint] = NSLayoutConstraint.constraintsWithVisualFormat("|-[centeredLabel]-|", options: [], metrics: nil, views: views) - constraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|-[centeredLabel]-|", options: [], metrics: nil, views: views) - NSLayoutConstraint.activateConstraints(constraints) + var constraints: [NSLayoutConstraint] = NSLayoutConstraint.constraints(withVisualFormat: "|-[centeredLabel]-|", options: [], metrics: nil, views: views) + constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-[centeredLabel]-|", options: [], metrics: nil, views: views) + NSLayoutConstraint.activate(constraints) } required init?(coder aDecoder: NSCoder) { @@ -35,7 +35,7 @@ final class CustomTableViewCell: UITableViewCell, CellType { // MARK: - CellType - func configure(row row: Row) { + func configure(row: Row) { centeredLabel.text = row.text } } diff --git a/Example/NibTableViewCell.swift b/Example/NibTableViewCell.swift index 86ee10b..022d966 100644 --- a/Example/NibTableViewCell.swift +++ b/Example/NibTableViewCell.swift @@ -5,16 +5,16 @@ final class NibTableViewCell: UITableViewCell, CellType { // MARK: - Properties - @IBOutlet weak private var centeredLabel: UILabel! + @IBOutlet weak fileprivate var centeredLabel: UILabel! // MARK: - CellType static func nib() -> UINib? { - return UINib(nibName: String(self), bundle: nil) + return UINib(nibName: String(describing: self), bundle: nil) } - func configure(row row: Row) { + func configure(row: Row) { centeredLabel.text = row.text } } diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 78cf0f8..a54a2da 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -5,9 +5,9 @@ class ViewController: TableViewController { // MARK: - Properties - private let customAccessory: UIView = { + fileprivate let customAccessory: UIView = { let view = UIView(frame: CGRect(x: 0, y: 0, width: 32, height: 32)) - view.backgroundColor = .redColor() + view.backgroundColor = .red return view }() @@ -15,7 +15,7 @@ class ViewController: TableViewController { // MARK: - Initializers convenience init() { - self.init(style: .Grouped) + self.init(style: .grouped) } @@ -31,22 +31,22 @@ class ViewController: TableViewController { dataSource.sections = [ Section(header: "Styles", rows: [ Row(text: "Value 1", detailText: "Detail", cellClass: Value1Cell.self), - Row(text: "Value 1", detailText: "with an image", cellClass: Value1Cell.self, image: UIImage(named: "Settings")), + Row(text: "Value 1", detailText: "with an image", image: UIImage(named: "Settings"), cellClass: Value1Cell.self), Row(text: "Value 2", detailText: "Detail", cellClass: Value2Cell.self), Row(text: "Subtitle", detailText: "Detail", cellClass: SubtitleCell.self), - Row(text: "Button", detailText: "Detail", cellClass: ButtonCell.self, selection: { [unowned self] in + Row(text: "Button", detailText: "Detail", selection: { [unowned self] in self.showAlert(title: "Row Selection") - }), + }, cellClass: ButtonCell.self), Row(text: "Custom from nib", cellClass: NibTableViewCell.self) ], footer: "This is a section footer."), Section(header: "Accessories", rows: [ Row(text: "None"), - Row(text: "Disclosure Indicator", accessory: .DisclosureIndicator), - Row(text: "Detail Disclosure Button", accessory: .DetailDisclosureButton({ [unowned self] in + Row(text: "Disclosure Indicator", accessory: .disclosureIndicator), + Row(text: "Detail Disclosure Button", accessory: .detailDisclosureButton({ [unowned self] in self.showAlert(title: "Detail Disclosure Button") })), - Row(text: "Checkmark", accessory: .Checkmark), - Row(text: "Detail Button", accessory: .DetailButton({ [unowned self] in + Row(text: "Checkmark", accessory: .checkmark), + Row(text: "Detail Button", accessory: .detailButton({ [unowned self] in self.showAlert(title: "Detail Button") })), Row(text: "Custom View", accessory: .View(customAccessory)) @@ -62,10 +62,10 @@ class ViewController: TableViewController { ]), Section(header: "Editing", rows: [ Row(text: "Swipe this row", editActions: [ - Row.EditAction(title: "Warn", backgroundColor: .orangeColor(), selection: { [unowned self] in + Row.EditAction(title: "Warn", backgroundColor: .orange, selection: { [unowned self] in self.showAlert(title: "Warned.") }), - Row.EditAction(title: "Delete", style: .Destructive, selection: { [unowned self] in + Row.EditAction(title: "Delete", style: .destructive, selection: { [unowned self] in self.showAlert(title: "Deleted.") }) ]) @@ -81,9 +81,9 @@ class ViewController: TableViewController { // MARK: - Private - private func showAlert(title title: String? = nil, message: String? = "You tapped it. Good work.", button: String = "Thanks") { - let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert) - alert.addAction(UIAlertAction(title: button, style: .Cancel, handler: nil)) - presentViewController(alert, animated: true, completion: nil) + fileprivate func showAlert(title: String? = nil, message: String? = "You tapped it. Good work.", button: String = "Thanks") { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: button, style: .cancel, handler: nil)) + present(alert, animated: true, completion: nil) } } diff --git a/Example/WindowController.swift b/Example/WindowController.swift index cdd3b03..fa23683 100644 --- a/Example/WindowController.swift +++ b/Example/WindowController.swift @@ -2,7 +2,7 @@ import UIKit @UIApplicationMain class WindowController: UIResponder { var window: UIWindow? = { - let window = UIWindow(frame: UIScreen.mainScreen().bounds) + let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UINavigationController(rootViewController: ViewController()) return window }() @@ -10,7 +10,7 @@ import UIKit extension WindowController: UIApplicationDelegate { - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window?.makeKeyAndVisible() return true } diff --git a/Static.xcodeproj/project.pbxproj b/Static.xcodeproj/project.pbxproj index 1e88c83..6b7fa56 100644 --- a/Static.xcodeproj/project.pbxproj +++ b/Static.xcodeproj/project.pbxproj @@ -273,14 +273,14 @@ TargetAttributes = { 21826AA91B3F51A100AA9641 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 0800; + LastSwiftMigration = 0820; }; 21826AB31B3F51A100AA9641 = { CreatedOnToolsVersion = 7.0; }; 36748D4E1B5034EC0046F207 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 0800; + LastSwiftMigration = 0820; }; }; }; @@ -505,7 +505,7 @@ PRODUCT_NAME = Static; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -525,7 +525,7 @@ PRODUCT_NAME = Static; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -561,7 +561,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.venmo.Example; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -575,7 +575,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.venmo.Example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/Static/ButtonCell.swift b/Static/ButtonCell.swift index b386c7d..ba237e4 100644 --- a/Static/ButtonCell.swift +++ b/Static/ButtonCell.swift @@ -1,11 +1,11 @@ import UIKit -public class ButtonCell: UITableViewCell, CellType { +open class ButtonCell: UITableViewCell, CellType { // MARK: - Initializers public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - super.init(style: .Default, reuseIdentifier: reuseIdentifier) + super.init(style: .default, reuseIdentifier: reuseIdentifier) initialize() } @@ -17,7 +17,7 @@ public class ButtonCell: UITableViewCell, CellType { // MARK: - UIView - public override func tintColorDidChange() { + open override func tintColorDidChange() { super.tintColorDidChange() textLabel?.textColor = tintColor } @@ -25,7 +25,7 @@ public class ButtonCell: UITableViewCell, CellType { // MARK: - Private - private func initialize() { + fileprivate func initialize() { tintColorDidChange() } } diff --git a/Static/CellType.swift b/Static/CellType.swift index 978d54f..bf03d8d 100644 --- a/Static/CellType.swift +++ b/Static/CellType.swift @@ -4,7 +4,7 @@ public protocol CellType: class { static func description() -> String static func nib() -> UINib? - func configure(row row: Row) + func configure(row: Row) } extension CellType { @@ -14,7 +14,7 @@ extension CellType { } extension CellType where Self: UITableViewCell { - public func configure(row row: Row) { + public func configure(row: Row) { textLabel?.text = row.text detailTextLabel?.text = row.detailText imageView?.image = row.image diff --git a/Static/DataSource.swift b/Static/DataSource.swift index 4af5eac..cec111c 100644 --- a/Static/DataSource.swift +++ b/Static/DataSource.swift @@ -3,12 +3,12 @@ import UIKit /// Table view data source. /// /// You should always access this object from the main thread since it talks to UIKit. -public class DataSource: NSObject { +open class DataSource: NSObject { // MARK: - Properties /// The table view that will use this object as its data source. - public weak var tableView: UITableView? { + open weak var tableView: UITableView? { willSet { if let tableView = tableView { tableView.dataSource = nil @@ -19,38 +19,38 @@ public class DataSource: NSObject { } didSet { - assert(NSThread.isMainThread(), "You must access Static.DataSource from the main thread.") + assert(Thread.isMainThread, "You must access Static.DataSource from the main thread.") updateTableView() } } /// Sections to use in the table view. - public var sections: [Section] { + open var sections: [Section] { didSet { - assert(NSThread.isMainThread(), "You must access Static.DataSource from the main thread.") + assert(Thread.isMainThread, "You must access Static.DataSource from the main thread.") refresh() } } /// Section index titles. - public var sectionIndexTitles: [String]? { + open var sectionIndexTitles: [String]? { didSet { - assert(NSThread.isMainThread(), "You must access Static.DataSource from the main thread.") + assert(Thread.isMainThread, "You must access Static.DataSource from the main thread.") tableView?.reloadData() } } /// Automatically deselect rows after they are selected - public var automaticallyDeselectRows = true + open var automaticallyDeselectRows = true - private var registeredCellIdentifiers = Set() + fileprivate var registeredCellIdentifiers = Set() // MARK: - Initializers /// Initialize with optional `tableView` and `sections`. public init(tableView: UITableView? = nil, sections: [Section]? = nil) { - assert(NSThread.isMainThread(), "You must access Static.DataSource from the main thread.") + assert(Thread.isMainThread, "You must access Static.DataSource from the main thread.") self.tableView = tableView self.sections = sections ?? [] @@ -68,27 +68,27 @@ public class DataSource: NSObject { // MARK: - Public - public func rowAtPoint(point: CGPoint) -> Row? { - guard let indexPath = tableView?.indexPathForRowAtPoint(point) else { return nil } + open func rowAtPoint(_ point: CGPoint) -> Row? { + guard let indexPath = tableView?.indexPathForRow(at: point) else { return nil } return rowForIndexPath(indexPath) } // MARK: - Private - private func updateTableView() { + fileprivate func updateTableView() { guard let tableView = tableView else { return } tableView.dataSource = self tableView.delegate = self refresh() } - private func refresh() { + fileprivate func refresh() { refreshTableSections() refreshRegisteredCells() } - private func sectionForIndex(index: Int) -> Section? { + fileprivate func sectionForIndex(_ index: Int) -> Section? { if sections.count <= index { assert(false, "Invalid section index: \(index)") return nil @@ -97,7 +97,7 @@ public class DataSource: NSObject { return sections[index] } - private func rowForIndexPath(indexPath: NSIndexPath) -> Row? { + fileprivate func rowForIndexPath(_ indexPath: IndexPath) -> Row? { if let section = sectionForIndex(indexPath.section) { let rows = section.rows if rows.count >= indexPath.row { @@ -109,7 +109,7 @@ public class DataSource: NSObject { return nil } - private func refreshTableSections(oldSections: [Section]? = nil) { + fileprivate func refreshTableSections(_ oldSections: [Section]? = nil) { guard let tableView = tableView else { return } guard let oldSections = oldSections else { tableView.reloadData() @@ -119,30 +119,30 @@ public class DataSource: NSObject { let oldCount = oldSections.count let newCount = sections.count let delta = newCount - oldCount - let animation: UITableViewRowAnimation = .Automatic + let animation: UITableViewRowAnimation = .automatic tableView.beginUpdates() if delta == 0 { - tableView.reloadSections(NSIndexSet(indexesInRange: NSMakeRange(0, newCount)), withRowAnimation: animation) + tableView.reloadSections(IndexSet(integersIn: NSMakeRange(0, newCount).toRange()!), with: animation) } else { if delta > 0 { // Insert sections - tableView.insertSections(NSIndexSet(indexesInRange: NSMakeRange(oldCount - 1, delta)), withRowAnimation: animation) + tableView.insertSections(IndexSet(integersIn: NSMakeRange(oldCount - 1, delta).toRange() ?? 0..<0), with: animation) } else { // Remove sections - tableView.deleteSections(NSIndexSet(indexesInRange: NSMakeRange(oldCount - 1, -delta)), withRowAnimation: animation) + tableView.deleteSections(IndexSet(integersIn: NSMakeRange(oldCount - 1, -delta).toRange() ?? 0..<0), with: animation) } // Reload existing sections let commonCount = min(oldCount, newCount) - tableView.reloadSections(NSIndexSet(indexesInRange: NSMakeRange(0, commonCount)), withRowAnimation: animation) + tableView.reloadSections(IndexSet(integersIn: NSMakeRange(0, commonCount).toRange()!), with: animation) } tableView.endUpdates() } - private func refreshRegisteredCells() { + fileprivate func refreshRegisteredCells() { // A table view is required to manipulate registered cells guard let tableView = tableView else { return } @@ -159,9 +159,9 @@ public class DataSource: NSObject { registeredCellIdentifiers.insert(identifier) if let nib = row.cellClass.nib() { - tableView.registerNib(nib, forCellReuseIdentifier: identifier) + tableView.register(nib, forCellReuseIdentifier: identifier) } else { - tableView.registerClass(row.cellClass, forCellReuseIdentifier: identifier) + tableView.register(row.cellClass, forCellReuseIdentifier: identifier) } } } @@ -169,13 +169,13 @@ public class DataSource: NSObject { extension DataSource: UITableViewDataSource { - public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sectionForIndex(section)?.rows.count ?? 0 } - public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if let row = rowForIndexPath(indexPath) { - let tableCell = tableView.dequeueReusableCellWithIdentifier(row.cellIdentifier, forIndexPath: indexPath) + let tableCell = tableView.dequeueReusableCell(withIdentifier: row.cellIdentifier, for: indexPath) if let cell = tableCell as? CellType { cell.configure(row: row) @@ -187,39 +187,39 @@ extension DataSource: UITableViewDataSource { return UITableViewCell() } - public func numberOfSectionsInTableView(tableView: UITableView) -> Int { + public func numberOfSections(in tableView: UITableView) -> Int { return sections.count } - public func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return sectionForIndex(section)?.header?.title } - public func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return sectionForIndex(section)?.header?.view } - public func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return sectionForIndex(section)?.header?.viewHeight ?? UITableViewAutomaticDimension } - public func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? { + public func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { return sectionForIndex(section)?.footer?.title } - public func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return sectionForIndex(section)?.footer?.view } - public func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return sectionForIndex(section)?.footer?.viewHeight ?? UITableViewAutomaticDimension } - public func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { + public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { return rowForIndexPath(indexPath)?.canEdit ?? false } - public func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? { + public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { return rowForIndexPath(indexPath)?.editActions.map { action in let rowAction = UITableViewRowAction(style: action.style, title: action.title) { (_, _) in @@ -241,14 +241,14 @@ extension DataSource: UITableViewDataSource { } } - public func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? { - guard let sectionIndexTitles = sectionIndexTitles where sectionIndexTitles.count >= sections.count else { return nil } + public func sectionIndexTitles(for tableView: UITableView) -> [String]? { + guard let sectionIndexTitles = sectionIndexTitles, sectionIndexTitles.count >= sections.count else { return nil } return sectionIndexTitles } - public func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int { - for (i, section) in sections.enumerate() { - if let indexTitle = section.indexTitle where indexTitle == title { + public func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { + for (i, section) in sections.enumerated() { + if let indexTitle = section.indexTitle, indexTitle == title { return i } } @@ -258,13 +258,13 @@ extension DataSource: UITableViewDataSource { extension DataSource: UITableViewDelegate { - public func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool { + public func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { return rowForIndexPath(indexPath)?.isSelectable ?? false } - public func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if automaticallyDeselectRows { - tableView.deselectRowAtIndexPath(indexPath, animated: true) + tableView.deselectRow(at: indexPath, animated: true) } if let row = rowForIndexPath(indexPath) { @@ -272,22 +272,22 @@ extension DataSource: UITableViewDelegate { } } - public func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) { + public func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) { if let row = rowForIndexPath(indexPath) { row.accessory.selection?() } } - public func tableView(tableView: UITableView, shouldShowMenuForRowAtIndexPath indexPath: NSIndexPath) -> Bool { + public func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool { return rowForIndexPath(indexPath)?.canCopy ?? false } - public func tableView(tableView: UITableView, canPerformAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) -> Bool { + public func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool { return action == #selector(UIResponder.copy(_:)) && (rowForIndexPath(indexPath)?.canCopy ?? false) } - public func tableView(tableView: UITableView, performAction action: Selector, forRowAtIndexPath indexPath: NSIndexPath, withSender sender: AnyObject?) { - if let row = rowForIndexPath(indexPath) where action == #selector(UIResponder.copy(_:)) { + public func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) { + if let row = rowForIndexPath(indexPath), action == #selector(UIResponder.copy(_:)) { row.copyAction?(row) } } diff --git a/Static/Row.swift b/Static/Row.swift index 4712e51..0f4de4c 100644 --- a/Static/Row.swift +++ b/Static/Row.swift @@ -14,19 +14,19 @@ public struct Row: Hashable, Equatable { /// Representation of a row accessory. public enum Accessory: Equatable { /// No accessory - case None + case none /// Chevron - case DisclosureIndicator + case disclosureIndicator /// Info button with chevron. Handles selection. - case DetailDisclosureButton(Selection) + case detailDisclosureButton(Selection) /// Checkmark - case Checkmark + case checkmark /// Info button. Handles selection. - case DetailButton(Selection) + case detailButton(Selection) /// Custom view case View(UIView) @@ -34,18 +34,18 @@ public struct Row: Hashable, Equatable { /// Table view cell accessory type public var type: UITableViewCellAccessoryType { switch self { - case DisclosureIndicator: return .DisclosureIndicator - case DetailDisclosureButton(_): return .DetailDisclosureButton - case Checkmark: return .Checkmark - case DetailButton(_): return .DetailButton - default: return .None + case .disclosureIndicator: return .disclosureIndicator + case .detailDisclosureButton(_): return .detailDisclosureButton + case .checkmark: return .checkmark + case .detailButton(_): return .detailButton + default: return .none } } /// Accessory view public var view: UIView? { switch self { - case View(let view): return view + case .View(let view): return view default: return nil } } @@ -53,8 +53,8 @@ public struct Row: Hashable, Equatable { /// Selection block for accessory buttons public var selection: Selection? { switch self { - case DetailDisclosureButton(let selection): return selection - case DetailButton(let selection): return selection + case .detailDisclosureButton(let selection): return selection + case .detailButton(let selection): return selection default: return nil } } @@ -79,7 +79,7 @@ public struct Row: Hashable, Equatable { /// Invoked when selecting the action. public let selection: Selection? - public init(title: String, style: UITableViewRowActionStyle = .Default, backgroundColor: UIColor? = nil, backgroundEffect: UIVisualEffect? = nil, selection: Selection? = nil) { + public init(title: String, style: UITableViewRowActionStyle = .default, backgroundColor: UIColor? = nil, backgroundEffect: UIVisualEffect? = nil, selection: Selection? = nil) { self.title = title self.style = style self.backgroundColor = backgroundColor @@ -144,7 +144,7 @@ public struct Row: Hashable, Equatable { // MARK: - Initializers public init(text: String? = nil, detailText: String? = nil, selection: Selection? = nil, - image: UIImage? = nil, accessory: Accessory = .None, cellClass: CellType.Type? = nil, context: Context? = nil, editActions: [EditAction] = [], copyAction: CopyAction? = nil, UUID: String = NSUUID().UUIDString) { + image: UIImage? = nil, accessory: Accessory = .none, cellClass: CellType.Type? = nil, context: Context? = nil, editActions: [EditAction] = [], copyAction: CopyAction? = nil, UUID: String = Foundation.UUID().uuidString) { self.UUID = UUID self.text = text @@ -167,11 +167,11 @@ public func ==(lhs: Row, rhs: Row) -> Bool { public func ==(lhs: Row.Accessory, rhs: Row.Accessory) -> Bool { switch (lhs, rhs) { - case (.None, .None): return true - case (.DisclosureIndicator, .DisclosureIndicator): return true - case (.DetailDisclosureButton(_), .DetailDisclosureButton(_)): return true - case (.Checkmark, .Checkmark): return true - case (.DetailButton(_), .DetailButton(_)): return true + case (.none, .none): return true + case (.disclosureIndicator, .disclosureIndicator): return true + case (.detailDisclosureButton(_), .detailDisclosureButton(_)): return true + case (.checkmark, .checkmark): return true + case (.detailButton(_), .detailButton(_)): return true case (.View(let l), .View(let r)): return l == r default: return false } diff --git a/Static/Section.swift b/Static/Section.swift index cb6d69c..05bc058 100644 --- a/Static/Section.swift +++ b/Static/Section.swift @@ -57,7 +57,7 @@ public struct Section: Hashable, Equatable { // MARK: - Initiailizers - public init(header: Extremity? = nil, rows: [Row] = [], footer: Extremity? = nil, indexTitle: String? = nil, UUID: String = NSUUID().UUIDString) { + public init(header: Extremity? = nil, rows: [Row] = [], footer: Extremity? = nil, indexTitle: String? = nil, UUID: String = Foundation.UUID().uuidString) { self.UUID = UUID self.header = header self.rows = rows @@ -67,7 +67,7 @@ public struct Section: Hashable, Equatable { } -extension Section.Extremity: StringLiteralConvertible { +extension Section.Extremity: ExpressibleByStringLiteral { public typealias UnicodeScalarLiteralType = StringLiteralType public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType diff --git a/Static/SubtitleCell.swift b/Static/SubtitleCell.swift index e7f80d5..33f6228 100644 --- a/Static/SubtitleCell.swift +++ b/Static/SubtitleCell.swift @@ -1,8 +1,8 @@ import UIKit -public class SubtitleCell: UITableViewCell, CellType { +open class SubtitleCell: UITableViewCell, CellType { public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - super.init(style: .Subtitle, reuseIdentifier: reuseIdentifier) + super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) } public required init?(coder aDecoder: NSCoder) { diff --git a/Static/TableViewController.swift b/Static/TableViewController.swift index f816ac0..52533f7 100644 --- a/Static/TableViewController.swift +++ b/Static/TableViewController.swift @@ -1,20 +1,24 @@ import UIKit /// Table view controller with a `DataSource` setup to use its `tableView`. -public class TableViewController: UIViewController { +open class TableViewController: UIViewController { + + private lazy var __once: () = { [weak self] in + self?.tableView.reloadData() + }() // MARK: - Properties /// Returns the table view managed by the controller object. - public let tableView: UITableView + open let tableView: UITableView /// A Boolean value indicating if the controller clears the selection when the table appears. /// /// The default value of this property is true. When true, the table view controller clears the table’s current selection when it receives a viewWillAppear: message. Setting this property to false preserves the selection. - public var clearsSelectionOnViewWillAppear: Bool = true + open var clearsSelectionOnViewWillAppear: Bool = true /// Table view data source. - public var dataSource = DataSource() { + open var dataSource = DataSource() { willSet { dataSource.tableView = nil } @@ -33,8 +37,8 @@ public class TableViewController: UIViewController { dataSource.tableView = tableView } - public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { - tableView = UITableView(frame: .zero, style: .Plain) + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + tableView = UITableView(frame: .zero, style: .plain) super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) dataSource.tableView = tableView } @@ -44,29 +48,29 @@ public class TableViewController: UIViewController { } public convenience init() { - self.init(style: .Plain) + self.init(style: .plain) } // MARK: - UIViewController - public override func loadView() { - tableView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] + open override func loadView() { + tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view = tableView } - public override func viewWillAppear(animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) performInitialLoad() clearSelectionsIfNecessary(animated) } - public override func viewDidAppear(animated: Bool) { + open override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) tableView.flashScrollIndicators() } - public override func setEditing(editing: Bool, animated: Bool) { + open override func setEditing(_ editing: Bool, animated: Bool) { super.setEditing(editing, animated: animated) tableView.setEditing(editing, animated: animated) } @@ -74,38 +78,36 @@ public class TableViewController: UIViewController { // MARK: - Private - private var initialLoadOnceToken = dispatch_once_t() - private func performInitialLoad() { - dispatch_once(&initialLoadOnceToken) { - self.tableView.reloadData() - } + fileprivate var initialLoadOnceToken = Int() + fileprivate func performInitialLoad() { + _ = self.__once } - private func clearSelectionsIfNecessary(animated: Bool) { - guard let selectedIndexPaths = tableView.indexPathsForSelectedRows where clearsSelectionOnViewWillAppear else { return } - guard let coordinator = transitionCoordinator() else { + fileprivate func clearSelectionsIfNecessary(_ animated: Bool) { + guard let selectedIndexPaths = tableView.indexPathsForSelectedRows, clearsSelectionOnViewWillAppear else { return } + guard let coordinator = transitionCoordinator else { deselectRowsAtIndexPaths(selectedIndexPaths, animated: animated) return } - let animation: UIViewControllerTransitionCoordinatorContext -> Void = { [weak self] _ in + let animation: (UIViewControllerTransitionCoordinatorContext) -> Void = { [weak self] _ in self?.deselectRowsAtIndexPaths(selectedIndexPaths, animated: animated) } - let completion: UIViewControllerTransitionCoordinatorContext -> Void = { [weak self] context in - if context.isCancelled() { + let completion: (UIViewControllerTransitionCoordinatorContext) -> Void = { [weak self] context in + if context.isCancelled { self?.selectRowsAtIndexPaths(selectedIndexPaths, animated: animated) } } - coordinator.animateAlongsideTransition(animation, completion: completion) + coordinator.animate(alongsideTransition: animation, completion: completion) } - private func selectRowsAtIndexPaths(indexPaths: [NSIndexPath], animated: Bool) { - indexPaths.forEach { tableView.selectRowAtIndexPath($0, animated: animated, scrollPosition: .None) } + fileprivate func selectRowsAtIndexPaths(_ indexPaths: [IndexPath], animated: Bool) { + indexPaths.forEach { tableView.selectRow(at: $0, animated: animated, scrollPosition: .none) } } - private func deselectRowsAtIndexPaths(indexPaths: [NSIndexPath], animated: Bool) { - indexPaths.forEach { tableView.deselectRowAtIndexPath($0, animated: animated) } + fileprivate func deselectRowsAtIndexPaths(_ indexPaths: [IndexPath], animated: Bool) { + indexPaths.forEach { tableView.deselectRow(at: $0, animated: animated) } } } diff --git a/Static/Value1Cell.swift b/Static/Value1Cell.swift index ffc17d9..5fb25ec 100644 --- a/Static/Value1Cell.swift +++ b/Static/Value1Cell.swift @@ -1,8 +1,8 @@ import UIKit -public class Value1Cell: UITableViewCell, CellType { +open class Value1Cell: UITableViewCell, CellType { public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - super.init(style: .Value1, reuseIdentifier: reuseIdentifier) + super.init(style: .value1, reuseIdentifier: reuseIdentifier) } public required init?(coder aDecoder: NSCoder) { diff --git a/Static/Value2Cell.swift b/Static/Value2Cell.swift index 198c700..56b8285 100644 --- a/Static/Value2Cell.swift +++ b/Static/Value2Cell.swift @@ -1,8 +1,8 @@ import UIKit -public class Value2Cell: UITableViewCell, CellType { +open class Value2Cell: UITableViewCell, CellType { public override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - super.init(style: .Value2, reuseIdentifier: reuseIdentifier) + super.init(style: .value2, reuseIdentifier: reuseIdentifier) } public required init?(coder aDecoder: NSCoder) { From 272c11ba3c512129c11f7a7c82ee1e5ad5b4e3b3 Mon Sep 17 00:00:00 2001 From: Tom Kraina Date: Fri, 31 Mar 2017 21:34:13 +0200 Subject: [PATCH 7/9] Fix crashing bug when running UI tests in Simulator with iOS 10 and above https://openradar.appspot.com/31375101 --- Static.podspec | 4 ++-- Static/DataSource.swift | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Static.podspec b/Static.podspec index 5ce901a..de4f1ea 100644 --- a/Static.podspec +++ b/Static.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |spec| spec.name = 'Static' - spec.version = '1.2.0.2' + spec.version = '3.0.0.1' 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' spec.license = { type: 'MIT', file: 'LICENSE' } spec.source = { git: 'https://github.com/venmo/Static.git', tag: "v#{spec.version}" } - spec.author = { 'Venmo' => 'ios@venmo.com', 'Sam Soffes' => 'sam@soff.es' } + spec.author = { 'Venmo' => 'ios@venmo.com', 'Sam Soffes' => 'sam@soff.es', 'Tom Kraina' => 'me@tomkraina.com' } spec.platform = :ios, '8.0' spec.frameworks = 'UIKit' diff --git a/Static/DataSource.swift b/Static/DataSource.swift index cec111c..c7c8d0e 100644 --- a/Static/DataSource.swift +++ b/Static/DataSource.swift @@ -282,7 +282,10 @@ extension DataSource: UITableViewDelegate { return rowForIndexPath(indexPath)?.canCopy ?? false } - public func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool { + + // The parameter indexPath: IndexPath? is optinal for a purpose. See: https://openradar.appspot.com/31375101 + public func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath?, withSender sender: Any?) -> Bool { + guard let indexPath = indexPath else { return false } return action == #selector(UIResponder.copy(_:)) && (rowForIndexPath(indexPath)?.canCopy ?? false) } From c7a128163b98501bd0b19865fac3918ee8446c9a Mon Sep 17 00:00:00 2001 From: Tom Kraina Date: Thu, 14 Dec 2017 16:33:15 +0100 Subject: [PATCH 8/9] =?UTF-8?q?Remove=20workaround=20to=20avoid=20crash=20?= =?UTF-8?q?as=20it=E2=80=99s=20no=20longer=20needed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Static/DataSource.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Static/DataSource.swift b/Static/DataSource.swift index 5a74363..b5d1199 100644 --- a/Static/DataSource.swift +++ b/Static/DataSource.swift @@ -309,10 +309,7 @@ extension DataSource: UITableViewDelegate { return row(at: indexPath)?.canCopy ?? false } - - // The parameter indexPath: IndexPath? is optinal for a purpose. See: https://openradar.appspot.com/31375101 - public func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath?, withSender sender: Any?) -> Bool { - guard let indexPath = indexPath else { return false } + public func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool { return action == #selector(UIResponder.copy(_:)) && (row(at: indexPath)?.canCopy ?? false) } From 4605861671f2b2fc90e07823b5f69df57b41d400 Mon Sep 17 00:00:00 2001 From: Tom Kraina Date: Thu, 14 Dec 2017 16:33:31 +0100 Subject: [PATCH 9/9] Use self sizing cells in example --- Example/NibTableViewCell.xib | 18 +++++++++++------- Example/ViewController.swift | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Example/NibTableViewCell.xib b/Example/NibTableViewCell.xib index cd832b5..998c327 100644 --- a/Example/NibTableViewCell.xib +++ b/Example/NibTableViewCell.xib @@ -1,7 +1,12 @@ - - + + + + + - + + + @@ -14,20 +19,19 @@ - + + - diff --git a/Example/ViewController.swift b/Example/ViewController.swift index fd654a9..c239ad7 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -26,7 +26,7 @@ class ViewController: TableViewController { title = "Static" - tableView.rowHeight = 50 + tableView.estimatedRowHeight = 60 // Note: // Required to be set pre iOS11, to support autosizing