Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Experimental Text Node Implementation #259

Merged
merged 6 commits into from
May 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions AsyncDisplayKit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
- [ASDisplayNode] Pass drawParameter in rendering context callbacks [Michael Schneider](https://github.com/maicki)[#248](https://github.com/TextureGroup/Texture/pull/248)
- [ASTextNode] Move to class method of drawRect:withParameters:isCancelled:isRasterizing: for drawing [Michael Schneider] (https://github.com/maicki)[#232](https://github.com/TextureGroup/Texture/pull/232)
- [ASDisplayNode] Remove instance:-drawRect:withParameters:isCancelled:isRasterizing: (https://github.com/maicki)[#232](https://github.com/TextureGroup/Texture/pull/232)
- [ASTextNode] Add an experimental new implementation. See `+[ASTextNode setExperimentOptions:]`. [Adlai Holler](https://github.com/Adlai-Holler)[#259](https://github.com/TextureGroup/Texture/pull/259)
- [ASVideoNode] Added error reporing to ASVideoNode and it's delegate [#260](https://github.com/TextureGroup/Texture/pull/260)
- [ASCollectionNode] Fixed conversion of item index paths between node & view. [Adlai Holler](https://github.com/Adlai-Holler) [#262](https://github.com/TextureGroup/Texture/pull/262)
25 changes: 25 additions & 0 deletions Source/ASTextNode+Beta.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@

NS_ASSUME_NONNULL_BEGIN

typedef NS_OPTIONS(NSUInteger, ASTextNodeExperimentOptions) {
// All subclass instances use the experimental implementation.
ASTextNodeExperimentSubclasses = 1 << 0,
// Random instances of ASTextNode (50% chance) (not subclasses) use experimental impl.
// Useful for profiling with apps that have no custom text node subclasses.
ASTextNodeExperimentRandomInstances = 1 << 1,
// All instances of ASTextNode itself use experimental implementation. Supersedes `.randomInstances`.
ASTextNodeExperimentAllInstances = 1 << 2,
// Add highlighting etc. for debugging.
ASTextNodeExperimentDebugging = 1 << 3
};

@interface ASTextNode ()

/**
Expand All @@ -38,6 +50,19 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, assign) UIEdgeInsets textContainerInset;

/**
* Opt in to an experimental implementation of text node. The implementation may improve performance and correctness,
* but may not support all features and has not been thoroughly tested in production.
*
* @precondition You may not call this after allocating any text nodes. You may only call this once.
*/
+ (void)setExperimentOptions:(ASTextNodeExperimentOptions)options;

/**
* Returns YES if this node is using the experimental implementation. NO otherwise. Will not change.
*/
@property (atomic, readonly) BOOL usingExperiment;

@end

NS_ASSUME_NONNULL_END
73 changes: 65 additions & 8 deletions Source/ASTextNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//

#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASTextNode2.h>
#import <AsyncDisplayKit/ASTextNode+Beta.h>

#include <mutex>
Expand Down Expand Up @@ -263,14 +264,6 @@ - (NSString *)_plainStringForDescription

#pragma mark - ASDisplayNode

- (void)clearContents
{
// We discard the backing store and renderer to prevent the very large
// memory overhead of maintaining these for all text nodes. They can be
// regenerated when layout is necessary.
[super clearContents]; // ASDisplayNode will set layer.contents = nil
}

- (void)didLoad
{
[super didLoad];
Expand Down Expand Up @@ -1386,6 +1379,70 @@ + (void)_registerAttributedText:(NSAttributedString *)str
}
#endif

static ASDN::Mutex _experimentLock;
static ASTextNodeExperimentOptions _experimentOptions;
static BOOL _hasAllocatedNode;

+ (void)setExperimentOptions:(ASTextNodeExperimentOptions)options
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ASDN::MutexLocker lock(_experimentLock);

// They must call this before allocating any text nodes.
ASDisplayNodeAssertFalse(_hasAllocatedNode);

_experimentOptions = options;

// Set superclass of all subclasses to ASTextNode2
if (options & ASTextNodeExperimentSubclasses) {
unsigned int classCount;
Class originalClass = [ASTextNode class];
Class newClass = [ASTextNode2 class];
Class *classes = objc_copyClassList(&classCount);
for (int i = 0; i < classCount; i++) {
Class c = classes[i];
if (class_getSuperclass(c) == originalClass) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
class_setSuperclass(c, newClass);
#pragma clang diagnostic pop
}
}
free(classes);
}

if (options & ASTextNodeExperimentDebugging) {
[ASTextNode2 enableDebugging];
}
});
}

+ (id)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ASDN::MutexLocker lock(_experimentLock);
_hasAllocatedNode = YES;
});

