merged with master

This commit is contained in:
Luke Parham 2016-01-24 23:14:43 -06:00
commit 41721aa42a
65 changed files with 2547 additions and 197 deletions

View File

@ -1,11 +1,11 @@
Pod::Spec.new do |spec|
spec.name = 'AsyncDisplayKit'
spec.version = '1.9.5'
spec.version = '1.9.6'
spec.license = { :type => 'BSD' }
spec.homepage = 'http://asyncdisplaykit.org'
spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' }
spec.summary = 'Smooth asynchronous user interfaces for iOS apps.'
spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.5' }
spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.6' }
spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/'
@ -47,4 +47,6 @@ Pod::Spec.new do |spec|
}
spec.ios.deployment_target = '7.0'
# tvOS not recognized by older versions of Cocoapods - add this only after tvOS support complete.
# spec.tvos.deployment_target = '9.0'
end

View File

@ -1587,8 +1587,6 @@
058D09B9195D04C000B7D73C /* Frameworks */,
058D09BA195D04C000B7D73C /* Resources */,
3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */,
527A806066E1F4E2795090DF /* Embed Pods Frameworks */,
1B86F48711505F91D5FEF571 /* Embed Pods Frameworks */,
);
buildRules = (
);
@ -1718,21 +1716,6 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
527A806066E1F4E2795090DF /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */

Binary file not shown.

View File

@ -11,8 +11,9 @@
@interface ASButtonNode : ASControlNode
@property (nonatomic, readonly) ASTextNode *titleNode;
@property (nonatomic, readonly) ASImageNode *imageNode;
@property (nonatomic, readonly) ASTextNode * _Nonnull titleNode;
@property (nonatomic, readonly) ASImageNode * _Nonnull imageNode;
@property (nonatomic, readonly) ASImageNode * _Nonnull backgroundImageNode;
/**
Spacing between image and title. Defaults to 8.0.
@ -36,11 +37,66 @@
*/
@property (nonatomic, assign) ASVerticalAlignment contentVerticalAlignment;
/**
* Returns the styled title associated with the specified state.
*
* @param state The state that uses the styled title. The possible values are described in ASControlState.
*
* @return The title for the specified state.
*/
- (NSAttributedString * _Nullable)attributedTitleForState:(ASControlState)state;
- (NSAttributedString *)attributedTitleForState:(ASControlState)state;
- (void)setAttributedTitle:(NSAttributedString *)title forState:(ASControlState)state;
/**
* Sets the styled title to use for the specified state. This will reset styled title previously set with -setTitle:withFont:withColor:forState.
*
* @param title The styled text string to use for the title.
* @param state The state that uses the specified title. The possible values are described in ASControlState.
*/
- (void)setAttributedTitle:(nullable NSAttributedString *)title forState:(ASControlState)state;
- (UIImage *)imageForState:(ASControlState)state;
- (void)setImage:(UIImage *)image forState:(ASControlState)state;
/**
* Sets the title to use for the specified state. This will reset styled title previously set with -setAttributedTitle:forState.
*
* @param title The styled text string to use for the title.
* @param font The font to use for the title.
* @param color The color to use for the title.
* @param state The state that uses the specified title. The possible values are described in ASControlState.
*/
- (void)setTitle:(nonnull NSString *)title withFont:(nullable UIFont *)font withColor:(nullable UIColor *)color forState:(ASControlState)state;
/**
* Returns the image used for a button state.
*
* @param state The state that uses the image. Possible values are described in ASControlState.
*
* @return The image used for the specified state.
*/
- (UIImage * _Nullable)imageForState:(ASControlState)state;
/**
* Sets the image to use for the specified state.
*
* @param image The image to use for the specified state.
* @param state The state that uses the specified title. The values are described in ASControlState.
*/
- (void)setImage:(nullable UIImage *)image forState:(ASControlState)state;
/**
* Sets the background image to use for the specified state.
*
* @param image The image to use for the specified state.
* @param state The state that uses the specified title. The values are described in ASControlState.
*/
- (void)setBackgroundImage:(nullable UIImage *)image forState:(ASControlState)state;
/**
* Returns the background image used for a button state.
*
* @param state The state that uses the image. Possible values are described in ASControlState.
*
* @return The background image used for the specified state.
*/
- (UIImage * _Nullable)backgroundImageForState:(ASControlState)state;
@end

View File

@ -10,6 +10,7 @@
#import "ASStackLayoutSpec.h"
#import "ASThread.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASBackgroundLayoutSpec.h"
@interface ASButtonNode ()
{
@ -24,6 +25,11 @@
UIImage *_highlightedImage;
UIImage *_selectedImage;
UIImage *_disabledImage;
UIImage *_normalBackgroundImage;
UIImage *_highlightedBackgroundImage;
UIImage *_selectedBackgroundImage;
UIImage *_disabledBackgroundImage;
}
@end
@ -41,33 +47,50 @@
_titleNode = [[ASTextNode alloc] init];
_imageNode = [[ASImageNode alloc] init];
_backgroundImageNode = [[ASImageNode alloc] init];
[_backgroundImageNode setContentMode:UIViewContentModeScaleToFill];
[_titleNode setLayerBacked:YES];
[_imageNode setLayerBacked:YES];
[_backgroundImageNode setLayerBacked:YES];
_contentHorizontalAlignment = ASAlignmentMiddle;
_contentVerticalAlignment = ASAlignmentCenter;
[self addSubnode:_backgroundImageNode];
[self addSubnode:_titleNode];
[self addSubnode:_imageNode];
}
return self;
}
- (void)setLayerBacked:(BOOL)layerBacked
{
ASDisplayNodeAssert(!layerBacked, @"ASButtonNode must not be layer backed!");
[super setLayerBacked:layerBacked];
}
- (void)setEnabled:(BOOL)enabled
{
[super setEnabled:enabled];
[self updateImage];
[self updateTitle];
[self updateButtonContent];
}
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
[self updateImage];
[self updateTitle];
[self updateButtonContent];
}
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
[self updateButtonContent];
}
- (void)updateButtonContent
{
[self updateBackgroundImage];
[self updateImage];
[self updateTitle];
}
@ -120,6 +143,27 @@
}
}
- (void)updateBackgroundImage
{
ASDN::MutexLocker l(_propertyLock);
UIImage *newImage;
if (self.enabled == NO && _disabledBackgroundImage) {
newImage = _disabledBackgroundImage;
} else if (self.highlighted && _highlightedBackgroundImage) {
newImage = _highlightedBackgroundImage;
} else if (self.selected && _selectedBackgroundImage) {
newImage = _selectedBackgroundImage;
} else {
newImage = _normalBackgroundImage;
}
if (newImage != self.backgroundImageNode.image) {
self.backgroundImageNode.image = newImage;
[self setNeedsLayout];
}
}
- (CGFloat)contentSpacing
{
ASDN::MutexLocker l(_propertyLock);
@ -152,6 +196,18 @@
[self setNeedsLayout];
}
- (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *)color forState:(ASControlState)state
{
NSDictionary *attributes = @{
NSFontAttributeName: font ? font :[UIFont systemFontOfSize:[UIFont buttonFontSize]],
NSForegroundColorAttributeName : color ? color : [UIColor blackColor]
};
NSAttributedString *string = [[NSAttributedString alloc] initWithString:title
attributes:attributes];
[self setAttributedTitle:string forState:state];
}
- (NSAttributedString *)attributedTitleForState:(ASControlState)state
{
ASDN::MutexLocker l(_propertyLock);
@ -246,6 +302,54 @@
[self updateImage];
}
- (void)setBackgroundImage:(UIImage *)image forState:(ASControlState)state
{
ASDN::MutexLocker l(_propertyLock);
switch (state) {
case ASControlStateNormal:
_normalBackgroundImage = image;
break;
case ASControlStateHighlighted:
_highlightedBackgroundImage = image;
break;
case ASControlStateSelected:
_selectedBackgroundImage = image;
break;
case ASControlStateDisabled:
_disabledBackgroundImage = image;
break;
default:
break;
}
[self updateBackgroundImage];
}
- (UIImage *)backgroundImageForState:(ASControlState)state
{
ASDN::MutexLocker l(_propertyLock);
switch (state) {
case ASControlStateNormal:
return _normalBackgroundImage;
case ASControlStateHighlighted:
return _highlightedBackgroundImage;
case ASControlStateSelected:
return _selectedBackgroundImage;
case ASControlStateDisabled:
return _disabledBackgroundImage;
default:
return _normalBackgroundImage;
}
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init];
@ -265,12 +369,18 @@
stack.children = children;
return stack;
if (self.backgroundImageNode.image) {
return [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:stack
background:self.backgroundImageNode];
} else {
return stack;
}
}
- (void)layout
{
[super layout];
self.backgroundImageNode.hidden = self.backgroundImageNode.image == nil;
self.imageNode.hidden = self.imageNode.image == nil;
self.titleNode.hidden = self.titleNode.attributedString.length > 0 == NO;
}

View File

@ -6,6 +6,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASCollectionNode.h"
@protocol ASCollectionViewLayoutFacilitatorProtocol;
NS_ASSUME_NONNULL_BEGIN

View File

@ -91,6 +91,13 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)layoutDidFinish;
/**
* @abstract Called on a background thread if !isNodeLoaded - called on the main thread if isNodeLoaded.
*
* @discussion When the .calculatedLayout property is set to a new ASLayout (directly from -calculateLayoutThatFits: or
* calculated via use of -layoutSpecThatFits:), subclasses may inspect it here.
*/
- (void)calculatedLayoutDidChange;
/** @name Layout calculation */

View File

@ -641,7 +641,9 @@ NS_ASSUME_NONNULL_END
@property (atomic, assign) UIViewContentMode contentMode; // default=UIViewContentModeScaleToFill
@property (atomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default=YES (NO for layer-backed nodes)
#if TARGET_OS_IOS
@property (atomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO
#endif
@property (atomic, assign, nullable) CGColorRef shadowColor; // default=opaque rgb black
@property (atomic, assign) CGFloat shadowOpacity; // default=0.0
@property (atomic, assign) CGSize shadowOffset; // default=(0, -3)
@ -658,6 +660,16 @@ NS_ASSUME_NONNULL_END
- (BOOL)isFirstResponder;
- (BOOL)canPerformAction:(nonnull SEL)action withSender:(nonnull id)sender;
#if TARGET_OS_TV
//Focus Engine
- (void)setNeedsFocusUpdate;
- (BOOL)canBecomeFocused;
- (void)updateFocusIfNeeded;
- (void)didUpdateFocusInContext:(nonnull UIFocusUpdateContext *)context withAnimationCoordinator:(nonnull UIFocusAnimationCoordinator *)coordinator;
- (BOOL)shouldUpdateFocusInContext:(nonnull UIFocusUpdateContext *)context;
- (nullable UIView *)preferredFocusedView;
#endif
// Accessibility support
@property (atomic, assign) BOOL isAccessibilityElement;
@property (nullable, atomic, copy) NSString *accessibilityLabel;

View File

@ -594,6 +594,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
_layout = [self calculateLayoutThatFits:constrainedSize];
_constrainedSize = constrainedSize;
_flags.isMeasured = YES;
[self calculatedLayoutDidChange];
}
ASDisplayNodeAssertTrue(_layout.layoutableObject == self);
@ -615,6 +616,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return _layout;
}
- (void)calculatedLayoutDidChange
{
}
- (BOOL)displaysAsynchronously
{
ASDN::MutexLocker l(_propertyLock);
@ -2312,6 +2317,38 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
return self;
}
#if TARGET_OS_TV
#pragma mark - UIFocusEnvironment Protocol (tvOS)
- (void)setNeedsFocusUpdate
{
}
- (void)updateFocusIfNeeded
{
}
- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
{
return YES;
}
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
}
- (UIView *)preferredFocusedView
{
if (self.nodeLoaded) {
return self.view;
} else {
return nil;
}
}
#endif
@end
@implementation ASDisplayNode (Debugging)

