// // ViewController.swift // Sample // // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. // This source code is licensed under the BSD-style license found in the // LICENSE file in the root directory of this source tree. An additional grant // of patent rights can be found in the PATENTS file in the same directory. // // 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 // FACEBOOK 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 import AsyncDisplayKit final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate { struct State { var itemCount: Int var fetchingMore: Bool static let empty = State(itemCount: 20, fetchingMore: false) } enum Action { case beginBatchFetch case endBatchFetch(resultCount: Int) } fileprivate(set) var state: State = .empty init() { super.init(node: ASTableNode()) node.delegate = self node.dataSource = self } required init?(coder aDecoder: NSCoder) { fatalError("storyboards are incompatible with truth and beauty") } // MARK: ASTableView data source and delegate. func tableView(_ tableView: ASTableView, nodeForRowAt indexPath: IndexPath) -> ASCellNode { // Should read the row count directly from table view but // https://github.com/facebook/AsyncDisplayKit/issues/1159 let rowCount = self.tableView(tableView, numberOfRowsInSection: 0) if state.fetchingMore && (indexPath as NSIndexPath).row == rowCount - 1 { return TailLoadingCellNode() } let node = ASTextCellNode() node.text = String(format: "[%ld.%ld] says hello!", (indexPath as NSIndexPath).section, (indexPath as NSIndexPath).row) return node } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { var count = state.itemCount if state.fetchingMore { count += 1 } return count } func tableView(_ tableView: ASTableView, willBeginBatchFetchWith context: ASBatchContext) { /// This call will come in on a background thread. Switch to main /// to add our spinner, then fire off our fetch. DispatchQueue.main.async { let oldState = self.state self.state = ViewController.handleAction(.beginBatchFetch, fromState: oldState) self.renderDiff(oldState) } ViewController.fetchDataWithCompletion { resultCount in let action = Action.endBatchFetch(resultCount: resultCount) let oldState = self.state self.state = ViewController.handleAction(action, fromState: oldState) self.renderDiff(oldState) context.completeBatchFetching(true) } } fileprivate func renderDiff(_ oldState: State) { let tableView = node.view tableView?.beginUpdates() // Add or remove items let rowCountChange = state.itemCount - oldState.itemCount if rowCountChange > 0 { let indexPaths = (oldState.itemCount.. Void) { let time = DispatchTime.now() + Double(Int64(TimeInterval(NSEC_PER_SEC) * 1.0)) / Double(NSEC_PER_SEC) DispatchQueue.main.asyncAfter(deadline: time) { let resultCount = Int(arc4random_uniform(20)) completion(resultCount) } } fileprivate static func handleAction(_ action: Action, fromState state: State) -> State { var state = state switch action { case .beginBatchFetch: state.fetchingMore = true case let .endBatchFetch(resultCount): state.itemCount += resultCount state.fetchingMore = false } return state } }