mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-15 10:53:27 +00:00
merged with master
This commit is contained in:
commit
41721aa42a
@ -1,11 +1,11 @@
|
|||||||
Pod::Spec.new do |spec|
|
Pod::Spec.new do |spec|
|
||||||
spec.name = 'AsyncDisplayKit'
|
spec.name = 'AsyncDisplayKit'
|
||||||
spec.version = '1.9.5'
|
spec.version = '1.9.6'
|
||||||
spec.license = { :type => 'BSD' }
|
spec.license = { :type => 'BSD' }
|
||||||
spec.homepage = 'http://asyncdisplaykit.org'
|
spec.homepage = 'http://asyncdisplaykit.org'
|
||||||
spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' }
|
spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' }
|
||||||
spec.summary = 'Smooth asynchronous user interfaces for iOS apps.'
|
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/'
|
spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/'
|
||||||
|
|
||||||
@ -47,4 +47,6 @@ Pod::Spec.new do |spec|
|
|||||||
}
|
}
|
||||||
|
|
||||||
spec.ios.deployment_target = '7.0'
|
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
|
end
|
||||||
|
|||||||
@ -1587,8 +1587,6 @@
|
|||||||
058D09B9195D04C000B7D73C /* Frameworks */,
|
058D09B9195D04C000B7D73C /* Frameworks */,
|
||||||
058D09BA195D04C000B7D73C /* Resources */,
|
058D09BA195D04C000B7D73C /* Resources */,
|
||||||
3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */,
|
3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */,
|
||||||
527A806066E1F4E2795090DF /* Embed Pods Frameworks */,
|
|
||||||
1B86F48711505F91D5FEF571 /* Embed Pods Frameworks */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -1718,21 +1716,6 @@
|
|||||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n";
|
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
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 */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
|||||||
BIN
AsyncDisplayKit/.ASVideoNode.mm.un~
Normal file
BIN
AsyncDisplayKit/.ASVideoNode.mm.un~
Normal file
Binary file not shown.
@ -11,8 +11,9 @@
|
|||||||
|
|
||||||
@interface ASButtonNode : ASControlNode
|
@interface ASButtonNode : ASControlNode
|
||||||
|
|
||||||
@property (nonatomic, readonly) ASTextNode *titleNode;
|
@property (nonatomic, readonly) ASTextNode * _Nonnull titleNode;
|
||||||
@property (nonatomic, readonly) ASImageNode *imageNode;
|
@property (nonatomic, readonly) ASImageNode * _Nonnull imageNode;
|
||||||
|
@property (nonatomic, readonly) ASImageNode * _Nonnull backgroundImageNode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Spacing between image and title. Defaults to 8.0.
|
Spacing between image and title. Defaults to 8.0.
|
||||||
@ -36,11 +37,66 @@
|
|||||||
*/
|
*/
|
||||||
@property (nonatomic, assign) ASVerticalAlignment contentVerticalAlignment;
|
@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
|
@end
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#import "ASStackLayoutSpec.h"
|
#import "ASStackLayoutSpec.h"
|
||||||
#import "ASThread.h"
|
#import "ASThread.h"
|
||||||
#import "ASDisplayNode+Subclasses.h"
|
#import "ASDisplayNode+Subclasses.h"
|
||||||
|
#import "ASBackgroundLayoutSpec.h"
|
||||||
|
|
||||||
@interface ASButtonNode ()
|
@interface ASButtonNode ()
|
||||||
{
|
{
|
||||||
@ -24,6 +25,11 @@
|
|||||||
UIImage *_highlightedImage;
|
UIImage *_highlightedImage;
|
||||||
UIImage *_selectedImage;
|
UIImage *_selectedImage;
|
||||||
UIImage *_disabledImage;
|
UIImage *_disabledImage;
|
||||||
|
|
||||||
|
UIImage *_normalBackgroundImage;
|
||||||
|
UIImage *_highlightedBackgroundImage;
|
||||||
|
UIImage *_selectedBackgroundImage;
|
||||||
|
UIImage *_disabledBackgroundImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@ -41,33 +47,50 @@
|
|||||||
|
|
||||||
_titleNode = [[ASTextNode alloc] init];
|
_titleNode = [[ASTextNode alloc] init];
|
||||||
_imageNode = [[ASImageNode alloc] init];
|
_imageNode = [[ASImageNode alloc] init];
|
||||||
|
_backgroundImageNode = [[ASImageNode alloc] init];
|
||||||
|
[_backgroundImageNode setContentMode:UIViewContentModeScaleToFill];
|
||||||
|
|
||||||
|
[_titleNode setLayerBacked:YES];
|
||||||
|
[_imageNode setLayerBacked:YES];
|
||||||
|
[_backgroundImageNode setLayerBacked:YES];
|
||||||
|
|
||||||
_contentHorizontalAlignment = ASAlignmentMiddle;
|
_contentHorizontalAlignment = ASAlignmentMiddle;
|
||||||
_contentVerticalAlignment = ASAlignmentCenter;
|
_contentVerticalAlignment = ASAlignmentCenter;
|
||||||
|
|
||||||
|
[self addSubnode:_backgroundImageNode];
|
||||||
[self addSubnode:_titleNode];
|
[self addSubnode:_titleNode];
|
||||||
[self addSubnode:_imageNode];
|
[self addSubnode:_imageNode];
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setLayerBacked:(BOOL)layerBacked
|
||||||
|
{
|
||||||
|
ASDisplayNodeAssert(!layerBacked, @"ASButtonNode must not be layer backed!");
|
||||||
|
[super setLayerBacked:layerBacked];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setEnabled:(BOOL)enabled
|
- (void)setEnabled:(BOOL)enabled
|
||||||
{
|
{
|
||||||
[super setEnabled:enabled];
|
[super setEnabled:enabled];
|
||||||
[self updateImage];
|
[self updateButtonContent];
|
||||||
[self updateTitle];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setHighlighted:(BOOL)highlighted
|
- (void)setHighlighted:(BOOL)highlighted
|
||||||
{
|
{
|
||||||
[super setHighlighted:highlighted];
|
[super setHighlighted:highlighted];
|
||||||
[self updateImage];
|
[self updateButtonContent];
|
||||||
[self updateTitle];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setSelected:(BOOL)selected
|
- (void)setSelected:(BOOL)selected
|
||||||
{
|
{
|
||||||
[super setSelected:selected];
|
[super setSelected:selected];
|
||||||
|
[self updateButtonContent];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateButtonContent
|
||||||
|
{
|
||||||
|
[self updateBackgroundImage];
|
||||||
[self updateImage];
|
[self updateImage];
|
||||||
[self updateTitle];
|
[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
|
- (CGFloat)contentSpacing
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(_propertyLock);
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
@ -152,6 +196,18 @@
|
|||||||
[self setNeedsLayout];
|
[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
|
- (NSAttributedString *)attributedTitleForState:(ASControlState)state
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(_propertyLock);
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
@ -246,6 +302,54 @@
|
|||||||
[self updateImage];
|
[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
|
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
|
||||||
{
|
{
|
||||||
ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init];
|
ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init];
|
||||||
@ -265,12 +369,18 @@
|
|||||||
|
|
||||||
stack.children = children;
|
stack.children = children;
|
||||||
|
|
||||||
|
if (self.backgroundImageNode.image) {
|
||||||
|
return [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:stack
|
||||||
|
background:self.backgroundImageNode];
|
||||||
|
} else {
|
||||||
return stack;
|
return stack;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)layout
|
- (void)layout
|
||||||
{
|
{
|
||||||
[super layout];
|
[super layout];
|
||||||
|
self.backgroundImageNode.hidden = self.backgroundImageNode.image == nil;
|
||||||
self.imageNode.hidden = self.imageNode.image == nil;
|
self.imageNode.hidden = self.imageNode.image == nil;
|
||||||
self.titleNode.hidden = self.titleNode.attributedString.length > 0 == NO;
|
self.titleNode.hidden = self.titleNode.attributedString.length > 0 == NO;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#import "ASCollectionNode.h"
|
||||||
@protocol ASCollectionViewLayoutFacilitatorProtocol;
|
@protocol ASCollectionViewLayoutFacilitatorProtocol;
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|||||||
@ -91,6 +91,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
*/
|
*/
|
||||||
- (void)layoutDidFinish;
|
- (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 */
|
/** @name Layout calculation */
|
||||||
|
|
||||||
|
|||||||
@ -641,7 +641,9 @@ NS_ASSUME_NONNULL_END
|
|||||||
@property (atomic, assign) UIViewContentMode contentMode; // default=UIViewContentModeScaleToFill
|
@property (atomic, assign) UIViewContentMode contentMode; // default=UIViewContentModeScaleToFill
|
||||||
|
|
||||||
@property (atomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // default=YES (NO for layer-backed nodes)
|
@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
|
@property (atomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; // default=NO
|
||||||
|
#endif
|
||||||
@property (atomic, assign, nullable) CGColorRef shadowColor; // default=opaque rgb black
|
@property (atomic, assign, nullable) CGColorRef shadowColor; // default=opaque rgb black
|
||||||
@property (atomic, assign) CGFloat shadowOpacity; // default=0.0
|
@property (atomic, assign) CGFloat shadowOpacity; // default=0.0
|
||||||
@property (atomic, assign) CGSize shadowOffset; // default=(0, -3)
|
@property (atomic, assign) CGSize shadowOffset; // default=(0, -3)
|
||||||
@ -658,6 +660,16 @@ NS_ASSUME_NONNULL_END
|
|||||||
- (BOOL)isFirstResponder;
|
- (BOOL)isFirstResponder;
|
||||||
- (BOOL)canPerformAction:(nonnull SEL)action withSender:(nonnull id)sender;
|
- (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
|
// Accessibility support
|
||||||
@property (atomic, assign) BOOL isAccessibilityElement;
|
@property (atomic, assign) BOOL isAccessibilityElement;
|
||||||
@property (nullable, atomic, copy) NSString *accessibilityLabel;
|
@property (nullable, atomic, copy) NSString *accessibilityLabel;
|
||||||
|
|||||||
@ -594,6 +594,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
_layout = [self calculateLayoutThatFits:constrainedSize];
|
_layout = [self calculateLayoutThatFits:constrainedSize];
|
||||||
_constrainedSize = constrainedSize;
|
_constrainedSize = constrainedSize;
|
||||||
_flags.isMeasured = YES;
|
_flags.isMeasured = YES;
|
||||||
|
[self calculatedLayoutDidChange];
|
||||||
}
|
}
|
||||||
|
|
||||||
ASDisplayNodeAssertTrue(_layout.layoutableObject == self);
|
ASDisplayNodeAssertTrue(_layout.layoutableObject == self);
|
||||||
@ -615,6 +616,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
|||||||
return _layout;
|
return _layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)calculatedLayoutDidChange
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)displaysAsynchronously
|
- (BOOL)displaysAsynchronously
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(_propertyLock);
|
ASDN::MutexLocker l(_propertyLock);
|
||||||
@ -2312,6 +2317,38 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
|
|||||||
return self;
|
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
|
@end
|
||||||
|
|
||||||
@implementation ASDisplayNode (Debugging)
|
@implementation ASDisplayNode (Debugging)
|
||||||
|
|||||||
@ -163,7 +163,9 @@
|
|||||||
_textKitComponents.textView = self.textView;
|
_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 = 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;
|
_textKitComponents.textView.delegate = self;
|
||||||
|
#if TARGET_OS_IOS
|
||||||
_textKitComponents.textView.editable = YES;
|
_textKitComponents.textView.editable = YES;
|
||||||
|
#endif
|
||||||
_textKitComponents.textView.typingAttributes = _typingAttributes;
|
_textKitComponents.textView.typingAttributes = _typingAttributes;
|
||||||
_textKitComponents.textView.returnKeyType = _returnKeyType;
|
_textKitComponents.textView.returnKeyType = _returnKeyType;
|
||||||
_textKitComponents.textView.accessibilityHint = _placeholderTextKitComponents.textStorage.string;
|
_textKitComponents.textView.accessibilityHint = _placeholderTextKitComponents.textStorage.string;
|
||||||
|
|||||||
@ -235,7 +235,21 @@
|
|||||||
UIRectFill({ .size = backingSize });
|
UIRectFill({ .size = backingSize });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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];
|
[image drawInRect:imageDrawRect];
|
||||||
|
}
|
||||||
|
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
UIGraphicsEndImageContext();
|
UIGraphicsEndImageContext();
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <AsyncDisplayKit/ASImageNode.h>
|
#import <AsyncDisplayKit/ASImageNode.h>
|
||||||
|
#if TARGET_OS_IOS
|
||||||
#import <MapKit/MapKit.h>
|
#import <MapKit/MapKit.h>
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
@ -14,7 +15,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@interface ASMapNode : ASImageNode
|
@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;
|
@property (nonatomic, assign) MKCoordinateRegion region;
|
||||||
|
|
||||||
@ -29,7 +36,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@property (nonatomic, assign, getter=isLiveMap) BOOL liveMap;
|
@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.
|
@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;
|
@property (nonatomic, assign) BOOL needsMapReloadOnBoundsChange;
|
||||||
@ -40,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@property (nonatomic, weak) id <MKMapViewDelegate> mapDelegate;
|
@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
|
* @param annotations An array of objects that conform to the MKAnnotation protocol
|
||||||
*/
|
*/
|
||||||
- (void)setAnnotations:(NSArray<id<MKAnnotation>> *)annotations;
|
- (void)setAnnotations:(NSArray<id<MKAnnotation>> *)annotations;
|
||||||
@ -48,3 +56,5 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -6,6 +6,7 @@
|
|||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if TARGET_OS_IOS
|
||||||
#import "ASMapNode.h"
|
#import "ASMapNode.h"
|
||||||
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
|
||||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||||
@ -17,7 +18,6 @@
|
|||||||
{
|
{
|
||||||
ASDN::RecursiveMutex _propertyLock;
|
ASDN::RecursiveMutex _propertyLock;
|
||||||
MKMapSnapshotter *_snapshotter;
|
MKMapSnapshotter *_snapshotter;
|
||||||
MKMapSnapshotOptions *_options;
|
|
||||||
NSArray *_annotations;
|
NSArray *_annotations;
|
||||||
CLLocationCoordinate2D _centerCoordinateOfMap;
|
CLLocationCoordinate2D _centerCoordinateOfMap;
|
||||||
}
|
}
|
||||||
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
@synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange;
|
@synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange;
|
||||||
@synthesize mapDelegate = _mapDelegate;
|
@synthesize mapDelegate = _mapDelegate;
|
||||||
@synthesize region = _region;
|
@synthesize options = _options;
|
||||||
@synthesize liveMap = _liveMap;
|
@synthesize liveMap = _liveMap;
|
||||||
|
|
||||||
#pragma mark - Lifecycle
|
#pragma mark - Lifecycle
|
||||||
@ -42,13 +42,6 @@
|
|||||||
_needsMapReloadOnBoundsChange = YES;
|
_needsMapReloadOnBoundsChange = YES;
|
||||||
_liveMap = NO;
|
_liveMap = NO;
|
||||||
_centerCoordinateOfMap = kCLLocationCoordinate2DInvalid;
|
_centerCoordinateOfMap = kCLLocationCoordinate2DInvalid;
|
||||||
|
|
||||||
//Default world-scale view
|
|
||||||
_region = MKCoordinateRegionForMapRect(MKMapRectWorld);
|
|
||||||
|
|
||||||
_options = [[MKMapSnapshotOptions alloc] init];
|
|
||||||
_options.region = _region;
|
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,23 +111,40 @@
|
|||||||
_needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange;
|
_needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MKCoordinateRegion)region
|
- (MKMapSnapshotOptions *)options
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(_propertyLock);
|
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
|
- (void)setRegion:(MKCoordinateRegion)region
|
||||||
{
|
{
|
||||||
ASDN::MutexLocker l(_propertyLock);
|
self.options.region = region;
|
||||||
_region = region;
|
|
||||||
if (self.isLiveMap) {
|
|
||||||
[_mapView setRegion:_region animated:YES];
|
|
||||||
} else {
|
|
||||||
_options.region = _region;
|
|
||||||
[self resetSnapshotter];
|
|
||||||
[self takeSnapshot];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Snapshotter
|
#pragma mark - Snapshotter
|
||||||
@ -182,14 +192,25 @@
|
|||||||
- (void)setUpSnapshotter
|
- (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.");
|
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:self.options];
|
||||||
_snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resetSnapshotter
|
- (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 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
|
#pragma mark - Actions
|
||||||
@ -200,7 +221,7 @@
|
|||||||
__weak ASMapNode *weakSelf = self;
|
__weak ASMapNode *weakSelf = self;
|
||||||
_mapView = [[MKMapView alloc] initWithFrame:CGRectZero];
|
_mapView = [[MKMapView alloc] initWithFrame:CGRectZero];
|
||||||
_mapView.delegate = weakSelf.mapDelegate;
|
_mapView.delegate = weakSelf.mapDelegate;
|
||||||
[_mapView setRegion:_options.region];
|
[weakSelf applySnapshotOptions];
|
||||||
[_mapView addAnnotations:_annotations];
|
[_mapView addAnnotations:_annotations];
|
||||||
[weakSelf setNeedsLayout];
|
[weakSelf setNeedsLayout];
|
||||||
[weakSelf.view addSubview:_mapView];
|
[weakSelf.view addSubview:_mapView];
|
||||||
@ -213,6 +234,7 @@
|
|||||||
|
|
||||||
- (void)removeLiveMap
|
- (void)removeLiveMap
|
||||||
{
|
{
|
||||||
|
// FIXME: With MKCoordinateRegion, isn't the center coordinate fully specified? Do we need this?
|
||||||
_centerCoordinateOfMap = _mapView.centerCoordinate;
|
_centerCoordinateOfMap = _mapView.centerCoordinate;
|
||||||
[_mapView removeFromSuperview];
|
[_mapView removeFromSuperview];
|
||||||
_mapView = nil;
|
_mapView = nil;
|
||||||
@ -231,7 +253,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Layout
|
#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
|
- (void)layout
|
||||||
{
|
{
|
||||||
[super layout];
|
[super layout];
|
||||||
@ -239,11 +279,13 @@
|
|||||||
_mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height);
|
_mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height);
|
||||||
} else {
|
} else {
|
||||||
// If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter.
|
// 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) {
|
if (_needsMapReloadOnBoundsChange) {
|
||||||
_options.size = self.bounds.size;
|
[self setSnapshotSizeIfNeeded:self.bounds.size];
|
||||||
[self resetSnapshotter];
|
// FIXME: Adding a check for FetchData here seems to cause intermittent map load failures, but shouldn't.
|
||||||
|
// if (ASInterfaceStateIncludesFetchData(self.interfaceState)) {
|
||||||
[self takeSnapshot];
|
[self takeSnapshot];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
#endif
|
||||||
@ -6,9 +6,10 @@
|
|||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if TARGET_OS_IOS
|
||||||
|
|
||||||
#import <AsyncDisplayKit/ASImageNode.h>
|
#import <AsyncDisplayKit/ASImageNode.h>
|
||||||
#import <AsyncDisplayKit/ASImageProtocols.h>
|
#import <AsyncDisplayKit/ASImageProtocols.h>
|
||||||
|
|
||||||
#import <Photos/Photos.h>
|
#import <Photos/Photos.h>
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
@ -116,13 +117,14 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) {
|
|||||||
*/
|
*/
|
||||||
@property (nullable, nonatomic, readonly) ASImageIdentifier displayedImageIdentifier;
|
@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.
|
* @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.
|
* @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below.
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, strong) PHImageManager *imageManager;
|
@property (nonatomic, strong) PHImageManager *imageManager;
|
||||||
|
#endif
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
@ -229,6 +231,7 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier
|
|||||||
*/
|
*/
|
||||||
- (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier;
|
- (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier;
|
||||||
|
|
||||||
|
#if TARGET_OS_IOS
|
||||||
/**
|
/**
|
||||||
* @abstract A PHAsset for the specific asset local identifier
|
* @abstract A PHAsset for the specific asset local identifier
|
||||||
* @param imageNode The sender.
|
* @param imageNode The sender.
|
||||||
@ -240,11 +243,11 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier
|
|||||||
* @return A PHAsset corresponding to `assetLocalIdentifier`, or nil if none is available.
|
* @return A PHAsset corresponding to `assetLocalIdentifier`, or nil if none is available.
|
||||||
*/
|
*/
|
||||||
- (nullable PHAsset *)multiplexImageNode:(ASMultiplexImageNode *)imageNode assetForLocalIdentifier:(NSString *)assetLocalIdentifier;
|
- (nullable PHAsset *)multiplexImageNode:(ASMultiplexImageNode *)imageNode assetForLocalIdentifier:(NSString *)assetLocalIdentifier;
|
||||||
|
#endif
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
#if TARGET_OS_IOS
|
||||||
@interface NSURL (ASPhotosFrameworkURLs)
|
@interface NSURL (ASPhotosFrameworkURLs)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -261,5 +264,8 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier
|
|||||||
options:(PHImageRequestOptions *)options;
|
options:(PHImageRequestOptions *)options;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
#endif
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@ -6,12 +6,12 @@
|
|||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
* 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 <AssetsLibrary/AssetsLibrary.h>
|
||||||
|
|
||||||
#import <Photos/Photos.h>
|
#import <Photos/Photos.h>
|
||||||
|
|
||||||
#import <libkern/OSAtomic.h>
|
#import <libkern/OSAtomic.h>
|
||||||
|
|
||||||
#import "ASAvailability.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;
|
- (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.
|
@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.
|
@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.
|
@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;
|
- (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.
|
@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.
|
@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;
|
_dataSource = dataSource;
|
||||||
_dataSourceFlags.image = [_dataSource respondsToSelector:@selector(multiplexImageNode:imageForImageIdentifier:)];
|
_dataSourceFlags.image = [_dataSource respondsToSelector:@selector(multiplexImageNode:imageForImageIdentifier:)];
|
||||||
_dataSourceFlags.URL = [_dataSource respondsToSelector:@selector(multiplexImageNode:URLForImageIdentifier:)];
|
_dataSourceFlags.URL = [_dataSource respondsToSelector:@selector(multiplexImageNode:URLForImageIdentifier:)];
|
||||||
|
#if TARGET_OS_IOS
|
||||||
_dataSourceFlags.asset = [_dataSource respondsToSelector:@selector(multiplexImageNode:assetForLocalIdentifier:)];
|
_dataSourceFlags.asset = [_dataSource respondsToSelector:@selector(multiplexImageNode:assetForLocalIdentifier:)];
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
@ -455,6 +458,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if TARGET_OS_IOS
|
||||||
// If it's an assets-library URL, we need to fetch it from the assets library.
|
// If it's an assets-library URL, we need to fetch it from the assets library.
|
||||||
if ([[nextImageURL scheme] isEqualToString:kAssetsLibraryURLScheme]) {
|
if ([[nextImageURL scheme] isEqualToString:kAssetsLibraryURLScheme]) {
|
||||||
// Load the asset.
|
// Load the asset.
|
||||||
@ -470,6 +474,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
finishedLoadingBlock(image, nextImageIdentifier, error);
|
finishedLoadingBlock(image, nextImageIdentifier, error);
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
else // Otherwise, it's a web URL that we can download.
|
else // Otherwise, it's a web URL that we can download.
|
||||||
{
|
{
|
||||||
// First, check the cache.
|
// 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
|
- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
|
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
|
||||||
@ -609,7 +614,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
_phImageRequestOperation = newImageRequestOp;
|
_phImageRequestOperation = newImageRequestOp;
|
||||||
[phImageRequestQueue addOperation:newImageRequestOp];
|
[phImageRequestQueue addOperation:newImageRequestOp];
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock
|
- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
|
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
|
||||||
@ -708,7 +713,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
#if TARGET_OS_IOS
|
||||||
@implementation NSURL (ASPhotosFrameworkURLs)
|
@implementation NSURL (ASPhotosFrameworkURLs)
|
||||||
|
|
||||||
+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options
|
+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options
|
||||||
@ -721,3 +726,6 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
|||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@ -48,12 +48,13 @@
|
|||||||
[super didLoad];
|
[super didLoad];
|
||||||
|
|
||||||
ASCollectionView *cv = self.view;
|
ASCollectionView *cv = self.view;
|
||||||
|
#if TARGET_OS_IOS
|
||||||
cv.pagingEnabled = YES;
|
cv.pagingEnabled = YES;
|
||||||
|
cv.scrollsToTop = NO;
|
||||||
|
#endif
|
||||||
cv.allowsSelection = NO;
|
cv.allowsSelection = NO;
|
||||||
cv.showsVerticalScrollIndicator = NO;
|
cv.showsVerticalScrollIndicator = NO;
|
||||||
cv.showsHorizontalScrollIndicator = NO;
|
cv.showsHorizontalScrollIndicator = NO;
|
||||||
cv.scrollsToTop = NO;
|
|
||||||
|
|
||||||
// Zeroing contentInset is important, as UIKit will set the top inset for the navigation bar even though
|
// 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.
|
// our view is only horizontally scrollable. This causes UICollectionViewFlowLayout to log a warning.
|
||||||
|
|||||||
@ -73,8 +73,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
|
|
||||||
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
|
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
- (nullable NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath;
|
- (nullable NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
#if TARGET_OS_IOS
|
||||||
- (nullable NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath;
|
- (nullable NSArray<UITableViewRowAction *> *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
#endif
|
||||||
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
|
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath;
|
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|||||||
@ -35,13 +35,13 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
|||||||
|
|
||||||
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
|
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
|
||||||
textOrigin:(CGPoint)textOrigin
|
textOrigin:(CGPoint)textOrigin
|
||||||
backgroundColor:(CGColorRef)backgroundColor;
|
backgroundColor:(UIColor *)backgroundColor;
|
||||||
|
|
||||||
@property (nonatomic, strong, readonly) ASTextKitRenderer *renderer;
|
@property (nonatomic, strong, readonly) ASTextKitRenderer *renderer;
|
||||||
|
|
||||||
@property (nonatomic, assign, readonly) CGPoint textOrigin;
|
@property (nonatomic, assign, readonly) CGPoint textOrigin;
|
||||||
|
|
||||||
@property (nonatomic, assign, readonly) CGColorRef backgroundColor;
|
@property (nonatomic, strong, readonly) UIColor *backgroundColor;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@ -49,20 +49,18 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
|||||||
|
|
||||||
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
|
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
|
||||||
textOrigin:(CGPoint)textOrigin
|
textOrigin:(CGPoint)textOrigin
|
||||||
backgroundColor:(CGColorRef)backgroundColor
|
backgroundColor:(UIColor *)backgroundColor
|
||||||
{
|
{
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_renderer = renderer;
|
_renderer = renderer;
|
||||||
_textOrigin = textOrigin;
|
_textOrigin = textOrigin;
|
||||||
_backgroundColor = CGColorRetain(backgroundColor);
|
_backgroundColor = backgroundColor;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
CGColorRelease(_backgroundColor);
|
|
||||||
|
|
||||||
// Destruction of the layout managers/containers/text storage is quite
|
// Destruction of the layout managers/containers/text storage is quite
|
||||||
// expensive, and can take some time, so we dispatch onto a bg queue to
|
// expensive, and can take some time, so we dispatch onto a bg queue to
|
||||||
// actually dealloc.
|
// actually dealloc.
|
||||||
@ -182,24 +180,11 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
|||||||
NSString *truncationString = [_composedTruncationString string];
|
NSString *truncationString = [_composedTruncationString string];
|
||||||
if (plainString.length > 50)
|
if (plainString.length > 50)
|
||||||
plainString = [[plainString substringToIndex:50] stringByAppendingString:@"\u2026"];
|
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
|
#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.
|
// 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.
|
// This code was written before TextKit and when 512MB devices were still the overwhelming majority.
|
||||||
- (void)displayDidFinish
|
- (void)displayDidFinish
|
||||||
@ -240,13 +225,13 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
|||||||
- (void)setFrame:(CGRect)frame
|
- (void)setFrame:(CGRect)frame
|
||||||
{
|
{
|
||||||
[super setFrame:frame];
|
[super setFrame:frame];
|
||||||
[self _invalidateRendererIfNeeded:frame.size];
|
[self _invalidateRendererIfNeededForBoundsSize:frame.size];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setBounds:(CGRect)bounds
|
- (void)setBounds:(CGRect)bounds
|
||||||
{
|
{
|
||||||
[super setBounds:bounds];
|
[super setBounds:bounds];
|
||||||
[self _invalidateRendererIfNeeded:bounds.size];
|
[self _invalidateRendererIfNeededForBoundsSize:bounds.size];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Renderer Management
|
#pragma mark - Renderer Management
|
||||||
@ -291,12 +276,12 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
|||||||
|
|
||||||
- (void)_invalidateRendererIfNeeded
|
- (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,
|
// 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
|
// so our previous layout information is invalid, and TextKit may draw at the
|
||||||
// incorrect origin.
|
// 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) {
|
if (!_renderer) {
|
||||||
return YES;
|
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
|
// 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.
|
// 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;
|
return NO;
|
||||||
} else {
|
} else {
|
||||||
// It is very common to have a constrainedSize with a concrete, specific width but +Inf height.
|
// 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,
|
// 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).
|
// as this would essentially serve to set its constrainedSize to be its calculatedSize (unnecessary).
|
||||||
ASLayout *layout = self.calculatedLayout;
|
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;
|
return NO;
|
||||||
} else {
|
} else {
|
||||||
return YES;
|
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
|
#pragma mark - Modifying User Text
|
||||||
|
|
||||||
- (void)setAttributedString:(NSAttributedString *)attributedString
|
- (void)setAttributedString:(NSAttributedString *)attributedString
|
||||||
@ -409,12 +427,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
|||||||
|
|
||||||
// Fill background
|
// Fill background
|
||||||
if (!isRasterizing) {
|
if (!isRasterizing) {
|
||||||
CGColorRef backgroundColor = parameters.backgroundColor;
|
UIColor *backgroundColor = parameters.backgroundColor;
|
||||||
if (backgroundColor) {
|
if (backgroundColor) {
|
||||||
CGContextSetFillColorWithColor(context, backgroundColor);
|
[backgroundColor setFill];
|
||||||
CGContextSetBlendMode(context, kCGBlendModeCopy);
|
UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy);
|
||||||
CGContextFillRect(context, CGContextGetClipBoundingBox(context));
|
|
||||||
CGContextSetBlendMode(context, kCGBlendModeNormal);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,14 +446,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
|||||||
|
|
||||||
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
|
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
|
||||||
{
|
{
|
||||||
[self _invalidateRendererIfNeeded];
|
CGRect bounds = self.bounds;
|
||||||
|
[self _invalidateRendererIfNeededForBoundsSize:bounds.size];
|
||||||
|
|
||||||
// Offset the text origin by any shadow padding
|
// Offset the text origin by any shadow padding
|
||||||
UIEdgeInsets shadowPadding = [self shadowPadding];
|
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]
|
return [[ASTextNodeDrawParameters alloc] initWithRenderer:[self _renderer]
|
||||||
textOrigin:textOrigin
|
textOrigin:textOrigin
|
||||||
backgroundColor:self.backgroundColor.CGColor];
|
backgroundColor:self.backgroundColor];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Attributes
|
#pragma mark - Attributes
|
||||||
|
|||||||
@ -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>
|
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||||
|
|
||||||
@ -9,6 +16,10 @@ typedef NS_ENUM(NSUInteger, ASVideoGravity) {
|
|||||||
|
|
||||||
@protocol ASVideoNodeDelegate;
|
@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
|
@interface ASVideoNode : ASControlNode
|
||||||
@property (atomic, strong, readwrite) AVAsset *asset;
|
@property (atomic, strong, readwrite) AVAsset *asset;
|
||||||
@property (atomic, strong, readonly) AVPlayer *player;
|
@property (atomic, strong, readonly) AVPlayer *player;
|
||||||
|
|||||||
@ -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 "ASVideoNode.h"
|
||||||
#import "ASDisplayNode+Beta.h"
|
|
||||||
|
|
||||||
@interface ASVideoNode ()
|
@interface ASVideoNode ()
|
||||||
{
|
{
|
||||||
@ -34,12 +39,18 @@
|
|||||||
|
|
||||||
- (instancetype)init
|
- (instancetype)init
|
||||||
{
|
{
|
||||||
if (!(self = [super init])) { return nil; }
|
if (!(self = [super init])) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
_previewQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
|
_previewQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
|
||||||
|
|
||||||
[ASDisplayNode setShouldUseNewRenderingRange:YES];
|
[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;
|
self.gravity = AVLayerVideoGravityResizeAspect;
|
||||||
|
|
||||||
[self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
|
[self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside];
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
// Created by Adlai Holler on 9/25/15.
|
// Created by Adlai Holler on 9/25/15.
|
||||||
// Copyright © 2015 Facebook. All rights reserved.
|
// Copyright © 2015 Facebook. All rights reserved.
|
||||||
//
|
//
|
||||||
|
#if TARGET_OS_IOS
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <Photos/Photos.h>
|
#import <Photos/Photos.h>
|
||||||
|
|
||||||
@ -64,3 +64,4 @@ extern NSString *const ASPhotosURLScheme;
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
// NS_ASSUME_NONNULL_END
|
// NS_ASSUME_NONNULL_END
|
||||||
|
#endif
|
||||||
@ -5,7 +5,7 @@
|
|||||||
// Created by Adlai Holler on 9/25/15.
|
// Created by Adlai Holler on 9/25/15.
|
||||||
// Copyright © 2015 Facebook. All rights reserved.
|
// Copyright © 2015 Facebook. All rights reserved.
|
||||||
//
|
//
|
||||||
|
#if TARGET_OS_IOS
|
||||||
#import "ASPhotosFrameworkImageRequest.h"
|
#import "ASPhotosFrameworkImageRequest.h"
|
||||||
#import "ASBaseDefines.h"
|
#import "ASBaseDefines.h"
|
||||||
#import "ASAvailability.h"
|
#import "ASAvailability.h"
|
||||||
@ -159,3 +159,4 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h";
|
|||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
#endif
|
||||||
@ -21,8 +21,9 @@
|
|||||||
{
|
{
|
||||||
BOOL _rangeIsValid;
|
BOOL _rangeIsValid;
|
||||||
BOOL _queuedRangeUpdate;
|
BOOL _queuedRangeUpdate;
|
||||||
|
BOOL _layoutControllerImplementsSetVisibleIndexPaths;
|
||||||
ASScrollDirection _scrollDirection;
|
ASScrollDirection _scrollDirection;
|
||||||
NSSet *_allPreviousIndexPaths;
|
NSSet<NSIndexPath *> *_allPreviousIndexPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@ -58,64 +59,84 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setLayoutController:(id<ASLayoutController>)layoutController
|
||||||
|
{
|
||||||
|
_layoutController = layoutController;
|
||||||
|
_layoutControllerImplementsSetVisibleIndexPaths = [_layoutController respondsToSelector:@selector(setVisibleNodeIndexPaths:)];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)_updateVisibleNodeIndexPaths
|
- (void)_updateVisibleNodeIndexPaths
|
||||||
{
|
{
|
||||||
if (!_queuedRangeUpdate) {
|
ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController");
|
||||||
|
if (!_queuedRangeUpdate || !_layoutController) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Consider if we need to check this separately from the range calculation below.
|
// TODO: Consider if we need to use this codepath, or can rely on something more similar to the data & display ranges
|
||||||
NSArray *visibleNodePaths = [_dataSource visibleNodeIndexPathsForRangeController:self];
|
// 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)...
|
if (visibleNodePaths.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)...
|
||||||
_queuedRangeUpdate = NO;
|
_queuedRangeUpdate = NO;
|
||||||
return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later
|
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:[_dataSource viewportSizeForRangeController:self]];
|
||||||
[_layoutController setViewportSize:viewportSize];
|
|
||||||
|
|
||||||
// the layout controller needs to know what the current visible indices are to calculate range offsets
|
// 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];
|
[_layoutController setVisibleNodeIndexPaths:visibleNodePaths];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray *allNodes = [_dataSource completedNodes];
|
// allNodes is a 2D array: it contains arrays for each section, each containing nodes.
|
||||||
NSArray *currentSectionNodes = nil;
|
NSArray<NSArray *> *allNodes = [_dataSource completedNodes];
|
||||||
NSInteger currentSectionIndex = -1; // Will be unequal to any indexPath.section, so we set currentSectionNodes.
|
|
||||||
|
|
||||||
NSUInteger numberOfSections = [allNodes count];
|
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;
|
NSUInteger numberOfNodesInSection = 0;
|
||||||
|
|
||||||
NSSet *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths];
|
NSSet<NSIndexPath *> *visibleIndexPaths = [NSSet setWithArray:visibleNodePaths];
|
||||||
// = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeVisible];
|
NSSet<NSIndexPath *> *displayIndexPaths = nil;
|
||||||
NSSet *displayIndexPaths = nil;
|
NSSet<NSIndexPath *> *fetchDataIndexPaths = nil;
|
||||||
NSSet *fetchDataIndexPaths = nil;
|
|
||||||
NSMutableSet *allIndexPaths = nil;
|
// Prioritize the order in which we visit each. Visible nodes should be updated first so they are enqueued on
|
||||||
NSMutableArray *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
|
// the network or display queues before preloading (offscreen) nodes are enqueued.
|
||||||
|
NSMutableOrderedSet<NSIndexPath *> *allIndexPaths = [[NSMutableOrderedSet alloc] initWithSet:visibleIndexPaths];
|
||||||
|
|
||||||
ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self];
|
ASInterfaceState selfInterfaceState = [_dataSource interfaceStateForRangeController:self];
|
||||||
|
|
||||||
if (ASInterfaceStateIncludesVisible(selfInterfaceState)) {
|
if (ASInterfaceStateIncludesVisible(selfInterfaceState)) {
|
||||||
// If we are already visible, get busy! Better get started on preloading before the user scrolls more...
|
// If we are already visible, get busy! Better get started on preloading before the user scrolls more...
|
||||||
fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData];
|
fetchDataIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeFetchData];
|
||||||
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.
|
ASRangeTuningParameters parametersFetchData = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeFetchData];
|
||||||
allIndexPaths = [fetchDataIndexPaths mutableCopy];
|
ASRangeTuningParameters parametersDisplay = [_layoutController tuningParametersForRangeType:ASLayoutRangeTypeDisplay];
|
||||||
[allIndexPaths unionSet:displayIndexPaths];
|
if (parametersDisplay.leadingBufferScreenfuls == parametersFetchData.leadingBufferScreenfuls &&
|
||||||
[allIndexPaths unionSet:visibleIndexPaths];
|
parametersDisplay.trailingBufferScreenfuls == parametersFetchData.trailingBufferScreenfuls) {
|
||||||
|
displayIndexPaths = fetchDataIndexPaths;
|
||||||
} else {
|
} else {
|
||||||
allIndexPaths = [visibleIndexPaths mutableCopy];
|
displayIndexPaths = [_layoutController indexPathsForScrolling:_scrollDirection rangeType:ASLayoutRangeTypeDisplay];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets are magical. Add anything we had applied interfaceState to in the last update, so we can clear any
|
// Typically the fetchDataIndexPaths will be the largest, and be a superset of the others, though it may be disjoint.
|
||||||
|
// 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:fetchDataIndexPaths];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// 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.
|
// scroll or major main thread stall could cause entirely disjoint sets. In either case we must visit all.
|
||||||
NSSet *allCurrentIndexPaths = [allIndexPaths copy];
|
// 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];
|
[allIndexPaths unionSet:_allPreviousIndexPaths];
|
||||||
_allPreviousIndexPaths = allCurrentIndexPaths;
|
_allPreviousIndexPaths = allCurrentIndexPaths;
|
||||||
|
|
||||||
|
// This array is only used if logging is enabled.
|
||||||
|
NSMutableArray<NSIndexPath *> *modifiedIndexPaths = (RangeControllerLoggingEnabled ? [NSMutableArray array] : nil);
|
||||||
|
|
||||||
for (NSIndexPath *indexPath in allIndexPaths) {
|
for (NSIndexPath *indexPath in allIndexPaths) {
|
||||||
// Before a node / indexPath is exposed to ASRangeController, ASDataController should have already measured it.
|
// 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.
|
// For consistency, make sure each node knows that it should measure itself if something changes.
|
||||||
|
|||||||
@ -331,4 +331,36 @@
|
|||||||
return _node;
|
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
|
@end
|
||||||
|
|||||||
@ -81,6 +81,46 @@
|
|||||||
return YES;
|
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
|
- (BOOL)isFirstResponder
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertMainThread();
|
ASDisplayNodeAssertMainThread();
|
||||||
@ -298,7 +338,7 @@
|
|||||||
_bridge_prologue;
|
_bridge_prologue;
|
||||||
_setToViewOnly(userInteractionEnabled, enabled);
|
_setToViewOnly(userInteractionEnabled, enabled);
|
||||||
}
|
}
|
||||||
|
#if TARGET_OS_IOS
|
||||||
- (BOOL)isExclusiveTouch
|
- (BOOL)isExclusiveTouch
|
||||||
{
|
{
|
||||||
_bridge_prologue;
|
_bridge_prologue;
|
||||||
@ -310,7 +350,7 @@
|
|||||||
_bridge_prologue;
|
_bridge_prologue;
|
||||||
_setToViewOnly(exclusiveTouch, exclusiveTouch);
|
_setToViewOnly(exclusiveTouch, exclusiveTouch);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
- (BOOL)clipsToBounds
|
- (BOOL)clipsToBounds
|
||||||
{
|
{
|
||||||
_bridge_prologue;
|
_bridge_prologue;
|
||||||
|
|||||||
@ -716,8 +716,10 @@ static UIColor *defaultTintColor = nil;
|
|||||||
if (_flags.setUserInteractionEnabled)
|
if (_flags.setUserInteractionEnabled)
|
||||||
view.userInteractionEnabled = userInteractionEnabled;
|
view.userInteractionEnabled = userInteractionEnabled;
|
||||||
|
|
||||||
|
#if TARGET_OS_IOS
|
||||||
if (_flags.setExclusiveTouch)
|
if (_flags.setExclusiveTouch)
|
||||||
view.exclusiveTouch = exclusiveTouch;
|
view.exclusiveTouch = exclusiveTouch;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (_flags.setShadowColor)
|
if (_flags.setShadowColor)
|
||||||
layer.shadowColor = shadowColor;
|
layer.shadowColor = shadowColor;
|
||||||
@ -943,10 +945,10 @@ static UIColor *defaultTintColor = nil;
|
|||||||
|
|
||||||
pendingState.userInteractionEnabled = view.userInteractionEnabled;
|
pendingState.userInteractionEnabled = view.userInteractionEnabled;
|
||||||
(pendingState->_flags).setUserInteractionEnabled = YES;
|
(pendingState->_flags).setUserInteractionEnabled = YES;
|
||||||
|
#if TARGET_OS_IOS
|
||||||
pendingState.exclusiveTouch = view.exclusiveTouch;
|
pendingState.exclusiveTouch = view.exclusiveTouch;
|
||||||
(pendingState->_flags).setExclusiveTouch = YES;
|
(pendingState->_flags).setExclusiveTouch = YES;
|
||||||
|
#endif
|
||||||
pendingState.shadowColor = layer.shadowColor;
|
pendingState.shadowColor = layer.shadowColor;
|
||||||
(pendingState->_flags).setShadowColor = YES;
|
(pendingState->_flags).setShadowColor = YES;
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,8 @@
|
|||||||
constrainedSize:(CGSize)constrainedSize
|
constrainedSize:(CGSize)constrainedSize
|
||||||
layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory;
|
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
|
All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to
|
||||||
TextKit components may cause crashes.
|
TextKit components may cause crashes.
|
||||||
|
|||||||
@ -49,6 +49,16 @@
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (CGSize)constrainedSize
|
||||||
|
{
|
||||||
|
return _textContainer.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setConstrainedSize:(CGSize)constrainedSize
|
||||||
|
{
|
||||||
|
_textContainer.size = constrainedSize;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *,
|
- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *,
|
||||||
NSTextStorage *,
|
NSTextStorage *,
|
||||||
NSTextContainer *))block
|
NSTextContainer *))block
|
||||||
|
|||||||
@ -37,7 +37,6 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Designated Initializer
|
Designated Initializer
|
||||||
dvlkferufedgjnhjjfhldjedlunvtdtv
|
|
||||||
@discussion Sizing will occur as a result of initialization, so be careful when/where you use this.
|
@discussion Sizing will occur as a result of initialization, so be careful when/where you use this.
|
||||||
*/
|
*/
|
||||||
- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)textComponentAttributes
|
- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)textComponentAttributes
|
||||||
@ -51,7 +50,7 @@ dvlkferufedgjnhjjfhldjedlunvtdtv
|
|||||||
|
|
||||||
@property (nonatomic, assign, readonly) ASTextKitAttributes attributes;
|
@property (nonatomic, assign, readonly) ASTextKitAttributes attributes;
|
||||||
|
|
||||||
@property (nonatomic, assign, readonly) CGSize constrainedSize;
|
@property (nonatomic, assign, readwrite) CGSize constrainedSize;
|
||||||
|
|
||||||
#pragma mark - Drawing
|
#pragma mark - Drawing
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -17,6 +17,9 @@
|
|||||||
#import "ASTextKitTailTruncater.h"
|
#import "ASTextKitTailTruncater.h"
|
||||||
#import "ASTextKitTruncating.h"
|
#import "ASTextKitTruncating.h"
|
||||||
|
|
||||||
|
//#define LOG(...) NSLog(__VA_ARGS__)
|
||||||
|
#define LOG(...)
|
||||||
|
|
||||||
static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
||||||
{
|
{
|
||||||
static NSCharacterSet *truncationCharacterSet;
|
static NSCharacterSet *truncationCharacterSet;
|
||||||
@ -65,12 +68,10 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
|||||||
{
|
{
|
||||||
if (!_truncater) {
|
if (!_truncater) {
|
||||||
ASTextKitAttributes attributes = _attributes;
|
ASTextKitAttributes attributes = _attributes;
|
||||||
// We must inset the constrained size by the size of the shadower.
|
NSCharacterSet *avoidTailTruncationSet = attributes.avoidTailTruncationSet ? : _defaultAvoidTruncationCharacterSet();
|
||||||
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
|
|
||||||
_truncater = [[ASTextKitTailTruncater alloc] initWithContext:[self context]
|
_truncater = [[ASTextKitTailTruncater alloc] initWithContext:[self context]
|
||||||
truncationAttributedString:attributes.truncationAttributedString
|
truncationAttributedString:attributes.truncationAttributedString
|
||||||
avoidTailTruncationSet:attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet()
|
avoidTailTruncationSet:avoidTailTruncationSet];
|
||||||
constrainedSize:shadowConstrainedSize];
|
|
||||||
}
|
}
|
||||||
return _truncater;
|
return _truncater;
|
||||||
}
|
}
|
||||||
@ -79,6 +80,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
|||||||
{
|
{
|
||||||
if (!_context) {
|
if (!_context) {
|
||||||
ASTextKitAttributes attributes = _attributes;
|
ASTextKitAttributes attributes = _attributes;
|
||||||
|
// We must inset the constrained size by the size of the shadower.
|
||||||
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
|
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
|
||||||
_context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString
|
_context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString
|
||||||
lineBreakMode:attributes.lineBreakMode
|
lineBreakMode:attributes.lineBreakMode
|
||||||
@ -92,6 +94,30 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
|||||||
|
|
||||||
#pragma mark - Sizing
|
#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
|
- (void)_calculateSize
|
||||||
{
|
{
|
||||||
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by
|
// 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.
|
// to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect.
|
||||||
boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size});
|
boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size});
|
||||||
|
|
||||||
_calculatedSize = [_shadower outsetSizeWithInsetSize:CGSizeMake(boundingRect.size.width + boundingRect.origin.x, boundingRect.size.height + boundingRect.origin.y)];
|
_calculatedSize = [_shadower outsetSizeWithInsetSize:boundingRect.size];
|
||||||
}
|
|
||||||
|
|
||||||
- (CGSize)size
|
|
||||||
{
|
|
||||||
if (!_sizeIsCalculated) {
|
|
||||||
[self _calculateSize];
|
|
||||||
_sizeIsCalculated = YES;
|
|
||||||
}
|
|
||||||
return _calculatedSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Drawing
|
#pragma mark - Drawing
|
||||||
@ -136,8 +153,12 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
|||||||
[[self shadower] setShadowInContext:context];
|
[[self shadower] setShadowInContext:context];
|
||||||
UIGraphicsPushContext(context);
|
UIGraphicsPushContext(context);
|
||||||
|
|
||||||
|
LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds));
|
||||||
|
|
||||||
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||||
|
LOG(@"usedRect: %@", NSStringFromCGRect([layoutManager usedRectForTextContainer:textContainer]));
|
||||||
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
||||||
|
LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]));
|
||||||
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
|
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
|
||||||
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
|
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
|
||||||
}];
|
}];
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
__weak ASTextKitContext *_context;
|
__weak ASTextKitContext *_context;
|
||||||
NSAttributedString *_truncationAttributedString;
|
NSAttributedString *_truncationAttributedString;
|
||||||
NSCharacterSet *_avoidTailTruncationSet;
|
NSCharacterSet *_avoidTailTruncationSet;
|
||||||
CGSize _constrainedSize;
|
|
||||||
}
|
}
|
||||||
@synthesize visibleRanges = _visibleRanges;
|
@synthesize visibleRanges = _visibleRanges;
|
||||||
@synthesize truncationStringRect = _truncationStringRect;
|
@synthesize truncationStringRect = _truncationStringRect;
|
||||||
@ -26,13 +25,11 @@
|
|||||||
- (instancetype)initWithContext:(ASTextKitContext *)context
|
- (instancetype)initWithContext:(ASTextKitContext *)context
|
||||||
truncationAttributedString:(NSAttributedString *)truncationAttributedString
|
truncationAttributedString:(NSAttributedString *)truncationAttributedString
|
||||||
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
|
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
|
||||||
constrainedSize:(CGSize)constrainedSize
|
|
||||||
{
|
{
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_context = context;
|
_context = context;
|
||||||
_truncationAttributedString = truncationAttributedString;
|
_truncationAttributedString = truncationAttributedString;
|
||||||
_avoidTailTruncationSet = avoidTailTruncationSet;
|
_avoidTailTruncationSet = avoidTailTruncationSet;
|
||||||
_constrainedSize = constrainedSize;
|
|
||||||
|
|
||||||
[self _truncate];
|
[self _truncate];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,6 @@
|
|||||||
*/
|
*/
|
||||||
- (instancetype)initWithContext:(ASTextKitContext *)context
|
- (instancetype)initWithContext:(ASTextKitContext *)context
|
||||||
truncationAttributedString:(NSAttributedString *)truncationAttributedString
|
truncationAttributedString:(NSAttributedString *)truncationAttributedString
|
||||||
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
|
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet;
|
||||||
constrainedSize:(CGSize)constrainedSize;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -50,8 +50,7 @@
|
|||||||
}];
|
}];
|
||||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||||
truncationAttributedString:nil
|
truncationAttributedString:nil
|
||||||
avoidTailTruncationSet:nil
|
avoidTailTruncationSet:nil];
|
||||||
constrainedSize:constrainedSize];
|
|
||||||
XCTAssert(NSEqualRanges(textKitVisibleRange, tailTruncater.visibleRanges[0]));
|
XCTAssert(NSEqualRanges(textKitVisibleRange, tailTruncater.visibleRanges[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,8 +66,7 @@
|
|||||||
layoutManagerFactory:nil];
|
layoutManagerFactory:nil];
|
||||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@""]
|
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@""]];
|
||||||
constrainedSize:constrainedSize];
|
|
||||||
__block NSString *drawnString;
|
__block NSString *drawnString;
|
||||||
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||||
drawnString = textStorage.string;
|
drawnString = textStorage.string;
|
||||||
@ -90,8 +88,7 @@
|
|||||||
layoutManagerFactory:nil];
|
layoutManagerFactory:nil];
|
||||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
|
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]];
|
||||||
constrainedSize:constrainedSize];
|
|
||||||
(void)tailTruncater;
|
(void)tailTruncater;
|
||||||
__block NSString *drawnString;
|
__block NSString *drawnString;
|
||||||
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||||
@ -114,8 +111,7 @@
|
|||||||
layoutManagerFactory:nil];
|
layoutManagerFactory:nil];
|
||||||
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
|
||||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
|
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]];
|
||||||
constrainedSize:constrainedSize];
|
|
||||||
// So Xcode doesn't yell at me for an unused var...
|
// So Xcode doesn't yell at me for an unused var...
|
||||||
(void)tailTruncater;
|
(void)tailTruncater;
|
||||||
__block NSString *drawnString;
|
__block NSString *drawnString;
|
||||||
@ -139,8 +135,7 @@
|
|||||||
layoutManagerFactory:nil];
|
layoutManagerFactory:nil];
|
||||||
XCTAssertNoThrow([[ASTextKitTailTruncater alloc] initWithContext:context
|
XCTAssertNoThrow([[ASTextKitTailTruncater alloc] initWithContext:context
|
||||||
truncationAttributedString:[self _simpleTruncationAttributedString]
|
truncationAttributedString:[self _simpleTruncationAttributedString]
|
||||||
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
|
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]]);
|
||||||
constrainedSize:constrainedSize]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
3
examples/CatDealsCollectionView/Podfile
Normal file
3
examples/CatDealsCollectionView/Podfile
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
source 'https://github.com/CocoaPods/Specs.git'
|
||||||
|
platform :ios, '8.0'
|
||||||
|
pod 'AsyncDisplayKit', :path => '../..'
|
||||||
405
examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj
Normal file
405
examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj
Normal 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 */;
|
||||||
|
}
|
||||||
7
examples/CatDealsCollectionView/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
examples/CatDealsCollectionView/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:Sample.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -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>
|
||||||
10
examples/CatDealsCollectionView/Sample.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
examples/CatDealsCollectionView/Sample.xcworkspace/contents.xcworkspacedata
generated
Normal 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>
|
||||||
20
examples/CatDealsCollectionView/Sample/AppDelegate.h
Normal file
20
examples/CatDealsCollectionView/Sample/AppDelegate.h
Normal 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
|
||||||
51
examples/CatDealsCollectionView/Sample/AppDelegate.m
Normal file
51
examples/CatDealsCollectionView/Sample/AppDelegate.m
Normal 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
|
||||||
21
examples/CatDealsCollectionView/Sample/BlurbNode.h
Normal file
21
examples/CatDealsCollectionView/Sample/BlurbNode.h
Normal 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
|
||||||
112
examples/CatDealsCollectionView/Sample/BlurbNode.m
Normal file
112
examples/CatDealsCollectionView/Sample/BlurbNode.m
Normal 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
|
||||||
@ -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 |
21
examples/CatDealsCollectionView/Sample/Images.xcassets/cat_face.imageset/Contents.json
vendored
Normal file
21
examples/CatDealsCollectionView/Sample/Images.xcassets/cat_face.imageset/Contents.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
examples/CatDealsCollectionView/Sample/Images.xcassets/cat_face.imageset/cat_face.png
vendored
Normal file
BIN
examples/CatDealsCollectionView/Sample/Images.xcassets/cat_face.imageset/cat_face.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
59
examples/CatDealsCollectionView/Sample/Info.plist
Normal file
59
examples/CatDealsCollectionView/Sample/Info.plist
Normal 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>
|
||||||
21
examples/CatDealsCollectionView/Sample/ItemNode.h
Normal file
21
examples/CatDealsCollectionView/Sample/ItemNode.h
Normal 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
|
||||||
361
examples/CatDealsCollectionView/Sample/ItemNode.m
Normal file
361
examples/CatDealsCollectionView/Sample/ItemNode.m
Normal 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
|
||||||
23
examples/CatDealsCollectionView/Sample/ItemStyles.h
Normal file
23
examples/CatDealsCollectionView/Sample/ItemStyles.h
Normal 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
|
||||||
93
examples/CatDealsCollectionView/Sample/ItemStyles.m
Normal file
93
examples/CatDealsCollectionView/Sample/ItemStyles.m
Normal 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
|
||||||
27
examples/CatDealsCollectionView/Sample/ItemViewModel.h
Normal file
27
examples/CatDealsCollectionView/Sample/ItemViewModel.h
Normal 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
|
||||||
99
examples/CatDealsCollectionView/Sample/ItemViewModel.m
Normal file
99
examples/CatDealsCollectionView/Sample/ItemViewModel.m
Normal 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
|
||||||
@ -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>
|
||||||
15
examples/CatDealsCollectionView/Sample/LoadingNode.h
Normal file
15
examples/CatDealsCollectionView/Sample/LoadingNode.h
Normal 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
|
||||||
68
examples/CatDealsCollectionView/Sample/LoadingNode.m
Normal file
68
examples/CatDealsCollectionView/Sample/LoadingNode.m
Normal 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
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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
|
||||||
16
examples/CatDealsCollectionView/Sample/ViewController.h
Normal file
16
examples/CatDealsCollectionView/Sample/ViewController.h
Normal 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
|
||||||
244
examples/CatDealsCollectionView/Sample/ViewController.m
Normal file
244
examples/CatDealsCollectionView/Sample/ViewController.m
Normal 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
|
||||||
19
examples/CatDealsCollectionView/Sample/main.m
Normal file
19
examples/CatDealsCollectionView/Sample/main.m
Normal 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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
10
examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata
generated
Normal 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>
|
||||||
@ -128,7 +128,6 @@
|
|||||||
05E2127E19D4DB510098F589 /* Frameworks */,
|
05E2127E19D4DB510098F589 /* Frameworks */,
|
||||||
05E2127F19D4DB510098F589 /* Resources */,
|
05E2127F19D4DB510098F589 /* Resources */,
|
||||||
F012A6F39E0149F18F564F50 /* Copy Pods Resources */,
|
F012A6F39E0149F18F564F50 /* Copy Pods Resources */,
|
||||||
A5C135CBCFD74D965DE0D799 /* Embed Pods Frameworks */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -185,21 +184,6 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase 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 */ = {
|
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user