Skip to content

Commit

Permalink
Merge pull request #76 from adly-holler/dat-header
Browse files Browse the repository at this point in the history
iOS Table Header & Footer
  • Loading branch information
joshaber committed Jul 13, 2015
2 parents 8e7a982 + 4c04699 commit 515baca
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 28 deletions.
2 changes: 1 addition & 1 deletion Cartfile
@@ -1 +1 @@
github "joshaber/SwiftBox" "c11e489"
github "joshaber/SwiftBox" "bc875f"
2 changes: 1 addition & 1 deletion Cartfile.resolved
@@ -1,3 +1,3 @@
github "Quick/Nimble" "v0.4.2"
github "Quick/Quick" "v0.3.1"
github "joshaber/SwiftBox" "c11e489f2d7b39aa75f02236b09e81a106ce6fa9"
github "joshaber/SwiftBox" "bc875f7d7c6bc53b1cb57b795050edc7c342f3a9"
135 changes: 114 additions & 21 deletions Few-iOS/TableView.swift
Expand Up @@ -10,7 +10,47 @@ import UIKit

private let defaultRowHeight: CGFloat = 42

private class FewListHeaderFooter: UITableViewHeaderFooterView {
private class FewTableHeaderFooter: UIView {
var didChangeHeight: (FewTableHeaderFooter -> Void)?
private lazy var parentElement: RealizedElement = {[unowned self] in
return RealizedElement(element: Element(), view: self.contentView, parent: nil)
}()

private(set) lazy var contentView: UIView = {[unowned self] in
let v = UIView(frame: self.bounds)
configureViewToAutoresize(v)
self.addSubview(v)
return v
}()

private var realizedElement: RealizedElement?

func updateWithElement(element: Element) {
if let oldRealizedElement = realizedElement {
if element.canDiff(oldRealizedElement.element) {
element.applyDiff(oldRealizedElement.element, realizedSelf: oldRealizedElement)
} else {
oldRealizedElement.remove()

realizedElement = element.realize(parentElement)
}
} else {
realizedElement = element.realize(parentElement)
}

realizedElement?.layoutFromRoot()
let layout = element.assembleLayoutNode().layout(maxWidth: bounds.width)
let oldHeight = bounds.height
layout.apply(contentView)

if contentView.frame.height != oldHeight {
bounds.size.height = contentView.frame.height
didChangeHeight?(self)
}
}
}

private class FewSectionHeaderFooter: UITableViewHeaderFooterView {
private lazy var parentElement: RealizedElement = {[unowned self] in
return RealizedElement(element: Element(), view: self.contentView, parent: nil)
}()
Expand Down Expand Up @@ -69,6 +109,8 @@ private class TableViewHandler: NSObject, UITableViewDelegate, UITableViewDataSo
var elements: [[Element]]
var headers: [Element?]
var footers: [Element?]
let headerView = FewTableHeaderFooter()
let footerView = FewTableHeaderFooter()

var selectionChanged: (NSIndexPath -> ())?

Expand All @@ -78,17 +120,35 @@ private class TableViewHandler: NSObject, UITableViewDelegate, UITableViewDataSo
self.headers = headers
self.footers = footers
super.init()
tableView.registerClass(FewListHeaderFooter.self, forHeaderFooterViewReuseIdentifier: headerKey)
tableView.registerClass(FewListHeaderFooter.self, forHeaderFooterViewReuseIdentifier: footerKey)
headerView.didChangeHeight = {[weak tableView] view in
if let tableView = tableView where tableView.tableHeaderView == view {
// set header again to force table view to update contentSize & row offset
tableView.tableHeaderView = view
// begin/end updates because otherwise table view
// randomly miscomputes its row offset after header height change
UIView.performWithoutAnimation {
tableView.beginUpdates()
tableView.endUpdates()
}
}
}
footerView.didChangeHeight = {[weak tableView] view in
// set footer again to force table view to update contentSize
if let tableView = tableView where tableView.tableFooterView == view {
tableView.tableFooterView = view
}
}
tableView.registerClass(FewSectionHeaderFooter.self, forHeaderFooterViewReuseIdentifier: headerKey)
tableView.registerClass(FewSectionHeaderFooter.self, forHeaderFooterViewReuseIdentifier: footerKey)
tableView.registerClass(FewListCell.self, forCellReuseIdentifier: cellKey)
tableView.delegate = self
tableView.dataSource = self
}

func update(elements: [[Element]], headers: [Element?], footers: [Element?]) {
func update(elements: [[Element]], sectionHeaders: [Element?], sectionFooters: [Element?]) {
self.elements = elements
self.headers = headers
self.footers = footers
self.headers = sectionHeaders
self.footers = sectionFooters

updateCachedHeights()
tableView.reloadData()
Expand Down Expand Up @@ -132,7 +192,7 @@ private class TableViewHandler: NSObject, UITableViewDelegate, UITableViewDataSo
@objc func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section < headers.count {
if let header = headers[section] {
let view = tableView.dequeueReusableHeaderFooterViewWithIdentifier(headerKey) as! FewListHeaderFooter
let view = tableView.dequeueReusableHeaderFooterViewWithIdentifier(headerKey) as! FewSectionHeaderFooter
view.updateWithElement(header)
return view
}
Expand All @@ -152,7 +212,7 @@ private class TableViewHandler: NSObject, UITableViewDelegate, UITableViewDataSo
@objc func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if section < footers.count {
if let footer = footers[section] {
let view = tableView.dequeueReusableHeaderFooterViewWithIdentifier(footerKey) as! FewListHeaderFooter
let view = tableView.dequeueReusableHeaderFooterViewWithIdentifier(footerKey) as! FewSectionHeaderFooter
view.updateWithElement(footer)
return view
}
Expand Down Expand Up @@ -187,27 +247,30 @@ public class TableView: Element {
private let elements: [[Element]]
private let selectionChanged: (NSIndexPath -> ())?
private let selectedRow: NSIndexPath?
private let headers: [Element?]
private let footers: [Element?]
private let sectionHeaders: [Element?]
private let sectionFooters: [Element?]
private let header: Element?
private let footer: Element?

public init(_ elements: [[Element]], headers: [Element?] = [], footers: [Element?] = [], selectedRow: NSIndexPath? = nil, selectionChanged: (NSIndexPath -> ())? = nil) {
public init(_ elements: [[Element]], sectionHeaders: [Element?] = [], sectionFooters: [Element?] = [], header: Element? = nil, footer: Element? = nil, selectedRow: NSIndexPath? = nil, selectionChanged: (NSIndexPath -> ())? = nil) {
self.elements = elements
self.selectionChanged = selectionChanged
self.selectedRow = selectedRow
self.headers = headers
self.footers = footers
self.sectionHeaders = sectionHeaders
self.sectionFooters = sectionFooters
self.header = header
self.footer = footer
}

// MARK: -

public override func applyDiff(old: Element, realizedSelf: RealizedElement?) {
super.applyDiff(old, realizedSelf: realizedSelf)

if let tableView = realizedSelf?.view as? FewTableView {
let handler = tableView.handler
if let tableView = realizedSelf?.view as? FewTableView, handler = tableView.handler, oldSelf = old as? TableView {

handler?.update(elements, headers: headers, footers: footers)
handler?.selectionChanged = selectionChanged
handler.update(elements, sectionHeaders: sectionHeaders, sectionFooters: sectionFooters)
handler.selectionChanged = selectionChanged
let tableSelected = tableView.indexPathForSelectedRow()
if tableSelected != selectedRow {
if let selectedRow = selectedRow {
Expand All @@ -216,16 +279,46 @@ public class TableView: Element {
tableView.deselectRowAtIndexPath(tableSelected, animated: false)
}
}
if let header = header {
handler.headerView.updateWithElement(header)
if tableView.tableHeaderView != handler.headerView {
tableView.tableHeaderView = handler.headerView
}
} else if tableView.tableHeaderView == handler.headerView {
tableView.tableHeaderView = nil
}
if let footer = footer {
handler.footerView.updateWithElement(footer)
if tableView.tableFooterView != handler.footerView {
tableView.tableFooterView = handler.footerView
}
} else if tableView.tableFooterView == handler.footerView {
tableView.tableFooterView = nil
}

}

}

public override func createView() -> ViewType {
let tableView = FewTableView(frame: CGRectZero)
tableView.handler = TableViewHandler(tableView: tableView, elements: elements, headers: headers, footers: footers)
let tableView = FewTableView()
let handler = TableViewHandler(tableView: tableView, elements: elements, headers: sectionHeaders, footers: sectionFooters)
tableView.handler = handler
tableView.handler?.selectionChanged = selectionChanged
tableView.alpha = alpha
tableView.hidden = hidden

if let header = header {
handler.headerView.updateWithElement(header)
let layout = header.assembleLayoutNode().layout(maxWidth: tableView.frame.width)
layout.apply(handler.headerView)
tableView.tableHeaderView = handler.headerView
}
if let footer = footer {
handler.footerView.updateWithElement(footer)
let layout = footer.assembleLayoutNode().layout(maxWidth: tableView.frame.width)
layout.apply(handler.footerView)
tableView.tableFooterView = handler.footerView
}
return tableView
}

Expand All @@ -235,7 +328,7 @@ public class TableView: Element {
if let scrollView = realizedSelf?.view as? FewTableView {
let handler = scrollView.handler

handler?.update(elements, headers: headers, footers: footers)
handler?.update(elements, sectionHeaders: sectionHeaders, sectionFooters: sectionFooters)
}
}
}
6 changes: 5 additions & 1 deletion FewCore/Element.swift
Expand Up @@ -108,6 +108,8 @@ public class Element {
}

if let realizedSelf = realizedSelf {
realizedSelf.element = self

if let view = realizedSelf.view {
if hidden != view.hidden {
view.hidden = hidden
Expand All @@ -116,7 +118,9 @@ public class Element {
compareAndSetAlpha(view, alpha)
}

realizedSelf.element = self
if old.frame.size != frame.size && !frame.size.isUndefined {
realizedSelf.markNeedsLayout()
}

let childrenDiff = diffElementLists(realizedSelf.children, children)

Expand Down
2 changes: 2 additions & 0 deletions FewDemo-iOS/Info.plist
Expand Up @@ -28,6 +28,8 @@
<array>
<string>armv7</string>
</array>
<key>UIStatusBarHidden</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
Expand Down
29 changes: 25 additions & 4 deletions FewDemo-iOS/ViewController.swift
Expand Up @@ -66,19 +66,40 @@ private func renderRow2(row: Int) -> Element {
])
}

func renderTableView(component: Component<()>, state: ()) -> Element {
struct TableViewDemoState {
var headerHeight: CGFloat
var footerHeight: CGFloat
}

func renderTableView(component: Component<TableViewDemoState>, state: TableViewDemoState) -> Element {
let elements: [Element] = Array(1...100).map { rowNum in
if rowNum % 2 == 0 {
return renderRow1(rowNum)
} else {
return renderRow2(rowNum)
}
}
return TableView([elements], headers: [Label("Hello Header!")], footers: [Label("Hello Footer!")], selectionChanged: println)
return TableView([elements], sectionHeaders: [Label("Section Header!")],
header: Button(attributedTitle: NSAttributedString(string: "Increase Header Height"),
action: {
component.updateState { (var state) in
state.headerHeight += 10
return state
}
}).height(state.headerHeight).width(200),
footer: Button(attributedTitle: NSAttributedString(string: "Increase Footer Height"),
action: {
component.updateState { (var state) in
state.footerHeight += 10
return state
}
}).height(state.footerHeight).width(200),
sectionFooters: [Label("Section Footer!")],
selectionChanged: println)
.flex(1)
}

let TableViewDemo = { Component(initialState: (), render: renderTableView) }
let TableViewDemo = { Component(initialState: TableViewDemoState(headerHeight: 60, footerHeight: 60), render: renderTableView) }

func renderInput(component: Component<String>, state: String) -> Element {
return Element()
Expand All @@ -102,7 +123,7 @@ func renderInput(component: Component<String>, state: String) -> Element {
let InputDemo = { Component(initialState: "", render: renderInput) }

struct AppState {
let tableViewComponent: Component<()>
let tableViewComponent: Component<TableViewDemoState>
let counterComponent: Component<Int>
let inputComponent: Component<String>

Expand Down

0 comments on commit 515baca

Please sign in to comment.