View File

@ -163,7 +163,9 @@
_textKitComponents.textView = self.textView;
//_textKitComponents.textView = NO; // Unfortunately there's a bug here with iOS 7 DP5 that causes the text-view to only be one line high when scrollEnabled is NO. rdar://14729288
_textKitComponents.textView.delegate = self;
#if TARGET_OS_IOS
_textKitComponents.textView.editable = YES;
#endif
_textKitComponents.textView.typingAttributes = _typingAttributes;
_textKitComponents.textView.returnKeyType = _returnKeyType;
_textKitComponents.textView.accessibilityHint = _placeholderTextKitComponents.textStorage.string;

View File

@ -235,7 +235,21 @@
UIRectFill({ .size = backingSize });
}
[image drawInRect:imageDrawRect];
// iOS 9 appears to contain a thread safety regression when drawing the same CGImageRef on
// multiple threads concurrently. In fact, instead of crashing, it appears to deadlock.
// The issue is present in Mac OS X El Capitan and has been seen hanging Pro apps like Adobe Premier,
// as well as iOS games, and a small number of ASDK apps that provide the same image reference
// to many separate ASImageNodes. A workaround is to set .displaysAsynchronously = NO for the nodes
// that may get the same pointer for a given UI asset image, etc.
// FIXME: We should replace @synchronized here, probably using a global, locked NSMutableSet, and
// only if the object already exists in the set we should create a semaphore to signal waiting threads
// upon removal of the object from the set when the operation completes.
// Another option is to have ASDisplayNode+AsyncDisplay coordinate these cases, and share the decoded buffer.
// Details tracked in https://github.com/facebook/AsyncDisplayKit/issues/1068
@synchronized(image) {
[image drawInRect:imageDrawRect];
}
if (isCancelled()) {
UIGraphicsEndImageContext();

View File

@ -7,6 +7,7 @@
*/
#import <AsyncDisplayKit/ASImageNode.h>
#if TARGET_OS_IOS
#import <MapKit/MapKit.h>
NS_ASSUME_NONNULL_BEGIN
@ -14,7 +15,13 @@ NS_ASSUME_NONNULL_BEGIN
@interface ASMapNode : ASImageNode
/**
The current region of ASMapNode. This can be set at any time and ASMapNode will animate the change. This property may be set from a background thread before the node is loaded, and will automatically be applied to define the region of the static snapshot (if .liveMap = NO) or the internal MKMapView (otherwise).
The current options of ASMapNode. This can be set at any time and ASMapNode will animate the change.<br><br>This property may be set from a background thread before the node is loaded, and will automatically be applied to define the behavior of the static snapshot (if .liveMap = NO) or the internal MKMapView (otherwise).<br><br> Changes to the region and camera options will only be animated when when the liveMap mode is enabled, otherwise these options will be applied statically to the new snapshot. <br><br> The options object is used to specify properties even when the liveMap mode is enabled, allowing seamless transitions between the snapshot and liveMap (as well as back to the snapshot).
*/
@property (nonatomic, strong) MKMapSnapshotOptions *options;
/** The region is simply the sub-field on the options object. If the objects object is reset,
this will in effect be overwritten and become the value of the .region property on that object.
Defaults to MKCoordinateRegionForMapRect(MKMapRectWorld).
*/
@property (nonatomic, assign) MKCoordinateRegion region;
@ -29,7 +36,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign, getter=isLiveMap) BOOL liveMap;
/**
@abstract Whether ASMapNode should automatically request a new map snapshot to correspond to the new node size. Defaults to YES.
@abstract Whether ASMapNode should automatically request a new map snapshot to correspond to the new node size.
@default Default value is YES.
@discussion If mapSize is set then this will be set to NO, since the size will be the same in all orientations.
*/
@property (nonatomic, assign) BOOL needsMapReloadOnBoundsChange;
@ -40,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak) id <MKMapViewDelegate> mapDelegate;
/**
* @discussion This method set the annotations of the static map view and also to the live map view. Passing an empty array clears the map of any annotations.
* @discussion This method sets the annotations of the static map view and also to the live map view. Passing an empty array clears the map of any annotations.
* @param annotations An array of objects that conform to the MKAnnotation protocol
*/
- (void)setAnnotations:(NSArray<id<MKAnnotation>> *)annotations;
@ -48,3 +56,5 @@ NS_ASSUME_NONNULL_BEGIN
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -6,6 +6,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#if TARGET_OS_IOS
#import "ASMapNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
@ -17,7 +18,6 @@
{
ASDN::RecursiveMutex _propertyLock;
MKMapSnapshotter *_snapshotter;
MKMapSnapshotOptions *_options;
NSArray *_annotations;
CLLocationCoordinate2D _centerCoordinateOfMap;
}
@ -27,7 +27,7 @@
@synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange;
@synthesize mapDelegate = _mapDelegate;
@synthesize region = _region;
@synthesize options = _options;
@synthesize liveMap = _liveMap;
#pragma mark - Lifecycle
@ -42,13 +42,6 @@
_needsMapReloadOnBoundsChange = YES;
_liveMap = NO;
_centerCoordinateOfMap = kCLLocationCoordinate2DInvalid;
//Default world-scale view
_region = MKCoordinateRegionForMapRect(MKMapRectWorld);
_options = [[MKMapSnapshotOptions alloc] init];
_options.region = _region;
return self;
}
@ -118,23 +111,40 @@
_needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange;
}
- (MKCoordinateRegion)region
- (MKMapSnapshotOptions *)options
{
ASDN::MutexLocker l(_propertyLock);
return _region;
if (!_options) {
_options = [[MKMapSnapshotOptions alloc] init];
_options.region = MKCoordinateRegionForMapRect(MKMapRectWorld);
CGSize calculatedSize = self.calculatedSize;
if (!CGSizeEqualToSize(calculatedSize, CGSizeZero)) {
_options.size = calculatedSize;
}
}
return _options;
}
- (void)setOptions:(MKMapSnapshotOptions *)options
{
ASDN::MutexLocker l(_propertyLock);
_options = options;
if (self.isLiveMap) {
[self applySnapshotOptions];
} else {
[self resetSnapshotter];
[self takeSnapshot];
}
}
- (MKCoordinateRegion)region
{
return self.options.region;
}
- (void)setRegion:(MKCoordinateRegion)region
{
ASDN::MutexLocker l(_propertyLock);
_region = region;
if (self.isLiveMap) {
[_mapView setRegion:_region animated:YES];
} else {
_options.region = _region;
[self resetSnapshotter];
[self takeSnapshot];
}
self.options.region = region;
}
#pragma mark - Snapshotter
@ -182,14 +192,25 @@
- (void)setUpSnapshotter
{
ASDisplayNodeAssert(!CGSizeEqualToSize(CGSizeZero, self.calculatedSize), @"self.calculatedSize can not be zero. Make sure that you are setting a preferredFrameSize or wrapping ASMapNode in a ASRatioLayoutSpec or similar.");
_options.size = self.calculatedSize;
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options];
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options];
}
- (void)resetSnapshotter
{
// FIXME: The semantics of this method / name would suggest that we cancel + destroy the snapshotter,
// but not that we create a new one. We should probably only create the new one in -takeSnapshot or something.
[_snapshotter cancel];
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options];
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options];
}
- (void)applySnapshotOptions
{
MKMapSnapshotOptions *options = self.options;
[_mapView setCamera:options.camera animated:YES];
[_mapView setRegion:options.region animated:YES];
[_mapView setMapType:options.mapType];
_mapView.showsBuildings = options.showsBuildings;
_mapView.showsPointsOfInterest = options.showsPointsOfInterest;
}
#pragma mark - Actions
@ -200,7 +221,7 @@
__weak ASMapNode *weakSelf = self;
_mapView = [[MKMapView alloc] initWithFrame:CGRectZero];
_mapView.delegate = weakSelf.mapDelegate;
[_mapView setRegion:_options.region];
[weakSelf applySnapshotOptions];
[_mapView addAnnotations:_annotations];
[weakSelf setNeedsLayout];
[weakSelf.view addSubview:_mapView];
@ -213,6 +234,7 @@
- (void)removeLiveMap
{
// FIXME: With MKCoordinateRegion, isn't the center coordinate fully specified? Do we need this?
_centerCoordinateOfMap = _mapView.centerCoordinate;
[_mapView removeFromSuperview];
_mapView = nil;
@ -231,7 +253,25 @@
}
#pragma mark - Layout
// Layout isn't usually needed in the box model, but since we are making use of MKMapView which is hidden in an ASDisplayNode this is preferred.
- (void)setSnapshotSizeIfNeeded:(CGSize)snapshotSize
{
if (!CGSizeEqualToSize(self.options.size, snapshotSize)) {
_options.size = snapshotSize;
[self resetSnapshotter];
}
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
CGSize size = self.preferredFrameSize;
if (CGSizeEqualToSize(size, CGSizeZero)) {
size = constrainedSize;
}
[self setSnapshotSizeIfNeeded:size];
return constrainedSize;
}
// Layout isn't usually needed in the box model, but since we are making use of MKMapView this is preferred.
- (void)layout
{
[super layout];
@ -239,11 +279,13 @@
_mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height);
} else {
// If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter.
if (!CGSizeEqualToSize(_options.size, self.bounds.size) && _needsMapReloadOnBoundsChange) {
_options.size = self.bounds.size;
[self resetSnapshotter];
if (_needsMapReloadOnBoundsChange) {
[self setSnapshotSizeIfNeeded:self.bounds.size];
// FIXME: Adding a check for FetchData here seems to cause intermittent map load failures, but shouldn't.
// if (ASInterfaceStateIncludesFetchData(self.interfaceState)) {
[self takeSnapshot];
}
}
}
@end
@end
#endif

View File

