From eb4c21c54540d2c1c0b63a6b0665a77fea810e6c Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 26 Jul 2018 09:44:10 -0700 Subject: [PATCH 01/15] Optimize drawing code + add examples how to round corners (#996) * Use CoreGraphics for drawing and cropping of node content * Smaller fixes --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 ++-- CHANGELOG.md | 2 +- Source/ASDisplayNode.mm | 55 ++++++------ Source/ASImageNode.mm | 38 +++++++-- Source/Base/ASBaseDefines.h | 2 +- Source/Details/CoreGraphics+ASConvenience.h | 9 +- Source/Details/CoreGraphics+ASConvenience.m | 19 ----- Source/Details/CoreGraphics+ASConvenience.mm | 64 ++++++++++++++ Source/Layout/ASDimension.h | 10 +-- Source/Layout/ASDimension.mm | 2 +- Source/Private/ASDisplayNode+AsyncDisplay.mm | 78 +++++++++++++----- ...Convenience.m => UIImage+ASConvenience.mm} | 48 +++++++---- Source/tvOS/ASControlNode+tvOS.m | 8 +- Source/tvOS/ASImageNode+tvOS.m | 4 +- .../testRoundedCornerBlock@2x.png | Bin 0 -> 11609 bytes examples/ASDKgram/Sample/PhotoCellNode.m | 4 +- 16 files changed, 250 insertions(+), 109 deletions(-) delete mode 100644 Source/Details/CoreGraphics+ASConvenience.m create mode 100644 Source/Details/CoreGraphics+ASConvenience.mm rename Source/{UIImage+ASConvenience.m => UIImage+ASConvenience.mm} (86%) create mode 100644 Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3c6668706..d3e26159d 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -109,7 +109,7 @@ 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; }; 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */; }; 509E68651B3AEDC5009B9150 /* CoreGraphics+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */; }; + 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */; }; 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.mm in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.mm */; }; 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; 680346941CE4052A0009FEB4 /* ASNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -176,7 +176,7 @@ 7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */; }; 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; + 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */; }; 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */; }; 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A7D9581D44542100BF333E /* ASWeakMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -621,7 +621,7 @@ 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewLayoutController.h; sourceTree = ""; }; 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewLayoutController.m; sourceTree = ""; }; 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CoreGraphics+ASConvenience.h"; sourceTree = ""; }; - 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CoreGraphics+ASConvenience.m"; sourceTree = ""; }; + 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "CoreGraphics+ASConvenience.mm"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTruncationTests.mm; sourceTree = ""; }; @@ -734,7 +734,7 @@ 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRelativeLayoutSpec.h; sourceTree = ""; }; 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ASConvenience.h"; sourceTree = ""; }; - 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ASConvenience.m"; sourceTree = ""; }; + 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIImage+ASConvenience.mm"; sourceTree = ""; }; 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeSnapshotTests.m; sourceTree = ""; }; 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = ""; }; 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = ""; }; @@ -1234,7 +1234,7 @@ 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */, 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */, - 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */, + 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */, CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */, CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.m */, ); @@ -1422,7 +1422,7 @@ CC3B20871C3F7A5400798563 /* ASWeakSet.h */, CC3B20881C3F7A5400798563 /* ASWeakSet.m */, 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */, - 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */, + 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, DBC452DA1C5BF64600B16017 /* NSArray+Diffing.mm */, CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */, @@ -2405,7 +2405,7 @@ CCA282C51E9EAE630037E8B7 /* ASLayerBackingTipProvider.m in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, - 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, + 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.mm in Sources */, CCAA0B80206ADBF30057B336 /* ASRecursiveUnfairLock.m in Sources */, CCBDDD0620C62A2D00CBA922 /* ASMainThreadDeallocation.mm in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, @@ -2504,7 +2504,7 @@ 6959433F1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */, 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.m in Sources */, CC034A141E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m in Sources */, - 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */, + 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.mm in Sources */, 254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */, 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f87a289..5be5334b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ - Reduced binary size by disabling exception support (which we don't use.) [Adlai Holler](https://github.com/Adlai-Holler) - Create and set delegate for clip corner layers within ASDisplayNode [Michael Schneider](https://github.com/maicki) [#1029](https://github.com/TextureGroup/Texture/pull/1029) - Improve locking situation in ASVideoPlayerNode [Michael Schneider](https://github.com/maicki) [#1042](https://github.com/TextureGroup/Texture/pull/1042) - +- Optimize drawing code + add examples how to round corners. [Michael Schneider](https://github.com/maicki) ## 2.7 - Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 347d41c42..0fa7a9eb6 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -53,6 +53,7 @@ #import #import #import +#import // Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) #if TIME_DISPLAYNODE_OPS @@ -1508,7 +1509,7 @@ - (void)_pendingNodeDidDisplay:(ASDisplayNode *)node __instanceLock__.lock(); if (_placeholderLayer.superlayer && !placeholderShouldPersist) { void (^cleanupBlock)() = ^{ - [_placeholderLayer removeFromSuperlayer]; + [self->_placeholderLayer removeFromSuperlayer]; }; if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { @@ -1666,24 +1667,29 @@ - (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor CGSize size = CGSizeMake(radius + 1, radius + 1); ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay); - CGContextRef ctx = UIGraphicsGetCurrentContext(); + CGContextRef context = UIGraphicsGetCurrentContext(); if (isRight == YES) { - CGContextTranslateCTM(ctx, -radius + 1, 0); + CGContextTranslateCTM(context, -radius + 1, 0); } if (isTop == YES) { - CGContextTranslateCTM(ctx, 0, -radius + 1); + CGContextTranslateCTM(context, 0, -radius + 1); } - UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius]; - [roundedRect setUsesEvenOddFillRule:YES]; - [roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]]; - [backgroundColor setFill]; - [roundedRect fill]; - + + CGMutablePathRef roundedPath = CGPathCreateMutable(); + CGPathRef addedPath = ASCGRoundedPathCreate(CGRectMake(0, 0, radius * 2, radius * 2), radius); + CGPathAddPath(roundedPath, NULL, addedPath); + CGPathAddRect(roundedPath, NULL, CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)); + CGContextAddPath(context, roundedPath); + CGContextSetFillColorWithColor(context, backgroundColor.CGColor); + CGContextEOFillPath(context); + // No lock needed, as _clipCornerLayers is only modified on the main thread. - CALayer *clipCornerLayer = _clipCornerLayers[idx]; - clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); - clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height); - clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); + _clipCornerLayers[idx].contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); + _clipCornerLayers[idx].bounds = CGRectMake(0.0, 0.0, size.width, size.height); + _clipCornerLayers[idx].anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); + + CGPathRelease(addedPath); + CGPathRelease(roundedPath); } [self _layoutClipCornersIfNeeded]; }); @@ -1868,7 +1874,10 @@ - (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer [self displayDidFinish]; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" - (void)displayWillStart {} +#pragma clang diagnostic pop - (void)displayWillStartAsynchronously:(BOOL)asynchronously { ASDisplayNodeAssertMainThread(); @@ -2979,11 +2988,11 @@ - (void)didExitHierarchy if (ASInterfaceStateIncludesVisible(self.pendingInterfaceState)) { void(^exitVisibleInterfaceState)(void) = ^{ // This block intentionally retains self. - __instanceLock__.lock(); - unsigned isStillInHierarchy = _flags.isInHierarchy; - BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState); - ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible); - __instanceLock__.unlock(); + self->__instanceLock__.lock(); + unsigned isStillInHierarchy = self->_flags.isInHierarchy; + BOOL isVisible = ASInterfaceStateIncludesVisible(self->_pendingInterfaceState); + ASInterfaceState newState = (self->_pendingInterfaceState & ~ASInterfaceStateVisible); + self->__instanceLock__.unlock(); if (!isStillInHierarchy && isVisible) { #if ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR if (![self supportsRangeManagedInterfaceState]) { @@ -3142,8 +3151,8 @@ - (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState [self setDisplaySuspended:YES]; //schedule clear contents on next runloop dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(__instanceLock__); - if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { + ASDN::MutexLocker l(self->__instanceLock__); + if (ASInterfaceStateIncludesDisplay(self->_interfaceState) == NO) { [self clearContents]; } }); @@ -3160,8 +3169,8 @@ - (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState [[self asyncLayer] cancelAsyncDisplay]; //schedule clear contents on next runloop dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(__instanceLock__); - if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { + ASDN::MutexLocker l(self->__instanceLock__); + if (ASInterfaceStateIncludesDisplay(self->_interfaceState) == NO) { [self clearContents]; } }); diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index d060d8993..374099136 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -737,19 +737,43 @@ asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat { return ^(UIImage *originalImage) { ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); - UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}]; + + CGContextRef context = UIGraphicsGetCurrentContext(); + CGRect rect = (CGRect){CGPointZero, originalImage.size}; + CGMutablePathRef path = CGPathCreateMutable(); + + CGPathAddEllipseInRect(path, NULL, rect); + CGContextAddPath(context, path); + // Make the image round - [roundOutline addClip]; + CGContextClip(context); + + // Although drawAtPoint:blendMode: would consider the CTM already, we are using CGContext* functions for drawing + // the image instead calling drawAtPoint:blendMode. This will save use 50% of retain calls for the image + CGContextSetBlendMode(context, kCGBlendModeCopy); + CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect)); + CGContextScaleCTM(context, originalImage.scale, -originalImage.scale); + CGContextSetAlpha(context, 1.0); + CGContextDrawImage(context, rect, originalImage.CGImage); - // Draw the original image - [originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; + CGPathRelease(path); // Draw a border on top. if (borderWidth > 0.0) { - [borderColor setStroke]; - [roundOutline setLineWidth:borderWidth]; - [roundOutline stroke]; + // Begin a new path for the border + CGContextBeginPath(context); + + CGFloat strokeThickness = borderWidth; + CGFloat strokeInset = floor((strokeThickness + 1.0f) / 2.0f) - 1.0f; + CGPathRef path = CGPathCreateWithEllipseInRect(CGRectInset(rect, strokeInset, strokeInset), NULL); + CGContextAddPath(context, path); + + CGContextSetStrokeColorWithColor(context, borderColor.CGColor); + CGContextSetLineWidth(context, borderWidth); + CGContextStrokePath(context); + + CGPathRelease(path); } return ASGraphicsGetImageAndEndCurrentContext(); diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 2fabc0dde..b3543ced6 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -130,7 +130,7 @@ #endif #endif -#define ASOVERLOADABLE __attribute__((overloadable)) +#define AS_OVERLOADABLE __attribute__((overloadable)) #if __has_attribute(noescape) diff --git a/Source/Details/CoreGraphics+ASConvenience.h b/Source/Details/CoreGraphics+ASConvenience.h index 9eeef3192..6229d4ad2 100644 --- a/Source/Details/CoreGraphics+ASConvenience.h +++ b/Source/Details/CoreGraphics+ASConvenience.h @@ -17,8 +17,8 @@ #import -#import #import +#import #import @@ -56,4 +56,11 @@ ASDISPLAYNODE_INLINE BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CG return fabs(size1.width - size2.width) < delta && fabs(size1.height - size2.height) < delta; }; +AS_OVERLOADABLE AS_WARN_UNUSED_RESULT AS_EXTERN CGPathRef ASCGRoundedPathCreate(CGRect rect, UIRectCorner corners, CGSize cornerRadii); + +AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT CGPathRef ASCGRoundedPathCreate(CGRect rect, CGFloat cornerRadius) { + return ASCGRoundedPathCreate(rect, UIRectCornerAllCorners, CGSizeMake(cornerRadius, cornerRadius)); +} + + NS_ASSUME_NONNULL_END diff --git a/Source/Details/CoreGraphics+ASConvenience.m b/Source/Details/CoreGraphics+ASConvenience.m deleted file mode 100644 index 92169ffe4..000000000 --- a/Source/Details/CoreGraphics+ASConvenience.m +++ /dev/null @@ -1,19 +0,0 @@ -// -// CoreGraphics+ASConvenience.m -// Texture -// -// 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 /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. 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 - diff --git a/Source/Details/CoreGraphics+ASConvenience.mm b/Source/Details/CoreGraphics+ASConvenience.mm new file mode 100644 index 000000000..2a3b6b84e --- /dev/null +++ b/Source/Details/CoreGraphics+ASConvenience.mm @@ -0,0 +1,64 @@ +// +// CoreGraphics+ASConvenience.m +// Texture +// +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Pinterest, Inc. 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 + +AS_OVERLOADABLE CGPathRef ASCGRoundedPathCreate(CGRect rect, UIRectCorner corners, CGSize cornerRadii) { + CGMutablePathRef path = CGPathCreateMutable(); + + const CGPoint topLeft = rect.origin; + const CGPoint topRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect)); + const CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)); + const CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect)); + + if (corners & UIRectCornerTopLeft) { + CGPathMoveToPoint(path, NULL, topLeft.x+cornerRadii.width, topLeft.y); + } else { + CGPathMoveToPoint(path, NULL, topLeft.x, topLeft.y); + } + + if (corners & UIRectCornerTopRight) { + CGPathAddLineToPoint(path, NULL, topRight.x-cornerRadii.width, topRight.y); + CGPathAddCurveToPoint(path, NULL, topRight.x, topRight.y, topRight.x, topRight.y+cornerRadii.height, topRight.x, topRight.y+cornerRadii.height); + } else { + CGPathAddLineToPoint(path, NULL, topRight.x, topRight.y); + } + + if (corners & UIRectCornerBottomRight) { + CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y-cornerRadii.height); + CGPathAddCurveToPoint(path, NULL, bottomRight.x, bottomRight.y, bottomRight.x-cornerRadii.width, bottomRight.y, bottomRight.x-cornerRadii.width, bottomRight.y); + } else { + CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y); + } + + if (corners & UIRectCornerBottomLeft) { + CGPathAddLineToPoint(path, NULL, bottomLeft.x+cornerRadii.width, bottomLeft.y); + CGPathAddCurveToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomLeft.x, bottomLeft.y-cornerRadii.height, bottomLeft.x, bottomLeft.y-cornerRadii.height); + } else { + CGPathAddLineToPoint(path, NULL, bottomLeft.x, bottomLeft.y); + } + + if (corners & UIRectCornerTopLeft) { + CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y+cornerRadii.height); + CGPathAddCurveToPoint(path, NULL, topLeft.x, topLeft.y, topLeft.x+cornerRadii.width, topLeft.y, topLeft.x+cornerRadii.width, topLeft.y); + } else { + CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y); + } + + CGPathCloseSubpath(path); + return path; +} diff --git a/Source/Layout/ASDimension.h b/Source/Layout/ASDimension.h index 462406eed..ffe6138b5 100644 --- a/Source/Layout/ASDimension.h +++ b/Source/Layout/ASDimension.h @@ -94,7 +94,7 @@ AS_EXTERN ASDimension const ASDimensionAuto; /** * Returns a dimension with the specified type and value. */ -ASOVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit unit, CGFloat value) +AS_OVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit unit, CGFloat value) { if (unit == ASDimensionUnitAuto ) { ASDisplayNodeCAssert(value == 0, @"ASDimension auto value must be 0."); @@ -112,7 +112,7 @@ ASOVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit /** * Returns a dimension with the specified points value. */ -ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensionMake(CGFloat points) +AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensionMake(CGFloat points) { return ASDimensionMake(ASDimensionUnitPoints, points); } @@ -122,7 +122,7 @@ ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensio * Examples: ASDimensionMake(@"50%") = ASDimensionMake(ASDimensionUnitFraction, 0.5) * ASDimensionMake(@"0.5pt") = ASDimensionMake(ASDimensionUnitPoints, 0.5) */ -ASOVERLOADABLE AS_WARN_UNUSED_RESULT AS_EXTERN ASDimension ASDimensionMake(NSString *dimension); +AS_OVERLOADABLE AS_WARN_UNUSED_RESULT AS_EXTERN ASDimension ASDimensionMake(NSString *dimension); /** * Returns a dimension with the specified points value. @@ -244,7 +244,7 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASSizeRangeHasSignificantArea(AS /** * Creates an ASSizeRange with provided min and max size. */ -ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize min, CGSize max) +AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize min, CGSize max) { ASDisplayNodeCAssertPositiveReal(@"Range min width", min.width); ASDisplayNodeCAssertPositiveReal(@"Range min height", min.height); @@ -263,7 +263,7 @@ ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRang /** * Creates an ASSizeRange with provided size as both min and max. */ -ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize exactSize) +AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize exactSize) { return ASSizeRangeMake(exactSize, exactSize); } diff --git a/Source/Layout/ASDimension.mm b/Source/Layout/ASDimension.mm index 4fd0e1800..a27226dda 100644 --- a/Source/Layout/ASDimension.mm +++ b/Source/Layout/ASDimension.mm @@ -25,7 +25,7 @@ ASDimension const ASDimensionAuto = {ASDimensionUnitAuto, 0}; -ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension) +AS_OVERLOADABLE ASDimension ASDimensionMake(NSString *dimension) { if (dimension.length > 0) { diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 2cf772aec..16cf9b39e 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -25,6 +25,7 @@ #import #import #import +#import @interface ASDisplayNode () <_ASDisplayLayerDelegate> @@ -111,10 +112,13 @@ - (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode CGContextTranslateCTM(context, frame.origin.x, frame.origin.y); - //support cornerRadius + // Support cornerRadius if (rasterizingFromAscendent && clipsToBounds) { if (cornerRadius) { - [[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius] addClip]; + CGPathRef cornerRadiusPath = ASCGRoundedPathCreate(bounds, cornerRadius); + CGContextAddPath(context, cornerRadiusPath); + CGContextClip(context); + CGPathRelease(cornerRadiusPath); } else { CGContextClipToRect(context, bounds); } @@ -127,13 +131,18 @@ - (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode CGContextFillRect(context, bounds); } - // If there is a display block, call it to get the image, then copy the image into the current context (which is the rasterized container's backing store). + // If there is a display block, call it to get the image, then copy the image into the current context (which + // is the rasterized container's backing store). if (displayBlock) { UIImage *image = (UIImage *)displayBlock(); if (image) { BOOL opaque = ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)); CGBlendMode blendMode = opaque ? kCGBlendModeCopy : kCGBlendModeNormal; - [image drawInRect:bounds blendMode:blendMode alpha:1]; + CGContextSetBlendMode(context, blendMode); + CGContextTranslateCTM(context, 0, CGRectGetMaxY(bounds) + CGRectGetMinY(bounds)); + CGContextScaleCTM(context, 1, -1); + CGContextSetAlpha(context, 1.0); + CGContextDrawImage(context, bounds, image.CGImage); } } }; @@ -295,7 +304,10 @@ - (void)__willDisplayNodeContentWithRenderingContext:(CGContextRef)context drawP ASDisplayNodeAssert(context == UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); // TODO: This clip path should be removed if we are rasterizing. CGRect boundingBox = CGContextGetClipBoundingBox(context); - [[UIBezierPath bezierPathWithRoundedRect:boundingBox cornerRadius:cornerRadius] addClip]; + CGPathRef cornerRadiusPath = ASCGRoundedPathCreate(boundingBox, cornerRadius); + CGContextAddPath(context, cornerRadiusPath); + CGContextClip(context); + CGPathRelease(cornerRadiusPath); } if (willDisplayNodeContentWithRenderingContext) { @@ -332,34 +344,58 @@ - (void)__didDisplayNodeContentWithRenderingContext:(CGContextRef)context image: CGFloat white = 0.0f, alpha = 0.0f; [backgroundColor getWhite:&white alpha:&alpha]; ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale); + context = UIGraphicsGetCurrentContext(); [*image drawInRect:bounds]; } else { bounds = CGContextGetClipBoundingBox(context); } ASDisplayNodeAssert(UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); + + CGContextSaveGState(context); - UIBezierPath *roundedHole = [UIBezierPath bezierPathWithRect:bounds]; - [roundedHole appendPath:[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius * contentsScale]]; - roundedHole.usesEvenOddFillRule = YES; - - UIBezierPath *roundedPath = nil; - if (borderWidth > 0.0f) { // Don't create roundedPath and stroke if borderWidth is 0.0 - CGFloat strokeThickness = borderWidth * contentsScale; - CGFloat strokeInset = ((strokeThickness + 1.0f) / 2.0f) - 1.0f; - roundedPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(bounds, strokeInset, strokeInset) - cornerRadius:_cornerRadius * contentsScale]; - roundedPath.lineWidth = strokeThickness; - [[UIColor colorWithCGColor:borderColor] setStroke]; - } + CGMutablePathRef roundedHole = CGPathCreateMutable(); + CGPathAddRect(roundedHole, NULL, bounds); + + CGPathRef additionalPath = ASCGRoundedPathCreate(bounds, cornerRadius * contentsScale); + CGPathAddPath(roundedHole, NULL, additionalPath); + + CGContextAddPath(context, roundedHole); // Punch out the corners by copying the backgroundColor over them. // This works for everything from clearColor to opaque colors. - [backgroundColor setFill]; - [roundedHole fillWithBlendMode:kCGBlendModeCopy alpha:1.0f]; + CGContextSetFillColorWithColor(context, backgroundColor.CGColor); + + CGContextSetAlpha(context, 1.0); + CGContextSetBlendMode(context, kCGBlendModeCopy); + CGContextEOFillPath(context); + + CGPathRelease(additionalPath); + CGPathRelease(roundedHole); - [roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil. + CGContextRestoreGState(context); + // Drawing borders with ASCornerRoundingTypePrecomposited set has some problems at the moment. If the borderWidth is + // set, besides we are drawing the border with the given corner radius, the CALayer also picks up the borderWidth + // value and draws the border without the cornerRadius. + if (borderWidth > 0.0f) { // Don't create roundedPath and stroke if borderWidth is 0.0 + CGContextSaveGState(context); + + CGFloat strokeThickness = borderWidth * contentsScale; + CGFloat strokeInset = ((strokeThickness + 1.0f) / 2.0f) - 1.0f; + CGPathRef roundedPath = ASCGRoundedPathCreate(CGRectInset(bounds, strokeInset, strokeInset), _cornerRadius * contentsScale); + CGContextAddPath(context, roundedPath); + + CGContextSetLineWidth(context, strokeThickness); + CGContextSetStrokeColorWithColor(context, borderColor); + + CGContextStrokePath(context); + + CGPathRelease(roundedPath); + + CGContextRestoreGState(context); + } + if (*image) { *image = ASGraphicsGetImageAndEndCurrentContext(); } diff --git a/Source/UIImage+ASConvenience.m b/Source/UIImage+ASConvenience.mm similarity index 86% rename from Source/UIImage+ASConvenience.m rename to Source/UIImage+ASConvenience.mm index c1d0751e3..84e87b0fc 100644 --- a/Source/UIImage+ASConvenience.m +++ b/Source/UIImage+ASConvenience.mm @@ -19,6 +19,7 @@ #import #import #import +#import #pragma mark - ASDKFastImageNamed @@ -114,9 +115,9 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius // UIBezierPath objects are fairly small and these are equally sized. 20 should be plenty for many different parameters. __pathCache.countLimit = 20; }); - + // Treat clear background color as no background color - if ([cornerColor isEqual:[UIColor clearColor]]) { + if (CGColorGetAlpha(cornerColor.CGColor) == 0) { cornerColor = nil; } @@ -140,33 +141,46 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius // We should probably check if the background color has any alpha component but that // might be expensive due to needing to check mulitple color spaces. ASGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); - + + CGContextRef context = UIGraphicsGetCurrentContext(); + + // Draw Corners BOOL contextIsClean = YES; if (cornerColor) { contextIsClean = NO; - [cornerColor setFill]; + + CGContextSetFillColorWithColor(context, cornerColor.CGColor); // Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overrides directly. - UIRectFillUsingBlendMode(bounds, kCGBlendModeCopy); + CGContextSetBlendMode(context, kCGBlendModeCopy); + CGContextFillRect(context, bounds); } - + + // Draw fill BOOL canUseCopy = contextIsClean || (CGColorGetAlpha(fillColor.CGColor) == 1); - [fillColor setFill]; - [path fillWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; - + CGContextSetFillColorWithColor(context, fillColor.CGColor); + CGContextSetBlendMode(context, canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal); + CGContextSetAlpha(context, 1.0); + CGContextAddPath(context, path.CGPath); + CGContextFillPath(context); + + // Add a border if (borderColor) { - [borderColor setStroke]; - // Inset border fully inside filled path (not halfway on each side of path) CGRect strokeRect = CGRectInset(bounds, borderWidth / 2.0, borderWidth / 2.0); - + // It is rarer to have a stroke path, and our cache key only handles rounded rects for the exact-stretchable // size calculated by cornerRadius, so we won't bother caching this path. Profiling validates this decision. - UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect - byRoundingCorners:roundedCorners - cornerRadii:cornerRadii]; - [strokePath setLineWidth:borderWidth]; + CGPathRef strokePath = ASCGRoundedPathCreate(strokeRect, roundedCorners, cornerRadii); + + CGContextSetStrokeColorWithColor(context, borderColor.CGColor); + CGContextSetLineWidth(context, borderWidth); + CGContextSetAlpha(context, 1.0); BOOL canUseCopy = (CGColorGetAlpha(borderColor.CGColor) == 1); - [strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; + CGContextSetBlendMode(context, (canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal)); + CGContextAddPath(context, strokePath); + CGContextStrokePath(context); + + CGPathRelease(strokePath); } UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); diff --git a/Source/tvOS/ASControlNode+tvOS.m b/Source/tvOS/ASControlNode+tvOS.m index 77e541862..c18b67eec 100644 --- a/Source/tvOS/ASControlNode+tvOS.m +++ b/Source/tvOS/ASControlNode+tvOS.m @@ -83,7 +83,9 @@ - (void)applyDefaultShadowProperties:(CALayer *)layer layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowRadius = 12.0; layer.shadowOpacity = 0.45; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL); + layer.shadowPath = shadowPath; + CGPathRelease(shadowPath); } - (void)setDefaultFocusAppearance @@ -93,7 +95,9 @@ - (void)setDefaultFocusAppearance layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowRadius = 0; layer.shadowOpacity = 0; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL); + layer.shadowPath = shadowPath; + CGPathRelease(shadowPath); self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); } @end diff --git a/Source/tvOS/ASImageNode+tvOS.m b/Source/tvOS/ASImageNode+tvOS.m index 9482fdc8c..8f8486b94 100644 --- a/Source/tvOS/ASImageNode+tvOS.m +++ b/Source/tvOS/ASImageNode+tvOS.m @@ -169,7 +169,9 @@ - (void)setFocusedState layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowRadius = 12.0; layer.shadowOpacity = 0.45; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; + CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL); + layer.shadowPath = shadowPath; + CGPathRelease(shadowPath); view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); } diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4686232a23cb980c70b44339427ddfa81412562 GIT binary patch literal 11609 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 Date: Tue, 31 Jul 2018 04:40:18 -0700 Subject: [PATCH 02/15] Revert "Optimize drawing code + add examples how to round corners (#996)" (#1055) This reverts commit eb4c21c54540d2c1c0b63a6b0665a77fea810e6c. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 16 ++-- CHANGELOG.md | 2 +- Source/ASDisplayNode.mm | 55 ++++++------ Source/ASImageNode.mm | 38 ++------- Source/Base/ASBaseDefines.h | 2 +- Source/Details/CoreGraphics+ASConvenience.h | 9 +- Source/Details/CoreGraphics+ASConvenience.m | 19 +++++ Source/Details/CoreGraphics+ASConvenience.mm | 64 -------------- Source/Layout/ASDimension.h | 10 +-- Source/Layout/ASDimension.mm | 2 +- Source/Private/ASDisplayNode+AsyncDisplay.mm | 78 +++++------------- ...Convenience.mm => UIImage+ASConvenience.m} | 48 ++++------- Source/tvOS/ASControlNode+tvOS.m | 8 +- Source/tvOS/ASImageNode+tvOS.m | 4 +- .../testRoundedCornerBlock@2x.png | Bin 11609 -> 0 bytes examples/ASDKgram/Sample/PhotoCellNode.m | 4 +- 16 files changed, 109 insertions(+), 250 deletions(-) create mode 100644 Source/Details/CoreGraphics+ASConvenience.m delete mode 100644 Source/Details/CoreGraphics+ASConvenience.mm rename Source/{UIImage+ASConvenience.mm => UIImage+ASConvenience.m} (86%) delete mode 100644 Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index d3e26159d..3c6668706 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -109,7 +109,7 @@ 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; }; 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */; }; 509E68651B3AEDC5009B9150 /* CoreGraphics+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */; }; + 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */; }; 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.mm in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.mm */; }; 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; 680346941CE4052A0009FEB4 /* ASNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -176,7 +176,7 @@ 7AB338671C55B3460055FDE8 /* ASRelativeLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */; }; 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */; }; + 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */; }; 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A7D9581D44542100BF333E /* ASWeakMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -621,7 +621,7 @@ 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewLayoutController.h; sourceTree = ""; }; 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewLayoutController.m; sourceTree = ""; }; 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CoreGraphics+ASConvenience.h"; sourceTree = ""; }; - 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "CoreGraphics+ASConvenience.mm"; sourceTree = ""; }; + 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CoreGraphics+ASConvenience.m"; sourceTree = ""; }; 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBasicImageDownloaderTests.m; sourceTree = ""; }; 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTruncationTests.mm; sourceTree = ""; }; @@ -734,7 +734,7 @@ 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRelativeLayoutSpec.h; sourceTree = ""; }; 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ASConvenience.h"; sourceTree = ""; }; - 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIImage+ASConvenience.mm"; sourceTree = ""; }; + 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ASConvenience.m"; sourceTree = ""; }; 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeSnapshotTests.m; sourceTree = ""; }; 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = ""; }; 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = ""; }; @@ -1234,7 +1234,7 @@ 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */, 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */, - 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */, + 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */, CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */, CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.m */, ); @@ -1422,7 +1422,7 @@ CC3B20871C3F7A5400798563 /* ASWeakSet.h */, CC3B20881C3F7A5400798563 /* ASWeakSet.m */, 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */, - 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.mm */, + 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, DBC452DA1C5BF64600B16017 /* NSArray+Diffing.mm */, CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */, @@ -2405,7 +2405,7 @@ CCA282C51E9EAE630037E8B7 /* ASLayerBackingTipProvider.m in Sources */, 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, - 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.mm in Sources */, + 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, CCAA0B80206ADBF30057B336 /* ASRecursiveUnfairLock.m in Sources */, CCBDDD0620C62A2D00CBA922 /* ASMainThreadDeallocation.mm in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, @@ -2504,7 +2504,7 @@ 6959433F1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */, 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.m in Sources */, CC034A141E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m in Sources */, - 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.mm in Sources */, + 509E68661B3AEDD7009B9150 /* CoreGraphics+ASConvenience.m in Sources */, 254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */, 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 5be5334b2..90f87a289 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ - Reduced binary size by disabling exception support (which we don't use.) [Adlai Holler](https://github.com/Adlai-Holler) - Create and set delegate for clip corner layers within ASDisplayNode [Michael Schneider](https://github.com/maicki) [#1029](https://github.com/TextureGroup/Texture/pull/1029) - Improve locking situation in ASVideoPlayerNode [Michael Schneider](https://github.com/maicki) [#1042](https://github.com/TextureGroup/Texture/pull/1042) -- Optimize drawing code + add examples how to round corners. [Michael Schneider](https://github.com/maicki) + ## 2.7 - Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 0fa7a9eb6..347d41c42 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -53,7 +53,6 @@ #import #import #import -#import // Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) #if TIME_DISPLAYNODE_OPS @@ -1509,7 +1508,7 @@ - (void)_pendingNodeDidDisplay:(ASDisplayNode *)node __instanceLock__.lock(); if (_placeholderLayer.superlayer && !placeholderShouldPersist) { void (^cleanupBlock)() = ^{ - [self->_placeholderLayer removeFromSuperlayer]; + [_placeholderLayer removeFromSuperlayer]; }; if (_placeholderFadeDuration > 0.0 && ASInterfaceStateIncludesVisible(self.interfaceState)) { @@ -1667,29 +1666,24 @@ - (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor CGSize size = CGSizeMake(radius + 1, radius + 1); ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay); - CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextRef ctx = UIGraphicsGetCurrentContext(); if (isRight == YES) { - CGContextTranslateCTM(context, -radius + 1, 0); + CGContextTranslateCTM(ctx, -radius + 1, 0); } if (isTop == YES) { - CGContextTranslateCTM(context, 0, -radius + 1); + CGContextTranslateCTM(ctx, 0, -radius + 1); } - - CGMutablePathRef roundedPath = CGPathCreateMutable(); - CGPathRef addedPath = ASCGRoundedPathCreate(CGRectMake(0, 0, radius * 2, radius * 2), radius); - CGPathAddPath(roundedPath, NULL, addedPath); - CGPathAddRect(roundedPath, NULL, CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)); - CGContextAddPath(context, roundedPath); - CGContextSetFillColorWithColor(context, backgroundColor.CGColor); - CGContextEOFillPath(context); - - // No lock needed, as _clipCornerLayers is only modified on the main thread. - _clipCornerLayers[idx].contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); - _clipCornerLayers[idx].bounds = CGRectMake(0.0, 0.0, size.width, size.height); - _clipCornerLayers[idx].anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); + UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, radius * 2, radius * 2) cornerRadius:radius]; + [roundedRect setUsesEvenOddFillRule:YES]; + [roundedRect appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(-1, -1, radius * 2 + 1, radius * 2 + 1)]]; + [backgroundColor setFill]; + [roundedRect fill]; - CGPathRelease(addedPath); - CGPathRelease(roundedPath); + // No lock needed, as _clipCornerLayers is only modified on the main thread. + CALayer *clipCornerLayer = _clipCornerLayers[idx]; + clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); + clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height); + clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); } [self _layoutClipCornersIfNeeded]; }); @@ -1874,10 +1868,7 @@ - (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer [self displayDidFinish]; } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - (void)displayWillStart {} -#pragma clang diagnostic pop - (void)displayWillStartAsynchronously:(BOOL)asynchronously { ASDisplayNodeAssertMainThread(); @@ -2988,11 +2979,11 @@ - (void)didExitHierarchy if (ASInterfaceStateIncludesVisible(self.pendingInterfaceState)) { void(^exitVisibleInterfaceState)(void) = ^{ // This block intentionally retains self. - self->__instanceLock__.lock(); - unsigned isStillInHierarchy = self->_flags.isInHierarchy; - BOOL isVisible = ASInterfaceStateIncludesVisible(self->_pendingInterfaceState); - ASInterfaceState newState = (self->_pendingInterfaceState & ~ASInterfaceStateVisible); - self->__instanceLock__.unlock(); + __instanceLock__.lock(); + unsigned isStillInHierarchy = _flags.isInHierarchy; + BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState); + ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible); + __instanceLock__.unlock(); if (!isStillInHierarchy && isVisible) { #if ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR if (![self supportsRangeManagedInterfaceState]) { @@ -3151,8 +3142,8 @@ - (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState [self setDisplaySuspended:YES]; //schedule clear contents on next runloop dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(self->__instanceLock__); - if (ASInterfaceStateIncludesDisplay(self->_interfaceState) == NO) { + ASDN::MutexLocker l(__instanceLock__); + if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { [self clearContents]; } }); @@ -3169,8 +3160,8 @@ - (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState [[self asyncLayer] cancelAsyncDisplay]; //schedule clear contents on next runloop dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(self->__instanceLock__); - if (ASInterfaceStateIncludesDisplay(self->_interfaceState) == NO) { + ASDN::MutexLocker l(__instanceLock__); + if (ASInterfaceStateIncludesDisplay(_interfaceState) == NO) { [self clearContents]; } }); diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index 374099136..d060d8993 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -737,43 +737,19 @@ asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat { return ^(UIImage *originalImage) { ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); - - CGContextRef context = UIGraphicsGetCurrentContext(); + UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}]; - CGRect rect = (CGRect){CGPointZero, originalImage.size}; - CGMutablePathRef path = CGPathCreateMutable(); - - CGPathAddEllipseInRect(path, NULL, rect); - CGContextAddPath(context, path); - // Make the image round - CGContextClip(context); - - // Although drawAtPoint:blendMode: would consider the CTM already, we are using CGContext* functions for drawing - // the image instead calling drawAtPoint:blendMode. This will save use 50% of retain calls for the image - CGContextSetBlendMode(context, kCGBlendModeCopy); - CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect)); - CGContextScaleCTM(context, originalImage.scale, -originalImage.scale); - CGContextSetAlpha(context, 1.0); - CGContextDrawImage(context, rect, originalImage.CGImage); + [roundOutline addClip]; - CGPathRelease(path); + // Draw the original image + [originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; // Draw a border on top. if (borderWidth > 0.0) { - // Begin a new path for the border - CGContextBeginPath(context); - - CGFloat strokeThickness = borderWidth; - CGFloat strokeInset = floor((strokeThickness + 1.0f) / 2.0f) - 1.0f; - CGPathRef path = CGPathCreateWithEllipseInRect(CGRectInset(rect, strokeInset, strokeInset), NULL); - CGContextAddPath(context, path); - - CGContextSetStrokeColorWithColor(context, borderColor.CGColor); - CGContextSetLineWidth(context, borderWidth); - CGContextStrokePath(context); - - CGPathRelease(path); + [borderColor setStroke]; + [roundOutline setLineWidth:borderWidth]; + [roundOutline stroke]; } return ASGraphicsGetImageAndEndCurrentContext(); diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index b3543ced6..2fabc0dde 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -130,7 +130,7 @@ #endif #endif -#define AS_OVERLOADABLE __attribute__((overloadable)) +#define ASOVERLOADABLE __attribute__((overloadable)) #if __has_attribute(noescape) diff --git a/Source/Details/CoreGraphics+ASConvenience.h b/Source/Details/CoreGraphics+ASConvenience.h index 6229d4ad2..9eeef3192 100644 --- a/Source/Details/CoreGraphics+ASConvenience.h +++ b/Source/Details/CoreGraphics+ASConvenience.h @@ -17,8 +17,8 @@ #import +#import #import -#import #import @@ -56,11 +56,4 @@ ASDISPLAYNODE_INLINE BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CG return fabs(size1.width - size2.width) < delta && fabs(size1.height - size2.height) < delta; }; -AS_OVERLOADABLE AS_WARN_UNUSED_RESULT AS_EXTERN CGPathRef ASCGRoundedPathCreate(CGRect rect, UIRectCorner corners, CGSize cornerRadii); - -AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT CGPathRef ASCGRoundedPathCreate(CGRect rect, CGFloat cornerRadius) { - return ASCGRoundedPathCreate(rect, UIRectCornerAllCorners, CGSizeMake(cornerRadius, cornerRadius)); -} - - NS_ASSUME_NONNULL_END diff --git a/Source/Details/CoreGraphics+ASConvenience.m b/Source/Details/CoreGraphics+ASConvenience.m new file mode 100644 index 000000000..92169ffe4 --- /dev/null +++ b/Source/Details/CoreGraphics+ASConvenience.m @@ -0,0 +1,19 @@ +// +// CoreGraphics+ASConvenience.m +// Texture +// +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Pinterest, Inc. 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 + diff --git a/Source/Details/CoreGraphics+ASConvenience.mm b/Source/Details/CoreGraphics+ASConvenience.mm deleted file mode 100644 index 2a3b6b84e..000000000 --- a/Source/Details/CoreGraphics+ASConvenience.mm +++ /dev/null @@ -1,64 +0,0 @@ -// -// CoreGraphics+ASConvenience.m -// Texture -// -// 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 /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. 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 - -AS_OVERLOADABLE CGPathRef ASCGRoundedPathCreate(CGRect rect, UIRectCorner corners, CGSize cornerRadii) { - CGMutablePathRef path = CGPathCreateMutable(); - - const CGPoint topLeft = rect.origin; - const CGPoint topRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect)); - const CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)); - const CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect)); - - if (corners & UIRectCornerTopLeft) { - CGPathMoveToPoint(path, NULL, topLeft.x+cornerRadii.width, topLeft.y); - } else { - CGPathMoveToPoint(path, NULL, topLeft.x, topLeft.y); - } - - if (corners & UIRectCornerTopRight) { - CGPathAddLineToPoint(path, NULL, topRight.x-cornerRadii.width, topRight.y); - CGPathAddCurveToPoint(path, NULL, topRight.x, topRight.y, topRight.x, topRight.y+cornerRadii.height, topRight.x, topRight.y+cornerRadii.height); - } else { - CGPathAddLineToPoint(path, NULL, topRight.x, topRight.y); - } - - if (corners & UIRectCornerBottomRight) { - CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y-cornerRadii.height); - CGPathAddCurveToPoint(path, NULL, bottomRight.x, bottomRight.y, bottomRight.x-cornerRadii.width, bottomRight.y, bottomRight.x-cornerRadii.width, bottomRight.y); - } else { - CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y); - } - - if (corners & UIRectCornerBottomLeft) { - CGPathAddLineToPoint(path, NULL, bottomLeft.x+cornerRadii.width, bottomLeft.y); - CGPathAddCurveToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomLeft.x, bottomLeft.y-cornerRadii.height, bottomLeft.x, bottomLeft.y-cornerRadii.height); - } else { - CGPathAddLineToPoint(path, NULL, bottomLeft.x, bottomLeft.y); - } - - if (corners & UIRectCornerTopLeft) { - CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y+cornerRadii.height); - CGPathAddCurveToPoint(path, NULL, topLeft.x, topLeft.y, topLeft.x+cornerRadii.width, topLeft.y, topLeft.x+cornerRadii.width, topLeft.y); - } else { - CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y); - } - - CGPathCloseSubpath(path); - return path; -} diff --git a/Source/Layout/ASDimension.h b/Source/Layout/ASDimension.h index ffe6138b5..462406eed 100644 --- a/Source/Layout/ASDimension.h +++ b/Source/Layout/ASDimension.h @@ -94,7 +94,7 @@ AS_EXTERN ASDimension const ASDimensionAuto; /** * Returns a dimension with the specified type and value. */ -AS_OVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit unit, CGFloat value) +ASOVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit unit, CGFloat value) { if (unit == ASDimensionUnitAuto ) { ASDisplayNodeCAssert(value == 0, @"ASDimension auto value must be 0."); @@ -112,7 +112,7 @@ AS_OVERLOADABLE ASDISPLAYNODE_INLINE ASDimension ASDimensionMake(ASDimensionUnit /** * Returns a dimension with the specified points value. */ -AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensionMake(CGFloat points) +ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensionMake(CGFloat points) { return ASDimensionMake(ASDimensionUnitPoints, points); } @@ -122,7 +122,7 @@ AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASDimension ASDimensi * Examples: ASDimensionMake(@"50%") = ASDimensionMake(ASDimensionUnitFraction, 0.5) * ASDimensionMake(@"0.5pt") = ASDimensionMake(ASDimensionUnitPoints, 0.5) */ -AS_OVERLOADABLE AS_WARN_UNUSED_RESULT AS_EXTERN ASDimension ASDimensionMake(NSString *dimension); +ASOVERLOADABLE AS_WARN_UNUSED_RESULT AS_EXTERN ASDimension ASDimensionMake(NSString *dimension); /** * Returns a dimension with the specified points value. @@ -244,7 +244,7 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASSizeRangeHasSignificantArea(AS /** * Creates an ASSizeRange with provided min and max size. */ -AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize min, CGSize max) +ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize min, CGSize max) { ASDisplayNodeCAssertPositiveReal(@"Range min width", min.width); ASDisplayNodeCAssertPositiveReal(@"Range min height", min.height); @@ -263,7 +263,7 @@ AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRan /** * Creates an ASSizeRange with provided size as both min and max. */ -AS_OVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize exactSize) +ASOVERLOADABLE ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMake(CGSize exactSize) { return ASSizeRangeMake(exactSize, exactSize); } diff --git a/Source/Layout/ASDimension.mm b/Source/Layout/ASDimension.mm index a27226dda..4fd0e1800 100644 --- a/Source/Layout/ASDimension.mm +++ b/Source/Layout/ASDimension.mm @@ -25,7 +25,7 @@ ASDimension const ASDimensionAuto = {ASDimensionUnitAuto, 0}; -AS_OVERLOADABLE ASDimension ASDimensionMake(NSString *dimension) +ASOVERLOADABLE ASDimension ASDimensionMake(NSString *dimension) { if (dimension.length > 0) { diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 16cf9b39e..2cf772aec 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -25,7 +25,6 @@ #import #import #import -#import @interface ASDisplayNode () <_ASDisplayLayerDelegate> @@ -112,13 +111,10 @@ - (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode CGContextTranslateCTM(context, frame.origin.x, frame.origin.y); - // Support cornerRadius + //support cornerRadius if (rasterizingFromAscendent && clipsToBounds) { if (cornerRadius) { - CGPathRef cornerRadiusPath = ASCGRoundedPathCreate(bounds, cornerRadius); - CGContextAddPath(context, cornerRadiusPath); - CGContextClip(context); - CGPathRelease(cornerRadiusPath); + [[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius] addClip]; } else { CGContextClipToRect(context, bounds); } @@ -131,18 +127,13 @@ - (void)_recursivelyRasterizeSelfAndSublayersWithIsCancelledBlock:(asdisplaynode CGContextFillRect(context, bounds); } - // If there is a display block, call it to get the image, then copy the image into the current context (which - // is the rasterized container's backing store). + // If there is a display block, call it to get the image, then copy the image into the current context (which is the rasterized container's backing store). if (displayBlock) { UIImage *image = (UIImage *)displayBlock(); if (image) { BOOL opaque = ASImageAlphaInfoIsOpaque(CGImageGetAlphaInfo(image.CGImage)); CGBlendMode blendMode = opaque ? kCGBlendModeCopy : kCGBlendModeNormal; - CGContextSetBlendMode(context, blendMode); - CGContextTranslateCTM(context, 0, CGRectGetMaxY(bounds) + CGRectGetMinY(bounds)); - CGContextScaleCTM(context, 1, -1); - CGContextSetAlpha(context, 1.0); - CGContextDrawImage(context, bounds, image.CGImage); + [image drawInRect:bounds blendMode:blendMode alpha:1]; } } }; @@ -304,10 +295,7 @@ - (void)__willDisplayNodeContentWithRenderingContext:(CGContextRef)context drawP ASDisplayNodeAssert(context == UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); // TODO: This clip path should be removed if we are rasterizing. CGRect boundingBox = CGContextGetClipBoundingBox(context); - CGPathRef cornerRadiusPath = ASCGRoundedPathCreate(boundingBox, cornerRadius); - CGContextAddPath(context, cornerRadiusPath); - CGContextClip(context); - CGPathRelease(cornerRadiusPath); + [[UIBezierPath bezierPathWithRoundedRect:boundingBox cornerRadius:cornerRadius] addClip]; } if (willDisplayNodeContentWithRenderingContext) { @@ -344,58 +332,34 @@ - (void)__didDisplayNodeContentWithRenderingContext:(CGContextRef)context image: CGFloat white = 0.0f, alpha = 0.0f; [backgroundColor getWhite:&white alpha:&alpha]; ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale); - context = UIGraphicsGetCurrentContext(); [*image drawInRect:bounds]; } else { bounds = CGContextGetClipBoundingBox(context); } ASDisplayNodeAssert(UIGraphicsGetCurrentContext(), @"context is expected to be pushed on UIGraphics stack %@", self); - - CGContextSaveGState(context); - - CGMutablePathRef roundedHole = CGPathCreateMutable(); - CGPathAddRect(roundedHole, NULL, bounds); - - CGPathRef additionalPath = ASCGRoundedPathCreate(bounds, cornerRadius * contentsScale); - CGPathAddPath(roundedHole, NULL, additionalPath); - - CGContextAddPath(context, roundedHole); - - // Punch out the corners by copying the backgroundColor over them. - // This works for everything from clearColor to opaque colors. - CGContextSetFillColorWithColor(context, backgroundColor.CGColor); - CGContextSetAlpha(context, 1.0); - CGContextSetBlendMode(context, kCGBlendModeCopy); - CGContextEOFillPath(context); - - CGPathRelease(additionalPath); - CGPathRelease(roundedHole); + UIBezierPath *roundedHole = [UIBezierPath bezierPathWithRect:bounds]; + [roundedHole appendPath:[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:cornerRadius * contentsScale]]; + roundedHole.usesEvenOddFillRule = YES; - CGContextRestoreGState(context); - - // Drawing borders with ASCornerRoundingTypePrecomposited set has some problems at the moment. If the borderWidth is - // set, besides we are drawing the border with the given corner radius, the CALayer also picks up the borderWidth - // value and draws the border without the cornerRadius. + UIBezierPath *roundedPath = nil; if (borderWidth > 0.0f) { // Don't create roundedPath and stroke if borderWidth is 0.0 - CGContextSaveGState(context); - CGFloat strokeThickness = borderWidth * contentsScale; CGFloat strokeInset = ((strokeThickness + 1.0f) / 2.0f) - 1.0f; - CGPathRef roundedPath = ASCGRoundedPathCreate(CGRectInset(bounds, strokeInset, strokeInset), _cornerRadius * contentsScale); - CGContextAddPath(context, roundedPath); - - CGContextSetLineWidth(context, strokeThickness); - CGContextSetStrokeColorWithColor(context, borderColor); - - CGContextStrokePath(context); - - CGPathRelease(roundedPath); - - CGContextRestoreGState(context); + roundedPath = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(bounds, strokeInset, strokeInset) + cornerRadius:_cornerRadius * contentsScale]; + roundedPath.lineWidth = strokeThickness; + [[UIColor colorWithCGColor:borderColor] setStroke]; } - + + // Punch out the corners by copying the backgroundColor over them. + // This works for everything from clearColor to opaque colors. + [backgroundColor setFill]; + [roundedHole fillWithBlendMode:kCGBlendModeCopy alpha:1.0f]; + + [roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil. + if (*image) { *image = ASGraphicsGetImageAndEndCurrentContext(); } diff --git a/Source/UIImage+ASConvenience.mm b/Source/UIImage+ASConvenience.m similarity index 86% rename from Source/UIImage+ASConvenience.mm rename to Source/UIImage+ASConvenience.m index 84e87b0fc..c1d0751e3 100644 --- a/Source/UIImage+ASConvenience.mm +++ b/Source/UIImage+ASConvenience.m @@ -19,7 +19,6 @@ #import #import #import -#import #pragma mark - ASDKFastImageNamed @@ -115,9 +114,9 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius // UIBezierPath objects are fairly small and these are equally sized. 20 should be plenty for many different parameters. __pathCache.countLimit = 20; }); - + // Treat clear background color as no background color - if (CGColorGetAlpha(cornerColor.CGColor) == 0) { + if ([cornerColor isEqual:[UIColor clearColor]]) { cornerColor = nil; } @@ -141,46 +140,33 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius // We should probably check if the background color has any alpha component but that // might be expensive due to needing to check mulitple color spaces. ASGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); - - CGContextRef context = UIGraphicsGetCurrentContext(); - - // Draw Corners + BOOL contextIsClean = YES; if (cornerColor) { contextIsClean = NO; - - CGContextSetFillColorWithColor(context, cornerColor.CGColor); + [cornerColor setFill]; // Copy "blend" mode is extra fast because it disregards any value currently in the buffer and overrides directly. - CGContextSetBlendMode(context, kCGBlendModeCopy); - CGContextFillRect(context, bounds); + UIRectFillUsingBlendMode(bounds, kCGBlendModeCopy); } - - // Draw fill + BOOL canUseCopy = contextIsClean || (CGColorGetAlpha(fillColor.CGColor) == 1); - CGContextSetFillColorWithColor(context, fillColor.CGColor); - CGContextSetBlendMode(context, canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal); - CGContextSetAlpha(context, 1.0); - CGContextAddPath(context, path.CGPath); - CGContextFillPath(context); - - // Add a border + [fillColor setFill]; + [path fillWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; + if (borderColor) { + [borderColor setStroke]; + // Inset border fully inside filled path (not halfway on each side of path) CGRect strokeRect = CGRectInset(bounds, borderWidth / 2.0, borderWidth / 2.0); - + // It is rarer to have a stroke path, and our cache key only handles rounded rects for the exact-stretchable // size calculated by cornerRadius, so we won't bother caching this path. Profiling validates this decision. - CGPathRef strokePath = ASCGRoundedPathCreate(strokeRect, roundedCorners, cornerRadii); - - CGContextSetStrokeColorWithColor(context, borderColor.CGColor); - CGContextSetLineWidth(context, borderWidth); - CGContextSetAlpha(context, 1.0); + UIBezierPath *strokePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect + byRoundingCorners:roundedCorners + cornerRadii:cornerRadii]; + [strokePath setLineWidth:borderWidth]; BOOL canUseCopy = (CGColorGetAlpha(borderColor.CGColor) == 1); - CGContextSetBlendMode(context, (canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal)); - CGContextAddPath(context, strokePath); - CGContextStrokePath(context); - - CGPathRelease(strokePath); + [strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; } UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); diff --git a/Source/tvOS/ASControlNode+tvOS.m b/Source/tvOS/ASControlNode+tvOS.m index c18b67eec..77e541862 100644 --- a/Source/tvOS/ASControlNode+tvOS.m +++ b/Source/tvOS/ASControlNode+tvOS.m @@ -83,9 +83,7 @@ - (void)applyDefaultShadowProperties:(CALayer *)layer layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowRadius = 12.0; layer.shadowOpacity = 0.45; - CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL); - layer.shadowPath = shadowPath; - CGPathRelease(shadowPath); + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; } - (void)setDefaultFocusAppearance @@ -95,9 +93,7 @@ - (void)setDefaultFocusAppearance layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowRadius = 0; layer.shadowOpacity = 0; - CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL); - layer.shadowPath = shadowPath; - CGPathRelease(shadowPath); + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); } @end diff --git a/Source/tvOS/ASImageNode+tvOS.m b/Source/tvOS/ASImageNode+tvOS.m index 8f8486b94..9482fdc8c 100644 --- a/Source/tvOS/ASImageNode+tvOS.m +++ b/Source/tvOS/ASImageNode+tvOS.m @@ -169,9 +169,7 @@ - (void)setFocusedState layer.shadowColor = [UIColor blackColor].CGColor; layer.shadowRadius = 12.0; layer.shadowOpacity = 0.45; - CGPathRef shadowPath = CGPathCreateWithRect(self.layer.bounds, NULL); - layer.shadowPath = shadowPath; - CGPathRelease(shadowPath); + layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); } diff --git a/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png b/Tests/ReferenceImages_iOS_10/ASImageNodeSnapshotTests/testRoundedCornerBlock@2x.png deleted file mode 100644 index c4686232a23cb980c70b44339427ddfa81412562..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11609 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 Date: Wed, 1 Aug 2018 15:52:38 -0600 Subject: [PATCH 03/15] Fix headers in markdown (#1053) --- docs/_docs/automatic-layout-basics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/_docs/automatic-layout-basics.md b/docs/_docs/automatic-layout-basics.md index 8a830b1cd..128a1040d 100755 --- a/docs/_docs/automatic-layout-basics.md +++ b/docs/_docs/automatic-layout-basics.md @@ -6,7 +6,7 @@ prevPage: scroll-node.html nextPage: automatic-layout-containers.html --- -##Box Model Layout +## Box Model Layout ASLayout is an automatic, asynchronous, purely Objective-C box model layout feature. It is a simplified version of CSS flex box, loosely inspired by ComponentKit’s Layout. It is designed to make your layouts extensible and reusable. @@ -14,7 +14,7 @@ ASLayout is an automatic, asynchronous, purely Objective-C box model layout feat `` instances (all ASDisplayNodes and subclasses) do not have any size or position information. Instead, Texture calls the `layoutSpecThatFits:` method with a given size constraint and the component must return a structure describing both its size, and the position and sizes of its children. -##Terminology +## Terminology The terminology is a bit confusing, so here is a brief description of all of the Texture automatic layout players: @@ -30,7 +30,7 @@ Every ASLayoutSpec must act on at least one child. The ASLayoutSpec has the resp You don’t need to be aware of **`ASLayout`** except to know that it represents a computed immutable layout tree and is returned by objects conforming to the `` protocol. -##Layout for UIKit Components: +## Layout for UIKit Components: - for UIViews that are added directly, you will still need to manually lay it out in `didLoad:` - for UIViews that are added via `[ASDisplayNode initWithViewBlock:]` or its variants, you can then include it in `layoutSpecThatFits:` From 78be342e77ec82cec5fa0e1e0b15cb172864edeb Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 2 Aug 2018 07:36:26 -0700 Subject: [PATCH 04/15] [ASTextNode2] Simplify allocWithZone: + initialize implementation #trivial (#1059) * Simplify ASTextNode2 alloc + initialize implementation * Kick the CI by marking two methods as NO_ESCAPE for Xcode 10 --- Source/ASTextNode.mm | 41 +++++++++++++++---------------------- Tests/ASDisplayLayerTests.m | 4 ++-- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index 5d51adbff..f71363d68 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -1346,36 +1346,29 @@ + (void)_registerAttributedText:(NSAttributedString *)str } #endif -+ (id)allocWithZone:(struct _NSZone *)zone +// All direct descendants of ASTextNode get their superclass replaced by ASTextNode2. ++ (void)initialize { - // If they're not experimenting, just forward. - if (!ASActivateExperimentalFeature(ASExperimentalTextNode)) { - return [super allocWithZone:zone]; - } - - // We are plain ASTextNode. Just swap in an ASTextNode2 instead. - if (self == [ASTextNode class]) { - return (ASTextNode *)[ASTextNode2 allocWithZone:zone]; - } - - // We are descended from ASTextNode. We need to change the superclass for the - // ASTextNode subclass to ASTextNode2. - // Walk up the class hierarchy until we find ASTextNode. - // Note: This may be called on multiple threads simultaneously. - Class s; - for (Class c = self; c != Nil && c != [ASTextNode class]; c = s) { - s = class_getSuperclass(c); - if (s == [ASTextNode class]) { + // Texture requires that node subclasses call [super initialize] + [super initialize]; + + if (class_getSuperclass(self) == [ASTextNode class] + && ASActivateExperimentalFeature(ASExperimentalTextNode)) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - // Direct descendent. Update superclass of c and end. - class_setSuperclass(c, [ASTextNode2 class]); + class_setSuperclass(self, [ASTextNode2 class]); #pragma clang diagnostic pop - break; - } } +} - return [super allocWithZone:zone]; +// For direct allocations of ASTextNode itself, we override allocWithZone: ++ (id)allocWithZone:(struct _NSZone *)zone +{ + if (ASActivateExperimentalFeature(ASExperimentalTextNode)) { + return (ASTextNode *)[ASTextNode2 allocWithZone:zone]; + } else { + return [super allocWithZone:zone]; + } } @end diff --git a/Tests/ASDisplayLayerTests.m b/Tests/ASDisplayLayerTests.m index 5f20e928e..e462e0a4a 100644 --- a/Tests/ASDisplayLayerTests.m +++ b/Tests/ASDisplayLayerTests.m @@ -217,7 +217,7 @@ + (BOOL)respondsToSelector:(SEL)selector } // DANGER: Don't use the delegate as the parameters in real code; this is not thread-safe and just for accounting in unit tests! -+ (UIImage *)displayWithParameters:(_ASDisplayLayerTestDelegate *)delegate isCancelled:(asdisplaynode_iscancelled_block_t)sentinelBlock ++ (UIImage *)displayWithParameters:(_ASDisplayLayerTestDelegate *)delegate isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)sentinelBlock { UIImage *contents = bogusImage(); if (delegate->_displayLayerBlock != NULL) { @@ -228,7 +228,7 @@ + (UIImage *)displayWithParameters:(_ASDisplayLayerTestDelegate *)delegate isCan } // DANGER: Don't use the delegate as the parameters in real code; this is not thread-safe and just for accounting in unit tests! -+ (void)drawRect:(CGRect)bounds withParameters:(_ASDisplayLayerTestDelegate *)delegate isCancelled:(asdisplaynode_iscancelled_block_t)sentinelBlock isRasterizing:(BOOL)isRasterizing ++ (void)drawRect:(CGRect)bounds withParameters:(_ASDisplayLayerTestDelegate *)delegate isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)sentinelBlock isRasterizing:(BOOL)isRasterizing { __atomic_add_fetch(&delegate->_drawRectCount, 1, __ATOMIC_SEQ_CST); } From 093ae3fba03103c78d0f1df917cf9f1f3a2008d2 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 2 Aug 2018 08:39:01 -0700 Subject: [PATCH 05/15] Remove CATransaction signposts because they cause more transactions than needed and are too chatty. (#1060) --- CHANGELOG.md | 1 + Source/ASRunLoopQueue.mm | 68 ---------------------------------------- 2 files changed, 1 insertion(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f87a289..2533928b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Reduced binary size by disabling exception support (which we don't use.) [Adlai Holler](https://github.com/Adlai-Holler) - Create and set delegate for clip corner layers within ASDisplayNode [Michael Schneider](https://github.com/maicki) [#1029](https://github.com/TextureGroup/Texture/pull/1029) - Improve locking situation in ASVideoPlayerNode [Michael Schneider](https://github.com/maicki) [#1042](https://github.com/TextureGroup/Texture/pull/1042) +- Remove CA transaction signpost injection because it causes more transactions and is too chatty. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.7 diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index 6f5c2232a..2cc4fc6b3 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -254,29 +254,6 @@ - (void)drain @end -#if AS_KDEBUG_ENABLE -/** - * This is real, private CA API. Valid as of iOS 10. - */ -typedef enum { - kCATransactionPhasePreLayout, - kCATransactionPhasePreCommit, - kCATransactionPhasePostCommit, -} CATransactionPhase; - -@interface CATransaction (Private) -+ (void)addCommitHandler:(void(^)(void))block forPhase:(CATransactionPhase)phase; -+ (int)currentState; -@end -#endif - -#pragma mark - ASAbstractRunLoopQueue - -@interface ASAbstractRunLoopQueue (Private) -+ (void)load; -+ (void)registerCATransactionObservers; -@end - @implementation ASAbstractRunLoopQueue - (instancetype)init @@ -289,51 +266,6 @@ - (instancetype)init return self; } -#if AS_KDEBUG_ENABLE -+ (void)load -{ - [self registerCATransactionObservers]; -} - -+ (void)registerCATransactionObservers -{ - static BOOL privateCAMethodsExist; - static dispatch_block_t preLayoutHandler; - static dispatch_block_t preCommitHandler; - static dispatch_block_t postCommitHandler; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - privateCAMethodsExist = [CATransaction respondsToSelector:@selector(addCommitHandler:forPhase:)]; - privateCAMethodsExist &= [CATransaction respondsToSelector:@selector(currentState)]; - if (!privateCAMethodsExist) { - NSLog(@"Private CA methods are gone."); - } - preLayoutHandler = ^{ - ASSignpostStartCustom(ASSignpostCATransactionLayout, 0, [CATransaction currentState]); - }; - preCommitHandler = ^{ - int state = [CATransaction currentState]; - ASSignpostEndCustom(ASSignpostCATransactionLayout, 0, state, ASSignpostColorDefault); - ASSignpostStartCustom(ASSignpostCATransactionCommit, 0, state); - }; - postCommitHandler = ^{ - ASSignpostEndCustom(ASSignpostCATransactionCommit, 0, [CATransaction currentState], ASSignpostColorDefault); - // Can't add new observers inside an observer. rdar://problem/31253952 - dispatch_async(dispatch_get_main_queue(), ^{ - [self registerCATransactionObservers]; - }); - }; - }); - - if (privateCAMethodsExist) { - [CATransaction addCommitHandler:preLayoutHandler forPhase:kCATransactionPhasePreLayout]; - [CATransaction addCommitHandler:preCommitHandler forPhase:kCATransactionPhasePreCommit]; - [CATransaction addCommitHandler:postCommitHandler forPhase:kCATransactionPhasePostCommit]; - } -} - -#endif // AS_KDEBUG_ENABLE - @end #pragma mark - ASRunLoopQueue From e76b4f02f68d1d0efed73addca1dc7529f70c3ae Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 3 Aug 2018 10:23:05 -0700 Subject: [PATCH 06/15] Remove extra string/attributed string creation in accessibility properties (#1062) --- CHANGELOG.md | 1 + Source/Private/_ASPendingState.mm | 91 ++++++++++++++++--------------- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2533928b4..98865abd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Create and set delegate for clip corner layers within ASDisplayNode [Michael Schneider](https://github.com/maicki) [#1029](https://github.com/TextureGroup/Texture/pull/1029) - Improve locking situation in ASVideoPlayerNode [Michael Schneider](https://github.com/maicki) [#1042](https://github.com/TextureGroup/Texture/pull/1042) - Remove CA transaction signpost injection because it causes more transactions and is too chatty. [Adlai Holler](https://github.com/Adlai-Holler) +- Optimize display node accessibility by not creating attributed & non-attributed copies of hint, label, and value. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.7 diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index a87707bb6..3565aa835 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -607,92 +607,92 @@ - (void)setIsAccessibilityElement:(BOOL)newIsAccessibilityElement - (NSString *)accessibilityLabel { + if (_flags.setAccessibilityAttributedLabel) { + return accessibilityAttributedLabel.string; + } return accessibilityLabel; } - (void)setAccessibilityLabel:(NSString *)newAccessibilityLabel { - if (! ASObjectIsEqual(accessibilityLabel, newAccessibilityLabel)) { - _flags.setAccessibilityLabel = YES; - _flags.setAccessibilityAttributedLabel = YES; - accessibilityLabel = newAccessibilityLabel ? [newAccessibilityLabel copy] : nil; - accessibilityAttributedLabel = newAccessibilityLabel ? [[NSAttributedString alloc] initWithString:newAccessibilityLabel] : nil; - } + ASCompareAssignCopy(accessibilityLabel, newAccessibilityLabel); + _flags.setAccessibilityLabel = YES; + _flags.setAccessibilityAttributedLabel = NO; } - (NSAttributedString *)accessibilityAttributedLabel { + if (_flags.setAccessibilityLabel) { + return [[NSAttributedString alloc] initWithString:accessibilityLabel]; + } return accessibilityAttributedLabel; } - (void)setAccessibilityAttributedLabel:(NSAttributedString *)newAccessibilityAttributedLabel { - if (! ASObjectIsEqual(accessibilityAttributedLabel, newAccessibilityAttributedLabel)) { - _flags.setAccessibilityAttributedLabel = YES; - _flags.setAccessibilityLabel = YES; - accessibilityAttributedLabel = newAccessibilityAttributedLabel ? [newAccessibilityAttributedLabel copy] : nil; - accessibilityLabel = newAccessibilityAttributedLabel ? [newAccessibilityAttributedLabel.string copy] : nil; - } + ASCompareAssignCopy(accessibilityAttributedLabel, newAccessibilityAttributedLabel); + _flags.setAccessibilityAttributedLabel = YES; + _flags.setAccessibilityLabel = NO; } - (NSString *)accessibilityHint { + if (_flags.setAccessibilityAttributedHint) { + return accessibilityAttributedHint.string; + } return accessibilityHint; } - (void)setAccessibilityHint:(NSString *)newAccessibilityHint { - if (! ASObjectIsEqual(accessibilityHint, newAccessibilityHint)) { - _flags.setAccessibilityHint = YES; - _flags.setAccessibilityAttributedHint = YES; - accessibilityHint = newAccessibilityHint ? [newAccessibilityHint copy] : nil; - accessibilityAttributedHint = newAccessibilityHint ? [[NSAttributedString alloc] initWithString:newAccessibilityHint] : nil; - } + ASCompareAssignCopy(accessibilityHint, newAccessibilityHint); + _flags.setAccessibilityHint = YES; + _flags.setAccessibilityAttributedHint = NO; } - (NSAttributedString *)accessibilityAttributedHint { + if (_flags.setAccessibilityHint) { + return [[NSAttributedString alloc] initWithString:accessibilityHint]; + } return accessibilityAttributedHint; } - (void)setAccessibilityAttributedHint:(NSAttributedString *)newAccessibilityAttributedHint { - if (! ASObjectIsEqual(accessibilityAttributedHint, newAccessibilityAttributedHint)) { - _flags.setAccessibilityAttributedHint = YES; - _flags.setAccessibilityHint = YES; - accessibilityAttributedHint = newAccessibilityAttributedHint ? [newAccessibilityAttributedHint copy] : nil; - accessibilityHint = newAccessibilityAttributedHint ? [newAccessibilityAttributedHint.string copy] : nil; - } + ASCompareAssignCopy(accessibilityAttributedHint, newAccessibilityAttributedHint); + _flags.setAccessibilityAttributedHint = YES; + _flags.setAccessibilityHint = NO; } - (NSString *)accessibilityValue { + if (_flags.setAccessibilityAttributedValue) { + return accessibilityAttributedValue.string; + } return accessibilityValue; } - (void)setAccessibilityValue:(NSString *)newAccessibilityValue { - if (! ASObjectIsEqual(accessibilityValue, newAccessibilityValue)) { - _flags.setAccessibilityValue = YES; - _flags.setAccessibilityAttributedValue = YES; - accessibilityValue = newAccessibilityValue ? [newAccessibilityValue copy] : nil; - accessibilityAttributedValue = newAccessibilityValue ? [[NSAttributedString alloc] initWithString:newAccessibilityValue] : nil; - } + ASCompareAssignCopy(accessibilityValue, newAccessibilityValue); + _flags.setAccessibilityValue = YES; + _flags.setAccessibilityAttributedValue = NO; } - (NSAttributedString *)accessibilityAttributedValue { + if (_flags.setAccessibilityValue) { + return [[NSAttributedString alloc] initWithString:accessibilityValue]; + } return accessibilityAttributedValue; } - (void)setAccessibilityAttributedValue:(NSAttributedString *)newAccessibilityAttributedValue { - if (! ASObjectIsEqual(accessibilityAttributedValue, newAccessibilityAttributedValue)) { - _flags.setAccessibilityAttributedValue = YES; - _flags.setAccessibilityValue = YES; - accessibilityAttributedValue = newAccessibilityAttributedValue? [newAccessibilityAttributedValue copy] : nil; - accessibilityValue = newAccessibilityAttributedValue ? [newAccessibilityAttributedValue.string copy] : nil; - } + ASCompareAssignCopy(accessibilityAttributedValue, newAccessibilityAttributedValue); + _flags.setAccessibilityAttributedValue = YES; + _flags.setAccessibilityValue = NO; } - (UIAccessibilityTraits)accessibilityTraits @@ -1087,20 +1087,23 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setAccessibilityLabel) view.accessibilityLabel = accessibilityLabel; - if (AS_AT_LEAST_IOS11 && flags.setAccessibilityAttributedLabel) - [view setValue:accessibilityAttributedLabel forKey:@"accessibilityAttributedLabel"]; - if (flags.setAccessibilityHint) view.accessibilityHint = accessibilityHint; - if (AS_AT_LEAST_IOS11 && flags.setAccessibilityAttributedHint) - [view setValue:accessibilityAttributedHint forKey:@"accessibilityAttributedHint"]; - if (flags.setAccessibilityValue) view.accessibilityValue = accessibilityValue; - if (AS_AT_LEAST_IOS11 && flags.setAccessibilityAttributedValue) - [view setValue:accessibilityAttributedValue forKey:@"accessibilityAttributedValue"]; + if (AS_AVAILABLE_IOS(11)) { + if (flags.setAccessibilityAttributedLabel) { + view.accessibilityAttributedLabel = accessibilityAttributedLabel; + } + if (flags.setAccessibilityAttributedHint) { + view.accessibilityAttributedHint = accessibilityAttributedHint; + } + if (flags.setAccessibilityAttributedValue) { + view.accessibilityAttributedValue = accessibilityAttributedValue; + } + } if (flags.setAccessibilityTraits) view.accessibilityTraits = accessibilityTraits; From 40e3bf8952ad960dbd82ab9e0e8cd192536177ee Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 3 Aug 2018 10:24:37 -0700 Subject: [PATCH 07/15] Remove objc association & weak proxy from node -> controller pointer (#1061) * Remove objc association & weak proxy from node -> controller relationship * Rename ASNodeController+Beta.m to ASNodeControllerx+Beta.mm Currently we can't import ASDisplayNodeInternal from C * Update project pointers * Rename ASNodeControllerx+Beta.mm to ASNodeController+Beta.mm --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 +-- CHANGELOG.md | 1 + ...roller+Beta.m => ASNodeController+Beta.mm} | 60 ++----------------- Source/Private/ASDisplayNodeInternal.h | 3 + 4 files changed, 14 insertions(+), 58 deletions(-) rename Source/{ASNodeController+Beta.m => ASNodeController+Beta.mm} (59%) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3c6668706..68c61c7e9 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -158,7 +158,7 @@ 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */; }; 697B315A1CFE4B410049936F /* ASEditableTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */; }; 698371DB1E4379CD00437585 /* ASNodeController+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 698371D91E4379CD00437585 /* ASNodeController+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 698371DC1E4379CD00437585 /* ASNodeController+Beta.m in Sources */ = {isa = PBXBuildFile; fileRef = 698371DA1E4379CD00437585 /* ASNodeController+Beta.m */; }; + 698371DC1E4379CD00437585 /* ASNodeController+Beta.mm in Sources */ = {isa = PBXBuildFile; fileRef = 698371DA1E4379CD00437585 /* ASNodeController+Beta.mm */; }; 698C8B621CAB49FC0052DC3F /* ASLayoutElementExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutElementExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 698DFF441E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 698DFF431E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; }; 698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 698DFF461E36B7E9002891F1 /* ASLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -716,7 +716,7 @@ 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASLayoutSpec+Subclasses.mm"; sourceTree = ""; }; 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASEditableTextNodeTests.m; sourceTree = ""; }; 698371D91E4379CD00437585 /* ASNodeController+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASNodeController+Beta.h"; sourceTree = ""; }; - 698371DA1E4379CD00437585 /* ASNodeController+Beta.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASNodeController+Beta.m"; sourceTree = ""; }; + 698371DA1E4379CD00437585 /* ASNodeController+Beta.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASNodeController+Beta.mm"; sourceTree = ""; }; 698C8B601CAB49FC0052DC3F /* ASLayoutElementExtensibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementExtensibility.h; sourceTree = ""; }; 698DFF431E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutSpecUtilities.h; sourceTree = ""; }; 698DFF461E36B7E9002891F1 /* ASLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecUtilities.h; sourceTree = ""; }; @@ -1197,7 +1197,7 @@ 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */, 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */, 698371D91E4379CD00437585 /* ASNodeController+Beta.h */, - 698371DA1E4379CD00437585 /* ASNodeController+Beta.m */, + 698371DA1E4379CD00437585 /* ASNodeController+Beta.mm */, DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */, DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */, 25E327541C16819500A2170C /* ASPagerNode.h */, @@ -2374,7 +2374,7 @@ DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */, CCA282C11E9EAE010037E8B7 /* ASTip.m in Sources */, B350624C1B010EFD0018CF92 /* _ASPendingState.mm in Sources */, - 698371DC1E4379CD00437585 /* ASNodeController+Beta.m in Sources */, + 698371DC1E4379CD00437585 /* ASNodeController+Beta.mm in Sources */, CC6AA2DB1E9F03B900978E87 /* ASDisplayNode+Ancestry.m in Sources */, 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 98865abd8..64ec5fff1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Reduced binary size by disabling exception support (which we don't use.) [Adlai Holler](https://github.com/Adlai-Holler) - Create and set delegate for clip corner layers within ASDisplayNode [Michael Schneider](https://github.com/maicki) [#1029](https://github.com/TextureGroup/Texture/pull/1029) - Improve locking situation in ASVideoPlayerNode [Michael Schneider](https://github.com/maicki) [#1042](https://github.com/TextureGroup/Texture/pull/1042) +- Optimize ASDisplayNode -> ASNodeController reference by removing weak proxy and objc associated objects. [Adlai Holler](https://github.com/Adlai-Holler) - Remove CA transaction signpost injection because it causes more transactions and is too chatty. [Adlai Holler](https://github.com/Adlai-Holler) - Optimize display node accessibility by not creating attributed & non-attributed copies of hint, label, and value. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASNodeController+Beta.m b/Source/ASNodeController+Beta.mm similarity index 59% rename from Source/ASNodeController+Beta.m rename to Source/ASNodeController+Beta.mm index 5f5fcddf6..be6da32fe 100644 --- a/Source/ASNodeController+Beta.m +++ b/Source/ASNodeController+Beta.mm @@ -16,38 +16,18 @@ // #import +#import #import #import -#import #define _node (_shouldInvertStrongReference ? _weakNode : _strongNode) -@interface ASDisplayNode (ASNodeControllerOwnership) - -// This property exists for debugging purposes. Don't use __nodeController in production code. -@property (nonatomic, readonly) ASNodeController *__nodeController; - -// These setters are mutually exclusive. Setting one will clear the relationship of the other. -- (void)__setNodeControllerStrong:(ASNodeController *)nodeController; -- (void)__setNodeControllerWeak:(ASNodeController *)nodeController; - -@end - @implementation ASNodeController { ASDisplayNode *_strongNode; __weak ASDisplayNode *_weakNode; } -- (instancetype)init -{ - self = [super init]; - if (self) { - - } - return self; -} - - (void)loadNode { self.node = [[ASDisplayNode alloc] init]; @@ -66,12 +46,14 @@ - (void)setupReferencesWithNode:(ASDisplayNode *)node if (_shouldInvertStrongReference) { // The node should own the controller; weak reference from controller to node. _weakNode = node; - [node __setNodeControllerStrong:self]; + node->_strongNodeController = self; + node->_weakNodeController = nil; _strongNode = nil; } else { // The controller should own the node; weak reference from node to controller. _strongNode = node; - [node __setNodeControllerWeak:self]; + node->_weakNodeController = self; + node->_strongNodeController = nil; _weakNode = nil; } @@ -111,40 +93,10 @@ - (void)interfaceStateDidChange:(ASInterfaceState)newState @end -@implementation ASDisplayNode (ASNodeControllerOwnership) - -- (ASNodeController *)__nodeController -{ - ASNodeController *nodeController = nil; - id object = objc_getAssociatedObject(self, @selector(__nodeController)); - - if ([object isKindOfClass:[ASWeakProxy class]]) { - nodeController = (ASNodeController *)[(ASWeakProxy *)object target]; - } else { - nodeController = (ASNodeController *)object; - } - - return nodeController; -} - -- (void)__setNodeControllerStrong:(ASNodeController *)nodeController -{ - objc_setAssociatedObject(self, @selector(__nodeController), nodeController, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (void)__setNodeControllerWeak:(ASNodeController *)nodeController -{ - // Associated objects don't support weak references. Since assign can become a dangling pointer, use ASWeakProxy. - ASWeakProxy *nodeControllerProxy = [ASWeakProxy weakProxyWithTarget:nodeController]; - objc_setAssociatedObject(self, @selector(__nodeController), nodeControllerProxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -@end - @implementation ASDisplayNode (ASNodeController) - (ASNodeController *)nodeController { - return self.__nodeController; + return _weakNodeController ?: _strongNodeController; } @end diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 3c7c2e887..5e0e1d36e 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -34,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; @class _ASPendingState; +@class ASNodeController; struct ASDisplayNodeFlags; BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); @@ -92,6 +93,8 @@ AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimest ASInterfaceState _pendingInterfaceState; UIView *_view; CALayer *_layer; + ASNodeController *_strongNodeController; + __weak ASNodeController *_weakNodeController; std::atomic _atomicFlags; From c5b1d09b499c168c91b59384330d5a8e6a1749a8 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 3 Aug 2018 16:35:24 -0700 Subject: [PATCH 08/15] Remove direct ivar access on non-self object to fix mocking case #trivial (#1066) * Remove direct ivar access on non-self object to prevent issues when the object is actually a mock * Comment * Remove lock to avoid deadlock risk --- Source/ASDisplayNode.mm | 13 +++++++++++++ Source/ASNodeController+Beta.mm | 5 +---- Source/Private/ASDisplayNodeInternal.h | 15 +++++++++++++-- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 347d41c42..0fc41d40a 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -47,6 +47,7 @@ #import #import #import +#import #import #import #import @@ -876,6 +877,18 @@ - (void)setAutomaticallyRelayoutOnLayoutMarginsChanges:(BOOL)flag _automaticallyRelayoutOnLayoutMarginsChanges = flag; } +- (void)__setNodeController:(ASNodeController *)controller +{ + // See docs for why we don't lock. + if (controller.shouldInvertStrongReference) { + _strongNodeController = controller; + _weakNodeController = nil; + } else { + _weakNodeController = controller; + _strongNodeController = nil; + } +} + #pragma mark - UIResponder #define HANDLE_NODE_RESPONDER_METHOD(__sel) \ diff --git a/Source/ASNodeController+Beta.mm b/Source/ASNodeController+Beta.mm index be6da32fe..6dcd1a079 100644 --- a/Source/ASNodeController+Beta.mm +++ b/Source/ASNodeController+Beta.mm @@ -46,17 +46,14 @@ - (void)setupReferencesWithNode:(ASDisplayNode *)node if (_shouldInvertStrongReference) { // The node should own the controller; weak reference from controller to node. _weakNode = node; - node->_strongNodeController = self; - node->_weakNodeController = nil; _strongNode = nil; } else { // The controller should own the node; weak reference from node to controller. _strongNode = node; - node->_weakNodeController = self; - node->_strongNodeController = nil; _weakNode = nil; } + [node __setNodeController:self]; [node addInterfaceStateDelegate:self]; } diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 5e0e1d36e..927513754 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -93,8 +93,6 @@ AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimest ASInterfaceState _pendingInterfaceState; UIView *_view; CALayer *_layer; - ASNodeController *_strongNodeController; - __weak ASNodeController *_weakNodeController; std::atomic _atomicFlags; @@ -138,6 +136,9 @@ AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimest ASDisplayNode * __weak _supernode; NSMutableArray *_subnodes; + ASNodeController *_strongNodeController; + __weak ASNodeController *_weakNodeController; + // Set this to nil whenever you modify _subnodes NSArray *_cachedSubnodes; @@ -276,6 +277,16 @@ AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimest */ - (void)__setNeedsDisplay; +/** + * Setup the node -> controller reference. Strong or weak is based on + * the "shouldInvertStrongReference" property of the controller. + * + * Note: To prevent lock-ordering deadlocks, this method does not take the node's lock. + * In practice, changing the node controller of a node multiple times is not + * supported behavior. + */ +- (void)__setNodeController:(ASNodeController *)controller; + /** * Called whenever the node needs to layout its subnodes and, if it's already loaded, its subviews. Executes the layout pass for the node * From b136e84b4e9f413b501a2cf91377b672b84aedf7 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 4 Aug 2018 07:33:53 -0700 Subject: [PATCH 09/15] Add an experimental framesetter cache in ASTextNode2 (#1063) * Add an experimental framesetter cache in ASTextNode2, and stop keeping framesetters around * Update configuration schema * Fix imports * Fix import again and remove set statement --- CHANGELOG.md | 1 + Schemas/configuration.json | 1 + Source/ASExperimentalFeatures.h | 1 + Source/ASExperimentalFeatures.m | 3 +- .../TextExperiment/Component/ASTextLayout.h | 2 - .../TextExperiment/Component/ASTextLayout.m | 82 +++++++++++++++---- 6 files changed, 73 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64ec5fff1..b62de5359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - Optimize ASDisplayNode -> ASNodeController reference by removing weak proxy and objc associated objects. [Adlai Holler](https://github.com/Adlai-Holler) - Remove CA transaction signpost injection because it causes more transactions and is too chatty. [Adlai Holler](https://github.com/Adlai-Holler) - Optimize display node accessibility by not creating attributed & non-attributed copies of hint, label, and value. [Adlai Holler](https://github.com/Adlai-Holler) +- Add an experimental feature that reuses CTFramesetter objects in ASTextNode2 to improve performance. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.7 diff --git a/Schemas/configuration.json b/Schemas/configuration.json index 71b591729..29c4375a4 100644 --- a/Schemas/configuration.json +++ b/Schemas/configuration.json @@ -21,6 +21,7 @@ "exp_network_image_queue", "exp_dealloc_queue_v2", "exp_collection_teardown", + "exp_framesetter_cache" ] } } diff --git a/Source/ASExperimentalFeatures.h b/Source/ASExperimentalFeatures.h index cc02d8a68..448deb87a 100644 --- a/Source/ASExperimentalFeatures.h +++ b/Source/ASExperimentalFeatures.h @@ -27,6 +27,7 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) { ASExperimentalNetworkImageQueue = 1 << 5, // exp_network_image_queue ASExperimentalDeallocQueue = 1 << 6, // exp_dealloc_queue_v2 ASExperimentalCollectionTeardown = 1 << 7, // exp_collection_teardown + ASExperimentalFramesetterCache = 1 << 8, // exp_framesetter_cache ASExperimentalFeatureAll = 0xFFFFFFFF }; diff --git a/Source/ASExperimentalFeatures.m b/Source/ASExperimentalFeatures.m index dea872b36..5b33fe50b 100644 --- a/Source/ASExperimentalFeatures.m +++ b/Source/ASExperimentalFeatures.m @@ -23,7 +23,8 @@ @"exp_infer_layer_defaults", @"exp_network_image_queue", @"exp_dealloc_queue_v2", - @"exp_collection_teardown"])); + @"exp_collection_teardown", + @"exp_framesetter_cache"])); if (flags == ASExperimentalFeatureAll) { return allNames; diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.h b/Source/Private/TextExperiment/Component/ASTextLayout.h index 544d9f5f3..1a6625a3a 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.h +++ b/Source/Private/TextExperiment/Component/ASTextLayout.h @@ -225,8 +225,6 @@ AS_EXTERN const CGSize ASTextContainerMaxSize; @property (nonatomic, readonly) NSAttributedString *text; ///< The text range in full text @property (nonatomic, readonly) NSRange range; -///< CTFrameSetter -@property (nonatomic, readonly) CTFramesetterRef frameSetter; ///< CTFrame @property (nonatomic, readonly) CTFrameRef frame; ///< Array of `ASTextLine`, no truncated diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.m b/Source/Private/TextExperiment/Component/ASTextLayout.m index d0aceee9a..d3d316d14 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.m +++ b/Source/Private/TextExperiment/Component/ASTextLayout.m @@ -16,11 +16,15 @@ // #import + +#import #import #import #import #import +#import + const CGSize ASTextContainerMaxSize = (CGSize){0x100000, 0x100000}; typedef struct { @@ -320,7 +324,6 @@ @interface ASTextLayout () @property (nonatomic) NSAttributedString *text; @property (nonatomic) NSRange range; -@property (nonatomic) CTFramesetterRef frameSetter; @property (nonatomic) CTFrameRef frame; @property (nonatomic) NSArray *lines; @property (nonatomic) ASTextLine *truncatedLine; @@ -484,10 +487,71 @@ + (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttri frameAttrs[(id)kCTFrameProgressionAttributeName] = @(kCTFrameProgressionRightToLeft); } - // create CoreText objects - ctSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)text); + /* + * Framesetter cache. + * Framesetters can only be used by one thread at a time. + * Create a CFSet with no callbacks (raw pointers) to keep track of which + * framesetters are in use on other threads. If the one for our string is already in use, + * just create a new one. This should be pretty rare. + */ + static pthread_mutex_t busyFramesettersLock = PTHREAD_MUTEX_INITIALIZER; + static NSCache *framesetterCache; + static CFMutableSetRef busyFramesetters; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + if (ASActivateExperimentalFeature(ASExperimentalFramesetterCache)) { + framesetterCache = [[NSCache alloc] init]; + framesetterCache.name = @"org.TextureGroup.Texture.framesetterCache"; + busyFramesetters = CFSetCreateMutable(NULL, 0, NULL); + } + }); + + BOOL haveCached = NO, useCached = NO; + if (framesetterCache) { + // Check if there's one in the cache. + ctSetter = (__bridge_retained CTFramesetterRef)[framesetterCache objectForKey:text]; + + if (ctSetter) { + haveCached = YES; + + // Check-and-set busy on the cached one. + pthread_mutex_lock(&busyFramesettersLock); + BOOL busy = CFSetContainsValue(busyFramesetters, ctSetter); + if (!busy) { + CFSetAddValue(busyFramesetters, ctSetter); + useCached = YES; + } + pthread_mutex_unlock(&busyFramesettersLock); + + // Release if it was busy. + if (busy) { + CFRelease(ctSetter); + ctSetter = NULL; + } + } + } + + // Create a framesetter if needed. + if (!ctSetter) { + ctSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)text); + } + if (!ctSetter) FAIL_AND_RETURN ctFrame = CTFramesetterCreateFrame(ctSetter, ASTextCFRangeFromNSRange(range), cgPath, (CFDictionaryRef)frameAttrs); + + // Return to cache. + if (framesetterCache) { + if (useCached) { + // If reused: mark available. + pthread_mutex_lock(&busyFramesettersLock); + CFSetRemoveValue(busyFramesetters, ctSetter); + pthread_mutex_unlock(&busyFramesettersLock); + } else if (!haveCached) { + // If first framesetter, add to cache. + [framesetterCache setObject:(__bridge id)ctSetter forKey:text]; + } + } + if (!ctFrame) FAIL_AND_RETURN lines = [NSMutableArray new]; ctLines = CTFrameGetLines(ctFrame); @@ -857,8 +921,7 @@ + (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttri if (attachments.count == 0) { attachments = attachmentRanges = attachmentRects = nil; } - - layout.frameSetter = ctSetter; + layout.frame = ctFrame; layout.lines = lines; layout.truncatedLine = truncatedLine; @@ -903,14 +966,6 @@ + (NSArray *)layoutWithContainers:(NSArray *)containers text:(NSAttributedString return layouts; } -- (void)setFrameSetter:(CTFramesetterRef)frameSetter { - if (_frameSetter != frameSetter) { - if (frameSetter) CFRetain(frameSetter); - if (_frameSetter) CFRelease(_frameSetter); - _frameSetter = frameSetter; - } -} - - (void)setFrame:(CTFrameRef)frame { if (_frame != frame) { if (frame) CFRetain(frame); @@ -920,7 +975,6 @@ - (void)setFrame:(CTFrameRef)frame { } - (void)dealloc { - if (_frameSetter) CFRelease(_frameSetter); if (_frame) CFRelease(_frame); if (_lineRowsIndex) free(_lineRowsIndex); if (_lineRowsEdge) free(_lineRowsEdge); From 847884a7b463aede869d3d850fd4548d8f817073 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sun, 5 Aug 2018 08:20:20 -0700 Subject: [PATCH 10/15] Add NS_DESIGNATED_INITIALIZER to ASViewController initWithNode: (#1054) * Add NS_DESIGNATED_INITIALIZER to ASViewController initWithNode: * Add changelog --- CHANGELOG.md | 2 +- Source/ASViewController.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b62de5359..96a8c1b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ - Remove CA transaction signpost injection because it causes more transactions and is too chatty. [Adlai Holler](https://github.com/Adlai-Holler) - Optimize display node accessibility by not creating attributed & non-attributed copies of hint, label, and value. [Adlai Holler](https://github.com/Adlai-Holler) - Add an experimental feature that reuses CTFramesetter objects in ASTextNode2 to improve performance. [Adlai Holler](https://github.com/Adlai-Holler) - +- Add NS_DESIGNATED_INITIALIZER to ASViewController initWithNode: [Michael Schneider](https://github.com/maicki) [#1054](https://github.com/TextureGroup/Texture/pull/1054) ## 2.7 - Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877) diff --git a/Source/ASViewController.h b/Source/ASViewController.h index b04137cc0..9f1e78dee 100644 --- a/Source/ASViewController.h +++ b/Source/ASViewController.h @@ -44,7 +44,7 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C * * @see ASVisibilityDepth */ -- (instancetype)initWithNode:(DisplayNodeType)node; +- (instancetype)initWithNode:(DisplayNodeType)node NS_DESIGNATED_INITIALIZER; NS_ASSUME_NONNULL_END From 2bb216b02e77440a27bad9ea1b44513f1c0996a0 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 5 Aug 2018 17:24:13 -0700 Subject: [PATCH 11/15] Readability improvements in ASDataController #trivial (#1067) --- Source/Details/ASDataController.mm | 59 ++++++++++++------------------ 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index 05e266a7d..9ecfe2dc8 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -149,14 +149,13 @@ - (void)setLayoutDelegate:(id)layoutDelegate #pragma mark - Cell Layout -- (void)_allocateNodesFromElements:(NSArray *)elements completion:(ASDataControllerCompletionBlock)completionHandler +- (void)_allocateNodesFromElements:(NSArray *)elements { ASSERT_ON_EDITING_QUEUE; NSUInteger nodeCount = elements.count; __weak id weakDataSource = _dataSource; if (nodeCount == 0 || weakDataSource == nil) { - completionHandler(); return; } @@ -165,30 +164,23 @@ - (void)_allocateNodesFromElements:(NSArray *)elements co { as_activity_create_for_scope("Data controller batch"); - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + // TODO: Should we use USER_INITIATED here since the user is probably waiting? + dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0); ASDispatchApply(nodeCount, queue, 0, ^(size_t i) { - __strong id strongDataSource = weakDataSource; - if (strongDataSource == nil) { + if (!weakDataSource) { return; } - // Allocate the node. - ASCollectionElement *context = elements[i]; - ASCellNode *node = context.node; - if (node == nil) { - ASDisplayNodeAssertNotNil(node, @"Node block created nil node; %@, %@", self, strongDataSource); - node = [[ASCellNode alloc] init]; // Fallback to avoid crash for production apps. - } - + unowned ASCollectionElement *element = elements[i]; + unowned ASCellNode *node = element.node; // Layout the node if the size range is valid. - ASSizeRange sizeRange = context.constrainedSize; + ASSizeRange sizeRange = element.constrainedSize; if (ASSizeRangeHasSignificantArea(sizeRange)) { [self _layoutNode:node withConstrainedSize:sizeRange]; } }); } - completionHandler(); ASSignpostEndCustom(ASSignpostDataControllerBatch, self, 0, (weakDataSource != nil ? ASSignpostColorDefault : ASSignpostColorRed)); } @@ -648,28 +640,9 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet __block __unused os_activity_scope_state_s preparationScope = {}; // unused if deployment target < iOS10 as_activity_scope_enter(as_activity_create("Prepare nodes for collection update", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT), &preparationScope); - dispatch_block_t completion = ^() { - [_mainSerialQueue performBlockOnMainThread:^{ - as_activity_scope_leave(&preparationScope); - // Step 4: Inform the delegate - [_delegate dataController:self updateWithChangeSet:changeSet updates:^{ - // Step 5: Deploy the new data as "completed" - // - // Note that since the backing collection view might be busy responding to user events (e.g scrolling), - // it will not consume the batch update blocks immediately. - // As a result, in a short intermidate time, the view will still be relying on the old data source state. - // Thus, we can't just swap the new map immediately before step 4, but until this update block is executed. - // (https://github.com/TextureGroup/Texture/issues/378) - self.visibleMap = newMap; - }]; - }]; - --_editingTransactionGroupCount; - }; - // Step 3: Call the layout delegate if possible. Otherwise, allocate and layout all elements if (canDelegate) { [layoutDelegateClass calculateLayoutWithContext:layoutContext]; - completion(); } else { let elementsToProcess = [[NSMutableArray alloc] init]; for (ASCollectionElement *element in newMap) { @@ -682,8 +655,24 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet [elementsToProcess addObject:element]; } } - [self _allocateNodesFromElements:elementsToProcess completion:completion]; + [self _allocateNodesFromElements:elementsToProcess]; } + + // Step 4: Inform the delegate on main thread + [_mainSerialQueue performBlockOnMainThread:^{ + as_activity_scope_leave(&preparationScope); + [_delegate dataController:self updateWithChangeSet:changeSet updates:^{ + // Step 5: Deploy the new data as "completed" + // + // Note that since the backing collection view might be busy responding to user events (e.g scrolling), + // it will not consume the batch update blocks immediately. + // As a result, in a short intermidate time, the view will still be relying on the old data source state. + // Thus, we can't just swap the new map immediately before step 4, but until this update block is executed. + // (https://github.com/TextureGroup/Texture/issues/378) + self.visibleMap = newMap; + }]; + }]; + --_editingTransactionGroupCount; }); if (_usesSynchronousDataLoading) { From 03e6ce09164407dbfd81a9c407724f4ebc73bdbf Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 7 Aug 2018 08:32:43 -0700 Subject: [PATCH 12/15] Reduce copying in ASTextNode2 stack (#1065) * Remove copying in text stack, make text container have an optional immutable mode * Changelog * Comment * Update CHANGELOG.md * Use new name * Import header --- CHANGELOG.md | 1 + Source/ASTextNode2.mm | 18 +++++--- .../TextExperiment/Component/ASTextLayout.h | 3 ++ .../TextExperiment/Component/ASTextLayout.m | 45 +++++++++++++------ 4 files changed, 48 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96a8c1b2d..9ec23015b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - Optimize display node accessibility by not creating attributed & non-attributed copies of hint, label, and value. [Adlai Holler](https://github.com/Adlai-Holler) - Add an experimental feature that reuses CTFramesetter objects in ASTextNode2 to improve performance. [Adlai Holler](https://github.com/Adlai-Holler) - Add NS_DESIGNATED_INITIALIZER to ASViewController initWithNode: [Michael Schneider](https://github.com/maicki) [#1054](https://github.com/TextureGroup/Texture/pull/1054) +- Optimize text stack by removing unneeded copying. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.7 - Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877) diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index 7d3d8d7b3..3ab1d2f35 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -236,12 +236,17 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize ASLockScopeSelf(); - ASTextContainer *container = [_textContainer copy]; - NSAttributedString *attributedText = self.attributedText; - container.size = constrainedSize; + ASTextContainer *container; + if (!CGSizeEqualToSize(container.size, constrainedSize)) { + container = [_textContainer copy]; + container.size = constrainedSize; + [container makeImmutable]; + } else { + container = _textContainer; + } [self _ensureTruncationText]; - NSMutableAttributedString *mutableText = [attributedText mutableCopy]; + NSMutableAttributedString *mutableText = [_attributedText mutableCopy]; [self prepareAttributedString:mutableText]; ASTextLayout *layout = [ASTextNode2 compatibleLayoutWithContainer:container text:mutableText]; @@ -365,9 +370,12 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { ASLockScopeSelf(); [self _ensureTruncationText]; + + // Unlike layout, here we must copy the container since drawing is asynchronous. ASTextContainer *copiedContainer = [_textContainer copy]; copiedContainer.size = self.bounds.size; - NSMutableAttributedString *mutableText = [self.attributedText mutableCopy] ?: [[NSMutableAttributedString alloc] init]; + [copiedContainer makeImmutable]; + NSMutableAttributedString *mutableText = [_attributedText mutableCopy] ?: [[NSMutableAttributedString alloc] init]; [self prepareAttributedString:mutableText]; diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.h b/Source/Private/TextExperiment/Component/ASTextLayout.h index 1a6625a3a..c78f4271f 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.h +++ b/Source/Private/TextExperiment/Component/ASTextLayout.h @@ -67,6 +67,9 @@ AS_EXTERN const CGSize ASTextContainerMaxSize; /// Creates a container with the specified path. @param path The path. + (instancetype)containerWithPath:(nullable UIBezierPath *)path NS_RETURNS_RETAINED; +/// Mark this immutable, so you get free copies going forward. +- (void)makeImmutable; + /// The constrained size. (if the size is larger than ASTextContainerMaxSize, it will be clipped) @property CGSize size; diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.m b/Source/Private/TextExperiment/Component/ASTextLayout.m index d3d316d14..4b3ba691f 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.m +++ b/Source/Private/TextExperiment/Component/ASTextLayout.m @@ -17,6 +17,7 @@ #import +#import #import #import #import @@ -134,26 +135,36 @@ - (instancetype)init { return self; } -- (id)copyWithZone:(NSZone *)zone { - ASTextContainer *one = [self.class new]; +- (id)copyForced:(BOOL)forceCopy +{ dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); + if (_readonly && !forceCopy) { + dispatch_semaphore_signal(_lock); + return self; + } + + ASTextContainer *one = [self.class new]; one->_size = _size; one->_insets = _insets; one->_path = _path; - one->_exclusionPaths = _exclusionPaths.copy; + one->_exclusionPaths = [_exclusionPaths copy]; one->_pathFillEvenOdd = _pathFillEvenOdd; one->_pathLineWidth = _pathLineWidth; one->_verticalForm = _verticalForm; one->_maximumNumberOfRows = _maximumNumberOfRows; one->_truncationType = _truncationType; - one->_truncationToken = _truncationToken.copy; + one->_truncationToken = [_truncationToken copy]; one->_linePositionModifier = [(NSObject *)_linePositionModifier copy]; dispatch_semaphore_signal(_lock); return one; } -- (id)mutableCopyWithZone:(nullable NSZone *)zone { - return [self copyWithZone:zone]; +- (id)copyWithZone:(NSZone *)zone { + return [self copyForced:NO]; +} + +- (id)mutableCopyWithZone:(NSZone *)zone { + return [self copyForced:YES]; } - (void)encodeWithCoder:(NSCoder *)aCoder { @@ -189,18 +200,25 @@ - (id)initWithCoder:(NSCoder *)aDecoder { return self; } +- (void)makeImmutable +{ + dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); + _readonly = YES; + dispatch_semaphore_signal(_lock); +} + #define Getter(...) \ dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ __VA_ARGS__; \ dispatch_semaphore_signal(_lock); #define Setter(...) \ -if (_readonly) { \ -@throw [NSException exceptionWithName:NSInternalInconsistencyException \ -reason:@"Cannot change the property of the 'container' in 'ASTextLayout'." userInfo:nil]; \ -return; \ -} \ dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ +if (__builtin_expect(_readonly, NO)) { \ + ASDisplayNodeFailAssert(@"Attempt to modify immutable text container."); \ + dispatch_semaphore_signal(_lock); \ + return; \ +} \ __VA_ARGS__; \ dispatch_semaphore_signal(_lock); @@ -407,11 +425,10 @@ + (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttri if (lineRowsIndex) free(lineRowsIndex); \ return nil; } - text = text.mutableCopy; - container = container.copy; + container = [container copy]; if (!text || !container) return nil; if (range.location + range.length > text.length) return nil; - container->_readonly = YES; + [container makeImmutable]; maximumNumberOfRows = container.maximumNumberOfRows; // It may use larger constraint size when create CTFrame with From 5f912d1cd1259503dab392658fcaaa8431bb10cf Mon Sep 17 00:00:00 2001 From: Eric Jensen Date: Wed, 15 Aug 2018 11:08:32 -0700 Subject: [PATCH 13/15] Remove double scaling of lineHeightMultiple & paragraphSpacing attributes in ASTextKitFontSizeAdjuster (#1056) --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 ++- CHANGELOG.md | 1 + Source/TextKit/ASTextKitFontSizeAdjuster.mm | 4 +- Tests/ASTextKitFontSizeAdjusterTests.mm | 51 ++++++++++++++++++ Tests/ASTextNodeSnapshotTests.m | 16 ++++++ .../testFontPointSizeScaling@2x.png | Bin 0 -> 1770 bytes .../testFontPointSizeScaling@2x.png | Bin 0 -> 1770 bytes 7 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 Tests/ASTextKitFontSizeAdjusterTests.mm create mode 100644 Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testFontPointSizeScaling@2x.png create mode 100644 Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testFontPointSizeScaling@2x.png diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 68c61c7e9..30f3f6e94 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -228,6 +228,7 @@ ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */; }; ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */; }; ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */; }; + AE440175210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE440174210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm */; }; AE6987C11DD04E1000B9E458 /* ASPagerNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AE6987C01DD04E1000B9E458 /* ASPagerNodeTests.m */; }; AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */; }; B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -814,6 +815,7 @@ ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASOverlayLayoutSpecSnapshotTests.mm; sourceTree = ""; }; ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRatioLayoutSpecSnapshotTests.mm; sourceTree = ""; }; ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackLayoutSpecSnapshotTests.mm; sourceTree = ""; }; + AE440174210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitFontSizeAdjusterTests.mm; sourceTree = ""; }; AE6987C01DD04E1000B9E458 /* ASPagerNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPagerNodeTests.m; sourceTree = ""; }; AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDefaultPlayButton.h; sourceTree = ""; }; AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDefaultPlayButton.m; sourceTree = ""; }; @@ -1253,7 +1255,6 @@ 058D09C5195D04C000B7D73C /* Tests */ = { isa = PBXGroup; children = ( - CC35CEC520DD87280006448D /* ASCollectionsTests.m */, DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, AC026B571BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.m */, 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */, @@ -1264,6 +1265,7 @@ CC051F1E1D7A286A006434CB /* ASCALayerTests.m */, ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */, CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */, + CC35CEC520DD87280006448D /* ASCollectionsTests.m */, 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */, 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */, CCEDDDD8200C518800FFCD0A /* ASConfigurationTests.m */, @@ -1314,8 +1316,10 @@ 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */, CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */, 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */, + AE440174210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm */, 254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */, 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */, + C057D9BC20B5453D00FC9112 /* ASTextNode2SnapshotTests.m */, CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.m */, 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */, 058D0A36195D057000B7D73C /* ASTextNodeTests.m */, @@ -1325,7 +1329,6 @@ 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */, CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */, AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */, - C057D9BC20B5453D00FC9112 /* ASTextNode2SnapshotTests.m */, CCA221D21D6FA7EF00AF6A0F /* ASViewControllerTests.m */, 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */, CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */, @@ -2265,6 +2268,7 @@ buildActionMask = 2147483647; files = ( CCEDDDD9200C518800FFCD0A /* ASConfigurationTests.m in Sources */, + AE440175210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm in Sources */, E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.m in Sources */, 4496D0731FA9EA6B001CC8D5 /* ASTraitCollectionTests.m in Sources */, 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec23015b..01ecb97ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Add an experimental feature that reuses CTFramesetter objects in ASTextNode2 to improve performance. [Adlai Holler](https://github.com/Adlai-Holler) - Add NS_DESIGNATED_INITIALIZER to ASViewController initWithNode: [Michael Schneider](https://github.com/maicki) [#1054](https://github.com/TextureGroup/Texture/pull/1054) - Optimize text stack by removing unneeded copying. [Adlai Holler](https://github.com/Adlai-Holler) +- Remove double scaling of lineHeightMultiple & paragraphSpacing attributes in ASTextKitFontSizeAdjuster. [Eric Jensen](https://github.com/ejensen) ## 2.7 - Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877) diff --git a/Source/TextKit/ASTextKitFontSizeAdjuster.mm b/Source/TextKit/ASTextKitFontSizeAdjuster.mm index dce59597f..13d3e9296 100644 --- a/Source/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/Source/TextKit/ASTextKitFontSizeAdjuster.mm @@ -87,8 +87,6 @@ + (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString paragraphStyle.tailIndent = (paragraphStyle.tailIndent * scaleFactor); paragraphStyle.minimumLineHeight = (paragraphStyle.minimumLineHeight * scaleFactor); paragraphStyle.maximumLineHeight = (paragraphStyle.maximumLineHeight * scaleFactor); - paragraphStyle.lineHeightMultiple = (paragraphStyle.lineHeightMultiple * scaleFactor); - paragraphStyle.paragraphSpacing = (paragraphStyle.paragraphSpacing * scaleFactor); [attrString removeAttribute:NSParagraphStyleAttributeName range:range]; [attrString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; @@ -214,7 +212,7 @@ - (CGFloat)scaleFactor if (longestWordFits == NO) { // we need to check the longest word to make sure it fits - longestWordFits = std::ceil((longestWordSize.width * adjustedScale) <= _constrainedSize.width); + longestWordFits = std::ceil((longestWordSize.width * adjustedScale) <= _constrainedSize.width); } // if the longest word fits, go ahead and check max line and height. If it didn't fit continue to the next scale factor diff --git a/Tests/ASTextKitFontSizeAdjusterTests.mm b/Tests/ASTextKitFontSizeAdjusterTests.mm new file mode 100644 index 000000000..937028d3f --- /dev/null +++ b/Tests/ASTextKitFontSizeAdjusterTests.mm @@ -0,0 +1,51 @@ +// +// ASTextKitFontSizeAdjusterTests.mm +// Texture +// +// Copyright (c) 2018-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 +#import + +@interface ASFontSizeAdjusterTests : XCTestCase + +@end + +@implementation ASFontSizeAdjusterTests + +- (void)testFontSizeAdjusterAttributes +{ + NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; + paragraphStyle.lineHeightMultiple = 2.0; + paragraphStyle.lineSpacing = 2.0; + paragraphStyle.paragraphSpacing = 4.0; + paragraphStyle.firstLineHeadIndent = 6.0; + paragraphStyle.headIndent = 8.0; + paragraphStyle.tailIndent = 10.0; + paragraphStyle.minimumLineHeight = 12.0; + paragraphStyle.maximumLineHeight = 14.0; + + NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet" + attributes:@{ NSParagraphStyleAttributeName: paragraphStyle }]; + + [ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:string withScaleFactor:0.5]; + + NSParagraphStyle *adjustedParagraphStyle = [string attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:nil]; + + XCTAssertEqual(adjustedParagraphStyle.lineHeightMultiple, 2.0); + XCTAssertEqual(adjustedParagraphStyle.lineSpacing, 1.0); + XCTAssertEqual(adjustedParagraphStyle.paragraphSpacing, 2.0); + XCTAssertEqual(adjustedParagraphStyle.firstLineHeadIndent, 3.0); + XCTAssertEqual(adjustedParagraphStyle.headIndent, 4.0); + XCTAssertEqual(adjustedParagraphStyle.tailIndent, 5.0); + XCTAssertEqual(adjustedParagraphStyle.minimumLineHeight, 6.0); + XCTAssertEqual(adjustedParagraphStyle.maximumLineHeight, 7.0); +} + +@end diff --git a/Tests/ASTextNodeSnapshotTests.m b/Tests/ASTextNodeSnapshotTests.m index c81f5241a..1d705f77b 100644 --- a/Tests/ASTextNodeSnapshotTests.m +++ b/Tests/ASTextNodeSnapshotTests.m @@ -137,4 +137,20 @@ - (void)DISABLED_testThatTruncationTokenAttributesPrecedeThoseInheritedFromTextW ASSnapshotVerifyNode(textNode, nil); } +- (void)testFontPointSizeScaling +{ + NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; + paragraphStyle.lineHeightMultiple = 0.5; + paragraphStyle.lineSpacing = 2.0; + + ASTextNode *textNode = [[ASTextNode alloc] init]; + textNode.style.maxSize = CGSizeMake(60, 80); + textNode.pointSizeScaleFactors = @[@0.5]; + textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is an important thing" + attributes:@{ NSParagraphStyleAttributeName: paragraphStyle }]; + + ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); + ASSnapshotVerifyNode(textNode, nil); +} + @end diff --git a/Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testFontPointSizeScaling@2x.png b/Tests/ReferenceImages_64/ASTextNodeSnapshotTests/testFontPointSizeScaling@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..111bd5004842fec4a3eab9adc46b4b649de4286d GIT binary patch literal 1770 zcmbW2c{tRI8pmfEeq%7BXzWHvLn*REp)qELj578ma(<3#EXQ%O7o9VVt*KimLzWuM zSTad+Y*((IuB;g;OA#YvZ{+0KqBA|u{pbFB-{<+hpYQwr@%`(4p7(~clRa8mRT>6^ zp&ibUUBwI)i!23*dn%_IiZ zzb04LSwqfwoN>H(RLZ2XwtdL;Eq{QzPgnngyLO7bK6UrCZN>27@aBR=pCLgINE3+W zo9l-~@z%53Cm#ydx2R< zl$ff8ovLYgEOc;Ak|~Uj!-m-Sm}Sb~$x1ZcFpLv}P;_knqur?Hmi|7E#)r^|bnGGA zYE<`jtIq|`b|&_-3mZ%c{k}f$5eHhT=YPBrTOR4?-ox=Yw!SWi$Z{)=APitvjvFK^ zay~-m$&F|OnAZ?eNX|tP&vhk8ba|L3HEFrv>t759m^Fmt+vJtHZK4vS?zEq3b{LO; zF2^&%u4%La+IN=KnaZZ5fk;^xPXo{o zvc6wcg%B21We-lwTRBAYM zf&|i`Q&BE2k!mDr1fEyr!B^cJ12(1VAi=pN`Auvp&6F zJ~*_ygB-o)@M4J;HTsim*3tqH$xEO!ytX_ayHz+X0>5)V(q35^{)_5!zO1yi_JYY$4fT?Cz9ymltv z`+XKZ>~c4=Y-oxYs%{ZD_}e@Ylwk0C+>qx7f$;`vLz{0jM$tTR5AGyu&z=fKejGZ>Yw!?N1NYOVJRT z#4XKqUjxQr%Mk^?$v;bP4xCj>D;;|}a&}-%NX5yKIBkQ4`=XOpi|P_P&fx}^;F_*< z3{Y4e!@PbSUz>bX2=PS+G)!d^a_00zv5HZNgjVFy#D%>RK$%O6io4|qW}LMBpG1YW zFE3ktIa|b{QK8{iGd(njlAwpi<2UNB$hQ2xBK3;13sc~<@iM=F)+*0Hg-w~_)kZFR z@Ynt#A}zBK+XM+XZcIO{4g{)LFu7UKYM#H4Rfqs;a6uD*l<`V_i7jEkmT6`=#)y)) zQqP`(y3PBHw?gdu4UDt3y-S!BE?_^a$HW#~tfumBU9_=dz^`0tpJBgI`Xy2gMFkea zR6n)-L#U`E$My}(7>x;bw)e`V(q6=UMSX%l*gUV&BGDqlKa6(rp4- k8E^z3t84`RKPFB^Ij{Y)L_>GrcX24pf#O82vIUd=2G<-I2mk;8 literal 0 HcmV?d00001 diff --git a/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testFontPointSizeScaling@2x.png b/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testFontPointSizeScaling@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..111bd5004842fec4a3eab9adc46b4b649de4286d GIT binary patch literal 1770 zcmbW2c{tRI8pmfEeq%7BXzWHvLn*REp)qELj578ma(<3#EXQ%O7o9VVt*KimLzWuM zSTad+Y*((IuB;g;OA#YvZ{+0KqBA|u{pbFB-{<+hpYQwr@%`(4p7(~clRa8mRT>6^ zp&ibUUBwI)i!23*dn%_IiZ zzb04LSwqfwoN>H(RLZ2XwtdL;Eq{QzPgnngyLO7bK6UrCZN>27@aBR=pCLgINE3+W zo9l-~@z%53Cm#ydx2R< zl$ff8ovLYgEOc;Ak|~Uj!-m-Sm}Sb~$x1ZcFpLv}P;_knqur?Hmi|7E#)r^|bnGGA zYE<`jtIq|`b|&_-3mZ%c{k}f$5eHhT=YPBrTOR4?-ox=Yw!SWi$Z{)=APitvjvFK^ zay~-m$&F|OnAZ?eNX|tP&vhk8ba|L3HEFrv>t759m^Fmt+vJtHZK4vS?zEq3b{LO; zF2^&%u4%La+IN=KnaZZ5fk;^xPXo{o zvc6wcg%B21We-lwTRBAYM zf&|i`Q&BE2k!mDr1fEyr!B^cJ12(1VAi=pN`Auvp&6F zJ~*_ygB-o)@M4J;HTsim*3tqH$xEO!ytX_ayHz+X0>5)V(q35^{)_5!zO1yi_JYY$4fT?Cz9ymltv z`+XKZ>~c4=Y-oxYs%{ZD_}e@Ylwk0C+>qx7f$;`vLz{0jM$tTR5AGyu&z=fKejGZ>Yw!?N1NYOVJRT z#4XKqUjxQr%Mk^?$v;bP4xCj>D;;|}a&}-%NX5yKIBkQ4`=XOpi|P_P&fx}^;F_*< z3{Y4e!@PbSUz>bX2=PS+G)!d^a_00zv5HZNgjVFy#D%>RK$%O6io4|qW}LMBpG1YW zFE3ktIa|b{QK8{iGd(njlAwpi<2UNB$hQ2xBK3;13sc~<@iM=F)+*0Hg-w~_)kZFR z@Ynt#A}zBK+XM+XZcIO{4g{)LFu7UKYM#H4Rfqs;a6uD*l<`V_i7jEkmT6`=#)y)) zQqP`(y3PBHw?gdu4UDt3y-S!BE?_^a$HW#~tfumBU9_=dz^`0tpJBgI`Xy2gMFkea zR6n)-L#U`E$My}(7>x;bw)e`V(q6=UMSX%l*gUV&BGDqlKa6(rp4- k8E^z3t84`RKPFB^Ij{Y)L_>GrcX24pf#O82vIUd=2G<-I2mk;8 literal 0 HcmV?d00001 From 022b6b77854d77b8bc82d548f52eb303e29408dd Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 20 Aug 2018 23:26:14 +0200 Subject: [PATCH 14/15] Fix multiple documentation issues #trivial (#1073) --- docs/_data/nav_docs.yml | 1 - docs/_docs/asrunloopqueue.md | 2 +- docs/_docs/automatic-layout-containers.md | 8 +-- .../layout2-layout-element-properties.md | 70 +++++++++---------- docs/_docs/team.md | 2 +- 5 files changed, 41 insertions(+), 42 deletions(-) diff --git a/docs/_data/nav_docs.yml b/docs/_data/nav_docs.yml index 91e76d343..6b4b7b807 100755 --- a/docs/_data/nav_docs.yml +++ b/docs/_data/nav_docs.yml @@ -44,7 +44,6 @@ - title: Advanced Technologies items: - asvisibility - - asenvironment - asrunloopqueue - title: Node Containers items: diff --git a/docs/_docs/asrunloopqueue.md b/docs/_docs/asrunloopqueue.md index cd2e978c2..f40f532ed 100755 --- a/docs/_docs/asrunloopqueue.md +++ b/docs/_docs/asrunloopqueue.md @@ -2,7 +2,7 @@ title: ASRunLoopQueue layout: docs permalink: /docs/asrunloopqueue.html -prevPage: asenvironment.html +prevPage: asvisibility.html --- Even with main thread work, Texture is able to dramatically reduce its impact on the user experience by way of the rather amazing ASRunLoopQueue. diff --git a/docs/_docs/automatic-layout-containers.md b/docs/_docs/automatic-layout-containers.md index 8bd56d205..b612b37c7 100755 --- a/docs/_docs/automatic-layout-containers.md +++ b/docs/_docs/automatic-layout-containers.md @@ -16,7 +16,7 @@ Both nodes and layoutSpecs conform to the `` protocol. Any `ASLay ### Single Child layoutSpecs - +
@@ -39,10 +39,10 @@ Both nodes and layoutSpecs conform to the `` protocol. Any `ASLay - + - + @@ -70,7 +70,7 @@ The following layoutSpecs may contain one or more children.
LayoutSpec Description
ASRatioLayoutSpec

Lays out a component at a fixed aspect ratio (which can be scaled).

This spec is great for objects that do not have an intrinisic size, such as ASNetworkImageNodes and ASVideoNodes.

Lays out a component at a fixed aspect ratio (which can be scaled).

This spec is great for objects that do not have an intrinisic size, such as ASNetworkImageNodes and ASVideoNodes.

ASRelativeLayoutSpecASRelativeLayoutSpec

Lays out a component and positions it within the layout bounds according to vertical and horizontal positional specifiers. Similar to the “9-part” image areas, a child can be positioned at any of the 4 corners, or the middle of any of the 4 edges, as well as the center.

-# ASLayoutable Properties +### ASLayoutable Properties The following properties can be applied to both nodes _and_ `layoutSpec`s; both conform to the `ASLayoutable` protocol. diff --git a/docs/_docs/layout2-layout-element-properties.md b/docs/_docs/layout2-layout-element-properties.md index 21ed56b11..18735a8cd 100755 --- a/docs/_docs/layout2-layout-element-properties.md +++ b/docs/_docs/layout2-layout-element-properties.md @@ -22,42 +22,42 @@ nextPage: layout2-api-sizing.html Description - `CGFloat .style.spacingBefore` + CGFloat .style.spacingBefore Additional space to place before this object in the stacking direction. - `CGFloat .style.spacingAfter` + CGFloat .style.spacingAfter Additional space to place after this object in the stacking direction. - `CGFloat .style.flexGrow` + CGFloat .style.flexGrow If the sum of childrens' stack dimensions is less than the minimum size, should this object grow? - `CGFloat .style.flexShrink` + CGFloat .style.flexShrink If the sum of childrens' stack dimensions is greater than the maximum size, should this object shrink? - `ASDimension .style.flexBasis` - Specifies the initial size for this object, in the stack dimension (horizontal or vertical), before the `flexGrow` / `flexShrink` properties are applied and the remaining space is distributed. + ASDimension .style.flexBasis + Specifies the initial size for this object, in the stack dimension (horizontal or vertical), before the flexGrow / flexShrink properties are applied and the remaining space is distributed. - `ASStackLayoutAlignSelf .style.alignSelf` + ASStackLayoutAlignSelf .style.alignSelf Orientation of the object along cross axis, overriding alignItems. Options include:
    -
  • `ASStackLayoutAlignSelfAuto`
  • -
  • `ASStackLayoutAlignSelfStart`
  • -
  • `ASStackLayoutAlignSelfEnd`
  • -
  • `ASStackLayoutAlignSelfCenter`
  • -
  • `ASStackLayoutAlignSelfStretch`
  • +
  • ASStackLayoutAlignSelfAuto
  • +
  • ASStackLayoutAlignSelfStart
  • +
  • ASStackLayoutAlignSelfEnd
  • +
  • ASStackLayoutAlignSelfCenter
  • +
  • ASStackLayoutAlignSelfStretch
- `CGFloat .style.ascender` + CGFloat .style.ascender Used for baseline alignment. The distance from the top of the object to its baseline. - `CGFloat .style.descender` + CGFloat .style.descender Used for baseline alignment. The distance from the baseline of the object to its bottom. @@ -75,8 +75,8 @@ nextPage: layout2-api-sizing.html Description - `CGPoint .style.layoutPosition` - The `CGPoint` position of this object within its `ASAbsoluteLayoutSpec` parent spec. + CGPoint .style.layoutPosition + The CGPoint position of this object within its ASAbsoluteLayoutSpec parent spec. @@ -92,55 +92,55 @@ nextPage: layout2-api-sizing.html Description - `ASDimension .style.width` - The `width` property specifies the width of the content area of an `ASLayoutElement`. The `minWidth` and `maxWidth` properties override `width`. Defaults to `ASDimensionAuto`. + ASDimension .style.width + The width property specifies the width of the content area of an ASLayoutElement. The minWidth and maxWidth properties override width. Defaults to ASDimensionAuto. - `ASDimension .style.height` - The `height` property specifies the height of the content area of an `ASLayoutElement`. The `minHeight` and `maxHeight` properties override `height`. Defaults to `ASDimensionAuto`. + ASDimension .style.height + The height property specifies the height of the content area of an ASLayoutElement. The minHeight and maxHeight properties override height. Defaults to ASDimensionAuto. - `ASDimension .style.minWidth` - The `minWidth` property is used to set the minimum width of a given element. It prevents the used value of the `width` property from becoming smaller than the value specified for `minWidth`. The value of `minWidth` overrides both `maxWidth` and `width`. Defaults to `ASDimensionAuto`. + ASDimension .style.minWidth + The minWidth property is used to set the minimum width of a given element. It prevents the used value of the width property from becoming smaller than the value specified for minWidth. The value of minWidth overrides both maxWidth and width. Defaults to ASDimensionAuto. - `ASDimension .style.maxWidth` - The `maxWidth` property is used to set the maximum width of a given element. It prevents the used value of the `width` property from becoming larger than the value specified for `maxWidth`. The value of `maxWidth` overrides `width`, but `minWidth` overrides `maxWidth`. Defaults to `ASDimensionAuto`. + ASDimension .style.maxWidth + The maxWidth property is used to set the maximum width of a given element. It prevents the used value of the width property from becoming larger than the value specified for maxWidth. The value of maxWidth overrides width, but minWidth overrides maxWidth. Defaults to ASDimensionAuto. - `ASDimension .style.minHeight` - The `minHeight` property is used to set the minimum height of a given element. It prevents the used value of the `height` property from becoming smaller than the value specified for `minHeight`. The value of `minHeight` overrides both `maxHeight` and `height`. Defaults to `ASDimensionAuto`. + ASDimension .style.minHeight + The minHeight property is used to set the minimum height of a given element. It prevents the used value of the height property from becoming smaller than the value specified for minHeight. The value of minHeight overrides both maxHeight and height. Defaults to ASDimensionAuto. - `ASDimension .style.maxHeight` - The `maxHeight` property is used to set the maximum height of a given element. It prevents the used value of the `height` property from becoming larger than the value specified for `maxHeight`. The value of `maxHeight` overrides `height`, but `minHeight` overrides `maxHeight`. Defaults to `ASDimensionAuto` + ASDimension .style.maxHeight + The maxHeight property is used to set the maximum height of a given element. It prevents the used value of the height property from becoming larger than the value specified for maxHeight. The value of maxHeight overrides height, but minHeight overrides maxHeight. Defaults to ASDimensionAuto - `CGSize .style.preferredSize` + CGSize .style.preferredSize

Provides a suggested size for a layout element. If the optional minSize or maxSize are provided, and the preferredSize exceeds these, the minSize or maxSize will be enforced. If this optional value is not provided, the layout element’s size will default to it’s intrinsic content size provided calculateSizeThatFits:

This method is optional, but one of either preferredSize or preferredLayoutSize is required for nodes that either have no intrinsic content size or should be laid out at a different size than its intrinsic content size. For example, this property could be set on an ASImageNode to display at a size different from the underlying image size.

Warning: calling the getter when the size's width or height are relative will cause an assert.

- `CGSize .style.minSize` + CGSize .style.minSize

An optional property that provides a minimum size bound for a layout element. If provided, this restriction will always be enforced. If a parent layout element’s minimum size is smaller than its child’s minimum size, the child’s minimum size will be enforced and its size will extend out of the layout spec’s.

For example, if you set a preferred relative width of 50% and a minimum width of 200 points on an element in a full screen container, this would result in a width of 160 points on an iPhone screen. However, since 160 pts is lower than the minimum width of 200 pts, the minimum width would be used.

- `CGSize .style.maxSize` + CGSize .style.maxSize

An optional property that provides a maximum size bound for a layout element. If provided, this restriction will always be enforced. If a child layout element’s maximum size is smaller than its parent, the child’s maximum size will be enforced and its size will extend out of the layout spec’s.

For example, if you set a preferred relative width of 50% and a maximum width of 120 points on an element in a full screen container, this would result in a width of 160 points on an iPhone screen. However, since 160 pts is higher than the maximum width of 120 pts, the maximum width would be used.

- `ASLayoutSize .style.preferredLayoutSize` - Provides a suggested RELATIVE size for a layout element. An ASLayoutSize uses percentages rather than points to specify layout. E.g. width should be 50% of the parent’s width. If the optional minLayoutSize or maxLayoutSize are provided, and the preferredLayoutSize exceeds these, the minLayoutSize or maxLayoutSize will be enforced. If this optional value is not provided, the layout element’s size will default to its intrinsic content size provided `calculateSizeThatFits:` + ASLayoutSize .style.preferredLayoutSize + Provides a suggested RELATIVE size for a layout element. An ASLayoutSize uses percentages rather than points to specify layout. E.g. width should be 50% of the parent’s width. If the optional minLayoutSize or maxLayoutSize are provided, and the preferredLayoutSize exceeds these, the minLayoutSize or maxLayoutSize will be enforced. If this optional value is not provided, the layout element’s size will default to its intrinsic content size provided calculateSizeThatFits: - `ASLayoutSize .style.minLayoutSize` + ASLayoutSize .style.minLayoutSize An optional property that provides a minimum RELATIVE size bound for a layout element. If provided, this restriction will always be enforced. If a parent layout element’s minimum relative size is smaller than its child’s minimum relative size, the child’s minimum relative size will be enforced and its size will extend out of the layout spec’s. - `ASLayoutSize .style.maxLayoutSize` + ASLayoutSize .style.maxLayoutSize An optional property that provides a maximum RELATIVE size bound for a layout element. If provided, this restriction will always be enforced. If a parent layout element’s maximum relative size is smaller than its child’s maximum relative size, the child’s maximum relative size will be enforced and its size will extend out of the layout spec’s. diff --git a/docs/_docs/team.md b/docs/_docs/team.md index 5f6726589..e3a964391 100755 --- a/docs/_docs/team.md +++ b/docs/_docs/team.md @@ -13,7 +13,7 @@ permalink: /docs/team.html -

Michael Schneider (@maicki) is especially passionate about API design and recently led the re-architecture of the layout API for the 2.0 release. As our resident layout expert, Michael volunteers much of his own time to help developers on Texture's public slack channel. Previous, Michael worked on Pocket for iOS, Mac and Chrome and the Instapaper Mac app.

+

Michael Schneider (@maicki) is especially passionate about API design and recently led the re-architecture of the layout API for the 2.0 release. As our resident layout expert, Michael volunteers much of his own time to help developers on Texture's public slack channel. Before he joined Pinterest, Michael worked on Pocket for iOS, Mac and Chrome and Read Later an Instapaper and Pocket Mac app.

From 2e95f5503eed6ac766e2b7a293a33b0c5722bca1 Mon Sep 17 00:00:00 2001 From: JK Junkyu Jeon Date: Tue, 21 Aug 2018 23:54:36 +0900 Subject: [PATCH 15/15] Update showcase to add Wishpoke (#1078) --- docs/showcase.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/showcase.md b/docs/showcase.md index 1eaddbf0d..fed05f3d0 100755 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -256,6 +256,12 @@ permalink: /showcase.html
Apollo for Reddit + + + +
+ Wishpoke +