// All instances || (random instances && rand() != 0)
BOOL useExperiment = (_experimentOptions & ASTextNodeExperimentAllInstances)
|| ((_experimentOptions & ASTextNodeExperimentRandomInstances)
&& (arc4random_uniform(2) != 0));

if (useExperiment) {
return (ASTextNode *)[ASTextNode2 allocWithZone:zone];
} else {
return [super allocWithZone:zone];
}
}

- (BOOL)usingExperiment
{
return NO;
}

@end

@implementation ASTextNode (Deprecated)
Expand Down
228 changes: 228 additions & 0 deletions Source/Private/ASTextNode2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
//
// ASTextNode2.h
// Texture
//
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//

#import <AsyncDisplayKit/ASControlNode.h>

// Import this to get ASTextNodeHighlightStyle
#import <AsyncDisplayKit/ASTextNode.h>

NS_ASSUME_NONNULL_BEGIN

/**
@abstract Draws interactive rich text.
@discussion Backed by the code in TextExperiment folder, on top of CoreText.
*/
@interface ASTextNode2 : ASControlNode

/**
@abstract The styled text displayed by the node.
@discussion Defaults to nil, no text is shown.
For inline image attachments, add an attribute of key NSAttachmentAttributeName, with a value of an NSTextAttachment.
*/
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;

#pragma mark - Truncation

/**
@abstract The attributedText to use when the text must be truncated.
@discussion Defaults to a localized ellipsis character.
*/
@property (nullable, nonatomic, copy) NSAttributedString *truncationAttributedText;

/**
@summary The second attributed string appended for truncation.
@discussion This string will be highlighted on touches.
@default nil
*/
@property (nullable, nonatomic, copy) NSAttributedString *additionalTruncationMessage;

/**
@abstract Determines how the text is truncated to fit within the receiver's maximum size.
@discussion Defaults to NSLineBreakByWordWrapping.
@note Setting a truncationMode in attributedString will override the truncation mode set here.
*/
@property (nonatomic, assign) NSLineBreakMode truncationMode;

/**
@abstract If the text node is truncated. Text must have been sized first.
*/
@property (nonatomic, readonly, assign, getter=isTruncated) BOOL truncated;

/**
@abstract The maximum number of lines to render of the text before truncation.
@default 0 (No limit)
*/
@property (nonatomic, assign) NSUInteger maximumNumberOfLines;

/**
@abstract The number of lines in the text. Text must have been sized first.
*/
@property (nonatomic, readonly, assign) NSUInteger lineCount;

/**
* An array of path objects representing the regions where text should not be displayed.
*
* @discussion The default value of this property is an empty array. You can
* assign an array of UIBezierPath objects to exclude text from one or more regions in
* the text node's bounds. You can use this property to have text wrap around images,
* shapes or other text like a fancy magazine.
*/
@property (nullable, nonatomic, strong) NSArray<UIBezierPath *> *exclusionPaths;

#pragma mark - Placeholders

/**
* @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES.
*
* @discussion Defaults to NO. When YES, it draws rectangles for each line of text,
* following the true shape of the text's wrapping. This visually mirrors the overall
* shape and weight of paragraphs, making the appearance of the finished text less jarring.
*/
@property (nonatomic, assign) BOOL placeholderEnabled;

/**
@abstract The placeholder color.
*/
@property (nullable, nonatomic, strong) UIColor *placeholderColor;

/**
@abstract Inset each line of the placeholder.
*/
@property (nonatomic, assign) UIEdgeInsets placeholderInsets;

#pragma mark - Shadow

/**
@abstract When you set these ASDisplayNode properties, they are composited into the bitmap instead of being applied by CA.

@property (nonatomic, assign) CGColorRef shadowColor;
@property (nonatomic, assign) CGFloat shadowOpacity;
@property (nonatomic, assign) CGSize shadowOffset;
@property (nonatomic, assign) CGFloat shadowRadius;
*/