@ -6,9 +6,10 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#if TARGET_OS_IOS
#import <AsyncDisplayKit/ASImageNode.h>
#import <AsyncDisplayKit/ASImageProtocols.h>
#import <Photos/Photos.h>
NS_ASSUME_NONNULL_BEGIN
@ -116,13 +117,14 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) {
*/
@property (nullable, nonatomic, readonly) ASImageIdentifier displayedImageIdentifier;
#if TARGET_OS_IOS
/**
* @abstract The image manager that this image node should use when requesting images from the Photos framework. If this is `nil` (the default), then `PHImageManager.defaultManager` is used.
* @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below.
*/
@property (nonatomic, strong) PHImageManager *imageManager;
#endif
@end
@ -229,6 +231,7 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier
*/
- (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier;
#if TARGET_OS_IOS
/**
* @abstract A PHAsset for the specific asset local identifier
* @param imageNode The sender.
@ -240,11 +243,11 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier
* @return A PHAsset corresponding to `assetLocalIdentifier`, or nil if none is available.
*/
- (nullable PHAsset *)multiplexImageNode:(ASMultiplexImageNode *)imageNode assetForLocalIdentifier:(NSString *)assetLocalIdentifier;
#endif
@end
#pragma mark -
#if TARGET_OS_IOS
@interface NSURL (ASPhotosFrameworkURLs)
/**
@ -261,5 +264,8 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier
options:(PHImageRequestOptions *)options;
@end
#endif
NS_ASSUME_NONNULL_END
NS_ASSUME_NONNULL_END
#endif

View File

@ -6,12 +6,12 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASMultiplexImageNode.h"
#if TARGET_OS_IOS
#import "ASMultiplexImageNode.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <Photos/Photos.h>
#import <libkern/OSAtomic.h>
#import "ASAvailability.h"
@ -112,6 +112,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
*/
- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock;
#if TARGET_OS_IOS
/**
@abstract Loads the image corresponding to the given assetURL from the device's Assets Library.
@param imageIdentifier The identifier for the image to be loaded. May not be nil.
@ -131,7 +132,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
@param error An error describing why the load failed, if it failed; nil otherwise.
*/
- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock;
#endif
/**
@abstract Downloads the image corresponding to the given imageIdentifier from the given URL.
@param imageIdentifier The identifier for the image to be downloaded. May not be nil.
@ -262,7 +263,9 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
_dataSource = dataSource;
_dataSourceFlags.image = [_dataSource respondsToSelector:@selector(multiplexImageNode:imageForImageIdentifier:)];
_dataSourceFlags.URL = [_dataSource respondsToSelector:@selector(multiplexImageNode:URLForImageIdentifier:)];
#if TARGET_OS_IOS
_dataSourceFlags.asset = [_dataSource respondsToSelector:@selector(multiplexImageNode:assetForLocalIdentifier:)];
#endif
}
#pragma mark -
@ -455,6 +458,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
return;
}
#if TARGET_OS_IOS
// If it's an assets-library URL, we need to fetch it from the assets library.
if ([[nextImageURL scheme] isEqualToString:kAssetsLibraryURLScheme]) {
// Load the asset.
@ -470,6 +474,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
finishedLoadingBlock(image, nextImageIdentifier, error);
}];
}
#endif
else // Otherwise, it's a web URL that we can download.
{
// First, check the cache.
@ -499,7 +504,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
}];
}
}
#if TARGET_OS_IOS
- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock
{
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
@ -609,7 +614,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
_phImageRequestOperation = newImageRequestOp;
[phImageRequestQueue addOperation:newImageRequestOp];
}
#endif
- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock
{
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
@ -708,7 +713,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
}
@end
#if TARGET_OS_IOS
@implementation NSURL (ASPhotosFrameworkURLs)
+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options
@ -720,4 +725,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
return request.url;
}
@end
@end
#endif
#endif

View File

@ -48,12 +48,13 @@
[super didLoad];
ASCollectionView *cv = self.view;
#if TARGET_OS_IOS
cv.pagingEnabled = YES;
cv.scrollsToTop = NO;
#endif
cv.allowsSelection = NO;
cv.showsVerticalScrollIndicator = NO;
cv.showsHorizontalScrollIndicator = NO;
cv.scrollsToTop = NO;
// Zeroing contentInset is important, as UIKit will set the top inset for the navigation bar even though
// our view is only horizontally scrollable. This causes UICollectionViewFlowLayout to log a warning.

View File

@ -73,8 +73,9 @@ NS_ASSUME_NONNULL_BEGIN
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath;
#if TARGET_OS_IOS
- (nullable NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath;
#endif
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath;

View File

@ -35,13 +35,13 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
textOrigin:(CGPoint)textOrigin
backgroundColor:(CGColorRef)backgroundColor;
backgroundColor:(UIColor *)backgroundColor;
@property (nonatomic, strong, readonly) ASTextKitRenderer *renderer;
@property (nonatomic, assign, readonly) CGPoint textOrigin;
@property (nonatomic, assign, readonly) CGColorRef backgroundColor;
@property (nonatomic, strong, readonly) UIColor *backgroundColor;
@end
@ -49,20 +49,18 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
textOrigin:(CGPoint)textOrigin
backgroundColor:(CGColorRef)backgroundColor
backgroundColor:(UIColor *)backgroundColor
{
if (self = [super init]) {
_renderer = renderer;
_textOrigin = textOrigin;
_backgroundColor = CGColorRetain(backgroundColor);
_backgroundColor = backgroundColor;
}
return self;
}
- (void)dealloc
{
CGColorRelease(_backgroundColor);
// Destruction of the layout managers/containers/text storage is quite
// expensive, and can take some time, so we dispatch onto a bg queue to
// actually dealloc.
@ -182,24 +180,11 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
NSString *truncationString = [_composedTruncationString string];
if (plainString.length > 50)
plainString = [[plainString substringToIndex:50] stringByAppendingString:@"\u2026"];
return [NSString stringWithFormat:@"<%@: %p; text = \"%@\"; truncation string = \"%@\"; frame = %@>", self.class, self, plainString, truncationString, self.nodeLoaded ? NSStringFromCGRect(self.layer.frame) : nil];
return [NSString stringWithFormat:@"<%@: %p; text = \"%@\"; truncation string = \"%@\"; frame = %@; renderer = %p>", self.class, self, plainString, truncationString, self.nodeLoaded ? NSStringFromCGRect(self.layer.frame) : nil, _renderer];
}
#pragma mark - ASDisplayNode
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width);
ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height);
_constrainedSize = constrainedSize;
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
return [[self _renderer] size];
}
// FIXME: Re-evaluate if it is still the right decision to clear the renderer at this stage.
// This code was written before TextKit and when 512MB devices were still the overwhelming majority.
- (void)displayDidFinish
@ -240,13 +225,13 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
[self _invalidateRendererIfNeeded:frame.size];
[self _invalidateRendererIfNeededForBoundsSize:frame.size];
}
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
[self _invalidateRendererIfNeeded:bounds.size];
[self _invalidateRendererIfNeededForBoundsSize:bounds.size];
}
#pragma mark - Renderer Management
@ -291,12 +276,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (void)_invalidateRendererIfNeeded
{
[self _invalidateRendererIfNeeded:self.bounds.size];
[self _invalidateRendererIfNeededForBoundsSize:self.bounds.size];
}
- (void)_invalidateRendererIfNeeded:(CGSize)newSize
- (void)_invalidateRendererIfNeededForBoundsSize:(CGSize)boundsSize
{
if ([self _needInvalidateRenderer:newSize]) {
if ([self _needInvalidateRendererForBoundsSize:boundsSize]) {
// Our bounds of frame have changed to a size that is not identical to our constraining size,
// so our previous layout information is invalid, and TextKit may draw at the
// incorrect origin.
@ -305,7 +290,9 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
}
}
- (BOOL)_needInvalidateRenderer:(CGSize)newSize
#pragma mark - Layout and Sizing
- (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize
{
if (!_renderer) {
return YES;
@ -313,9 +300,9 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
// If the size is not the same as the constraint we provided to the renderer, start out assuming we need
// a new one. However, there are common cases where the constrained size doesn't need to be the same as calculated.
CGSize oldSize = _renderer.constrainedSize;
CGSize rendererConstrainedSize = _renderer.constrainedSize;
if (CGSizeEqualToSize(newSize, oldSize)) {
if (CGSizeEqualToSize(boundsSize, rendererConstrainedSize)) {
return NO;
} else {
// It is very common to have a constrainedSize with a concrete, specific width but +Inf height.
@ -324,7 +311,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
// experience truncation and don't need to recreate the renderer with the size it already calculated,
// as this would essentially serve to set its constrainedSize to be its calculatedSize (unnecessary).
ASLayout *layout = self.calculatedLayout;
if (layout != nil && CGSizeEqualToSize(newSize, layout.size)) {
if (layout != nil && CGSizeEqualToSize(boundsSize, layout.size)) {
if (boundsSize.width != rendererConstrainedSize.width) {
// Don't bother changing _constrainedSize, as ASDisplayNode's -measure: method would have a cache miss
// and ask us to recalculate layout if it were called with the same calculatedSize that got us to this point!
_renderer.constrainedSize = boundsSize;
}
return NO;
} else {
return YES;
@ -332,6 +324,32 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
}
}
- (void)calculatedLayoutDidChange
{
ASLayout *layout = self.calculatedLayout;
if (layout != nil) {
_renderer.constrainedSize = layout.size;
}
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width);
ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height);
_constrainedSize = constrainedSize;
// Instead of invalidating the renderer, in case this is a new call with a different constrained size,
// just update the size of the NSTextContainer that is owned by the renderer's internal context object.
[self _renderer].constrainedSize = _constrainedSize;
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
return [[self _renderer] size];
}
#pragma mark - Modifying User Text
- (void)setAttributedString:(NSAttributedString *)attributedString
@ -409,12 +427,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
// Fill background
if (!isRasterizing) {
CGColorRef backgroundColor = parameters.backgroundColor;
UIColor *backgroundColor = parameters.backgroundColor;
if (backgroundColor) {
CGContextSetFillColorWithColor(context, backgroundColor);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextFillRect(context, CGContextGetClipBoundingBox(context));
CGContextSetBlendMode(context, kCGBlendModeNormal);
[backgroundColor setFill];
UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy);
}
}
@ -430,14 +446,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
{
[self _invalidateRendererIfNeeded];
CGRect bounds = self.bounds;
[self _invalidateRendererIfNeededForBoundsSize:bounds.size];
// Offset the text origin by any shadow padding
UIEdgeInsets shadowPadding = [self shadowPadding];
CGPoint textOrigin = CGPointMake(self.bounds.origin.x - shadowPadding.left, self.bounds.origin.y - shadowPadding.top);
CGPoint textOrigin = CGPointMake(bounds.origin.x - shadowPadding.left, bounds.origin.y - shadowPadding.top);
return [[ASTextNodeDrawParameters alloc] initWithRenderer:[self _renderer]
textOrigin:textOrigin
backgroundColor:self.backgroundColor.CGColor];
backgroundColor:self.backgroundColor];
}
#pragma mark - Attributes

View File

@ -1,3 +1,10 @@
/* 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.
*/
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@ -9,6 +16,10 @@ typedef NS_ENUM(NSUInteger, ASVideoGravity) {
@protocol ASVideoNodeDelegate;
// If you need ASVideoNode, please use AsyncDisplayKit master until this comment is removed.
// As of 1.9.6, ASVideoNode accidentally triggers creating the AVPlayerLayer even before playing
// the video. Using a lot of them intended to show static frame placeholders will be slow.
@interface ASVideoNode : ASControlNode
@property (atomic, strong, readwrite) AVAsset *asset;
@property (atomic, strong, readonly) AVPlayer *player;

View File

@ -1,7 +1,12 @@
/* 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.
*/
#import "ASVideoNode.h"
#import "ASDisplayNode+Beta.h"
@interface ASVideoNode ()
{
@ -34,11 +39,17 @@
- (instancetype)init
{
if (!(self = [super init])) { return nil; }
if (!(self = [super init])) {
return nil;
}
_previewQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
[ASDisplayNode setShouldUseNewRenderingRange:YES];
#if DEBUG
NSLog(@"*** Warning: ASVideoNode is a new component - the 1.9.6 version may cause performance hiccups.");
#endif
self.gravity = AVLayerVideoGravityResizeAspect;

View File

@ -5,7 +5,7 @@
// Created by Adlai Holler on 9/25/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#if TARGET_OS_IOS
#import <Foundation/Foundation.h>
#import <Photos/Photos.h>
@ -64,3 +64,4 @@ extern NSString *const ASPhotosURLScheme;
@end
// NS_ASSUME_NONNULL_END
#endif

View File

@ -5,7 +5,7 @@
// Created by Adlai Holler on 9/25/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#if TARGET_OS_IOS
#import "ASPhotosFrameworkImageRequest.h"
#import "ASBaseDefines.h"
#import "ASAvailability.h"
@ -159,3 +159,4 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h";
}
@end
#endif

View File

@ -21,8 +21,9 @@
{
BOOL _rangeIsValid;
BOOL _queuedRangeUpdate;
BOOL _layoutControllerImplementsSetVisibleIndexPaths;
ASScrollDirection _scrollDirection;
NSSet *_allPreviousIndexPaths;
NSSet<NSIndexPath *> *_allPreviousIndexPaths;
}
@end
@ -58,64 +59,84 @@
});
}
- (void)setLayoutController:(id<ASLayoutController>)layoutController
{
_layoutController = layoutController;
_layoutControllerImplementsSetVisibleIndexPaths = [_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)];
}
- (void)_updateVisibleNodeIndexPaths
{
if (!_queuedRangeUpdate) {
ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController");
if (!_queuedRangeUpdate || !_layoutController) {
return;
}
// FIXME: Consider if we need to check this separately from the range calculation below.
NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self];
// TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges
// Example: ... = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
NSArray<NSIndexPath *> *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self];
if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)...
_queuedRangeUpdate = NO;
return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later
}
CGSize viewportSize = [_dataSource viewportSizeForRangeController:self];
[_layoutController setViewportSize:viewportSize];
[_layoutController setViewportSize:[_dataSource viewportSizeForRangeController:self]];
// the layout controller needs to know what the current visible indices are to calculate range offsets
if ([_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)]) {
if (_layoutControllerImplementsSetVisibleIndexPaths) {
[_layoutController setVisibleNodeIndexPaths:visibleNodePaths];
}
NSArray *allNodes = [_dataSource completedNodes];
NSArray *currentSectionNodes = nil;
NSInteger currentSectionIndex = -1; // Will be unequal to any indexPath.section, so we set currentSectionNodes.
// allNodes is a 2D array: it contains arrays for each section, each containing nodes.
NSArray<NSArray *> *allNodes = [_dataSource completedNodes];
NSUInteger numberOfSections = [allNodes count];
NSArray<ASDisplayNode *> *currentSectionNodes = nil;
NSInteger currentSectionIndex = -1; // Set to -1 so we don't match any indexPath.section on the first iteration.
NSUInteger numberOfNodesInSection = 0;
NSSet *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths];
// = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
NSSet *displayIndexPaths = nil;
NSSet *fetchDataIndexPaths = nil;
NSMutableSet *allIndexPaths = nil;
NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
NSSet<NSIndexPath *> *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths];
NSSet<NSIndexPath *> *displayIndexPaths = nil;
NSSet<NSIndexPath *> *fetchDataIndexPaths = nil;
// Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on
// the network or display queues before preloading (offscreen) nodes are enqueued.
NSMutableOrderedSet<NSIndexPath *> *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths];
ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self];
if (ASInterfaceStateIncludesVisible(selfInterfaceState)) {
// If we are already visible, get busy! Better get started on preloading before the user scrolls more...
fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData];
displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay];
ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeFetchData];
ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeDisplay];
if (parametersDisplay.leadingBufferScreenfuls == parametersFetchData.leadingBufferScreenfuls &&
parametersDisplay.trailingBufferScreenfuls == parametersFetchData.trailingBufferScreenfuls) {
displayIndexPaths = fetchDataIndexPaths;
} else {
displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay];
}
// Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint.
allIndexPaths = [fetchDataIndexPaths mutableCopy];
// Because allIndexPaths is an NSMutableOrderedSet, this adds the non-duplicate items /after/ the existing items.
// This means that during iteration, we will first visit visible, then display, then fetch data nodes.
[allIndexPaths unionSet:displayIndexPaths];
[allIndexPaths unionSet:visibleIndexPaths];
} else {
allIndexPaths = [visibleIndexPaths mutableCopy];
[allIndexPaths unionSet:fetchDataIndexPaths];
}
// Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any
// Add anything we had applied interfaceState to in the last update, but is no longer in range, so we can clear any
// range flags it still has enabled. Most of the time, all but a few elements are equal; a large programmatic
// scroll or major main thread stall could cause entirely disjoint sets, but we must visit all.
NSSet *allCurrentIndexPaths = [allIndexPaths copy];
// scroll or major main thread stall could cause entirely disjoint sets. In either case we must visit all.
// Calling "-set" on NSMutableOrderedSet just references the underlying mutable data store, so we must copy it.
NSSet<NSIndexPath *> *allCurrentIndexPaths = [[allIndexPaths set] copy];
[allIndexPaths unionSet:_allPreviousIndexPaths];
_allPreviousIndexPaths = allCurrentIndexPaths;
// This array is only used if logging is enabled.
NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
for (NSIndexPath *indexPath in allIndexPaths) {
// Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it.
// For consistency, make sure each node knows that it should measure itself if something changes.

View File

@ -331,4 +331,36 @@
return _node;
}
#if TARGET_OS_TV
#pragma mark - tvOS
- (BOOL)canBecomeFocused
{
return [_node canBecomeFocused];
}
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
return [_node didUpdateFocusInContext:context withAnimationCoordinator:coordinator];
}
- (void)setNeedsFocusUpdate
{
return [_node setNeedsFocusUpdate];
}
- (void)updateFocusIfNeeded
{
return [_node updateFocusIfNeeded];
}
- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
{
return [_node shouldUpdateFocusInContext:context];
}
- (UIView *)preferredFocusedView
{
return [_node preferredFocusedView];
}
#endif
@end

