From 8f10d8ea998fc47dec9d7d05af5e37fde99f474b Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 1 Feb 2016 12:10:38 -0800 Subject: [PATCH] More progress with the Swift example --- examples/Kittens/Sample/ViewController.m | 34 ++++---- .../Swift/Sample.xcodeproj/project.pbxproj | 9 +- .../xcshareddata/xcschemes/Sample.xcscheme | 13 +-- .../contents.xcworkspacedata | 10 +++ examples/Swift/Sample/Info.plist | 2 +- .../Swift/Sample/TailLoadingCellNode.swift | 53 ++++++++++++ examples/Swift/Sample/ViewController.swift | 83 ++++++++++++++++++- 7 files changed, 177 insertions(+), 27 deletions(-) create mode 100644 examples/Swift/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples/Swift/Sample/TailLoadingCellNode.swift diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 63df8deed9..2f2c7136ba 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -165,28 +165,24 @@ static const NSInteger kMaxLitterSize = 100; // max number of kitten cell - (void)tableView:(UITableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - sleep(1); - dispatch_async(dispatch_get_main_queue(), ^{ - - // populate a new array of random-sized kittens - NSArray *moarKittens = [self createLitterWithSize:kLitterBatchSize]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // populate a new array of random-sized kittens + NSArray *moarKittens = [self createLitterWithSize:kLitterBatchSize]; - NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; - - // find number of kittens in the data source and create their indexPaths - NSInteger existingRows = _kittenDataSource.count + 1; - - for (NSInteger i = 0; i < moarKittens.count; i++) { - [indexPaths addObject:[NSIndexPath indexPathForRow:existingRows + i inSection:0]]; - } + NSMutableArray *indexPaths = [[NSMutableArray alloc] init]; + + // find number of kittens in the data source and create their indexPaths + NSInteger existingRows = _kittenDataSource.count + 1; + + for (NSInteger i = 0; i < moarKittens.count; i++) { + [indexPaths addObject:[NSIndexPath indexPathForRow:existingRows + i inSection:0]]; + } - // add new kittens to the data source & notify table of new indexpaths - [_kittenDataSource addObjectsFromArray:moarKittens]; - [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; + // add new kittens to the data source & notify table of new indexpaths + [_kittenDataSource addObjectsFromArray:moarKittens]; + [tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade]; - [context completeBatchFetching:YES]; - }); + [context completeBatchFetching:YES]; }); } diff --git a/examples/Swift/Sample.xcodeproj/project.pbxproj b/examples/Swift/Sample.xcodeproj/project.pbxproj index ac8d78cedf..18f8aba889 100644 --- a/examples/Swift/Sample.xcodeproj/project.pbxproj +++ b/examples/Swift/Sample.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 6C5053DB19EE266A00E385DE /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C5053D919EE266A00E385DE /* Default-667h@2x.png */; }; 6C5053DC19EE266A00E385DE /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */; }; 92E46E91A7D47AEC5B2B2F55 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FC29F18AE7C8C204A5CD4F2 /* Pods.framework */; }; + CCB01CAB1C5FEA6E00CA64C4 /* TailLoadingCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB01CAA1C5FEA6E00CA64C4 /* TailLoadingCellNode.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -25,6 +26,7 @@ 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; 7FC29F18AE7C8C204A5CD4F2 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 841652076B3E9351337AA7C7 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; + CCB01CAA1C5FEA6E00CA64C4 /* TailLoadingCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TailLoadingCellNode.swift; sourceTree = ""; }; E3EE87D12CE3EF73FAE2EF02 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -64,6 +66,7 @@ 050E7C7019D22E19004363C2 /* Sample */ = { isa = PBXGroup; children = ( + CCB01CAA1C5FEA6E00CA64C4 /* TailLoadingCellNode.swift */, 050E7C7319D22E19004363C2 /* AppDelegate.swift */, 050E7C7519D22E19004363C2 /* ViewController.swift */, 050E7C7119D22E19004363C2 /* Supporting Files */, @@ -130,7 +133,7 @@ attributes = { LastSwiftMigration = 0700; LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0600; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = Facebook; TargetAttributes = { 050E7C6D19D22E19004363C2 = { @@ -222,6 +225,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CCB01CAB1C5FEA6E00CA64C4 /* TailLoadingCellNode.swift in Sources */, 050E7C7619D22E19004363C2 /* ViewController.swift in Sources */, 050E7C7419D22E19004363C2 /* AppDelegate.swift in Sources */, ); @@ -250,6 +254,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -314,6 +319,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -325,6 +331,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/examples/Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme index 8d7f73e325..f7f575e824 100644 --- a/examples/Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme +++ b/examples/Swift/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -38,15 +38,18 @@ ReferencedContainer = "container:Sample.xcodeproj"> + + @@ -62,10 +65,10 @@ diff --git a/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata b/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/Swift/Sample/Info.plist b/examples/Swift/Sample/Info.plist index 35d842827b..fb4115c84c 100644 --- a/examples/Swift/Sample/Info.plist +++ b/examples/Swift/Sample/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/examples/Swift/Sample/TailLoadingCellNode.swift b/examples/Swift/Sample/TailLoadingCellNode.swift new file mode 100644 index 0000000000..e6b4d333f0 --- /dev/null +++ b/examples/Swift/Sample/TailLoadingCellNode.swift @@ -0,0 +1,53 @@ +// +// TailLoadingCellNode.swift +// Sample +// +// Created by Adlai Holler on 2/1/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +import AsyncDisplayKit +import UIKit + +final class TailLoadingCellNode: ASCellNode { + let spinner = SpinnerNode() + let text = ASTextNode() + + override init() { + super.init() + addSubnode(text) + text.attributedString = NSAttributedString( + string: "Loading…", + attributes: [ + NSFontAttributeName: UIFont.systemFontOfSize(12), + NSForegroundColorAttributeName: UIColor.lightGrayColor(), + NSKernAttributeName: -0.3 + ]) + addSubnode(spinner) + } + + override func layoutSpecThatFits(constrainedSize: ASSizeRange) -> ASLayoutSpec { + return ASStackLayoutSpec( + direction: .Horizontal, + spacing: 16, + justifyContent: .Center, + alignItems: .Center, + children: [ text, spinner ]) + } +} + +final class SpinnerNode: ASDisplayNode { + var activityIndicatorView: UIActivityIndicatorView { + return view as! UIActivityIndicatorView + } + + override init() { + super.init(viewBlock: { UIActivityIndicatorView(activityIndicatorStyle: .Gray) }, didLoadBlock: nil) + preferredFrameSize.height = 32 + } + + override func didLoad() { + super.didLoad() + activityIndicatorView.startAnimating() + } +} \ No newline at end of file diff --git a/examples/Swift/Sample/ViewController.swift b/examples/Swift/Sample/ViewController.swift index 4158103571..491478ccdb 100644 --- a/examples/Swift/Sample/ViewController.swift +++ b/examples/Swift/Sample/ViewController.swift @@ -14,10 +14,23 @@ import AsyncDisplayKit final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate { + struct State { + var rowCount: Int + var showingSpinner: Bool + static let empty = State(rowCount: 20, showingSpinner: false) + } + + enum Action { + case BeginBatchFetch + case EndBatchFetch(resultCount: Int) + } + var tableNode: ASTableNode { return node as! ASTableNode } + private(set) var state: State = .empty + init() { super.init(node: ASTableNode()) tableNode.delegate = self @@ -35,6 +48,11 @@ final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate // MARK: ASTableView data source and delegate. func tableView(tableView: ASTableView, nodeForRowAtIndexPath indexPath: NSIndexPath) -> ASCellNode { + NSLog("Number of rows %d", tableView.numberOfRowsInSection(0)) + if state.showingSpinner && indexPath.row == tableView.numberOfRowsInSection(0) - 1 { + return TailLoadingCellNode() + } + let node = ASTextCellNode() node.text = String(format: "[%ld.%ld] says hello!", indexPath.section, indexPath.row) @@ -46,7 +64,70 @@ final class ViewController: ASViewController, ASTableDataSource, ASTableDelegate } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 20 + var count = state.rowCount + if state.showingSpinner { + count += 1 + } + return count } + func tableView(tableView: ASTableView, willBeginBatchFetchWithContext context: ASBatchContext) { + context.cancelBatchFetching() + dispatch_async(dispatch_get_main_queue()) { + let oldState = self.state + self.state = ViewController.handleAction(.BeginBatchFetch, fromState: oldState) + self.render(oldState) + } + return; + + let time = dispatch_time(DISPATCH_TIME_NOW, Int64(NSTimeInterval(NSEC_PER_SEC) * 3)) + dispatch_after(time, dispatch_get_main_queue()) { + let action = Action.EndBatchFetch(resultCount: 20) + let oldState = self.state + self.state = ViewController.handleAction(action, fromState: oldState) + self.render(oldState) + context.completeBatchFetching(true) + } + } + + func render(oldState: State) { + let tableView = tableNode.view + tableView.beginUpdates() + + // Add or remove items + let rowCountChange = state.rowCount - oldState.rowCount + if rowCountChange > 0 { + let indexPaths = (oldState.rowCount.. State { + switch action { + case .BeginBatchFetch: + state.showingSpinner = true + case let .EndBatchFetch(resultCount): + state.rowCount += resultCount + state.showingSpinner = false + } + return state + } }