diff --git a/CHANGELOG.md b/CHANGELOG.md index aafb27d..af6e31e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.7.0 + +- Adds new `HeaderFooterReusable` protocol to allow providing separate `Reusable` types for rendering a section's header and footer. +- Adds letter spacing and line height to `TextStyle`. +- Adds target offset to `willEndDragging` signal of `ScrollViewDelegate`. +- Adds will display cell signal to `CollectionViewDelegate`. + ## 1.6.3 - Performace. Added custom `count` implementation for `TableSection` to improve performance of e.g. `Table.isValidIndex` that might be called a lot for large tables. diff --git a/Documentation/Tables.md b/Documentation/Tables.md index cdf293f..39c0f59 100644 --- a/Documentation/Tables.md +++ b/Documentation/Tables.md @@ -77,6 +77,28 @@ If you are ok with losing type information you can also consider using the `Mixe var mixedTable = Table<(), MixedReusable>(rows: [.init(1), .init("A"), .init("B"), .init(2)]) ``` +## HeaderFooterReusable + +If you conform your `Table`'s `Section` type to `Reusable` a table's section will be rendered by the view provided by `makeAndConfigure()`. However if you like to provide both a header and a footer view from your section model data, you can conform your `Section` type to `HeaderFooterReusable` and provide separate header and footer types for rendering: + +```swift +struct MySection { ... } + +extension MySection: HeaderFooterReusable { + var header: MyHeaderType { ... } + var footer: MyFooterType { ... } +} +``` + +As it is common with header and footer that are just strings, Form includes the `HeaderFooter` type for your convenience: + +```swift +struct HeaderFooter: HeaderFooterReusable, Hashable { + var header: String + var footer: String +} +``` + ## TableKit Once you have your data in a `Table` and your `Row` and `Section` types conforming to `Reusable`, you can construct a `TableKit` that will provide a `UITableView` set up with a proper data source and delegate: diff --git a/Examples/Demo/Example/Contents.swift b/Examples/Demo/Example/Contents.swift index 11601f4..6045f35 100644 --- a/Examples/Demo/Example/Contents.swift +++ b/Examples/Demo/Example/Contents.swift @@ -116,6 +116,10 @@ extension UIViewController { bag += section.appendRow(title: "TableKit and Nested Either Reusable").append(.chevron).onValueDisposePrevious { present { $0.presentTableUsingKitAndNestedEitherReusable(style: style) } } + + bag += section.appendRow(title: "TableKit and HeaderFooterReusable").append(.chevron).onValueDisposePrevious { + present { $0.presentTableUsingKitAndHeaderFooterReusable(style: style) } + } } bag += self.install(form) diff --git a/Examples/Demo/Example/CustomStyle.swift b/Examples/Demo/Example/CustomStyle.swift index affb9aa..fb94e62 100644 --- a/Examples/Demo/Example/CustomStyle.swift +++ b/Examples/Demo/Example/CustomStyle.swift @@ -86,7 +86,7 @@ extension TextStyle { static let regularButton = TextStyle(font: .regularButton, color: .mintGreenDark, alignment: .center) static let disabledButton = TextStyle(font: .regularButton, color: .textGray, alignment: .center) static let whiteButton = TextStyle(font: .regularButton, color: .white, alignment: .center) - static let headerText = TextStyle(font: .headerText, color: .textGray).restyled { $0.kerning = 0.8 }.uppercased + static let headerText = TextStyle(font: .headerText, color: .textGray).restyled { $0.letterSpacing = 0.8 }.uppercased static let footer = smallText.centerAligned.multilined() static let headerBlack = headerText.colored(.black).multilined() static let header = headerText.multilined() diff --git a/Examples/Demo/Example/Tables.swift b/Examples/Demo/Example/Tables.swift index edc41dc..ab114f5 100644 --- a/Examples/Demo/Example/Tables.swift +++ b/Examples/Demo/Example/Tables.swift @@ -195,6 +195,21 @@ extension UIViewController { return bag } + + func presentTableUsingKitAndHeaderFooterReusable(style: DynamicTableViewFormStyle) -> Disposable { + displayableTitle = "TableKit and HeaderFooterReusable" + let bag = DisposeBag() + + let tableKit = TableKit(table: sectionTable, style: style, bag: bag) + bag += self.install(tableKit.view) + + bag += self.navigationItem.addItem(UIBarButtonItem(title: "Swap"), position: .right).onValue { + swap(§ionTable, &swapSectionTable) + tableKit.set(sectionTable) + } + + return bag + } } private var table = Table(sections: [("Header 1", 0..<5), ("Header 2", 5..<10)]) @@ -220,3 +235,6 @@ extension Double: Reusable { }) } } + +private var sectionTable = Table(sections: [(HeaderFooter(header: "Header 1", footer: "Footer 1"), 0..<5), (HeaderFooter(header: "Header 2", footer: "Footer 2"), 5..<10)]) +private var swapSectionTable = Table(sections: [(HeaderFooter(header: "Header 1", footer: "Footer 1"), 0..<2), (HeaderFooter(header: "Header 1b", footer: "Footer 1b"), 3..<7), (HeaderFooter(header: "Header 2", footer: "Footer 2"), 7..<10)]) diff --git a/Form/Info.plist b/Form/Info.plist index 2bd8f0d..01a700f 100644 --- a/Form/Info.plist +++ b/Form/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.6.3 + 1.7.0 CFBundleSignature ???? CFBundleVersion diff --git a/Form/Reusable.swift b/Form/Reusable.swift index a6afa5b..a84e321 100644 --- a/Form/Reusable.swift +++ b/Form/Reusable.swift @@ -147,3 +147,16 @@ extension Either: Reusable where Left: Reusable, Right: Reusable, Left.ReuseType } } } + +/// Conforming types specifies what `Reusable` conforming types to use for a section's header and footer. +/// If you need both a header and footer you typically conforms your `Table`'s `Section` type to this protocol. +public protocol HeaderFooterReusable { + associatedtype Header: Reusable + associatedtype Footer: Reusable + + /// The value use to render a section's header + var header: Header { get } + + /// The value use to render a section's footer + var footer: Footer { get } +} diff --git a/Form/TableKit.swift b/Form/TableKit.swift index 68ab9b8..e34de42 100644 --- a/Form/TableKit.swift +++ b/Form/TableKit.swift @@ -251,6 +251,22 @@ public extension TableKit where Row: Reusable, Row.ReuseType: ViewRepresentable, } } +public extension TableKit where Row: Reusable, Row.ReuseType: ViewRepresentable, Section: HeaderFooterReusable, Section.Header.ReuseType: ViewRepresentable, Section.Footer.ReuseType: ViewRepresentable { + /// Creates a new instance + /// - Parameters: + /// - table: The initial table. Defaults to an empty table. + /// - bag: A bag used to add table kit activities. + convenience init(table: Table = Table(), style: DynamicTableViewFormStyle = .default, view: UITableView? = nil, bag: DisposeBag) { + self.init(table: table, style: style, view: view, bag: bag, headerForSection: { table, section in + table.dequeueHeaderFooterView(forItem: section.header, style: style.header, formStyle: style.form, reuseIdentifier: "header") + }, footerForSection: { table, section in + table.dequeueHeaderFooterView(forItem: section.footer, style: style.footer, formStyle: style.form, reuseIdentifier: "footer") + }, cellForRow: { table, row in + table.dequeueCell(forItem: row, style: style) + }) + } +} + extension TableKit: SignalProvider { public var providedSignal: ReadWriteSignal { return ReadSignal(capturing: self.table, callbacker: callbacker).writable(signalOnSet: true) { self.table = $0 } diff --git a/Form/UITableViewHeaderFooterView+Utilities.swift b/Form/UITableViewHeaderFooterView+Utilities.swift index d045a7c..fd7a4af 100644 --- a/Form/UITableViewHeaderFooterView+Utilities.swift +++ b/Form/UITableViewHeaderFooterView+Utilities.swift @@ -146,6 +146,16 @@ extension String: Reusable { } } +public struct HeaderFooter: HeaderFooterReusable, Hashable { + public var header: String + public var footer: String + + public init(header: String, footer: String) { + self.header = header + self.footer = footer + } +} + public struct DateHeader: Equatable { public let date: Date public let dateFormatter: DateFormatter diff --git a/FormFramework.podspec b/FormFramework.podspec index 7cc2414..bdf1396 100644 --- a/FormFramework.podspec +++ b/FormFramework.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "FormFramework" - s.version = "1.6.3" + s.version = "1.7.0" s.module_name = "Form" s.summary = "Powerful iOS layout and styling" s.description = <<-DESC