View File

@ -81,6 +81,46 @@
return YES;
}
#if TARGET_OS_TV
// Focus Engine
- (BOOL)canBecomeFocused
{
return YES;
}
- (void)setNeedsFocusUpdate
{
ASDisplayNodeAssertMainThread();
[_view setNeedsFocusUpdate];
}
- (void)updateFocusIfNeeded
{
ASDisplayNodeAssertMainThread();
[_view updateFocusIfNeeded];
}
- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context
{
return YES;
}
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
}
- (UIView *)preferredFocusedView
{
if (self.nodeLoaded) {
return _view;
}
else {
return nil;
}
}
#endif
- (BOOL)isFirstResponder
{
ASDisplayNodeAssertMainThread();
@ -298,7 +338,7 @@
_bridge_prologue;
_setToViewOnly(userInteractionEnabled, enabled);
}
#if TARGET_OS_IOS
- (BOOL)isExclusiveTouch
{
_bridge_prologue;
@ -310,7 +350,7 @@
_bridge_prologue;
_setToViewOnly(exclusiveTouch, exclusiveTouch);
}
#endif
- (BOOL)clipsToBounds
{
_bridge_prologue;

View File

@ -716,9 +716,11 @@ static UIColor *defaultTintColor = nil;
if (_flags.setUserInteractionEnabled)
view.userInteractionEnabled = userInteractionEnabled;
#if TARGET_OS_IOS
if (_flags.setExclusiveTouch)
view.exclusiveTouch = exclusiveTouch;
#endif
if (_flags.setShadowColor)
layer.shadowColor = shadowColor;
@ -943,10 +945,10 @@ static UIColor *defaultTintColor = nil;
pendingState.userInteractionEnabled = view.userInteractionEnabled;
(pendingState->_flags).setUserInteractionEnabled = YES;
#if TARGET_OS_IOS
pendingState.exclusiveTouch = view.exclusiveTouch;
(pendingState->_flags).setExclusiveTouch = YES;
#endif
pendingState.shadowColor = layer.shadowColor;
(pendingState->_flags).setShadowColor = YES;

View File

@ -30,6 +30,8 @@
constrainedSize:(CGSize)constrainedSize
layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory;
@property (nonatomic, assign, readwrite) CGSize constrainedSize;
/**
All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to
TextKit components may cause crashes.

View File

@ -49,6 +49,16 @@
return self;
}
- (CGSize)constrainedSize
{
return _textContainer.size;
}
- (void)setConstrainedSize:(CGSize)constrainedSize
{
_textContainer.size = constrainedSize;
}
- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *,
NSTextStorage *,
NSTextContainer *))block

View File

@ -37,7 +37,6 @@
/**
Designated Initializer
dvlkferufedgjnhjjfhldjedlunvtdtv
@discussion Sizing will occur as a result of initialization, so be careful when/where you use this.
*/
- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)textComponentAttributes
@ -51,7 +50,7 @@ dvlkferufedgjnhjjfhldjedlunvtdtv
@property (nonatomic, assign, readonly) ASTextKitAttributes attributes;
@property (nonatomic, assign, readonly) CGSize constrainedSize;
@property (nonatomic, assign, readwrite) CGSize constrainedSize;
#pragma mark - Drawing
/*

View File

@ -17,6 +17,9 @@
#import "ASTextKitTailTruncater.h"
#import "ASTextKitTruncating.h"
//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)
static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
{
static NSCharacterSet *truncationCharacterSet;
@ -65,12 +68,10 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
{
if (!_truncater) {
ASTextKitAttributes attributes = _attributes;
// We must inset the constrained size by the size of the shadower.
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
NSCharacterSet *avoidTailTruncationSet = attributes.avoidTailTruncationSet ? : _defaultAvoidTruncationCharacterSet();
_truncater = [[ASTextKitTailTruncater alloc] initWithContext:[self context]
truncationAttributedString:attributes.truncationAttributedString
avoidTailTruncationSet:attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet()
constrainedSize:shadowConstrainedSize];
avoidTailTruncationSet:avoidTailTruncationSet];
}
return _truncater;
}
@ -79,6 +80,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
{
if (!_context) {
ASTextKitAttributes attributes = _attributes;
// We must inset the constrained size by the size of the shadower.
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
_context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString
lineBreakMode:attributes.lineBreakMode
@ -92,6 +94,30 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
#pragma mark - Sizing
- (CGSize)size
{
if (!_sizeIsCalculated) {
[self _calculateSize];
_sizeIsCalculated = YES;
}
return _calculatedSize;
}
- (void)setConstrainedSize:(CGSize)constrainedSize
{
if (!CGSizeEqualToSize(constrainedSize, _constrainedSize)) {
_sizeIsCalculated = NO;
_constrainedSize = constrainedSize;
// If the context isn't created yet, it will be initialized with the appropriate size when next accessed.
if (_context) {
// If we're updating an existing context, make sure to use the same inset logic used during initialization.
// This codepath allows us to reuse the
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:constrainedSize];
_context.constrainedSize = shadowConstrainedSize;
}
}
}
- (void)_calculateSize
{
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by
@ -111,16 +137,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
// to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect.
boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size});
_calculatedSize = [_shadower outsetSizeWithInsetSize:CGSizeMake(boundingRect.size.width + boundingRect.origin.x, boundingRect.size.height + boundingRect.origin.y)];
}
- (CGSize)size
{
if (!_sizeIsCalculated) {
[self _calculateSize];
_sizeIsCalculated = YES;
}
return _calculatedSize;
_calculatedSize = [_shadower outsetSizeWithInsetSize:boundingRect.size];
}
#pragma mark - Drawing
@ -136,8 +153,12 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
[[self shadower] setShadowInContext:context];
UIGraphicsPushContext(context);
LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds));
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
LOG(@"usedRect: %@", NSStringFromCGRect([layoutManager usedRectForTextContainer:textContainer]));
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]));
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
}];

View File

@ -18,7 +18,6 @@
__weak ASTextKitContext *_context;
NSAttributedString *_truncationAttributedString;
NSCharacterSet *_avoidTailTruncationSet;
CGSize _constrainedSize;
}
@synthesize visibleRanges = _visibleRanges;
@synthesize truncationStringRect = _truncationStringRect;
@ -26,13 +25,11 @@
- (instancetype)initWithContext:(ASTextKitContext *)context
truncationAttributedString:(NSAttributedString *)truncationAttributedString
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
constrainedSize:(CGSize)constrainedSize
{
if (self = [super init]) {
_context = context;
_truncationAttributedString = truncationAttributedString;
_avoidTailTruncationSet = avoidTailTruncationSet;
_constrainedSize = constrainedSize;
[self _truncate];
}

View File

@ -31,7 +31,6 @@
*/
- (instancetype)initWithContext:(ASTextKitContext *)context
truncationAttributedString:(NSAttributedString *)truncationAttributedString
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
constrainedSize:(CGSize)constrainedSize;
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet;
@end

