TableKit/Tablet/TableRowBuilder.swift

247 lines
8.5 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Copyright (c) 2015 Max Sokolov https://twitter.com/max_sokolov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import UIKit
public typealias ReturnValue = AnyObject?
/**
Responsible for building cells of given type and passing items to them.
*/
public class TableBaseRowBuilder<DataType, CellType where CellType: UITableViewCell> : RowBuilder {
public private(set) weak var tableDirector: TableDirector?
private var actions = [String: ActionHandler<DataType, CellType>]()
private var items = [DataType]()
public let reusableIdentifier: String
public var numberOfRows: Int {
return items.count
}
public init(item: DataType, id: String? = nil) {
reusableIdentifier = id ?? String(CellType)
items.append(item)
}
public init(items: [DataType]? = nil, id: String? = nil) {
reusableIdentifier = id ?? String(CellType)
if let items = items {
self.items.appendContentsOf(items)
}
}
public func rowHeight(index: Int) -> CGFloat {
return UITableViewAutomaticDimension
}
public func estimatedRowHeight() -> CGFloat {
return 44
}
// MARK: - Chaining actions -
public func action(key: String, handler: (data: ActionData<DataType, CellType>) -> Void) -> Self {
actions[key] = .Handler(handler)
return self
}
public func action(type: ActionType, handler: (data: ActionData<DataType, CellType>) -> Void) -> Self {
actions[type.key] = .Handler(handler)
return self
}
public func valueAction(type: ActionType, handler: (data: ActionData<DataType, CellType>) -> ReturnValue) -> Self {
actions[type.key] = .ValueHandler(handler)
return self
}
public func invoke(action action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? {
if let action = actions[action.key] {
return action.invoke(ActionData(cell: cell as? CellType, indexPath: indexPath, item: items[itemIndex], itemIndex: itemIndex, userInfo: userInfo))
}
return nil
}
private func registerCell(inTableView tableView: UITableView) {
if tableView.dequeueReusableCellWithIdentifier(reusableIdentifier) != nil {
return
}
let resource = String(CellType)
let bundle = NSBundle(forClass: CellType.self)
if let _ = bundle.pathForResource(resource, ofType: "nib") { // existing cell
tableView.registerNib(UINib(nibName: resource, bundle: bundle), forCellReuseIdentifier: reusableIdentifier)
} else {
tableView.registerClass(CellType.self, forCellReuseIdentifier: reusableIdentifier)
}
}
public func willUpdateDirector(director: TableDirector?) {
tableDirector = director
}
// MARK: - Items manipulation -
public func append(items items: [DataType]) {
self.items.appendContentsOf(items)
}
public func clear() {
items.removeAll()
}
}
/**
Responsible for building configurable cells of given type and passing items to them.
*/
public class TableRowBuilder<DataType, CellType: ConfigurableCell where CellType.T == DataType, CellType: UITableViewCell> : TableBaseRowBuilder<DataType, CellType> {
public init(item: DataType) {
super.init(item: item, id: CellType.reusableIdentifier())
}
public init(items: [DataType]? = nil) {
super.init(items: items, id: CellType.reusableIdentifier())
}
public override func invoke(action action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? {
if case .configure = action {
(cell as? CellType)?.configure(items[itemIndex])
}
return super.invoke(action: action, cell: cell, indexPath: indexPath, itemIndex: itemIndex, userInfo: userInfo)
}
public override func estimatedRowHeight() -> CGFloat {
return CGFloat(CellType.estimatedHeight())
}
}
public class TablePrototypeRowBuilder<DataType: Hashable, CellType: ConfigurableCell where CellType.T == DataType, CellType: UITableViewCell> : TableBaseRowBuilder<DataType, CellType> {
private var cachedHeights = [Int: CGFloat]()
private var prototypeCell: CellType?
public init(item: DataType) {
super.init(item: item, id: CellType.reusableIdentifier())
}
public init(items: [DataType]? = nil) {
super.init(items: items, id: CellType.reusableIdentifier())
}
public override func estimatedRowHeight() -> CGFloat {
return UITableViewAutomaticDimension
}
func heightCall(item: DataType, width: CGFloat) -> CGFloat {
guard let cell = prototypeCell else { return 0 }
cell.bounds = CGRectMake(0, 0, width, cell.bounds.height)
cell.configure(item)
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1
}
// прехит по мере скроллинга в бэк
// прехит не должен прехитить то что уже есть (показанное)
// по мере скроллинга уметь отменять перхит ()
public override func rowHeight(index: Int) -> CGFloat {
guard let cell = prototypeCell else { return 0 }
let item = items[index]
if let height = cachedHeights[item.hashValue] {
return height
}
let height = heightCall(item, width: tableDirector?.tableView?.bounds.size.width ?? 0)
cachedHeights[item.hashValue] = height
print(tableDirector?.tableView?.bounds.size.width, cell.bounds.height, height)
return height
}
public func preheat(item: DataType) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let height = self.heightCall(item, width: 0)
// check if actual height exists
// calc height
//let heights = self.items.map { self.heightZ($0) }
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// check if table width is actual
// store height in cache
}
}
}
public override func invoke(action action: ActionType, cell: UITableViewCell?, indexPath: NSIndexPath, itemIndex: Int, userInfo: [NSObject: AnyObject]?) -> AnyObject? {
if case .configure = action {
(cell as? CellType)?.configure(items[itemIndex])
}
return super.invoke(action: action, cell: cell, indexPath: indexPath, itemIndex: itemIndex, userInfo: userInfo)
}
public override func willUpdateDirector(director: TableDirector?) {
tableDirector = director
if let tableView = director?.tableView, cell = tableView.dequeueReusableCellWithIdentifier(reusableIdentifier) as? CellType {
prototypeCell = cell
}
}
}
public func +=<DataType, CellType>(left: TableBaseRowBuilder<DataType, CellType>, right: DataType) {
left.append(items: [right])
}
public func +=<DataType, CellType>(left: TableBaseRowBuilder<DataType, CellType>, right: [DataType]) {
left.append(items: right)
}