Beef up the properties, beef up the demo app

This commit is contained in:
Adlai Holler 2016-02-17 18:30:42 -08:00
parent 7a6006e627
commit 563d0893a3
8 changed files with 197 additions and 50 deletions

View File

@ -94,13 +94,12 @@
- (void)setNeedsLayout - (void)setNeedsLayout
{ {
ASDisplayNodeAssertThreadAffinity(self);
CGSize oldSize = self.calculatedSize; CGSize oldSize = self.calculatedSize;
[super setNeedsLayout]; [super setNeedsLayout];
if (_layoutDelegate != nil && self.isNodeLoaded) { if (_layoutDelegate != nil && self.isNodeLoaded) {
BOOL sizeChanged = !CGSizeEqualToSize(oldSize, self.calculatedSize);
ASPerformBlockOnMainThread(^{ ASPerformBlockOnMainThread(^{
BOOL sizeChanged = !CGSizeEqualToSize(oldSize, self.calculatedSize);
[_layoutDelegate nodeDidRelayout:self sizeChanged:sizeChanged]; [_layoutDelegate nodeDidRelayout:self sizeChanged:sizeChanged];
}); });
} }

View File

@ -628,7 +628,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize completion:(void(^)())completion - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize completion:(void(^)())completion
{ {
ASDisplayNodeAssertThreadAffinity(self);
ASDN::MutexLocker l(_propertyLock); ASDN::MutexLocker l(_propertyLock);
if (![self __shouldSize]) if (![self __shouldSize])
return nil; return nil;
@ -1868,7 +1867,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
{ {
ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock);
if (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) { if (_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) {
ASLayoutSpec *layoutSpec = [self layoutSpecThatFits:constrainedSize]; ASLayoutSpec *layoutSpec = [self layoutSpecThatFits:constrainedSize];
layoutSpec.isMutable = NO; layoutSpec.isMutable = NO;
@ -1895,25 +1894,25 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{ {
ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock);
return _preferredFrameSize; return _preferredFrameSize;
} }
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{ {
ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock);
return nil; return nil;
} }
- (ASLayout *)calculatedLayout - (ASLayout *)calculatedLayout
{ {
ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock);
return _layout; return _layout;
} }
- (CGSize)calculatedSize - (CGSize)calculatedSize
{ {
ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock);
return _layout.size; return _layout.size;
} }
@ -1944,7 +1943,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)invalidateCalculatedLayout - (void)invalidateCalculatedLayout
{ {
ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock);
// This will cause -measureWithSizeRange: to actually compute the size instead of returning the previously cached size // This will cause -measureWithSizeRange: to actually compute the size instead of returning the previously cached size
_flags.isMeasured = NO; _flags.isMeasured = NO;
} }

View File

@ -335,9 +335,9 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie
{ {
_bridge_prologue_write; _bridge_prologue_write;
_setToLayer(opaque, newOpaque); _setToLayer(opaque, newOpaque);
// NOTE: If we're in the background, then when the pending state
// FIXME: Would like to setNeedsDisplay if opaqueness changed, but // is applied to the view on main, we will call `setNeedsDisplay` if
// not safe to read old value in background. // the new opaque value doesn't match the one on the layer.
} }
- (BOOL)isUserInteractionEnabled - (BOOL)isUserInteractionEnabled
@ -539,8 +539,9 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { _pendingVie
{ {
_bridge_prologue_write; _bridge_prologue_write;
_setToLayer(backgroundColor, newBackgroundColor.CGColor); _setToLayer(backgroundColor, newBackgroundColor.CGColor);
// FIXME: Would like to setNeedsDisplay if background color changed, but // NOTE: If we're in the background, then when the pending state
// not safe to read old color in background. // is applied to the view on main, we will call `setNeedsDisplay` if
// the new background color doesn't match the one on the layer.
} }
- (UIColor *)tintColor - (UIColor *)tintColor

View File

@ -557,6 +557,13 @@ static UIColor *defaultTintColor = nil;
- (void)applyToLayer:(CALayer *)layer - (void)applyToLayer:(CALayer *)layer
{ {
ASPendingStateFlags flags = _flags; ASPendingStateFlags flags = _flags;
if (flags.needsDisplay
|| (flags.setOpaque && opaque != layer.opaque)
|| (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, layer.backgroundColor))) {
[layer setNeedsDisplay];
}
if (flags.setAnchorPoint) if (flags.setAnchorPoint)
layer.anchorPoint = anchorPoint; layer.anchorPoint = anchorPoint;
@ -629,9 +636,6 @@ static UIColor *defaultTintColor = nil;
if (flags.setEdgeAntialiasingMask) if (flags.setEdgeAntialiasingMask)
layer.edgeAntialiasingMask = edgeAntialiasingMask; layer.edgeAntialiasingMask = edgeAntialiasingMask;
if (flags.needsDisplay)
[layer setNeedsDisplay];
if (flags.needsLayout) if (flags.needsLayout)
[layer setNeedsLayout]; [layer setNeedsLayout];
@ -658,6 +662,12 @@ static UIColor *defaultTintColor = nil;
CALayer *layer = view.layer; CALayer *layer = view.layer;
ASPendingStateFlags flags = _flags; ASPendingStateFlags flags = _flags;
if (flags.needsDisplay
|| (flags.setOpaque && opaque != view.opaque)
|| (flags.setBackgroundColor && !CGColorEqualToColor(backgroundColor, layer.backgroundColor))) {
[view setNeedsDisplay];
}
if (flags.setAnchorPoint) if (flags.setAnchorPoint)
layer.anchorPoint = anchorPoint; layer.anchorPoint = anchorPoint;
@ -752,9 +762,6 @@ static UIColor *defaultTintColor = nil;
if (flags.setEdgeAntialiasingMask) if (flags.setEdgeAntialiasingMask)
layer.edgeAntialiasingMask = edgeAntialiasingMask; layer.edgeAntialiasingMask = edgeAntialiasingMask;
if (flags.needsDisplay)
[view setNeedsDisplay];
if (flags.needsLayout) if (flags.needsLayout)
[view setNeedsLayout]; [view setNeedsLayout];

View File

@ -11,6 +11,8 @@
CCD3736F1C751C8A00AB7199 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3736E1C751C8A00AB7199 /* ViewController.swift */; }; CCD3736F1C751C8A00AB7199 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3736E1C751C8A00AB7199 /* ViewController.swift */; };
CCD373741C751C8A00AB7199 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CCD373731C751C8A00AB7199 /* Assets.xcassets */; }; CCD373741C751C8A00AB7199 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CCD373731C751C8A00AB7199 /* Assets.xcassets */; };
CCD373771C751C8A00AB7199 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */; }; CCD373771C751C8A00AB7199 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */; };
CCD3737F1C7520AB00AB7199 /* DemoCellNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD3737E1C7520AB00AB7199 /* DemoCellNode.swift */; };
CCD373811C75228900AB7199 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCD373801C75228900AB7199 /* Utilities.swift */; };
FE56E788869496B3522E8AE2 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D966CA4D089E4178A58E447C /* Pods.framework */; }; FE56E788869496B3522E8AE2 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D966CA4D089E4178A58E447C /* Pods.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -23,6 +25,8 @@
CCD373731C751C8A00AB7199 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; CCD373731C751C8A00AB7199 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
CCD373761C751C8A00AB7199 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; CCD373761C751C8A00AB7199 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
CCD373781C751C8A00AB7199 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; CCD373781C751C8A00AB7199 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
CCD3737E1C7520AB00AB7199 /* DemoCellNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoCellNode.swift; sourceTree = "<group>"; };
CCD373801C75228900AB7199 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = "<group>"; };
D966CA4D089E4178A58E447C /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D966CA4D089E4178A58E447C /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -78,6 +82,8 @@
children = ( children = (
CCD3736C1C751C8A00AB7199 /* AppDelegate.swift */, CCD3736C1C751C8A00AB7199 /* AppDelegate.swift */,
CCD3736E1C751C8A00AB7199 /* ViewController.swift */, CCD3736E1C751C8A00AB7199 /* ViewController.swift */,
CCD373801C75228900AB7199 /* Utilities.swift */,
CCD3737E1C7520AB00AB7199 /* DemoCellNode.swift */,
CCD373731C751C8A00AB7199 /* Assets.xcassets */, CCD373731C751C8A00AB7199 /* Assets.xcassets */,
CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */, CCD373751C751C8A00AB7199 /* LaunchScreen.storyboard */,
CCD373781C751C8A00AB7199 /* Info.plist */, CCD373781C751C8A00AB7199 /* Info.plist */,
@ -206,7 +212,9 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
CCD3737F1C7520AB00AB7199 /* DemoCellNode.swift in Sources */,
CCD3736F1C751C8A00AB7199 /* ViewController.swift in Sources */, CCD3736F1C751C8A00AB7199 /* ViewController.swift in Sources */,
CCD373811C75228900AB7199 /* Utilities.swift in Sources */,
CCD3736D1C751C8A00AB7199 /* AppDelegate.swift in Sources */, CCD3736D1C751C8A00AB7199 /* AppDelegate.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View File

@ -0,0 +1,87 @@
//
// DemoCellNode.swift
// BackgroundPropertySetting
//
// Created by Adlai Holler on 2/17/16.
// Copyright © 2016 Adlai Holler. All rights reserved.
//
import UIKit
import AsyncDisplayKit
final class DemoCellNode: ASCellNode {
let childA = ASDisplayNode()
let childB = ASDisplayNode()
var state = State.Right
override init() {
super.init()
usesImplicitHierarchyManagement = true
}
override func layoutSpecThatFits(constrainedSize: ASSizeRange) -> ASLayoutSpec {
let specA = ASRatioLayoutSpec(ratio: 1, child: childA)
specA.flexBasis = ASRelativeDimensionMakeWithPoints(1)
specA.flexGrow = true
let specB = ASRatioLayoutSpec(ratio: 1, child: childB)
specB.flexBasis = ASRelativeDimensionMakeWithPoints(1)
specB.flexGrow = true
let children = state.isReverse ? [ specB, specA ] : [ specA, specB ]
let direction: ASStackLayoutDirection = state.isVertical ? .Vertical : .Horizontal
return ASStackLayoutSpec(direction: direction,
spacing: 20,
justifyContent: .SpaceAround,
alignItems: .Center,
children: children)
}
override func animateLayoutTransition(context: ASContextTransitioning!) {
childA.frame = context.initialFrameForNode(childA)
childB.frame = context.initialFrameForNode(childB)
let tinyDelay = drand48() / 10
UIView.animateWithDuration(0.5, delay: tinyDelay, usingSpringWithDamping: 0.9, initialSpringVelocity: 1.5, options: .BeginFromCurrentState, animations: { () -> Void in
self.childA.frame = context.finalFrameForNode(self.childA)
self.childB.frame = context.finalFrameForNode(self.childB)
}, completion: {
context.completeTransition($0)
})
}
enum State {
case Right
case Up
case Left
case Down
var isVertical: Bool {
switch self {
case .Up, .Down:
return true
default:
return false
}
}
var isReverse: Bool {
switch self {
case .Left, .Up:
return true
default:
return false
}
}
mutating func advance() {
switch self {
case .Right:
self = .Up
case .Up:
self = .Left
case .Left:
self = .Down
case .Down:
self = .Right
}
}
}
}

View File

@ -0,0 +1,15 @@
//
// Utilities.swift
// BackgroundPropertySetting
//
// Created by Adlai Holler on 2/17/16.
// Copyright © 2016 Adlai Holler. All rights reserved.
//
import UIKit
extension UIColor {
static func random() -> UIColor {
return UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0)
}
}

View File

@ -9,56 +9,87 @@
import UIKit import UIKit
import AsyncDisplayKit import AsyncDisplayKit
final class ViewController: ASViewController, ASTableDelegate, ASTableDataSource { final class ViewController: ASViewController, ASCollectionDelegate, ASCollectionDataSource {
let itemCount = 1000
var tableNode: ASTableNode { let itemSize: CGSize
return node as! ASTableNode let padding: CGFloat
var collectionNode: ASCollectionNode {
return node as! ASCollectionNode
} }
init() { init() {
super.init(node: ASTableNode(style: .Plain)) let layout = UICollectionViewFlowLayout()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Update", style: .Plain, target: self, action: "didTapUpdateButton") (padding, itemSize) = ViewController.computeLayoutSizesForMainScreen()
tableNode.delegate = self layout.minimumInteritemSpacing = padding
tableNode.dataSource = self layout.minimumLineSpacing = padding
title = "Background Node Updating Demo" super.init(node: ASCollectionNode(collectionViewLayout: layout))
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Color", style: .Plain, target: self, action: "didTapColorsButton")
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Layout", style: .Plain, target: self, action: "didTapLayoutButton")
collectionNode.delegate = self
collectionNode.dataSource = self
title = "Background Updating"
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
let rowCount = 20
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // MARK: ASCollectionDataSource
return rowCount
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return itemCount
} }
func tableView(tableView: ASTableView, nodeBlockForRowAtIndexPath indexPath: NSIndexPath) -> ASCellNodeBlock { func collectionView(collectionView: ASCollectionView, nodeBlockForItemAtIndexPath indexPath: NSIndexPath) -> ASCellNodeBlock {
return { return {
let node = ASCellNode() let node = DemoCellNode()
node.backgroundColor = getRandomColor() node.backgroundColor = UIColor.random()
node.childA.backgroundColor = UIColor.random()
node.childB.backgroundColor = UIColor.random()
return node return node
} }
} }
@objc private func didTapUpdateButton() { func collectionView(collectionView: ASCollectionView, constrainedSizeForNodeAtIndexPath indexPath: NSIndexPath) -> ASSizeRange {
let currentlyVisibleNodes = tableNode.view.visibleNodes() return ASSizeRangeMake(itemSize, itemSize)
}
// MARK: Action Handling
@objc private func didTapColorsButton() {
let currentlyVisibleNodes = collectionNode.view.visibleNodes()
let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0) let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
dispatch_async(queue) { dispatch_async(queue) {
for case let node as ASCellNode in currentlyVisibleNodes { for case let node as DemoCellNode in currentlyVisibleNodes {
node.backgroundColor = getRandomColor() node.backgroundColor = UIColor.random()
} }
} }
} }
@objc private func didTapLayoutButton() {
let currentlyVisibleNodes = collectionNode.view.visibleNodes()
let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
dispatch_async(queue) {
for case let node as DemoCellNode in currentlyVisibleNodes {
node.state.advance()
node.setNeedsLayout()
}
}
} }
func getRandomColor() -> UIColor{ // MARK: Static
let randomRed:CGFloat = CGFloat(drand48())
let randomGreen:CGFloat = CGFloat(drand48())
let randomBlue:CGFloat = CGFloat(drand48())
return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
static func computeLayoutSizesForMainScreen() -> (padding: CGFloat, itemSize: CGSize) {
let numberOfColumns = 4
let screen = UIScreen.mainScreen()
let scale = screen.scale
let screenWidth = Int(screen.bounds.width * screen.scale)
let itemWidthPx = (screenWidth - (numberOfColumns - 1)) / numberOfColumns
let leftover = screenWidth - itemWidthPx * numberOfColumns
let paddingPx = leftover / (numberOfColumns - 1)
let itemDimension = CGFloat(itemWidthPx) / scale
let padding = CGFloat(paddingPx) / scale
return (padding: padding, itemSize: CGSize(width: itemDimension, height: itemDimension))
}
} }