View File

@ -50,8 +50,7 @@
}];
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:nil
avoidTailTruncationSet:nil
constrainedSize:constrainedSize];
avoidTailTruncationSet:nil];
XCTAssert(NSEqualRanges(textKitVisibleRange, tailTruncater.visibleRanges[0]));
}
@ -67,8 +66,7 @@
layoutManagerFactory:nil];
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@""]
constrainedSize:constrainedSize];
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@""]];
__block NSString *drawnString;
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
drawnString = textStorage.string;
@ -90,8 +88,7 @@
layoutManagerFactory:nil];
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
constrainedSize:constrainedSize];
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]];
(void)tailTruncater;
__block NSString *drawnString;
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
@ -114,8 +111,7 @@
layoutManagerFactory:nil];
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
constrainedSize:constrainedSize];
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]];
// So Xcode doesn't yell at me for an unused var...
(void)tailTruncater;
__block NSString *drawnString;
@ -139,8 +135,7 @@
layoutManagerFactory:nil];
XCTAssertNoThrow([[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
constrainedSize:constrainedSize]);
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]]);
}
@end

View File

@ -0,0 +1,3 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
pod 'AsyncDisplayKit', :path => '../..'

View File

@ -0,0 +1,405 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25FDEC911BF31EE700CEB123 /* ItemNode.m */; };
7A83848E1C34359D002CDD08 /* ItemViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A83848D1C34359D002CDD08 /* ItemViewModel.m */; };
7A8384941C343680002CDD08 /* BlurbNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A8384921C343680002CDD08 /* BlurbNode.m */; };
7A8384971C344057002CDD08 /* ItemStyles.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A8384961C344057002CDD08 /* ItemStyles.m */; };
7ACD5F891C415B7500E7BE16 /* LoadingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD5F881C415B7500E7BE16 /* LoadingNode.m */; };
7ACD5F961C4847C000E7BE16 /* PlaceholderNetworkImageNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD5F951C4847C000E7BE16 /* PlaceholderNetworkImageNode.m */; };
9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; };
AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; };
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; };
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; };
AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; };
FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F02BAF78E68BC56FD8C161B7 /* libPods.a */; };
FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
25FDEC901BF31EE700CEB123 /* ItemNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemNode.h; sourceTree = "<group>"; };
25FDEC911BF31EE700CEB123 /* ItemNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemNode.m; sourceTree = "<group>"; };
2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; };
7A83848C1C34359D002CDD08 /* ItemViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemViewModel.h; sourceTree = "<group>"; };
7A83848D1C34359D002CDD08 /* ItemViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemViewModel.m; sourceTree = "<group>"; };
7A8384911C343680002CDD08 /* BlurbNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlurbNode.h; sourceTree = "<group>"; };
7A8384921C343680002CDD08 /* BlurbNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlurbNode.m; sourceTree = "<group>"; };
7A8384951C344057002CDD08 /* ItemStyles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemStyles.h; sourceTree = "<group>"; };
7A8384961C344057002CDD08 /* ItemStyles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemStyles.m; sourceTree = "<group>"; };
7ACD5F871C415B7500E7BE16 /* LoadingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoadingNode.h; sourceTree = "<group>"; };
7ACD5F881C415B7500E7BE16 /* LoadingNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LoadingNode.m; sourceTree = "<group>"; };
7ACD5F941C4847C000E7BE16 /* PlaceholderNetworkImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaceholderNetworkImageNode.h; sourceTree = "<group>"; };
7ACD5F951C4847C000E7BE16 /* PlaceholderNetworkImageNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaceholderNetworkImageNode.m; sourceTree = "<group>"; };
9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Launchboard.storyboard; sourceTree = "<group>"; };
AC3C4A5E1A11F47200143C57 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
AC3C4A621A11F47200143C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AC3C4A631A11F47200143C57 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
AC3C4A651A11F47200143C57 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
AC3C4A661A11F47200143C57 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
AC3C4A681A11F47200143C57 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
AC3C4A691A11F47200143C57 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; };
F02BAF78E68BC56FD8C161B7 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PresentingViewController.h; sourceTree = "<group>"; };
FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PresentingViewController.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
AC3C4A5B1A11F47200143C57 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
90A2B9C5397C46134C8A793B /* Pods */ = {
isa = PBXGroup;
children = (
2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */,
CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
AC3C4A551A11F47200143C57 = {
isa = PBXGroup;
children = (
AC3C4A601A11F47200143C57 /* Sample */,
AC3C4A5F1A11F47200143C57 /* Products */,
90A2B9C5397C46134C8A793B /* Pods */,
D6E38FF0CB18E3F55CF06437 /* Frameworks */,
);
sourceTree = "<group>";
};
AC3C4A5F1A11F47200143C57 /* Products */ = {
isa = PBXGroup;
children = (
AC3C4A5E1A11F47200143C57 /* Sample.app */,
);
name = Products;
sourceTree = "<group>";
};
AC3C4A601A11F47200143C57 /* Sample */ = {
isa = PBXGroup;
children = (
AC3C4A651A11F47200143C57 /* AppDelegate.h */,
AC3C4A661A11F47200143C57 /* AppDelegate.m */,
AC3C4A681A11F47200143C57 /* ViewController.h */,
AC3C4A691A11F47200143C57 /* ViewController.m */,
7ACD5F941C4847C000E7BE16 /* PlaceholderNetworkImageNode.h */,
7ACD5F951C4847C000E7BE16 /* PlaceholderNetworkImageNode.m */,
FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */,
FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */,
AC3C4A8D1A11F80C00143C57 /* Images.xcassets */,
AC3C4A611A11F47200143C57 /* Supporting Files */,
25FDEC901BF31EE700CEB123 /* ItemNode.h */,
25FDEC911BF31EE700CEB123 /* ItemNode.m */,
7A8384951C344057002CDD08 /* ItemStyles.h */,
7A8384961C344057002CDD08 /* ItemStyles.m */,
7A83848C1C34359D002CDD08 /* ItemViewModel.h */,
7A83848D1C34359D002CDD08 /* ItemViewModel.m */,
7A8384911C343680002CDD08 /* BlurbNode.h */,
7A8384921C343680002CDD08 /* BlurbNode.m */,
7ACD5F871C415B7500E7BE16 /* LoadingNode.h */,
7ACD5F881C415B7500E7BE16 /* LoadingNode.m */,
);
indentWidth = 2;
path = Sample;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
AC3C4A611A11F47200143C57 /* Supporting Files */ = {
isa = PBXGroup;
children = (
AC3C4A621A11F47200143C57 /* Info.plist */,
AC3C4A631A11F47200143C57 /* main.m */,
9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
D6E38FF0CB18E3F55CF06437 /* Frameworks */ = {
isa = PBXGroup;
children = (
F02BAF78E68BC56FD8C161B7 /* libPods.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
AC3C4A5D1A11F47200143C57 /* Sample */ = {
isa = PBXNativeTarget;
buildConfigurationList = AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */;
buildPhases = (
F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */,
AC3C4A5A1A11F47200143C57 /* Sources */,
AC3C4A5B1A11F47200143C57 /* Frameworks */,
AC3C4A5C1A11F47200143C57 /* Resources */,
A6902C454C7661D0D277AC62 /* Copy Pods Resources */,
B4CD33E927E6F4EE5DD6CCF0 /* Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Sample;
productName = Sample;
productReference = AC3C4A5E1A11F47200143C57 /* Sample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
AC3C4A561A11F47200143C57 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
AC3C4A5D1A11F47200143C57 = {
CreatedOnToolsVersion = 6.1;
};
};
};
buildConfigurationList = AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = AC3C4A551A11F47200143C57;
productRefGroup = AC3C4A5F1A11F47200143C57 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
AC3C4A5D1A11F47200143C57 /* Sample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
AC3C4A5C1A11F47200143C57 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */,
AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
A6902C454C7661D0D277AC62 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
B4CD33E927E6F4EE5DD6CCF0 /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
AC3C4A5A1A11F47200143C57 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */,
7ACD5F891C415B7500E7BE16 /* LoadingNode.m in Sources */,
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */,
7A8384971C344057002CDD08 /* ItemStyles.m in Sources */,
FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */,
7A8384941C343680002CDD08 /* BlurbNode.m in Sources */,
7A83848E1C34359D002CDD08 /* ItemViewModel.m in Sources */,
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */,
7ACD5F961C4847C000E7BE16 /* PlaceholderNetworkImageNode.m in Sources */,
AC3C4A641A11F47200143C57 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
AC3C4A7F1A11F47200143C57 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
AC3C4A801A11F47200143C57 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
AC3C4A821A11F47200143C57 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
AC3C4A831A11F47200143C57 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AC3C4A7F1A11F47200143C57 /* Debug */,
AC3C4A801A11F47200143C57 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AC3C4A821A11F47200143C57 /* Debug */,
AC3C4A831A11F47200143C57 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = AC3C4A561A11F47200143C57 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Sample.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AC3C4A5D1A11F47200143C57"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AC3C4A5D1A11F47200143C57"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AC3C4A5D1A11F47200143C57"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AC3C4A5D1A11F47200143C57"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Sample.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,20 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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/UIKit.h>
#define SIMULATE_WEB_RESPONSE 0
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

View File

@ -0,0 +1,51 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 "AppDelegate.h"
#import "PresentingViewController.h"
#import "ViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = [[UINavigationController alloc] init];
[self pushNewViewControllerAnimated:NO];
[self.window makeKeyAndVisible];
return YES;
}
- (void)pushNewViewControllerAnimated:(BOOL)animated
{
UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
#if SIMULATE_WEB_RESPONSE
UIViewController *viewController = [[PresentingViewController alloc] init];
#else
UIViewController *viewController = [[ViewController alloc] init];
viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Push Another Copy" style:UIBarButtonItemStylePlain target:self action:@selector(pushNewViewController)];
#endif
[navController pushViewController:viewController animated:animated];
}
- (void)pushNewViewController
{
[self pushNewViewControllerAnimated:YES];
}
@end

View File

@ -0,0 +1,21 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 <AsyncDisplayKit/AsyncDisplayKit.h>
/**
* Simple node that displays a placekitten.com attribution.
*/
@interface BlurbNode : ASCellNode
+ (CGFloat)desiredHeightForWidth:(CGFloat)width;
@end

View File

@ -0,0 +1,112 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 "BlurbNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
static CGFloat kFixedHeight = 75.0f;
static CGFloat kTextPadding = 10.0f;
@interface BlurbNode () <ASTextNodeDelegate>
{
ASTextNode *_textNode;
}
@end
@implementation BlurbNode
#pragma mark -
#pragma mark ASCellNode.
+ (CGFloat)desiredHeightForWidth:(CGFloat)width {
return kFixedHeight;
}
- (instancetype)init
{
if (!(self = [super init]))
return nil;
self.backgroundColor = [UIColor lightGrayColor];
// create a text node
_textNode = [[ASTextNode alloc] init];
_textNode.maximumNumberOfLines = 2;
// configure the node to support tappable links
_textNode.delegate = self;
_textNode.userInteractionEnabled = YES;
// generate an attributed string using the custom link attribute specified above
NSString *blurb = @"Kittens courtesy lorempixel.com \U0001F638 \nTitles courtesy of catipsum.com";
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:blurb];
[string addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"HelveticaNeue-Light" size:16.0f] range:NSMakeRange(0, blurb.length)];
[string addAttributes:@{
NSLinkAttributeName: [NSURL URLWithString:@"http://lorempixel.com/"],
NSForegroundColorAttributeName: [UIColor blueColor],
NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot),
}
range:[blurb rangeOfString:@"lorempixel.com"]];
[string addAttributes:@{
NSLinkAttributeName: [NSURL URLWithString:@"http://www.catipsum.com/"],
NSForegroundColorAttributeName: [UIColor blueColor],
NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot),
} range:[blurb rangeOfString:@"catipsum.com"]];
_textNode.attributedString = string;
// add it as a subnode, and we're done
[self addSubnode:_textNode];
return self;
}
- (void)didLoad
{
// enable highlighting now that self.layer has loaded -- see ASHighlightOverlayLayer.h
self.layer.as_allowsHighlightDrawing = YES;
[super didLoad];
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASCenterLayoutSpec *centerSpec = [[ASCenterLayoutSpec alloc] init];
centerSpec.centeringOptions = ASCenterLayoutSpecCenteringX;
centerSpec.sizingOptions = ASCenterLayoutSpecSizingOptionMinimumY;
centerSpec.child = _textNode;
UIEdgeInsets padding =UIEdgeInsetsMake(kTextPadding, kTextPadding, kTextPadding, kTextPadding);
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:padding child:centerSpec];
}
#pragma mark -
#pragma mark ASTextNodeDelegate methods.
- (BOOL)textNode:(ASTextNode *)richTextNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point
{
// opt into link highlighting -- tap and hold the link to try it! must enable highlighting on a layer, see -didLoad
return YES;
}
- (void)textNode:(ASTextNode *)richTextNode tappedLinkAttribute:(NSString *)attribute value:(NSURL *)URL atPoint:(CGPoint)point textRange:(NSRange)textRange
{
// the node tapped a link, open it
[[UIApplication sharedApplication] openURL:URL];
}
@end

View File

@ -0,0 +1,39 @@
{
"images" : [
{
"orientation" : "portrait",
"idiom" : "iphone",
"filename" : "Default-568h@2x.png",
"minimum-system-version" : "7.0",
"subtype" : "retina4",
"scale" : "2x"
},
{
"idiom" : "iphone",
"scale" : "1x",
"orientation" : "portrait"
},
{
"idiom" : "iphone",
"scale" : "2x",
"orientation" : "portrait"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"filename" : "Default-568h@2x.png",
"subtype" : "retina4",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"minimum-system-version" : "7.0",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "cat_face.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>http://lorempixel.com</key>
<string></string>
</dict>
</dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIcons</key>
<dict/>
<key>CFBundleIcons~ipad</key>
<dict/>
<key>CFBundleIdentifier</key>
<string>com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>Launchboard</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,21 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 <AsyncDisplayKit/AsyncDisplayKit.h>
#import "ItemViewModel.h"
@interface ItemNode : ASCellNode
- initWithViewModel:(ItemViewModel *)viewModel;
+ (CGSize)sizeForWidth:(CGFloat)width;
+ (CGSize)preferredViewSize;
@end

View File

@ -0,0 +1,361 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 "ItemNode.h"
#import "ItemStyles.h"
#import "PlaceholderNetworkImageNode.h"
const CGFloat kFixedLabelsAreaHeight = 96.0;
const CGFloat kDesignWidth = 320.0;
const CGFloat kDesignHeight = 299.0;
const CGFloat kBadgeHeight = 34.0;
const CGFloat kSoldOutGBHeight = 50.0;
@interface ItemNode() <ASNetworkImageNodeDelegate>
@property (nonatomic, strong) ItemViewModel *viewModel;
@property (nonatomic, strong) PlaceholderNetworkImageNode *dealImageView;
@property (nonatomic, strong) ASTextNode *titleLabel;
@property (nonatomic, strong) ASTextNode *firstInfoLabel;
@property (nonatomic, strong) ASTextNode *distanceLabel;
@property (nonatomic, strong) ASTextNode *secondInfoLabel;
@property (nonatomic, strong) ASTextNode *originalPriceLabel;
@property (nonatomic, strong) ASTextNode *finalPriceLabel;
@property (nonatomic, strong) ASTextNode *soldOutLabelFlat;
@property (nonatomic, strong) ASDisplayNode *soldOutLabelBackground;
@property (nonatomic, strong) ASDisplayNode *soldOutOverlay;
@property (nonatomic, strong) ASTextNode *badge;
@end
@implementation ItemNode
- (instancetype)initWithViewModel:(ItemViewModel *)viewModel
{
self = [super init];
if (self != nil) {
_viewModel = viewModel;
[self setup];
[self updateLabels];
[self updateBackgroundColor];
}
return self;
}
+ (BOOL)isRTL {
return [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft;
}
- (void)setup {
self.dealImageView = [[PlaceholderNetworkImageNode alloc] init];
self.dealImageView.delegate = self;
self.dealImageView.placeholderEnabled = YES;
self.dealImageView.placeholderImageOverride = [ItemStyles placeholderImage];
self.dealImageView.defaultImage = [ItemStyles placeholderImage];
self.dealImageView.contentMode = UIViewContentModeScaleToFill;
self.dealImageView.placeholderFadeDuration = 0.0;
self.dealImageView.layerBacked = YES;
self.titleLabel = [[ASTextNode alloc] init];
self.titleLabel.maximumNumberOfLines = 2;
self.titleLabel.alignSelf = ASStackLayoutAlignSelfStart;
self.titleLabel.flexGrow = YES;
self.titleLabel.layerBacked = YES;
self.firstInfoLabel = [[ASTextNode alloc] init];
self.firstInfoLabel.maximumNumberOfLines = 1;
self.firstInfoLabel.layerBacked = YES;
self.secondInfoLabel = [[ASTextNode alloc] init];
self.secondInfoLabel.maximumNumberOfLines = 1;
self.secondInfoLabel.layerBacked = YES;
self.distanceLabel = [[ASTextNode alloc] init];
self.distanceLabel.maximumNumberOfLines = 1;
self.distanceLabel.layerBacked = YES;
self.originalPriceLabel = [[ASTextNode alloc] init];
self.originalPriceLabel.maximumNumberOfLines = 1;
self.originalPriceLabel.layerBacked = YES;
self.finalPriceLabel = [[ASTextNode alloc] init];
self.finalPriceLabel.maximumNumberOfLines = 1;
self.finalPriceLabel.layerBacked = YES;
self.badge = [[ASTextNode alloc] init];
self.badge.hidden = YES;
self.badge.layerBacked = YES;
self.soldOutLabelFlat = [[ASTextNode alloc] init];
self.soldOutLabelFlat.layerBacked = YES;
self.soldOutLabelBackground = [[ASDisplayNode alloc] init];
self.soldOutLabelBackground.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight)));
self.soldOutLabelBackground.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9];
self.soldOutLabelBackground.flexGrow = YES;
self.soldOutLabelBackground.layerBacked = YES;
self.soldOutOverlay = [[ASDisplayNode alloc] init];
self.soldOutOverlay.flexGrow = YES;
self.soldOutOverlay.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5];
self.soldOutOverlay.layerBacked = YES;
[self addSubnode:self.dealImageView];
[self addSubnode:self.titleLabel];
[self addSubnode:self.firstInfoLabel];
[self addSubnode:self.secondInfoLabel];
[self addSubnode:self.originalPriceLabel];
[self addSubnode:self.finalPriceLabel];
[self addSubnode:self.distanceLabel];
[self addSubnode:self.badge];
[self addSubnode:self.soldOutLabelBackground];
[self addSubnode:self.soldOutLabelFlat];
[self addSubnode:self.soldOutOverlay];
self.soldOutOverlay.hidden = YES;
self.soldOutLabelBackground.hidden = YES;
self.soldOutLabelFlat.hidden = YES;
[self addSubnode:self.soldOutOverlay];
if ([ItemNode isRTL]) {
self.titleLabel.alignSelf = ASStackLayoutAlignSelfEnd;
self.firstInfoLabel.alignSelf = ASStackLayoutAlignSelfEnd;
self.distanceLabel.alignSelf = ASStackLayoutAlignSelfEnd;
self.secondInfoLabel.alignSelf = ASStackLayoutAlignSelfEnd;
self.originalPriceLabel.alignSelf = ASStackLayoutAlignSelfStart;
self.finalPriceLabel.alignSelf = ASStackLayoutAlignSelfStart;
} else {
self.firstInfoLabel.alignSelf = ASStackLayoutAlignSelfStart;
self.distanceLabel.alignSelf = ASStackLayoutAlignSelfStart;
self.secondInfoLabel.alignSelf = ASStackLayoutAlignSelfStart;
self.originalPriceLabel.alignSelf = ASStackLayoutAlignSelfEnd;
self.finalPriceLabel.alignSelf = ASStackLayoutAlignSelfEnd;
}
}
- (void)updateLabels {
// Set Title text
if (self.viewModel.titleText) {
self.titleLabel.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.titleText attributes:[ItemStyles titleStyle]];
}
if (self.viewModel.firstInfoText) {
self.firstInfoLabel.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.firstInfoText attributes:[ItemStyles subtitleStyle]];
}
if (self.viewModel.secondInfoText) {
self.secondInfoLabel.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.secondInfoText attributes:[ItemStyles secondInfoStyle]];
}
if (self.viewModel.originalPriceText) {
self.originalPriceLabel.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.originalPriceText attributes:[ItemStyles originalPriceStyle]];
}
if (self.viewModel.finalPriceText) {
self.finalPriceLabel.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.finalPriceText attributes:[ItemStyles finalPriceStyle]];
}
if (self.viewModel.distanceLabelText) {
NSString *format = [ItemNode isRTL] ? @"%@ •" : @"• %@";
NSString *distanceText = [NSString stringWithFormat:format, self.viewModel.distanceLabelText];
self.distanceLabel.attributedString = [[NSAttributedString alloc] initWithString:distanceText attributes:[ItemStyles distanceStyle]];
}
BOOL isSoldOut = self.viewModel.soldOutText != nil;
if (isSoldOut) {
NSString *soldOutText = self.viewModel.soldOutText;
self.soldOutLabelFlat.attributedString = [[NSAttributedString alloc] initWithString:soldOutText attributes:[ItemStyles soldOutStyle]];
}
self.soldOutOverlay.hidden = !isSoldOut;
self.soldOutLabelFlat.hidden = !isSoldOut;
self.soldOutLabelBackground.hidden = !isSoldOut;
BOOL hasBadge = self.viewModel.badgeText != nil;
if (hasBadge) {
self.badge.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.badgeText attributes:[ItemStyles badgeStyle]];
self.badge.backgroundColor = [ItemStyles badgeColor];
}
self.badge.hidden = !hasBadge;
}
- (void)updateBackgroundColor
{
if (self.highlighted) {
self.backgroundColor = [[UIColor grayColor] colorWithAlphaComponent:0.3];
} else if (self.selected) {
self.backgroundColor = [UIColor lightGrayColor];
} else {
self.backgroundColor = [UIColor whiteColor];
}
}
- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image {
}
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
[self updateBackgroundColor];
}
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
[self updateBackgroundColor];
}
#pragma mark - superclass
- (void)displayWillStart {
[super displayWillStart];
[self fetchData];
}
- (void)fetchData {
[super fetchData];
if (self.viewModel) {
[self loadImage];
}
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {
ASLayoutSpec *textSpec = [self textSpec];
ASLayoutSpec *imageSpec = [self imageSpecWithSize:constrainedSize];
ASOverlayLayoutSpec *soldOutOverImage = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:imageSpec overlay:[self soldOutLabelSpec]];
NSArray *stackChildren = @[soldOutOverImage, textSpec];
ASStackLayoutSpec *mainStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch children:stackChildren];
ASOverlayLayoutSpec *soldOutOverlay = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:mainStack overlay:self.soldOutOverlay];
return soldOutOverlay;
}
- (ASLayoutSpec *)textSpec {
CGFloat kInsetHorizontal = 16.0;
CGFloat kInsetTop = 6.0;
CGFloat kInsetBottom = 0.0;
UIEdgeInsets textInsets = UIEdgeInsetsMake(kInsetTop, kInsetHorizontal, kInsetBottom, kInsetHorizontal);
ASLayoutSpec *verticalSpacer = [[ASLayoutSpec alloc] init];
verticalSpacer.flexGrow = YES;
ASLayoutSpec *horizontalSpacer1 = [[ASLayoutSpec alloc] init];
horizontalSpacer1.flexGrow = YES;
ASLayoutSpec *horizontalSpacer2 = [[ASLayoutSpec alloc] init];
horizontalSpacer2.flexGrow = YES;
NSArray *info1Children = @[self.firstInfoLabel, self.distanceLabel, horizontalSpacer1, self.originalPriceLabel];
NSArray *info2Children = @[self.secondInfoLabel, horizontalSpacer2, self.finalPriceLabel];
if ([ItemNode isRTL]) {
info1Children = [[info1Children reverseObjectEnumerator] allObjects];
info2Children = [[info2Children reverseObjectEnumerator] allObjects];
}
ASStackLayoutSpec *info1Stack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:1.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsBaselineLast children:info1Children];
ASStackLayoutSpec *info2Stack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentCenter alignItems:ASStackLayoutAlignItemsBaselineLast children:info2Children];
ASStackLayoutSpec *textStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0.0 justifyContent:ASStackLayoutJustifyContentEnd alignItems:ASStackLayoutAlignItemsStretch children:@[self.titleLabel, verticalSpacer, info1Stack, info2Stack]];
ASInsetLayoutSpec *textWrapper = [ASInsetLayoutSpec insetLayoutSpecWithInsets:textInsets child:textStack];
textWrapper.flexGrow = YES;
return textWrapper;
}
- (ASLayoutSpec *)imageSpecWithSize:(ASSizeRange)constrainedSize {
CGFloat imageRatio = [self imageRatioFromSize:constrainedSize.max];
ASRatioLayoutSpec *imagePlace = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:imageRatio child:self.dealImageView];
self.badge.layoutPosition = CGPointMake(0, constrainedSize.max.height - kFixedLabelsAreaHeight - kBadgeHeight);
self.badge.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(0), ASRelativeDimensionMakeWithPoints(kBadgeHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kBadgeHeight)));
ASStaticLayoutSpec *badgePosition = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[self.badge]];
ASOverlayLayoutSpec *badgeOverImage = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:imagePlace overlay:badgePosition];
badgeOverImage.flexGrow = YES;
return badgeOverImage;
}
- (ASLayoutSpec *)soldOutLabelSpec {
ASCenterLayoutSpec *centerSoldOutLabel = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY child:self.soldOutLabelFlat];
ASStaticLayoutSpec *soldOutBG = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[self.soldOutLabelBackground]];
ASCenterLayoutSpec *centerSoldOut = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:soldOutBG];
ASBackgroundLayoutSpec *soldOutLabelOverBackground = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:centerSoldOutLabel background:centerSoldOut];
return soldOutLabelOverBackground;
}
+ (CGSize)sizeForWidth:(CGFloat)width {
CGFloat height = [self scaledHeightForPreferredSize:[self preferredViewSize] scaledWidth:width];
return CGSizeMake(width, height);
}
+ (CGSize)preferredViewSize {
return CGSizeMake(kDesignWidth, kDesignHeight);
}
+ (CGFloat)scaledHeightForPreferredSize:(CGSize)preferredSize scaledWidth:(CGFloat)scaledWidth {
CGFloat scale = scaledWidth / kDesignWidth;
CGFloat scaledHeight = ceilf(scale * (kDesignHeight - kFixedLabelsAreaHeight)) + kFixedLabelsAreaHeight;
return scaledHeight;
}
#pragma mark - view operations
- (CGFloat)imageRatioFromSize:(CGSize)size {
CGFloat imageHeight = size.height - kFixedLabelsAreaHeight;
CGFloat imageRatio = imageHeight / size.width;
return imageRatio;
}
- (CGSize)imageSize {
if (!CGSizeEqualToSize(self.dealImageView.frame.size, CGSizeZero)) {
return self.dealImageView.frame.size;
} else if (!CGSizeEqualToSize(self.calculatedSize, CGSizeZero)) {
CGFloat imageRatio = [self imageRatioFromSize:self.calculatedSize];
CGFloat imageWidth = self.calculatedSize.width;
return CGSizeMake(imageWidth, imageRatio * imageWidth);
} else {
return CGSizeZero;
}
}
- (void)loadImage {
CGSize imageSize = [self imageSize];
if (CGSizeEqualToSize(CGSizeZero, imageSize)) {
return;
}
NSURL *url = [self.viewModel imageURLWithSize:imageSize];
// if we're trying to set the deal image to what it already was, skip the work
if ([[url absoluteString] isEqualToString:[self.dealImageView.URL absoluteString]]) {
return;
}
// Clear the flag that says we've loaded our image
[self.dealImageView setURL:url];
}
@end

