Swiftgram/AsyncDisplayKit/ASViewController.mm
rcancro 8bb4eba080 Initial attempt at implementing Display Traits
Initial attempt to get display traits working with ASEnvironment.

To get proper ASDisplayTraits support, you must use an ASViewController. The ASViewController implements UITraitCollection-related methods (`traitCollectionDidChange:`, `willTransitionToTraitCollection:withTransitionCoordinator:`, viewWillTransitionToSize:withTransitionCoordinator`) to update the internal ASDisplayTraits and propagate them to subnodes.

ASTableNode and ASCollectionNode don't actually have their cells as subnodes, so a little bit of trickery is involved (on `setEnvironment:` the table/collection node gets its data controllers completedNodes and propagates the new traits. see `ASDisplayTraitsCollectionTableSetEnvironmentState`). The data controller also passes the current display traits when creating new cells.

ASViewController also supports the ability to return a custom set of display traits. So if you have a modal dialog that should always be told it is in a compact size class, you can set the override block before displaying the VC.

A new example, called Display Traits, has been added. It shows how display traits can be used in a ASViewController with a normal ASDisplayNode as its root, as well as in ASViewControllers hosting table nodes and collection nodes. There is also an example of overriding the default display traits of a VC.

Please provide feedback!
2016-05-10 14:44:22 -07:00

207 lines
6.1 KiB
Plaintext

//
// ASViewController.m
// AsyncDisplayKit
//
// Created by Huy Nguyen on 16/09/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import "ASViewController.h"
#import "ASAssert.h"
#import "ASDimension.h"
#import "ASDisplayNode+FrameworkPrivate.h"
#import "ASDisplayNode+Beta.h"
#import "ASEnvironmentInternal.h"
#import "ASRangeControllerUpdateRangeProtocol+Beta.h"
@implementation ASViewController
{
BOOL _ensureDisplayed;
BOOL _automaticallyAdjustRangeModeBasedOnViewEvents;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
ASDisplayNodeAssert(NO, @"ASViewController requires using -initWithNode:");
return [self initWithNode:[[ASDisplayNode alloc] init]];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
ASDisplayNodeAssert(NO, @"ASViewController requires using -initWithNode:");
return [self initWithNode:[[ASDisplayNode alloc] init]];
}
- (instancetype)initWithNode:(ASDisplayNode *)node
{
if (!(self = [super initWithNibName:nil bundle:nil])) {
return nil;
}
ASDisplayNodeAssertNotNil(node, @"Node must not be nil");
ASDisplayNodeAssertTrue(!node.layerBacked);
_node = node;
_automaticallyAdjustRangeModeBasedOnViewEvents = NO;
return self;
}
- (void)dealloc
{
if (_displayTraitsContext != nil) {
ASDisplayTraitsClearDisplayContext(self.node);
_displayTraitsContext = nil;
}
}
- (void)loadView
{
ASDisplayNodeAssertTrue(!_node.layerBacked);
// Apple applies a frame and autoresizing masks we need. Allocating a view is not
// nearly as expensive as adding and removing it from a hierarchy, and fortunately
// we can avoid that here. Enabling layerBacking on a single node in the hierarchy
// will have a greater performance benefit than the impact of this transient view.
[super loadView];
UIView *view = self.view;
CGRect frame = view.frame;
UIViewAutoresizing autoresizingMask = view.autoresizingMask;
// We have what we need, so now create and assign the view we actually want.
view = _node.view;
_node.frame = frame;
_node.autoresizingMask = autoresizingMask;
self.view = view;
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[_node measureWithSizeRange:[self nodeConstrainedSize]];
}
- (void)viewDidLayoutSubviews
{
if (_ensureDisplayed && self.neverShowPlaceholders) {
_ensureDisplayed = NO;
[self.node recursivelyEnsureDisplaySynchronously:YES];
}
[super viewDidLayoutSubviews];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
_ensureDisplayed = YES;
[_node measureWithSizeRange:[self nodeConstrainedSize]];
[_node recursivelyFetchData];
[self updateCurrentRangeModeWithModeIfPossible:ASLayoutRangeModeFull];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self updateCurrentRangeModeWithModeIfPossible:ASLayoutRangeModeMinimum];
}
#pragma mark - Automatic range mode
- (BOOL)automaticallyAdjustRangeModeBasedOnViewEvents
{
return _automaticallyAdjustRangeModeBasedOnViewEvents;
}
- (void)setAutomaticallyAdjustRangeModeBasedOnViewEvents:(BOOL)automaticallyAdjustRangeModeBasedOnViewEvents
{
_automaticallyAdjustRangeModeBasedOnViewEvents = automaticallyAdjustRangeModeBasedOnViewEvents;
}
- (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode
{
if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { return; }
if (![_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]) { return; }
id<ASRangeControllerUpdateRangeProtocol> updateRangeNode = (id<ASRangeControllerUpdateRangeProtocol>)_node;
[updateRangeNode updateCurrentRangeWithMode:rangeMode];
}
#pragma mark - Layout Helpers
- (ASSizeRange)nodeConstrainedSize
{
CGSize viewSize = self.view.bounds.size;
return ASSizeRangeMake(viewSize, viewSize);
}
- (ASInterfaceState)interfaceState
{
return _node.interfaceState;
}
#pragma mark - ASDisplayTraits
- (ASDisplayTraits)displayTraitsForTraitCollection:(UITraitCollection *)traitCollection
{
if (self.overrideDisplayTraitsWithTraitCollection) {
return self.overrideDisplayTraitsWithTraitCollection(traitCollection);
}
ASDisplayTraits displayTraits = ASDisplayTraitsFromUITraitCollection(traitCollection);
displayTraits.displayContext = _displayTraitsContext;
return displayTraits;
}
- (ASDisplayTraits)displayTraitsForWindowSize:(CGSize)windowSize
{
if (self.overrideDisplayTraitsWithWindowSize) {
return self.overrideDisplayTraitsWithWindowSize(windowSize);
}
return self.node.environmentState.displayTraits;
}
- (void)progagateNewDisplayTraits:(ASDisplayTraits)displayTraits
{
ASEnvironmentState environmentState = self.node.environmentState;
ASDisplayTraits oldDisplayTraits = environmentState.displayTraits;
if (ASDisplayTraitsIsEqualToASDisplayTraits(displayTraits, oldDisplayTraits) == NO) {
environmentState.displayTraits = displayTraits;
[self.node setEnvironmentState:environmentState];
[self.node setNeedsLayout];
NSArray<id<ASEnvironment>> *children = [self.node children];
for (id<ASEnvironment> child in children) {
ASEnvironmentStatePropagateDown(child, environmentState.displayTraits);
}
}
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
{
[super traitCollectionDidChange:previousTraitCollection];
ASDisplayTraits displayTraits = [self displayTraitsForTraitCollection:self.traitCollection];
[self progagateNewDisplayTraits:displayTraits];
}
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
ASDisplayTraits displayTraits = [self displayTraitsForTraitCollection:self.traitCollection];
[self progagateNewDisplayTraits:displayTraits];
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
ASDisplayTraits displayTraits = [self displayTraitsForWindowSize:size];
[self progagateNewDisplayTraits:displayTraits];
}
@end