From 2796c8e189c39d6d92596f3131063b1389a3fb2e Mon Sep 17 00:00:00 2001 From: Sergey Date: Fri, 24 Dec 2021 17:31:35 +0200 Subject: [PATCH] [Accessibility] Ship ASExperimentalDoNotCacheAccessibilityElements (#1888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We did not notice any effect on performance of the Pinterest app by not caching `accessibilityElements` in `_ASDisplayView`. By not caching the elements, we can be sure that the elements will be correct even when nodes change visibility state. There will be a performance impact when voice over is enabled, but providing the correct elements for the current state of a view is more important than performance in this case. https://github.com/TextureGroup/Texture/issues/1853 Remove background deallocation helper code (#1890) * Remove background deallocation helper code Last use removed in Texture with #1840, now PINS no longer uses it either. Less OOMs is so nice. * remove methods from docs Call will / did display node for ASTextNode. Fixes #1680 (#1893) Do not expose tgmath.h to all clients of Texture (#1900) * Do not expose tgmath.h to all clients of Texture - tgmath.h #undef the `log` macro for mathematical reasons. Code that may also use a log name (such as CocoaLumberjack) will get confused by this when they try to use `NS_SWIFT_NAME` with `log` as part of the name. - `ABS` from NSObjCRuntime.h is what is typically used for abs on `CGFloat`. - Note: removing tgmath.h from the Texture umbrella header may expose clients that implicitly depended upon it being imported. Sources may have to be updated after this to `#import ` explicitly. Disable text kit lock (#1910) * Add experiment to disable global textkit lock * Forgot the bang Add an experiment that makes ASDataController to do everything on main thread (#1911) * Add an experiment that makes ASDataController to do everything on main thread Under this experiment, ASDataController will allocate and layout all nodes on the main thread. This helps to avoid deadlocks that would otherwise occur if some of the node allocations or layouts caused ASDataController's background queue to block on main thread. As a bonus, this experiment also helps to measure how much performance wins we get from doing the work off main. * Remove ASSERT_ON_EDITING_QUEUE More on ASDataController's main-thread-only mode (#1915) Follow up on #1911: it's not enough to execute step 3 on the main thread because -_allocateNodesFromElements: uses ASDispatchApply to offload the work to other threads. So this diff adds a flag to tell that method to do everything serially on the calling thread. Fix failing ASConfigurationTests (#1923) * Fix failing ASConfigurationTests * Update configuration.json as well Fix hit point when ASCollectionNode inverted set to true (#1781) * Account for possible inverted transform during hit test When ASCollectionNode has the `inverted` flag set, a transform gets set on the cell node. We need to make sure that we account for that when dealing in the view coordinates. * Store self.node and self.node.view in local variables for better readability. * Add a test for hit testing in an inverted ASCollectionNode Ship ASExperimentalDispatchApply (#1924) Closes #1850. [ASTextNode2] Make some ASTextNode2 layout files public (#1939) * Trying to make ASTextLinePositionModifier public * d’oh * be a little more restrictive on the files we pull into the pod * Never mind, I guess we need all of these * update the project file as well * try this again * I think this will work this time. Set ASTableView isAccessibilityElement, accessibilityElementsHidden properties from its Element's node (#1941) Update CocoaPods to use the CDN instead of the old trunk repo. (#1957) Remove redundant assignment. (#1960) Fix mutation of variable that is never read. (#1961) use https for slack link (#1950) fix missing hidden class (#1952) Fix WKWebView Accessibility (#1955) * Return nil instead of empty array when no accessibility elements are found. Fixes #1954. * Use nullability annotations to fix static analyzer warnings. * Add UI test target. * Add UI test to make sure web view stays accessible. * Revert "Add UI test to make sure web view stays accessible." This reverts commit 00253f49a0af329602b0d9709b58bc92dcd90147. * Revert "Add UI test target." This reverts commit 288b5e0f564ef3ba3fb5568baa832bc03124cdc9. * Add unit test to make sure accessibility elements are correct when a WKWebView is wrapped in an ASDisplayNode. Update asdkGram swift sample to swift version 5.3 (#1962) Fix order-dependent ASTextNodeTests (#1963) Exposes a new option in ASImageDownloaderProtocol to retry image downloads (#1948) * Exposes a new option in ASImageDownloaderProtocol to retry image downloads At the moment the ASBasicImageDownloader does not automatically retry image downloads if the remote host is unreachable. On the contrary the ASPINRemoteImageDownloader automatically retries. Retrying is something that ultimately clients need to be able to control, for example to fail fast to an alternative image rather than keep retrying for more than one minute while not displaying any image. This change exposes a new option in the ASImageDownloaderProtocol to retry image downloads. It also uses this new option in both ASNetworkImageNode and also ASMultiplexImageNode, setting it to YES to preserve the current behaviour. * Fixes a failing test in ASMultiplexImageNodeTests * Fixes ScreenNode.m ScreenNode.m is implementing ASImageDownloaderProtocol and needs to be fixed to reflect changes in the latter. Add experiment to ensure ASCollectionView's range controller updates on changeset updates (#1976) This experiment makes sure a ASCollectionView's `rangeController` updates when a changeset WITH updates is applied. Currently it is possible for nodes inserted into the preload range to not get preloaded when performing a batch update. For example, suppose a collection node has: - Tuning parameters with a preload range of 1 screenful for the given range mode. - Nodes A and B where A is visible and B is off screen. Currently if node B is deleted and a new node C is inserted in its place, node C will not get preloaded until the collection node is scrolled. This is because the preloading mechanism relies on a `setNeedsUpdate` call on the range controller as part of the `-collectionView:willDisplayCell:forItemAtIndexPath:` delegate method when the batch update is submitted. However, in the example outlined above, this sometimes doesn't happen automtically, causing the range update to be delayed until the next the view scrolls. Remove Facebook and shift everything around, add Remix by Buffer (#1978) Expand ASExperimentalRangeUpdateOnChangesetUpdate to ASTableView (#1979) A previous commit (https://github.com/TextureGroup/Texture/commit/8f7444e0ece61d6ab12ecceb590be26d8d7cc99d) aimed to fix a preloading bug for ASCollectionView. This commit expands this fix to ASTableView as the bug occurs there too. Previous commit message for context: This experiment makes sure a ASCollectionView's `rangeController` updates when a changeset WITH updates is applied. Currently it is possible for nodes inserted into the preload range to not get preloaded when performing a batch update. For example, suppose a collection node has: - Tuning parameters with a preload range of 1 screenful for the given range mode. - Nodes A and B where A is visible and B is off screen. Currently if node B is deleted and a new node C is inserted in its place, node C will not get preloaded until the collection node is scrolled. This is because the preloading mechanism relies on a `setNeedsUpdate` call on the range controller as part of the `-collectionView:willDisplayCell:forItemAtIndexPath:` delegate method when the batch update is submitted. However, in the example outlined above, this sometimes doesn't happen automtically, causing the range update to be delayed until the next the view scrolls. Remove trailing semicolons between method parameters and body (#1973) Having a semi-colon between a method parameters list and a method body is not not correct and is usually caused by a copy and paste error while creating the method definition from its declaration. Fixes the following compilation warnings when building with -Wsemicolon-before-method-body (which is part of -Wextra): ASPINRemoteImageDownloader.mm:230:85: error: semicolon before method body is ignored [-Werror,-Wsemicolon-before-method-body] - (id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; ^ ASPINRemoteImageDownloader.mm:275:76: error: semicolon before method body is ignored [-Werror,-Wsemicolon-before-method-body] completion:(ASImageDownloaderCompletion)completion; ^ 2 errors generated. Fixes applied to both code, examples and samples in documentation. [Layout] Add RTL support to LayoutSpecs (#1983) * [Layout] Add RTL support to LayoutSpecs This is largely a slight update for https://github.com/TextureGroup/Texture/pull/1805. If RTL is enabled, `calculateLayoutLayoutSpec:` will flip the origin of all sublayouts. The new part of the diff is that ASBatchFetching now supports proper fetching on RTL horizontal scrollViews. * Fix build and add RTL batch fetching tests [RTL/Batching] Make ASDisplayShouldFetchBatchForScrollView aware of flipped CV layouts (#1985) * [RTL/Batching] Make ASDisplayShouldFetchBatchForScrollView aware of flipped CV layouts UICollectionViewLayout has a property called `flipsHorizontallyInOppositeLayoutDirection`. If this is set to `YES` then a RTL collectionView’s contentOffset behaves like it does in LTR. In other words, the first item is at contentOffset 0. In this case, the existing logic for `ASDisplayShouldFetchBatchForScrollView` works in RTL. If you don’t override `flipsHorizontallyInOppositeLayoutDirection` to be `YES`, then it means that in RTL languages the first item in your collectionView will actually be at x offset `collectionView.contentSize.width - collectionView.frame.size.width`. As you scroll to the right, the content offset will decrease until you reach the end of the data at a content offset of 0,0. In this case, `ASDisplayShouldFetchBatchForScrollView` needs to know that you are in RTL and the layout is not flipped. It can then use the contentOffset as the `remainingDistance` to determine when to fetch. * fix indentation * assert that we are on main when accessing CV layout [RTL] Guard access of flipsHorizontallyInOppositeLayoutDirection for iOS >= 11 (#2003) `flipsHorizontallyInOppositeLayoutDirection` is available in iOS11 and greater. Texture still supports iOS9 so we need to make sure not to call this it in those cases. Rename ASNavigationController to ASDKNavigationController to fix name collision (#2020) As of iOS15 the AuthenticationServices framework has a class named `ASNavigationController`. We need to rename our `ASNavigationController` to protect against undefined behavior. Note: This change was based on this PR https://github.com/TextureGroup/Texture/pull/2014. We were slow in merging it and the author has not replied, so I'm making a new one to get this landed. [3.1.0] Create new version of ASDK (#2021) With the breaking change of renaming ASNavigationController to ASDKNavigationController, we have released a new version of Texture. Please see `ThreeMigrationGuide.md` for how to handle the breaking changes in 3.1.0. [3.1.0] Update CHANGELOG [3.1.0] Update .github_changelog_generator Update RELEASE.md Remove AssetsLibrary dependency for tvOS (#2034) - The framework isn't available on tvOS. This causes CocoaPods linting to fail which prevented me from pushing the new release out. - One way to fix this is to have a different `default_subspecs` for tvOS that doesn't have AssetsLibrary subspec, but per-platform `default_subspecs` doesn't seem to be supported by CocoaPods. So I updated the subspec itself to only depend on the framework for iOS. This means the subspec is empty/useless for tvOS (and other platforms FWIW). - Tested with `pod spec lint Texture.podspec`. - Fixes #1992 and part of #1549. Also unblocks 3.1.0 release. - For the long term, we can remove the subspec entirely when iOS 9 is deprecated (#1828). --- .github_changelog_generator | 4 +- AsyncDisplayKit.xcodeproj/project.pbxproj | 218 +++++++++--------- CHANGELOG.md | 39 ++++ Podfile | 2 - Podfile.lock | 4 +- README.md | 2 +- RELEASE.md | 2 +- Schemas/configuration.json | 7 +- Source/ASCellNode.mm | 4 +- Source/ASCollectionNode.mm | 2 +- Source/ASCollectionView.mm | 11 +- Source/ASDisplayNode+LayoutSpec.mm | 21 ++ Source/ASDisplayNode.mm | 15 ++ Source/ASDisplayNodeExtras.mm | 2 +- Source/ASExperimentalFeatures.h | 4 +- Source/ASExperimentalFeatures.mm | 4 +- Source/ASMultiplexImageNode.h | 6 + Source/ASMultiplexImageNode.mm | 5 +- Source/ASNetworkImageNode.h | 6 + Source/ASNetworkImageNode.mm | 8 +- Source/ASPagerNode.mm | 2 +- Source/ASRunLoopQueue.h | 13 +- Source/ASRunLoopQueue.mm | 61 ----- Source/ASTableView.mm | 8 +- Source/ASTextNode.mm | 24 +- Source/AsyncDisplayKit.h | 1 + Source/Debug/AsyncDisplayKit+Debug.mm | 4 +- Source/Details/ASBasicImageDownloader.h | 2 +- Source/Details/ASBasicImageDownloader.mm | 3 + Source/Details/ASDataController.mm | 73 ++++-- Source/Details/ASImageProtocols.h | 4 + Source/Details/ASPINRemoteImageDownloader.mm | 14 +- Source/Details/CoreGraphics+ASConvenience.h | 5 +- .../Transactions/_ASAsyncTransaction.mm | 2 +- Source/Details/_ASDisplayViewAccessiblity.mm | 39 ++-- Source/Layout/ASCenterLayoutSpec.mm | 2 +- Source/Layout/ASInsetLayoutSpec.mm | 2 +- Source/Layout/ASRatioLayoutSpec.mm | 2 +- Source/Private/ASBatchFetching.h | 9 +- Source/Private/ASBatchFetching.mm | 17 +- .../ASCollectionViewFlowLayoutInspector.mm | 4 +- Source/Private/ASDispatch.h | 2 +- Source/Private/ASDispatch.mm | 40 +--- Source/Private/ASDisplayNode+AsyncDisplay.mm | 2 +- .../Private/ASDisplayNode+FrameworkPrivate.h | 2 +- Source/Private/ASDisplayNode+UIViewBridge.mm | 36 +-- Source/Private/ASDisplayNodeInternal.h | 6 +- Source/Private/ASInternalHelpers.h | 3 - Source/Private/ASInternalHelpers.mm | 5 - .../Component/ASTextDebugOption.h | 0 .../Component/ASTextDebugOption.mm | 0 .../TextExperiment/Component/ASTextInput.h | 0 .../TextExperiment/Component/ASTextInput.mm | 0 .../TextExperiment/Component/ASTextLayout.h | 0 .../TextExperiment/Component/ASTextLayout.mm | 2 +- .../TextExperiment/Component/ASTextLine.h | 0 .../TextExperiment/Component/ASTextLine.mm | 0 .../TextExperiment/String/ASTextAttribute.h | 0 .../TextExperiment/String/ASTextAttribute.mm | 0 .../TextExperiment/String/ASTextRunDelegate.h | 0 .../String/ASTextRunDelegate.mm | 0 .../TextExperiment/Utility/ASTextUtilities.h | 0 .../TextExperiment/Utility/ASTextUtilities.mm | 0 .../Utility/NSAttributedString+ASText.h | 0 .../Utility/NSAttributedString+ASText.mm | 0 .../Utility/NSParagraphStyle+ASText.h | 0 .../Utility/NSParagraphStyle+ASText.mm | 0 Source/TextKit/ASTextKitContext.mm | 18 +- Source/TextKit/ASTextKitFontSizeAdjuster.mm | 2 +- Source/TextKit/ASTextKitRenderer.mm | 2 +- Tests/ASBasicImageDownloaderTests.mm | 2 + Tests/ASBatchFetchingTests.mm | 131 +++++++++-- Tests/ASCollectionViewTests.mm | 110 ++++++++- Tests/ASConfigurationTests.mm | 8 +- ...ts.mm => ASDKNavigationControllerTests.mm} | 6 +- Tests/ASDisplayViewAccessibilityTests.mm | 29 +++ Tests/ASMultiplexImageNodeTests.mm | 6 +- Tests/ASNetworkImageNodeTests.mm | 4 +- Tests/ASTableViewTests.mm | 127 +++++++++- Tests/ASTextNodeTests.mm | 6 + Tests/Common/ASTestCase.mm | 3 - Texture.podspec | 6 +- ThreeMigrationGuide.md | 6 +- docs/_docs/automatic-subnode-mgmt.md | 4 +- docs/_docs/development/how-to-debug.md | 4 - docs/_docs/development/node-lifecycle.md | 6 - docs/_docs/development/threading.md | 20 -- docs/_docs/inversion.md | 2 +- docs/_docs/multiplex-image-node.md | 2 +- docs/showcase.md | 100 ++++---- docs/slack.md | 2 +- examples/ASCollectionView/Podfile.lock | 59 +++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../ASCollectionView/Sample/ViewController.m | 2 +- examples/ASDKTube/Sample/ViewController.m | 2 +- examples/ASDKgram/Sample/PhotoCellNode.m | 2 +- examples/ASDKgram/Sample/PhotoTableViewCell.m | 2 +- examples/ASDKgram/Sample/UserModel.m | 2 +- .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../CustomCollectionView-Swift/Podfile.lock | 59 +++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../Sample/ViewController.m | 2 +- examples/Videos/Sample/ASVideoNode.m | 12 +- examples/Videos/Sample/ViewController.m | 8 +- .../ASDKgram-Swift.xcodeproj/project.pbxproj | 11 +- .../ASDKgram-Swift/AppDelegate.swift | 2 +- .../PhotoFeedTableNodeController.swift | 6 +- .../PhotoFeedTableViewController.swift | 4 +- .../ASDKgram-Swift/PhotoModel.swift | 20 +- .../ASDKgram-Swift/PhotoTableViewCell.swift | 2 +- examples_extra/ASDKgram-Swift/Podfile.lock | 44 ++++ .../Sample/ImageViewController.m | 2 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + examples_extra/Multiplex/Sample/ScreenNode.m | 1 + 118 files changed, 1159 insertions(+), 507 deletions(-) rename Source/{Private => }/TextExperiment/Component/ASTextDebugOption.h (100%) rename Source/{Private => }/TextExperiment/Component/ASTextDebugOption.mm (100%) rename Source/{Private => }/TextExperiment/Component/ASTextInput.h (100%) rename Source/{Private => }/TextExperiment/Component/ASTextInput.mm (100%) rename Source/{Private => }/TextExperiment/Component/ASTextLayout.h (100%) rename Source/{Private => }/TextExperiment/Component/ASTextLayout.mm (99%) rename Source/{Private => }/TextExperiment/Component/ASTextLine.h (100%) rename Source/{Private => }/TextExperiment/Component/ASTextLine.mm (100%) rename Source/{Private => }/TextExperiment/String/ASTextAttribute.h (100%) rename Source/{Private => }/TextExperiment/String/ASTextAttribute.mm (100%) rename Source/{Private => }/TextExperiment/String/ASTextRunDelegate.h (100%) rename Source/{Private => }/TextExperiment/String/ASTextRunDelegate.mm (100%) rename Source/{Private => }/TextExperiment/Utility/ASTextUtilities.h (100%) rename Source/{Private => }/TextExperiment/Utility/ASTextUtilities.mm (100%) rename Source/{Private => }/TextExperiment/Utility/NSAttributedString+ASText.h (100%) rename Source/{Private => }/TextExperiment/Utility/NSAttributedString+ASText.mm (100%) rename Source/{Private => }/TextExperiment/Utility/NSParagraphStyle+ASText.h (100%) rename Source/{Private => }/TextExperiment/Utility/NSParagraphStyle+ASText.mm (100%) rename Tests/{ASNavigationControllerTests.mm => ASDKNavigationControllerTests.mm} (94%) create mode 100644 examples/ASCollectionView/Podfile.lock create mode 100644 examples/ASCollectionView/Sample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/ASViewController/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/ASViewController/Sample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/CustomCollectionView-Swift/Podfile.lock create mode 100644 examples/CustomCollectionView-Swift/Sample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/HorizontalWithinVerticalScrolling/Sample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 examples_extra/ASDKgram-Swift/Podfile.lock create mode 100644 examples_extra/EditableText/Sample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/.github_changelog_generator b/.github_changelog_generator index 4df42730a..71166a913 100644 --- a/.github_changelog_generator +++ b/.github_changelog_generator @@ -1,3 +1,3 @@ issues=false -since-tag=3.0.0-rc.2 -future-release=3.0.0 +since-tag=3.1.0 +future-release=3.2.0 diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index db31ef2b2..d44b9a7e4 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -192,6 +192,24 @@ 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9644CFE02193777C00213478 /* ASThrashUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = 9644CFDF2193777C00213478 /* ASThrashUtility.m */; }; 9692B4FF219E12370060C2C3 /* ASCollectionViewThrashTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9692B4FE219E12370060C2C3 /* ASCollectionViewThrashTests.mm */; }; + 9C0BA49C2582CE35001C293B /* ASTextDebugOption.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C0BA4882582CE35001C293B /* ASTextDebugOption.mm */; }; + 9C0BA49D2582CE35001C293B /* ASTextDebugOption.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C0BA4892582CE35001C293B /* ASTextDebugOption.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C0BA49E2582CE35001C293B /* ASTextLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C0BA48A2582CE35001C293B /* ASTextLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C0BA49F2582CE35001C293B /* ASTextInput.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C0BA48B2582CE35001C293B /* ASTextInput.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C0BA4A02582CE35001C293B /* ASTextLine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C0BA48C2582CE35001C293B /* ASTextLine.mm */; }; + 9C0BA4A12582CE35001C293B /* ASTextLine.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C0BA48D2582CE35001C293B /* ASTextLine.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C0BA4A22582CE35001C293B /* ASTextLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C0BA48E2582CE35001C293B /* ASTextLayout.mm */; }; + 9C0BA4A32582CE35001C293B /* ASTextInput.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C0BA48F2582CE35001C293B /* ASTextInput.mm */; }; + 9C0BA4A42582CE35001C293B /* ASTextAttribute.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C0BA4912582CE35001C293B /* ASTextAttribute.mm */; }; + 9C0BA4A52582CE35001C293B /* ASTextRunDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C0BA4922582CE35001C293B /* ASTextRunDelegate.mm */; }; + 9C0BA4A62582CE35001C293B /* ASTextAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C0BA4932582CE35001C293B /* ASTextAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C0BA4A72582CE35001C293B /* ASTextRunDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C0BA4942582CE35001C293B /* ASTextRunDelegate.h */; }; + 9C0BA4A82582CE35001C293B /* ASTextUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C0BA4962582CE35001C293B /* ASTextUtilities.h */; }; + 9C0BA4A92582CE35001C293B /* NSParagraphStyle+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C0BA4972582CE35001C293B /* NSParagraphStyle+ASText.h */; }; + 9C0BA4AA2582CE35001C293B /* NSAttributedString+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C0BA4982582CE35001C293B /* NSAttributedString+ASText.h */; }; + 9C0BA4AB2582CE35001C293B /* NSAttributedString+ASText.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C0BA4992582CE35001C293B /* NSAttributedString+ASText.mm */; }; + 9C0BA4AC2582CE35001C293B /* NSParagraphStyle+ASText.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C0BA49A2582CE35001C293B /* NSParagraphStyle+ASText.mm */; }; + 9C0BA4AD2582CE35001C293B /* ASTextUtilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C0BA49B2582CE35001C293B /* ASTextUtilities.mm */; }; 9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.mm */; }; 9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -416,24 +434,6 @@ CCBBBF5D1EB161760069AA91 /* ASRangeManagingNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCBDDD0520C62A2D00CBA922 /* ASMainThreadDeallocation.h in Headers */ = {isa = PBXBuildFile; fileRef = CCBDDD0320C62A2D00CBA922 /* ASMainThreadDeallocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCBDDD0620C62A2D00CBA922 /* ASMainThreadDeallocation.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCBDDD0420C62A2D00CBA922 /* ASMainThreadDeallocation.mm */; }; - CCCCCCD51EC3EF060087FE10 /* ASTextDebugOption.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCC31EC3EF060087FE10 /* ASTextDebugOption.h */; }; - CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCC41EC3EF060087FE10 /* ASTextDebugOption.mm */; }; - CCCCCCD71EC3EF060087FE10 /* ASTextInput.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCC51EC3EF060087FE10 /* ASTextInput.h */; }; - CCCCCCD81EC3EF060087FE10 /* ASTextInput.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCC61EC3EF060087FE10 /* ASTextInput.mm */; }; - CCCCCCD91EC3EF060087FE10 /* ASTextLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCC71EC3EF060087FE10 /* ASTextLayout.h */; }; - CCCCCCDA1EC3EF060087FE10 /* ASTextLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCC81EC3EF060087FE10 /* ASTextLayout.mm */; }; - CCCCCCDB1EC3EF060087FE10 /* ASTextLine.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCC91EC3EF060087FE10 /* ASTextLine.h */; }; - CCCCCCDC1EC3EF060087FE10 /* ASTextLine.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCCA1EC3EF060087FE10 /* ASTextLine.mm */; }; - CCCCCCDD1EC3EF060087FE10 /* ASTextAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCCC1EC3EF060087FE10 /* ASTextAttribute.h */; }; - CCCCCCDE1EC3EF060087FE10 /* ASTextAttribute.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCCD1EC3EF060087FE10 /* ASTextAttribute.mm */; }; - CCCCCCDF1EC3EF060087FE10 /* ASTextRunDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCCE1EC3EF060087FE10 /* ASTextRunDelegate.h */; }; - CCCCCCE01EC3EF060087FE10 /* ASTextRunDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCCF1EC3EF060087FE10 /* ASTextRunDelegate.mm */; }; - CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCD11EC3EF060087FE10 /* ASTextUtilities.h */; }; - CCCCCCE21EC3EF060087FE10 /* ASTextUtilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD21EC3EF060087FE10 /* ASTextUtilities.mm */; }; - CCCCCCE31EC3EF060087FE10 /* NSParagraphStyle+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCD31EC3EF060087FE10 /* NSParagraphStyle+ASText.h */; }; - CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.mm */; }; - CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */; }; - CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.mm */; }; CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */ = {isa = PBXBuildFile; fileRef = CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.mm */; }; CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.mm */; }; @@ -452,6 +452,7 @@ CCEDDDD9200C518800FFCD0A /* ASConfigurationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCEDDDD8200C518800FFCD0A /* ASConfigurationTests.mm */; }; CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; CCF1FF5E20C4785000AAD8FC /* ASLocking.h in Headers */ = {isa = PBXBuildFile; fileRef = CCF1FF5D20C4785000AAD8FC /* ASLocking.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CD0F261C25CA03BB00735A79 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD0F261B25CA03BB00735A79 /* WebKit.framework */; }; D933F041224AD17F00FF495E /* ASTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D933F040224AD17F00FF495E /* ASTransactionTests.mm */; }; D99F9158232990F30083CC8E /* ASImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D99F9157232990F30083CC8E /* ASImageNodeTests.m */; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -770,6 +771,24 @@ 9644CFDE2193777C00213478 /* ASThrashUtility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASThrashUtility.h; sourceTree = ""; }; 9644CFDF2193777C00213478 /* ASThrashUtility.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASThrashUtility.m; sourceTree = ""; }; 9692B4FE219E12370060C2C3 /* ASCollectionViewThrashTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionViewThrashTests.mm; sourceTree = ""; }; + 9C0BA4882582CE35001C293B /* ASTextDebugOption.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextDebugOption.mm; sourceTree = ""; }; + 9C0BA4892582CE35001C293B /* ASTextDebugOption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextDebugOption.h; sourceTree = ""; }; + 9C0BA48A2582CE35001C293B /* ASTextLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextLayout.h; sourceTree = ""; }; + 9C0BA48B2582CE35001C293B /* ASTextInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextInput.h; sourceTree = ""; }; + 9C0BA48C2582CE35001C293B /* ASTextLine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextLine.mm; sourceTree = ""; }; + 9C0BA48D2582CE35001C293B /* ASTextLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextLine.h; sourceTree = ""; }; + 9C0BA48E2582CE35001C293B /* ASTextLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextLayout.mm; sourceTree = ""; }; + 9C0BA48F2582CE35001C293B /* ASTextInput.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextInput.mm; sourceTree = ""; }; + 9C0BA4912582CE35001C293B /* ASTextAttribute.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextAttribute.mm; sourceTree = ""; }; + 9C0BA4922582CE35001C293B /* ASTextRunDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextRunDelegate.mm; sourceTree = ""; }; + 9C0BA4932582CE35001C293B /* ASTextAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextAttribute.h; sourceTree = ""; }; + 9C0BA4942582CE35001C293B /* ASTextRunDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextRunDelegate.h; sourceTree = ""; }; + 9C0BA4962582CE35001C293B /* ASTextUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextUtilities.h; sourceTree = ""; }; + 9C0BA4972582CE35001C293B /* NSParagraphStyle+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSParagraphStyle+ASText.h"; sourceTree = ""; }; + 9C0BA4982582CE35001C293B /* NSAttributedString+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+ASText.h"; sourceTree = ""; }; + 9C0BA4992582CE35001C293B /* NSAttributedString+ASText.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSAttributedString+ASText.mm"; sourceTree = ""; }; + 9C0BA49A2582CE35001C293B /* NSParagraphStyle+ASText.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSParagraphStyle+ASText.mm"; sourceTree = ""; }; + 9C0BA49B2582CE35001C293B /* ASTextUtilities.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextUtilities.mm; sourceTree = ""; }; 9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutElement.h; sourceTree = ""; }; 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAsciiArtBoxCreator.h; sourceTree = ""; }; 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASAsciiArtBoxCreator.mm; sourceTree = ""; }; @@ -848,7 +867,7 @@ B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutManager.h; path = TextKit/ASLayoutManager.h; sourceTree = ""; }; B30BF6511C5964B0004FCD53 /* ASLayoutManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayoutManager.mm; path = TextKit/ASLayoutManager.mm; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASNavigationControllerTests.mm; sourceTree = ""; }; + BB5FC3CD1F9BA688007F191E /* ASDKNavigationControllerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDKNavigationControllerTests.mm; sourceTree = ""; }; BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTabBarControllerTests.mm; sourceTree = ""; }; BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = ""; }; C018DF20216BF26600181FDA /* ASAbstractLayoutController+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASAbstractLayoutController+FrameworkPrivate.h"; sourceTree = ""; }; @@ -951,24 +970,6 @@ CCBD05DF1E4147B000D18509 /* ASIGListAdapterBasedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIGListAdapterBasedDataSource.h; sourceTree = ""; }; CCBDDD0320C62A2D00CBA922 /* ASMainThreadDeallocation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASMainThreadDeallocation.h; sourceTree = ""; }; CCBDDD0420C62A2D00CBA922 /* ASMainThreadDeallocation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMainThreadDeallocation.mm; sourceTree = ""; }; - CCCCCCC31EC3EF060087FE10 /* ASTextDebugOption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextDebugOption.h; sourceTree = ""; }; - CCCCCCC41EC3EF060087FE10 /* ASTextDebugOption.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextDebugOption.mm; sourceTree = ""; }; - CCCCCCC51EC3EF060087FE10 /* ASTextInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextInput.h; sourceTree = ""; }; - CCCCCCC61EC3EF060087FE10 /* ASTextInput.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextInput.mm; sourceTree = ""; }; - CCCCCCC71EC3EF060087FE10 /* ASTextLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextLayout.h; sourceTree = ""; }; - CCCCCCC81EC3EF060087FE10 /* ASTextLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextLayout.mm; sourceTree = ""; }; - CCCCCCC91EC3EF060087FE10 /* ASTextLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextLine.h; sourceTree = ""; }; - CCCCCCCA1EC3EF060087FE10 /* ASTextLine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextLine.mm; sourceTree = ""; }; - CCCCCCCC1EC3EF060087FE10 /* ASTextAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextAttribute.h; sourceTree = ""; }; - CCCCCCCD1EC3EF060087FE10 /* ASTextAttribute.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextAttribute.mm; sourceTree = ""; }; - CCCCCCCE1EC3EF060087FE10 /* ASTextRunDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextRunDelegate.h; sourceTree = ""; }; - CCCCCCCF1EC3EF060087FE10 /* ASTextRunDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextRunDelegate.mm; sourceTree = ""; }; - CCCCCCD11EC3EF060087FE10 /* ASTextUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextUtilities.h; sourceTree = ""; }; - CCCCCCD21EC3EF060087FE10 /* ASTextUtilities.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextUtilities.mm; sourceTree = ""; }; - CCCCCCD31EC3EF060087FE10 /* NSParagraphStyle+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSParagraphStyle+ASText.h"; sourceTree = ""; }; - CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSParagraphStyle+ASText.mm"; sourceTree = ""; }; - CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+ASText.h"; sourceTree = ""; }; - CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSAttributedString+ASText.mm"; sourceTree = ""; }; CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASGraphicsContext.h; sourceTree = ""; }; CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASGraphicsContext.mm; sourceTree = ""; }; CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionModernDataSourceTests.mm; sourceTree = ""; }; @@ -993,6 +994,7 @@ CCEDDDD0200C488000FFCD0A /* ASConfiguration.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASConfiguration.mm; sourceTree = ""; }; CCEDDDD8200C518800FFCD0A /* ASConfigurationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASConfigurationTests.mm; sourceTree = ""; }; CCF1FF5D20C4785000AAD8FC /* ASLocking.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASLocking.h; sourceTree = ""; }; + CD0F261B25CA03BB00735A79 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; }; D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNode.mm; sourceTree = ""; }; @@ -1074,6 +1076,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CD0F261C25CA03BB00735A79 /* WebKit.framework in Frameworks */, CC36C19F218B894800232F23 /* CoreMedia.framework in Frameworks */, CC36C19E218B894400232F23 /* AVFoundation.framework in Frameworks */, CC90E1F41E383C0400FED591 /* AsyncDisplayKit.framework in Frameworks */, @@ -1153,6 +1156,7 @@ 058D09AE195D04C000B7D73C /* Frameworks */ = { isa = PBXGroup; children = ( + CD0F261B25CA03BB00735A79 /* WebKit.framework */, CC36C19B218B847400232F23 /* CoreMedia.framework */, CC36C199218B846F00232F23 /* CoreLocation.framework */, CC36C197218B846300232F23 /* QuartzCore.framework */, @@ -1184,6 +1188,7 @@ 058D0A01195D050800B7D73C /* Private */, 058D09B2195D04C000B7D73C /* Supporting Files */, 257754661BED245B00737CA5 /* TextKit */, + 9C0BA4862582CE35001C293B /* TextExperiment */, 690ED5911E36D118000627C0 /* tvOS */, CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */, DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */, @@ -1349,7 +1354,7 @@ CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */, 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.mm */, 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.mm */, - BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.mm */, + BB5FC3CD1F9BA688007F191E /* ASDKNavigationControllerTests.mm */, CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.mm */, ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */, AE6987C01DD04E1000B9E458 /* ASPagerNodeTests.mm */, @@ -1511,7 +1516,6 @@ CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */, E52F8AEE1EAE659600B5A912 /* Collection Layout */, 6947B0BB1E36B4E30007C478 /* Layout */, - CCCCCCC11EC3EF060087FE10 /* TextExperiment */, 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */, 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */, AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */, @@ -1676,6 +1680,55 @@ path = Layout; sourceTree = ""; }; + 9C0BA4862582CE35001C293B /* TextExperiment */ = { + isa = PBXGroup; + children = ( + 9C0BA4872582CE35001C293B /* Component */, + 9C0BA4902582CE35001C293B /* String */, + 9C0BA4952582CE35001C293B /* Utility */, + ); + path = TextExperiment; + sourceTree = ""; + }; + 9C0BA4872582CE35001C293B /* Component */ = { + isa = PBXGroup; + children = ( + 9C0BA4882582CE35001C293B /* ASTextDebugOption.mm */, + 9C0BA4892582CE35001C293B /* ASTextDebugOption.h */, + 9C0BA48A2582CE35001C293B /* ASTextLayout.h */, + 9C0BA48B2582CE35001C293B /* ASTextInput.h */, + 9C0BA48C2582CE35001C293B /* ASTextLine.mm */, + 9C0BA48D2582CE35001C293B /* ASTextLine.h */, + 9C0BA48E2582CE35001C293B /* ASTextLayout.mm */, + 9C0BA48F2582CE35001C293B /* ASTextInput.mm */, + ); + path = Component; + sourceTree = ""; + }; + 9C0BA4902582CE35001C293B /* String */ = { + isa = PBXGroup; + children = ( + 9C0BA4912582CE35001C293B /* ASTextAttribute.mm */, + 9C0BA4922582CE35001C293B /* ASTextRunDelegate.mm */, + 9C0BA4932582CE35001C293B /* ASTextAttribute.h */, + 9C0BA4942582CE35001C293B /* ASTextRunDelegate.h */, + ); + path = String; + sourceTree = ""; + }; + 9C0BA4952582CE35001C293B /* Utility */ = { + isa = PBXGroup; + children = ( + 9C0BA4962582CE35001C293B /* ASTextUtilities.h */, + 9C0BA4972582CE35001C293B /* NSParagraphStyle+ASText.h */, + 9C0BA4982582CE35001C293B /* NSAttributedString+ASText.h */, + 9C0BA4992582CE35001C293B /* NSAttributedString+ASText.mm */, + 9C0BA49A2582CE35001C293B /* NSParagraphStyle+ASText.mm */, + 9C0BA49B2582CE35001C293B /* ASTextUtilities.mm */, + ); + path = Utility; + sourceTree = ""; + }; AC6456051B0A333200CF11B8 /* Layout */ = { isa = PBXGroup; children = ( @@ -1748,55 +1801,6 @@ path = Common; sourceTree = ""; }; - CCCCCCC11EC3EF060087FE10 /* TextExperiment */ = { - isa = PBXGroup; - children = ( - CCCCCCC21EC3EF060087FE10 /* Component */, - CCCCCCCB1EC3EF060087FE10 /* String */, - CCCCCCD01EC3EF060087FE10 /* Utility */, - ); - path = TextExperiment; - sourceTree = ""; - }; - CCCCCCC21EC3EF060087FE10 /* Component */ = { - isa = PBXGroup; - children = ( - CCCCCCC31EC3EF060087FE10 /* ASTextDebugOption.h */, - CCCCCCC41EC3EF060087FE10 /* ASTextDebugOption.mm */, - CCCCCCC51EC3EF060087FE10 /* ASTextInput.h */, - CCCCCCC61EC3EF060087FE10 /* ASTextInput.mm */, - CCCCCCC71EC3EF060087FE10 /* ASTextLayout.h */, - CCCCCCC81EC3EF060087FE10 /* ASTextLayout.mm */, - CCCCCCC91EC3EF060087FE10 /* ASTextLine.h */, - CCCCCCCA1EC3EF060087FE10 /* ASTextLine.mm */, - ); - path = Component; - sourceTree = ""; - }; - CCCCCCCB1EC3EF060087FE10 /* String */ = { - isa = PBXGroup; - children = ( - CCCCCCCC1EC3EF060087FE10 /* ASTextAttribute.h */, - CCCCCCCD1EC3EF060087FE10 /* ASTextAttribute.mm */, - CCCCCCCE1EC3EF060087FE10 /* ASTextRunDelegate.h */, - CCCCCCCF1EC3EF060087FE10 /* ASTextRunDelegate.mm */, - ); - path = String; - sourceTree = ""; - }; - CCCCCCD01EC3EF060087FE10 /* Utility */ = { - isa = PBXGroup; - children = ( - CCCCCCD11EC3EF060087FE10 /* ASTextUtilities.h */, - CCCCCCD21EC3EF060087FE10 /* ASTextUtilities.mm */, - CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */, - CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.mm */, - CCCCCCD31EC3EF060087FE10 /* NSParagraphStyle+ASText.h */, - CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.mm */, - ); - path = Utility; - sourceTree = ""; - }; CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */ = { isa = PBXGroup; children = ( @@ -1892,6 +1896,11 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 9C0BA4A62582CE35001C293B /* ASTextAttribute.h in Headers */, + 9C0BA49F2582CE35001C293B /* ASTextInput.h in Headers */, + 9C0BA4A12582CE35001C293B /* ASTextLine.h in Headers */, + 9C0BA49D2582CE35001C293B /* ASTextDebugOption.h in Headers */, + 9C0BA49E2582CE35001C293B /* ASTextLayout.h in Headers */, 1A6C000D1FAB4E2100D05926 /* ASCornerLayoutSpec.h in Headers */, E54E00721F1D3828000B30D7 /* ASPagerNode+Beta.h in Headers */, E517F9C923BF14BC006E40E0 /* ASLayout+IGListDiffKit.h in Headers */, @@ -1905,7 +1914,6 @@ E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */, 9D9AA56D21E2568500172C09 /* ASDisplayNode+LayoutSpec.h in Headers */, E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */, - CCCCCCE31EC3EF060087FE10 /* NSParagraphStyle+ASText.h in Headers */, E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */, E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */, 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */, @@ -1924,7 +1932,6 @@ 68C215581DE10D330019C4BC /* ASCollectionViewLayoutInspector.h in Headers */, B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */, B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */, - CCCCCCD71EC3EF060087FE10 /* ASTextInput.h in Headers */, B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */, 9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */, 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */, @@ -1939,8 +1946,10 @@ CCB1F95C1EFB6350009C7475 /* ASSignpost.h in Headers */, C018DF21216BF26700181FDA /* ASAbstractLayoutController+FrameworkPrivate.h in Headers */, 34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */, + 9C0BA4A92582CE35001C293B /* NSParagraphStyle+ASText.h in Headers */, B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */, B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */, + 9C0BA4A82582CE35001C293B /* ASTextUtilities.h in Headers */, B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */, B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */, 34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */, @@ -1959,7 +1968,6 @@ B35062171B010EFD0018CF92 /* ASDataController.h in Headers */, 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */, 68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */, - CCCCCCD91EC3EF060087FE10 /* ASTextLayout.h in Headers */, A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */, 9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */, CC6AA2DA1E9F03B900978E87 /* ASDisplayNode+Ancestry.h in Headers */, @@ -1985,17 +1993,16 @@ 698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */, 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */, 9D302F9E2231B373005739C3 /* ASButtonNode+Yoga.h in Headers */, - CCCCCCD51EC3EF060087FE10 /* ASTextDebugOption.h in Headers */, CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, 683F563720E409D700CEB7A3 /* ASDisplayNode+InterfaceState.h in Headers */, 690BC8C120F6D3490052A434 /* ASDisplayNodeCornerLayerDelegate.h in Headers */, 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, - CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */, B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */, CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */, E5C347B11ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h in Headers */, + 9C0BA4A72582CE35001C293B /* ASTextRunDelegate.h in Headers */, CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */, B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */, CC0F88631E4281E700576FED /* ASSupplementaryNodeSource.h in Headers */, @@ -2008,7 +2015,6 @@ 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */, 254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */, 68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */, - CCCCCCDD1EC3EF060087FE10 /* ASTextAttribute.h in Headers */, B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */, 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */, AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */, @@ -2041,6 +2047,7 @@ 6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */, 692BE8D71E36B65B00C86D87 /* ASLayoutSpecPrivate.h in Headers */, 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */, + 9C0BA4AA2582CE35001C293B /* NSAttributedString+ASText.h in Headers */, DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */, 9F98C0271DBE29FC00476D92 /* ASControlTargetAction.h in Headers */, @@ -2090,12 +2097,9 @@ B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, CCA282CC1E9EB73E0037E8B7 /* ASTipNode.h in Headers */, 25E327571C16819500A2170C /* ASPagerNode.h in Headers */, - CCCCCCDB1EC3EF060087FE10 /* ASTextLine.h in Headers */, 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */, - CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */, CC35CEC320DD7F600006448D /* ASCollections.h in Headers */, CC7AF196200D9BD500A21BDE /* ASExperimentalFeatures.h in Headers */, - CCCCCCDF1EC3EF060087FE10 /* ASTextRunDelegate.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, CC0F885C1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h in Headers */, @@ -2323,7 +2327,7 @@ CC01EB6F23105C7F00CDB61A /* ASImageNodeSnapshotTests.mm in Sources */, CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.mm in Sources */, F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.mm in Sources */, - BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.mm in Sources */, + BB5FC3CE1F9BA689007F191E /* ASDKNavigationControllerTests.mm in Sources */, 81FF150722EB5F410039311A /* ASButtonNodeSnapshotTests.mm in Sources */, ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */, BB5FC3D11F9C9389007F191E /* ASTabBarControllerTests.mm in Sources */, @@ -2404,9 +2408,9 @@ CCA5F62E1EECC2A80060C137 /* ASAssert.mm in Sources */, 9F98C0261DBE29E000476D92 /* ASControlTargetAction.mm in Sources */, 9C70F2091CDABA36007D6C76 /* ASDKViewController.mm in Sources */, + 9C0BA4AD2582CE35001C293B /* ASTextUtilities.mm in Sources */, 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.mm in Sources */, CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.mm in Sources */, - CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.mm in Sources */, 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.mm in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.mm in Sources */, 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, @@ -2416,6 +2420,7 @@ 6947B0C51E36B5040007C478 /* ASStackPositionedLayout.mm in Sources */, B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.mm in Sources */, AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.mm in Sources */, + 9C0BA4A02582CE35001C293B /* ASTextLine.mm in Sources */, B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.mm in Sources */, CCA282BD1E9EABDD0037E8B7 /* ASTipProvider.mm in Sources */, 9019FBC01ED8061D00C45F72 /* ASYogaUtilities.mm in Sources */, @@ -2436,7 +2441,6 @@ 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, 690ED59B1E36D118000627C0 /* ASImageNode+tvOS.mm in Sources */, CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.mm in Sources */, - CCCCCCD81EC3EF060087FE10 /* ASTextInput.mm in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, 9D9AA56921E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm in Sources */, DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.mm in Sources */, @@ -2459,14 +2463,16 @@ 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.mm in Sources */, + 9C0BA4A22582CE35001C293B /* ASTextLayout.mm in Sources */, CCAA0B80206ADBF30057B336 /* ASRecursiveUnfairLock.mm in Sources */, + 9C0BA4AB2582CE35001C293B /* NSAttributedString+ASText.mm in Sources */, CCBDDD0620C62A2D00CBA922 /* ASMainThreadDeallocation.mm in Sources */, + 9C0BA49C2582CE35001C293B /* ASTextDebugOption.mm in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, CCB1F95A1EFB60A5009C7475 /* ASLog.mm in Sources */, 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.mm in Sources */, CCEDDDCB200C2AC300FFCD0A /* ASConfigurationInternal.mm in Sources */, 9D302F9F2231B373005739C3 /* ASButtonNode+Yoga.mm in Sources */, - CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.mm in Sources */, 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */, B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */, E5667E8E1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.mm in Sources */, @@ -2476,6 +2482,7 @@ 254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */, 68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */, E5711A301C840C96009619D4 /* ASCollectionElement.mm in Sources */, + 9C0BA4A52582CE35001C293B /* ASTextRunDelegate.mm in Sources */, B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */, E5E281761E71C845006B67C2 /* ASCollectionLayoutState.mm in Sources */, B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */, @@ -2502,8 +2509,6 @@ 34EFC75E1B701BF000AD841F /* ASInternalHelpers.mm in Sources */, 34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */, DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, - CCCCCCE01EC3EF060087FE10 /* ASTextRunDelegate.mm in Sources */, - CCCCCCDA1EC3EF060087FE10 /* ASTextLayout.mm in Sources */, CCEDDDD1200C488000FFCD0A /* ASConfiguration.mm in Sources */, 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.mm in Sources */, E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm in Sources */, @@ -2516,12 +2521,13 @@ B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */, 34EFC76D1B701CF100AD841F /* ASOverlayLayoutSpec.mm in Sources */, 044285101BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.mm in Sources */, - CCCCCCDE1EC3EF060087FE10 /* ASTextAttribute.mm in Sources */, + 9C0BA4A32582CE35001C293B /* ASTextInput.mm in Sources */, CCA282B51E9EA7310037E8B7 /* ASTipsController.mm in Sources */, B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */, 0442850A1BAA63FE00D16268 /* ASBatchFetching.mm in Sources */, CC35CEC420DD7F600006448D /* ASCollections.mm in Sources */, 68FC85E61CE29B9400EDD713 /* ASDKNavigationController.mm in Sources */, + 9C0BA4A42582CE35001C293B /* ASTextAttribute.mm in Sources */, 34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */, 254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */, 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */, @@ -2543,13 +2549,12 @@ E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.mm in Sources */, DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, 68FC85E51CE29B7E00EDD713 /* ASTabBarController.mm in Sources */, - CCCCCCDC1EC3EF060087FE10 /* ASTextLine.mm in Sources */, 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */, 1A6C000E1FAB4E2100D05926 /* ASCornerLayoutSpec.mm in Sources */, - CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.mm in Sources */, 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */, 909C4C761F09C98B00D6B76F /* ASTextNode2.mm in Sources */, 68C2155A1DE10D330019C4BC /* ASCollectionViewLayoutInspector.mm in Sources */, + 9C0BA4AC2582CE35001C293B /* NSParagraphStyle+ASText.mm in Sources */, DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.mm in Sources */, B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */, B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */, @@ -2558,7 +2563,6 @@ 254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.mm in Sources */, 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.mm in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.mm in Sources */, - CCCCCCE21EC3EF060087FE10 /* ASTextUtilities.mm in Sources */, CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.mm in Sources */, CC56013C1F06E9A700DC4FBE /* ASIntegerMap.mm in Sources */, 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 128b49a7a..eafa3a02c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog +## [3.1.0](https://github.com/TextureGroup/Texture/tree/3.1.0) (2021-09-09) + +[Full Changelog](https://github.com/TextureGroup/Texture/compare/3.0.0...3.1.0) + +**Fixed bugs:** + +- Fix hit point when ASCollectionNode inverted set to true [\#1781](https://github.com/TextureGroup/Texture/pull/1781) ([bdolman](https://github.com/bdolman)) + +**Merged pull requests:** + +- \[Minor Breaking API\] Rename ASNavigationController to ASDKNavigationController to fix name collision [\#2020](https://github.com/TextureGroup/Texture/pull/2020) ([rcancro](https://github.com/rcancro)) +- \[RTL\] Guard access of flipsHorizontallyInOppositeLayoutDirection for iOS \>= 11 [\#2003](https://github.com/TextureGroup/Texture/pull/2003) ([rcancro](https://github.com/rcancro)) +- \[RTL/Batching\] Make ASDisplayShouldFetchBatchForScrollView aware of flipped CV layouts [\#1985](https://github.com/TextureGroup/Texture/pull/1985) ([rcancro](https://github.com/rcancro)) +- \[Layout\] Add RTL support to LayoutSpecs [\#1983](https://github.com/TextureGroup/Texture/pull/1983) ([rcancro](https://github.com/rcancro)) +- Expand ASExperimentalRangeUpdateOnChangesetUpdate to ASTableView [\#1979](https://github.com/TextureGroup/Texture/pull/1979) ([rqueue](https://github.com/rqueue)) +- Docs: Remove Facebook and shift everything around, add Remix by Buffer [\#1978](https://github.com/TextureGroup/Texture/pull/1978) ([ay8s](https://github.com/ay8s)) +- Add experiment to ensure ASCollectionView's range controller updates … [\#1976](https://github.com/TextureGroup/Texture/pull/1976) ([rqueue](https://github.com/rqueue)) +- Remove trailing semicolons between method parameters and body [\#1973](https://github.com/TextureGroup/Texture/pull/1973) ([sdefresne](https://github.com/sdefresne)) +- Fix order-dependent ASTextNodeTests [\#1963](https://github.com/TextureGroup/Texture/pull/1963) ([tjaneczko](https://github.com/tjaneczko)) +- Update asdkGram swift sample to swift version 5.3 [\#1962](https://github.com/TextureGroup/Texture/pull/1962) ([MussaCharles](https://github.com/MussaCharles)) +- Fix mutation of variable that is never read [\#1961](https://github.com/TextureGroup/Texture/pull/1961) ([ZevEisenberg](https://github.com/ZevEisenberg)) +- Remove redundant assignment [\#1960](https://github.com/TextureGroup/Texture/pull/1960) ([ZevEisenberg](https://github.com/ZevEisenberg)) +- Podfile improvements [\#1957](https://github.com/TextureGroup/Texture/pull/1957) ([ZevEisenberg](https://github.com/ZevEisenberg)) +- Fix WKWebView Accessibility [\#1955](https://github.com/TextureGroup/Texture/pull/1955) ([ZevEisenberg](https://github.com/ZevEisenberg)) +- fix missing hidden class [\#1952](https://github.com/TextureGroup/Texture/pull/1952) ([joprice](https://github.com/joprice)) +- use https for slack link [\#1950](https://github.com/TextureGroup/Texture/pull/1950) ([joprice](https://github.com/joprice)) +- Exposes a new option in ASImageDownloaderProtocol to retry image downloads [\#1948](https://github.com/TextureGroup/Texture/pull/1948) ([chggr](https://github.com/chggr)) +- All ASCellNode nodes to be non accessible if needed [\#1941](https://github.com/TextureGroup/Texture/pull/1941) ([decim92](https://github.com/decim92)) +- \[ASTextNode2\] Make some ASTextNode2 layout files public [\#1939](https://github.com/TextureGroup/Texture/pull/1939) ([rcancro](https://github.com/rcancro)) +- Ship ASExperimentalDispatchApply [\#1924](https://github.com/TextureGroup/Texture/pull/1924) ([nguyenhuy](https://github.com/nguyenhuy)) +- Fix failing ASConfigurationTests [\#1923](https://github.com/TextureGroup/Texture/pull/1923) ([nguyenhuy](https://github.com/nguyenhuy)) +- More on ASDataController's main-thread-only mode [\#1915](https://github.com/TextureGroup/Texture/pull/1915) ([nguyenhuy](https://github.com/nguyenhuy)) +- Add an experiment that makes ASDataController to do everything on main thread [\#1911](https://github.com/TextureGroup/Texture/pull/1911) ([nguyenhuy](https://github.com/nguyenhuy)) +- Disable text kit lock [\#1910](https://github.com/TextureGroup/Texture/pull/1910) ([garrettmoon](https://github.com/garrettmoon)) +- Do not expose tgmath.h to all clients of Texture [\#1900](https://github.com/TextureGroup/Texture/pull/1900) ([bolsinga](https://github.com/bolsinga)) +- Call will / did display node for ASTextNode. Fixes \#1680 [\#1893](https://github.com/TextureGroup/Texture/pull/1893) ([garrettmoon](https://github.com/garrettmoon)) +- Remove background deallocation helper code [\#1890](https://github.com/TextureGroup/Texture/pull/1890) ([bolsinga](https://github.com/bolsinga)) +- \[Accessibility\] Ship ASExperimentalDoNotCacheAccessibilityElements [\#1888](https://github.com/TextureGroup/Texture/pull/1888) ([rcancro](https://github.com/rcancro)) + ## [3.0.0](https://github.com/TextureGroup/Texture/tree/3.0.0) (2020-07-15) [Full Changelog](https://github.com/TextureGroup/Texture/compare/3.0.0-rc.2...3.0.0) diff --git a/Podfile b/Podfile index cb97714dc..4d8a38822 100644 --- a/Podfile +++ b/Podfile @@ -1,5 +1,3 @@ -source 'https://github.com/CocoaPods/Specs.git' - platform :ios, '9.0' target :'AsyncDisplayKitTests' do diff --git a/Podfile.lock b/Podfile.lock index c0d6a30ec..0b7f6286a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -7,7 +7,7 @@ DEPENDENCIES: - OCMock (~> 3.6) SPEC REPOS: - https://github.com/CocoaPods/Specs.git: + trunk: - iOSSnapshotTestCase - OCMock @@ -15,6 +15,6 @@ SPEC CHECKSUMS: iOSSnapshotTestCase: 9ab44cb5aa62b84d31847f40680112e15ec579a6 OCMock: 29f6e52085b4e7d9b075cbf03ed7c3112f82f934 -PODFILE CHECKSUM: ff1a1777e31f49e6e4b7b148d0f10e504db7fa26 +PODFILE CHECKSUM: 1b4ea0e8ab7d94a46b1964a2354686c2e599c8c2 COCOAPODS: 1.10.2 diff --git a/README.md b/README.md index 57e0c542d..28feb6eb4 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ As the framework has grown, many features have been added that can save develope ## Getting Help -We use Slack for real-time debugging, community updates, and general talk about Texture. [Signup](http://asdk-slack-auto-invite.herokuapp.com) yourself or email textureframework@gmail.com to get an invite. +We use Slack for real-time debugging, community updates, and general talk about Texture. [Signup](https://asdk-slack-auto-invite.herokuapp.com) yourself or email textureframework@gmail.com to get an invite. ## Release process diff --git a/RELEASE.md b/RELEASE.md index f7ea00e92..b494f6f8a 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -8,7 +8,7 @@ This document describes the process for a public Texture release. ### Process - Run `github_changelog_generator` in Texture project directory: `github_changelog_generator --token --user TextureGroup --project Texture`. To avoid hitting rate limit, the generator will replace the entire file with just the changes from this version – revert that giant deletion to get the entire new changelog. - Update `spec.version` within `Texture.podspec` and the `since-tag` and `future-release` fields in `.github_changelog_generator`. -- Create a new PR with the updated `Texture.podspec` and the newly generated changelog. +- Create a new PR with the updated `Texture.podspec` and `.github_changelog_generator`, and the newly generated changelog. - After merging in the PR, [create a new GitHub release](https://github.com/TextureGroup/Texture/releases/new). Use the generated changelog for the new release. - Push to Cocoapods with `pod trunk push` diff --git a/Schemas/configuration.json b/Schemas/configuration.json index 4ce900e5d..481663b2d 100644 --- a/Schemas/configuration.json +++ b/Schemas/configuration.json @@ -13,7 +13,6 @@ "items": { "type": "string", "enum": [ - "exp_graphics_contexts", "exp_text_node", "exp_interface_state_coalesce", "exp_infer_layer_defaults", @@ -22,8 +21,10 @@ "exp_skip_clear_data", "exp_did_enter_preload_skip_asm_layout", "exp_dispatch_apply", - "exp_oom_bg_dealloc_disable", - "exp_do_not_cache_accessibility_elements", + "exp_drawing_global", + "exp_optimize_data_controller_pipeline", + "exp_disable_global_textkit_lock", + "exp_main_thread_only_data_controller", ] } } diff --git a/Source/ASCellNode.mm b/Source/ASCellNode.mm index 208eb5f05..5c7451198 100644 --- a/Source/ASCellNode.mm +++ b/Source/ASCellNode.mm @@ -159,7 +159,7 @@ - (void)setHighlighted:(BOOL)highlighted } } -- (void)__setSelectedFromUIKit:(BOOL)selected; +- (void)__setSelectedFromUIKit:(BOOL)selected { // Note: Race condition could mean redundant sets. Risk is low. if (ASLockedSelf(_selected != selected)) { @@ -169,7 +169,7 @@ - (void)__setSelectedFromUIKit:(BOOL)selected; } } -- (void)__setHighlightedFromUIKit:(BOOL)highlighted; +- (void)__setHighlightedFromUIKit:(BOOL)highlighted { // Note: Race condition could mean redundant sets. Risk is low. if (ASLockedSelf(_highlighted != highlighted)) { diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index f1da3e5ff..77ac1fa1d 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -1168,7 +1168,7 @@ - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *) #pragma mark - ASRangeControllerUpdateRangeProtocol -- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode; +- (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode { if ([self pendingState]) { _pendingState.rangeMode = rangeMode; diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index afe9716ce..e08c55f98 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -1857,7 +1857,13 @@ - (void)_checkForBatchFetching - (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset velocity:(CGPoint)velocity { - if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, self.scrollableDirections, contentOffset, velocity)) { + // Since we are accessing self.collectionViewLayout, we should make sure we are on main + ASDisplayNodeAssertMainThread(); + BOOL flipsHorizontallyInOppositeLayoutDirection = NO; + if (AS_AVAILABLE_IOS(11.0)) { + flipsHorizontallyInOppositeLayoutDirection = self.collectionViewLayout.flipsHorizontallyInOppositeLayoutDirection; + } + if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, self.scrollableDirections, contentOffset, velocity, flipsHorizontallyInOppositeLayoutDirection)) { [self _beginBatchFetching]; } } @@ -2311,6 +2317,9 @@ - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet // Flush any range changes that happened as part of submitting the update. as_activity_scope(changeSet.rootActivity); + if (numberOfUpdates > 0 && ASActivateExperimentalFeature(ASExperimentalRangeUpdateOnChangesetUpdate)) { + [self->_rangeController setNeedsUpdate]; + } [self->_rangeController updateIfNeeded]; } }); diff --git a/Source/ASDisplayNode+LayoutSpec.mm b/Source/ASDisplayNode+LayoutSpec.mm index 93ce0e6dd..04ca0f6ab 100644 --- a/Source/ASDisplayNode+LayoutSpec.mm +++ b/Source/ASDisplayNode+LayoutSpec.mm @@ -107,6 +107,27 @@ - (ASLayout *)calculateLayoutLayoutSpec:(ASSizeRange)constrainedSize } layout = [layout filteredNodeLayoutTree]; + // Flip layout if layout should be rendered right-to-left + BOOL shouldRenderRTLLayout = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:_semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft; + if (shouldRenderRTLLayout) { + for (ASLayout *sublayout in layout.sublayouts) { + switch (_semanticContentAttribute) { + case UISemanticContentAttributeUnspecified: + case UISemanticContentAttributeForceRightToLeft: { + // Flip + CGPoint flippedPosition = CGPointMake(layout.size.width - CGRectGetWidth(sublayout.frame) - sublayout.position.x, sublayout.position.y); + sublayout.position = flippedPosition; + } + case UISemanticContentAttributePlayback: + case UISemanticContentAttributeForceLeftToRight: + case UISemanticContentAttributeSpatial: + // Don't flip + break; + } + } + } + + return layout; } diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index abb339bdd..b96a77477 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -1625,6 +1625,21 @@ - (void)updateCornerRoundingWithType:(ASCornerRoundingType)newRoundingType }); } +- (void)updateSemanticContentAttributeWithAttribute:(UISemanticContentAttribute)attribute +{ + __instanceLock__.lock(); + UISemanticContentAttribute oldAttribute = _semanticContentAttribute; + _semanticContentAttribute = attribute; + __instanceLock__.unlock(); + + ASPerformBlockOnMainThread(^{ + // If the value has changed we should attempt to relayout. + if (attribute != oldAttribute) { + [self setNeedsLayout]; + } + }); +} + - (void)recursivelySetDisplaySuspended:(BOOL)flag { _recursivelySetDisplaySuspended(self, nil, flag); diff --git a/Source/ASDisplayNodeExtras.mm b/Source/ASDisplayNodeExtras.mm index 2cebbb536..caf68ca8e 100644 --- a/Source/ASDisplayNodeExtras.mm +++ b/Source/ASDisplayNodeExtras.mm @@ -65,7 +65,7 @@ ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNode, UIWi // if not already, about to be set to invisible as it is not possible for an element to be visible // while outside of a window. ASInterfaceState interfaceState = displayNode.pendingInterfaceState; - return (window == nil ? (interfaceState &= (~ASInterfaceStateVisible)) : interfaceState); + return (window == nil ? (interfaceState & (~ASInterfaceStateVisible)) : interfaceState); } else { // For not range managed nodes we might be on our own to try to guess if we're visible. return (window == nil ? ASInterfaceStateNone : (ASInterfaceStateVisible | ASInterfaceStateDisplay)); diff --git a/Source/ASExperimentalFeatures.h b/Source/ASExperimentalFeatures.h index 7729e2e34..a8b655ce9 100644 --- a/Source/ASExperimentalFeatures.h +++ b/Source/ASExperimentalFeatures.h @@ -28,7 +28,9 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) { ASExperimentalDispatchApply = 1 << 7, // exp_dispatch_apply ASExperimentalDrawingGlobal = 1 << 8, // exp_drawing_global ASExperimentalOptimizeDataControllerPipeline = 1 << 9, // exp_optimize_data_controller_pipeline - ASExperimentalDoNotCacheAccessibilityElements = 1 << 10, // exp_do_not_cache_accessibility_elements + ASExperimentalDisableGlobalTextkitLock = 1 << 10, // exp_disable_global_textkit_lock + ASExperimentalMainThreadOnlyDataController = 1 << 11, // exp_main_thread_only_data_controller + ASExperimentalRangeUpdateOnChangesetUpdate = 1 << 12, // exp_range_update_on_changeset_update ASExperimentalFeatureAll = 0xFFFFFFFF }; diff --git a/Source/ASExperimentalFeatures.mm b/Source/ASExperimentalFeatures.mm index fef124b45..6113dc405 100644 --- a/Source/ASExperimentalFeatures.mm +++ b/Source/ASExperimentalFeatures.mm @@ -22,7 +22,9 @@ @"exp_dispatch_apply", @"exp_drawing_global", @"exp_optimize_data_controller_pipeline", - @"exp_do_not_cache_accessibility_elements"])); + @"exp_disable_global_textkit_lock", + @"exp_main_thread_only_data_controller", + @"exp_range_update_on_changeset_update"])); if (flags == ASExperimentalFeatureAll) { return allNames; } diff --git a/Source/ASMultiplexImageNode.h b/Source/ASMultiplexImageNode.h index 72a913a90..a3ce97a9b 100644 --- a/Source/ASMultiplexImageNode.h +++ b/Source/ASMultiplexImageNode.h @@ -130,6 +130,12 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) { */ @property (nonatomic) BOOL shouldRenderProgressImages; +/** + * Specifies whether the underlying image downloader should attempt to retry downloading the image if the remote + * host is unreachable. It will have no effect if the downloader does not support retrying. The default is YES. + */ +@property BOOL shouldRetryImageDownload; + /** * @abstract The image manager that this image node should use when requesting images from the Photos framework. If this is `nil` (the default), then `PHImageManager.defaultManager` is used. diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index 182d4284a..10a205458 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -157,13 +157,14 @@ - (instancetype)initWithCache:(id)cache downloader:(id_downloader downloadImageWithURL:imageURL + shouldRetry:[self shouldRetryImageDownload] priority:priority callbackQueue:callbackQueue downloadProgress:downloadProgressBlock @@ -820,6 +822,7 @@ - (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL c and their requests are put into the same pool. */ downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL + shouldRetry:[self shouldRetryImageDownload] callbackQueue:callbackQueue downloadProgress:downloadProgressBlock completion:completion]; diff --git a/Source/ASNetworkImageNode.h b/Source/ASNetworkImageNode.h index fa07a3d49..bc931cbee 100644 --- a/Source/ASNetworkImageNode.h +++ b/Source/ASNetworkImageNode.h @@ -116,6 +116,12 @@ NS_ASSUME_NONNULL_BEGIN */ @property BOOL shouldRenderProgressImages; +/** + * Specifies whether the underlying image downloader should attempt to retry downloading the image if the remote + * host is unreachable. It will have no effect if the downloader does not support retrying. The default is YES. + */ +@property BOOL shouldRetryImageDownload; + /** * The image quality of the current image. * diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 085fc063c..8509ce39b 100644 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -96,7 +96,7 @@ - (instancetype)initWithCache:(id)cache downloader:(id)cache downloader:(id ima ASImageDownloaderPriority priority = ASImageDownloaderPriorityWithInterfaceState(interfaceState); downloadIdentifier = [self->_downloader downloadImageWithURL:url - - priority:priority + shouldRetry:[self shouldRetryImageDownload] + priority:priority callbackQueue:callbackQueue downloadProgress:downloadProgress completion:completion]; @@ -672,6 +673,7 @@ - (void)_downloadImageWithCompletion:(void (^)(id ima and their requests are put into the same pool. */ downloadIdentifier = [self->_downloader downloadImageWithURL:url + shouldRetry:[self shouldRetryImageDownload] callbackQueue:callbackQueue downloadProgress:downloadProgress completion:completion]; diff --git a/Source/ASPagerNode.mm b/Source/ASPagerNode.mm index 7ad8f1ad0..082ffc660 100644 --- a/Source/ASPagerNode.mm +++ b/Source/ASPagerNode.mm @@ -51,7 +51,7 @@ - (instancetype)init return [self initWithCollectionViewLayout:flowLayout]; } -- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; +- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout { ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout."); ASDisplayNodeAssertTrue(flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal); diff --git a/Source/ASRunLoopQueue.h b/Source/ASRunLoopQueue.h index 298fe76ca..c66a5dbfb 100644 --- a/Source/ASRunLoopQueue.h +++ b/Source/ASRunLoopQueue.h @@ -32,8 +32,7 @@ AS_SUBCLASSING_RESTRICTED * * @discussion You may pass @c nil for the handler if you simply want the objects to * be retained at enqueue time, and released during the run loop step. This is useful - * for creating a "main deallocation queue", as @c ASDeallocQueue creates its own - * worker thread with its own run loop. + * for creating a "main deallocation queue". */ - (instancetype)initWithRunLoop:(CFRunLoopRef)runloop retainObjects:(BOOL)retainsObjects @@ -78,14 +77,4 @@ NS_INLINE ASCATransactionQueue *ASCATransactionQueueGet(void) { return _ASSharedCATransactionQueue; } -@interface ASDeallocQueue : NSObject - -+ (ASDeallocQueue *)sharedDeallocationQueue NS_RETURNS_RETAINED; - -- (void)drain; - -- (void)releaseObjectInBackground:(id __strong _Nullable * _Nonnull)objectPtr; - -@end - NS_ASSUME_NONNULL_END diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index fd896af89..8c917b07a 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -27,67 +27,6 @@ static void runLoopSourceCallback(void *info) { #endif } -#pragma mark - ASDeallocQueue - -@implementation ASDeallocQueue { - std::vector _queue; - AS::Mutex _lock; -} - -+ (ASDeallocQueue *)sharedDeallocationQueue NS_RETURNS_RETAINED -{ - static ASDeallocQueue *deallocQueue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - deallocQueue = [[ASDeallocQueue alloc] init]; - }); - return deallocQueue; -} - -- (void)dealloc -{ - ASDisplayNodeFailAssert(@"Singleton should not dealloc."); -} - -- (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr -{ - NSParameterAssert(objectPtr != NULL); - - // Cast to CFType so we can manipulate retain count manually. - const auto cfPtr = (CFTypeRef *)(void *)objectPtr; - if (!cfPtr || !*cfPtr) { - return; - } - - _lock.lock(); - const auto isFirstEntry = _queue.empty(); - // Push the pointer into our queue and clear their pointer. - // This "steals" the +1 from ARC and nils their pointer so they can't - // access or release the object. - _queue.push_back(*cfPtr); - *cfPtr = NULL; - _lock.unlock(); - - if (isFirstEntry) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.100 * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ - [self drain]; - }); - } -} - -- (void)drain -{ - _lock.lock(); - const auto q = std::move(_queue); - _lock.unlock(); - for (CFTypeRef ref : q) { - // NOTE: Could check that retain count is 1 and retry later if not. - CFRelease(ref); - } -} - -@end - @implementation ASAbstractRunLoopQueue - (instancetype)init diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 97eb09f28..195cb183c 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -116,7 +116,8 @@ - (void)setElement:(ASCollectionElement *)element self.selectionStyle = node.selectionStyle; self.focusStyle = node.focusStyle; self.accessoryType = node.accessoryType; - + self.isAccessibilityElement = node.isAccessibilityElement; + self.accessibilityElementsHidden = node.accessibilityElementsHidden; // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) // This is actually a workaround for a bug we are seeing in some rare cases (selected background view // overlaps other cells if size of ASCellNode has changed.) @@ -1485,7 +1486,7 @@ - (void)_checkForBatchFetching - (void)_beginBatchFetchingIfNeededWithContentOffset:(CGPoint)contentOffset velocity:(CGPoint)velocity { - if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, ASScrollDirectionVerticalDirections, contentOffset, velocity)) { + if (ASDisplayShouldFetchBatchForScrollView(self, self.scrollDirection, ASScrollDirectionVerticalDirections, contentOffset, velocity, NO)) { [self _beginBatchFetching]; } } @@ -1678,6 +1679,9 @@ - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet LOG(@"--- UITableView endUpdates"); ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ [super endUpdates]; + if (numberOfUpdates > 0 && ASActivateExperimentalFeature(ASExperimentalRangeUpdateOnChangesetUpdate)) { + [self->_rangeController setNeedsUpdate]; + } [self->_rangeController updateIfNeeded]; [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates]; }); diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index afffba507..8df442667 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -141,6 +141,8 @@ @interface ASTextNodeDrawParameter : NSObject { BOOL _opaque; CGRect _bounds; ASPrimitiveTraitCollection _traitCollection; + ASDisplayNodeContextModifier _willDisplayNodeContentWithRenderingContext; + ASDisplayNodeContextModifier _didDisplayNodeContentWithRenderingContext; } @end @@ -152,7 +154,9 @@ - (instancetype)initWithRendererAttributes:(ASTextKitAttributes)rendererAttribut contentScale:(CGFloat)contentScale opaque:(BOOL)opaque bounds:(CGRect)bounds - traitCollection: (ASPrimitiveTraitCollection)traitCollection + traitCollection:(ASPrimitiveTraitCollection)traitCollection +willDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)willDisplayNodeContentWithRenderingContext + didDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)didDisplayNodeContentWithRenderingContext { self = [super init]; if (self != nil) { @@ -163,6 +167,8 @@ - (instancetype)initWithRendererAttributes:(ASTextKitAttributes)rendererAttribut _opaque = opaque; _bounds = bounds; _traitCollection = traitCollection; + _willDisplayNodeContentWithRenderingContext = willDisplayNodeContentWithRenderingContext; + _didDisplayNodeContentWithRenderingContext = didDisplayNodeContentWithRenderingContext; } return self; } @@ -560,7 +566,9 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer contentScale:_contentsScaleForDisplay opaque:self.isOpaque bounds:[self threadSafeBounds] - traitCollection:self.primitiveTraitCollection]; + traitCollection:self.primitiveTraitCollection + willDisplayNodeContentWithRenderingContext:self.willDisplayNodeContentWithRenderingContext + didDisplayNodeContentWithRenderingContext:self.didDisplayNodeContentWithRenderingContext]; } + (UIImage *)displayWithParameters:(id)parameters isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled @@ -574,12 +582,19 @@ + (UIImage *)displayWithParameters:(id)parameters isCancelled:(NS_NOES UIColor *backgroundColor = drawParameter->_backgroundColor; UIEdgeInsets textContainerInsets = drawParameter ? drawParameter->_textContainerInsets : UIEdgeInsetsZero; ASTextKitRenderer *renderer = [drawParameter rendererForBounds:drawParameter->_bounds]; + ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = drawParameter->_willDisplayNodeContentWithRenderingContext; + ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = drawParameter->_didDisplayNodeContentWithRenderingContext; UIImage *result = ASGraphicsCreateImage(drawParameter->_traitCollection, CGSizeMake(drawParameter->_bounds.size.width, drawParameter->_bounds.size.height), drawParameter->_opaque, drawParameter->_contentScale, nil, nil, ^{ CGContextRef context = UIGraphicsGetCurrentContext(); ASDisplayNodeAssert(context, @"This is no good without a context."); CGContextSaveGState(context); + + if (context && willDisplayNodeContentWithRenderingContext) { + willDisplayNodeContentWithRenderingContext(context, drawParameter); + } + CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top); // Fill background @@ -591,6 +606,11 @@ + (UIImage *)displayWithParameters:(id)parameters isCancelled:(NS_NOES // Draw text [renderer drawInContext:context bounds:drawParameter->_bounds]; + + if (context && didDisplayNodeContentWithRenderingContext) { + didDisplayNodeContentWithRenderingContext(context, drawParameter); + } + CGContextRestoreGState(context); }); diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index 6afc67fcb..4f1e7e3a1 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -110,6 +110,7 @@ #import #import #import +#import #import #import #import diff --git a/Source/Debug/AsyncDisplayKit+Debug.mm b/Source/Debug/AsyncDisplayKit+Debug.mm index 965b265cd..45a8cb4e6 100644 --- a/Source/Debug/AsyncDisplayKit+Debug.mm +++ b/Source/Debug/AsyncDisplayKit+Debug.mm @@ -25,7 +25,7 @@ @implementation ASImageNode (Debugging) -+ (void)setShouldShowImageScalingOverlay:(BOOL)show; ++ (void)setShouldShowImageScalingOverlay:(BOOL)show { __shouldShowImageScalingOverlay = show; } @@ -437,7 +437,7 @@ - (void)updateRangeController:(ASRangeController *)controller rangeMode:(ASLayoutRangeMode)rangeMode displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters preloadTuningParameters:(ASRangeTuningParameters)preloadTuningParameters - interfaceState:(ASInterfaceState)interfaceState; + interfaceState:(ASInterfaceState)interfaceState { _ASRangeDebugBarView *viewToUpdate = [self barViewForRangeController:controller]; diff --git a/Source/Details/ASBasicImageDownloader.h b/Source/Details/ASBasicImageDownloader.h index 7667bf8f9..4b6753118 100644 --- a/Source/Details/ASBasicImageDownloader.h +++ b/Source/Details/ASBasicImageDownloader.h @@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes. * The userInfo provided by this downloader is `nil`. * - * This is a very basic image downloader. It does not support caching, progressive downloading and likely + * This is a very basic image downloader. It does not support caching, retrying, progressive downloading and likely * isn't something you should use in production. If you'd like something production ready, see @c ASPINRemoteImageDownloader * * @note It is strongly recommended you include PINRemoteImage and use @c ASPINRemoteImageDownloader instead. diff --git a/Source/Details/ASBasicImageDownloader.mm b/Source/Details/ASBasicImageDownloader.mm index f577eb1e2..f71d232c0 100644 --- a/Source/Details/ASBasicImageDownloader.mm +++ b/Source/Details/ASBasicImageDownloader.mm @@ -253,11 +253,13 @@ - (instancetype)_init #pragma mark ASImageDownloaderProtocol. - (nullable id)downloadImageWithURL:(NSURL *)URL + shouldRetry:(BOOL)shouldRetry callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion { return [self downloadImageWithURL:URL + shouldRetry:shouldRetry priority:ASImageDownloaderPriorityImminent // maps to default priority callbackQueue:callbackQueue downloadProgress:downloadProgress @@ -265,6 +267,7 @@ - (nullable id)downloadImageWithURL:(NSURL *)URL } - (nullable id)downloadImageWithURL:(NSURL *)URL + shouldRetry:(BOOL)shouldRetry priority:(ASImageDownloaderPriority)priority callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(ASImageDownloaderProgress)downloadProgress diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index 76e58d8be..10442da62 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -33,8 +33,6 @@ //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) -#define ASSERT_ON_EDITING_QUEUE ASDisplayNodeAssertNotNil(dispatch_get_specific(&kASDataControllerEditingQueueKey), @"%@ must be called on the editing transaction queue.", NSStringFromSelector(_cmd)) - const static char * kASDataControllerEditingQueueKey = "kASDataControllerEditingQueueKey"; const static char * kASDataControllerEditingQueueContext = "kASDataControllerEditingQueueContext"; @@ -131,10 +129,17 @@ - (void)setLayoutDelegate:(id)layoutDelegate #pragma mark - Cell Layout +/** + * Allocates and layouts nodes from the given collection elements, and blocks the current thread while doing so. + * + * @param elements The elements from which nodes can be allocated and laid out. + * @param strictlyOnCurrentThread Whether or not all the work must be done strictly on the current thread. + * YES means all nodes will be allocated and laid out serially on the current thread. + * NO means the work can be offloaded to other thread(s), potentially reduce the blocking time on the calling thread. + */ - (void)_allocateNodesFromElements:(NSArray *)elements + strictlyOnCurrentThread:(BOOL)strictlyOnCurrentThread { - ASSERT_ON_EDITING_QUEUE; - NSUInteger nodeCount = elements.count; __weak id weakDataSource = _dataSource; if (nodeCount == 0 || weakDataSource == nil) { @@ -146,12 +151,7 @@ - (void)_allocateNodesFromElements:(NSArray *)elements { as_activity_create_for_scope("Data controller batch"); - dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0); - NSUInteger threadCount = 0; - if ([_dataSource dataControllerShouldSerializeNodeCreation:self]) { - threadCount = 1; - } - ASDispatchApply(nodeCount, queue, threadCount, ^(size_t i) { + void(^work)(size_t) = ^(size_t i) { __strong id strongDataSource = weakDataSource; if (strongDataSource == nil) { return; @@ -170,7 +170,20 @@ - (void)_allocateNodesFromElements:(NSArray *)elements if (ASSizeRangeHasSignificantArea(sizeRange)) { [self _layoutNode:node withConstrainedSize:sizeRange]; } - }); + }; + + if (strictlyOnCurrentThread) { + for (NSUInteger i = 0; i < nodeCount; i++) { + work(i); + } + } else { + dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0); + NSUInteger threadCount = 0; + if ([_dataSource dataControllerShouldSerializeNodeCreation:self]) { + threadCount = 1; + } + ASDispatchApply(nodeCount, queue, threadCount, work); + } } ASSignpostEnd(DataControllerBatch, self, "count: %lu", (unsigned long)nodeCount); @@ -622,13 +635,13 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet os_log_debug(ASCollectionLog(), "New content: %@", newMap.smallDescription); Class layoutDelegateClass = [self.layoutDelegate class]; - ++_editingTransactionGroupCount; - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - __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); - // Step 3: Call the layout delegate if possible. Otherwise, allocate and layout all elements + // Step 3: Call the layout delegate if possible. Otherwise, allocate and layout all elements + void (^step3)(BOOL) = ^(BOOL strictlyOnCurrentThread){ if (canDelegate) { + // Don't pass strictlyOnCurrentThread to the layout delegate. Instead give it + // total control over its threading behavior, as long as it blocks the + // calling thread while preparing the layout (which is part of the API contract). [layoutDelegateClass calculateLayoutWithContext:layoutContext]; } else { const auto elementsToProcess = [[NSMutableArray alloc] init]; @@ -642,7 +655,33 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet [elementsToProcess addObject:element]; } } - [self _allocateNodesFromElements:elementsToProcess]; + [self _allocateNodesFromElements:elementsToProcess + strictlyOnCurrentThread:strictlyOnCurrentThread]; + } + }; + + // Step 3 can be done on the main thread or on _editingTransactionQueue + // depending on an experiment. + BOOL mainThreadOnly = ASActivateExperimentalFeature(ASExperimentalMainThreadOnlyDataController); + if (mainThreadOnly) { + // In main-thread-only mode allocate and layout all nodes serially on the main thread. + // + // After this step, we'll still dispatch to _editingTransactionQueue only to schedule a block + // to _mainSerialQueue to execute next steps. This is not optimized because + // in theory we can skip _editingTransactionQueue entirely, but it's much safer + // because change sets will still flow through the pipeline in pretty the same way + // (main thread -> _editingTransactionQueue -> _mainSerialQueue) and so + // any methods that block on _editingTransactionQueue will still work. + step3(YES); + } + + ++_editingTransactionGroupCount; + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + __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); + + if (!mainThreadOnly) { + step3(NO); } // Step 4: Inform the delegate on main thread diff --git a/Source/Details/ASImageProtocols.h b/Source/Details/ASImageProtocols.h index d91b8a778..91a0d579f 100644 --- a/Source/Details/ASImageProtocols.h +++ b/Source/Details/ASImageProtocols.h @@ -92,6 +92,7 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { /** @abstract Downloads an image with the given URL. @param URL The URL of the image to download. + @param shouldRetry Whether to attempt to retry downloading if the remote host is currently unreachable. @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. @param downloadProgress The block to be invoked when the download of `URL` progresses. @param completion The block to be invoked when the download has completed, or has failed. @@ -100,6 +101,7 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { retain the identifier if you wish to use it later. */ - (nullable id)downloadImageWithURL:(NSURL *)URL + shouldRetry:(BOOL)shouldRetry callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion; @@ -117,6 +119,7 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { /** @abstract Downloads an image with the given URL. @param URL The URL of the image to download. + @param shouldRetry Whether to attempt to retry downloading if the remote host is currently unreachable. @param priority The priority at which the image should be downloaded. @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. @param downloadProgress The block to be invoked when the download of `URL` progresses. @@ -127,6 +130,7 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { retain the identifier if you wish to use it later. */ - (nullable id)downloadImageWithURL:(NSURL *)URL + shouldRetry:(BOOL)shouldRetry priority:(ASImageDownloaderPriority)priority callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress diff --git a/Source/Details/ASPINRemoteImageDownloader.mm b/Source/Details/ASPINRemoteImageDownloader.mm index df538f019..8576cc7d3 100644 --- a/Source/Details/ASPINRemoteImageDownloader.mm +++ b/Source/Details/ASPINRemoteImageDownloader.mm @@ -212,7 +212,7 @@ - (BOOL)sharedImageManagerSupportsMemoryRemoval } #endif -- (id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; +- (id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL { PINRemoteImageManager *manager = [self sharedPINRemoteImageManager]; PINRemoteImageManagerResult *result = [manager synchronousImageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode]; @@ -255,11 +255,13 @@ - (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL } - (nullable id)downloadImageWithURL:(NSURL *)URL + shouldRetry:(BOOL)shouldRetry callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion; + completion:(ASImageDownloaderCompletion)completion { return [self downloadImageWithURL:URL + shouldRetry:shouldRetry priority:ASImageDownloaderPriorityImminent // maps to default priority callbackQueue:callbackQueue downloadProgress:downloadProgress @@ -267,6 +269,7 @@ - (nullable id)downloadImageWithURL:(NSURL *)URL } - (nullable id)downloadImageWithURL:(NSURL *)URL + shouldRetry:(BOOL)shouldRetry priority:(ASImageDownloaderPriority)priority callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(ASImageDownloaderProgress)downloadProgress @@ -301,8 +304,13 @@ - (nullable id)downloadImageWithURL:(NSURL *)URL // extra downloads isn't worth the effort of rechecking caches every single time. In order to provide // feedback to the consumer about whether images are cached, we can't simply make the cache a no-op and // check the cache as part of this download. + PINRemoteImageManagerDownloadOptions options = PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache; + if (!shouldRetry) { + options |= PINRemoteImageManagerDownloadOptionsSkipRetry; + } + return [[self sharedPINRemoteImageManager] downloadImageWithURL:URL - options:PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache + options:options priority:pi_priority progressImage:nil progressDownload:progressDownload diff --git a/Source/Details/CoreGraphics+ASConvenience.h b/Source/Details/CoreGraphics+ASConvenience.h index a0805f170..e1e96033a 100644 --- a/Source/Details/CoreGraphics+ASConvenience.h +++ b/Source/Details/CoreGraphics+ASConvenience.h @@ -8,10 +8,7 @@ // #import - #import -#import - #import @@ -45,7 +42,7 @@ ASDISPLAYNODE_INLINE CGFloat ASCGFloatFromNumber(NSNumber *number) ASDISPLAYNODE_INLINE BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta) { - return fabs(size1.width - size2.width) < delta && fabs(size1.height - size2.height) < delta; + return ABS(size1.width - size2.width) < delta && ABS(size1.height - size2.height) < delta; }; NS_ASSUME_NONNULL_END diff --git a/Source/Details/Transactions/_ASAsyncTransaction.mm b/Source/Details/Transactions/_ASAsyncTransaction.mm index 35d9a43c5..48348ca8a 100644 --- a/Source/Details/Transactions/_ASAsyncTransaction.mm +++ b/Source/Details/Transactions/_ASAsyncTransaction.mm @@ -44,7 +44,7 @@ - (void)dealloc NSAssert(_operationCompletionBlock == nil, @"Should have been called and released before -dealloc"); } -- (void)callAndReleaseCompletionBlock:(BOOL)canceled; +- (void)callAndReleaseCompletionBlock:(BOOL)canceled { ASDisplayNodeAssertMainThread(); if (_operationCompletionBlock) { diff --git a/Source/Details/_ASDisplayViewAccessiblity.mm b/Source/Details/_ASDisplayViewAccessiblity.mm index 7fb79eaa2..8f3e12991 100644 --- a/Source/Details/_ASDisplayViewAccessiblity.mm +++ b/Source/Details/_ASDisplayViewAccessiblity.mm @@ -311,27 +311,18 @@ static void CollectAccessibilityElements(ASDisplayNode *node, NSMutableArray *el } } -@interface _ASDisplayView () { - NSArray *_accessibilityElements; -} - -@end - @implementation _ASDisplayView (UIAccessibilityContainer) #pragma mark - UIAccessibility -- (void)setAccessibilityElements:(NSArray *)accessibilityElements +- (void)setAccessibilityElements:(nullable NSArray *)accessibilityElements { - ASDisplayNodeAssertMainThread(); - // While it looks very strange to ignore the accessibilyElements param and set _accessibilityElements to nil, it is actually on purpose. - // _ASDisplayView's accessibilityElements method will always defer to the node for accessibilityElements when _accessibilityElements is - // nil. Calling setAccessibilityElements on _ASDisplayView is basically clearing the cache and forcing _ASDisplayView to ask the node - // for its accessibilityElements the next time they are requested. - _accessibilityElements = nil; + // this is a no-op. You should not be setting accessibilityElements directly on _ASDisplayView. + // if you wish to set accessibilityElements, do so in your node. UIKit will call _ASDisplayView's + // accessibilityElements which will in turn ask its node for its elements. } -- (NSArray *)accessibilityElements +- (nullable NSArray *)accessibilityElements { ASDisplayNodeAssertMainThread(); @@ -340,19 +331,19 @@ - (NSArray *)accessibilityElements return @[]; } - // when items become hidden/visible we have to manually clear the _accessibilityElements in order to get an updated version - // Instead, let's try computing the elements every time and see how badly it affects performance. - if (_accessibilityElements == nil || ASActivateExperimentalFeature(ASExperimentalDoNotCacheAccessibilityElements)) { - _accessibilityElements = [viewNode accessibilityElements]; - } - return _accessibilityElements; + // we no longer cache accessibilityElements. When caching, in order to provide correct element when items become hidden/visible + // we had to manually clear _accessibilityElements. This seemed like a heavy burden to place on a user, and one that is also + // not immediately obvious. While recomputing accessibilityElements may be expensive, this will only affect users that have + // voice over enabled (we checked to ensure performance did not suffer by not caching for an overall user base). For those + // users with voice over on, being correct is almost certainly more important than being performant. + return [viewNode accessibilityElements]; } @end @implementation ASDisplayNode (AccessibilityInternal) -- (NSArray *)accessibilityElements +- (nullable NSArray *)accessibilityElements { // NSObject implements the informal accessibility protocol. This means that all ASDisplayNodes already have an accessibilityElements // property. If an ASDisplayNode subclass has explicitly set the property, let's use that instead of traversing the node tree to try @@ -364,12 +355,14 @@ - (NSArray *)accessibilityElements if (!self.isNodeLoaded) { ASDisplayNodeFailAssert(@"Cannot access accessibilityElements since node is not loaded"); - return @[]; + return nil; } NSMutableArray *accessibilityElements = [[NSMutableArray alloc] init]; CollectAccessibilityElements(self, accessibilityElements); SortAccessibilityElements(accessibilityElements); - return accessibilityElements; + // If we did not find any accessibility elements, return nil instead of empty array. This allows a WKWebView within the node + // to participate in accessibility. + return accessibilityElements.count == 0 ? nil : accessibilityElements; } @end diff --git a/Source/Layout/ASCenterLayoutSpec.mm b/Source/Layout/ASCenterLayoutSpec.mm index 9bf29ea51..4c8ca5b77 100644 --- a/Source/Layout/ASCenterLayoutSpec.mm +++ b/Source/Layout/ASCenterLayoutSpec.mm @@ -17,7 +17,7 @@ @implementation ASCenterLayoutSpec - (instancetype)initWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions - child:(id)child; + child:(id)child { ASRelativeLayoutSpecPosition verticalPosition = [self verticalPositionFromCenteringOptions:centeringOptions]; ASRelativeLayoutSpecPosition horizontalPosition = [self horizontalPositionFromCenteringOptions:centeringOptions]; diff --git a/Source/Layout/ASInsetLayoutSpec.mm b/Source/Layout/ASInsetLayoutSpec.mm index 93fd6eea4..6beaefeda 100644 --- a/Source/Layout/ASInsetLayoutSpec.mm +++ b/Source/Layout/ASInsetLayoutSpec.mm @@ -39,7 +39,7 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner) @implementation ASInsetLayoutSpec -- (instancetype)initWithInsets:(UIEdgeInsets)insets child:(id)child; +- (instancetype)initWithInsets:(UIEdgeInsets)insets child:(id)child { if (!(self = [super init])) { return nil; diff --git a/Source/Layout/ASRatioLayoutSpec.mm b/Source/Layout/ASRatioLayoutSpec.mm index ceda856cd..120918276 100644 --- a/Source/Layout/ASRatioLayoutSpec.mm +++ b/Source/Layout/ASRatioLayoutSpec.mm @@ -31,7 +31,7 @@ + (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id)child; +- (instancetype)initWithRatio:(CGFloat)ratio child:(id)child { if (!(self = [super init])) { return nil; diff --git a/Source/Private/ASBatchFetching.h b/Source/Private/ASBatchFetching.h index fec0c6764..6682a55ad 100644 --- a/Source/Private/ASBatchFetching.h +++ b/Source/Private/ASBatchFetching.h @@ -34,13 +34,16 @@ NS_ASSUME_NONNULL_BEGIN @param scrollableDirections The possible scrolling directions of the scroll view. @param contentOffset The offset that the scrollview will scroll to. @param velocity The velocity of the scroll view (in points) at the moment the touch was released. + @param flipsHorizontallyInOppositeLayoutDirection Whether or not this scroll view flips its layout automatically in RTL. + See flipsHorizontallyInOppositeLayoutDirection in UICollectionViewLayout @return Whether or not the current state should proceed with batch fetching. */ ASDK_EXTERN BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView *scrollView, ASScrollDirection scrollDirection, ASScrollDirection scrollableDirections, CGPoint contentOffset, - CGPoint velocity); + CGPoint velocity, + BOOL flipsHorizontallyInOppositeLayoutDirection); /** @@ -55,6 +58,8 @@ ASDK_EXTERN BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView delegate); NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASBatchFetching.mm b/Source/Private/ASBatchFetching.mm index cf3c100ea..49e8e62f0 100644 --- a/Source/Private/ASBatchFetching.mm +++ b/Source/Private/ASBatchFetching.mm @@ -15,7 +15,8 @@ BOOL ASDisplayShouldFetchBatchForScrollView(UIScrollView delegate = scrollView.batchFetchingDelegate; BOOL visible = (scrollView.window != nil); - return ASDisplayShouldFetchBatchForContext(context, scrollDirection, scrollableDirections, bounds, contentSize, contentOffset, leadingScreens, visible, velocity, delegate); + BOOL shouldRenderRTLLayout = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:scrollView.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft; + return ASDisplayShouldFetchBatchForContext(context, scrollDirection, scrollableDirections, bounds, contentSize, contentOffset, leadingScreens, visible, shouldRenderRTLLayout, velocity, flipsHorizontallyInOppositeLayoutDirection, delegate); } BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, @@ -40,7 +42,9 @@ BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, CGPoint targetOffset, CGFloat leadingScreens, BOOL visible, + BOOL shouldRenderRTLLayout, CGPoint velocity, + BOOL flipsHorizontallyInOppositeLayoutDirection, id delegate) { // Do not allow fetching if a batch is already in-flight and hasn't been completed or cancelled @@ -79,13 +83,18 @@ BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context, } // If they are scrolling toward the head of content, don't batch fetch. - BOOL isScrollingTowardHead = (ASScrollDirectionContainsUp(scrollDirection) || ASScrollDirectionContainsLeft(scrollDirection)); + BOOL isScrollingTowardHead = (ASScrollDirectionContainsUp(scrollDirection) || (shouldRenderRTLLayout ? ASScrollDirectionContainsRight(scrollDirection) : ASScrollDirectionContainsLeft(scrollDirection))); if (isScrollingTowardHead) { return NO; } CGFloat triggerDistance = viewLength * leadingScreens; - CGFloat remainingDistance = contentLength - viewLength - offset; + CGFloat remainingDistance = 0; + if (!flipsHorizontallyInOppositeLayoutDirection && shouldRenderRTLLayout && ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { + remainingDistance = offset; + } else { + remainingDistance = contentLength - viewLength - offset; + } BOOL result = remainingDistance <= triggerDistance; if (delegate != nil && velocityLength > 0.0) { diff --git a/Source/Private/ASCollectionViewFlowLayoutInspector.mm b/Source/Private/ASCollectionViewFlowLayoutInspector.mm index 52d341c86..7e35a3966 100644 --- a/Source/Private/ASCollectionViewFlowLayoutInspector.mm +++ b/Source/Private/ASCollectionViewFlowLayoutInspector.mm @@ -34,7 +34,7 @@ @implementation ASCollectionViewFlowLayoutInspector { #pragma mark Lifecycle -- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout; +- (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout { NSParameterAssert(flowLayout); @@ -47,7 +47,7 @@ - (instancetype)initWithFlowLayout:(UICollectionViewFlowLayout *)flowLayout; #pragma mark ASCollectionViewLayoutInspecting -- (void)didChangeCollectionViewDelegate:(id)delegate; +- (void)didChangeCollectionViewDelegate:(id)delegate { if (delegate == nil) { memset(&_delegateFlags, 0, sizeof(_delegateFlags)); diff --git a/Source/Private/ASDispatch.h b/Source/Private/ASDispatch.h index caccf74de..6ef5a8c96 100644 --- a/Source/Private/ASDispatch.h +++ b/Source/Private/ASDispatch.h @@ -11,7 +11,7 @@ #import /** - * Like dispatch_apply, but you can set the thread count. 0 means 2*active CPUs. + * Like dispatch_apply, but you can set the thread count. 0 means letting dispatch_apply determine it. * * Note: The actual number of threads may be lower than threadCount, if libdispatch * decides the system can't handle it. In reality this rarely happens. diff --git a/Source/Private/ASDispatch.mm b/Source/Private/ASDispatch.mm index 769a9185d..b82032da7 100644 --- a/Source/Private/ASDispatch.mm +++ b/Source/Private/ASDispatch.mm @@ -9,43 +9,27 @@ #import #import - // Prefer C atomics in this file because ObjC blocks can't capture C++ atomics well. #import -/** - * Like dispatch_apply, but you can set the thread count. 0 means 2*active CPUs. - * - * Note: The actual number of threads may be lower than threadCount, if libdispatch - * decides the system can't handle it. In reality this rarely happens. - */ void ASDispatchApply(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)) { if (threadCount == 0) { - if (ASActivateExperimentalFeature(ASExperimentalDispatchApply)) { - dispatch_apply(iterationCount, queue, work); - return; + dispatch_apply(iterationCount, queue, work); + } else { + dispatch_group_t group = dispatch_group_create(); + __block atomic_size_t counter = ATOMIC_VAR_INIT(0); + for (NSUInteger t = 0; t < threadCount; t++) { + dispatch_group_async(group, queue, ^{ + size_t i; + while ((i = atomic_fetch_add(&counter, 1)) < iterationCount) { + work(i); + } + }); } - threadCount = NSProcessInfo.processInfo.activeProcessorCount * 2; - } - dispatch_group_t group = dispatch_group_create(); - __block atomic_size_t counter = ATOMIC_VAR_INIT(0); - for (NSUInteger t = 0; t < threadCount; t++) { - dispatch_group_async(group, queue, ^{ - size_t i; - while ((i = atomic_fetch_add(&counter, 1)) < iterationCount) { - work(i); - } - }); + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); } - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); }; -/** - * Like dispatch_async, but you can set the thread count. 0 means 2*active CPUs. - * - * Note: The actual number of threads may be lower than threadCount, if libdispatch - * decides the system can't handle it. In reality this rarely happens. - */ void ASDispatchAsync(size_t iterationCount, dispatch_queue_t queue, NSUInteger threadCount, NS_NOESCAPE void(^work)(size_t i)) { if (threadCount == 0) { threadCount = NSProcessInfo.processInfo.activeProcessorCount * 2; diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index fd77a0697..c4ea0a20f 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -485,7 +485,7 @@ - (void)setWillDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModif _willDisplayNodeContentWithRenderingContext = contextModifier; } -- (void)setDidDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)contextModifier; +- (void)setDidDisplayNodeContentWithRenderingContext:(ASDisplayNodeContextModifier)contextModifier { MutexLocker l(__instanceLock__); _didDisplayNodeContentWithRenderingContext = contextModifier; diff --git a/Source/Private/ASDisplayNode+FrameworkPrivate.h b/Source/Private/ASDisplayNode+FrameworkPrivate.h index ce53e79e9..b7e8b1538 100644 --- a/Source/Private/ASDisplayNode+FrameworkPrivate.h +++ b/Source/Private/ASDisplayNode+FrameworkPrivate.h @@ -321,7 +321,7 @@ NS_INLINE UIAccessibilityTraits ASInteractiveAccessibilityTraitsMask() { } @interface ASDisplayNode (AccessibilityInternal) -- (NSArray *)accessibilityElements; +- (nullable NSArray *)accessibilityElements; @end; @interface UIView (ASDisplayNodeInternal) diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index 42dbbfcb6..114e8119f 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -944,14 +944,15 @@ - (void)setEdgeAntialiasingMask:(CAEdgeAntialiasingMask)edgeAntialiasingMask - (UISemanticContentAttribute)semanticContentAttribute { - _bridge_prologue_read; - return _getFromViewOnly(semanticContentAttribute); + AS::MutexLocker l(__instanceLock__); + return _semanticContentAttribute; } - (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentAttribute { - _bridge_prologue_write; + AS::MutexLocker l(__instanceLock__); _setToViewOnly(semanticContentAttribute, semanticContentAttribute); + _semanticContentAttribute = semanticContentAttribute; #if YOGA [self semanticContentAttributeDidChange:semanticContentAttribute]; #endif @@ -1137,22 +1138,6 @@ - (BOOL)_locked_insetsLayoutMarginsFromSafeArea @implementation ASDisplayNode (UIViewBridgeAccessibility) -// Walks up the view tree to nil out all the cached accsesibilityElements. This is required when changing -// accessibility properties like accessibilityViewIsModal. -- (void)invalidateAccessibilityElements -{ - // If we are not caching accessibilityElements we don't need to do anything here. - if (ASActivateExperimentalFeature(ASExperimentalDoNotCacheAccessibilityElements)) { - return; - } - - // we want to check if we are on the main thread first, since _loaded checks the layer and can only be done on main - if (ASDisplayNodeThreadIsMain() && _loaded(self)) { - self.view.accessibilityElements = nil; - [self.supernode invalidateAccessibilityElements]; - } -} - - (BOOL)isAccessibilityElement { _bridge_prologue_read; @@ -1310,13 +1295,7 @@ - (BOOL)accessibilityElementsHidden - (void)setAccessibilityElementsHidden:(BOOL)accessibilityElementsHidden { _bridge_prologue_write; - BOOL oldHiddenValue = _getFromViewOnly(accessibilityElementsHidden); _setAccessibilityToViewAndProperty(_flags.accessibilityElementsHidden, accessibilityElementsHidden, accessibilityElementsHidden, accessibilityElementsHidden); - - // if we made a change, we need to clear the view's accessibilityElements cache. - if (!ASActivateExperimentalFeature(ASExperimentalDoNotCacheAccessibilityElements) && self.isNodeLoaded && oldHiddenValue != accessibilityElementsHidden) { - [self invalidateAccessibilityElements]; - } } - (BOOL)accessibilityViewIsModal @@ -1328,16 +1307,9 @@ - (BOOL)accessibilityViewIsModal - (void)setAccessibilityViewIsModal:(BOOL)accessibilityViewIsModal { _bridge_prologue_write; - BOOL oldAccessibilityViewIsModal = _getFromViewOnly(accessibilityViewIsModal); _setAccessibilityToViewAndProperty(_flags.accessibilityViewIsModal, accessibilityViewIsModal, accessibilityViewIsModal, accessibilityViewIsModal); - - // if we made a change, we need to clear the view's accessibilityElements cache. - if (!ASActivateExperimentalFeature(ASExperimentalDoNotCacheAccessibilityElements) && self.isNodeLoaded && oldAccessibilityViewIsModal != accessibilityViewIsModal) { - [self invalidateAccessibilityElements]; - } } - - (BOOL)shouldGroupAccessibilityChildren { _bridge_prologue_read; diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 22f36483b..b9f5f40d4 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -258,7 +258,8 @@ static constexpr CACornerMask kASCACornerAllCorners = // These properties are used on iOS 10 and lower, where safe area is not supported by UIKit. UIEdgeInsets _fallbackSafeAreaInsets; - + // Right-to-Left layout support + UISemanticContentAttribute _semanticContentAttribute; #pragma mark - ASDisplayNode (Debugging) ASLayout *_unflattenedLayout; @@ -332,6 +333,9 @@ static constexpr CACornerMask kASCACornerAllCorners = cornerRadius:(CGFloat)newCornerRadius maskedCorners:(CACornerMask)newMaskedCorners; +/// Update the Semantic Content Attribute. Trigger layout if this value has changed. +- (void)updateSemanticContentAttributeWithAttribute:(UISemanticContentAttribute)attribute; + /// Alternative initialiser for backing with a custom view class. Supports asynchronous display with _ASDisplayView subclasses. - (instancetype)initWithViewClass:(Class)viewClass; diff --git a/Source/Private/ASInternalHelpers.h b/Source/Private/ASInternalHelpers.h index 69a8eabc2..29fa26897 100644 --- a/Source/Private/ASInternalHelpers.h +++ b/Source/Private/ASInternalHelpers.h @@ -34,9 +34,6 @@ ASDK_EXTERN void ASPerformBlockOnMainThread(void (^block)(void)); /// Dispatches the given block to a background queue with priority of DISPATCH_QUEUE_PRIORITY_DEFAULT if not already run on a background queue ASDK_EXTERN void ASPerformBlockOnBackgroundThread(void (^block)(void)); // DISPATCH_QUEUE_PRIORITY_DEFAULT -/// For deallocation of objects on a background thread without GCD overhead / thread explosion -ASDK_EXTERN void ASPerformBackgroundDeallocation(id __strong _Nullable * _Nonnull object); - ASDK_EXTERN CGFloat ASScreenScale(void); ASDK_EXTERN CGSize ASFloorSizeValues(CGSize s); diff --git a/Source/Private/ASInternalHelpers.mm b/Source/Private/ASInternalHelpers.mm index 7811c3b6b..4d6e1fde8 100644 --- a/Source/Private/ASInternalHelpers.mm +++ b/Source/Private/ASInternalHelpers.mm @@ -140,11 +140,6 @@ void ASPerformBlockOnBackgroundThread(void (^block)(void)) } } -void ASPerformBackgroundDeallocation(id __strong _Nullable * _Nonnull object) -{ - [[ASDeallocQueue sharedDeallocationQueue] releaseObjectInBackground:object]; -} - Class _Nullable ASGetClassFromType(const char * _Nullable type) { // Class types all start with @" diff --git a/Source/Private/TextExperiment/Component/ASTextDebugOption.h b/Source/TextExperiment/Component/ASTextDebugOption.h similarity index 100% rename from Source/Private/TextExperiment/Component/ASTextDebugOption.h rename to Source/TextExperiment/Component/ASTextDebugOption.h diff --git a/Source/Private/TextExperiment/Component/ASTextDebugOption.mm b/Source/TextExperiment/Component/ASTextDebugOption.mm similarity index 100% rename from Source/Private/TextExperiment/Component/ASTextDebugOption.mm rename to Source/TextExperiment/Component/ASTextDebugOption.mm diff --git a/Source/Private/TextExperiment/Component/ASTextInput.h b/Source/TextExperiment/Component/ASTextInput.h similarity index 100% rename from Source/Private/TextExperiment/Component/ASTextInput.h rename to Source/TextExperiment/Component/ASTextInput.h diff --git a/Source/Private/TextExperiment/Component/ASTextInput.mm b/Source/TextExperiment/Component/ASTextInput.mm similarity index 100% rename from Source/Private/TextExperiment/Component/ASTextInput.mm rename to Source/TextExperiment/Component/ASTextInput.mm diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.h b/Source/TextExperiment/Component/ASTextLayout.h similarity index 100% rename from Source/Private/TextExperiment/Component/ASTextLayout.h rename to Source/TextExperiment/Component/ASTextLayout.h diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.mm b/Source/TextExperiment/Component/ASTextLayout.mm similarity index 99% rename from Source/Private/TextExperiment/Component/ASTextLayout.mm rename to Source/TextExperiment/Component/ASTextLayout.mm index 1567322ba..f01c25908 100644 --- a/Source/Private/TextExperiment/Component/ASTextLayout.mm +++ b/Source/TextExperiment/Component/ASTextLayout.mm @@ -2385,7 +2385,7 @@ static void ASTextDrawRun(ASTextLine *line, CTRunRef run, CGContextRef context, if (mode) { // CJK glyph, need rotated CGFloat ofs = (ascent - descent) * 0.5; CGFloat w = glyphAdvances[g].width * 0.5; - CGFloat x = x = line.position.x + verticalOffset + glyphPositions[g].y + (ofs - w); + CGFloat x = line.position.x + verticalOffset + glyphPositions[g].y + (ofs - w); CGFloat y = -line.position.y + size.height - glyphPositions[g].x - (ofs + w); if (mode == ASTextRunGlyphDrawModeVerticalRotateMove) { x += w; diff --git a/Source/Private/TextExperiment/Component/ASTextLine.h b/Source/TextExperiment/Component/ASTextLine.h similarity index 100% rename from Source/Private/TextExperiment/Component/ASTextLine.h rename to Source/TextExperiment/Component/ASTextLine.h diff --git a/Source/Private/TextExperiment/Component/ASTextLine.mm b/Source/TextExperiment/Component/ASTextLine.mm similarity index 100% rename from Source/Private/TextExperiment/Component/ASTextLine.mm rename to Source/TextExperiment/Component/ASTextLine.mm diff --git a/Source/Private/TextExperiment/String/ASTextAttribute.h b/Source/TextExperiment/String/ASTextAttribute.h similarity index 100% rename from Source/Private/TextExperiment/String/ASTextAttribute.h rename to Source/TextExperiment/String/ASTextAttribute.h diff --git a/Source/Private/TextExperiment/String/ASTextAttribute.mm b/Source/TextExperiment/String/ASTextAttribute.mm similarity index 100% rename from Source/Private/TextExperiment/String/ASTextAttribute.mm rename to Source/TextExperiment/String/ASTextAttribute.mm diff --git a/Source/Private/TextExperiment/String/ASTextRunDelegate.h b/Source/TextExperiment/String/ASTextRunDelegate.h similarity index 100% rename from Source/Private/TextExperiment/String/ASTextRunDelegate.h rename to Source/TextExperiment/String/ASTextRunDelegate.h diff --git a/Source/Private/TextExperiment/String/ASTextRunDelegate.mm b/Source/TextExperiment/String/ASTextRunDelegate.mm similarity index 100% rename from Source/Private/TextExperiment/String/ASTextRunDelegate.mm rename to Source/TextExperiment/String/ASTextRunDelegate.mm diff --git a/Source/Private/TextExperiment/Utility/ASTextUtilities.h b/Source/TextExperiment/Utility/ASTextUtilities.h similarity index 100% rename from Source/Private/TextExperiment/Utility/ASTextUtilities.h rename to Source/TextExperiment/Utility/ASTextUtilities.h diff --git a/Source/Private/TextExperiment/Utility/ASTextUtilities.mm b/Source/TextExperiment/Utility/ASTextUtilities.mm similarity index 100% rename from Source/Private/TextExperiment/Utility/ASTextUtilities.mm rename to Source/TextExperiment/Utility/ASTextUtilities.mm diff --git a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h b/Source/TextExperiment/Utility/NSAttributedString+ASText.h similarity index 100% rename from Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h rename to Source/TextExperiment/Utility/NSAttributedString+ASText.h diff --git a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.mm b/Source/TextExperiment/Utility/NSAttributedString+ASText.mm similarity index 100% rename from Source/Private/TextExperiment/Utility/NSAttributedString+ASText.mm rename to Source/TextExperiment/Utility/NSAttributedString+ASText.mm diff --git a/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.h b/Source/TextExperiment/Utility/NSParagraphStyle+ASText.h similarity index 100% rename from Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.h rename to Source/TextExperiment/Utility/NSParagraphStyle+ASText.h diff --git a/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.mm b/Source/TextExperiment/Utility/NSParagraphStyle+ASText.mm similarity index 100% rename from Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.mm rename to Source/TextExperiment/Utility/NSParagraphStyle+ASText.mm diff --git a/Source/TextKit/ASTextKitContext.mm b/Source/TextKit/ASTextKitContext.mm index 8c24644d4..d0b6708fd 100644 --- a/Source/TextKit/ASTextKitContext.mm +++ b/Source/TextKit/ASTextKitContext.mm @@ -35,12 +35,16 @@ - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString if (self = [super init]) { static AS::Mutex *mutex = NULL; static dispatch_once_t onceToken; - // Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. - dispatch_once(&onceToken, ^{ - mutex = new AS::Mutex(); - }); - if (mutex != NULL) { - mutex->lock(); + + BOOL useGlobalTextKitLock = !ASActivateExperimentalFeature(ASExperimentalDisableGlobalTextkitLock); + if (useGlobalTextKitLock) { + // Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. + dispatch_once(&onceToken, ^{ + mutex = new AS::Mutex(); + }); + if (mutex != NULL) { + mutex->lock(); + } } __instanceLock__ = std::make_shared(); @@ -76,7 +80,7 @@ - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString _textContainer.exclusionPaths = exclusionPaths; [_layoutManager addTextContainer:_textContainer]; - if (mutex != NULL) { + if (useGlobalTextKitLock && mutex != NULL) { mutex->unlock(); } } diff --git a/Source/TextKit/ASTextKitFontSizeAdjuster.mm b/Source/TextKit/ASTextKitFontSizeAdjuster.mm index 30591a87d..86465a2fc 100644 --- a/Source/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/Source/TextKit/ASTextKitFontSizeAdjuster.mm @@ -40,7 +40,7 @@ @implementation ASTextKitFontSizeAdjuster - (instancetype)initWithContext:(ASTextKitContext *)context constrainedSize:(CGSize)constrainedSize - textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes; + textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes { if (self = [super init]) { _context = context; diff --git a/Source/TextKit/ASTextKitRenderer.mm b/Source/TextKit/ASTextKitRenderer.mm index 34895cc3e..30fc33a9a 100644 --- a/Source/TextKit/ASTextKitRenderer.mm +++ b/Source/TextKit/ASTextKitRenderer.mm @@ -190,7 +190,7 @@ - (BOOL)canUseFastPath #pragma mark - Drawing -- (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds; +- (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds { // We add an assertion so we can track the rare conditions where a graphics context is not present ASDisplayNodeAssertNotNil(context, @"This is no good without a context."); diff --git a/Tests/ASBasicImageDownloaderTests.mm b/Tests/ASBasicImageDownloaderTests.mm index 4e5ff2862..6f87c0f89 100644 --- a/Tests/ASBasicImageDownloaderTests.mm +++ b/Tests/ASBasicImageDownloaderTests.mm @@ -28,6 +28,7 @@ - (void)testAsynchronouslyDownloadTheSameURLTwice subdirectory:@"TestResources"]; [downloader downloadImageWithURL:URL + shouldRetry:YES callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) downloadProgress:nil completion:^(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { @@ -35,6 +36,7 @@ - (void)testAsynchronouslyDownloadTheSameURLTwice }]; [downloader downloadImageWithURL:URL + shouldRetry:YES callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) downloadProgress:nil completion:^(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { diff --git a/Tests/ASBatchFetchingTests.mm b/Tests/ASBatchFetchingTests.mm index 99b67f05c..96ef717e3 100644 --- a/Tests/ASBatchFetchingTests.mm +++ b/Tests/ASBatchFetchingTests.mm @@ -30,91 +30,192 @@ @implementation ASBatchFetchingTests - (void)testBatchNullState { ASBatchContext *context = [[ASBatchContext alloc] init]; - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, CGRectZero, CGSizeZero, CGPointZero, 0.0, YES, CGPointZero, nil); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, CGRectZero, CGSizeZero, CGPointZero, 0.0, YES, NO, CGPointZero, NO, nil); XCTAssert(shouldFetch == NO, @"Should not fetch in the null state"); + + // test RTL + BOOL shouldFetchRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, CGRectZero, CGSizeZero, CGPointZero, 0.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(shouldFetchRTL == NO, @"Should not fetch in the null state"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + BOOL shouldFetchRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, CGRectZero, CGSizeZero, CGPointZero, 0.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(shouldFetchRTLFlip == NO, @"Should not fetch in the null state"); } - (void)testBatchAlreadyFetching { ASBatchContext *context = [[ASBatchContext alloc] init]; [context beginBatchFetching]; - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(shouldFetch == NO, @"Should not fetch when context is already fetching"); + + // test RTL + BOOL shouldFetchRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(shouldFetchRTL == NO, @"Should not fetch when context is already fetching"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + BOOL shouldFetchRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(shouldFetchRTLFlip == NO, @"Should not fetch in the null state"); } - (void)testUnsupportedScrollDirections { ASBatchContext *context = [[ASBatchContext alloc] init]; - BOOL fetchRight = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil); + BOOL fetchRight = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(fetchRight == YES, @"Should fetch for scrolling right"); - BOOL fetchDown = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil); + BOOL fetchDown = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(fetchDown == YES, @"Should fetch for scrolling down"); - BOOL fetchUp = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil); + BOOL fetchUp = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(fetchUp == NO, @"Should not fetch for scrolling up"); - BOOL fetchLeft = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, CGPointZero, nil); + BOOL fetchLeft = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(fetchLeft == NO, @"Should not fetch for scrolling left"); + + // test RTL + BOOL fetchRightRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(fetchRightRTL == NO, @"Should not fetch for scrolling right"); + BOOL fetchDownRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(fetchDownRTL == YES, @"Should fetch for scrolling down"); + BOOL fetchUpRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(fetchUpRTL == NO, @"Should not fetch for scrolling up"); + BOOL fetchLeftRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(fetchLeftRTL == YES, @"Should fetch for scrolling left"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + BOOL fetchRightRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(fetchRightRTLFlip == NO, @"Should fetch for scrolling right"); + BOOL fetchDownRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(fetchDownRTLFlip == YES, @"Should fetch for scrolling down"); + BOOL fetchUpRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, ASScrollDirectionVerticalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(fetchUpRTLFlip == NO, @"Should not fetch for scrolling up"); + BOOL fetchLeftRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(fetchLeftRTLFlip == YES, @"Should not fetch for scrolling left"); } - (void)testVerticalScrollToExactLeading { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // scroll to 1-screen top offset, height is 1 screen, so bottom is 1 screen away from end of content - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0, YES, CGPointZero, nil); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling to exactly 1 leading screen away"); + + // test RTL + BOOL shouldFetchRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(shouldFetchRTL == YES, @"Fetch should begin when vertically scrolling to exactly 1 leading screen away"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + BOOL shouldFetchRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(shouldFetchRTLFlip == YES, @"Fetch should begin when vertically scrolling to exactly 1 leading screen away"); } - (void)testVerticalScrollToLessThanLeading { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, scroll only 1/2 of one screen - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0, YES, CGPointZero, nil); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(shouldFetch == NO, @"Fetch should not begin when vertically scrolling less than the leading distance away"); + + // test RTL + BOOL shouldFetchRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(shouldFetchRTL == NO, @"Fetch should not begin when vertically scrolling less than the leading distance away"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + BOOL shouldFetchRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(shouldFetchRTLFlip == NO, @"Fetch should not begin when vertically scrolling less than the leading distance away"); } - (void)testVerticalScrollingPastContentSize { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, top offset to 3-screens, height 1 screen, so its 1 screen past the leading - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0, YES, CGPointZero, nil); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size"); + + // test RTL + BOOL shouldFetchRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(shouldFetchRTL == YES, @"Fetch should begin when vertically scrolling past the content size"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + BOOL shouldFetchRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(shouldFetchRTLFlip == YES, @"Fetch should begin when vertically scrolling past the content size"); } - (void)testHorizontalScrollToExactLeading { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // scroll to 1-screen left offset, width is 1 screen, so right is 1 screen away from end of content - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionVerticalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0, YES, CGPointZero, nil); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionVerticalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(shouldFetch == YES, @"Fetch should begin when horizontally scrolling to exactly 1 leading screen away"); + + // test RTL + BOOL shouldFetchRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionVerticalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(shouldFetchRTL == YES, @"Fetch should begin when horizontally scrolling to exactly 1 leading screen away"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + BOOL shouldFetchRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionVerticalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(shouldFetchRTLFlip == YES, @"Fetch should begin when horizontally scrolling to exactly 1 leading screen away"); } - (void)testHorizontalScrollToLessThanLeading { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, scroll only 1/2 of one screen - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0, YES, CGPointZero, nil); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(shouldFetch == NO, @"Fetch should not begin when horizontally scrolling less than the leading distance away"); + + // In RTL since scrolling is reversed, our remaining distance is actually our offset (0.5) which is less than our leading screen (1). So we do want to fetch + BOOL shouldFetchRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(shouldFetchRTL == YES, @"Fetch should begin when horizontally scrolling less than the leading distance away"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + BOOL shouldFetchRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(shouldFetchRTLFlip == NO, @"Fetch should not begin when horizontally scrolling less than the leading distance away"); } - (void)testHorizontalScrollingPastContentSize { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // 3 screens of content, left offset to 3-screens, width 1 screen, so its 1 screen past the leading - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0, YES, CGPointZero, nil); - XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size"); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0, YES, NO, CGPointZero, NO, nil); + XCTAssert(shouldFetch == YES, @"Fetch should begin when horizontally scrolling past the content size"); + + // In RTL scrolling is reversed, our remaining distance is actually our offset (3) which is more than our leading screen (1). So we do no fetch + BOOL shouldFetchRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(shouldFetchRTL == NO, @"Fetch not should begin when horizontally scrolling past the content size"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + // 3 screens of content, left offset to 3-screens, width 1 screen, so its 1 screen past the leading + BOOL shouldFetchFlipRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(shouldFetchFlipRTL == YES, @"Fetch should begin when horizontally scrolling past the content size"); } - (void)testVerticalScrollingSmallContentSize { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // when the content size is < screen size, the target offset will always be 0 - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0, YES, CGPointZero, nil); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree"); + + // test RTL + BOOL shouldFetchRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(shouldFetchRTL == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + BOOL shouldFetchRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, ASScrollDirectionVerticalDirections, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(shouldFetchRTLFlip == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree"); } - (void)testHorizontalScrollingSmallContentSize { CGFloat screen = 1.0; ASBatchContext *context = [[ASBatchContext alloc] init]; // when the content size is < screen size, the target offset will always be 0 - BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0, YES, CGPointZero, nil); + BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0, YES, NO, CGPointZero, NO, nil); XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree"); + + // test RTL + BOOL shouldFetchRTL = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0, YES, YES, CGPointZero, NO, nil); + XCTAssert(shouldFetchRTL == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree"); + + // test RTL with a layout that automatically flips (should act the same as LTR) + BOOL shouldFetchRTLFlip = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, ASScrollDirectionHorizontalDirections, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0, YES, YES, CGPointZero, YES, nil); + XCTAssert(shouldFetchRTLFlip == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree"); } @end diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index 496c1feba..af0230c5d 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -24,6 +24,7 @@ @interface ASTextCellNodeWithSetSelectedCounter : ASTextCellNode @property (nonatomic) NSUInteger setSelectedCounter; @property (nonatomic) NSUInteger applyLayoutAttributesCount; +@property (nonatomic) NSUInteger didEnterPreloadStateCount; @end @@ -40,6 +41,12 @@ - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttribut _applyLayoutAttributesCount++; } +- (void)didEnterPreloadState +{ + [super didEnterPreloadState]; + _didEnterPreloadStateCount++; +} + @end @interface ASTestSectionContext : NSObject @@ -177,7 +184,8 @@ - (void)setUp { [super setUp]; ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalOptimizeDataControllerPipeline; + config.experimentalFeatures = ASExperimentalOptimizeDataControllerPipeline + | ASExperimentalRangeUpdateOnChangesetUpdate; [ASConfigurationManager test_resetWithConfiguration:config]; } @@ -387,6 +395,31 @@ - (void)testThatCollectionNodeConformsToExpectedProtocols XCTAssert([node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]); } +/** + * Test that hit tests are correct when the collection node is inverted. + */ +- (void)testInvertedCollectionViewHitTest +{ + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + [window setRootViewController:testController]; + [window makeKeyAndVisible]; + + testController.collectionNode.inverted = true; + [testController.collectionNode reloadData]; + [testController.collectionNode waitUntilAllUpdatesAreProcessed]; + [testController.collectionView layoutIfNeeded]; + + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; + UICollectionViewCell *cell = [testController.collectionView cellForItemAtIndexPath:indexPath]; + ASDisplayNode *node = [testController.collectionNode nodeForItemAtIndexPath:indexPath]; + + CGPoint testPointInCollectionView = CGPointMake(CGRectGetMidX(cell.frame), CGRectGetMidY(cell.frame)); + UIView *hitTestView = [testController.collectionView hitTest:testPointInCollectionView withEvent:nil]; + + XCTAssertEqualObjects(hitTestView, node.view, @"Expected node's view to be the result of the hit test."); +} + #pragma mark - Update Validations #define updateValidationTestPrologue \ @@ -493,6 +526,81 @@ - (void)testThatDeletingAndReloadingASectionThrowsAnException } completion:nil]); } +- (void)testItemsInsertedIntoThePreloadRangeGetPreloaded +{ + updateValidationTestPrologue + + ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1, .trailingBufferScreenfuls = 1 }; + [cn setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; + [cn updateCurrentRangeWithMode:ASLayoutRangeModeMinimum]; + + __weak ASCollectionViewTestController *weakController = testController; + NSIndexPath *lastVisibleIndex = [cv indexPathsForVisibleItems].lastObject; + + NSInteger itemCount = weakController.asyncDelegate->_itemCounts[lastVisibleIndex.section]; + BOOL isLastItemInSection = lastVisibleIndex.row == itemCount - 1; + NSInteger nextItemSection = isLastItemInSection ? lastVisibleIndex.section + 1 : lastVisibleIndex.section; + NSInteger nextItemRow = isLastItemInSection ? 0 : lastVisibleIndex.row + 1; + + XCTAssertTrue(weakController.asyncDelegate->_itemCounts.size() > nextItemSection, @"There is no items after the last visible item. Update the section/row counts so that there is one for this test to work properly."); + XCTAssertTrue(weakController.asyncDelegate->_itemCounts[nextItemSection] > nextItemRow, @"There is no items after the last visible item. Update the section/row counts so that there is one for this test to work properly."); + + NSIndexPath *nextItemIndexPath = [NSIndexPath indexPathForRow:nextItemRow inSection:nextItemSection]; + ASTextCellNodeWithSetSelectedCounter *nodeBeforeUpdate = (ASTextCellNodeWithSetSelectedCounter *)[cv nodeForItemAtIndexPath:nextItemIndexPath]; + + XCTestExpectation *noChangeDone = [self expectationWithDescription:@"Batch update with no changes done and completion block has been called. Tuning params set to 1 screenful."]; + + __block ASTextCellNodeWithSetSelectedCounter *nodeAfterUpdate; + [cv performBatchUpdates:^{ + } completion:^(BOOL finished) { + nodeAfterUpdate = (ASTextCellNodeWithSetSelectedCounter *)[cv nodeForItemAtIndexPath:nextItemIndexPath]; + [noChangeDone fulfill]; + }]; + + [self waitForExpectations:@[ noChangeDone ] timeout:1]; + + XCTAssertTrue(nodeBeforeUpdate == nodeAfterUpdate, @"Node should not have changed since no updates were made."); + XCTAssertTrue(nodeAfterUpdate.didEnterPreloadStateCount == 1, @"Node should have been preloaded."); + + XCTestExpectation *changeDone = [self expectationWithDescription:@"Batch update with changes done and completion block has been called. Tuning params set to 1 screenful."]; + + [cv performBatchUpdates:^{ + NSArray *indexPaths = @[ nextItemIndexPath ]; + [cv deleteItemsAtIndexPaths:indexPaths]; + [cv insertItemsAtIndexPaths:indexPaths]; + } completion:^(BOOL finished) { + nodeAfterUpdate = (ASTextCellNodeWithSetSelectedCounter *)[cv nodeForItemAtIndexPath:nextItemIndexPath]; + [changeDone fulfill]; + }]; + + [self waitForExpectations:@[ changeDone ] timeout:1]; + + XCTAssertTrue(nodeBeforeUpdate != nodeAfterUpdate, @"Node should have changed after updating."); + XCTAssertTrue(nodeAfterUpdate.didEnterPreloadStateCount == 1, @"New node should have been preloaded."); + + minimumPreloadParams = { .leadingBufferScreenfuls = 0, .trailingBufferScreenfuls = 0 }; + [cn setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; + [cn updateCurrentRangeWithMode:ASLayoutRangeModeMinimum]; + + XCTestExpectation *changeDoneZeroSreenfuls = [self expectationWithDescription:@"Batch update with changes done and completion block has been called. Tuning params set to 0 screenful."]; + + nodeBeforeUpdate = nodeAfterUpdate; + __block ASTextCellNodeWithSetSelectedCounter *nodeAfterUpdateZeroSreenfuls; + [cv performBatchUpdates:^{ + NSArray *indexPaths = @[ nextItemIndexPath ]; + [cv deleteItemsAtIndexPaths:indexPaths]; + [cv insertItemsAtIndexPaths:indexPaths]; + } completion:^(BOOL finished) { + nodeAfterUpdateZeroSreenfuls = (ASTextCellNodeWithSetSelectedCounter *)[cv nodeForItemAtIndexPath:nextItemIndexPath]; + [changeDoneZeroSreenfuls fulfill]; + }]; + + [self waitForExpectations:@[ changeDoneZeroSreenfuls ] timeout:1]; + + XCTAssertTrue(nodeBeforeUpdate != nodeAfterUpdateZeroSreenfuls, @"Node should have changed after updating."); + XCTAssertTrue(nodeAfterUpdateZeroSreenfuls.didEnterPreloadStateCount == 0, @"New node should NOT have been preloaded."); +} + - (void)testCellNodeLayoutAttributes { updateValidationTestPrologue diff --git a/Tests/ASConfigurationTests.mm b/Tests/ASConfigurationTests.mm index 755375af8..6bd23e0f4 100644 --- a/Tests/ASConfigurationTests.mm +++ b/Tests/ASConfigurationTests.mm @@ -28,7 +28,9 @@ ASExperimentalDispatchApply, ASExperimentalDrawingGlobal, ASExperimentalOptimizeDataControllerPipeline, - ASExperimentalDoNotCacheAccessibilityElements, + ASExperimentalDisableGlobalTextkitLock, + ASExperimentalMainThreadOnlyDataController, + ASExperimentalRangeUpdateOnChangesetUpdate, }; @interface ASConfigurationTests : ASTestCase @@ -51,7 +53,9 @@ + (NSArray *)names { @"exp_dispatch_apply", @"exp_drawing_global", @"exp_optimize_data_controller_pipeline", - @"exp_do_not_cache_accessibility_elements", + @"exp_disable_global_textkit_lock", + @"exp_main_thread_only_data_controller", + @"exp_range_update_on_changeset_update" ]; } diff --git a/Tests/ASNavigationControllerTests.mm b/Tests/ASDKNavigationControllerTests.mm similarity index 94% rename from Tests/ASNavigationControllerTests.mm rename to Tests/ASDKNavigationControllerTests.mm index 01d1a0251..22897963a 100644 --- a/Tests/ASNavigationControllerTests.mm +++ b/Tests/ASDKNavigationControllerTests.mm @@ -1,5 +1,5 @@ // -// ASNavigationControllerTests.mm +// ASDKNavigationControllerTests.mm // Texture // // Copyright (c) Pinterest, Inc. All rights reserved. @@ -10,10 +10,10 @@ #import -@interface ASNavigationControllerTests : XCTestCase +@interface ASDKNavigationControllerTests : XCTestCase @end -@implementation ASNavigationControllerTests +@implementation ASDKNavigationControllerTests - (void)testSetViewControllers { ASDKViewController *firstController = [ASDKViewController new]; diff --git a/Tests/ASDisplayViewAccessibilityTests.mm b/Tests/ASDisplayViewAccessibilityTests.mm index 2b8a848b1..d511e98fe 100644 --- a/Tests/ASDisplayViewAccessibilityTests.mm +++ b/Tests/ASDisplayViewAccessibilityTests.mm @@ -24,6 +24,7 @@ #import #import #import "ASDisplayNodeTestsHelper.h" +#import extern void SortAccessibilityElements(NSMutableArray *elements); @@ -187,6 +188,34 @@ - (void)testAccessibilityNonLayerbackedNodesOperationInNonContainer XCTAssertTrue([[updatedElements2.lastObject accessibilityLabel] isEqualToString:@"world"]); } +- (void)testAccessibilityElementsAreNilForWrappedWKWebView { + ASDisplayNode *container = [[ASDisplayNode alloc] init]; + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 320, 560)]; + [window addSubnode:container]; + [window makeKeyAndVisible]; + + container.frame = CGRectMake(50, 50, 200, 600); + WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 200, 400)]; + [container.view addSubview:webView]; + + NSString *htmlString = + @"" + @" " + @" " + @" " + @" " + @"

Texture is Awesome!

" + @"

Especially when web views inside nodes are accessible.

" + @" " + @" "; + [webView loadHTMLString:htmlString baseURL:nil]; + + // Accessibility elements should be nil in this case, because + // WKWebView handles accessibility out of process. + NSArray *accessibilityElements = container.accessibilityElements; + XCTAssertNil(accessibilityElements); +} + #pragma mark - #pragma mark UIAccessibilityAction Forwarding diff --git a/Tests/ASMultiplexImageNodeTests.mm b/Tests/ASMultiplexImageNodeTests.mm index 8ebd77c45..3806e46ee 100644 --- a/Tests/ASMultiplexImageNodeTests.mm +++ b/Tests/ASMultiplexImageNodeTests.mm @@ -222,14 +222,14 @@ - (void)testUncachedDownload // Mock a 50%-progress URL download. const CGFloat mockedProgress = 0.5; - OCMExpect([mockDownloader downloadImageWithURL:[self _testImageURL] priority:ASImageDownloaderPriorityPreload callbackQueue:OCMOCK_ANY downloadProgress:[OCMArg isNotNil] completion:[OCMArg isNotNil]]) + OCMExpect([mockDownloader downloadImageWithURL:[self _testImageURL] shouldRetry:YES priority:ASImageDownloaderPriorityPreload callbackQueue:OCMOCK_ANY downloadProgress:[OCMArg isNotNil] completion:[OCMArg isNotNil]]) .andDo(^(NSInvocation *inv){ // Simulate progress. - ASImageDownloaderProgress progressBlock = [inv as_argumentAtIndexAsObject:5]; + ASImageDownloaderProgress progressBlock = [inv as_argumentAtIndexAsObject:6]; progressBlock(mockedProgress); // Simulate completion. - ASImageDownloaderCompletion completionBlock = [inv as_argumentAtIndexAsObject:6]; + ASImageDownloaderCompletion completionBlock = [inv as_argumentAtIndexAsObject:7]; completionBlock([self _testImage], nil, nil, nil); }); diff --git a/Tests/ASNetworkImageNodeTests.mm b/Tests/ASNetworkImageNodeTests.mm index a707cd163..af2d1a35f 100644 --- a/Tests/ASNetworkImageNodeTests.mm +++ b/Tests/ASNetworkImageNodeTests.mm @@ -43,7 +43,7 @@ - (void)DISABLED_testThatProgressBlockIsSetAndClearedCorrectlyOnVisibility node.URL = [NSURL URLWithString:@"http://imageA"]; // Enter preload range, wait for download start. - [[[downloader expect] andForwardToRealObject] downloadImageWithURL:[OCMArg isNotNil] callbackQueue:OCMOCK_ANY downloadProgress:OCMOCK_ANY completion:OCMOCK_ANY]; + [[[downloader expect] andForwardToRealObject] downloadImageWithURL:[OCMArg isNotNil] shouldRetry:OCMOCK_ANY callbackQueue:OCMOCK_ANY downloadProgress:OCMOCK_ANY completion:OCMOCK_ANY]; [node enterInterfaceState:ASInterfaceStatePreload]; [downloader verifyWithDelay:5]; @@ -138,7 +138,7 @@ - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier // nop } -- (id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion +- (id)downloadImageWithURL:(NSURL *)URL shouldRetry:(BOOL)shouldRetry callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion { return @(_currentDownloadID++); } diff --git a/Tests/ASTableViewTests.mm b/Tests/ASTableViewTests.mm index 0d07d129b..b908a8751 100644 --- a/Tests/ASTableViewTests.mm +++ b/Tests/ASTableViewTests.mm @@ -118,6 +118,7 @@ - (void)dealloc @interface ASTestTextCellNode : ASTextCellNode /** Calculated by counting how many times -layoutSpecThatFits: is called on the main thread. */ @property (nonatomic) int numberOfLayoutsOnMainThread; +@property (nonatomic) NSUInteger didEnterPreloadStateCount; @end @implementation ASTestTextCellNode @@ -130,6 +131,12 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize return [super layoutSpecThatFits:constrainedSize]; } +- (void)didEnterPreloadState +{ + [super didEnterPreloadState]; + _didEnterPreloadStateCount++; +} + @end @interface ASTableViewFilledDataSource : NSObject @@ -232,6 +239,36 @@ - (ASSizeRange)tableView:(ASTableView *)tableView constrainedSizeForRowAtIndexPa @end +@interface ATableViewTestController: UIViewController + +@property (nonatomic) ASTableNode *tableNode; +@property (nonatomic) ASTableViewFilledDataSource *dataSource; + +@end + +@implementation ATableViewTestController + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + ASTableNode *tableNode = [[ASTableNode alloc] initWithStyle:UITableViewStylePlain]; + tableNode.frame = CGRectMake(0, 0, 100, 500); + + ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; + + tableNode.delegate = dataSource; + tableNode.dataSource = dataSource; + + self.tableNode = tableNode; + self.dataSource = dataSource; + + [self.view addSubview:self.tableNode.view]; + } + return self; +} + +@end + @interface ASTableViewTests : ASTestCase @property (nonatomic, retain) ASTableView *testTableView; @end @@ -242,7 +279,8 @@ - (void)setUp { [super setUp]; ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalOptimizeDataControllerPipeline; + config.experimentalFeatures = ASExperimentalOptimizeDataControllerPipeline + | ASExperimentalRangeUpdateOnChangesetUpdate; [ASConfigurationManager test_resetWithConfiguration:config]; } @@ -761,6 +799,93 @@ - (void)testThatNilBatchUpdatesCanBeSubmitted [node performBatchAnimated:NO updates:nil completion:nil]; } +- (void)testItemsInsertedIntoThePreloadRangeGetPreloaded +{ + // Start table node setup + ATableViewTestController *testController = [[ATableViewTestController alloc] initWithNibName:nil bundle:nil]; + ASTableNode *tableNode = testController.tableNode; + ASTableViewFilledDataSource *dataSource = testController.dataSource; + + UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + window.rootViewController = testController; + [window makeKeyAndVisible]; + + ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1, .trailingBufferScreenfuls = 1 }; + [tableNode setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; + [tableNode updateCurrentRangeWithMode:ASLayoutRangeModeMinimum]; + + [tableNode reloadData]; + [tableNode waitUntilAllUpdatesAreProcessed]; + [testController.tableNode.view layoutIfNeeded]; + // End table node setup + + + NSIndexPath *lastVisibleIndex = [[tableNode indexPathsForVisibleRows] sortedArrayUsingSelector:@selector(compare:)].lastObject; + + NSInteger itemCount = dataSource.rowsPerSection; + BOOL isLastItemInSection = lastVisibleIndex.row == itemCount - 1; + NSInteger nextItemSection = isLastItemInSection ? lastVisibleIndex.section + 1 : lastVisibleIndex.section; + NSInteger nextItemRow = isLastItemInSection ? 0 : lastVisibleIndex.row + 1; + + XCTAssertTrue(dataSource.numberOfSections > nextItemSection, @"There is no items after the last visible item. Update the section/row counts so that there is one for this test to work properly."); + XCTAssertTrue(dataSource.rowsPerSection > nextItemRow, @"There is no items after the last visible item. Update the section/row counts so that there is one for this test to work properly."); + + NSIndexPath *nextItemIndexPath = [NSIndexPath indexPathForRow:nextItemRow inSection:nextItemSection]; + ASTestTextCellNode *nodeBeforeUpdate = (ASTestTextCellNode *)[tableNode nodeForRowAtIndexPath:nextItemIndexPath]; + + XCTestExpectation *noChangeDone = [self expectationWithDescription:@"Batch update with no changes done and completion block has been called. Tuning params set to 1 screenful."]; + + __block ASTestTextCellNode *nodeAfterUpdate; + [tableNode performBatchUpdates:^{ + } completion:^(BOOL finished) { + nodeAfterUpdate = (ASTestTextCellNode *)[tableNode nodeForRowAtIndexPath:nextItemIndexPath]; + [noChangeDone fulfill]; + }]; + + [self waitForExpectations:@[ noChangeDone ] timeout:1]; + + XCTAssertTrue(nodeBeforeUpdate == nodeAfterUpdate, @"Node should not have changed since no updates were made."); + XCTAssertTrue(nodeAfterUpdate.didEnterPreloadStateCount == 1, @"Node should have been preloaded."); + + XCTestExpectation *changeDone = [self expectationWithDescription:@"Batch update with changes done and completion block has been called. Tuning params set to 1 screenful."]; + + [tableNode performBatchUpdates:^{ + NSArray *indexPaths = @[ nextItemIndexPath ]; + [tableNode deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; + [tableNode insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; + } completion:^(BOOL finished) { + nodeAfterUpdate = (ASTestTextCellNode *)[tableNode nodeForRowAtIndexPath:nextItemIndexPath]; + [changeDone fulfill]; + }]; + + [self waitForExpectations:@[ changeDone ] timeout:1]; + + XCTAssertTrue(nodeBeforeUpdate != nodeAfterUpdate, @"Node should have changed after updating."); + XCTAssertTrue(nodeAfterUpdate.didEnterPreloadStateCount == 1, @"New node should have been preloaded."); + + minimumPreloadParams = { .leadingBufferScreenfuls = 0, .trailingBufferScreenfuls = 0 }; + [tableNode setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; + [tableNode updateCurrentRangeWithMode:ASLayoutRangeModeMinimum]; + + XCTestExpectation *changeDoneZeroSreenfuls = [self expectationWithDescription:@"Batch update with changes done and completion block has been called. Tuning params set to 0 screenful."]; + + nodeBeforeUpdate = nodeAfterUpdate; + __block ASTestTextCellNode *nodeAfterUpdateZeroSreenfuls; + [tableNode performBatchUpdates:^{ + NSArray *indexPaths = @[ nextItemIndexPath ]; + [tableNode deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; + [tableNode insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; + } completion:^(BOOL finished) { + nodeAfterUpdateZeroSreenfuls = (ASTestTextCellNode *)[tableNode nodeForRowAtIndexPath:nextItemIndexPath]; + [changeDoneZeroSreenfuls fulfill]; + }]; + + [self waitForExpectations:@[ changeDoneZeroSreenfuls ] timeout:1]; + + XCTAssertTrue(nodeBeforeUpdate != nodeAfterUpdateZeroSreenfuls, @"Node should have changed after updating."); + XCTAssertTrue(nodeAfterUpdateZeroSreenfuls.didEnterPreloadStateCount == 0, @"New node should NOT have been preloaded."); +} + // https://github.com/facebook/AsyncDisplayKit/issues/2252#issuecomment-263689979 - (void)testIssue2252 { diff --git a/Tests/ASTextNodeTests.mm b/Tests/ASTextNodeTests.mm index e01f2516f..e0291b787 100644 --- a/Tests/ASTextNodeTests.mm +++ b/Tests/ASTextNodeTests.mm @@ -61,6 +61,12 @@ @implementation ASTextNodeTests - (void)setUp { [super setUp]; + + // Reset experimental features to test the first version of ASTextNode. + ASConfiguration *config = [ASConfiguration new]; + config.experimentalFeatures = kNilOptions; + [ASConfigurationManager test_resetWithConfiguration:config]; + _textNode = [[ASTextNode alloc] init]; _textNodeBucket = [[NSMutableArray alloc] init]; diff --git a/Tests/Common/ASTestCase.mm b/Tests/Common/ASTestCase.mm index 30b42bde4..81236d271 100644 --- a/Tests/Common/ASTestCase.mm +++ b/Tests/Common/ASTestCase.mm @@ -85,9 +85,6 @@ - (void)invokeTest @autoreleasepool { [super invokeTest]; } - - // Now that the autorelease pool is drained, drain the dealloc queue also. - [[ASDeallocQueue sharedDeallocationQueue] drain]; } + (ASTestCase *)currentTestCase diff --git a/Texture.podspec b/Texture.podspec index ce8cc6997..ac68625aa 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Texture' - spec.version = '3.0.0' + spec.version = '3.1.0' spec.license = { :type => 'Apache 2', } spec.homepage = 'http://texturegroup.org' spec.authors = { 'Huy Nguyen' => 'hi@huynguyen.dev', 'Garrett Moon' => 'garrett@excitedpixel.com', 'Scott Goodson' => 'scottgoodson@gmail.com', 'Michael Schneider' => 'mischneider1@gmail.com', 'Adlai Holler' => 'adlai@icloud.com' } @@ -24,7 +24,9 @@ Pod::Spec.new do |spec| 'Source/Base/*.h', 'Source/Debug/**/*.h', 'Source/TextKit/ASTextNodeTypes.h', - 'Source/TextKit/ASTextKitComponents.h' + 'Source/TextKit/ASTextKitComponents.h', + 'Source/TextExperiment/Component/*.h', + 'Source/TextExperiment/String/ASTextAttribute.h', ] core.source_files = [ diff --git a/ThreeMigrationGuide.md b/ThreeMigrationGuide.md index 4ea5bf372..b3eb0a6c1 100644 --- a/ThreeMigrationGuide.md +++ b/ThreeMigrationGuide.md @@ -1,7 +1,11 @@ -## Texture 3.0 Migration Guide +## Texture 3.1 Migration Guide Got a tip for upgrading? Please open a PR to this document! +- Rename all instances of ASNavigationController to ASDKNavigationController + +## Texture 3.0 Migration Guide + - Rename all instances of ASViewController to ASDKViewController ### Breaking API Changes diff --git a/docs/_docs/automatic-subnode-mgmt.md b/docs/_docs/automatic-subnode-mgmt.md index e4ff53420..bfd3e5294 100755 --- a/docs/_docs/automatic-subnode-mgmt.md +++ b/docs/_docs/automatic-subnode-mgmt.md @@ -27,7 +27,7 @@ By setting `.automaticallyManagesSubnodes` to `YES` on the `ASCellNode`, we _no
-- (instancetype)initWithPhotoObject:(PhotoModel *)photo;
+- (instancetype)initWithPhotoObject:(PhotoModel *)photo
 {
   self = [super init];
   
@@ -103,7 +103,7 @@ class PhotoCellNode {
 
 
-- (instancetype)initWithPhotoObject:(PhotoModel *)photo;
+- (instancetype)initWithPhotoObject:(PhotoModel *)photo
 {
   self = [super init];
   
diff --git a/docs/_docs/development/how-to-debug.md b/docs/_docs/development/how-to-debug.md
index 8098cd4a8..75f8c2ec7 100644
--- a/docs/_docs/development/how-to-debug.md
+++ b/docs/_docs/development/how-to-debug.md
@@ -75,10 +75,6 @@ Here is the collection view's `dealloc`
     [self setAsyncDelegate:nil];
     [self setAsyncDataSource:nil];
   }
-
-  // Data controller & range controller may own a ton of nodes, let's deallocate those off-main.
-  ASPerformBackgroundDeallocation(&_dataController);
-  ASPerformBackgroundDeallocation(&_rangeController);
 }
 ```
 
diff --git a/docs/_docs/development/node-lifecycle.md b/docs/_docs/development/node-lifecycle.md
index 6484eeed5..3725b8770 100644
--- a/docs/_docs/development/node-lifecycle.md
+++ b/docs/_docs/development/node-lifecycle.md
@@ -31,12 +31,6 @@ For more details, look into ASCollectionLayout, ASCollectionGalleryLayoutDelegat
 
 As mentioned above, since ASCellNodes are not meant to be reused, they have a longer lifecycle compared to the view or layer that they encapsulate or their corresponding UICollectionViewCell or UITableViewCell. ASCellNodes are deallocated when they are no longer used and removed from the container node. This can occur after a batch update that includes a reload data or deletion, or after the container node is no longer used and thus released.
 
-For the latter case in which the container node is no longer used and thus released, their cell nodes are not released immediately. That is because collection and table nodes might hold a large number of cell nodes and releasing all of them (and their subnodes, more on this later) at the same time can cause a noticeable delay. To avoid that, ASCollectionNode and ASTableNode release their instance variables, most noticeably an ASDataController instance, on a background thread using a helper called ASDeallocQueue. Since the ASDataController instance is the true owner of all cell nodes -- it has a strong reference to all of them --, all of those cell nodes are deallocated off the main thread as well. As a result, you can expect a delay from which a collection or table view is released until all the cell nodes are fully released and their memory reclaimed. It's important to remember this when debugging memory leaks: objects referenced by the data controller may take a bit to show as dealloced by Instruments.
-
-## ASDeallocQueue
-
-As mentioned above, ASDeallocQueue helps to defer the deallocation of objects given to it by increasing the reference count of each object -- essentially retaining them and acting as their sole owner -- and then release them later on a background thread.
-
 # Nodes that are not managed by containers
 
 These are nodes that are often directly created by client code, such as direct and indirect subnodes of cell nodes. When a node is added to a parent node, the parent node retains it until it's removed from the parent node, or until the parent node is deallocated. As a result, if the subnode is not retained by client code in any other way or if it's not removed from the parent node, the subnode's lifecycle is tied to the parent node's lifecycle. In addition, since nodes often live in a hierarchy, the entire node hierarchy has the same lifecycle as the root node's. Lastly, if the root node is managed by a node container -- directly in the case of ASDKViewController and the like, or indirectly as a cell node of a collection or table node --, then the entire node hierarchy is managed by the node container.
diff --git a/docs/_docs/development/threading.md b/docs/_docs/development/threading.md
index fad2e994c..06ea796cd 100644
--- a/docs/_docs/development/threading.md
+++ b/docs/_docs/development/threading.md
@@ -250,25 +250,6 @@ Since this lock is also shared, it will prevent other routines from entering unt
 
 __Method 2__
 
-An alternative method is to manage the duration of the lock hold manually rather than using the runtime and scope. You must remember to unlock.
-
-```
-@implementation ASDeallocQueue {
-  ASDN::Mutex _lock;
-}
-
-- (void)releaseObjectInBackground:(id  _Nullable __strong *)objectPtr
-{
-  NSParameterAssert(objectPtr != NULL); // do I actually need to lock ?
-  // other conditions or non-shared tasks
-  _lock.lock();
-  sharedObject.modify(newData);
-  _lock.unlock();
-}
-```
-
-__Method 3__
-
 `ASThread` provides `ASLockScopeSelf()`. This is a convenience over `ASLockScopeUnowned()`. This will unlock itself once the scope in which the lock was created is released. Only use this when you are confident that the lock should remain until scope is complete. You can only have one lock defined for `self`, thus it will block all other branches.
 
 ```
@@ -311,7 +292,6 @@ API | Description |
 `ASDisplayNodeAssertMainThread();` | Place this at the start of the every function definition that performs work synchronously on the main thread.
 `ASPerformBlockOnMainThread(block)` | If on main thread already, run block synchronously, otherwise use `dispatch_async(dispatch_get_main_queue(block))`
 `ASPerformMainThreadDeallocation(&object)` | Schedule async deallocation of UIKit components
-`ASPerformBackgroundDeallocation(&object)` | Schedule async deallocation of __non-UIKit__ objects
 `ASPerformBlockOnBackgroundThread(block)` | Perform work on background
 
 
diff --git a/docs/_docs/inversion.md b/docs/_docs/inversion.md
index 5dd18140d..121cc07a9 100755
--- a/docs/_docs/inversion.md
+++ b/docs/_docs/inversion.md
@@ -23,7 +23,7 @@ When this is enabled, developers only have to take one more step to have full in
  self.tableNode.view.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, inset, 0);
   
-
+