View File

@ -0,0 +1,23 @@
//
// ItemStyles.h
// Sample
//
// Created by Samuel Stow on 12/30/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface ItemStyles : NSObject
+ (NSDictionary *)titleStyle;
+ (NSDictionary *)subtitleStyle;
+ (NSDictionary *)distanceStyle;
+ (NSDictionary *)secondInfoStyle;
+ (NSDictionary *)originalPriceStyle;
+ (NSDictionary *)finalPriceStyle;
+ (NSDictionary *)soldOutStyle;
+ (NSDictionary *)badgeStyle;
+ (UIColor *)badgeColor;
+ (UIImage *)placeholderImage;
@end

View File

@ -0,0 +1,93 @@
//
// ItemStyles.m
// Sample
//
// Created by Samuel Stow on 12/30/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import "ItemStyles.h"
const CGFloat kTitleFontSize = 20.0;
const CGFloat kInfoFontSize = 14.0;
UIColor *kTitleColor;
UIColor *kInfoColor;
UIColor *kFinalPriceColor;
UIFont *kTitleFont;
UIFont *kInfoFont;
@implementation ItemStyles
+ (void)initialize {
if (self == [ItemStyles class]) {
kTitleColor = [UIColor darkGrayColor];
kInfoColor = [UIColor grayColor];
kFinalPriceColor = [UIColor greenColor];
kTitleFont = [UIFont boldSystemFontOfSize:kTitleFontSize];
kInfoFont = [UIFont systemFontOfSize:kInfoFontSize];
}
}
+ (NSDictionary *)titleStyle {
// Title Label
return @{ NSFontAttributeName:kTitleFont,
NSForegroundColorAttributeName:kTitleColor };
}
+ (NSDictionary *)subtitleStyle {
// First Subtitle
return @{ NSFontAttributeName:kInfoFont,
NSForegroundColorAttributeName:kInfoColor };
}
+ (NSDictionary *)distanceStyle {
// Distance Label
return @{ NSFontAttributeName:kInfoFont,
NSForegroundColorAttributeName:kInfoColor};
}
+ (NSDictionary *)secondInfoStyle {
// Second Subtitle
return @{ NSFontAttributeName:kInfoFont,
NSForegroundColorAttributeName:kInfoColor};
}
+ (NSDictionary *)originalPriceStyle {
// Original price
return @{ NSFontAttributeName:kInfoFont,
NSForegroundColorAttributeName:kInfoColor,
NSStrikethroughStyleAttributeName:@(NSUnderlineStyleSingle)};
}
+ (NSDictionary *)finalPriceStyle {
// Discounted / Claimable price label
return @{ NSFontAttributeName:kTitleFont,
NSForegroundColorAttributeName:kFinalPriceColor};
}
+ (NSDictionary *)soldOutStyle {
// Setup Sold Out Label
return @{ NSFontAttributeName:kTitleFont,
NSForegroundColorAttributeName:kTitleColor};
}
+ (NSDictionary *)badgeStyle {
// Setup Sold Out Label
return @{ NSFontAttributeName:kTitleFont,
NSForegroundColorAttributeName:[UIColor whiteColor]};
}
+ (UIColor *)badgeColor {
return [[UIColor purpleColor] colorWithAlphaComponent:0.4];
}
+ (UIImage *)placeholderImage {
static UIImage *__catFace = nil;
if (!__catFace) {
__catFace = [UIImage imageNamed:@"cat_face"];
}
return __catFace;
}
@end