/**
@abstract The number of pixels used for shadow padding on each side of the receiver.
@discussion Each inset will be less than or equal to zero, so that applying
UIEdgeInsetsRect(boundingRectForText, shadowPadding)
will return a CGRect large enough to fit both the text and the appropriate shadow padding.
*/
@property (nonatomic, readonly, assign) UIEdgeInsets shadowPadding;

#pragma mark - Positioning

/**
@abstract Returns an array of rects bounding the characters in a given text range.
@param textRange A range of text. Must be valid for the receiver's string.
@discussion Use this method to detect all the different rectangles a given range of text occupies.
The rects returned are not guaranteed to be contiguous (for example, if the given text range spans
a line break, the rects returned will be on opposite sides and different lines). The rects returned
are in the coordinate system of the receiver.
*/
- (NSArray<NSValue *> *)rectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;

/**
@abstract Returns an array of rects used for highlighting the characters in a given text range.
@param textRange A range of text. Must be valid for the receiver's string.
@discussion Use this method to detect all the different rectangles the highlights of a given range of text occupies.
The rects returned are not guaranteed to be contiguous (for example, if the given text range spans
a line break, the rects returned will be on opposite sides and different lines). The rects returned
are in the coordinate system of the receiver. This method is useful for visual coordination with a
highlighted range of text.
*/
- (NSArray<NSValue *> *)highlightRectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;

/**
@abstract Returns a bounding rect for the given text range.
@param textRange A range of text. Must be valid for the receiver's string.
@discussion The height of the frame returned is that of the receiver's line-height; adjustment for
cap-height and descenders is not performed. This method raises an exception if textRange is not
a valid substring range of the receiver's string.
*/
- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT;

/**
@abstract Returns the trailing rectangle of space in the receiver, after the final character.
@discussion Use this method to detect which portion of the receiver is not occupied by characters.
The rect returned is in the coordinate system of the receiver.
*/
- (CGRect)trailingRect AS_WARN_UNUSED_RESULT;


#pragma mark - Actions

/**
@abstract The set of attribute names to consider links. Defaults to NSLinkAttributeName.
*/
@property (nonatomic, copy) NSArray<NSString *> *linkAttributeNames;

/**
@abstract Indicates whether the receiver has an entity at a given point.
@param point The point, in the receiver's coordinate system.
@param attributeNameOut The name of the attribute at the point. Can be NULL.
@param rangeOut The ultimate range of the found text. Can be NULL.
@result YES if an entity exists at `point`; NO otherwise.
*/
- (nullable id)linkAttributeValueAtPoint:(CGPoint)point attributeName:(out NSString * _Nullable * _Nullable)attributeNameOut range:(out NSRange * _Nullable)rangeOut AS_WARN_UNUSED_RESULT;

/**
@abstract The style to use when highlighting text.
*/
@property (nonatomic, assign) ASTextNodeHighlightStyle highlightStyle;

/**
@abstract The range of text highlighted by the receiver. Changes to this property are not animated by default.
*/
@property (nonatomic, assign) NSRange highlightRange;

/**
@abstract Set the range of text to highlight, with optional animation.

@param highlightRange The range of text to highlight.

@param animated Whether the text should be highlighted with an animation.
*/
- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated;

/**
@abstract Responds to actions from links in the text node.
@discussion The delegate must be set before the node is loaded, and implement
textNode:longPressedLinkAttribute:value:atPoint:textRange: in order for
the long press gesture recognizer to be installed.
*/
@property (nonatomic, weak) id<ASTextNodeDelegate> delegate;

/**
@abstract If YES and a long press is recognized, touches are cancelled. Default is NO
*/
@property (nonatomic, assign) BOOL longPressCancelsTouches;

/**
@abstract if YES will not intercept touches for non-link areas of the text. Default is NO.
*/
@property (nonatomic, assign) BOOL passthroughNonlinkTouches;

+ (void)enableDebugging;

@end

@interface ASTextNode2 (Unavailable)

- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable;

- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable;

@end

NS_ASSUME_NONNULL_END


Loading