diff --git a/README.md b/README.md
index 334ab6d..1fe3a73 100644
--- a/README.md
+++ b/README.md
@@ -104,28 +104,34 @@ class ViewController: UIViewController {
****
-# oneleif Project
+## oneleif project
+This means that the project is sponsored by the oneleif community, and the collaborators are team members from oneleif.
-![](https://github.com/oneleif/olDocs/blob/master/assets/images/oneleif_logos/full_logo/oneleif_whiteback.png)
+![](https://github.com/oneleif/olDocs/blob/master/assets/images/oneleif_logos/full_logo/oneleif_whiteback.png)
-### Project Info
-This project is a oneleif active project.
[![](https://img.shields.io/badge/oneleif-Twitter-blue.svg)](https://twitter.com/oneleifdev)
[![](https://img.shields.io/badge/oneleif-YouTube-red.svg)](https://www.youtube.com/channel/UC3HN0jID38K0Vb_WChvgQmA)
+## What is oneleif?
+oneleif is a nonprofit community that supports tech minded individuals. We do this by offering a fun loving community that works on Open Sourced projects together.
+We love to give back through free resources and guidance.
+
## How to join oneleif
Click on the link below to join the Discord server.
-You will start with limited permissions, in a text channel that only moderators will see.
+[![](https://img.shields.io/badge/oneleif-Discord-7284be.svg)](https://discord.gg/tv9UdJK)
-To get full access: read the rules, make an introduction in #introductions, and add an appropriate username.
+-OR-
-When you're done with the above, shoot a message to the #start channel to let us know, and we will give you full access.
+[Check out our website](http://oneleif.com)
-[![](https://img.shields.io/badge/oneleif-Discord-7284be.svg)](https://discord.gg/tv9UdJK)
### Questions?
Feel free to email us at: oneleifdev@gmail.com
+
+-OR-
+
+Ask questions in the discord
diff --git a/Sources/SwiftUIKit/Extensions/NSLayoutConstraint+SwiftUIKit.swift b/Sources/SwiftUIKit/Extensions/NSLayoutConstraint+SwiftUIKit.swift
new file mode 100644
index 0000000..d1934b1
--- /dev/null
+++ b/Sources/SwiftUIKit/Extensions/NSLayoutConstraint+SwiftUIKit.swift
@@ -0,0 +1,49 @@
+//
+// NSLayoutConstraint+SwiftUIKit.swift
+// SwiftUIKit
+//
+// Created by Zach Eriksen on 5/17/20.
+//
+
+import UIKit
+
+@available(iOS 10.0, *)
+public extension NSLayoutConstraint {
+
+ /// Check if the `constraint` is connected to the `anchor`
+ func isConnected(toAnchor anchor: NSLayoutAnchor) -> Bool {
+ firstAnchor == anchor || secondAnchor == anchor
+ }
+}
+
+@available(iOS 10.0, *)
+public extension UIView {
+
+ /// The leading constraints held by the view
+ var leadingConstraints: [NSLayoutConstraint] {
+ constraints.filter { (constraint) -> Bool in
+ constraint.isConnected(toAnchor: leadingAnchor)
+ }
+ }
+
+ /// The trailing constraints held by the view
+ var trailingConstraints: [NSLayoutConstraint] {
+ constraints.filter { (constraint) -> Bool in
+ constraint.isConnected(toAnchor: trailingAnchor)
+ }
+ }
+
+ /// The top constraints held by the view
+ var topConstraints: [NSLayoutConstraint] {
+ constraints.filter { (constraint) -> Bool in
+ constraint.isConnected(toAnchor: topAnchor)
+ }
+ }
+
+ /// The bottom constraints held by the view
+ var bottomConstraints: [NSLayoutConstraint] {
+ constraints.filter { (constraint) -> Bool in
+ constraint.isConnected(toAnchor: bottomAnchor)
+ }
+ }
+}
diff --git a/Sources/SwiftUIKit/Extensions/UIView+SwiftUIKit.swift b/Sources/SwiftUIKit/Extensions/UIView+SwiftUIKit.swift
index 72fa5c4..3289540 100644
--- a/Sources/SwiftUIKit/Extensions/UIView+SwiftUIKit.swift
+++ b/Sources/SwiftUIKit/Extensions/UIView+SwiftUIKit.swift
@@ -292,6 +292,48 @@ public extension UIView {
return self
}
+ /// Update a padding anchor's constant value
+ @available(iOS 10.0, *)
+ @discardableResult
+ func update(padding: Padding) -> Self {
+ switch padding {
+ case .top(let value):
+ topConstraints.first?.constant = CGFloat(value)
+ case .bottom(let value):
+ bottomConstraints.first?.constant = CGFloat(-value)
+ case .leading(let value):
+ leadingConstraints.first?.constant = CGFloat(value)
+ case .trailing(let value):
+ trailingConstraints.first?.constant = CGFloat(-value)
+ }
+
+ return self
+ }
+
+ /// Update an array of padding anchors' constant values
+ @available(iOS 10.0, *)
+ @discardableResult
+ func update(padding: [Padding]) -> Self {
+ padding.forEach { update(padding: $0) }
+
+ return self
+ }
+
+ /// Update all padding anchors' constant value
+ @available(iOS 10.0, *)
+ @discardableResult
+ func update(padding: Float) -> Self {
+ update(padding: [
+ .top(padding),
+ .bottom(padding),
+ .leading(padding),
+ .trailing(padding)
+ ])
+
+ return self
+ }
+
+
/// Remove the height anchor constraint
@available(iOS 10.0, *)
@discardableResult
@@ -432,7 +474,7 @@ public extension UIView {
/// - animated: Should animate setting the left UIBarButtonItem
@discardableResult
func navigateSetLeft(barButton: UIBarButtonItem?, animated: Bool = true) -> Self {
- Navigate.shared.setLeft(barButton: barButton)
+ Navigate.shared.setLeft(barButton: barButton, animated: animated)
return self
}
@@ -443,7 +485,7 @@ public extension UIView {
/// - animated: Should animate setting the right UIBarButtonItem
@discardableResult
func navigateSetRight(barButton: UIBarButtonItem?, animated: Bool = true) -> Self {
- Navigate.shared.setRight(barButton: barButton)
+ Navigate.shared.setRight(barButton: barButton, animated: animated)
return self
}
@@ -454,7 +496,7 @@ public extension UIView {
/// - animated: Should animate setting the left [UIBarButtonItem]
@discardableResult
func navigateSetLeft(barButtons: [UIBarButtonItem]?, animated: Bool = true) -> Self {
- Navigate.shared.setLeft(barButtons: barButtons)
+ Navigate.shared.setLeft(barButtons: barButtons, animated: animated)
return self
}
@@ -465,7 +507,7 @@ public extension UIView {
/// - animated: Should animate setting the right [UIBarButtonItem]
@discardableResult
func navigateSetRight(barButtons: [UIBarButtonItem]?, animated: Bool = true) -> Self {
- Navigate.shared.setRight(barButtons: barButtons)
+ Navigate.shared.setRight(barButtons: barButtons, animated: animated)
return self
}
diff --git a/Sources/SwiftUIKit/Views/CollectionView.swift b/Sources/SwiftUIKit/Views/CollectionView.swift
new file mode 100644
index 0000000..2f5014f
--- /dev/null
+++ b/Sources/SwiftUIKit/Views/CollectionView.swift
@@ -0,0 +1,660 @@
+//
+// Collection.swift
+// SwiftUIKit
+//
+// Created by Oskar on 16/05/2020.
+//
+
+import UIKit
+
+@available(iOS 11.0, *)
+public typealias CollectionViewCell = DataIdentifiable & CellConfigurable & CellUpdatable & UICollectionViewCell
+
+@available(iOS 11, *)
+public class CollectionView: UICollectionView {
+ public var data: [[CellDisplayable]]
+
+ fileprivate var titles: [String]? = nil
+ fileprivate var sectionsInsets: [UIEdgeInsets]? = nil
+ fileprivate var footerSizeForSections: [CGSize]? = nil
+ fileprivate var headerSizesForSections: [CGSize]? = nil
+ fileprivate var minimumLineSpacingForSections: [CGFloat]? = nil
+ fileprivate var minimumInteritemSpacingForSections: [CGFloat]? = nil
+
+ fileprivate var shouldSelectItemAtHandler: ((IndexPath) -> Bool)? = nil
+ fileprivate var didSelectItemAtHandler: ((IndexPath) -> ())? = nil
+ fileprivate var shouldDeselectItemAtHandler: ((IndexPath) -> Bool)? = nil
+ fileprivate var didDeselectItemAtHandler: ((IndexPath) -> ())? = nil
+ fileprivate var shouldHighlightItemAtHandler: ((IndexPath) -> Bool)? = nil
+ fileprivate var didHighlightItemAtHandler: ((IndexPath) -> ())? = nil
+ fileprivate var didUnhighlightItemAtHandler: ((IndexPath) -> ())? = nil
+ fileprivate var canFocusItemAtHandler: ((IndexPath) -> Bool)? = nil
+
+ fileprivate var willDisplayCellHandler: ((CollectionViewCell, IndexPath) -> ())? = nil
+ fileprivate var didEndDisplayingCell: ((CollectionViewCell, IndexPath) -> ())? = nil
+
+ fileprivate var numberOfItemsInSectionHandler: ((UICollectionView, Int) -> Int)? = nil
+ fileprivate var cellForItemAtHandler: ((UICollectionView, IndexPath) -> (UICollectionViewCell))? = nil
+ fileprivate var targetIndexPathForMoveHandler: ((_ from: IndexPath, _ to: IndexPath) -> IndexPath)? = nil
+ fileprivate var targetContentOffsetForHandler: ((_ proposed: CGPoint) -> CGPoint)? = nil
+ fileprivate var layoutSizeForItemHandler: ((UICollectionViewLayout, IndexPath) -> CGSize)? = nil
+ fileprivate var transitionLayoutForHandler: ((_ old: UICollectionViewLayout, _ new: UICollectionViewLayout) -> UICollectionViewTransitionLayout)? = nil
+
+ fileprivate var didBeginMultipleSelectionInteractionAtHandler: ((IndexPath) -> ())? = nil
+ fileprivate var didEndMultipleSelectionInteractionHandler: ((UICollectionView) -> ())? = nil
+ fileprivate var shouldBeginMultipleSelectionInteractionAtHandler: ((IndexPath) -> Bool)? = nil
+
+ public init(initialData: [[CellDisplayable]] = [[CellDisplayable]]()) {
+ data = initialData
+
+ let layout = UICollectionViewFlowLayout()
+ layout.estimatedItemSize = .zero
+
+ super.init(frame: .zero, collectionViewLayout: layout)
+
+ delegate = self
+ dataSource = self
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+// MARK: - Auxiliary modifiers
+@available(iOS 11, *)
+public extension CollectionView {
+ /// Set a new delegate for CollectionView.
+ /// All modifiers depending on delegate will do nothing after calling that.
+ @discardableResult
+ func set(delegateTo delegate: UICollectionViewDelegate) -> Self {
+ self.delegate = delegate
+
+ return self
+ }
+
+ /// Set a new delegate for CollectionView.
+ /// All modifiers depending on data source will do nothing after calling that.
+ @discardableResult
+ func set(dataSourceTo dataSource: UICollectionViewDataSource) -> Self {
+ self.dataSource = dataSource
+
+ return self
+ }
+}
+
+// MARK: - Update data and layout
+@available(iOS 11.0, *)
+public extension CollectionView {
+ /// Set a new layout for collection view.
+ @discardableResult
+ func set(layout: UICollectionViewLayout) -> Self {
+ setCollectionViewLayout(layout, animated: false)
+
+ return self
+ }
+
+ @discardableResult
+ func update(shouldReloadData: Bool = false,
+ _ closure: ([[CellDisplayable]]) -> [[CellDisplayable]]) -> Self {
+ data = closure(data)
+
+ if shouldReloadData {
+ reloadData()
+ }
+
+ return self
+ }
+
+ @discardableResult
+ func append(shouldReloadData: Bool = false,
+ _ closure: () -> [[CellDisplayable]]) -> Self {
+ data += closure()
+
+ if shouldReloadData {
+ reloadData()
+ }
+
+ return self
+ }
+}
+
+// MARK: - Prefetching
+@available(iOS 11, *)
+public extension CollectionView {
+ @discardableResult
+ func prefetchingEnabled(_ bool: Bool) -> Self {
+ isPrefetchingEnabled = bool
+
+ return self
+ }
+
+ @discardableResult
+ func prefetchDataSource(_ prefetchDataSource: () -> (UICollectionViewDataSourcePrefetching)) -> Self {
+ self.prefetchDataSource = prefetchDataSource()
+
+ return self
+ }
+}
+
+// MARK: - Creating Collection Cells
+@available(iOS 11, *)
+public extension CollectionView {
+ @discardableResult
+ func register(cells: [CollectionViewCell.Type]) -> Self {
+ cells.forEach {
+ super.register($0, forCellWithReuseIdentifier: $0.ID)
+ }
+
+ return self
+ }
+}
+
+// MARK: - Items management
+@available(iOS 11, *)
+public extension CollectionView {
+ @discardableResult
+ func insertItems(at indexPaths: [IndexPath]) -> Self {
+ super.insertItems(at: indexPaths)
+
+ return self
+ }
+
+ @discardableResult
+ func insertItem(at indexPath: IndexPath) -> Self {
+ super.insertItems(at: [indexPath])
+
+ return self
+ }
+
+ @discardableResult
+ func moveItem(at sourceIndexPath: IndexPath, to targetIndexPath: IndexPath) -> Self {
+ super.moveItem(at: sourceIndexPath, to: targetIndexPath)
+
+ return self
+ }
+
+ @discardableResult
+ func moveItems(at sourceIndexPaths: [IndexPath], to targetIndexPaths: [IndexPath]) -> Self {
+ for (source, target) in zip(sourceIndexPaths, targetIndexPaths) {
+ super.moveItem(at: source, to: target)
+ }
+
+ return self
+ }
+
+ @discardableResult
+ func deleteItem(at indexPath: IndexPath) -> Self {
+ super.deleteItems(at: [indexPath])
+
+ return self
+ }
+
+ @discardableResult
+ func deleteItems(at indexPaths: [IndexPath]) -> Self {
+ super.deleteItems(at: indexPaths)
+
+ return self
+ }
+
+ @discardableResult
+ func scrollToItem(at indexPath: IndexPath, at position: UICollectionView.ScrollPosition, animated: Bool = true) -> Self {
+ super.scrollToItem(at: indexPath, at: position, animated: animated)
+
+ return self
+ }
+
+ @discardableResult
+ func allowSelection(_ bool: Bool, multiple: Bool = false) -> Self {
+ allowsSelection = bool
+ allowsMultipleSelection = multiple
+
+ return self
+ }
+
+ @discardableResult
+ func selectItem(at indexPath: IndexPath, animated: Bool, scrollPosition: UICollectionView.ScrollPosition) -> Self {
+ super.selectItem(at: indexPath, animated: animated, scrollPosition: scrollPosition)
+
+ return self
+ }
+
+ @discardableResult
+ func deselectItem(at indexPath: IndexPath, animated: Bool) -> Self {
+ super.deselectItem(at: indexPath, animated: animated)
+
+ return self
+ }
+}
+
+// MARK: - Section Management
+@available(iOS 11, *)
+public extension CollectionView {
+ @discardableResult
+ func insertSections(at indexSet: IndexSet) -> Self {
+ super.insertSections(indexSet)
+
+ return self
+ }
+
+ @discardableResult
+ func moveSection(from section: Int, to newSection: Int) -> Self {
+ super.moveSection(section, toSection: newSection)
+
+ return self
+ }
+
+ @discardableResult
+ func deleteSections(at sections: IndexSet) -> Self {
+ super.deleteSections(sections)
+
+ return self
+ }
+}
+
+
+// MARK: - Appearance customization
+@available(iOS 11, *)
+public extension CollectionView {
+ @discardableResult
+ func set(backgroundView
+ : UIView) -> Self {
+ self.backgroundView = backgroundView
+
+ return self
+ }
+}
+
+// MARK: - Data Source modifiers
+@available(iOS 11, *)
+public extension CollectionView {
+ /// - Parameter handler: Contains actually declared collection view and given section, expects to return number of items for given section.
+ @discardableResult
+ func setNumberOfItemsInSection(handler: @escaping ((UICollectionView, Int) -> Int)) -> Self {
+ numberOfItemsInSectionHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func configureCell(handler: @escaping ((UICollectionView, IndexPath) -> UICollectionViewCell)) -> Self {
+ cellForItemAtHandler = handler
+
+ return self
+ }
+
+ /// Set index titles for CollectionView
+ /// - Parameter array: Used to provide titles for Collection View, order used is the same as the order of parameter.
+ @discardableResult
+ func setSectionTitles(shouldReloadData: Bool = false, to array: [String]) -> Self {
+ titles = array
+
+ if shouldReloadData {
+ reloadData()
+ }
+
+ return self
+ }
+}
+
+// MARK: - Auxiliary Data Source
+@available(iOS 11, *)
+extension CollectionView: UICollectionViewDataSource {
+ public func numberOfSections(in collectionView: UICollectionView) -> Int {
+ return data.count
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ return data[section].count
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ let cellData = data[indexPath.section][indexPath.row]
+
+ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellData.cellID, for: indexPath)
+
+ if let configure = cell as? CellUpdatable {
+ configure.update(forData: cellData)
+ }
+
+ guard cell.contentView.allSubviews.count == 0 else {
+ return cell
+ }
+
+ if let configure = cell as? CellConfigurable {
+ configure.configure(forData: cellData)
+ }
+
+ return cell
+ }
+
+ public func indexTitles(for collectionView: UICollectionView) -> [String]? {
+ return titles
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, indexPathForIndexTitle title: String, at index: Int) -> IndexPath {
+ IndexPath(index: index)
+ }
+}
+
+// MARK: - Delegation modifiers
+@available(iOS 11, *)
+public extension CollectionView {
+ @discardableResult
+ func shouldSelectItem(_ handler: @escaping ((IndexPath) -> Bool)) -> Self {
+ shouldSelectItemAtHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didSelectItemAt(_ handler: @escaping ((IndexPath) -> ())) -> Self {
+ didSelectItemAtHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func shouldDeselectItemAt(_ handler: @escaping ((IndexPath) -> Bool)) -> Self {
+ shouldDeselectItemAtHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didDeselectItemAt(_ handler: @escaping ((IndexPath) -> ())) -> Self {
+ didDeselectItemAtHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func shouldBeginMultipleSelectionInteractionAt(_ handler: @escaping ((IndexPath) -> Bool)) -> Self {
+ shouldBeginMultipleSelectionInteractionAtHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didBeginMultipleSelectionInteractionAt(_ handler: @escaping ((IndexPath) -> ())) -> Self {
+ didBeginMultipleSelectionInteractionAtHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didEndMultipleSelectionSelectionInteraction(_ handler: @escaping ((UICollectionView) -> ())) -> Self {
+ didEndMultipleSelectionInteractionHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func shouldHighlightItemAt(_ handler: @escaping ((IndexPath) -> Bool)) -> Self {
+ shouldHighlightItemAtHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didHighlightItemAt(_ handler: @escaping ((IndexPath) -> ())) -> Self {
+ didHighlightItemAtHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didUnhighlightItemAt(_ handler: @escaping ((IndexPath) -> ())) -> Self {
+ didUnhighlightItemAtHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func willDisplayCellHandlerFor(_ handler: @escaping ((CollectionViewCell, IndexPath) -> ())) -> Self {
+ willDisplayCellHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didEndDisplayingCellFor(_ handler: @escaping ((CollectionViewCell, IndexPath) -> ())) -> Self {
+ didEndDisplayingCell = handler
+
+ return self
+ }
+
+ @discardableResult
+ func transitionLayoutFor(_ handler: @escaping (_ old: UICollectionViewLayout, _ new: UICollectionViewLayout) -> UICollectionViewTransitionLayout) -> Self {
+ transitionLayoutForHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func targetContentOffsetFor(_ handler: @escaping (_ proposed: CGPoint) -> CGPoint) -> Self {
+ targetContentOffsetForHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func targetIndexPathForMove(_ handler: @escaping (_ from: IndexPath, _ to: IndexPath) -> IndexPath) -> Self {
+ targetIndexPathForMoveHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func canFocusItemAt(_ handler: @escaping (IndexPath) -> Bool) -> Self {
+ canFocusItemAtHandler = handler
+
+ return self
+ }
+}
+
+// MARK: - Auxiliary Delegation
+@available(iOS 11, *)
+extension CollectionView: UICollectionViewDelegate {
+ public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
+ return shouldSelectItemAtHandler?(indexPath) ?? true
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+ didSelectItemAtHandler?(indexPath)
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool {
+ shouldDeselectItemAtHandler?(indexPath) ?? true
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
+ didDeselectItemAtHandler?(indexPath)
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool {
+ shouldBeginMultipleSelectionInteractionAtHandler?(indexPath) ?? true
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, didBeginMultipleSelectionInteractionAt indexPath: IndexPath) {
+ didBeginMultipleSelectionInteractionAtHandler?(indexPath)
+ }
+
+ public func collectionViewDidEndMultipleSelectionInteraction(_ collectionView: UICollectionView) {
+ didEndMultipleSelectionInteractionHandler?(collectionView)
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
+ shouldHighlightItemAtHandler?(indexPath) ?? true
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
+ didHighlightItemAtHandler?(indexPath)
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
+ didUnhighlightItemAtHandler?(indexPath)
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
+ willDisplayCellHandler?(cell as! CollectionViewCell, indexPath)
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
+ didEndDisplayingCell?(cell as! CollectionViewCell, indexPath)
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, transitionLayoutForOldLayout fromLayout: UICollectionViewLayout, newLayout toLayout: UICollectionViewLayout) -> UICollectionViewTransitionLayout {
+ transitionLayoutForHandler?(fromLayout, toLayout) ??
+ UICollectionViewTransitionLayout(currentLayout: fromLayout, nextLayout: toLayout)
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
+ targetContentOffsetForHandler?(proposedContentOffset) ?? proposedContentOffset
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
+ targetIndexPathForMoveHandler?(originalIndexPath, proposedIndexPath) ?? proposedIndexPath
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool {
+ canFocusItemAtHandler?(indexPath) ?? true
+ }
+}
+
+// MARK: - Layout modifiers
+@available(iOS 11, *)
+public extension CollectionView {
+ @discardableResult
+ func layoutSizeForItem(_ handler: @escaping ((UICollectionViewLayout, IndexPath) -> CGSize)) -> Self {
+ layoutSizeForItemHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func sectionInsets(shouldUpdate: Bool = true, insets: [UIEdgeInsets]) -> Self {
+ sectionsInsets = insets
+
+ if shouldUpdate {
+ layoutIfNeeded()
+ }
+
+ return self
+ }
+
+ @discardableResult
+ func minimumLineSpacing(shouldUpdate: Bool = true, forSections spacings: [CGFloat]) -> Self {
+ minimumLineSpacingForSections = spacings
+
+ if shouldUpdate {
+ layoutIfNeeded()
+ }
+
+ return self
+ }
+
+ @discardableResult
+ func minimumInteritemSpacing(shouldUpdate: Bool = true, forSections spacings: [CGFloat]) -> Self {
+ minimumInteritemSpacingForSections = spacings
+
+ if shouldUpdate {
+ layoutIfNeeded()
+ }
+
+ return self
+ }
+
+ @discardableResult
+ func headerSize(shouldUpdate: Bool = true, forSections sizes: [CGSize]) -> Self {
+ headerSizesForSections = sizes
+
+ if shouldUpdate {
+ layoutIfNeeded()
+ }
+
+ return self
+ }
+
+ @discardableResult
+ func footerSize(shouldUpdate: Bool = true, forSections sizes: [CGSize]) -> Self {
+ footerSizeForSections = sizes
+
+ if shouldUpdate {
+ layoutIfNeeded()
+ }
+
+ return self
+ }
+}
+
+// MARK: - Collection Flow Layout Delegate
+@available(iOS 11, *)
+extension CollectionView: UICollectionViewDelegateFlowLayout {
+ /// Returns value based on size of given array's count and `numberOfSections` integer.
+ /// - Parameters:
+ /// - array: Array of objects containing value that can be returned
+ /// - section: section which will be used to get value from array
+ /// - Returns: First value of array if array's count isn't equal `numberOfSections`, value from given index if array's count is equal to `numberOfSections` and nil if array is nil.
+ func returnValidValue(for array: [T]?, section: Int) -> T? {
+ if let array = array,
+ array.count != 0 {
+
+ if array.count != numberOfSections ||
+ section > numberOfSections {
+ let element = array[0]
+ return element
+ } else {
+ let element = array[section]
+ return element
+ }
+ }
+
+ return nil
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+ layoutSizeForItemHandler?(collectionViewLayout, indexPath) ?? CGSize(width: 60, height: 60)
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
+ if let inset = returnValidValue(for: sectionsInsets, section: section) {
+ return inset
+ }
+
+ return UIEdgeInsets()
+ }
+
+
+ public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
+ if let spacing = returnValidValue(for: minimumLineSpacingForSections, section: section) {
+ return spacing
+ }
+
+ return 10
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
+ if let spacing = returnValidValue(for: minimumInteritemSpacingForSections, section: section) {
+ return spacing
+ }
+
+ return 5
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
+ if let size = returnValidValue(for: headerSizesForSections, section: section) {
+ return size
+ }
+
+ return .zero
+ }
+
+ public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
+ if let size = returnValidValue(for: footerSizeForSections, section: section) {
+ return size
+ }
+
+ return .zero
+ }
+}
diff --git a/Sources/SwiftUIKit/Views/ContainerView.swift b/Sources/SwiftUIKit/Views/ContainerView.swift
new file mode 100644
index 0000000..4fc0420
--- /dev/null
+++ b/Sources/SwiftUIKit/Views/ContainerView.swift
@@ -0,0 +1,43 @@
+//
+// ContainerView.swift
+// SwiftUIKit
+//
+// Created by Zach Eriksen on 5/17/20.
+//
+
+import UIKit
+
+@available(iOS 9.0, *)
+public class ContainerView: UIView {
+ private weak var parentViewController: UIViewController?
+ var viewController: UIViewController?
+
+ public init(parent: UIViewController, child: () -> UIViewController) {
+ parentViewController = parent
+ viewController = child()
+ super.init(frame: .zero)
+
+ embedViewController()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+@available(iOS 9.0, *)
+private extension ContainerView {
+ func embedViewController() {
+ guard let parent = parentViewController,
+ let child = viewController else {
+ return
+ }
+
+ parent.addChild(child)
+ child.didMove(toParent: parent)
+
+ embed {
+ child.view
+ }
+ }
+}
diff --git a/Sources/SwiftUIKit/Views/Table.swift b/Sources/SwiftUIKit/Views/List.swift
similarity index 95%
rename from Sources/SwiftUIKit/Views/Table.swift
rename to Sources/SwiftUIKit/Views/List.swift
index 285f768..63172f9 100644
--- a/Sources/SwiftUIKit/Views/Table.swift
+++ b/Sources/SwiftUIKit/Views/List.swift
@@ -8,7 +8,7 @@
import UIKit
@available(iOS 9.0, *)
-public class Table: UITableView {
+public class List: UITableView {
private var data: [UIView]
private var defaultCellHeight: Float?
private var estimatedCellHeight: Float?
@@ -38,7 +38,7 @@ public class Table: UITableView {
}
@available(iOS 9.0, *)
-public extension Table {
+public extension List {
@discardableResult
func didSelectHandler(_ action: @escaping (UIView) -> Void) -> Self {
self.didSelectHandler = action
@@ -55,7 +55,7 @@ public extension Table {
}
@available(iOS 9.0, *)
-extension Table: UITableViewDataSource {
+extension List: UITableViewDataSource {
public func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
@@ -96,7 +96,7 @@ extension Table: UITableViewDataSource {
}
@available(iOS 9.0, *)
-extension Table: UITableViewDelegate {
+extension List: UITableViewDelegate {
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
didSelectHandler?(data[indexPath.row])
}
diff --git a/Sources/SwiftUIKit/Views/Map.swift b/Sources/SwiftUIKit/Views/Map.swift
new file mode 100644
index 0000000..d2afd5b
--- /dev/null
+++ b/Sources/SwiftUIKit/Views/Map.swift
@@ -0,0 +1,496 @@
+//
+// Map.swift
+// SwiftUIKit
+//
+// Created by Oskar on 12/04/2020.
+//
+
+import Foundation
+import MapKit
+
+public struct MapPoint {
+ public let latitude: Double
+ public let longitude: Double
+ public let title: String
+ public let subtitle: String
+
+ public init(latitude: Double, longitude: Double, title: String, subtitle: String) {
+ self.latitude = latitude
+ self.longitude = longitude
+ self.title = title
+ self.subtitle = subtitle
+ }
+}
+
+public class Map: MKMapView {
+
+ fileprivate var initialCoordinates: CLLocationCoordinate2D
+
+ fileprivate lazy var onFinishLoadingHandler: ((MKMapView) -> ())? = nil
+
+ fileprivate lazy var afterRegionChangeHandler: ((MKMapView) -> ())? = nil
+
+ fileprivate lazy var beforeRegionChangeHandler: ((MKMapView) -> ())? = nil
+
+ fileprivate lazy var annotationViewConfigurationHandler: ((MKAnnotationView?, MKAnnotation) -> (MKAnnotationView?))? = nil
+
+ fileprivate lazy var onAccessoryTapHandler: ((MKMapView, MKAnnotationView, UIControl) -> ())? = nil
+
+ fileprivate lazy var onAnnotationViewStateChangeHandler: ((MKMapView, MKAnnotationView, MKAnnotationView.DragState, MKAnnotationView.DragState) -> ())? = nil
+
+ fileprivate lazy var onAnnotationSelectHandler: ((MKMapView, MKAnnotationView) -> ())? = nil
+
+ fileprivate lazy var onAnnotationDeselectHandler: ((MKMapView, MKAnnotationView) -> ())? = nil
+
+ fileprivate lazy var annotationViewIdentifier: String? = nil
+
+ public init(lat latitude: Double,
+ lon longitude: Double,
+ points: (() -> [MapPoint])? = nil) {
+ let coordinates = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
+
+ initialCoordinates = coordinates
+
+ super.init(frame: .zero)
+
+ let span = MKCoordinateSpan(latitudeDelta: region.span.latitudeDelta / 2,
+ longitudeDelta: region.span.longitudeDelta / 2)
+
+ let region = MKCoordinateRegion(center: coordinates, span: span)
+ setRegion(region, animated: true)
+
+ if let points = points {
+ add(points: points())
+ }
+
+ self.delegate = self
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
+
+// MARK: - Initializers
+public extension Map {
+ convenience init(region: MKCoordinateRegion,
+ points: (() -> [MapPoint])? = nil) {
+ self.init(lat: region.center.latitude,
+ lon: region.center.longitude,
+ points: points)
+ }
+}
+
+// MARK: - Accessing Map Properties
+public extension Map {
+ @discardableResult
+ func type(_ type: MKMapType) -> Self {
+ mapType = type
+
+ return self
+ }
+
+ @discardableResult
+ func zoomEnabled(_ value: Bool = true) -> Self {
+ isZoomEnabled = value
+
+ return self
+ }
+
+ @discardableResult
+ func scrollEnabled(_ value: Bool = true) -> Self {
+ isScrollEnabled = value
+
+ return self
+ }
+
+ @discardableResult
+ func pitchEnabled(_ value: Bool = true) -> Self {
+ isPitchEnabled = value
+
+ return self
+ }
+
+ @discardableResult
+ func rotateEnabled(_ value: Bool = true) -> Self {
+ isRotateEnabled = value
+
+ return self
+ }
+
+ /// Note: If delegate isn't its own class, modifiers based on delegate's methods will do nothing.
+ @discardableResult
+ func delegate(_ delegate: MKMapViewDelegate?) -> Self {
+ self.delegate = delegate ?? self
+
+ return self
+ }
+}
+
+// MARK: - Manipulating the Visible Portion of the Map
+public extension Map {
+ @discardableResult
+ func zoom(_ multiplier: Double) -> Self {
+ let _center = initialCoordinates
+ let _span = MKCoordinateSpan(latitudeDelta: region.span.latitudeDelta / multiplier / 10,
+ longitudeDelta: region.span.longitudeDelta / multiplier / 10)
+ let _region = MKCoordinateRegion(center: _center, span: _span)
+
+ setRegion(_region, animated: false)
+ return self
+ }
+
+ @discardableResult
+ func visible(rect: MKMapRect,
+ animate: Bool = true,
+ edgePadding: UIEdgeInsets? = nil
+ ) -> Self {
+ if let padding = edgePadding {
+ setVisibleMapRect(rect, edgePadding: padding, animated: animate)
+ } else {
+ setVisibleMapRect(rect, animated: animate)
+ }
+
+ return self
+ }
+
+ /// Changes coordinates and span.
+ @discardableResult
+ func move(to region: MKCoordinateRegion, animate: Bool = true) -> Self {
+ initialCoordinates = region.center
+ setRegion(region, animated: animate)
+
+ return self
+ }
+
+
+ /// Changes only coordinates.
+ @discardableResult
+ func move(to coordinates: CLLocationCoordinate2D, animate: Bool = true) -> Self {
+ let _region = MKCoordinateRegion(center: coordinates, span: region.span)
+ initialCoordinates = coordinates
+ setRegion(_region, animated: animate)
+
+ return self
+ }
+
+ @discardableResult
+ func center(_ center: CLLocationCoordinate2D, animated: Bool = true) -> Self {
+ setCenter(center, animated: animated)
+
+ return self
+ }
+
+ @discardableResult
+ func show(annotations: [MKAnnotation], animated: Bool = true) -> Self {
+ super.showAnnotations(annotations, animated: animated)
+
+ return self
+ }
+
+ @discardableResult
+ func show(annotations: MKAnnotation..., animated: Bool = true) -> Self {
+ super.showAnnotations(annotations, animated: animated)
+
+ return self
+ }
+}
+
+// MARK: - Constraining the Map View
+
+@available(iOS 13.0, *)
+public extension Map {
+ @discardableResult
+ func camera(boundary: MKMapView.CameraBoundary?, animated: Bool = true) -> Self {
+ setCameraBoundary(boundary, animated: animated)
+
+ return self
+ }
+
+ @discardableResult
+ func set(cameraZoomRange: MKMapView.CameraZoomRange?, animated: Bool) -> Self {
+ super.setCameraZoomRange(cameraZoomRange, animated: animated)
+
+ return self
+ }
+}
+
+// MARK: - Configuring the Map's Appearance
+public extension Map {
+ @discardableResult
+ func camera(_ camera: MKMapCamera, animated: Bool = true) -> Self {
+ setCamera(camera, animated: animated)
+
+ return self
+ }
+
+ @discardableResult
+ func showBuildings(_ bool: Bool) -> Self {
+ showsBuildings = bool
+
+ return self
+ }
+}
+
+@available(iOS 13.0, *)
+public extension Map {
+ @discardableResult
+ func showCompass(_ bool: Bool) -> Self {
+ showsCompass = bool
+
+ return self
+ }
+
+ @discardableResult
+ func showScale(_ bool: Bool) -> Self {
+ showsScale = bool
+
+ return self
+ }
+
+ @discardableResult
+ func showTraffic(_ bool: Bool) -> Self {
+ showsTraffic = bool
+
+ return self
+ }
+
+ @discardableResult
+ func pointOfInterestFilter(filter: MKPointOfInterestFilter?) -> Self {
+ pointOfInterestFilter = filter
+
+ return self
+ }
+}
+
+// MARK: - Displaying the User's Location
+public extension Map {
+ @discardableResult
+ func showUserLocation(_ bool: Bool) -> Self {
+ showsUserLocation = bool
+
+ return self
+ }
+
+ @discardableResult
+ func user(trackingMode: MKUserTrackingMode, animated: Bool = true) -> Self {
+ setUserTrackingMode(trackingMode, animated: animated)
+
+ return self
+ }
+}
+
+// MARK: - Managing Annotation Selections
+public extension Map {
+ @discardableResult
+ func select(annotation: MKAnnotation, animated: Bool = true) -> Self {
+ selectAnnotation(annotation, animated: animated)
+
+ return self
+ }
+
+ @discardableResult
+ func deselect(annotation: MKAnnotation, animated: Bool = true) -> Self {
+ deselectAnnotation(annotation, animated: animated)
+
+ return self
+ }
+}
+
+// MARK: - Annotating the Map
+public extension Map {
+ @discardableResult
+ func remove(annotation: MKAnnotation) -> Self {
+ removeAnnotation(annotation)
+
+ return self
+ }
+
+ @discardableResult
+ func remove(annotations: [MKAnnotation]) -> Self {
+ removeAnnotations(annotations)
+
+ return self
+ }
+
+ @discardableResult
+ func add(annotation: MKAnnotation) -> Self {
+ addAnnotation(annotation)
+
+ return self
+ }
+
+ @discardableResult
+ func add(point: MapPoint) -> Self {
+ DispatchQueue.global().async {
+ let annotation = MKPointAnnotation()
+
+ annotation.coordinate = CLLocationCoordinate2D(latitude: point.latitude,
+ longitude: point.longitude)
+ annotation.title = point.title
+ annotation.subtitle = point.subtitle
+
+ DispatchQueue.main.async {
+ self.addAnnotation(annotation)
+ }
+ }
+
+ return self
+ }
+
+ @discardableResult
+ func add(annotations: [MKAnnotation]) -> Self {
+ addAnnotations(annotations)
+
+ return self
+ }
+
+ @discardableResult
+ func add(points: [MapPoint]) -> Self {
+ for point in points {
+ add(point: point)
+ }
+
+ return self
+ }
+}
+
+// MARK: - Creating Annotation Views
+@available(iOS 11.0, *)
+public extension Map {
+ @discardableResult
+ func register(classes: [String: AnyClass?]) -> Self {
+ for (identifier, annotationClass) in classes {
+ register(annotationClass, forAnnotationViewWithReuseIdentifier: identifier)
+ }
+
+ return self
+ }
+}
+
+// MARK: - Adjusting Map Regions and Rectangles
+public extension Map {
+ @discardableResult
+ func fitTo(region: MKCoordinateRegion) -> Self {
+ self.region = regionThatFits(region)
+
+ return self
+ }
+
+ @discardableResult
+ func fitTo(rect: MKMapRect, edgePadding: UIEdgeInsets? = nil) -> Self {
+ if let edgePadding = edgePadding {
+ mapRectThatFits(rect, edgePadding: edgePadding)
+ } else {
+ mapRectThatFits(rect)
+ }
+
+ return self
+ }
+}
+
+// MARK: - Delegate wrappers
+// If delegate isn't its own class, methods below will not execute.
+public extension Map {
+ @discardableResult
+ func onFinishLoading(_ handler: @escaping (MKMapView) -> ()) -> Self {
+ guard delegate === self else { return self }
+ onFinishLoadingHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func afterRegionChange(_ handler: @escaping (MKMapView) -> ()) -> Self {
+ guard delegate === self else { return self }
+ afterRegionChangeHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func beforeRegionChange(_ handler: @escaping (MKMapView) -> ()) -> Self {
+ guard delegate === self else { return self }
+ beforeRegionChangeHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func configure(identifier: String?, _ annotationView: @escaping ((MKAnnotationView?, MKAnnotation) -> (MKAnnotationView?))) -> Self {
+ guard delegate === self else { return self }
+ annotationViewIdentifier = identifier
+ annotationViewConfigurationHandler = annotationView
+
+ return self
+ }
+
+ @discardableResult
+ func onAccessoryTap(_ handler: @escaping (MKMapView, MKAnnotationView, UIControl) -> ()) -> Self {
+ onAccessoryTapHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func onAnnotationViewStateChange(_ handler: @escaping ((MKMapView, MKAnnotationView, MKAnnotationView.DragState, MKAnnotationView.DragState) -> ())) -> Self {
+ onAnnotationViewStateChangeHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func onAnnotationSelect(_ handler: @escaping ((MKMapView, MKAnnotationView) -> ())) -> Self {
+ onAnnotationSelectHandler = handler
+
+ return self
+ }
+
+ @discardableResult
+ func onAnnotationDeselect(_ handler: @escaping ((MKMapView, MKAnnotationView) -> ())) -> Self {
+ onAnnotationDeselectHandler = handler
+
+ return self
+ }
+}
+
+// MARK: - Delegation
+extension Map: MKMapViewDelegate {
+ public func mapViewDidFinishLoadingMap(_ mapView: MKMapView) {
+ onFinishLoadingHandler?(mapView)
+ }
+
+ public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
+ afterRegionChangeHandler?(mapView)
+ }
+
+ public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
+ beforeRegionChangeHandler?(mapView)
+ }
+
+ public func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
+ if let identifier = annotationViewIdentifier {
+ let annotationView = dequeueReusableAnnotationView(withIdentifier: identifier)
+
+ return annotationViewConfigurationHandler?(annotationView, annotation)
+ }
+
+ return annotationViewConfigurationHandler?(nil, annotation)
+ }
+
+ public func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
+ onAccessoryTapHandler?(mapView, view, control)
+ }
+
+ public func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, didChange newState: MKAnnotationView.DragState, fromOldState oldState: MKAnnotationView.DragState) {
+ onAnnotationViewStateChangeHandler?(mapView, view, newState, oldState)
+ }
+
+ public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
+ onAnnotationSelectHandler?(mapView, view)
+ }
+
+ public func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) {
+ onAnnotationDeselectHandler?(mapView, view)
+ }
+}
diff --git a/Sources/SwiftUIKit/Views/Slider.swift b/Sources/SwiftUIKit/Views/Slider.swift
index 31ba233..7feafa1 100644
--- a/Sources/SwiftUIKit/Views/Slider.swift
+++ b/Sources/SwiftUIKit/Views/Slider.swift
@@ -18,9 +18,9 @@ public class Slider: UISlider {
super.init(frame: .zero)
- self.value = value
self.minimumValue = from
self.maximumValue = to
+ self.value = value
self.valueChangedHandler = valueChangedHandler
addTarget(self, action: #selector(handleValueChanged), for: .valueChanged)
diff --git a/Sources/SwiftUIKit/Views/TableView.swift b/Sources/SwiftUIKit/Views/TableView.swift
index 4ddea84..834a7ae 100644
--- a/Sources/SwiftUIKit/Views/TableView.swift
+++ b/Sources/SwiftUIKit/Views/TableView.swift
@@ -7,33 +7,35 @@
import UIKit
-@available(iOS 9.0, *)
+@available(iOS 11.0, *)
public protocol CellDisplayable {
var cellID: String { get }
}
-@available(iOS 9.0, *)
-public protocol DataConfigurable: UITableViewCell {
+@available(iOS 11.0, *)
+public protocol DataIdentifiable {
static var ID: String { get }
}
-@available(iOS 9.0, *)
-public protocol CellUpdatable: UITableViewCell {
+@available(iOS 11.0, *)
+public protocol CellUpdatable {
func update(forData data: CellDisplayable)
}
-@available(iOS 9.0, *)
-public protocol CellConfigurable: UITableViewCell {
+@available(iOS 11.0, *)
+public protocol CellConfigurable {
func configure(forData data: CellDisplayable)
}
-@available(iOS 9.0, *)
-public typealias TableViewCell = DataConfigurable & CellConfigurable & CellUpdatable
+@available(iOS 11.0, *)
+public typealias TableViewCell = DataIdentifiable & CellConfigurable & CellUpdatable & UITableViewCell
public typealias TableHeaderFooterViewHandler = (Int) -> UIView?
public typealias TableHeaderFooterTitleHandler = (Int) -> String?
+public typealias TableDidSelectIndexPathHandler = (IndexPath) -> Void
+public typealias TableHighlightIndexPathHandler = (IndexPath) -> Bool
-@available(iOS 9.0, *)
+@available(iOS 11.0, *)
public class TableView: UITableView {
public var data: [[CellDisplayable]]
@@ -41,6 +43,26 @@ public class TableView: UITableView {
fileprivate var footerViewForSection: TableHeaderFooterViewHandler?
fileprivate var headerTitleForSection: TableHeaderFooterTitleHandler?
fileprivate var footerTitleForSection: TableHeaderFooterTitleHandler?
+ fileprivate var didSelectRowAtIndexPath: TableDidSelectIndexPathHandler?
+ fileprivate var shouldHighlightRowAtIndexPath: TableHighlightIndexPathHandler?
+ fileprivate var canEditRowAtIndexPath: ((IndexPath) -> Bool)?
+ fileprivate var canMoveRowAtIndexPath: ((IndexPath) -> Bool)?
+ fileprivate var canFocusRowAtIndexPath: ((IndexPath) -> Bool)?
+ fileprivate var indentationLevelForRowAtIndexPath: ((IndexPath) -> Int)?
+ fileprivate var shouldIndentWhileEditingRowAtIndexPath: ((IndexPath) -> Bool)?
+ fileprivate var shouldShowMenuForRowAtIndexPath: ((IndexPath) -> Bool)?
+ fileprivate var editingStyleForRowAtIndexPath: ((IndexPath) -> UITableViewCell.EditingStyle)?
+ fileprivate var titleForDeleteConfirmationButtonForRowAtIndexPath: ((IndexPath) -> String)?
+ fileprivate var editActionsForRowAtIndexPath: ((IndexPath) -> [UITableViewRowAction])?
+ fileprivate var commitEditingStyleForRowAtIndexPath: ((UITableViewCell.EditingStyle, IndexPath) -> Void)?
+ fileprivate var didDeselectRowAtIndexPath: ((IndexPath) -> Void)?
+ fileprivate var willBeginEditingRowAtIndexPath: ((IndexPath) -> Void)?
+ fileprivate var didEndEditingRowAtIndexPath: ((IndexPath?) -> Void)?
+ fileprivate var didHighlightRowAtIndexPath: ((IndexPath) -> Void)?
+ fileprivate var didUnhighlightRowAtIndexPath: ((IndexPath) -> Void)?
+ fileprivate var moveRowAtSourceIndexPathToDestinationIndexPath: ((IndexPath, IndexPath) -> Void)?
+ fileprivate var leadingSwipeActionsConfigurationForRowAtIndexPath: ((IndexPath) -> UISwipeActionsConfiguration)?
+ fileprivate var trailingSwipeActionsConfigurationForRowAtIndexPath: ((IndexPath) -> UISwipeActionsConfiguration)?
public init(initalData: [[CellDisplayable]] = [[CellDisplayable]](),
style: UITableView.Style = .plain) {
@@ -56,7 +78,7 @@ public class TableView: UITableView {
}
}
-@available(iOS 9.0, *)
+@available(iOS 11.0, *)
public extension TableView {
@discardableResult
func update(shouldReloadData: Bool = false,
@@ -83,12 +105,12 @@ public extension TableView {
}
}
-@available(iOS 9.0, *)
+@available(iOS 11.0, *)
extension TableView: UITableViewDelegate {
}
-@available(iOS 9.0, *)
+@available(iOS 11.0, *)
extension TableView: UITableViewDataSource {
func sections() -> Int {
data.count
@@ -106,6 +128,10 @@ extension TableView: UITableViewDataSource {
rows(forSection: section)
}
+ public func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: IndexPath) -> Int {
+ indentationLevelForRowAtIndexPath?(indexPath) ?? 0
+ }
+
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData = data[indexPath.section][indexPath.row]
@@ -126,6 +152,8 @@ extension TableView: UITableViewDataSource {
return cell
}
+ // MARK: HeaderForSection
+
public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
headerViewForSection?(section)
}
@@ -134,6 +162,8 @@ extension TableView: UITableViewDataSource {
headerTitleForSection?(section)
}
+ // MARK: FooterForSection
+
public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
footerViewForSection?(section)
}
@@ -141,9 +171,95 @@ extension TableView: UITableViewDataSource {
public func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
footerTitleForSection?(section)
}
+
+ // MARK: CanRowAt
+
+ public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
+ canEditRowAtIndexPath?(indexPath) ?? false
+ }
+
+ public func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
+ canMoveRowAtIndexPath?(indexPath) ?? false
+ }
+
+ public func tableView(_ tableView: UITableView, canFocusRowAt indexPath: IndexPath) -> Bool {
+ canFocusRowAtIndexPath?(indexPath) ?? false
+ }
+
+ // MARK: ShouldRowAt
+
+ public func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
+ shouldHighlightRowAtIndexPath?(indexPath) ?? true
+ }
+
+ public func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
+ shouldIndentWhileEditingRowAtIndexPath?(indexPath) ?? false
+ }
+
+ public func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
+ shouldShowMenuForRowAtIndexPath?(indexPath) ?? false
+ }
+
+ // MARK: Editing
+
+ public func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
+ editingStyleForRowAtIndexPath?(indexPath) ?? .none
+ }
+
+ public func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
+ titleForDeleteConfirmationButtonForRowAtIndexPath?(indexPath)
+ }
+
+ public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
+ editActionsForRowAtIndexPath?(indexPath)
+ }
+
+ // MARK: Actions
+
+ public func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
+ commitEditingStyleForRowAtIndexPath?(editingStyle, indexPath)
+ }
+
+ public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ didSelectRowAtIndexPath?(indexPath)
+ }
+
+ public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
+ didDeselectRowAtIndexPath?(indexPath)
+ }
+
+ public func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
+ willBeginEditingRowAtIndexPath?(indexPath)
+ }
+
+ public func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
+ didEndEditingRowAtIndexPath?(indexPath)
+ }
+
+ public func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
+ didHighlightRowAtIndexPath?(indexPath)
+ }
+
+ public func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
+ didUnhighlightRowAtIndexPath?(indexPath)
+ }
+
+ public func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
+ moveRowAtSourceIndexPathToDestinationIndexPath?(sourceIndexPath, destinationIndexPath)
+ }
+
+ @available(iOS 11.0, *)
+ public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
+ leadingSwipeActionsConfigurationForRowAtIndexPath?(indexPath) ?? .none
+ }
+
+ @available(iOS 11.0, *)
+ public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
+ trailingSwipeActionsConfigurationForRowAtIndexPath?(indexPath) ?? .none
+ }
}
-@available(iOS 9.0, *)
+@available(iOS 11.0, *)
public extension TableView {
@discardableResult
func set(dataSource: UITableViewDataSource) -> Self {
@@ -195,6 +311,144 @@ public extension TableView {
return self
}
+
+ @discardableResult
+ func indentationLevelForRowAtIndexPath(_ handler: @escaping (IndexPath) -> Int) -> Self {
+ indentationLevelForRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func canEditRowAtIndexPath(_ handler: @escaping (IndexPath) -> Bool) -> Self {
+ canEditRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func canMoveRowAtIndexPath(_ handler: @escaping (IndexPath) -> Bool) -> Self {
+ canMoveRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func canFocusRowAtIndexPath(_ handler: @escaping (IndexPath) -> Bool) -> Self {
+ canFocusRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func shouldHighlightRow(_ handler: @escaping TableHighlightIndexPathHandler) -> Self {
+ shouldHighlightRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func shouldIndentWhileEditingRowAtIndexPath(_ handler: @escaping (IndexPath) -> Bool) -> Self {
+ shouldIndentWhileEditingRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func shouldShowMenuForRowAtIndexPath(_ handler: @escaping (IndexPath) -> Bool) -> Self {
+ shouldShowMenuForRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func editingStyleForRowAtIndexPath(_ handler: @escaping (IndexPath) -> UITableViewCell.EditingStyle) -> Self {
+ editingStyleForRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func titleForDeleteConfirmationButtonForRowAtIndexPath(_ handler: @escaping (IndexPath) -> String) -> Self {
+ titleForDeleteConfirmationButtonForRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func editActionsForRowAtIndexPath(_ handler: @escaping (IndexPath) -> [UITableViewRowAction]) -> Self {
+ editActionsForRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func commitEditingStyleForRowAtIndexPath(_ handler: @escaping (UITableViewCell.EditingStyle, IndexPath) -> Void) -> Self {
+ commitEditingStyleForRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didSelectRow(_ handler: @escaping TableDidSelectIndexPathHandler) -> Self {
+ didSelectRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didDeselectRowAtIndexPath(_ handler: @escaping (IndexPath) -> Void) -> Self {
+ didDeselectRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func willBeginEditingRowAtIndexPath(_ handler: @escaping (IndexPath) -> Void) -> Self {
+ willBeginEditingRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didEndEditingRowAtIndexPath(_ handler: @escaping (IndexPath?) -> Void) -> Self {
+ didEndEditingRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didHighlightRowAtIndexPath(_ handler: @escaping (IndexPath) -> Void) -> Self {
+ didHighlightRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func didUnhighlightRowAtIndexPath(_ handler: @escaping (IndexPath) -> Void) -> Self {
+ didUnhighlightRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func moveRowAtSourceIndexPathToDestinationIndexPath(_ handler: @escaping (IndexPath, IndexPath) -> Void) -> Self {
+ moveRowAtSourceIndexPathToDestinationIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func leadingSwipeActionsConfigurationForRowAtIndexPath(_ handler: @escaping (IndexPath) -> UISwipeActionsConfiguration) -> Self {
+ leadingSwipeActionsConfigurationForRowAtIndexPath = handler
+
+ return self
+ }
+
+ @discardableResult
+ func trailingSwipeActionsConfigurationForRowAtIndexPath(_ handler: @escaping (IndexPath) -> UISwipeActionsConfiguration) -> Self {
+ trailingSwipeActionsConfigurationForRowAtIndexPath = handler
+
+ return self
+ }
}
-
-
diff --git a/Tests/SwiftUIKitTests/ContainerView/ContainerViewTests.swift b/Tests/SwiftUIKitTests/ContainerView/ContainerViewTests.swift
new file mode 100644
index 0000000..1c19569
--- /dev/null
+++ b/Tests/SwiftUIKitTests/ContainerView/ContainerViewTests.swift
@@ -0,0 +1,90 @@
+//
+// ContainerViewTests.swift
+// SwiftUIKitTests
+//
+// Created by Zach Eriksen on 5/17/20.
+//
+
+import XCTest
+import UIKit
+import SwiftUIKit
+
+@available(iOS 9.0, *)
+class ContainerViewTests: XCTestCase {
+
+ func testBasicContainerView() {
+ let mainVC = UIViewController()
+ let someVC = UIViewController()
+ let containerView = ContainerView(parent: mainVC) {
+ someVC
+ }
+
+ XCTAssertEqual(mainVC.children.count, 1)
+ XCTAssertEqual(someVC.children.count, 0)
+ XCTAssertEqual(mainVC.view.allSubviews.count, 0)
+ XCTAssertEqual(someVC.view.allSubviews.count, 0)
+ XCTAssertEqual(containerView.allSubviews.count, 1)
+ }
+
+ func testEmbedContainerView() {
+ let mainVC = UIViewController()
+ let someVC = UIViewController()
+ let containerView = ContainerView(parent: mainVC) {
+ someVC
+ }
+
+ XCTAssertEqual(mainVC.children.count, 1)
+ XCTAssertEqual(someVC.children.count, 0)
+ XCTAssertEqual(mainVC.view.allSubviews.count, 0)
+ XCTAssertEqual(someVC.view.allSubviews.count, 0)
+ XCTAssertEqual(containerView.allSubviews.count, 1)
+
+ mainVC.view.embed {
+ containerView
+ }
+
+ XCTAssertEqual(mainVC.children.count, 1)
+ XCTAssertEqual(someVC.children.count, 0)
+ XCTAssertEqual(mainVC.view.allSubviews.count, 2)
+ XCTAssertEqual(someVC.view.allSubviews.count, 0)
+ XCTAssertEqual(containerView.allSubviews.count, 1)
+ }
+
+ func testChildVCEmbedContainerView() {
+ let mainVC = UIViewController()
+ let someVC = UIViewController()
+ let containerView = ContainerView(parent: mainVC) {
+ someVC
+ }
+
+ XCTAssertEqual(mainVC.children.count, 1)
+ XCTAssertEqual(someVC.children.count, 0)
+ XCTAssertEqual(mainVC.view.allSubviews.count, 0)
+ XCTAssertEqual(someVC.view.allSubviews.count, 0)
+ XCTAssertEqual(containerView.allSubviews.count, 1)
+
+ mainVC.view.embed {
+ containerView
+ }
+
+ XCTAssertEqual(mainVC.children.count, 1)
+ XCTAssertEqual(someVC.children.count, 0)
+ XCTAssertEqual(mainVC.view.allSubviews.count, 2)
+ XCTAssertEqual(someVC.view.allSubviews.count, 0)
+ XCTAssertEqual(containerView.allSubviews.count, 1)
+
+ someVC.view.embed {
+ VStack {
+ [
+ Label("Something")
+ ]
+ }
+ }
+
+ XCTAssertEqual(mainVC.children.count, 1)
+ XCTAssertEqual(someVC.children.count, 0)
+ XCTAssertEqual(mainVC.view.allSubviews.count, 5)
+ XCTAssertEqual(someVC.view.allSubviews.count, 3)
+ XCTAssertEqual(containerView.allSubviews.count, 4)
+ }
+}
diff --git a/Tests/SwiftUIKitTests/Core/BasicSwiftUIKitTests.swift b/Tests/SwiftUIKitTests/Core/BasicSwiftUIKitTests.swift
index e9b97a5..655dc4f 100644
--- a/Tests/SwiftUIKitTests/Core/BasicSwiftUIKitTests.swift
+++ b/Tests/SwiftUIKitTests/Core/BasicSwiftUIKitTests.swift
@@ -9,7 +9,7 @@ import Foundation
import XCTest
@testable import SwiftUIKit
-@available(iOS 9.0, *)
+@available(iOS 10.0, *)
final class BasicSwiftUIKitTests: XCTestCase {
func testDefaultView() {
@@ -31,9 +31,26 @@ final class BasicSwiftUIKitTests: XCTestCase {
viewToEmbed
}
+ let leadingConstraint = view.leadingConstraints.first
+ let bottomConstraint = view.bottomConstraints.first
+ let trailingConstraint = view.trailingConstraints.first
+ let topConstraint = view.topConstraints.first
+
XCTAssertNil(view.backgroundColor)
XCTAssert(view.allSubviews.count == 1)
XCTAssert(view.constraints.count == 4)
+ XCTAssertEqual(leadingConstraint?.constant, 0)
+ XCTAssertEqual(bottomConstraint?.constant, 0)
+ XCTAssertEqual(trailingConstraint?.constant, 0)
+ XCTAssertEqual(topConstraint?.constant, 0)
+
+ view.update(padding: 16)
+
+ XCTAssert(view.constraints.count == 4)
+ XCTAssertEqual(leadingConstraint?.constant, 16)
+ XCTAssertEqual(bottomConstraint?.constant, -16)
+ XCTAssertEqual(trailingConstraint?.constant, -16)
+ XCTAssertEqual(topConstraint?.constant, 16)
}
func testEmbedView_WithOnePadding() {
@@ -48,9 +65,18 @@ final class BasicSwiftUIKitTests: XCTestCase {
viewToEmbed
}
+ let constraint = view.leadingConstraints.first
+
XCTAssertNil(view.backgroundColor)
XCTAssert(view.allSubviews.count == 1)
XCTAssert(view.constraints.count == 1)
+ XCTAssertEqual(constraint?.constant, 16)
+
+ view.update(padding: .leading(8))
+ view.update(padding: .trailing(16))
+
+ XCTAssertEqual(constraint?.constant, 8)
+ XCTAssertEqual(view.constraints.count, 1)
}
func testEmbedView_WithTwoPadding() {
@@ -66,9 +92,21 @@ final class BasicSwiftUIKitTests: XCTestCase {
viewToEmbed
}
+ let leadingConstraint = view.leadingConstraints.first
+ let bottomConstraint = view.bottomConstraints.first
+
XCTAssertNil(view.backgroundColor)
XCTAssert(view.allSubviews.count == 1)
XCTAssert(view.constraints.count == 2)
+ XCTAssertEqual(leadingConstraint?.constant, 16)
+ XCTAssertEqual(bottomConstraint?.constant, -16)
+
+ view.update(padding: .leading(8))
+ view.update(padding: .bottom(32))
+
+ XCTAssert(view.constraints.count == 2)
+ XCTAssertEqual(leadingConstraint?.constant, 8)
+ XCTAssertEqual(bottomConstraint?.constant, -32)
}
func testEmbedView_WithThreePadding() {
@@ -85,9 +123,23 @@ final class BasicSwiftUIKitTests: XCTestCase {
viewToEmbed
}
+ let leadingConstraint = view.leadingConstraints.first
+ let bottomConstraint = view.bottomConstraints.first
+ let trailingConstraint = view.trailingConstraints.first
+
XCTAssertNil(view.backgroundColor)
XCTAssert(view.allSubviews.count == 1)
XCTAssert(view.constraints.count == 3)
+ XCTAssertEqual(leadingConstraint?.constant, 16)
+ XCTAssertEqual(bottomConstraint?.constant, -16)
+ XCTAssertEqual(trailingConstraint?.constant, -16)
+
+ view.update(padding: [.leading(32), .trailing(32), .bottom(32)])
+
+ XCTAssert(view.constraints.count == 3)
+ XCTAssertEqual(leadingConstraint?.constant, 32)
+ XCTAssertEqual(bottomConstraint?.constant, -32)
+ XCTAssertEqual(trailingConstraint?.constant, -32)
}
func testEmbedView_WithAllPadding() {
@@ -105,9 +157,26 @@ final class BasicSwiftUIKitTests: XCTestCase {
viewToEmbed
}
+ let leadingConstraint = view.leadingConstraints.first
+ let bottomConstraint = view.bottomConstraints.first
+ let trailingConstraint = view.trailingConstraints.first
+ let topConstraint = view.topConstraints.first
+
XCTAssertNil(view.backgroundColor)
XCTAssert(view.allSubviews.count == 1)
XCTAssert(view.constraints.count == 4)
+ XCTAssertEqual(leadingConstraint?.constant, 16)
+ XCTAssertEqual(bottomConstraint?.constant, -16)
+ XCTAssertEqual(trailingConstraint?.constant, -16)
+ XCTAssertEqual(topConstraint?.constant, 16)
+
+ view.update(padding: 32)
+
+ XCTAssert(view.constraints.count == 4)
+ XCTAssertEqual(leadingConstraint?.constant, 32)
+ XCTAssertEqual(bottomConstraint?.constant, -32)
+ XCTAssertEqual(trailingConstraint?.constant, -32)
+ XCTAssertEqual(topConstraint?.constant, 32)
}
func testEmbedViews() {
@@ -251,11 +320,14 @@ final class BasicSwiftUIKitTests: XCTestCase {
}
}
+ // Will fail unless...
+ // === (iOS >= 13) ===
XCTAssert(switchView.allSubviews.count == 8, "switchView.allSubviews.count == \(switchView.allSubviews.count)")
XCTAssert(uiSwitchView.allSubviews.count == 8, "uiSwitchView.allSubviews.count == \(uiSwitchView.allSubviews.count)")
-
XCTAssert(view.allSubviews.count == 12, "view.allSubviews.count == \(view.allSubviews.count)")
XCTAssert(otherView.allSubviews.count == 12, "otherView.allSubviews.count == \(otherView.allSubviews.count)")
+ // === (End) ===
+
XCTAssert(viewWithoutSwitch.allSubviews.count == 3, "viewWithoutSwitch.allSubviews.count == \(viewWithoutSwitch.allSubviews.count)")
switchView.clear()
diff --git a/Tests/SwiftUIKitTests/TableView/TableViewTests.swift b/Tests/SwiftUIKitTests/TableView/TableViewTests.swift
index f6fce6d..0cf1c9d 100644
--- a/Tests/SwiftUIKitTests/TableView/TableViewTests.swift
+++ b/Tests/SwiftUIKitTests/TableView/TableViewTests.swift
@@ -9,7 +9,7 @@ import XCTest
import UIKit
@testable import SwiftUIKit
-@available(iOS 9.0, *)
+@available(iOS 11.0, *)
class TableViewTests: XCTestCase {
func testTableViewNoCells() {
@@ -116,7 +116,7 @@ class TableViewTests: XCTestCase {
}
}
-@available(iOS 9.0, *)
+@available(iOS 11.0, *)
fileprivate class TableTestHelper {
struct InfoData {
let title: String
@@ -124,22 +124,22 @@ fileprivate class TableTestHelper {
let bio: String
}
- class InfoCell: UITableViewCell {
+ class InfoCell: TableViewCell {
let label: Label = Label("")
let detailLabel: Label = Label("")
let bioLabel: Label = Label("")
}
}
-@available(iOS 9.0, *)
+@available(iOS 11.0, *)
extension TableTestHelper.InfoData: CellDisplayable {
var cellID: String {
TableTestHelper.InfoCell.ID
}
}
-@available(iOS 9.0, *)
-extension TableTestHelper.InfoCell: TableViewCell {
+@available(iOS 11.0, *)
+extension TableTestHelper.InfoCell {
static var ID: String {
"InfoCell"
}