View File

@ -0,0 +1,27 @@
//
// GPDealViewModel.h
// Groupon
//
// Created by Samuel Stow on 12/29/15.
// Copyright © 2015 Groupon Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface ItemViewModel : NSObject
+ (instancetype)randomItem;
@property (nonatomic, copy) NSString *titleText;
@property (nonatomic, copy) NSString *firstInfoText;
@property (nonatomic, copy) NSString *secondInfoText;
@property (nonatomic, copy) NSString *originalPriceText;
@property (nonatomic, copy) NSString *finalPriceText;
@property (nonatomic, copy) NSString *soldOutText;
@property (nonatomic, copy) NSString *distanceLabelText;
@property (nonatomic, copy) NSString *badgeText;
- (NSURL *)imageURLWithSize:(CGSize)size;
@end

View File

@ -0,0 +1,99 @@
//
// GPDealViewModel.m
// Groupon
//
// Created by Samuel Stow on 12/29/15.
// Copyright © 2015 Groupon Inc. All rights reserved.
//
#import "ItemViewModel.h"
NSArray *titles;
NSArray *firstInfos;
NSArray *badges;
@interface ItemViewModel()
@property (nonatomic, assign) NSInteger catNumber;
@property (nonatomic, assign) NSInteger labelNumber;
@end
@implementation ItemViewModel
+ (instancetype)randomItem {
return [[ItemViewModel alloc] init];
}
- (instancetype)init {
self = [super init];
if (self) {
_titleText = [self randomObjectFromArray:titles];
_firstInfoText = [self randomObjectFromArray:firstInfos];
_secondInfoText = [NSString stringWithFormat:@"%zd+ bought", [self randomNumberInRange:5 to:6000]];
_originalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:40 to:90]];
_finalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:5 to:30]];
BOOL isSoldOut = arc4random() % 5 == 0;
_soldOutText = isSoldOut ? @"SOLD OUT" : nil;
_distanceLabelText = [NSString stringWithFormat:@"%zd mi", [self randomNumberInRange:1 to:20]];
BOOL isBadged = arc4random() % 2 == 0;
if (isBadged) {
_badgeText = [self randomObjectFromArray:badges];
}
_catNumber = [self randomNumberInRange:1 to:10];
_labelNumber = [self randomNumberInRange:1 to:10000];
}
return self;
}
- (NSURL *)imageURLWithSize:(CGSize)size {
NSString *imageText = [NSString stringWithFormat:@"Fun cat pic %zd", self.labelNumber];
NSString *urlString = [NSString stringWithFormat:@"http://lorempixel.com/%zd/%zd/cats/%zd/%@",
(NSInteger)roundl(size.width),
(NSInteger)roundl(size.height), self.catNumber, imageText];
urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return [NSURL URLWithString:urlString];
}
// titles courtesy of http://www.catipsum.com/
+ (void)initialize {
titles = @[@"Leave fur on owners clothes intrigued by the shower",
@"Meowwww",
@"Immediately regret falling into bathtub stare out the window",
@"Jump launch to pounce upon little yarn mouse, bare fangs at toy run hide in litter box until treats are fed",
@"Sleep nap",
@"Lick butt",
@"Chase laser lick arm hair present belly, scratch hand when stroked"];
firstInfos = @[@"Kitty Shop",
@"Cat's r us",
@"Fantastic Felines",
@"The Cat Shop",
@"Cat in a hat",
@"Cat-tastic"
];
badges = @[@"ADORABLE",
@"BOUNCES",
@"HATES CUCUMBERS",
@"SCRATCHY"
];
}
- (id)randomObjectFromArray:(NSArray *)strings
{
u_int32_t ipsumCount = (u_int32_t)[strings count];
u_int32_t location = arc4random_uniform(ipsumCount);
return strings[location];
}
- (uint32_t)randomNumberInRange:(uint32_t)start to:(uint32_t)end {
return start + arc4random_uniform(end - start);
}
@end

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/>
</dependencies>
<scenes/>
</document>

