(at indexPath: IndexPath, of kind: String) -> (Item, ListViewItemModel)? {
23 | let section = sections[indexPath.section]
24 | let identifier: String
25 | let headerFooterModel: ListViewItemModel
26 | switch kind {
27 | case UICollectionElementKindSectionHeader:
28 | guard let model = section.header else { return nil }
29 | identifier = model.reuseIdentifier
30 | headerFooterModel = model
31 | case UICollectionElementKindSectionFooter:
32 | guard let model = section.footer else { return nil }
33 | identifier = model.reuseIdentifier
34 | headerFooterModel = model
35 | default:
36 | return nil
37 | }
38 | let item: Item? = listView.reusableHeaderFooterItem(withIdentifier: identifier, for: indexPath, of: kind)
39 | guard let view = item else { return nil }
40 | return (view, headerFooterModel)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Source/ListKit/Core/DiffableListViewHolder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SectionManager.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 02/01/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import ProtoKit
12 |
13 | /*
14 | -- `ListManager` is the only public class which one needs to use to manage
15 | table/collection-views
16 |
17 | -- `listView` could be a table/collection-view
18 |
19 | -- `shouldPerformBatchUpdate` controls whether to do a complete reload or
20 | just update the changes
21 |
22 | -- `sections` is the only public variable one needs to set to update
23 | table/collection-view
24 | */
25 |
26 | public typealias Closure = ((P) -> (Q))
27 |
28 | open class DiffableListViewHolder: ListViewHolder {
29 |
30 | let diffCalculator: ListableDiffCalculator
31 |
32 | public weak var delegate: AnyObject?
33 | public var shouldPerformBatchUpdate: Bool
34 |
35 | // datasource
36 | public var sections: [SectionModel] = [] {
37 | didSet {
38 | if shouldPerformBatchUpdate { diffCalculator.batchReload(sections) }
39 | else { listView.reloadItems() }
40 | }
41 | }
42 |
43 | public init(listView: DiffableListView, shouldPerformBatchUpdate: Bool = true, delegate: AnyObject? = nil) {
44 | self.delegate = delegate
45 | self.diffCalculator = listView.diffCalculator
46 | self.shouldPerformBatchUpdate = shouldPerformBatchUpdate
47 |
48 | super.init(listView: listView)
49 | }
50 |
51 | override open func numberOfSectionsIn(listableView: ListableView) -> Int {
52 | return sections.count
53 | }
54 |
55 | override open func listableView(_ listableView: ListableView, numberOfItemsInSection section: Int) -> Int {
56 | return sections[section].cells.count
57 | }
58 |
59 | override open func listableView(_ listableView: ListableView, itemForItemAt indexPath: IndexPath) -> Item {
60 | let data: (Item, ListViewItemModel) = itemData(at: indexPath)
61 | let (item, model) = data
62 | guard let cell = item as? ListViewItemModelInjectable, let injector = cell.itemModel else { return item }
63 | injector(model)
64 | return item
65 | }
66 |
67 | override open func listableView(_ listableView: ListableView, viewForHeaderFooterAt indexPath: IndexPath, of kind: String) -> Item? {
68 | let data: (Item, ListViewItemModel)? = headerFooterItemData(at: indexPath, of: kind)
69 | guard let (item, model) = data else { return nil }
70 | guard let view = item as? ListViewItemModelInjectable, let injector = view.itemModel else { return item }
71 | injector(model)
72 | return item
73 | }
74 |
75 | override open func listableView(_ listableView: ListableView, sizeForItemAt indexPath: IndexPath) -> CGSize {
76 | return sections[indexPath.section].cells[indexPath.row].size
77 | }
78 |
79 | override open func listableView(_ listableView: ListableView, estimatedHeightForItemAt indexPath: IndexPath) -> CGFloat {
80 | return sections[indexPath.section].cells[indexPath.row].estimatedHeight
81 | }
82 |
83 | override open func listableView(_ listableView: ListableView, heightForHeaderInSection section: Int) -> CGFloat {
84 | return sections[section].header?.height ?? 0.0
85 | }
86 |
87 | override open func listableView(_ listableView: ListableView, heightForFooterInSection section: Int) -> CGFloat {
88 | return sections[section].footer?.height ?? 0.0
89 | }
90 |
91 | override open func listableView(_ listableView: ListableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
92 | return sections[section].header?.estimatedHeight ?? 0.0
93 | }
94 |
95 | override open func listableView(_ listableView: ListableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
96 | return sections[section].footer?.estimatedHeight ?? 0.0
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Source/ListKit/Core/EquatableItemModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EquatableListViewItemModel.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 01/01/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /*
13 | -- `EquatableListViewItemModel` is an internal struct that gets injected into
14 | dwifft calculator
15 | */
16 |
17 | struct EquatableListViewItemModel: ListViewItemModel, Equatable {
18 |
19 | var id: String
20 | var reuseIdentifier: String
21 | var height: CGFloat
22 | var width: CGFloat
23 |
24 | init(id: String, reuseIdentifier: String, height: CGFloat, width: CGFloat = 0.0) {
25 | self.id = id
26 | self.reuseIdentifier = reuseIdentifier
27 | self.height = height
28 | self.width = width
29 | }
30 |
31 | public static func ==(lhs: EquatableListViewItemModel, rhs: EquatableListViewItemModel) -> Bool {
32 | return lhs.id == rhs.id
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Source/ListKit/Core/ListViewItemModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListViewItemModel.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 04/01/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /*
13 | -- `ListViewItemModel` is used to define a cell, header or footer
14 |
15 | -- conform UITableViewCell/UICollectionView or UITableViewHeaderFooterView models to `ListViewItemModel` protocol.
16 |
17 | -- `id` is used to distingiush any 2 cell-model so it should be unique.
18 |
19 | -- return `UITableViewAutomaticDimension` for automatic height calculation or some constant as height.
20 | */
21 |
22 | public protocol ListViewItemModel {
23 |
24 | var id: String { get }
25 | var width: CGFloat { get }
26 | var height: CGFloat { get }
27 | var estimatedHeight: CGFloat { get }
28 | var reuseIdentifier: String { get }
29 | }
30 |
31 | public extension ListViewItemModel {
32 |
33 | var height: CGFloat { return UITableViewAutomaticDimension }
34 | var width: CGFloat { return 0.0 }
35 | var estimatedHeight: CGFloat { return 100.0 }
36 |
37 | var size: CGSize { return CGSize(width: width, height: height) }
38 | }
39 |
40 |
41 | /*
42 | -- Internal logic
43 | */
44 |
45 | extension ListViewItemModel {
46 |
47 | var model: EquatableListViewItemModel {
48 | return EquatableListViewItemModel(
49 | id: id,
50 | reuseIdentifier: reuseIdentifier,
51 | height: height,
52 | width: width
53 | )
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Source/ListKit/Core/ListViewItemModelInjectable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListViewItemModelInjectable.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 04/01/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /*
12 | -- conform custom UITableViewCell or UICollectionViewCell to `ItemUI`
13 | protocol to make sure that RGListKit calls `configure(withModel:)` method
14 |
15 | -- RGListKit internally calls `configure(withModel:)` inside
16 | `cellForRowAt(:)` (for table) & `cellForItemAt(:)` (for collection-view)
17 | */
18 |
19 | public protocol ListViewItemModelInjectable {
20 |
21 | var itemModel: ((ListViewItemModel) -> Void)? { get set }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/ListKit/Core/ListableDiffCalculator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListableDiffCalculator.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 03/05/17.
6 | // Copyright © 2017 Ritesh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import ProtoKit
12 |
13 | /*
14 | -- `ListableDiffCalculator` is an internal protocol which bridge the gap
15 | between Table/Collection-DiffCalculator
16 |
17 | -- `batchReload(:)` is called to perform batch updates on Table/Collection-View
18 | */
19 |
20 | public protocol ListableDiffCalculator {
21 |
22 | func batchReload(_ sections: [SectionModel])
23 | }
24 |
25 | public protocol DiffableListView: ListableView {
26 |
27 | var diffCalculator: ListableDiffCalculator { get }
28 | }
29 |
30 | extension UICollectionView: DiffableListView {
31 |
32 | public var diffCalculator: ListableDiffCalculator { return RGCollectionViewDiffCalculator(collectionView: self) }
33 | }
34 |
35 | extension UITableView: DiffableListView {
36 |
37 | public var diffCalculator: ListableDiffCalculator { return RGTableViewDiffCalculator(tableView: self) }
38 | }
39 |
--------------------------------------------------------------------------------
/Source/ListKit/Core/SectionModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SectionModel.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 04/01/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /*
12 | -- use `SectionModel` to define a model for a reusable section-view (table or collection)
13 |
14 | -- `id` is used to distinguish any 2 section-model so it should be unique.
15 |
16 | -- cell, header & footer --> all follow the same `ListViewItemModel` protocol since
17 | all are reusable and need the same info while configuring them.
18 | */
19 |
20 | public struct SectionModel: Equatable {
21 |
22 | public var id: String
23 | public var cells: [ListViewItemModel]
24 | public var header: ListViewItemModel?
25 | public var footer: ListViewItemModel?
26 |
27 | public init(id: String, cells: [ListViewItemModel], header: ListViewItemModel? = nil, footer: ListViewItemModel? = nil) {
28 | self.id = id
29 | self.header = header
30 | self.footer = footer
31 | self.cells = cells
32 | }
33 |
34 | public static func ==(lhs: SectionModel, rhs: SectionModel) -> Bool {
35 | return lhs.id == rhs.id
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Source/ListKit/Extension/Array+RGListKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+RGListKit.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 13/05/17.
6 | // Copyright © 2017 Ritesh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array {
12 |
13 | mutating func replace(_ handler: Closure, with newElement: Element) {
14 | guard let idx = index(where: handler) else { return }
15 | self[idx] = newElement
16 | }
17 |
18 | func replaced(_ handler: Closure, with newElement: Element) -> [Element] {
19 | var items = self
20 | items.replace(handler, with: newElement)
21 | return items
22 | }
23 |
24 | func appended(with newItems: [Element]) -> [Element] {
25 | var items = self
26 | items.append(contentsOf: newItems)
27 | return items
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Source/ListKit/Extension/ListableViewDatasource+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListableViewDatasource+Extension.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 13/05/17.
6 | // Copyright © 2017 Ritesh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /*
12 | -- helper methods which one can use with both UITable/UICollectionView
13 | */
14 |
15 | public extension DiffableListViewHolder {
16 |
17 | /* returns a section from the list of sections based on a closure that returns a bool */
18 |
19 | public func section(_ handler: Closure) -> SectionModel? {
20 | return sections.filter(handler).first
21 | }
22 |
23 | /*
24 | - replaces a section with a new section
25 | - `sectionHandler` closure contains the required section that needs to be replaced
26 | - return the new section inside `sectionHandler` closure
27 | */
28 | public func replace(
29 | section filterHandler: Closure,
30 | with sectionHandler: Closure)
31 | {
32 | guard let oldSection = section(filterHandler) else { return }
33 | let newSection = sectionHandler(oldSection)
34 | guard let idx = sections.index(where: { $0.id == oldSection.id }) else { return }
35 | sections[idx] = newSection
36 | }
37 |
38 | /*
39 | - collapses the required section (removes all the items in the required section)
40 | - returns the items in the section which has been collapsed
41 | */
42 | public func collapseSection(_ handler: Closure) -> [T] {
43 | var items = [T]()
44 | replace(section: handler) { (section) -> SectionModel in
45 | var newSection = section
46 | items = newSection.cells as! [T]
47 | newSection.cells = []
48 | return newSection
49 | }
50 | return items
51 | }
52 |
53 | /*
54 | - expands the required section (inserts all the items in the required section)
55 | - new items need to be injected to the function (collapseSection(:) returns the collapsed items)
56 | */
57 | public func expandSection(_ handler: Closure, withItems items: [ListViewItemModel]) {
58 | replace(section: handler) { (section) -> SectionModel in
59 | var newSection = section
60 | newSection.cells = items
61 | return newSection
62 | }
63 | }
64 | }
65 |
66 | extension SectionModel {
67 |
68 | public func item(_ handler: Closure) -> ListViewItemModel? {
69 | return cells.filter(handler).first
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Source/Reactive/Reactive.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Reactive.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 04/10/17.
6 | // Copyright © 2017 Ritesh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveSwift
11 | import ReactiveCocoa
12 | import ProtoKit
13 |
14 | public protocol ReactiveListViewItemModelInjectable {
15 |
16 | var itemModel: MutableProperty { get }
17 | }
18 |
19 |
20 | public protocol ReactiveStackViewItemModelInjectable {
21 |
22 | var itemModel: MutableProperty { get }
23 | }
24 |
25 | public extension Reactive where Base: ReactiveDiffableListViewHolder {
26 |
27 | var sections: BindingTarget<[SectionModel]> {
28 | return makeBindingTarget { $0.sections = $1 }
29 | }
30 | }
31 |
32 | public extension Reactive where Base: ReactiveStackViewHolder {
33 |
34 | var itemModels: BindingTarget<[StackViewItemModel]> {
35 | return makeBindingTarget { $0.itemModels = $1 }
36 | }
37 | }
38 |
39 | open class ReactiveDiffableListViewHolder: DiffableListViewHolder {
40 |
41 | override open func listableView(_ listableView: ListableView, itemForItemAt indexPath: IndexPath) -> Item {
42 | let data: (Item, ListViewItemModel) = itemData(at: indexPath)
43 | let (item, model) = data
44 | guard let cell = item as? ReactiveListViewItemModelInjectable else { return item }
45 | cell.itemModel.value = model
46 | return item
47 | }
48 |
49 | override open func listableView(_ listableView: ListableView, viewForHeaderFooterAt indexPath: IndexPath, of kind: String) -> Item? {
50 | let data: (Item, ListViewItemModel)? = headerFooterItemData(at: indexPath, of: kind)
51 | guard let (item, model) = data else { return nil }
52 | guard let view = item as? ReactiveListViewItemModelInjectable else { return item }
53 | view.itemModel.value = model
54 | return item
55 | }
56 | }
57 |
58 | open class ReactiveStackViewHolder: StackViewHolder {
59 |
60 | override open func listableView(_ listableView: ListableView, itemForItemAt indexPath: IndexPath) -> Item {
61 | let model = itemModels[indexPath.row]
62 | let item: Item = listableView.reusableItem(withIdentifier: holderType.typeName, for: indexPath)
63 | guard let holder = item as? StackViewItemHolder else { return item }
64 | let contentView = holder.attach(itemType: model.itemType)
65 | guard let stackItem = contentView as? ReactiveStackViewItemModelInjectable else { return item }
66 | stackItem.itemModel.value = model
67 | return item
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Source/StackViewKit/StackViewCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StackViewCollectionViewCell.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 01/12/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import ProtoKit
12 |
13 | final class StackViewCollectionViewCell: UICollectionViewCell {
14 |
15 | var item: Nibable?
16 | }
17 |
18 | extension StackViewCollectionViewCell: StackViewItemHolder {}
19 |
--------------------------------------------------------------------------------
/Source/StackViewKit/StackViewCollectionViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Source/StackViewKit/StackViewHolder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StackViewHolder
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 03/12/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import ProtoKit
12 |
13 | open class StackViewHolder: ListViewHolder {
14 |
15 | let holderType = StackViewCollectionViewCell.self
16 | let dynamicConstraint: NSLayoutConstraint
17 | let isVertical: Bool
18 |
19 | public init(listView: ListableView, dynamicConstraint: NSLayoutConstraint, isVertical: Bool = true) {
20 | self.dynamicConstraint = dynamicConstraint
21 | self.isVertical = isVertical
22 | super.init(listView: listView)
23 | let bundle = Bundle(for: holderType)
24 | let nib = UINib(nibName: holderType.typeName, bundle: bundle)
25 | listView.registerItem(with: nib, for: holderType.typeName)
26 | }
27 |
28 | public var itemModels: [StackViewItemModel] = [] {
29 | didSet {
30 | dynamicConstraint.constant = dynamicConstraintValue
31 | listView.reloadItems()
32 | }
33 | }
34 |
35 | override open func numberOfSectionsIn(listableView: ListableView) -> Int {
36 | return 1
37 | }
38 |
39 | override open func listableView(_ listableView: ListableView, numberOfItemsInSection section: Int) -> Int {
40 | return itemModels.count
41 | }
42 |
43 | override open func listableView(_ listableView: ListableView, itemForItemAt indexPath: IndexPath) -> Item {
44 | let model = itemModels[indexPath.row]
45 | let item: Item = listableView.reusableItem(withIdentifier: holderType.typeName, for: indexPath)
46 | guard let holder = item as? StackViewItemHolder else { return item }
47 | let contentView = holder.attach(itemType: model.itemType)
48 | guard let stackItem = contentView as? StackViewItemModelInjectable else { return item }
49 | stackItem.itemModel?(model)
50 | return item
51 | }
52 |
53 | override open func listableView(_ listableView: ListableView, estimatedHeightForItemAt indexPath: IndexPath) -> CGFloat {
54 | return itemModels[indexPath.row].estimatedHeight
55 | }
56 |
57 | override open func listableView(_ listableView: ListableView, sizeForItemAt indexPath: IndexPath) -> CGSize {
58 | return itemModels[indexPath.row].size
59 | }
60 | }
61 |
62 | extension StackViewHolder {
63 |
64 | var dynamicConstraintValue: CGFloat {
65 | if isVertical { return itemModels.reduce(0, { $0 + $1.height }) }
66 | else { return itemModels.reduce(0, { $0 + $1.width }) }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Source/StackViewKit/StackViewItemHolder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StackViewItemHolder.swift
3 | // GenericTable
4 | //
5 | // Created by Ritesh Gupta on 03/12/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import ProtoKit
12 |
13 | public protocol StackViewItemHolder: ReusableListViewItem {
14 |
15 | var item: Nibable? { get set }
16 | var holderView: UIView { get }
17 | }
18 |
19 | public extension StackViewItemHolder {
20 |
21 | public func attach(itemType: Nibable.Type) -> Nibable {
22 | guard let item = self.item else {
23 | let _item = itemType.instance()
24 | self.item = _item
25 | holderView.attach(subview: _item)
26 | return _item
27 | }
28 | return item
29 | }
30 | }
31 |
32 | public extension StackViewItemHolder where Self: UITableViewCell {
33 |
34 | public var holderView: UIView { return contentView }
35 | }
36 |
37 | public extension StackViewItemHolder where Self: UICollectionViewCell {
38 |
39 | public var holderView: UIView { return contentView }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/Source/StackViewKit/StackViewItemModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StackViewItemModel.swift
3 | // GenericTable
4 | //
5 | // Created by Ritesh Gupta on 03/12/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import ProtoKit
12 |
13 | public protocol StackViewItemModel: ListViewItemModel {
14 |
15 | var itemType: Nibable.Type { get }
16 | }
17 |
18 | public extension StackViewItemModel {
19 |
20 | public var id: String { return UUID().uuidString }
21 | public var reuseIdentifier: String { return "" }
22 | }
23 |
--------------------------------------------------------------------------------
/Source/StackViewKit/StackViewItemProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StackViewItemProvider.swift
3 | // GenericTable
4 | //
5 | // Created by Ritesh Gupta on 03/12/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import ProtoKit
12 |
13 | public protocol StackViewItemModelInjectable: Nibable {
14 |
15 | var itemModel: ((StackViewItemModel) -> Void)? { get set }
16 | }
17 |
--------------------------------------------------------------------------------
/Source/StackViewKit/UIView+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Extension.swift
3 | // RGListKit
4 | //
5 | // Created by Ritesh Gupta on 01/12/17.
6 | // Copyright © 2017 Ritesh Gupta. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | public extension UIView {
13 |
14 | public func attach(subview view: UIView) {
15 | view.translatesAutoresizingMaskIntoConstraints = false
16 | view.frame = bounds
17 | addSubview(view)
18 | let views = ["view": view]
19 | addConstraints("H".constraints(for: views))
20 | addConstraints("V".constraints(for: views))
21 | }
22 | }
23 |
24 | fileprivate extension String {
25 |
26 | // because you can 🙃
27 | func constraints(`for` views: [String: Any]) -> [NSLayoutConstraint] {
28 | return NSLayoutConstraint.constraints(withVisualFormat: "\(self):|[view]|", options: [], metrics: nil, views: views)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------