View File

@ -0,0 +1,15 @@
//
// LoadingNode.h
// Sample
//
// Created by Samuel Stow on 1/9/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface LoadingNode : ASCellNode
+ (CGFloat)desiredHeightForWidth:(CGFloat)width;
@end

View File

@ -0,0 +1,68 @@
//
// LoadingNode.m
// Sample
//
// Created by Samuel Stow on 1/9/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import "LoadingNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASCenterLayoutSpec.h>
static CGFloat kFixedHeight = 200.0f;
@interface LoadingNode ()
{
ASDisplayNode *_loadingSpinner;
}
@end
@implementation LoadingNode
#pragma mark -
#pragma mark ASCellNode.
+ (CGFloat)desiredHeightForWidth:(CGFloat)width {
return kFixedHeight;
}
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_loadingSpinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[spinner startAnimating];
return spinner;
}];
_loadingSpinner.preferredFrameSize = CGSizeMake(50, 50);
// add it as a subnode, and we're done
[self addSubnode:_loadingSpinner];
return self;
}
- (void)layout {
[super layout];
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASCenterLayoutSpec *centerSpec = [[ASCenterLayoutSpec alloc] init];
centerSpec.centeringOptions = ASCenterLayoutSpecCenteringXY;
centerSpec.sizingOptions = ASCenterLayoutSpecSizingOptionDefault;
centerSpec.child = _loadingSpinner;
return centerSpec;
}
@end

View File

@ -0,0 +1,15 @@
//
// PlacholderNetworkImageNode.h
// Sample
//
// Created by Samuel Stow on 1/14/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface PlaceholderNetworkImageNode : ASNetworkImageNode
@property (nonatomic, strong) UIImage *placeholderImageOverride;
@end

View File

@ -0,0 +1,18 @@
//
// PlacholderNetworkImageNode.m
// Sample
//
// Created by Samuel Stow on 1/14/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import "PlaceholderNetworkImageNode.h"
@implementation PlaceholderNetworkImageNode
- (UIImage *)placeholderImage {
return self.placeholderImageOverride;
}
@end

View File

@ -0,0 +1,13 @@
//
// PresentingViewController.h
// Sample
//
// Created by Tom King on 12/23/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface PresentingViewController : UIViewController
@end

View File

@ -0,0 +1,30 @@
//
// PresentingViewController.m
// Sample
//
// Created by Tom King on 12/23/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import "PresentingViewController.h"
#import "ViewController.h"
@interface PresentingViewController ()
@end
@implementation PresentingViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Push Details" style:UIBarButtonItemStylePlain target:self action:@selector(pushNewViewController)];
}
- (void)pushNewViewController
{
ViewController *controller = [[ViewController alloc] init];
[self.navigationController pushViewController:controller animated:true];
}
@end

View File

@ -0,0 +1,16 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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/UIKit.h>
@interface ViewController : UIViewController
@end

View File

@ -0,0 +1,244 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 "ViewController.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "ItemNode.h"
#import "BlurbNode.h"
#import "LoadingNode.h"
static const NSTimeInterval kWebResponseDelay = 1.0;
static const BOOL kSimulateWebResponse = YES;
static const NSInteger kBatchSize = 20;
static const CGFloat kHorizontalSectionPadding = 10.0f;
static const CGFloat kVerticalSectionPadding = 20.0f;
@interface ViewController () <ASCollectionViewDataSource, ASCollectionViewDelegateFlowLayout>
{
ASCollectionView *_collectionView;
NSMutableArray *_data;
}
@end
@implementation ViewController
#pragma mark -
#pragma mark UIViewController.
- (instancetype)init
{
self = [super init];
if (self) {
self.title = @"Cat Deals";
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
_collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collectionView.asyncDataSource = self;
_collectionView.asyncDelegate = self;
_collectionView.backgroundColor = [UIColor grayColor];
_collectionView.leadingScreensForBatching = 2;
ASRangeTuningParameters fetchDataTuning;
fetchDataTuning.leadingBufferScreenfuls = 2;
fetchDataTuning.trailingBufferScreenfuls = 1;
[_collectionView setTuningParameters:fetchDataTuning forRangeType:ASLayoutRangeTypeFetchData];
ASRangeTuningParameters preRenderTuning;
preRenderTuning.leadingBufferScreenfuls = 1;
preRenderTuning.trailingBufferScreenfuls = 0.5;
[_collectionView setTuningParameters:preRenderTuning forRangeType:ASLayoutRangeTypeDisplay];
[_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
[_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter];
_data = [[NSMutableArray alloc] init];
self.navigationItem.leftItemsSupplementBackButton = YES;
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadTapped)];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:_collectionView];
[self fetchMoreCatsWithCompletion:nil];
}
- (void)fetchMoreCatsWithCompletion:(void (^)(BOOL))completion {
if (kSimulateWebResponse) {
__weak typeof(self) weakSelf = self;
void(^mockWebService)() = ^{
NSLog(@"ViewController \"got data from a web service\"");
ViewController *strongSelf = weakSelf;
if (strongSelf != nil)
{
NSLog(@"ViewController is not nil");
[strongSelf appendMoreItems:kBatchSize completion:completion];
NSLog(@"ViewController finished updating collectionView");
}
else {
NSLog(@"ViewController is nil - won't update collectionView");
}
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kWebResponseDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), mockWebService);
} else {
[self appendMoreItems:kBatchSize completion:completion];
}
}
- (void)appendMoreItems:(NSInteger)numberOfNewItems completion:(void (^)(BOOL))completion {
NSArray *newData = [self getMoreData:numberOfNewItems];
dispatch_async(dispatch_get_main_queue(), ^{
[_collectionView performBatchUpdates:^{
[_data addObjectsFromArray:newData];
NSArray *addedIndexPaths = [self indexPathsForObjects:newData];
[_collectionView insertItemsAtIndexPaths:addedIndexPaths];
} completion:completion];
});
}
- (NSArray *)getMoreData:(NSInteger)count {
NSMutableArray *data = [NSMutableArray array];
for (int i = 0; i < count; i++) {
[data addObject:[ItemViewModel randomItem]];
}
return data;
}
- (NSArray *)indexPathsForObjects:(NSArray *)data {
NSMutableArray *indexPaths = [NSMutableArray array];
NSInteger section = 0;
for (ItemViewModel *viewModel in data) {
NSInteger item = [_data indexOfObject:viewModel];
NSAssert(item < [_data count] && item != NSNotFound, @"Item should be in _data");
[indexPaths addObject:[NSIndexPath indexPathForItem:item inSection:section]];
}
return indexPaths;
}
- (void)viewWillLayoutSubviews
{
_collectionView.frame = self.view.bounds;
}
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[_collectionView.collectionViewLayout invalidateLayout];
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
- (void)reloadTapped
{
[_collectionView reloadData];
}
#pragma mark -
#pragma mark ASCollectionView data source.
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
{
ItemViewModel *viewModel = _data[indexPath.item];
return [[ItemNode alloc] initWithViewModel:viewModel];
}
- (ASCellNode *)collectionView:(UICollectionView *)collectionView nodeForSupplementaryElementOfKind:(nonnull NSString *)kind atIndexPath:(nonnull NSIndexPath *)indexPath {
if ([kind isEqualToString:UICollectionElementKindSectionHeader] && indexPath.section == 0) {
return [[BlurbNode alloc] init];
} else if ([kind isEqualToString:UICollectionElementKindSectionFooter] && indexPath.section == 0) {
return [[LoadingNode alloc] init];
}
return nil;
}
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
if (section == 0) {
CGFloat width = CGRectGetWidth(self.view.frame) - 2 * kHorizontalSectionPadding;
return CGSizeMake(width, [BlurbNode desiredHeightForWidth:width]);
}
return CGSizeZero;
}
- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {
if (section == 0) {
CGFloat width = CGRectGetWidth(self.view.frame);
return CGSizeMake(width, [LoadingNode desiredHeightForWidth:width]);
}
return CGSizeZero;
}
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath {
CGFloat collectionViewWidth = CGRectGetWidth(self.view.frame) - 2 * kHorizontalSectionPadding;
CGFloat oneItemWidth = [ItemNode preferredViewSize].width;
NSInteger numColumns = floor(collectionViewWidth / oneItemWidth);
// Number of columns should be at least 1
numColumns = MAX(1, numColumns);
CGFloat totalSpaceBetweenColumns = (numColumns - 1) * kHorizontalSectionPadding;
CGFloat itemWidth = ((collectionViewWidth - totalSpaceBetweenColumns) / numColumns);
CGSize itemSize = [ItemNode sizeForWidth:itemWidth];
return ASSizeRangeMake(itemSize, itemSize);
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [_data count];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView
{
// lock the data source
// The data source should not be change until it is unlocked.
}
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView
{
// unlock the data source to enable data source updating.
}
- (void)collectionView:(UICollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context
{
NSLog(@"fetch additional content");
[self fetchMoreCatsWithCompletion:^(BOOL finished){
[context completeBatchFetching:YES];
}];
}
- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(kVerticalSectionPadding, kHorizontalSectionPadding, kVerticalSectionPadding, kHorizontalSectionPadding);
}
-(void)dealloc
{
NSLog(@"ViewController is deallocing");
}
@end

View File

@ -0,0 +1,19 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Sample.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -128,7 +128,6 @@
05E2127E19D4DB510098F589 /* Frameworks */,
05E2127F19D4DB510098F589 /* Resources */,
F012A6F39E0149F18F564F50 /* Copy Pods Resources */,
A5C135CBCFD74D965DE0D799 /* Embed Pods Frameworks */,
);
buildRules = (
);
@ -185,21 +184,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
A5C135CBCFD74D965DE0D799 /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;