diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index a85f29575b..13ad63c713 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 0442850A1BAA63FE00D16268 /* ASBatchFetching.mm in Sources */ = {isa = PBXBuildFile; fileRef = 044285061BAA63FE00D16268 /* ASBatchFetching.mm */; }; 0442850E1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 0442850B1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h */; settings = {ATTRIBUTES = (Private, ); }; }; 044285101BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0442850C1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.mm */; }; - 052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.mm */; }; 052EE06B1A15A0D8002C6279 /* TestResources in Resources */ = {isa = PBXBuildFile; fileRef = 052EE06A1A15A0D8002C6279 /* TestResources */; }; 057D02C41AC0A66700C7AC3C /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C31AC0A66700C7AC3C /* main.mm */; }; 057D02C71AC0A66700C7AC3C /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 057D02C61AC0A66700C7AC3C /* AppDelegate.mm */; }; @@ -25,7 +24,6 @@ 058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A2E195D057000B7D73C /* ASDisplayNodeAppearanceTests.mm */; }; 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.mm */; }; 058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.mm */; }; - 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.mm */; }; 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.mm */; }; 058D0A40195D057000B7D73C /* ASTextNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A36195D057000B7D73C /* ASTextNodeTests.mm */; }; 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */; }; @@ -63,14 +61,10 @@ 254C6B8A1BF94F8A003EC431 /* ASTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */; }; 254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */; }; 254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; }; - 25E327571C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 25E327591C16819500A2170C /* ASPagerNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 25E327551C16819500A2170C /* ASPagerNode.mm */; }; - 2767E9411BB19BD600EA9B77 /* ASDKViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASDKViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 2911485C1A77147A005D0878 /* ASControlNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.mm */; }; 296A0A351A951ABF005ACEAA /* ASBatchFetchingTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.mm */; }; 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.mm */; }; 2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.mm */; }; 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED081B17843500DA7C62 /* ASDimension.mm */; }; 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -99,7 +93,6 @@ 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.mm */; }; 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; }; 407B8BAE2310E2ED00CB979E /* ASLayoutSpecUtilitiesTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 407B8BAD2310E2ED00CB979E /* ASLayoutSpecUtilitiesTests.mm */; }; - 4080D66C2350384400CDC199 /* ASPINRemoteImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 471D04B1224CB98600649215 /* ASImageNodeBackingSizeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 471D04B0224CB98600649215 /* ASImageNodeBackingSizeTests.mm */; }; 4E9127691F64157600499623 /* ASRunLoopQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4E9127681F64157600499623 /* ASRunLoopQueueTests.mm */; }; 4EE3813FF44E20C399ACB962 /* Pods_AsyncDisplayKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5039B74209A895E07057081C /* Pods_AsyncDisplayKitTests.framework */; }; @@ -110,14 +103,9 @@ 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; 509E68651B3AEDC5009B9150 /* CoreGraphics+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.mm in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.mm */; }; - 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.mm */; }; - 680346941CE4052A0009FEB4 /* ASNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; - 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.mm */; }; 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.mm */; }; 68355B411CB57A6C001D4E68 /* ASImageContainerProtocolCategories.h in Headers */ = {isa = PBXBuildFile; fileRef = 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */; settings = {ATTRIBUTES = (Public, ); }; }; 683F563720E409D700CEB7A3 /* ASDisplayNode+InterfaceState.h in Headers */ = {isa = PBXBuildFile; fileRef = 683F563620E409D600CEB7A3 /* ASDisplayNode+InterfaceState.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B8A4DF1CBDB958007E4543 /* ASWeakProxy.h */; settings = {ATTRIBUTES = (Private, ); }; }; 68B8A4E41CBDB958007E4543 /* ASWeakProxy.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68B8A4E01CBDB958007E4543 /* ASWeakProxy.mm */; }; @@ -125,11 +113,6 @@ 68C2155A1DE10D330019C4BC /* ASCollectionViewLayoutInspector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.mm */; }; 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; settings = {ATTRIBUTES = (Private, ); }; }; 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; - 68FC85E31CE29B7E00EDD713 /* ASTabBarController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85E01CE29B7E00EDD713 /* ASTabBarController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68FC85E51CE29B7E00EDD713 /* ASTabBarController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85E11CE29B7E00EDD713 /* ASTabBarController.mm */; }; - 68FC85E61CE29B9400EDD713 /* ASNavigationController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85DD1CE29AB700EDD713 /* ASNavigationController.mm */; }; - 68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.mm */; }; 6900C5F41E8072DA00BCD75C /* ASImageNode+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 6900C5F31E8072DA00BCD75C /* ASImageNode+Private.h */; }; 6907C2581DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 6907C2561DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6907C25A1DC4ECFE00374C66 /* ASObjectDescriptionHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.mm */; }; @@ -138,11 +121,8 @@ 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */; }; 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 690C35631E055C7B00069B91 /* ASDimensionInternal.h */; settings = {ATTRIBUTES = (Public, ); }; }; 690ED58E1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 690ED58D1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 690ED5981E36D118000627C0 /* ASControlNode+tvOS.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690ED5931E36D118000627C0 /* ASControlNode+tvOS.mm */; }; - 690ED59B1E36D118000627C0 /* ASImageNode+tvOS.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690ED5951E36D118000627C0 /* ASImageNode+tvOS.mm */; }; 692510141E74FB44003F2DD0 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 692510131E74FB44003F2DD0 /* Default-568h@2x.png */; }; 692BE8D71E36B65B00C86D87 /* ASLayoutSpecPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 692BE8D61E36B65B00C86D87 /* ASLayoutSpecPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 693A1DCA1ECC944E00D0C9D2 /* IGListAdapter+AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6947B0BE1E36B4E30007C478 /* ASStackUnpositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 6947B0BC1E36B4E30007C478 /* ASStackUnpositionedLayout.h */; settings = {ATTRIBUTES = (Private, ); }; }; 6947B0C01E36B4E30007C478 /* ASStackUnpositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6947B0BD1E36B4E30007C478 /* ASStackUnpositionedLayout.mm */; }; 6947B0C31E36B5040007C478 /* ASStackPositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 6947B0C11E36B5040007C478 /* ASStackPositionedLayout.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -153,8 +133,6 @@ 6977965F1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */; settings = {ATTRIBUTES = (Private, ); }; }; 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */; }; 697B315A1CFE4B410049936F /* ASEditableTextNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 697B31591CFE4B410049936F /* ASEditableTextNodeTests.mm */; }; - 698371DB1E4379CD00437585 /* ASNodeController+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 698371D91E4379CD00437585 /* ASNodeController+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 698371DC1E4379CD00437585 /* ASNodeController+Beta.mm in Sources */ = {isa = PBXBuildFile; fileRef = 698371DA1E4379CD00437585 /* ASNodeController+Beta.mm */; }; 698C8B621CAB49FC0052DC3F /* ASLayoutElementExtensibility.h in Headers */ = {isa = PBXBuildFile; fileRef = 698C8B601CAB49FC0052DC3F /* ASLayoutElementExtensibility.h */; settings = {ATTRIBUTES = (Public, ); }; }; 698DFF441E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 698DFF431E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; }; 698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 698DFF461E36B7E9002891F1 /* ASLayoutSpecUtilities.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -165,7 +143,6 @@ 69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; 69FEE53D1D95A9AF0086F066 /* ASLayoutElementStyleTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69FEE53C1D95A9AF0086F066 /* ASLayoutElementStyleTests.mm */; }; - 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 764D83D51C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; settings = {ATTRIBUTES = (Public, ); }; }; 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.mm in Sources */ = {isa = PBXBuildFile; fileRef = 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.mm */; }; 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */; }; @@ -178,47 +155,15 @@ 83A7D95B1D44547700BF333E /* ASWeakMap.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.mm */; }; 83A7D95C1D44548100BF333E /* ASWeakMap.h in Headers */ = {isa = PBXBuildFile; fileRef = 83A7D9581D44542100BF333E /* ASWeakMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; 83A7D95E1D446A6E00BF333E /* ASWeakMapTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.mm */; }; - 8BBBAB8C1CEBAF1700107FC6 /* ASDefaultPlaybackButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.mm */; }; - 8BDA5FC71CDBDF91007D13B2 /* ASVideoPlayerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */; }; - 9019FBBF1ED8061D00C45F72 /* ASYogaUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 9019FBBB1ED8061D00C45F72 /* ASYogaUtilities.h */; }; - 9019FBC01ED8061D00C45F72 /* ASYogaUtilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9019FBBC1ED8061D00C45F72 /* ASYogaUtilities.mm */; }; - 909C4C751F09C98B00D6B76F /* ASTextNode2.h in Headers */ = {isa = PBXBuildFile; fileRef = 909C4C731F09C98B00D6B76F /* ASTextNode2.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 909C4C761F09C98B00D6B76F /* ASTextNode2.mm in Sources */ = {isa = PBXBuildFile; fileRef = 909C4C741F09C98B00D6B76F /* ASTextNode2.mm */; }; - 90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */ = {isa = PBXBuildFile; fileRef = 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */; }; - 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; - 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, ); }; }; 9C6BB3B31B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C70F2051CDA4F06007D6C76 /* ASTraitCollection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.mm */; }; 9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 9C70F2091CDABA36007D6C76 /* ASDKViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6BF1CCAC73C006A6476 /* ASDKViewController.mm */; }; 9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFFC6C11CCAC768006A6476 /* ASTableNode.mm */; }; - 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */; }; @@ -226,10 +171,7 @@ 9CC606651D24DF9E006581A0 /* NSIndexSet+ASHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.mm */; }; 9CDC18CD1B910E12004965E2 /* ASLayoutElementPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D302F9B2231B07E005739C3 /* ASButtonNode+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D302F9A2231B07E005739C3 /* ASButtonNode+Private.h */; }; - 9D302F9E2231B373005739C3 /* ASButtonNode+Yoga.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D302F9C2231B373005739C3 /* ASButtonNode+Yoga.h */; }; - 9D302F9F2231B373005739C3 /* ASButtonNode+Yoga.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9D302F9D2231B373005739C3 /* ASButtonNode+Yoga.mm */; }; 9D9AA56921E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9D9AA56721E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm */; }; - 9D9AA56B21E254B800172C09 /* ASDisplayNode+Yoga.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D9AA56A21E254B800172C09 /* ASDisplayNode+Yoga.h */; }; 9D9AA56D21E2568500172C09 /* ASDisplayNode+LayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D9AA56C21E2568500172C09 /* ASDisplayNode+LayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */; }; 9F98C0261DBE29E000476D92 /* ASControlTargetAction.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9F98C0241DBDF2A300476D92 /* ASControlTargetAction.mm */; }; @@ -251,9 +193,6 @@ ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */; }; ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */; }; AE440175210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE440174210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm */; }; - AE6987C11DD04E1000B9E458 /* ASPagerNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE6987C01DD04E1000B9E458 /* ASPagerNodeTests.mm */; }; - AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.mm */; }; - B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; B30BF6541C59D889004FCD53 /* ASLayoutManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = B30BF6511C5964B0004FCD53 /* ASLayoutManager.mm */; }; B35061F31B010EFD0018CF92 /* ASCellNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -272,8 +211,6 @@ B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */; }; B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09DD195D050800B7D73C /* ASImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09DE195D050800B7D73C /* ASImageNode.mm */; }; - B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */; }; B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */; }; B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -300,8 +237,6 @@ B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521D1A3F83C40061C0BA /* ASLayoutController.h */; }; B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.mm */; }; B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; }; B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */; }; B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -333,31 +268,21 @@ B35062581B010F070018CF92 /* ASAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3A1A15563400B4EBED /* ASAvailability.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A44195D058D00B7D73C /* ASBaseDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; B350625C1B010F070018CF92 /* ASLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3B1A15563400B4EBED /* ASLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.mm */; }; - BB5FC3D11F9C9389007F191E /* ASTabBarControllerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.mm */; }; C018DF21216BF26700181FDA /* ASAbstractLayoutController+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = C018DF20216BF26600181FDA /* ASAbstractLayoutController+FrameworkPrivate.h */; }; - C057D9BD20B5453D00FC9112 /* ASTextNode2SnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = C057D9BC20B5453D00FC9112 /* ASTextNode2SnapshotTests.mm */; }; C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC01EB6D23105C2000CDB61A /* TestAsset.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CC01EB6C23105C2000CDB61A /* TestAsset.xcassets */; }; CC01EB6F23105C7F00CDB61A /* ASImageNodeSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.mm */; }; CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.mm */; }; - CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC034A141E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.mm */; }; CC051F1F1D7A286A006434CB /* ASCALayerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC051F1E1D7A286A006434CB /* ASCALayerTests.mm */; }; CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.mm */; }; CC0F885B1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.mm */; }; CC0F885C1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = CC0F885A1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC0F885F1E4280B800576FED /* _ASCollectionViewCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC0F885D1E4280B800576FED /* _ASCollectionViewCell.mm */; }; CC0F88601E4280B800576FED /* _ASCollectionViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */; settings = {ATTRIBUTES = (Private, ); }; }; - CC0F88621E4281E200576FED /* ASSectionController.h in Headers */ = {isa = PBXBuildFile; fileRef = CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC0F88631E4281E700576FED /* ASSupplementaryNodeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = CCE04B2B1E314A32006AEBBB /* ASSupplementaryNodeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC0F886C1E4286FA00576FED /* ReferenceImages_64 in Resources */ = {isa = PBXBuildFile; fileRef = CC0F88691E4286FA00576FED /* ReferenceImages_64 */; }; CC11F97A1DB181180024D77B /* ASNetworkImageNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.mm */; }; CC18248C200D49C800875940 /* ASTextNodeCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = CC18248B200D49C800875940 /* ASTextNodeCommon.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC224E962066CA6D00BBA57F /* configuration.json in Resources */ = {isa = PBXBuildFile; fileRef = CC224E952066CA6D00BBA57F /* configuration.json */; }; CC2F65EE1E5FFB1600DA57C9 /* ASMutableElementMap.h in Headers */ = {isa = PBXBuildFile; fileRef = CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */; }; CC2F65EF1E5FFB1600DA57C9 /* ASMutableElementMap.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.mm */; }; CC35CEC320DD7F600006448D /* ASCollections.h in Headers */ = {isa = PBXBuildFile; fileRef = CC35CEC120DD7F600006448D /* ASCollections.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -367,10 +292,7 @@ CC36C191218B841A00232F23 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC36C190218B841A00232F23 /* CoreText.framework */; }; CC36C193218B842E00232F23 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC36C192218B842E00232F23 /* CoreGraphics.framework */; }; CC36C194218B844800232F23 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; - CC36C196218B845B00232F23 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC36C195218B845B00232F23 /* AVFoundation.framework */; }; CC36C198218B846300232F23 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC36C197218B846300232F23 /* QuartzCore.framework */; }; - CC36C19A218B846F00232F23 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC36C199218B846F00232F23 /* CoreLocation.framework */; }; - CC36C19C218B847400232F23 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC36C19B218B847400232F23 /* CoreMedia.framework */; }; CC36C19D218B849C00232F23 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC36C18E218B841600232F23 /* UIKit.framework */; }; CC36C19E218B894400232F23 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC36C195218B845B00232F23 /* AVFoundation.framework */; }; CC36C19F218B894800232F23 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC36C19B218B847400232F23 /* CoreMedia.framework */; }; @@ -384,8 +306,6 @@ CC4E8DAF232C2883007C3182 /* ASGraphicsContextTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC4E8DAE232C2882007C3182 /* ASGraphicsContextTests.mm */; }; CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = CC54A81B1D70077A00296A24 /* ASDispatch.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC54A81E1D7008B300296A24 /* ASDispatchTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC54A81D1D7008B300296A24 /* ASDispatchTests.mm */; }; - CC55A70D1E529FA200594372 /* UIResponder+AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.mm */; }; CC55A7111E52A0F200594372 /* ASResponderChainEnumerator.h in Headers */ = {isa = PBXBuildFile; fileRef = CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */; }; CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.mm */; }; CC56013B1F06E9A700DC4FBE /* ASIntegerMap.h in Headers */ = {isa = PBXBuildFile; fileRef = CC5601391F06E9A700DC4FBE /* ASIntegerMap.h */; }; @@ -400,31 +320,12 @@ CC698827247855F200487428 /* UIImage+ASConvenienceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC698826247855F200487428 /* UIImage+ASConvenienceTests.mm */; }; CC6AA2DA1E9F03B900978E87 /* ASDisplayNode+Ancestry.h in Headers */ = {isa = PBXBuildFile; fileRef = CC6AA2D81E9F03B900978E87 /* ASDisplayNode+Ancestry.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC6AA2DB1E9F03B900978E87 /* ASDisplayNode+Ancestry.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC6AA2D91E9F03B900978E87 /* ASDisplayNode+Ancestry.mm */; }; - CC7AF196200D9BD500A21BDE /* ASExperimentalFeatures.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7AF195200D9BD500A21BDE /* ASExperimentalFeatures.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CC7AF198200DAB2200A21BDE /* ASExperimentalFeatures.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC7AF197200D9E8400A21BDE /* ASExperimentalFeatures.mm */; }; - CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.mm */; }; - CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC8B05D61D73836400F54286 /* ASPerformanceTestContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC8B05D51D73836400F54286 /* ASPerformanceTestContext.mm */; }; CC8B05D81D73979700F54286 /* ASTextNodePerformanceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.mm */; }; CC90E1F41E383C0400FED591 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */; }; - CCA221D31D6FA7EF00AF6A0F /* ASDKViewControllerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCA221D21D6FA7EF00AF6A0F /* ASDKViewControllerTests.mm */; }; - CCA282B41E9EA7310037E8B7 /* ASTipsController.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282B21E9EA7310037E8B7 /* ASTipsController.h */; }; - CCA282B51E9EA7310037E8B7 /* ASTipsController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCA282B31E9EA7310037E8B7 /* ASTipsController.mm */; }; CCA282B81E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282B61E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCA282B91E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCA282B71E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.mm */; }; - CCA282BC1E9EABDD0037E8B7 /* ASTipProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282BA1E9EABDD0037E8B7 /* ASTipProvider.h */; }; - CCA282BD1E9EABDD0037E8B7 /* ASTipProvider.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCA282BB1E9EABDD0037E8B7 /* ASTipProvider.mm */; }; - CCA282C01E9EAE010037E8B7 /* ASTip.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282BE1E9EAE010037E8B7 /* ASTip.h */; }; - CCA282C11E9EAE010037E8B7 /* ASTip.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCA282BF1E9EAE010037E8B7 /* ASTip.mm */; }; - CCA282C41E9EAE630037E8B7 /* ASLayerBackingTipProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282C21E9EAE630037E8B7 /* ASLayerBackingTipProvider.h */; }; - CCA282C51E9EAE630037E8B7 /* ASLayerBackingTipProvider.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCA282C31E9EAE630037E8B7 /* ASLayerBackingTipProvider.mm */; }; - CCA282C81E9EB64B0037E8B7 /* ASDisplayNodeTipState.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */; }; - CCA282C91E9EB64B0037E8B7 /* ASDisplayNodeTipState.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCA282C71E9EB64B0037E8B7 /* ASDisplayNodeTipState.mm */; }; - CCA282CC1E9EB73E0037E8B7 /* ASTipNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282CA1E9EB73E0037E8B7 /* ASTipNode.h */; }; - CCA282CD1E9EB73E0037E8B7 /* ASTipNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCA282CB1E9EB73E0037E8B7 /* ASTipNode.mm */; }; - CCA282D01E9EBF6C0037E8B7 /* ASTipsWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */; }; - CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.mm */; }; CCA5F62E1EECC2A80060C137 /* ASAssert.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCA5F62D1EECC2A80060C137 /* ASAssert.mm */; }; CCAA0B7F206ADBF30057B336 /* ASRecursiveUnfairLock.h in Headers */ = {isa = PBXBuildFile; fileRef = CCAA0B7D206ADBF30057B336 /* ASRecursiveUnfairLock.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCAA0B80206ADBF30057B336 /* ASRecursiveUnfairLock.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCAA0B7E206ADBF30057B336 /* ASRecursiveUnfairLock.mm */; }; @@ -445,12 +346,6 @@ CCED5E3E2020D36800395C40 /* ASNetworkImageLoadInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCED5E3F2020D36800395C40 /* ASNetworkImageLoadInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.mm */; }; CCED5E412020D49D00395C40 /* ASNetworkImageLoadInfo+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - CCEDDDCA200C2AC300FFCD0A /* ASConfigurationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEDDDC8200C2AC300FFCD0A /* ASConfigurationInternal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CCEDDDCB200C2AC300FFCD0A /* ASConfigurationInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCEDDDC9200C2AC300FFCD0A /* ASConfigurationInternal.mm */; }; - CCEDDDCD200C2CB900FFCD0A /* ASConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEDDDCC200C2CB900FFCD0A /* ASConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CCEDDDCF200C42A200FFCD0A /* ASConfigurationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEDDDCE200C42A200FFCD0A /* ASConfigurationDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - CCEDDDD1200C488000FFCD0A /* ASConfiguration.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCEDDDD0200C488000FFCD0A /* ASConfiguration.mm */; }; - 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, ); }; }; D933F041224AD17F00FF495E /* ASTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D933F040224AD17F00FF495E /* ASTransactionTests.mm */; }; @@ -460,8 +355,6 @@ DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C25F1C6408D6004EDCF5 /* _ASTransitionContext.h */; settings = {ATTRIBUTES = (Private, ); }; }; DBC452DE1C5C6A6A00B16017 /* ArrayDiffingTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.mm */; }; DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.mm */; }; - DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DBDB83971C6E879900D0098C /* ASPagerFlowLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = DBDB83931C6E879900D0098C /* ASPagerFlowLayout.mm */; }; DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; settings = {ATTRIBUTES = (Private, ); }; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -472,50 +365,21 @@ DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; settings = {ATTRIBUTES = (Private, ); }; }; DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; - DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; - E517F9C823BF14BC006E40E0 /* ASLayout+IGListDiffKit.mm in Sources */ = {isa = PBXBuildFile; fileRef = E517F9C623BF14BC006E40E0 /* ASLayout+IGListDiffKit.mm */; }; - E517F9C923BF14BC006E40E0 /* ASLayout+IGListDiffKit.h in Headers */ = {isa = PBXBuildFile; fileRef = E517F9C723BF14BC006E40E0 /* ASLayout+IGListDiffKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.mm */; }; - E54E00721F1D3828000B30D7 /* ASPagerNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E54E81FC1EB357BD00FFE8E1 /* ASPageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */; }; - E54E81FD1EB357BD00FFE8E1 /* ASPageTable.mm in Sources */ = {isa = PBXBuildFile; fileRef = E54E81FB1EB357BD00FFE8E1 /* ASPageTable.mm */; }; E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; }; - E5667E8C1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = E5667E8B1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h */; settings = {ATTRIBUTES = (Private, ); }; }; - E5667E8E1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5667E8D1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.mm */; }; E5711A2C1C840C81009619D4 /* ASCollectionElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASCollectionElement.h */; settings = {ATTRIBUTES = (Public, ); }; }; E5711A301C840C96009619D4 /* ASCollectionElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */; }; - E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */ = {isa = PBXBuildFile; fileRef = E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */; settings = {ATTRIBUTES = (Private, ); }; }; - E5775AFE1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */; }; - E5775B001F13D25400CAC9BC /* ASCollectionLayoutState+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E5775AFF1F13D25400CAC9BC /* ASCollectionLayoutState+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - E5775B021F16759300CAC9BC /* ASCollectionLayoutCache.h in Headers */ = {isa = PBXBuildFile; fileRef = E5775B011F16759300CAC9BC /* ASCollectionLayoutCache.h */; settings = {ATTRIBUTES = (Private, ); }; }; - E5775B041F16759F00CAC9BC /* ASCollectionLayoutCache.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5775B031F16759F00CAC9BC /* ASCollectionLayoutCache.mm */; }; - E5855DEF1EBB4D83003639AE /* ASCollectionLayoutDefines.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.mm */; }; - E5855DF01EBB4D83003639AE /* ASCollectionLayoutDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */; settings = {ATTRIBUTES = (Private, ); }; }; E586F96C1F9F9E2900ECE00E /* ASScrollNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = E586F96B1F9F9E2900ECE00E /* ASScrollNodeTests.mm */; }; - E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.mm */; }; - E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E58E9E451E941D74004CFC59 /* ASCollectionLayoutContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */; }; - E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E58E9E491E941DA5004CFC59 /* ASCollectionLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */; settings = {ATTRIBUTES = (Private, ); }; }; - E58E9E4A1E941DA5004CFC59 /* ASCollectionLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */; }; E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */; settings = {ATTRIBUTES = (Public, ); }; }; E5B078001E69F4EB00C24B5B /* ASElementMap.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5B077FE1E69F4EB00C24B5B /* ASElementMap.mm */; }; E5B225281F1790D6001E1431 /* ASHashing.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B225271F1790B5001E1431 /* ASHashing.h */; settings = {ATTRIBUTES = (Public, ); }; }; E5B225291F1790EE001E1431 /* ASHashing.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5B225261F1790B5001E1431 /* ASHashing.mm */; }; E5B2252E1F17E521001E1431 /* ASDispatch.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5B2252D1F17E521001E1431 /* ASDispatch.mm */; }; - E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; E5C347B11ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E5C347B01ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h */; }; E5C347B31ECB40AA00EC4BE4 /* ASTableNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = E5C347B21ECB40AA00EC4BE4 /* ASTableNode+Beta.h */; }; - E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */ = {isa = PBXBuildFile; fileRef = E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E5E281761E71C845006B67C2 /* ASCollectionLayoutState.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5E281751E71C845006B67C2 /* ASCollectionLayoutState.mm */; }; - E5E2D72E1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; - E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm */; }; F325E48C21745F9E00AC93A4 /* ASButtonNodeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F325E48B21745F9E00AC93A4 /* ASButtonNodeTests.mm */; }; - F325E490217460B100AC93A4 /* ASTextNode2Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F325E48F217460B000AC93A4 /* ASTextNode2Tests.mm */; }; F3F698D2211CAD4600800CB1 /* ASDisplayViewAccessibilityTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F3F698D1211CAD4600800CB1 /* ASDisplayViewAccessibilityTests.mm */; }; F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.mm */; }; - FA4FAF15200A850200E735BD /* ASControlNode+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4FAF14200A850200E735BD /* ASControlNode+Private.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -536,11 +400,8 @@ 0442850C1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTwoDimensionalArrayUtils.mm; sourceTree = ""; }; 0516FA3A1A15563400B4EBED /* ASAvailability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAvailability.h; sourceTree = ""; }; 0516FA3B1A15563400B4EBED /* ASLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLog.h; sourceTree = ""; }; - 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMultiplexImageNode.h; sourceTree = ""; }; - 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMultiplexImageNode.mm; sourceTree = ""; }; 051943121A1575630030A7D0 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 051943141A1575670030A7D0 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; - 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMultiplexImageNodeTests.mm; sourceTree = ""; }; 052EE06A1A15A0D8002C6279 /* TestResources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = TestResources; sourceTree = ""; }; 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBasicImageDownloader.h; sourceTree = ""; }; 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBasicImageDownloader.mm; sourceTree = ""; }; @@ -585,8 +446,6 @@ 058D09E5195D050800B7D73C /* _ASDisplayView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASDisplayView.mm; sourceTree = ""; }; 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASHighlightOverlayLayer.h; sourceTree = ""; }; 058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASHighlightOverlayLayer.mm; sourceTree = ""; }; - 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMutableAttributedStringBuilder.h; sourceTree = ""; }; - 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMutableAttributedStringBuilder.mm; sourceTree = ""; }; 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableAttributedString+TextKitAdditions.h"; sourceTree = ""; }; 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSMutableAttributedString+TextKitAdditions.mm"; sourceTree = ""; }; 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransaction.h; sourceTree = ""; }; @@ -615,7 +474,6 @@ 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeTests.mm; sourceTree = ""; }; 058D0A30195D057000B7D73C /* ASDisplayNodeTestsHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeTestsHelper.h; sourceTree = ""; }; 058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeTestsHelper.mm; sourceTree = ""; }; - 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMutableAttributedStringBuilderTests.mm; sourceTree = ""; }; 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitCoreTextAdditionsTests.mm; sourceTree = ""; }; 058D0A36195D057000B7D73C /* ASTextNodeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeTests.mm; sourceTree = ""; }; 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeWordKernerTests.mm; sourceTree = ""; }; @@ -665,8 +523,6 @@ 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitCoreTextAdditions.h; path = TextKit/ASTextKitCoreTextAdditions.h; sourceTree = ""; }; 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeTypes.h; path = TextKit/ASTextNodeTypes.h; sourceTree = ""; }; 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextNodeWordKerner.mm; path = TextKit/ASTextNodeWordKerner.mm; sourceTree = ""; }; - 25E327541C16819500A2170C /* ASPagerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASPagerNode.h; sourceTree = ""; }; - 25E327551C16819500A2170C /* ASPagerNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASPagerNode.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 2911485B1A77147A005D0878 /* ASControlNodeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASControlNodeTests.mm; sourceTree = ""; }; 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = ""; }; 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASBasicImageDownloaderInternal.h; sourceTree = ""; }; @@ -687,26 +543,16 @@ 471D04B0224CB98600649215 /* ASImageNodeBackingSizeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASImageNodeBackingSizeTests.mm; sourceTree = ""; }; 4E9127681F64157600499623 /* ASRunLoopQueueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRunLoopQueueTests.mm; sourceTree = ""; }; 5039B74209A895E07057081C /* Pods_AsyncDisplayKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AsyncDisplayKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASImageNode+AnimatedImage.mm"; sourceTree = ""; }; - 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPINRemoteImageDownloader.mm; sourceTree = ""; }; 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageContainerProtocolCategories.h; sourceTree = ""; }; 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASImageContainerProtocolCategories.mm; sourceTree = ""; }; - 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPINRemoteImageDownloader.h; sourceTree = ""; }; 683F563620E409D600CEB7A3 /* ASDisplayNode+InterfaceState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+InterfaceState.h"; sourceTree = ""; }; 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Beta.h"; sourceTree = ""; }; - 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+AnimatedImagePrivate.h"; sourceTree = ""; }; 68B8A4DF1CBDB958007E4543 /* ASWeakProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakProxy.h; sourceTree = ""; }; 68B8A4E01CBDB958007E4543 /* ASWeakProxy.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASWeakProxy.mm; sourceTree = ""; }; 68C215561DE10D330019C4BC /* ASCollectionViewLayoutInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewLayoutInspector.h; sourceTree = ""; }; 68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionViewLayoutInspector.mm; sourceTree = ""; }; 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMainSerialQueue.h; sourceTree = ""; }; 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMainSerialQueue.mm; sourceTree = ""; }; - 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASNavigationController.h; sourceTree = ""; }; - 68FC85DD1CE29AB700EDD713 /* ASNavigationController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASNavigationController.mm; sourceTree = ""; }; - 68FC85E01CE29B7E00EDD713 /* ASTabBarController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTabBarController.h; sourceTree = ""; }; - 68FC85E11CE29B7E00EDD713 /* ASTabBarController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTabBarController.mm; sourceTree = ""; }; - 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVisibilityProtocols.h; sourceTree = ""; }; - 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASVisibilityProtocols.mm; sourceTree = ""; }; 6900C5F31E8072DA00BCD75C /* ASImageNode+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+Private.h"; sourceTree = ""; }; 6907C2561DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASObjectDescriptionHelpers.h; sourceTree = ""; }; 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASObjectDescriptionHelpers.mm; sourceTree = ""; }; @@ -715,8 +561,6 @@ 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDimensionInternal.mm; sourceTree = ""; }; 690C35631E055C7B00069B91 /* ASDimensionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDimensionInternal.h; sourceTree = ""; }; 690ED58D1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementStylePrivate.h; sourceTree = ""; }; - 690ED5931E36D118000627C0 /* ASControlNode+tvOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASControlNode+tvOS.mm"; sourceTree = ""; }; - 690ED5951E36D118000627C0 /* ASImageNode+tvOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASImageNode+tvOS.mm"; sourceTree = ""; }; 692510131E74FB44003F2DD0 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 692BE8D61E36B65B00C86D87 /* ASLayoutSpecPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecPrivate.h; sourceTree = ""; }; 6947B0BC1E36B4E30007C478 /* ASStackUnpositionedLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackUnpositionedLayout.h; sourceTree = ""; }; @@ -729,8 +573,6 @@ 6977965D1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASLayoutSpec+Subclasses.h"; sourceTree = ""; }; 6977965E1D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASLayoutSpec+Subclasses.mm"; sourceTree = ""; }; 697B31591CFE4B410049936F /* ASEditableTextNodeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEditableTextNodeTests.mm; sourceTree = ""; }; - 698371D91E4379CD00437585 /* ASNodeController+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASNodeController+Beta.h"; sourceTree = ""; }; - 698371DA1E4379CD00437585 /* ASNodeController+Beta.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASNodeController+Beta.mm"; sourceTree = ""; }; 698C8B601CAB49FC0052DC3F /* ASLayoutElementExtensibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementExtensibility.h; sourceTree = ""; }; 698DFF431E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutSpecUtilities.h; sourceTree = ""; }; 698DFF461E36B7E9002891F1 /* ASLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecUtilities.h; sourceTree = ""; }; @@ -756,39 +598,10 @@ 83A7D9581D44542100BF333E /* ASWeakMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakMap.h; sourceTree = ""; }; 83A7D9591D44542100BF333E /* ASWeakMap.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASWeakMap.mm; sourceTree = ""; }; 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASWeakMapTests.mm; sourceTree = ""; }; - 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDefaultPlaybackButton.h; sourceTree = ""; }; - 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDefaultPlaybackButton.mm; sourceTree = ""; }; - 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVideoPlayerNode.h; sourceTree = ""; }; - 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASVideoPlayerNode.mm; sourceTree = ""; }; - 9019FBBB1ED8061D00C45F72 /* ASYogaUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASYogaUtilities.h; sourceTree = ""; }; - 9019FBBC1ED8061D00C45F72 /* ASYogaUtilities.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASYogaUtilities.mm; sourceTree = ""; }; - 909C4C731F09C98B00D6B76F /* ASTextNode2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNode2.h; sourceTree = ""; }; - 909C4C741F09C98B00D6B76F /* ASTextNode2.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNode2.mm; sourceTree = ""; }; - 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+Yoga.mm"; sourceTree = ""; }; - 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; - 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 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 = ""; }; @@ -797,13 +610,9 @@ 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTraitCollection.mm; sourceTree = ""; }; 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitFontSizeAdjuster.mm; path = TextKit/ASTextKitFontSizeAdjuster.mm; sourceTree = ""; }; 9CDC18CB1B910E12004965E2 /* ASLayoutElementPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementPrivate.h; sourceTree = ""; }; - 9CFFC6BF1CCAC73C006A6476 /* ASDKViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDKViewController.mm; sourceTree = ""; }; 9CFFC6C11CCAC768006A6476 /* ASTableNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTableNode.mm; sourceTree = ""; }; 9D302F9A2231B07E005739C3 /* ASButtonNode+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASButtonNode+Private.h"; sourceTree = ""; }; - 9D302F9C2231B373005739C3 /* ASButtonNode+Yoga.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASButtonNode+Yoga.h"; sourceTree = ""; }; - 9D302F9D2231B373005739C3 /* ASButtonNode+Yoga.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASButtonNode+Yoga.mm"; sourceTree = ""; }; 9D9AA56721E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+LayoutSpec.mm"; sourceTree = ""; }; - 9D9AA56A21E254B800172C09 /* ASDisplayNode+Yoga.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Yoga.h"; sourceTree = ""; }; 9D9AA56C21E2568500172C09 /* ASDisplayNode+LayoutSpec.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+LayoutSpec.h"; sourceTree = ""; }; 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionViewTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 9F98C0231DBDF2A300476D92 /* ASControlTargetAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASControlTargetAction.h; sourceTree = ""; }; @@ -821,7 +630,6 @@ AC6145421D8AFD4F003D62A2 /* ASSection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASSection.mm; path = ../Private/ASSection.mm; sourceTree = ""; }; AC6456071B0A335000CF11B8 /* ASCellNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCellNode.mm; sourceTree = ""; }; AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableViewInternal.h; sourceTree = ""; }; - ACC945A81BA9E7A0005E1FB8 /* ASDKViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDKViewController.h; sourceTree = ""; }; ACE87A2B1D73696800D7FF06 /* ASSectionContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASSectionContext.h; path = Details/ASSectionContext.h; sourceTree = ""; }; ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBackgroundLayoutSpec.h; sourceTree = ""; }; ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASBackgroundLayoutSpec.mm; sourceTree = ""; }; @@ -855,28 +663,16 @@ ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRatioLayoutSpecSnapshotTests.mm; sourceTree = ""; }; ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackLayoutSpecSnapshotTests.mm; sourceTree = ""; }; AE440174210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitFontSizeAdjusterTests.mm; sourceTree = ""; }; - AE6987C01DD04E1000B9E458 /* ASPagerNodeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPagerNodeTests.mm; sourceTree = ""; }; - AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDefaultPlayButton.h; sourceTree = ""; }; - AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDefaultPlayButton.mm; sourceTree = ""; }; - AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVideoNode.h; sourceTree = ""; }; - AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASVideoNode.mm; sourceTree = ""; }; - AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASVideoNodeTests.mm; sourceTree = ""; }; B0F880581BEAEC7500D17647 /* ASTableNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableNode.h; sourceTree = ""; }; - B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionViewLayoutFacilitatorProtocol.h; sourceTree = ""; }; B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCollectionNode+Beta.h"; sourceTree = ""; }; 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 = ""; }; - 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 = ""; }; - C057D9BC20B5453D00FC9112 /* ASTextNode2SnapshotTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNode2SnapshotTests.mm; sourceTree = ""; }; CC01EB6C23105C2000CDB61A /* TestAsset.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = TestAsset.xcassets; sourceTree = ""; }; CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = ""; }; CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+Convenience.mm"; sourceTree = ""; }; - CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit+IGListKitMethods.h"; sourceTree = ""; }; - CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "AsyncDisplayKit+IGListKitMethods.mm"; sourceTree = ""; }; CC051F1E1D7A286A006434CB /* ASCALayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCALayerTests.mm; sourceTree = ""; }; CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASUICollectionViewTests.mm; sourceTree = ""; }; CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionViewFlowLayoutInspector.mm; sourceTree = ""; }; @@ -886,7 +682,6 @@ CC0F88691E4286FA00576FED /* ReferenceImages_64 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ReferenceImages_64; sourceTree = ""; }; CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASNetworkImageNodeTests.mm; sourceTree = ""; }; CC18248B200D49C800875940 /* ASTextNodeCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASTextNodeCommon.h; sourceTree = ""; }; - CC224E952066CA6D00BBA57F /* configuration.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = configuration.json; sourceTree = ""; }; CC2E317F1DAC353700EEE891 /* ASCollectionView+Undeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCollectionView+Undeprecated.h"; sourceTree = ""; }; CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMutableElementMap.h; sourceTree = ""; }; CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMutableElementMap.mm; sourceTree = ""; }; @@ -913,8 +708,6 @@ CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASTableView+Undeprecated.h"; sourceTree = ""; }; CC54A81B1D70077A00296A24 /* ASDispatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASDispatch.h; sourceTree = ""; }; CC54A81D1D7008B300296A24 /* ASDispatchTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDispatchTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; - CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIResponder+AsyncDisplayKit.h"; sourceTree = ""; }; - CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIResponder+AsyncDisplayKit.mm"; sourceTree = ""; }; CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASResponderChainEnumerator.h; sourceTree = ""; }; CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASResponderChainEnumerator.mm; sourceTree = ""; }; CC5601391F06E9A700DC4FBE /* ASIntegerMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIntegerMap.h; sourceTree = ""; }; @@ -932,32 +725,12 @@ CC698826247855F200487428 /* UIImage+ASConvenienceTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "UIImage+ASConvenienceTests.mm"; sourceTree = ""; }; CC6AA2D81E9F03B900978E87 /* ASDisplayNode+Ancestry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ASDisplayNode+Ancestry.h"; path = "Base/ASDisplayNode+Ancestry.h"; sourceTree = ""; }; CC6AA2D91E9F03B900978E87 /* ASDisplayNode+Ancestry.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "ASDisplayNode+Ancestry.mm"; path = "Base/ASDisplayNode+Ancestry.mm"; sourceTree = ""; }; - CC7AF195200D9BD500A21BDE /* ASExperimentalFeatures.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASExperimentalFeatures.h; sourceTree = ""; }; - CC7AF197200D9E8400A21BDE /* ASExperimentalFeatures.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASExperimentalFeatures.mm; sourceTree = ""; }; - CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = ""; }; - CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPhotosFrameworkImageRequest.mm; sourceTree = ""; }; - CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPhotosFrameworkImageRequestTests.mm; sourceTree = ""; }; CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCellNode+Internal.h"; sourceTree = ""; }; CC8B05D41D73836400F54286 /* ASPerformanceTestContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPerformanceTestContext.h; sourceTree = ""; }; CC8B05D51D73836400F54286 /* ASPerformanceTestContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPerformanceTestContext.mm; sourceTree = ""; }; CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodePerformanceTests.mm; sourceTree = ""; }; - CCA221D21D6FA7EF00AF6A0F /* ASDKViewControllerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDKViewControllerTests.mm; sourceTree = ""; }; - CCA282B21E9EA7310037E8B7 /* ASTipsController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTipsController.h; sourceTree = ""; }; - CCA282B31E9EA7310037E8B7 /* ASTipsController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTipsController.mm; sourceTree = ""; }; CCA282B61E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit+Tips.h"; sourceTree = ""; }; CCA282B71E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "AsyncDisplayKit+Tips.mm"; sourceTree = ""; }; - CCA282BA1E9EABDD0037E8B7 /* ASTipProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTipProvider.h; sourceTree = ""; }; - CCA282BB1E9EABDD0037E8B7 /* ASTipProvider.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTipProvider.mm; sourceTree = ""; }; - CCA282BE1E9EAE010037E8B7 /* ASTip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTip.h; sourceTree = ""; }; - CCA282BF1E9EAE010037E8B7 /* ASTip.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTip.mm; sourceTree = ""; }; - CCA282C21E9EAE630037E8B7 /* ASLayerBackingTipProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayerBackingTipProvider.h; sourceTree = ""; }; - CCA282C31E9EAE630037E8B7 /* ASLayerBackingTipProvider.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayerBackingTipProvider.mm; sourceTree = ""; }; - CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeTipState.h; sourceTree = ""; }; - CCA282C71E9EB64B0037E8B7 /* ASDisplayNodeTipState.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeTipState.mm; sourceTree = ""; }; - CCA282CA1E9EB73E0037E8B7 /* ASTipNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTipNode.h; sourceTree = ""; }; - CCA282CB1E9EB73E0037E8B7 /* ASTipNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTipNode.mm; sourceTree = ""; }; - CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTipsWindow.h; sourceTree = ""; }; - CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTipsWindow.mm; sourceTree = ""; }; CCA5F62D1EECC2A80060C137 /* ASAssert.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASAssert.mm; sourceTree = ""; }; CCAA0B7D206ADBF30057B336 /* ASRecursiveUnfairLock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASRecursiveUnfairLock.h; sourceTree = ""; }; CCAA0B7E206ADBF30057B336 /* ASRecursiveUnfairLock.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRecursiveUnfairLock.mm; sourceTree = ""; }; @@ -966,17 +739,11 @@ CCB1F95B1EFB6316009C7475 /* ASSignpost.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASSignpost.h; sourceTree = ""; }; CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeSnapshotTests.mm; sourceTree = ""; }; CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeManagingNode.h; sourceTree = ""; }; - CCBD05DE1E4147B000D18509 /* ASIGListAdapterBasedDataSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASIGListAdapterBasedDataSource.mm; sourceTree = ""; }; - 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 = ""; }; 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 = ""; }; - CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSectionController.h; sourceTree = ""; }; - CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IGListAdapter+AsyncDisplayKit.h"; sourceTree = ""; }; - CCE04B211E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "IGListAdapter+AsyncDisplayKit.mm"; sourceTree = ""; }; - CCE04B2B1E314A32006AEBBB /* ASSupplementaryNodeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSupplementaryNodeSource.h; sourceTree = ""; }; CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASIntegerMapTests.mm; sourceTree = ""; }; CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutEngineTests.mm; sourceTree = ""; }; CCE4F9B61F0DBA5000062E4E /* ASLayoutTestNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTestNode.h; sourceTree = ""; }; @@ -987,12 +754,6 @@ CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASNetworkImageLoadInfo.h; sourceTree = ""; }; CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASNetworkImageLoadInfo.mm; sourceTree = ""; }; CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASNetworkImageLoadInfo+Private.h"; sourceTree = ""; }; - CCEDDDC8200C2AC300FFCD0A /* ASConfigurationInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASConfigurationInternal.h; sourceTree = ""; }; - CCEDDDC9200C2AC300FFCD0A /* ASConfigurationInternal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASConfigurationInternal.mm; sourceTree = ""; }; - CCEDDDCC200C2CB900FFCD0A /* ASConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASConfiguration.h; sourceTree = ""; }; - CCEDDDCE200C42A200FFCD0A /* ASConfigurationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASConfigurationDelegate.h; sourceTree = ""; }; - 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 = ""; }; 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 = ""; }; @@ -1006,59 +767,29 @@ DBC452DA1C5BF64600B16017 /* NSArray+Diffing.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSArray+Diffing.mm"; sourceTree = ""; }; DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ArrayDiffingTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDisplayNodeImplicitHierarchyTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; - DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPagerFlowLayout.h; sourceTree = ""; }; - DBDB83931C6E879900D0098C /* ASPagerFlowLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPagerFlowLayout.mm; sourceTree = ""; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDelegateProxy.mm; sourceTree = ""; }; DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASCollectionInternal.h; path = Details/ASCollectionInternal.h; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; - E517F9C623BF14BC006E40E0 /* ASLayout+IGListDiffKit.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASLayout+IGListDiffKit.mm"; sourceTree = ""; }; - E517F9C723BF14BC006E40E0 /* ASLayout+IGListDiffKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASLayout+IGListDiffKit.h"; sourceTree = ""; }; E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutFlatteningTests.mm; sourceTree = ""; }; E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTransition.mm; sourceTree = ""; }; E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTransition.h; sourceTree = ""; }; - E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASPagerNode+Beta.h"; sourceTree = ""; }; - E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPageTable.h; sourceTree = ""; }; - E54E81FB1EB357BD00FFE8E1 /* ASPageTable.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPageTable.mm; sourceTree = ""; }; E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutElement.mm; sourceTree = ""; }; - E5667E8B1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionGalleryLayoutInfo.h; sourceTree = ""; }; - E5667E8D1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASCollectionGalleryLayoutInfo.mm; sourceTree = ""; }; E5711A2A1C840C81009619D4 /* ASCollectionElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionElement.h; sourceTree = ""; }; E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionElement.mm; sourceTree = ""; }; - E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionGalleryLayoutItem.h; sourceTree = ""; }; - E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASCollectionGalleryLayoutItem.mm; sourceTree = ""; }; - E5775AFF1F13D25400CAC9BC /* ASCollectionLayoutState+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCollectionLayoutState+Private.h"; sourceTree = ""; }; - E5775B011F16759300CAC9BC /* ASCollectionLayoutCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutCache.h; sourceTree = ""; }; - E5775B031F16759F00CAC9BC /* ASCollectionLayoutCache.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutCache.mm; sourceTree = ""; }; - E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutDefines.mm; sourceTree = ""; }; - E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutDefines.h; sourceTree = ""; }; E586F96B1F9F9E2900ECE00E /* ASScrollNodeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNodeTests.mm; sourceTree = ""; }; - E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionFlowLayoutDelegate.h; sourceTree = ""; }; - E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionFlowLayoutDelegate.mm; sourceTree = ""; }; - E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutContext.h; sourceTree = ""; }; - E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutContext.mm; sourceTree = ""; }; - E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutDelegate.h; sourceTree = ""; }; - E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayout.h; sourceTree = ""; }; - E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayout.mm; sourceTree = ""; }; E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASElementMap.h; sourceTree = ""; }; E5B077FE1E69F4EB00C24B5B /* ASElementMap.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASElementMap.mm; sourceTree = ""; }; E5B225261F1790B5001E1431 /* ASHashing.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASHashing.mm; sourceTree = ""; }; E5B225271F1790B5001E1431 /* ASHashing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASHashing.h; sourceTree = ""; }; E5B2252D1F17E521001E1431 /* ASDispatch.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDispatch.mm; sourceTree = ""; }; - E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCollectionLayoutContext+Private.h"; sourceTree = ""; }; E5C347B01ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBatchFetchingDelegate.h; sourceTree = ""; }; E5C347B21ECB40AA00EC4BE4 /* ASTableNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASTableNode+Beta.h"; sourceTree = ""; }; - E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutState.h; sourceTree = ""; }; - E5E281751E71C845006B67C2 /* ASCollectionLayoutState.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutState.mm; sourceTree = ""; }; - E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionGalleryLayoutDelegate.h; sourceTree = ""; }; - E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionGalleryLayoutDelegate.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; F325E48B21745F9E00AC93A4 /* ASButtonNodeTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNodeTests.mm; sourceTree = ""; }; - F325E48F217460B000AC93A4 /* ASTextNode2Tests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNode2Tests.mm; sourceTree = ""; }; F3F698D1211CAD4600800CB1 /* ASDisplayViewAccessibilityTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayViewAccessibilityTests.mm; sourceTree = ""; }; F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeExtrasTests.mm; sourceTree = ""; }; - FA4FAF14200A850200E735BD /* ASControlNode+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+Private.h"; sourceTree = ""; }; FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1089,17 +820,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CC36C19C218B847400232F23 /* CoreMedia.framework in Frameworks */, - CC36C19A218B846F00232F23 /* CoreLocation.framework in Frameworks */, CC36C198218B846300232F23 /* QuartzCore.framework in Frameworks */, - CC36C196218B845B00232F23 /* AVFoundation.framework in Frameworks */, CC36C194218B844800232F23 /* Foundation.framework in Frameworks */, CC36C193218B842E00232F23 /* CoreGraphics.framework in Frameworks */, CC36C191218B841A00232F23 /* CoreText.framework in Frameworks */, CC36C18F218B841600232F23 /* UIKit.framework in Frameworks */, - 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */, - B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */, - B350625D1B0111740018CF92 /* Photos.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1131,7 +856,6 @@ isa = PBXGroup; children = ( 058D09B1195D04C000B7D73C /* Source */, - CC224E942066CA6D00BBA57F /* Schemas */, 058D09C5195D04C000B7D73C /* Tests */, 058D09AE195D04C000B7D73C /* Frameworks */, 058D09AD195D04C000B7D73C /* Products */, @@ -1179,23 +903,16 @@ CC35CEC120DD7F600006448D /* ASCollections.h */, CC35CEC220DD7F600006448D /* ASCollections.mm */, 058D0A42195D058D00B7D73C /* Base */, - CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */, DE89C1691DCEB9CC00D49D74 /* Debug */, 058D09E1195D050800B7D73C /* Details */, AC6456051B0A333200CF11B8 /* Layout */, 058D0A01195D050800B7D73C /* Private */, 058D09B2195D04C000B7D73C /* Supporting Files */, 257754661BED245B00737CA5 /* TextKit */, - 9C0BA4862582CE35001C293B /* TextExperiment */, - 690ED5911E36D118000627C0 /* tvOS */, CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */, DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */, DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */, - CCEDDDCC200C2CB900FFCD0A /* ASConfiguration.h */, - CCEDDDD0200C488000FFCD0A /* ASConfiguration.mm */, - CCEDDDC8200C2AC300FFCD0A /* ASConfigurationInternal.h */, - CCEDDDC9200C2AC300FFCD0A /* ASConfigurationInternal.mm */, - CCEDDDCE200C42A200FFCD0A /* ASConfigurationDelegate.h */, + 9D302F9A2231B07E005739C3 /* ASButtonNode+Private.h */, 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */, AC6456071B0A335000CF11B8 /* ASCellNode.mm */, DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */, @@ -1204,7 +921,6 @@ B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */, AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */, AC3C4A501A1139C100143C57 /* ASCollectionView.mm */, - B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */, AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */, DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, 058D09D5195D050800B7D73C /* ASControlNode.h */, @@ -1222,43 +938,23 @@ 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */, 9D9AA56C21E2568500172C09 /* ASDisplayNode+LayoutSpec.h */, 9D9AA56721E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm */, - 9D9AA56A21E254B800172C09 /* ASDisplayNode+Yoga.h */, - 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */, 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */, 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */, 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */, 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */, - CC7AF195200D9BD500A21BDE /* ASExperimentalFeatures.h */, - CC7AF197200D9E8400A21BDE /* ASExperimentalFeatures.mm */, 058D09DD195D050800B7D73C /* ASImageNode.h */, 058D09DE195D050800B7D73C /* ASImageNode.mm */, - 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */, CCF1FF5D20C4785000AAD8FC /* ASLocking.h */, CCBDDD0320C62A2D00CBA922 /* ASMainThreadDeallocation.h */, CCBDDD0420C62A2D00CBA922 /* ASMainThreadDeallocation.mm */, - 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */, - 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */, - 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */, - 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */, - 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */, - 68FC85DD1CE29AB700EDD713 /* ASNavigationController.mm */, CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */, CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.mm */, 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */, 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */, - 698371D91E4379CD00437585 /* ASNodeController+Beta.h */, - 698371DA1E4379CD00437585 /* ASNodeController+Beta.mm */, - DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */, - DBDB83931C6E879900D0098C /* ASPagerFlowLayout.mm */, - 25E327541C16819500A2170C /* ASPagerNode.h */, - 25E327551C16819500A2170C /* ASPagerNode.mm */, - E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */, CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */, D785F6601A74327E00291744 /* ASScrollNode.h */, D785F6611A74327E00291744 /* ASScrollNode.mm */, ACE87A2B1D73696800D7FF06 /* ASSectionContext.h */, - 68FC85E01CE29B7E00EDD713 /* ASTabBarController.h */, - 68FC85E11CE29B7E00EDD713 /* ASTabBarController.mm */, B0F880581BEAEC7500D17647 /* ASTableNode.h */, 9CFFC6C11CCAC768006A6476 /* ASTableNode.mm */, E5C347B21ECB40AA00EC4BE4 /* ASTableNode+Beta.h */, @@ -1270,24 +966,9 @@ 058D09DF195D050800B7D73C /* ASTextNode.h */, 058D09E0195D050800B7D73C /* ASTextNode.mm */, A373200E1C571B050011FC94 /* ASTextNode+Beta.h */, - 909C4C731F09C98B00D6B76F /* ASTextNode2.h */, - 909C4C741F09C98B00D6B76F /* ASTextNode2.mm */, - AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */, - AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */, - 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */, - 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */, - ACC945A81BA9E7A0005E1FB8 /* ASDKViewController.h */, - 9CFFC6BF1CCAC73C006A6476 /* ASDKViewController.mm */, - 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */, - 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.mm */, 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */, 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.mm */, - CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */, - CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.mm */, - 9D302F9A2231B07E005739C3 /* ASButtonNode+Private.h */, - 9D302F9C2231B373005739C3 /* ASButtonNode+Yoga.h */, - 9D302F9D2231B373005739C3 /* ASButtonNode+Yoga.mm */, ); path = Source; sourceTree = ""; @@ -1320,7 +1001,6 @@ 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.mm */, 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */, 9692B4FE219E12370060C2C3 /* ASCollectionViewThrashTests.mm */, - CCEDDDD8200C518800FFCD0A /* ASConfigurationTests.mm */, 2911485B1A77147A005D0878 /* ASControlNodeTests.mm */, 1A6C000F1FAB4ED400D05926 /* ASCornerLayoutSpecSnapshotTests.mm */, ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */, @@ -1350,15 +1030,10 @@ 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.mm */, CCE4F9B61F0DBA5000062E4E /* ASLayoutTestNode.h */, CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */, - 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.mm */, - 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.mm */, - BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.mm */, CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.mm */, ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */, - AE6987C01DD04E1000B9E458 /* ASPagerNodeTests.mm */, CC8B05D41D73836400F54286 /* ASPerformanceTestContext.h */, CC8B05D51D73836400F54286 /* ASPerformanceTestContext.mm */, - CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.mm */, ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, CCAA0B81206ADECB0057B336 /* ASRecursiveUnfairLockTests.mm */, 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, @@ -1367,15 +1042,12 @@ 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */, ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */, - BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.mm */, 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */, CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.mm */, 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.mm */, AE440174210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm */, 254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */, 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */, - C057D9BC20B5453D00FC9112 /* ASTextNode2SnapshotTests.mm */, - F325E48F217460B000AC93A4 /* ASTextNode2Tests.mm */, CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.mm */, 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.mm */, 058D0A36195D057000B7D73C /* ASTextNodeTests.mm */, @@ -1386,8 +1058,6 @@ CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */, D933F040224AD17F00FF495E /* ASTransactionTests.mm */, CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.mm */, - AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.mm */, - CCA221D21D6FA7EF00AF6A0F /* ASDKViewControllerTests.mm */, 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.mm */, CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.mm */, 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */, @@ -1415,7 +1085,6 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( - E5B077EB1E6843AF00C24B5B /* Collection Layout */, 25B171EA1C12242700508A7A /* Data Controller */, 058D09F7195D050800B7D73C /* Transactions */, 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */, @@ -1454,14 +1123,8 @@ 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */, 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */, 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */, - 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */, - 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.mm */, 6907C2561DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h */, 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.mm */, - CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */, - CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.mm */, - 68355B391CB57A5A001D4E68 /* ASPINRemoteImageDownloader.h */, - 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.mm */, 055F1A3619ABD413004DAFF1 /* ASRangeController.h */, 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */, 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */, @@ -1511,8 +1174,6 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( - CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */, - E52F8AEE1EAE659600B5A912 /* Collection Layout */, 6947B0BB1E36B4E30007C478 /* Layout */, 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */, 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */, @@ -1531,13 +1192,8 @@ CC2E317F1DAC353700EEE891 /* ASCollectionView+Undeprecated.h */, CC0F885A1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h */, CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.mm */, - FA4FAF14200A850200E735BD /* ASControlNode+Private.h */, 9F98C0231DBDF2A300476D92 /* ASControlTargetAction.h */, 9F98C0241DBDF2A300476D92 /* ASControlTargetAction.mm */, - 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */, - 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.mm */, - AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */, - AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.mm */, CC54A81B1D70077A00296A24 /* ASDispatch.h */, E5B2252D1F17E521001E1431 /* ASDispatch.mm */, 058D0A08195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm */, @@ -1549,16 +1205,11 @@ 690BC8C020F6D3490052A434 /* ASDisplayNodeCornerLayerDelegate.mm */, 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */, 6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */, - CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */, - CCA282C71E9EB64B0037E8B7 /* ASDisplayNodeTipState.mm */, - 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */, 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */, 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.mm */, 6900C5F31E8072DA00BCD75C /* ASImageNode+Private.h */, ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */, ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.mm */, - CCA282C21E9EAE630037E8B7 /* ASLayerBackingTipProvider.h */, - CCA282C31E9EAE630037E8B7 /* ASLayerBackingTipProvider.mm */, E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */, E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */, CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */, @@ -1569,16 +1220,6 @@ CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */, CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.mm */, CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */, - CCA282BE1E9EAE010037E8B7 /* ASTip.h */, - CCA282BF1E9EAE010037E8B7 /* ASTip.mm */, - CCA282CA1E9EB73E0037E8B7 /* ASTipNode.h */, - CCA282CB1E9EB73E0037E8B7 /* ASTipNode.mm */, - CCA282BA1E9EABDD0037E8B7 /* ASTipProvider.h */, - CCA282BB1E9EABDD0037E8B7 /* ASTipProvider.mm */, - CCA282B21E9EA7310037E8B7 /* ASTipsController.h */, - CCA282B31E9EA7310037E8B7 /* ASTipsController.mm */, - CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */, - CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.mm */, 0442850B1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h */, 0442850C1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.mm */, 83A7D9581D44542100BF333E /* ASWeakMap.h */, @@ -1654,15 +1295,6 @@ name = "Data Controller"; sourceTree = ""; }; - 690ED5911E36D118000627C0 /* tvOS */ = { - isa = PBXGroup; - children = ( - 690ED5931E36D118000627C0 /* ASControlNode+tvOS.mm */, - 690ED5951E36D118000627C0 /* ASImageNode+tvOS.mm */, - ); - path = tvOS; - sourceTree = ""; - }; 6947B0BB1E36B4E30007C478 /* Layout */ = { isa = PBXGroup; children = ( @@ -1678,55 +1310,6 @@ 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 = ( @@ -1749,8 +1332,6 @@ ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */, ACF6ED0B1B17843500DA7C62 /* ASLayout.h */, ACF6ED0C1B17843500DA7C62 /* ASLayout.mm */, - E517F9C723BF14BC006E40E0 /* ASLayout+IGListDiffKit.h */, - E517F9C623BF14BC006E40E0 /* ASLayout+IGListDiffKit.mm */, ACF6ED111B17843500DA7C62 /* ASLayoutElement.h */, E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */, 698C8B601CAB49FC0052DC3F /* ASLayoutElementExtensibility.h */, @@ -1769,20 +1350,10 @@ 9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */, ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */, ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */, - 9019FBBB1ED8061D00C45F72 /* ASYogaUtilities.h */, - 9019FBBC1ED8061D00C45F72 /* ASYogaUtilities.mm */, ); path = Layout; sourceTree = ""; }; - CC224E942066CA6D00BBA57F /* Schemas */ = { - isa = PBXGroup; - children = ( - CC224E952066CA6D00BBA57F /* configuration.json */, - ); - path = Schemas; - sourceTree = ""; - }; CC583ABF1EF9BAB400134156 /* Common */ = { isa = PBXGroup; children = ( @@ -1799,36 +1370,6 @@ path = Common; sourceTree = ""; }; - CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */ = { - isa = PBXGroup; - children = ( - CCF92DCE1E315FC50019E9C6 /* IGListKit Support */, - CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */, - CCE04B2B1E314A32006AEBBB /* ASSupplementaryNodeSource.h */, - ); - name = "Collection Data Adapter"; - sourceTree = ""; - }; - CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */ = { - isa = PBXGroup; - children = ( - CCBD05DF1E4147B000D18509 /* ASIGListAdapterBasedDataSource.h */, - CCBD05DE1E4147B000D18509 /* ASIGListAdapterBasedDataSource.mm */, - ); - name = "Collection Data Adapter"; - sourceTree = ""; - }; - CCF92DCE1E315FC50019E9C6 /* IGListKit Support */ = { - isa = PBXGroup; - children = ( - CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */, - CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.mm */, - CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */, - CCE04B211E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.mm */, - ); - name = "IGListKit Support"; - sourceTree = ""; - }; DE89C1691DCEB9CC00D49D74 /* Debug */ = { isa = PBXGroup; children = ( @@ -1840,43 +1381,6 @@ path = Debug; sourceTree = ""; }; - E52F8AEE1EAE659600B5A912 /* Collection Layout */ = { - isa = PBXGroup; - children = ( - E5667E8B1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h */, - E5667E8D1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.mm */, - E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */, - E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */, - E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */, - E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */, - E5775B011F16759300CAC9BC /* ASCollectionLayoutCache.h */, - E5775B031F16759F00CAC9BC /* ASCollectionLayoutCache.mm */, - E5B5B9D01E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h */, - E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */, - E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.mm */, - E5775AFF1F13D25400CAC9BC /* ASCollectionLayoutState+Private.h */, - ); - name = "Collection Layout"; - sourceTree = ""; - }; - E5B077EB1E6843AF00C24B5B /* Collection Layout */ = { - isa = PBXGroup; - children = ( - E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */, - E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.mm */, - E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */, - E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm */, - E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */, - E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.mm */, - E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */, - E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */, - E5E281751E71C845006B67C2 /* ASCollectionLayoutState.mm */, - E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */, - E54E81FB1EB357BD00FFE8E1 /* ASPageTable.mm */, - ); - name = "Collection Layout"; - sourceTree = ""; - }; FD40E2760492F0CAAEAD552D /* Pods */ = { isa = PBXGroup; children = ( @@ -1894,37 +1398,21 @@ 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 */, E5B225281F1790D6001E1431 /* ASHashing.h in Headers */, - CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */, - 693A1DCA1ECC944E00D0C9D2 /* IGListAdapter+AsyncDisplayKit.h in Headers */, - E5E2D72E1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h in Headers */, - E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */, CCBDDD0520C62A2D00CBA922 /* ASMainThreadDeallocation.h in Headers */, CCAA0B7F206ADBF30057B336 /* ASRecursiveUnfairLock.h in Headers */, - E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */, 9D9AA56D21E2568500172C09 /* ASDisplayNode+LayoutSpec.h in Headers */, E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */, - E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */, - E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */, 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */, 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */, CC18248C200D49C800875940 /* ASTextNodeCommon.h in Headers */, - 698371DB1E4379CD00437585 /* ASNodeController+Beta.h in Headers */, 6907C2581DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h in Headers */, 69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */, 698C8B621CAB49FC0052DC3F /* ASLayoutElementExtensibility.h in Headers */, 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */, B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */, 68355B411CB57A6C001D4E68 /* ASImageContainerProtocolCategories.h in Headers */, - 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */, B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, 68C215581DE10D330019C4BC /* ASCollectionViewLayoutInspector.h in Headers */, @@ -1938,16 +1426,12 @@ CCBBBF5D1EB161760069AA91 /* ASRangeManagingNode.h in Headers */, 9D302F9B2231B07E005739C3 /* ASButtonNode+Private.h in Headers */, B35062581B010F070018CF92 /* ASAvailability.h in Headers */, - 9019FBBF1ED8061D00C45F72 /* ASYogaUtilities.h in Headers */, DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */, - CC0F88621E4281E200576FED /* ASSectionController.h in Headers */, 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 */, @@ -1957,26 +1441,21 @@ ACE87A2C1D73696800D7FF06 /* ASSectionContext.h in Headers */, 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */, B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */, - 68FC85E31CE29B7E00EDD713 /* ASTabBarController.h in Headers */, CC56013B1F06E9A700DC4FBE /* ASIntegerMap.h in Headers */, B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */, - E54E81FC1EB357BD00FFE8E1 /* ASPageTable.h in Headers */, CCF1FF5E20C4785000AAD8FC /* ASLocking.h in Headers */, B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */, B35062171B010EFD0018CF92 /* ASDataController.h in Headers */, 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */, - 68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */, A37320101C571B740011FC94 /* ASTextNode+Beta.h in Headers */, 9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */, CC6AA2DA1E9F03B900978E87 /* ASDisplayNode+Ancestry.h in Headers */, 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */, B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */, - CCEDDDCD200C2CB900FFCD0A /* ASConfiguration.h in Headers */, B35061FB1B010EFD0018CF92 /* ASDisplayNode.h in Headers */, B35061FE1B010EFD0018CF92 /* ASDisplayNodeExtras.h in Headers */, CC0F88601E4280B800576FED /* _ASCollectionViewCell.h in Headers */, B35062001B010EFD0018CF92 /* ASEditableTextNode.h in Headers */, - 680346941CE4052A0009FEB4 /* ASNavigationController.h in Headers */, B350621B1B010EFD0018CF92 /* ASTableLayoutController.h in Headers */, B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */, C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */, @@ -1984,13 +1463,9 @@ B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */, B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */, 34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */, - CCEDDDCA200C2AC300FFCD0A /* ASConfigurationInternal.h in Headers */, 34EFC7671B701CD900AD841F /* ASLayout.h in Headers */, - DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */, 34EFC7691B701CE100AD841F /* ASLayoutElement.h in Headers */, 698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */, - 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */, - 9D302F9E2231B373005739C3 /* ASButtonNode+Yoga.h in Headers */, CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, @@ -2000,30 +1475,19 @@ 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 */, 254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */, 254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */, 690ED58E1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h in Headers */, - CC55A70D1E529FA200594372 /* UIResponder+AsyncDisplayKit.h in Headers */, 254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */, 254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */, 69CB62AC1CB8165900024920 /* _ASDisplayViewAccessiblity.h in Headers */, 254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */, - 68AF37DB1CBEF4D80077BF76 /* ASImageNode+AnimatedImagePrivate.h in Headers */, B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */, 044285081BAA63FE00D16268 /* ASBatchFetching.h in Headers */, AC026B701BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */, CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */, - E5775B021F16759300CAC9BC /* ASCollectionLayoutCache.h in Headers */, - E5775B001F13D25400CAC9BC /* ASCollectionLayoutState+Private.h in Headers */, - 4080D66C2350384400CDC199 /* ASPINRemoteImageDownloader.h in Headers */, - E5667E8C1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h in Headers */, - E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */, - E5855DF01EBB4D83003639AE /* ASCollectionLayoutDefines.h in Headers */, - E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */, 9C8898BD1C738BB800D6B02E /* ASTextKitFontSizeAdjuster.h in Headers */, 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */, CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */, @@ -2031,21 +1495,16 @@ 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */, 6947B0C31E36B5040007C478 /* ASStackPositionedLayout.h in Headers */, DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */, - FA4FAF15200A850200E735BD /* ASControlNode+Private.h in Headers */, B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */, CC57EAF71E3939350034C595 /* ASCollectionView+Undeprecated.h in Headers */, B35062521B010EFD0018CF92 /* ASDisplayNodeInternal.h in Headers */, AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */, - E58E9E491E941DA5004CFC59 /* ASCollectionLayout.h in Headers */, 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */, - 9D9AA56B21E254B800172C09 /* ASDisplayNode+Yoga.h in Headers */, CC58AA4B1E398E1D002C8CB4 /* ASBlockTypes.h in Headers */, - CCA282BC1E9EABDD0037E8B7 /* ASTipProvider.h in Headers */, 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 */, @@ -2054,7 +1513,6 @@ DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */, B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */, AC6145411D8AFAE8003D62A2 /* ASSection.h in Headers */, - 8BBBAB8C1CEBAF1700107FC6 /* ASDefaultPlaybackButton.h in Headers */, 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, 698DFF441E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h in Headers */, CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */, @@ -2071,44 +1529,28 @@ B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */, CC2F65EE1E5FFB1600DA57C9 /* ASMutableElementMap.h in Headers */, 34EFC76A1B701CE600AD841F /* ASLayoutSpec.h in Headers */, - CCA282D01E9EBF6C0037E8B7 /* ASTipsWindow.h in Headers */, B350625C1B010F070018CF92 /* ASLog.h in Headers */, CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */, - B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, - B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, - B13CA0F81C519EBA00E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h in Headers */, - 909C4C751F09C98B00D6B76F /* ASTextNode2.h in Headers */, B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */, - CCA282C81E9EB64B0037E8B7 /* ASDisplayNodeTipState.h in Headers */, 34EFC76C1B701CED00AD841F /* ASOverlayLayoutSpec.h in Headers */, B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */, 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, - CCA282C41E9EAE630037E8B7 /* ASLayerBackingTipProvider.h in Headers */, - CCEDDDCF200C42A200FFCD0A /* ASConfigurationDelegate.h in Headers */, E5C347B31ECB40AA00EC4BE4 /* ASTableNode+Beta.h in Headers */, 6900C5F41E8072DA00BCD75C /* ASImageNode+Private.h in Headers */, 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */, B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */, 254C6B751BF94DF4003EC431 /* ASTextKitComponents.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, - CCA282CC1E9EB73E0037E8B7 /* ASTipNode.h in Headers */, - 25E327571C16819500A2170C /* ASPagerNode.h in Headers */, 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */, CC35CEC320DD7F600006448D /* ASCollections.h in Headers */, - CC7AF196200D9BD500A21BDE /* ASExperimentalFeatures.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, CC0F885C1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h in Headers */, 764D83D51C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h in Headers */, - CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, 254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */, - CCA282B41E9EA7310037E8B7 /* ASTipsController.h in Headers */, 34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */, - CCA282C01E9EAE010037E8B7 /* ASTip.h in Headers */, - 2767E9411BB19BD600EA9B77 /* ASDKViewController.h in Headers */, - 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */, 9C6BB3B31B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h in Headers */, 34EFC7731B701D0700AD841F /* ASAbsoluteLayoutSpec.h in Headers */, B350620A1B010EFD0018CF92 /* ASTableView.h in Headers */, @@ -2121,7 +1563,6 @@ B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */, 044284FF1BAA3BD600D16268 /* UICollectionViewLayout+ASConvenience.h in Headers */, B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */, - 8BDA5FC71CDBDF91007D13B2 /* ASVideoPlayerNode.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2248,7 +1689,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - CC224E962066CA6D00BBA57F /* configuration.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2310,7 +1750,6 @@ buildActionMask = 2147483647; files = ( D933F041224AD17F00FF495E /* ASTransactionTests.mm in Sources */, - CCEDDDD9200C518800FFCD0A /* ASConfigurationTests.mm in Sources */, AE440175210FB7CF00B36DA2 /* ASTextKitFontSizeAdjusterTests.mm in Sources */, E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.mm in Sources */, 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.mm in Sources */, @@ -2325,18 +1764,14 @@ CC01EB6F23105C7F00CDB61A /* ASImageNodeSnapshotTests.mm in Sources */, CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.mm in Sources */, F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.mm in Sources */, - BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.mm in Sources */, 81FF150722EB5F410039311A /* ASButtonNodeSnapshotTests.mm in Sources */, ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */, - BB5FC3D11F9C9389007F191E /* ASTabBarControllerTests.mm in Sources */, 695BE2551DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm in Sources */, - CCA221D31D6FA7EF00AF6A0F /* ASDKViewControllerTests.mm in Sources */, 058D0A38195D057000B7D73C /* ASDisplayLayerTests.mm in Sources */, 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.mm in Sources */, CC583AD61EF9BDBE00134156 /* ASTestCase.mm in Sources */, 058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.mm in Sources */, CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.mm in Sources */, - AE6987C11DD04E1000B9E458 /* ASPagerNodeTests.mm in Sources */, 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.mm in Sources */, 9644CFE02193777C00213478 /* ASThrashUtility.m in Sources */, 696FCB311D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm in Sources */, @@ -2354,10 +1789,7 @@ AC026B581BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.mm in Sources */, ACF6ED5E1B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm in Sources */, ACF6ED601B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.mm in Sources */, - CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.mm in Sources */, - 052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.mm in Sources */, 407B8BAE2310E2ED00CB979E /* ASLayoutSpecUtilitiesTests.mm in Sources */, - 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.mm in Sources */, F325E48C21745F9E00AC93A4 /* ASButtonNodeTests.mm in Sources */, 9692B4FF219E12370060C2C3 /* ASCollectionViewThrashTests.mm in Sources */, E586F96C1F9F9E2900ECE00E /* ASScrollNodeTests.mm in Sources */, @@ -2369,7 +1801,6 @@ CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.mm in Sources */, CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */, 69B225671D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm in Sources */, - C057D9BD20B5453D00FC9112 /* ASTextNode2SnapshotTests.mm in Sources */, ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */, CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.mm in Sources */, @@ -2383,9 +1814,7 @@ CCAA0B82206ADECB0057B336 /* ASRecursiveUnfairLockTests.mm in Sources */, 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.mm in Sources */, 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */, - AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.mm in Sources */, 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */, - F325E490217460B100AC93A4 /* ASTextNode2Tests.mm in Sources */, 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.mm in Sources */, CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */, CCE4F9BE1F0ECE5200062E4E /* ASTLayoutFixture.mm in Sources */, @@ -2405,39 +1834,25 @@ DEB8ED7C1DD003D300DBDE55 /* ASLayoutTransition.mm in Sources */, 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 */, - 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.mm in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.mm in Sources */, - 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, CCA282B91E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.mm in Sources */, - 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.mm in Sources */, B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.mm in Sources */, 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 */, B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */, 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */, B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */, 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.mm in Sources */, B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */, - DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */, - CCA282C11E9EAE010037E8B7 /* ASTip.mm in Sources */, B350624C1B010EFD0018CF92 /* _ASPendingState.mm in Sources */, - 698371DC1E4379CD00437585 /* ASNodeController+Beta.mm in Sources */, CC6AA2DB1E9F03B900978E87 /* ASDisplayNode+Ancestry.mm in Sources */, 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, - DBDB83971C6E879900D0098C /* ASPagerFlowLayout.mm in Sources */, E5B078001E69F4EB00C24B5B /* ASElementMap.mm in Sources */, 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, - 690ED59B1E36D118000627C0 /* ASImageNode+tvOS.mm in Sources */, CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.mm in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, 9D9AA56921E23EE200172C09 /* ASDisplayNode+LayoutSpec.mm in Sources */, @@ -2445,44 +1860,30 @@ B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */, AC47D9421B3B891B00AAEE9D /* ASCellNode.mm in Sources */, - E58E9E451E941D74004CFC59 /* ASCollectionLayoutContext.mm in Sources */, 34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */, 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */, E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */, - 68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.mm in Sources */, CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.mm in Sources */, CCED5E3F2020D36800395C40 /* ASNetworkImageLoadInfo.mm in Sources */, 68B8A4E41CBDB958007E4543 /* ASWeakProxy.mm in Sources */, - E5775B041F16759F00CAC9BC /* ASCollectionLayoutCache.mm in Sources */, 9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */, 69CB62AE1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, B35061F61B010EFD0018CF92 /* ASCollectionView.mm in Sources */, - CCA282C51E9EAE630037E8B7 /* ASLayerBackingTipProvider.mm in Sources */, 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 */, 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */, B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */, - E5667E8E1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.mm in Sources */, - 25E327591C16819500A2170C /* ASPagerNode.mm in Sources */, 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.mm in Sources */, B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */, 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 */, B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */, B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */, @@ -2494,81 +1895,54 @@ CC0F885F1E4280B800576FED /* _ASCollectionViewCell.mm in Sources */, CC2F65EF1E5FFB1600DA57C9 /* ASMutableElementMap.mm in Sources */, B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.mm in Sources */, - E58E9E4A1E941DA5004CFC59 /* ASCollectionLayout.mm in Sources */, 6947B0C01E36B4E30007C478 /* ASStackUnpositionedLayout.mm in Sources */, 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.mm in Sources */, - E5855DEF1EBB4D83003639AE /* ASCollectionLayoutDefines.mm in Sources */, B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */, 690BC8C220F6D3490052A434 /* ASDisplayNodeCornerLayerDelegate.mm in Sources */, 254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */, 34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */, AC6145441D8AFD4F003D62A2 /* ASSection.mm in Sources */, - E5775AFE1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm in Sources */, 34EFC75E1B701BF000AD841F /* ASInternalHelpers.mm in Sources */, 34EFC7681B701CDE00AD841F /* ASLayout.mm in Sources */, DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, - CCEDDDD1200C488000FFCD0A /* ASConfiguration.mm in Sources */, 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.mm in Sources */, - E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm in Sources */, 34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */, CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */, 254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */, 6907C25A1DC4ECFE00374C66 /* ASObjectDescriptionHelpers.mm in Sources */, - B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */, - B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.mm in Sources */, B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */, 34EFC76D1B701CF100AD841F /* ASOverlayLayoutSpec.mm in Sources */, 044285101BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.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 /* ASNavigationController.mm in Sources */, - 9C0BA4A42582CE35001C293B /* ASTextAttribute.mm in Sources */, 34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */, 254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */, 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */, - 90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */, - CCA282C91E9EB64B0037E8B7 /* ASDisplayNodeTipState.mm in Sources */, 509E68601B3AED8E009B9150 /* ASScrollDirection.mm in Sources */, - E517F9C823BF14BC006E40E0 /* ASLayout+IGListDiffKit.mm in Sources */, B35062091B010EFD0018CF92 /* ASScrollNode.mm in Sources */, 69BCE3D91EC6513B007DCCAD /* ASDisplayNode+Layout.mm in Sources */, - 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */, - E54E81FD1EB357BD00FFE8E1 /* ASPageTable.mm in Sources */, 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */, 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */, - CC7AF198200DAB2200A21BDE /* ASExperimentalFeatures.mm in Sources */, E5B2252E1F17E521001E1431 /* ASDispatch.mm in Sources */, 9C70F2051CDA4F06007D6C76 /* ASTraitCollection.mm in Sources */, 83A7D95B1D44547700BF333E /* ASWeakMap.mm in Sources */, CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.mm in Sources */, - E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.mm in Sources */, DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */, - 68FC85E51CE29B7E00EDD713 /* ASTabBarController.mm in Sources */, 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */, 1A6C000E1FAB4E2100D05926 /* ASCornerLayoutSpec.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 */, - 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.mm in Sources */, - CC034A141E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.mm in Sources */, 254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.mm in Sources */, - 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.mm in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.mm in Sources */, - CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.mm in Sources */, CC56013C1F06E9A700DC4FBE /* ASIntegerMap.mm in Sources */, 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.mm in Sources */, - CCA282CD1E9EB73E0037E8B7 /* ASTipNode.mm in Sources */, 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.mm in Sources */, CC0F885B1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.mm in Sources */, - 690ED5981E36D118000627C0 /* ASControlNode+tvOS.mm in Sources */, 254C6B8A1BF94F8A003EC431 /* ASTextKitRenderer+TextChecking.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2671,10 +2045,6 @@ GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", - "AS_USE_ASSETS_LIBRARY=1", - "AS_USE_MAPKIT=1", - "AS_USE_PHOTOS=1", - "AS_USE_VIDEO=1", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -2758,6 +2128,10 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; INFOPLIST_FILE = "Tests/AsyncDisplayKitTests-Info.plist"; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-Wno-gnu-inline-cpp-without-extern", + ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; @@ -2781,6 +2155,10 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; INFOPLIST_FILE = "Tests/AsyncDisplayKitTests-Info.plist"; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-Wno-gnu-inline-cpp-without-extern", + ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; @@ -2917,6 +2295,10 @@ GCC_TREAT_WARNINGS_AS_ERRORS = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; INFOPLIST_FILE = "Tests/AsyncDisplayKitTests-Info.plist"; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-Wno-gnu-inline-cpp-without-extern", + ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncDisplayKitTestHost.app/AsyncDisplayKitTestHost"; diff --git a/Podfile.lock b/Podfile.lock index 95acfcef1f..40314bd719 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -17,4 +17,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ff1a1777e31f49e6e4b7b148d0f10e504db7fa26 -COCOAPODS: 1.9.1 +COCOAPODS: 1.10.1 diff --git a/Schemas/configuration.json b/Schemas/configuration.json deleted file mode 100644 index 481663b2d5..0000000000 --- a/Schemas/configuration.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "id": "configuration.json", - "title": "configuration", - "description" : "Schema definition of a Texture Configuration", - "$schema": "http://json-schema.org/schema#", - "type": "object", - "properties": { - "version" : { - "type" : "number" - }, - "experimental_features": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "exp_text_node", - "exp_interface_state_coalesce", - "exp_infer_layer_defaults", - "exp_collection_teardown", - "exp_framesetter_cache", - "exp_skip_clear_data", - "exp_did_enter_preload_skip_asm_layout", - "exp_dispatch_apply", - "exp_drawing_global", - "exp_optimize_data_controller_pipeline", - "exp_disable_global_textkit_lock", - "exp_main_thread_only_data_controller", - ] - } - } - } -} diff --git a/Source/ASButtonNode+Yoga.h b/Source/ASButtonNode+Yoga.h deleted file mode 100644 index 4fddc9df3a..0000000000 --- a/Source/ASButtonNode+Yoga.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// ASButtonNode+Yoga.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASButtonNode (Yoga) - -- (void)updateYogaLayoutIfNeeded; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASButtonNode+Yoga.mm b/Source/ASButtonNode+Yoga.mm deleted file mode 100644 index f4493bc402..0000000000 --- a/Source/ASButtonNode+Yoga.mm +++ /dev/null @@ -1,105 +0,0 @@ -// -// ASButtonNode+Yoga.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "ASButtonNode+Yoga.h" -#import -#import -#import - -#if YOGA -static void ASButtonNodeResolveHorizontalAlignmentForStyle(ASLayoutElementStyle *style, ASStackLayoutDirection _direction, ASHorizontalAlignment _horizontalAlignment, ASStackLayoutJustifyContent _justifyContent, ASStackLayoutAlignItems _alignItems) { - if (_direction == ASStackLayoutDirectionHorizontal) { - style.justifyContent = justifyContent(_horizontalAlignment, _justifyContent); - } else { - style.alignItems = alignment(_horizontalAlignment, _alignItems); - } -} - -static void ASButtonNodeResolveVerticalAlignmentForStyle(ASLayoutElementStyle *style, ASStackLayoutDirection _direction, ASVerticalAlignment _verticalAlignment, ASStackLayoutJustifyContent _justifyContent, ASStackLayoutAlignItems _alignItems) { - if (_direction == ASStackLayoutDirectionHorizontal) { - style.alignItems = alignment(_verticalAlignment, _alignItems); - } else { - style.justifyContent = justifyContent(_verticalAlignment, _justifyContent); - } -} - -@implementation ASButtonNode (Yoga) - -- (void)updateYogaLayoutIfNeeded -{ - NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:2]; - { - ASLockScopeSelf(); - - // Build up yoga children for button node again - unowned ASLayoutElementStyle *style = [self _locked_style]; - [style yogaNodeCreateIfNeeded]; - - // Setup stack layout values - style.flexDirection = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; - - // Resolve horizontal and vertical alignment - ASButtonNodeResolveHorizontalAlignmentForStyle(style, style.flexDirection, _contentHorizontalAlignment, style.justifyContent, style.alignItems); - ASButtonNodeResolveVerticalAlignmentForStyle(style, style.flexDirection, _contentVerticalAlignment, style.justifyContent, style.alignItems); - - // Setup new yoga children - if (_imageNode.image != nil) { - [_imageNode.style yogaNodeCreateIfNeeded]; - [children addObject:_imageNode]; - } - - if (_titleNode.attributedText.length > 0) { - [_titleNode.style yogaNodeCreateIfNeeded]; - if (_imageAlignment == ASButtonNodeImageAlignmentBeginning) { - [children addObject:_titleNode]; - } else { - [children insertObject:_titleNode atIndex:0]; - } - } - - // Add spacing between title and button - if (children.count == 2) { - unowned ASLayoutElementStyle *firstChildStyle = children.firstObject.style; - if (_laysOutHorizontally) { - firstChildStyle.margin = ASEdgeInsetsMake(UIEdgeInsetsMake(0, 0, 0, _contentSpacing)); - } else { - firstChildStyle.margin = ASEdgeInsetsMake(UIEdgeInsetsMake(0, 0, _contentSpacing, 0)); - } - } - - // Add padding to button - if (UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, _contentEdgeInsets) == NO) { - style.padding = ASEdgeInsetsMake(_contentEdgeInsets); - } - - // Add background node - if (_backgroundImageNode.image) { - [_backgroundImageNode.style yogaNodeCreateIfNeeded]; - [children insertObject:_backgroundImageNode atIndex:0]; - - _backgroundImageNode.style.positionType = YGPositionTypeAbsolute; - _backgroundImageNode.style.position = ASEdgeInsetsMake(UIEdgeInsetsZero); - } - } - - // Update new children - [self setYogaChildren:children]; -} - -@end - -#else - -@implementation ASButtonNode (Yoga) - -- (void)updateYogaLayoutIfNeeded {} - -@end - -#endif diff --git a/Source/ASButtonNode.mm b/Source/ASButtonNode.mm index 66b9cd3322..f2eaae408d 100644 --- a/Source/ASButtonNode.mm +++ b/Source/ASButtonNode.mm @@ -8,7 +8,6 @@ // #import -#import #import #import #import @@ -41,8 +40,6 @@ - (instancetype)init _contentEdgeInsets = UIEdgeInsetsZero; _imageAlignment = ASButtonNodeImageAlignmentBeginning; self.accessibilityTraits = self.defaultAccessibilityTraits; - - [self updateYogaLayoutIfNeeded]; } return self; } @@ -52,13 +49,7 @@ - (ASTextNode *)titleNode ASLockScopeSelf(); if (!_titleNode) { _titleNode = [[ASTextNode alloc] init]; - #if TARGET_OS_TV - // tvOS needs access to the underlying view - // of the button node to add a touch handler. - [_titleNode setLayerBacked:NO]; - #else - [_titleNode setLayerBacked:YES]; - #endif + [_titleNode setLayerBacked:YES]; _titleNode.style.flexShrink = 1.0; _titleNode.textColorFollowsTintColor = YES; } @@ -169,7 +160,6 @@ - (void)updateImage _imageNode.image = newImage; [self unlock]; - [self updateYogaLayoutIfNeeded]; [self setNeedsLayout]; return; } @@ -201,7 +191,6 @@ - (void)updateTitle [self unlock]; self.accessibilityLabel = self.defaultAccessibilityLabel; - [self updateYogaLayoutIfNeeded]; [self setNeedsLayout]; return; } @@ -230,7 +219,6 @@ - (void)updateBackgroundImage _backgroundImageNode.image = newImage; [self unlock]; - [self updateYogaLayoutIfNeeded]; [self setNeedsLayout]; return; } @@ -247,7 +235,6 @@ - (CGFloat)contentSpacing - (void)setContentSpacing:(CGFloat)contentSpacing { if (ASLockedSelfCompareAssign(_contentSpacing, contentSpacing)) { - [self updateYogaLayoutIfNeeded]; [self setNeedsLayout]; } } @@ -261,7 +248,6 @@ - (BOOL)laysOutHorizontally - (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally { if (ASLockedSelfCompareAssign(_laysOutHorizontally, laysOutHorizontally)) { - [self updateYogaLayoutIfNeeded]; [self setNeedsLayout]; } } @@ -515,8 +501,6 @@ - (UIAccessibilityTraits)defaultAccessibilityTraits } #pragma mark - Layout - -#if !YOGA - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { UIEdgeInsets contentEdgeInsets; @@ -561,7 +545,6 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize return spec; } -#endif - (void)layout { diff --git a/Source/ASCellNode.mm b/Source/ASCellNode.mm index 208eb5f058..ad00c16952 100644 --- a/Source/ASCellNode.mm +++ b/Source/ASCellNode.mm @@ -20,7 +20,6 @@ #import #import -#import #import #import @@ -79,17 +78,11 @@ - (void)didLoad _viewController = _viewControllerBlock(); _viewControllerBlock = nil; - if ([_viewController isKindOfClass:[ASDKViewController class]]) { - ASDKViewController *asViewController = (ASDKViewController *)_viewController; - _viewControllerNode = asViewController.node; - [_viewController loadViewIfNeeded]; - } else { - // Careful to avoid retain cycle - UIViewController *viewController = _viewController; - _viewControllerNode = [[ASDisplayNode alloc] initWithViewBlock:^{ - return viewController.view; - }]; - } + // Careful to avoid retain cycle + UIViewController *viewController = _viewController; + _viewControllerNode = [[ASDisplayNode alloc] initWithViewBlock:^{ + return viewController.view; + }]; [self addSubnode:_viewControllerNode]; // Since we just loaded our node, and added _viewControllerNode as a subnode, diff --git a/Source/ASCollectionNode+Beta.h b/Source/ASCollectionNode+Beta.h index feb485ba89..2e0cac6e46 100644 --- a/Source/ASCollectionNode+Beta.h +++ b/Source/ASCollectionNode+Beta.h @@ -9,7 +9,7 @@ #import -@protocol ASCollectionViewLayoutFacilitatorProtocol, ASCollectionLayoutDelegate, ASBatchFetchingDelegate; +@protocol ASBatchFetchingDelegate; @class ASElementMap; NS_ASSUME_NONNULL_BEGIN @@ -28,29 +28,8 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly) ASElementMap *visibleElements; -@property (nullable, readonly) id layoutDelegate; - @property (nullable, nonatomic, weak) id batchFetchingDelegate; -/** - * When this mode is enabled, ASCollectionView matches the timing of UICollectionView as closely as - * possible, ensuring that all reload and edit operations are performed on the main thread as - * blocking calls. - * - * This mode is useful for applications that are debugging issues with their collection view - * implementation. In particular, some applications do not properly conform to the API requirement - * of UICollectionView, and these applications may experience difficulties with ASCollectionView. - * Providing this mode allows for developers to work towards resolving technical debt in their - * collection view data source, while ramping up asynchronous collection layout. - * - * NOTE: Because this mode results in expensive operations like cell layout being performed on the - * main thread, it should be used as a tool to resolve data source conformance issues with Apple - * collection view API. - * - * @default defaults to ASCellLayoutModeNone. - */ -@property (nonatomic) ASCellLayoutMode cellLayoutMode; - /** * Returns YES if the ASCollectionNode contents are completely synchronized with the underlying collection-view layout. */ @@ -64,15 +43,7 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)onDidFinishSynchronizing:(void (^)(void))didFinishSynchronizing; -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator; - -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(nullable id)layoutFacilitator; - -- (void)beginUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead."); - -- (void)endUpdatesAnimated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead."); - -- (void)endUpdatesAnimated:(BOOL)animated completion:(nullable void (^)(BOOL))completion ASDISPLAYNODE_DEPRECATED_MSG("Use -performBatchUpdates:completion: instead."); +- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; @end diff --git a/Source/ASCollectionNode.h b/Source/ASCollectionNode.h index fde5a13021..59dc9cdd99 100644 --- a/Source/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -14,7 +14,6 @@ #import #import -@protocol ASCollectionViewLayoutFacilitatorProtocol; @protocol ASCollectionDelegate; @protocol ASCollectionDataSource; @class ASCollectionView; @@ -549,16 +548,10 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASCollectionNode (Deprecated) - -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed."); - -@end - /** * This is a node-based UICollectionViewDataSource. */ -@protocol ASCollectionDataSource +@protocol ASCollectionDataSource @optional @@ -701,62 +694,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSIndexPath *)indexPathForElementWithModelIdentifier:(NSString *)identifier inNode:(ASCollectionNode *)collectionNode; -/** - * Similar to -collectionView:cellForItemAtIndexPath:. - * - * @param collectionView The sender. - * - * @param indexPath The index path of the requested node. - * - * @return a node for display at this indexpath. This will be called on the main thread and should - * not implement reuse (it will be called once per row). Unlike UICollectionView's version, - * this method is not called when the row is about to display. - */ -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Similar to -collectionView:nodeForItemAtIndexPath: - * This method takes precedence over collectionView:nodeForItemAtIndexPath: if implemented. - * - * @param collectionView The sender. - * - * @param indexPath The index path of the requested node. - * - * @return a block that creates the node for display at this indexpath. - * Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). - */ -- (ASCellNodeBlock)collectionView:(ASCollectionView *)collectionView nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Asks the collection view to provide a supplementary node to display in the collection view. - * - * @param collectionView An object representing the collection view requesting this information. - * @param kind The kind of supplementary node to provide. - * @param indexPath The index path that specifies the location of the new supplementary node. - */ -- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Indicator to lock the data source for data fetching in async mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception - * due to the data access in async mode. - * - * @param collectionView The sender. - * @deprecated The data source is always accessed on the main thread, and this method will not be called. - */ -- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called."); - -/** - * Indicator to unlock the data source for data fetching in async mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception - * due to the data access in async mode. - * - * @param collectionView The sender. - * @deprecated The data source is always accessed on the main thread, and this method will not be called. - */ -- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called."); - @end /** @@ -824,76 +761,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)shouldBatchFetchForCollectionNode:(ASCollectionNode *)collectionNode; -/** - * Provides the constrained size range for measuring the node at the index path. - * - * @param collectionView The sender. - * - * @param indexPath The index path of the node. - * - * @return A constrained size range for layout the node at this index path. - */ -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's constrainedSizeForItemAtIndexPath: instead. PLEASE NOTE the very subtle method name change."); - -/** - * Informs the delegate that the collection view will add the given node - * at the given index path to the view hierarchy. - * - * @param collectionView The sender. - * @param node The node that will be displayed. - * @param indexPath The index path of the item that will be displayed. - * - * @warning AsyncDisplayKit processes collection view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - */ -- (void)collectionView:(ASCollectionView *)collectionView willDisplayNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Informs the delegate that the collection view did remove the provided node from the view hierarchy. - * This may be caused by the node scrolling out of view, or by deleting the item - * or its containing section with @c deleteItemsAtIndexPaths: or @c deleteSections: . - * - * @param collectionView The sender. - * @param node The node which was removed from the view hierarchy. - * @param indexPath The index path at which the node was located before it was removed. - * - * @warning AsyncDisplayKit processes collection view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - */ -- (void)collectionView:(ASCollectionView *)collectionView didEndDisplayingNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -- (void)collectionView:(ASCollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Tell the collectionView if batch fetching should begin. - * - * @param collectionView The sender. - * - * @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of - * objects that can be fetched or no network connection. - * - * If not implemented, the collectionView assumes that it should notify its asyncDelegate when batch fetching - * should occur. - */ -- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - -/** - * Informs the delegate that the collection view will add the node - * at the given index path to the view hierarchy. - * - * @param collectionView The sender. - * @param indexPath The index path of the item that will be displayed. - * - * @warning AsyncDisplayKit processes collection view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - * - * This method is deprecated. Use @c collectionView:willDisplayNode:forItemAtIndexPath: instead. - */ -- (void)collectionView:(ASCollectionView *)collectionView willDisplayNodeForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - @end @protocol ASCollectionDataSourceInterop diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index f1da3e5ff4..aa8eaf7175 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -13,8 +13,6 @@ #import #import #import -#import -#import #import #import #import @@ -34,7 +32,6 @@ @interface _ASCollectionPendingState : NSObject { // Keep these enums by the bitfield struct to save memory. ASLayoutRangeMode _rangeMode; - ASCellLayoutMode _cellLayoutMode; struct { unsigned int allowsSelection:1; // default is YES unsigned int allowsMultipleSelection:1; // default is NO @@ -54,7 +51,6 @@ @interface _ASCollectionPendingState : NSObject { @property (nonatomic) BOOL allowsSelection; // default is YES @property (nonatomic) BOOL allowsMultipleSelection; // default is NO @property (nonatomic) BOOL inverted; //default is NO -@property (nonatomic) ASCellLayoutMode cellLayoutMode; @property (nonatomic) CGFloat leadingScreensForBatching; @property (nonatomic, weak) id layoutInspector; @property (nonatomic) BOOL alwaysBounceVertical; @@ -102,16 +98,6 @@ - (void)setRangeMode:(ASLayoutRangeMode)rangeMode _rangeMode = rangeMode; } -- (ASCellLayoutMode)cellLayoutMode -{ - return _cellLayoutMode; -} - -- (void)setCellLayoutMode:(ASCellLayoutMode)cellLayoutMode -{ - _cellLayoutMode = cellLayoutMode; -} - - (BOOL)allowsSelection { return _flags.allowsSelection; @@ -260,20 +246,10 @@ - (void)setCollectionViewClass:(Class)collectionViewClass - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout { - return [self initWithFrame:CGRectZero collectionViewLayout:layout layoutFacilitator:nil]; + return [self initWithFrame:CGRectZero collectionViewLayout:layout]; } - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout -{ - return [self initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil]; -} - -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(id)layoutFacilitator -{ - return [self initWithFrame:CGRectZero collectionViewLayout:[[ASCollectionLayout alloc] initWithLayoutDelegate:layoutDelegate] layoutFacilitator:layoutFacilitator]; -} - -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator { if (self = [super init]) { // Must call the setter here to make sure pendingState is created and the layout is configured. @@ -282,7 +258,7 @@ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionVi __weak __typeof__(self) weakSelf = self; [self setViewBlock:^{ __typeof__(self) strongSelf = weakSelf; - return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:strongSelf->_pendingState.collectionViewLayout layoutFacilitator:layoutFacilitator owningNode:strongSelf]; + return [[[strongSelf collectionViewClass] alloc] _initWithFrame:frame collectionViewLayout:strongSelf->_pendingState.collectionViewLayout owningNode:strongSelf]; }]; } return self; @@ -319,7 +295,6 @@ - (void)didLoad view.inverted = pendingState.inverted; view.allowsSelection = pendingState.allowsSelection; view.allowsMultipleSelection = pendingState.allowsMultipleSelection; - view.cellLayoutMode = pendingState.cellLayoutMode; view.layoutInspector = pendingState.layoutInspector; view.showsVerticalScrollIndicator = pendingState.showsVerticalScrollIndicator; view.showsHorizontalScrollIndicator = pendingState.showsHorizontalScrollIndicator; @@ -682,10 +657,8 @@ - (BOOL)isPagingEnabled - (void)setCollectionViewLayout:(UICollectionViewLayout *)layout { if ([self pendingState]) { - [self _configureCollectionViewLayout:layout]; _pendingState.collectionViewLayout = layout; } else { - [self _configureCollectionViewLayout:layout]; self.view.collectionViewLayout = layout; } } @@ -760,15 +733,6 @@ - (ASElementMap *)visibleElements return self.dataController.visibleMap; } -- (id)layoutDelegate -{ - UICollectionViewLayout *layout = self.collectionViewLayout; - if ([layout isKindOfClass:[ASCollectionLayout class]]) { - return ((ASCollectionLayout *)layout).layoutDelegate; - } - return nil; -} - - (void)setBatchFetchingDelegate:(id)batchFetchingDelegate { _batchFetchingDelegate = batchFetchingDelegate; @@ -779,24 +743,6 @@ - (void)setBatchFetchingDelegate:(id)batchFetchingDeleg return _batchFetchingDelegate; } -- (ASCellLayoutMode)cellLayoutMode -{ - if ([self pendingState]) { - return _pendingState.cellLayoutMode; - } else { - return self.view.cellLayoutMode; - } -} - -- (void)setCellLayoutMode:(ASCellLayoutMode)cellLayoutMode -{ - if ([self pendingState]) { - _pendingState.cellLayoutMode = cellLayoutMode; - } else { - self.view.cellLayoutMode = cellLayoutMode; - } -} - #pragma mark - Range Tuning - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType @@ -1034,14 +980,6 @@ - (void)waitUntilAllUpdatesAreProcessed } } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)waitUntilAllUpdatesAreCommitted -{ - [self waitUntilAllUpdatesAreProcessed]; -} -#pragma clang diagnostic pop - - (void)reloadDataWithCompletion:(void (^)())completion { ASDisplayNodeAssertMainThread(); @@ -1071,30 +1009,6 @@ - (void)relayoutItems } } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)beginUpdates -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view beginUpdates]; - } -} - -- (void)endUpdatesAnimated:(BOOL)animated -{ - [self endUpdatesAnimated:animated completion:nil]; -} - -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion -{ - ASDisplayNodeAssertMainThread(); - if (self.nodeLoaded) { - [self.view endUpdatesAnimated:animated completion:completion]; - } -} -#pragma clang diagnostic pop - - (void)invalidateFlowLayoutDelegateMetrics { ASDisplayNodeAssertMainThread(); if (self.nodeLoaded) { @@ -1223,14 +1137,4 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequi return NO; } -#pragma mark - Private methods - -- (void)_configureCollectionViewLayout:(UICollectionViewLayout *)layout -{ - if ([layout isKindOfClass:[ASCollectionLayout class]]) { - ASCollectionLayout *collectionLayout = (ASCollectionLayout *)layout; - collectionLayout.collectionNode = self; - } -} - @end diff --git a/Source/ASCollectionView.h b/Source/ASCollectionView.h index 1b250a21fb..e0c3d12975 100644 --- a/Source/ASCollectionView.h +++ b/Source/ASCollectionView.h @@ -85,211 +85,11 @@ NS_ASSUME_NONNULL_BEGIN @interface ASCollectionView (Deprecated) -/* - * A Boolean value that determines whether the nodes that the data source renders will be flipped. - */ -@property (nonatomic) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * The number of screens left to scroll before the delegate -collectionView:beginBatchFetchingWithContext: is called. - * - * Defaults to two screenfuls. - */ -@property (nonatomic) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * Optional introspection object for the collection view's layout. - * - * @discussion Since supplementary and decoration views are controlled by the collection view's layout, this object - * is used as a bridge to provide information to the internal data controller about the existence of these views and - * their associated index paths. For collection views using `UICollectionViewFlowLayout`, a default inspector - * implementation `ASCollectionViewFlowLayoutInspector` is created and set on this property by default. Custom - * collection view layout subclasses will need to provide their own implementation of an inspector object for their - * supplementary views to be compatible with `ASCollectionView`'s supplementary node support. - */ -@property (nonatomic, weak) id layoutInspector ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * Determines collection view's current scroll direction. Supports 2-axis collection views. - * - * @return a bitmask of ASScrollDirection values. - */ -@property (nonatomic, readonly) ASScrollDirection scrollDirection ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * Determines collection view's scrollable directions. - * - * @return a bitmask of ASScrollDirection values. - */ -@property (nonatomic, readonly) ASScrollDirection scrollableDirections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * Forces the .contentInset to be UIEdgeInsetsZero. - * - * @discussion By default, UIKit sets the top inset to the navigation bar height, even for horizontally - * scrolling views. This can only be disabled by setting a property on the containing UIViewController, - * automaticallyAdjustsScrollViewInsets, which may not be accessible. ASPagerNode uses this to ensure - * its flow layout behaves predictably and does not log undefined layout warnings. - */ -@property (nonatomic) BOOL zeroContentInsets ASDISPLAYNODE_DEPRECATED_MSG("Set automaticallyAdjustsScrollViewInsets=NO on your view controller instead."); - -/** - * The distance that the content view is inset from the collection view edges. Defaults to UIEdgeInsetsZero. - */ -@property (nonatomic) UIEdgeInsets contentInset ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead"); - -/** - * The point at which the origin of the content view is offset from the origin of the collection view. - */ -@property (nonatomic) CGPoint contentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * The object that acts as the asynchronous delegate of the collection view - * - * @discussion The delegate must adopt the ASCollectionDelegate protocol. The collection view maintains a weak reference to the delegate object. - * - * The delegate object is responsible for providing size constraints for nodes and indicating whether batch fetching should begin. - */ -@property (nonatomic, weak) id asyncDelegate ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode's .delegate property instead."); - -/** - * The object that acts as the asynchronous data source of the collection view - * - * @discussion The datasource must adopt the ASCollectionDataSource protocol. The collection view maintains a weak reference to the datasource object. - * - * The datasource object is responsible for providing nodes or node creation blocks to the collection view. - */ -@property (nonatomic, weak) id asyncDataSource ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode's .dataSource property instead."); - -/** - * Initializes an ASCollectionView - * - * @discussion Initializes and returns a newly allocated collection view object with the specified layout. - * - * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil. - */ -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode instead of ASCollectionView."); - -/** - * Initializes an ASCollectionView - * - * @discussion Initializes and returns a newly allocated collection view object with the specified frame and layout. - * - * @param frame The frame rectangle for the collection view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization. - * @param layout The layout object to use for organizing items. The collection view stores a strong reference to the specified object. Must not be nil. - */ -- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout ASDISPLAYNODE_DEPRECATED_MSG("Please use ASCollectionNode instead of ASCollectionView."); - -/** - * Tuning parameters for a range type in full mode. - * - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in full mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Set the tuning parameters for a range type in full mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Tuning parameters for a range type in the specified mode. - * - * @param rangeMode The range mode to get the running parameters for. - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in the given mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Set the tuning parameters for a range type in the specified mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the running parameters for. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -- (nullable __kindof UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -- (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UICollectionViewScrollPosition)scrollPosition ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -@property (nonatomic, copy, readonly) NSArray *indexPathsForVisibleItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -@property (nullable, nonatomic, copy, readonly) NSArray *indexPathsForSelectedItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead."); - -/** - * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. - * The asyncDataSource must be updated to reflect the changes before the update block completes. - * - * @param animated NO to disable animations for this batch - * @param updates The block that performs the relevant insert, delete, reload, or move operations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Perform a batch of updates asynchronously. This method must be called from the main thread. - * The asyncDataSource must be updated to reflect the changes before update block completes. - * - * @param updates The block that performs the relevant insert, delete, reload, or move operations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on - * the main thread. - * @warning This method is substantially more expensive than UICollectionView's version. - */ -- (void)reloadDataWithCompletion:(nullable void (^)(void))completion AS_UNAVAILABLE("Use ASCollectionNode method instead."); - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UICollectionView's version. - */ -- (void)reloadData AS_UNAVAILABLE("Use ASCollectionNode method instead."); - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the collection. - */ -- (void)relayoutItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - /** * See ASCollectionNode.h for full documentation of these methods. */ @property (nonatomic, readonly) BOOL isProcessingUpdates; - (void)onDidFinishProcessingUpdates:(void (^)(void))completion; -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASCollectionNode waitUntilAllUpdatesAreProcessed] instead."); /** * See ASCollectionNode.h for full documentation of these methods. @@ -297,128 +97,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized; - (void)onDidFinishSynchronizing:(void (^)(void))completion; -/** - * Registers the given kind of supplementary node for use in creating node-backed supplementary views. - * - * @param elementKind The kind of supplementary node that will be requested through the data source. - * - * @discussion Use this method to register support for the use of supplementary nodes in place of the default - * `registerClass:forSupplementaryViewOfKind:withReuseIdentifier:` and `registerNib:forSupplementaryViewOfKind:withReuseIdentifier:` - * methods. This method will register an internal backing view that will host the contents of the supplementary nodes - * returned from the data source. - */ -- (void)registerSupplementaryNodeOfKind:(NSString *)elementKind ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Inserts one or more sections. - * - * @param sections An index set that specifies the sections to insert. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)insertSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Deletes one or more sections. - * - * @param sections An index set that specifies the sections to delete. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Reloads the specified sections. - * - * @param sections An index set that specifies the sections to reload. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadSections:(NSIndexSet *)sections ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Moves a section to a new location. - * - * @param section The index of the section to move. - * - * @param newSection The index that is the destination of the move for the section. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Inserts items at the locations identified by an array of index paths. - * - * @param indexPaths An array of NSIndexPath objects, each representing an item index and section index that together identify an item. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Deletes the items specified by an array of index paths. - * - * @param indexPaths An array of NSIndexPath objects identifying the items to delete. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Reloads the specified items. - * - * @param indexPaths An array of NSIndexPath objects identifying the items to reload. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Moves the item at a specified location to a destination location. - * - * @param indexPath The index path identifying the item to move. - * - * @param newIndexPath The index path that is the destination of the move for the item. - * - * @discussion This method must be called from the main thread. The asyncDataSource must be updated to reflect the changes - * before this method is called. - */ -- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -/** - * Query the sized node at @c indexPath for its calculatedSize. - * - * @param indexPath The index path for the node of interest. - * - * This method is deprecated. Call @c calculatedSize on the node of interest instead. First deprecated in version 2.0. - */ -- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Call -calculatedSize on the node of interest instead."); - -/** - * Similar to -visibleCells. - * - * @return an array containing the nodes being displayed on screen. - */ -- (NSArray<__kindof ASCellNode *> *)visibleNodes AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); - -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDataSource.") -@protocol ASCollectionViewDataSource -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegate.") -@protocol ASCollectionViewDelegate @end /** @@ -473,20 +151,6 @@ ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegate.") */ - (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section; -/** - * Asks the delegate for the size of the header in the specified section. - */ -- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForHeaderInSection: instead."); - -/** - * Asks the delegate for the size of the footer in the specified section. - */ -- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:sizeRangeForFooterInSection: instead."); - -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASCollectionDelegateFlowLayout.") -@protocol ASCollectionViewDelegateFlowLayout @end NS_ASSUME_NONNULL_END diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index afe9716ce3..dff558d111 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -13,11 +13,9 @@ #import #import #import -#import #import #import #import -#import #import #import #import @@ -61,10 +59,6 @@ flowLayout ? flowLayout.property : default; \ }) -// ASCellLayoutMode is an NSUInteger-based NS_OPTIONS field. Be careful with BOOL handling on the -// 32-bit Objective-C runtime, and pattern after ASInterfaceStateIncludesVisible() & friends. -#define ASCellLayoutModeIncludes(layoutMode) ((self->_cellLayoutMode & layoutMode) == layoutMode) - /// What, if any, invalidation should we perform during the next -layoutSubviews. typedef NS_ENUM(NSUInteger, ASCollectionViewInvalidationStyle) { /// Perform no invalidation. @@ -94,7 +88,6 @@ @interface ASCollectionView () _layoutInspector; NSHashTable<_ASCollectionViewCell *> *_cellsForVisibilityUpdates; NSHashTable *_cellsForLayoutUpdates; - id _layoutFacilitator; CGFloat _leadingScreensForBatching; // When we update our data controller in response to an interactive move, @@ -164,21 +157,6 @@ @interface ASCollectionView () )layoutFacilitator owningNode:(ASCollectionNode *)owningNode +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout owningNode:(ASCollectionNode *)owningNode { if (!(self = [super initWithFrame:frame collectionViewLayout:layout])) return nil; @@ -297,8 +270,6 @@ - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionV _lastBoundsSizeUsedForMeasuringNodes = self.bounds.size; - _layoutFacilitator = layoutFacilitator; - _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self]; super.delegate = (id)_proxyDelegate; @@ -327,37 +298,13 @@ - (void)dealloc // Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc. _isDeallocating = YES; - if (!ASActivateExperimentalFeature(ASExperimentalCollectionTeardown)) { - [self setAsyncDelegate:nil]; - [self setAsyncDataSource:nil]; - } + [self setAsyncDelegate:nil]; + [self setAsyncDataSource:nil]; } #pragma mark - #pragma mark Overrides. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -/** - * This method is not available to be called by the public i.e. - * it should only be called by UICollectionView itself. UICollectionView - * does this e.g. during the first layout pass, or if you call -numberOfSections - * before its content is loaded. - */ -- (void)reloadData -{ - [self _superReloadData:nil completion:nil]; - - // UICollectionView calls -reloadData during first layoutSubviews and when the data source changes. - // This fires off the first load of cell nodes. - if (_asyncDataSource != nil && !self.dataController.initialReloadDataHasBeenCalled) { - [self performBatchUpdates:^{ - [_changeSet reloadData]; - } completion:nil]; - } -} -#pragma clang diagnostic pop - - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated { if ([self validateIndexPath:indexPath]) { @@ -450,12 +397,6 @@ - (void)setAsyncDataSource:(id)asyncDataSource } else { _asyncDataSource = asyncDataSource; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - - _asyncDataSourceFlags.collectionViewNodeForItem = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]; - _asyncDataSourceFlags.collectionViewNodeBlockForItem = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; - _asyncDataSourceFlags.numberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; - _asyncDataSourceFlags.collectionViewNumberOfItemsInSection = [_asyncDataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)]; - _asyncDataSourceFlags.collectionViewNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForSupplementaryElementOfKind:atIndexPath:)]; _asyncDataSourceFlags.collectionNodeNodeForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForItemAtIndexPath:)]; _asyncDataSourceFlags.collectionNodeNodeBlockForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForItemAtIndexPath:)]; @@ -479,11 +420,9 @@ - (void)setAsyncDataSource:(id)asyncDataSource _asyncDataSourceFlags.modelIdentifierMethods = [_asyncDataSource respondsToSelector:@selector(modelIdentifierForElementAtIndexPath:inNode:)] && [_asyncDataSource respondsToSelector:@selector(indexPathForElementWithModelIdentifier:inNode:)]; - ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection || _asyncDataSourceFlags.collectionViewNumberOfItemsInSection, @"Data source must implement collectionNode:numberOfItemsInSection:"); + ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection, @"Data source must implement collectionNode:numberOfItemsInSection:"); ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeNodeBlockForItem - || _asyncDataSourceFlags.collectionNodeNodeForItem - || _asyncDataSourceFlags.collectionViewNodeBlockForItem - || _asyncDataSourceFlags.collectionViewNodeForItem, @"Data source must implement collectionNode:nodeBlockForItemAtIndexPath: or collectionNode:nodeForItemAtIndexPath:"); + || _asyncDataSourceFlags.collectionNodeNodeForItem, @"Data source must implement collectionNode:nodeBlockForItemAtIndexPath: or collectionNode:nodeForItemAtIndexPath:"); } _dataController.validationErrorSource = asyncDataSource; @@ -526,23 +465,6 @@ - (void)setAsyncDelegate:(id)asyncDelegate _asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]; _asyncDelegateFlags.scrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]; _asyncDelegateFlags.scrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]; - _asyncDelegateFlags.collectionViewWillDisplayNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNode:forItemAtIndexPath:)]; - if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem == NO) { - _asyncDelegateFlags.collectionViewWillDisplayNodeForItemDeprecated = [_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]; - } - _asyncDelegateFlags.collectionViewDidEndDisplayingNodeForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didEndDisplayingNode:forItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)]; - _asyncDelegateFlags.shouldBatchFetchForCollectionView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]; - _asyncDelegateFlags.collectionViewShouldSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldSelectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidSelectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewShouldDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldDeselectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidDeselectItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didDeselectItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewShouldHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldHighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidHighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didHighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewDidUnhighlightItem = [_asyncDelegate respondsToSelector:@selector(collectionView:didUnhighlightItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewShouldShowMenuForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:shouldShowMenuForItemAtIndexPath:)]; - _asyncDelegateFlags.collectionViewCanPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:canPerformAction:forItemAtIndexPath:withSender:)]; - _asyncDelegateFlags.collectionViewPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionView:performAction:forItemAtIndexPath:withSender:)]; _asyncDelegateFlags.collectionNodeWillDisplayItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:willDisplayItemWithNode:)]; _asyncDelegateFlags.collectionNodeDidEndDisplayingItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:didEndDisplayingItemWithNode:)]; _asyncDelegateFlags.collectionNodeWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(collectionNode:willBeginBatchFetchWithContext:)]; @@ -583,7 +505,7 @@ - (void)_asyncDelegateOrDataSourceDidChange { ASDisplayNodeAssertMainThread(); - if (_asyncDataSource == nil && _asyncDelegate == nil && !ASActivateExperimentalFeature(ASExperimentalSkipClearData)) { + if (_asyncDataSource == nil && _asyncDelegate == nil) { [_dataController clearData]; } } @@ -658,19 +580,6 @@ - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)range return [_rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType]; } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)setZeroContentInsets:(BOOL)zeroContentInsets -{ - _zeroContentInsets = zeroContentInsets; -} - -- (BOOL)zeroContentInsets -{ - return _zeroContentInsets; -} -#pragma clang diagnostic pop - /// Uses latest size range from data source and -layoutThatFits:. - (CGSize)sizeForElement:(ASCollectionElement *)element { @@ -698,17 +607,6 @@ - (CGSize)sizeForElement:(ASCollectionElement *)element } } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (CGSize)calculatedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - - ASCollectionElement *e = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; - return [self sizeForElement:e]; -} -#pragma clang diagnostic pop - - (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath { return [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node; @@ -1260,14 +1158,7 @@ - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICol if (_asyncDelegateFlags.collectionNodeWillDisplayItem && self.collectionNode != nil) { [_asyncDelegate collectionNode:self.collectionNode willDisplayItemWithNode:cellNode]; - } else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self willDisplayNode:cellNode forItemAtIndexPath:indexPath]; - } else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItemDeprecated) { - [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; } -#pragma clang diagnostic pop [_rangeController setNeedsUpdate]; @@ -1306,11 +1197,6 @@ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:( if (ASCollectionNode *collectionNode = self.collectionNode) { [_asyncDelegate collectionNode:collectionNode didEndDisplayingItemWithNode:cellNode]; } - } else if (_asyncDelegateFlags.collectionViewDidEndDisplayingNodeForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didEndDisplayingNode:cellNode forItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop } [_rangeController setNeedsUpdate]; @@ -1400,11 +1286,6 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtInde if (indexPath != nil) { return [_asyncDelegate collectionNode:collectionNode shouldSelectItemAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.collectionViewShouldSelectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldSelectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop } return YES; } @@ -1417,11 +1298,6 @@ - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPa if (indexPath != nil) { [_asyncDelegate collectionNode:collectionNode didSelectItemAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.collectionViewDidSelectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didSelectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop } } @@ -1433,11 +1309,6 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIn if (indexPath != nil) { return [_asyncDelegate collectionNode:collectionNode shouldDeselectItemAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.collectionViewShouldDeselectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldDeselectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop } return YES; } @@ -1450,11 +1321,6 @@ - (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndex if (indexPath != nil) { [_asyncDelegate collectionNode:collectionNode didDeselectItemAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.collectionViewDidDeselectItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didDeselectItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop } } @@ -1468,11 +1334,6 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtI } else { return YES; } - } else if (_asyncDelegateFlags.collectionViewShouldHighlightItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldHighlightItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop } return YES; } @@ -1485,11 +1346,6 @@ - (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtInde if (indexPath != nil) { [_asyncDelegate collectionNode:collectionNode didHighlightItemAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.collectionViewDidHighlightItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didHighlightItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop } } @@ -1501,11 +1357,6 @@ - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIn if (indexPath != nil) { [_asyncDelegate collectionNode:collectionNode didUnhighlightItemAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.collectionViewDidUnhighlightItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self didUnhighlightItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop } } @@ -1517,11 +1368,6 @@ - (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemA if (indexPath != nil) { return [_asyncDelegate collectionNode:collectionNode shouldShowMenuForItemAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.collectionViewShouldShowMenuForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self shouldShowMenuForItemAtIndexPath:indexPath]; -#pragma clang diagnostic pop } return NO; } @@ -1534,11 +1380,6 @@ - (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(nonn if (indexPath != nil) { return [_asyncDelegate collectionNode:collectionNode canPerformAction:action forItemAtIndexPath:indexPath sender:sender]; } - } else if (_asyncDelegateFlags.collectionViewCanPerformActionForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate collectionView:self canPerformAction:action forItemAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop } return NO; } @@ -1551,11 +1392,6 @@ - (void)collectionView:(UICollectionView *)collectionView performAction:(nonnull if (indexPath != nil) { [_asyncDelegate collectionNode:collectionNode performAction:action forItemAtIndexPath:indexPath sender:sender]; } - } else if (_asyncDelegateFlags.collectionViewPerformActionForItem) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate collectionView:self performAction:action forItemAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop } } @@ -1566,22 +1402,6 @@ - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath if (!_asyncDataSourceFlags.collectionNodeMoveItem) { return NO; } - - // Currently we do not support interactive moves when using async layout. The reason is, we do not have a mechanism - // to propagate the "presentation data" element map (containing the speculative in-progress moves) to the layout delegate, - // and this can cause exceptions to be thrown from UICV. For example, if you drag an item out of a section, - // the element map will still contain N items in that section, even though there's only N-1 shown, and UICV will - // throw an exception that you specified an element that doesn't exist. - // - // In iOS >= 11, this is made much easier by the UIDataSourceTranslating API. In previous versions of iOS our best bet - // would be to capture the invalidation contexts that are sent during interactive moves and make our own data source translator. - if ([self.collectionViewLayout isKindOfClass:[ASCollectionLayout class]]) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - os_log_debug(ASCollectionLog(), "Collection node item interactive movement is not supported when using a layout delegate. This message will only be logged once. Node: %@", ASObjectDescriptionMakeTiny(self)); - }); - return NO; - } // If the data source implements canMoveItem, let them decide. if (_asyncDataSourceFlags.collectionNodeCanMoveItem) { @@ -1773,11 +1593,7 @@ - (void)layoutSubviews switch (invalidationStyle) { case ASCollectionViewInvalidationStyleWithAnimation: if (0 == _superBatchUpdateCount) { - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysReloadData)) { - [self _superReloadData:nil completion:nil]; - } else { - [self _superPerformBatchUpdates:nil completion:nil]; - } + [self _superPerformBatchUpdates:nil completion:nil]; } break; case ASCollectionViewInvalidationStyleWithoutAnimation: @@ -1813,15 +1629,10 @@ - (ASBatchContext *)batchContext - (BOOL)canBatchFetch { // if the delegate does not respond to this method, there is no point in starting to fetch - BOOL canFetch = _asyncDelegateFlags.collectionNodeWillBeginBatchFetch || _asyncDelegateFlags.collectionViewWillBeginBatchFetch; + BOOL canFetch = _asyncDelegateFlags.collectionNodeWillBeginBatchFetch; if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionNode) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); return [_asyncDelegate shouldBatchFetchForCollectionNode:collectionNode]; - } else if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate shouldBatchFetchForCollectionView:self]; -#pragma clang diagnostic pop } else { return canFetch; } @@ -1872,13 +1683,6 @@ - (void)_beginBatchFetching os_log_debug(ASCollectionLog(), "Beginning batch fetch for %@ with context %@", collectionNode, self->_batchContext); [self->_asyncDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:self->_batchContext]; }); - } else if (_asyncDelegateFlags.collectionViewWillBeginBatchFetch) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self->_asyncDelegate collectionView:self willBeginBatchFetchWithContext:self->_batchContext]; -#pragma clang diagnostic pop - }); } } @@ -1886,21 +1690,11 @@ - (void)_beginBatchFetching - (BOOL)dataController:(ASDataController *)dataController shouldEagerlyLayoutNode:(ASCellNode *)node { - NSAssert(!ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysLazy), - @"ASCellLayoutModeAlwaysLazy flag is no longer supported"); return !node.shouldUseUIKitCell; } - (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyProcessChangeSet:(_ASHierarchyChangeSet *)changeSet { - // If we have AlwaysSync set, block and donate main priority. - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysSync)) { - return YES; - } - // Prioritize AlwaysAsync over the remaining heuristics for the Default mode. - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysAsync)) { - return NO; - } // Reload data is expensive, don't block main while doing so. if (changeSet.includesReloadData) { return NO; @@ -1919,7 +1713,7 @@ - (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyPro - (BOOL)dataControllerShouldSerializeNodeCreation:(ASDataController *)dataController { - return ASCellLayoutModeIncludes(ASCellLayoutModeSerializeNodeCreation); + return NO; } - (id)dataController:(ASDataController *)dataController nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath @@ -1946,15 +1740,6 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); cell = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath]; } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeBlockForItem) { - block = [_asyncDataSource collectionView:self nodeBlockForItemAtIndexPath:indexPath]; - } - if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeForItem) { - cell = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; - } -#pragma clang diagnostic pop if (block == nil) { if (cell == nil || ASDynamicCast(cell, ASCellNode) == nil) { @@ -1975,16 +1760,13 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt } // Wrap the node block - BOOL disableRangeController = ASCellLayoutModeIncludes(ASCellLayoutModeDisableRangeController); __weak __typeof__(self) weakSelf = self; return ^{ __typeof__(self) strongSelf = weakSelf; ASCellNode *node = (block ? block() : cell); ASDisplayNodeAssert([node isKindOfClass:[ASCellNode class]], @"ASCollectionNode provided a non-ASCellNode! %@, %@", node, strongSelf); - if (!disableRangeController) { - [node enterHierarchyState:ASHierarchyStateRangeManaged]; - } + [node enterHierarchyState:ASHierarchyStateRangeManaged]; if (node.interactionDelegate == nil) { node.interactionDelegate = strongSelf; } @@ -2000,11 +1782,6 @@ - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(N if (_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0); return [_asyncDataSource collectionNode:collectionNode numberOfItemsInSection:section]; - } else if (_asyncDataSourceFlags.collectionViewNumberOfItemsInSection) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource collectionView:self numberOfItemsInSection:section]; -#pragma clang diagnostic pop } else { return 0; } @@ -2014,11 +1791,6 @@ - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataControlle if (_asyncDataSourceFlags.numberOfSectionsInCollectionNode) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0); return [_asyncDataSource numberOfSectionsInCollectionNode:collectionNode]; - } else if (_asyncDataSourceFlags.numberOfSectionsInCollectionView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource numberOfSectionsInCollectionView:self]; -#pragma clang diagnostic pop } else { return 1; } @@ -2056,12 +1828,6 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementa GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); cell = [_asyncDataSource collectionNode:collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; } - if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeForSupplementaryElement) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - cell = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; -#pragma clang diagnostic pop - } if (block == nil) { if (cell == nil || ASDynamicCast(cell, ASCellNode) == nil) { @@ -2210,7 +1976,7 @@ - (NSString *)nameForRangeControllerDataSource - (BOOL)rangeControllerShouldUpdateRanges:(ASRangeController *)rangeController { - return !ASCellLayoutModeIncludes(ASCellLayoutModeDisableRangeController); + return YES; } - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates @@ -2222,23 +1988,6 @@ - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes } - //TODO Do we need to notify _layoutFacilitator before reloadData? - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeDelete]) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES]; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeInsert]) { - [_layoutFacilitator collectionViewWillEditSectionsAtIndexSet:change.indexSet batched:YES]; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeInsert]) { - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:change.indexPaths batched:YES]; - } - ASPerformBlockWithoutAnimation(!changeSet.animated, ^{ as_activity_scope(as_activity_create("Commit collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); if (changeSet.includesReloadData) { @@ -2248,7 +1997,6 @@ - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet os_log_debug(ASCollectionLog(), "Did reloadData %@", self.collectionNode); [changeSet executeCompletionHandlerWithFinished:YES]; } else { - [self->_layoutFacilitator collectionViewWillPerformBatchUpdates]; __block NSUInteger numberOfUpdates = 0; const auto completion = ^(BOOL finished) { @@ -2260,52 +2008,39 @@ - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet [changeSet executeCompletionHandlerWithFinished:finished]; }; - BOOL shouldReloadData = ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysReloadData); - // TODO: Consider adding !changeSet.isEmpty as a check to also disable shouldReloadData. - if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysBatchUpdateSectionReload) && - [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload].count > 0) { - shouldReloadData = NO; - } + [self _superPerformBatchUpdates:^{ + updates(); - if (shouldReloadData) { - // When doing a reloadData, the insert / delete calls are not necessary. - // Calling updates() is enough, as it commits .pendingMap to .visibleMap. - [self _superReloadData:updates completion:completion]; - } else { - [self _superPerformBatchUpdates:^{ - updates(); - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadItemsAtIndexPaths:change.indexPaths]; - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { - [super reloadSections:change.indexSet]; - numberOfUpdates++; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { - [super deleteItemsAtIndexPaths:change.indexPaths]; - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { - [super deleteSections:change.indexSet]; - numberOfUpdates++; - } - - for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { - [super insertSections:change.indexSet]; - numberOfUpdates++; - } - - for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { - [super insertItemsAtIndexPaths:change.indexPaths]; - numberOfUpdates++; - } - } completion:completion]; - } + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeReload]) { + [super reloadItemsAtIndexPaths:change.indexPaths]; + numberOfUpdates++; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeReload]) { + [super reloadSections:change.indexSet]; + numberOfUpdates++; + } + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { + [super deleteItemsAtIndexPaths:change.indexPaths]; + numberOfUpdates++; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalDelete]) { + [super deleteSections:change.indexSet]; + numberOfUpdates++; + } + + for (_ASHierarchySectionChange *change in [changeSet sectionChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { + [super insertSections:change.indexSet]; + numberOfUpdates++; + } + + for (_ASHierarchyItemChange *change in [changeSet itemChangesOfType:_ASHierarchyChangeTypeOriginalInsert]) { + [super insertItemsAtIndexPaths:change.indexPaths]; + numberOfUpdates++; + } + } completion:completion]; os_log_debug(ASCollectionLog(), "Completed batch update %{public}@", self.collectionNode); @@ -2351,10 +2086,6 @@ - (void)nodesDidRelayout:(NSArray *)nodes if (nodes.count == 0) { return; } - - const auto uikitIndexPaths = ASArrayByFlatMapping(nodes, ASCellNode *node, [self indexPathForNode:node]); - - [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:uikitIndexPaths batched:NO]; ASCollectionViewInvalidationStyle invalidationStyle = _nextLayoutInvalidationStyle; for (ASCellNode *node in nodes) { diff --git a/Source/ASCollectionViewLayoutFacilitatorProtocol.h b/Source/ASCollectionViewLayoutFacilitatorProtocol.h deleted file mode 100644 index 3db2dc9979..0000000000 --- a/Source/ASCollectionViewLayoutFacilitatorProtocol.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// ASCollectionViewLayoutFacilitatorProtocol.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#pragma once -#import - -/** - * This facilitator protocol is intended to help Layout to better - * gel with the CollectionView - */ -@protocol ASCollectionViewLayoutFacilitatorProtocol - -/** - * Inform that the collectionView is editing the cells at a list of indexPaths - * - * @param indexPaths an array of NSIndexPath objects of cells being/will be edited. - * @param isBatched indicates whether the editing operation will be batched by the collectionView - * - * NOTE: when isBatched, used in combination with -collectionViewWillPerformBatchUpdates - */ -- (void)collectionViewWillEditCellsAtIndexPaths:(NSArray *)indexPaths batched:(BOOL)isBatched; - -/** - * Inform that the collectionView is editing the sections at a set of indexes - * - * @param indexes an NSIndexSet of section indexes being/will be edited. - * @param batched indicates whether the editing operation will be batched by the collectionView - * - * NOTE: when batched, used in combination with -collectionViewWillPerformBatchUpdates - */ -- (void)collectionViewWillEditSectionsAtIndexSet:(NSIndexSet *)indexes batched:(BOOL)batched; - -/** - * Informs the delegate that the collectionView is about to call performBatchUpdates - */ -- (void)collectionViewWillPerformBatchUpdates; - -@end diff --git a/Source/ASCollectionViewProtocols.h b/Source/ASCollectionViewProtocols.h index ec1b8d5773..ac7d04cbeb 100644 --- a/Source/ASCollectionViewProtocols.h +++ b/Source/ASCollectionViewProtocols.h @@ -10,64 +10,8 @@ #import #import -typedef NS_OPTIONS(unsigned short, ASCellLayoutMode) { - /** - * No options set. If cell layout mode is set to ASCellLayoutModeNone, the default values for - * each flag listed below is used. - */ - ASCellLayoutModeNone = 0, - /** - * If ASCellLayoutModeAlwaysSync is enabled it will cause the ASDataController to wait on the - * background queue, and this ensures that any new / changed cells are in the hierarchy by the - * very next CATransaction / frame draw. - * - * Note: Sync & Async flags force the behavior to be always one or the other, regardless of the - * items. Default: If neither ASCellLayoutModeAlwaysSync or ASCellLayoutModeAlwaysAsync is set, - * default behavior is synchronous when there are 0 or 1 ASCellNodes in the data source, and - * asynchronous when there are 2 or more. - */ - ASCellLayoutModeAlwaysSync = 1 << 1, // Default OFF - ASCellLayoutModeAlwaysAsync = 1 << 2, // Default OFF - ASCellLayoutModeForceIfNeeded = 1 << 3, // Deprecated, default OFF. - ASCellLayoutModeAlwaysPassthroughDelegate = 1 << 4, // Deprecated, default ON. - /** Instead of using performBatchUpdates: prefer using reloadData for changes for collection view */ - ASCellLayoutModeAlwaysReloadData = 1 << 5, // Default OFF - /** If flag is enabled nodes are *not* gonna be range managed. */ - ASCellLayoutModeDisableRangeController = 1 << 6, // Default OFF - ASCellLayoutModeAlwaysLazy = 1 << 7, // Deprecated, default OFF. - /** - * Defines if the node creation should happen serialized and not in parallel within the - * data controller - */ - ASCellLayoutModeSerializeNodeCreation = 1 << 8, // Default OFF - /** - * When set, the performBatchUpdates: API (including animation) is used when handling Section - * Reload operations. This is useful only when ASCellLayoutModeAlwaysReloadData is enabled and - * cell height animations are desired. - */ - ASCellLayoutModeAlwaysBatchUpdateSectionReload = 1 << 9, // Default OFF -}; - NS_ASSUME_NONNULL_BEGIN -/** - * This is a subset of UICollectionViewDataSource. - * - * @see ASCollectionDataSource - */ -@protocol ASCommonCollectionDataSource - -@optional - -- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:numberOfItemsInSection: instead."); - -- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Implement -numberOfSectionsInCollectionNode: instead."); - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement - collectionNode:nodeForSupplementaryElementOfKind:atIndexPath: instead."); - -@end - - /** * This is a subset of UICollectionViewDelegate. * @@ -79,29 +23,6 @@ NS_ASSUME_NONNULL_BEGIN - (UICollectionViewTransitionLayout *)collectionView:(UICollectionView *)collectionView transitionLayoutForOldLayout:(UICollectionViewLayout *)fromLayout newLayout:(UICollectionViewLayout *)toLayout; -- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:willDisplaySupplementaryView:forElementKind:atIndexPath: instead."); -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -collectionNode:didEndDisplayingSupplementaryView:forElementKind:atIndexPath: instead."); - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldHighlightItemAtIndexPath: instead."); -- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didHighlightItemAtIndexPath: instead."); -- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didUnhighlightItemAtIndexPath: instead."); - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldSelectItemAtIndexPath: instead."); -- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didSelectItemAtIndexPath: instead."); -- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldDeselectItemAtIndexPath: instead."); -- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:didDeselectItemAtIndexPath: instead."); - -- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:shouldShowMenuForItemAtIndexPath: instead."); -- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:canPerformAction:forItemAtIndexPath:withSender: instead."); -- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement collectionNode:performAction:forItemAtIndexPath:withSender: instead."); - -- (nullable UIContextMenuConfiguration *)collectionView:(UICollectionView *)collectionView contextMenuConfigurationForItemAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(watchos, tvos); - -- (nullable UITargetedPreview *)collectionView:(UICollectionView *)collectionView previewForHighlightingContextMenuWithConfiguration:(UIContextMenuConfiguration *)configuration API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(watchos, tvos); - -- (nullable UITargetedPreview *)collectionView:(UICollectionView *)collectionView previewForDismissingContextMenuWithConfiguration:(UIContextMenuConfiguration *)configuration API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(watchos, tvos); - -- (void)collectionView:(UICollectionView *)collectionView willPerformPreviewActionForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration animator:(id)animator API_AVAILABLE(ios(13.0)) API_UNAVAILABLE(watchos, tvos); @end NS_ASSUME_NONNULL_END diff --git a/Source/ASConfiguration.h b/Source/ASConfiguration.h deleted file mode 100644 index c529dad801..0000000000 --- a/Source/ASConfiguration.h +++ /dev/null @@ -1,58 +0,0 @@ -// -// ASConfiguration.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@protocol ASConfigurationDelegate; - -NS_ASSUME_NONNULL_BEGIN - -static NSInteger const ASConfigurationSchemaCurrentVersion = 1; - -AS_SUBCLASSING_RESTRICTED -@interface ASConfiguration : NSObject - -/** - * Initialize this configuration with the provided dictionary, - * or nil to create an empty configuration. - * - * The schema is located in `schemas/configuration.json`. - */ -- (instancetype)initWithDictionary:(nullable NSDictionary *)dictionary; - -/** - * The delegate for configuration-related events. - * Delegate methods are called from a serial queue. - */ -@property (nonatomic, nullable) id delegate; - -/** - * The experimental features to enable in Texture. - * See ASExperimentalFeatures for functions to convert to/from a string array. - */ -@property (nonatomic) ASExperimentalFeatures experimentalFeatures; - -@end - -/** - * Implement this method in a category to make your - * configuration available to Texture. It will be read - * only once and copied. - * - * NOTE: To specify your configuration at compile-time, you can - * define AS_FIXED_CONFIG_JSON as a C-string of JSON. This method - * will then be implemented to parse that string and generate - * a configuration. - */ -@interface ASConfiguration (UserProvided) -+ (ASConfiguration *)textureConfiguration NS_RETURNS_RETAINED; -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASConfiguration.mm b/Source/ASConfiguration.mm deleted file mode 100644 index a201dc21e1..0000000000 --- a/Source/ASConfiguration.mm +++ /dev/null @@ -1,63 +0,0 @@ -// -// ASConfiguration.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -/// Not too performance-sensitive here. - -@implementation ASConfiguration - -- (instancetype)initWithDictionary:(NSDictionary *)dictionary -{ - if (self = [super init]) { - if (dictionary != nil) { - const auto featureStrings = ASDynamicCast(dictionary[@"experimental_features"], NSArray); - const auto version = ASDynamicCast(dictionary[@"version"], NSNumber).integerValue; - if (version != ASConfigurationSchemaCurrentVersion) { - NSLog(@"Texture warning: configuration schema is old version (%ld vs %ld)", (long)version, (long)ASConfigurationSchemaCurrentVersion); - } - self.experimentalFeatures = ASExperimentalFeaturesFromArray(featureStrings); - } else { - self.experimentalFeatures = kNilOptions; - } - } - return self; -} - -- (id)copyWithZone:(NSZone *)zone -{ - ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; - config.experimentalFeatures = self.experimentalFeatures; - config.delegate = self.delegate; - return config; -} - -@end - -//#define AS_FIXED_CONFIG_JSON "{ \"version\" : 1, \"experimental_features\": [ \"exp_text_node\" ] }" - -#ifdef AS_FIXED_CONFIG_JSON - -@implementation ASConfiguration (UserProvided) - -+ (ASConfiguration *)textureConfiguration NS_RETURNS_RETAINED -{ - NSData *data = [@AS_FIXED_CONFIG_JSON dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error; - NSDictionary *d = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; - if (!d) { - NSAssert(NO, @"Error parsing fixed config string '%s': %@", AS_FIXED_CONFIG_JSON, error); - return nil; - } else { - return [[ASConfiguration alloc] initWithDictionary:d]; - } -} - -@end - -#endif // AS_FIXED_CONFIG_JSON diff --git a/Source/ASConfigurationDelegate.h b/Source/ASConfigurationDelegate.h deleted file mode 100644 index fde3950cb5..0000000000 --- a/Source/ASConfigurationDelegate.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// ASConfigurationDelegate.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Used to communicate configuration-related events to the client. - */ -@protocol ASConfigurationDelegate - -/** - * Texture performed its first behavior related to the feature(s). - * This can be useful for tracking the impact of the behavior (A/B testing). - */ -- (void)textureDidActivateExperimentalFeatures:(ASExperimentalFeatures)features; - -@optional - -/** - * Texture framework initialized. This method is called synchronously - * on the main thread from ASInitializeFrameworkMainThread if you defined - * AS_INITIALIZE_FRAMEWORK_MANUALLY or otherwise from the default initialization point - * (currently a static constructor, called before main()). - */ -- (void)textureDidInitialize; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASConfigurationInternal.h b/Source/ASConfigurationInternal.h deleted file mode 100644 index fa69496224..0000000000 --- a/Source/ASConfigurationInternal.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// ASConfigurationInternal.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -/// Note this has to be public because it's imported by public header ASThread.h =/ -/// It will be private again after exp_unfair_lock ends. - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Quickly check if an experiment is enabled and notify the delegate - * that it's been activated. - * - * The delegate will be notified asynchronously. - */ -#if DEBUG -#define ASActivateExperimentalFeature(opt) _ASActivateExperimentalFeature(opt) -#else -#define ASActivateExperimentalFeature(opt) ({\ - static BOOL result;\ - static dispatch_once_t onceToken;\ - dispatch_once(&onceToken, ^{ result = _ASActivateExperimentalFeature(opt); });\ - result;\ -}) -#endif - -/** - * Internal function. Use the macro without the underbar. - */ -ASDK_EXTERN BOOL _ASActivateExperimentalFeature(ASExperimentalFeatures option); - -/** - * Notify the configuration delegate that the framework initialized, if needed. - */ -ASDK_EXTERN void ASNotifyInitialized(void); - -AS_SUBCLASSING_RESTRICTED -@interface ASConfigurationManager : NSObject - -/** - * No API for now. - * Just use ASActivateExperimentalFeature to access this efficiently. - */ - -/* Exposed for testing purposes only */ -+ (void)test_resetWithConfiguration:(nullable ASConfiguration *)configuration; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASConfigurationInternal.mm b/Source/ASConfigurationInternal.mm deleted file mode 100644 index 4bcc1ffd52..0000000000 --- a/Source/ASConfigurationInternal.mm +++ /dev/null @@ -1,110 +0,0 @@ -// -// ASConfigurationInternal.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASConfigurationInternal.h" -#import -#import -#import - -static ASConfigurationManager *ASSharedConfigurationManager; -static dispatch_once_t ASSharedConfigurationManagerOnceToken; - -NS_INLINE ASConfigurationManager *ASConfigurationManagerGet() { - dispatch_once(&ASSharedConfigurationManagerOnceToken, ^{ - ASSharedConfigurationManager = [[ASConfigurationManager alloc] init]; - }); - return ASSharedConfigurationManager; -} - -@implementation ASConfigurationManager { - ASConfiguration *_config; - dispatch_queue_t _delegateQueue; - BOOL _frameworkInitialized; - _Atomic(ASExperimentalFeatures) _activatedExperiments; -} - -+ (ASConfiguration *)defaultConfiguration NS_RETURNS_RETAINED -{ - ASConfiguration *config = [[ASConfiguration alloc] init]; - // TODO(wsdwsd0829): Fix #788 before enabling it. - // config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing; - return config; -} - -- (instancetype)init -{ - if (self = [super init]) { - _delegateQueue = dispatch_queue_create("org.TextureGroup.Texture.ConfigNotifyQueue", DISPATCH_QUEUE_SERIAL); - if ([ASConfiguration respondsToSelector:@selector(textureConfiguration)]) { - _config = [[ASConfiguration textureConfiguration] copy]; - } else { - _config = [ASConfigurationManager defaultConfiguration]; - } - } - return self; -} - -- (void)frameworkDidInitialize -{ - ASDisplayNodeAssertMainThread(); - if (_frameworkInitialized) { - ASDisplayNodeFailAssert(@"Framework initialized twice."); - return; - } - _frameworkInitialized = YES; - - const auto delegate = _config.delegate; - if ([delegate respondsToSelector:@selector(textureDidInitialize)]) { - [delegate textureDidInitialize]; - } -} - -- (BOOL)activateExperimentalFeature:(ASExperimentalFeatures)requested -{ - if (_config == nil) { - return NO; - } - - NSAssert(__builtin_popcountl(requested) == 1, @"Cannot activate multiple features at once with this method."); - - // We need to call out, whether it's enabled or not. - // A/B testing requires even "control" users to be activated. - ASExperimentalFeatures enabled = requested & _config.experimentalFeatures; - ASExperimentalFeatures prevTriggered = atomic_fetch_or(&_activatedExperiments, requested); - ASExperimentalFeatures newlyTriggered = requested & ~prevTriggered; - - // Notify delegate if needed. - if (newlyTriggered != 0) { - unowned id del = _config.delegate; - dispatch_async(_delegateQueue, ^{ - [del textureDidActivateExperimentalFeatures:newlyTriggered]; - }); - } - - return (enabled != 0); -} - -// Define this even when !DEBUG, since we may run our tests in release mode. -+ (void)test_resetWithConfiguration:(ASConfiguration *)configuration -{ - ASConfigurationManager *inst = ASConfigurationManagerGet(); - inst->_config = configuration ?: [self defaultConfiguration]; - atomic_store(&inst->_activatedExperiments, 0); -} - -@end - -BOOL _ASActivateExperimentalFeature(ASExperimentalFeatures feature) -{ - return [ASConfigurationManagerGet() activateExperimentalFeature:feature]; -} - -void ASNotifyInitialized() -{ - [ASConfigurationManagerGet() frameworkDidInitialize]; -} diff --git a/Source/ASControlNode.h b/Source/ASControlNode.h index 0918fdb0b0..407dfba1ed 100644 --- a/Source/ASControlNode.h +++ b/Source/ASControlNode.h @@ -42,20 +42,6 @@ typedef NS_OPTIONS(NSUInteger, ASControlNodeEvent) ASControlNodeEventAllEvents = 0xFFFFFFFF }; -/** - * Compatibility aliases for @c ASControlState enum. - * We previously provided our own enum, but when it was imported - * into Swift, the @c normal (0) option disappeared. - * - * Apple's UIControlState enum gets special treatment here, and - * UIControlStateNormal is available in Swift. - */ -typedef UIControlState ASControlState ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlState."); -static UIControlState const ASControlStateNormal ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateNormal.") = UIControlStateNormal; -static UIControlState const ASControlStateDisabled ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateDisabled.") = UIControlStateDisabled; -static UIControlState const ASControlStateHighlighted ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateHighlighted.") = UIControlStateHighlighted; -static UIControlState const ASControlStateSelected ASDISPLAYNODE_DEPRECATED_MSG("Use UIControlStateSelected.") = UIControlStateSelected; - /** @abstract ASControlNode is the base class for control nodes (such as buttons), or nodes that track touches to invoke targets with action messages. @discussion ASControlNode cannot be used directly. It instead defines the common interface and behavior structure for all its subclasses. Subclasses should import "ASControlNode+Subclasses.h" for information on methods intended to be overriden. @@ -135,15 +121,4 @@ static UIControlState const ASControlStateSelected ASDISPLAYNODE_DEPRECATED_MSG( - (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(nullable UIEvent *)event; @end -#if TARGET_OS_TV -@interface ASControlNode (tvOS) - -/** - @abstract How the node looks when it isn't focused. Exposed here so that subclasses can override. - */ -- (void)setDefaultFocusAppearance; - -@end -#endif - NS_ASSUME_NONNULL_END diff --git a/Source/ASControlNode.mm b/Source/ASControlNode.mm index 4bc0ae6041..74d6fedec6 100644 --- a/Source/ASControlNode.mm +++ b/Source/ASControlNode.mm @@ -15,9 +15,6 @@ #import #import #import -#if TARGET_OS_TV -#import -#endif // UIControl allows dragging some distance outside of the control itself during // tracking. This value depends on the device idiom (25 or 70 points), so @@ -91,20 +88,6 @@ - (instancetype)init return self; } -#if TARGET_OS_TV -- (void)didLoad -{ - [super didLoad]; - - // On tvOS all controls, such as buttons, interact with the focus system even if they don't have a target set on them. - // Here we add our own internal tap gesture to handle this behaviour. - self.userInteractionEnabled = YES; - UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_pressDown)]; - tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)]; - [self.view addGestureRecognizer:tapGestureRec]; -} -#endif - - (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled { [super setUserInteractionEnabled:userInteractionEnabled]; diff --git a/Source/ASDKViewController.h b/Source/ASDKViewController.h deleted file mode 100644 index 035b09e1d5..0000000000 --- a/Source/ASDKViewController.h +++ /dev/null @@ -1,101 +0,0 @@ -// -// ASDKViewController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@class ASTraitCollection; - -NS_ASSUME_NONNULL_BEGIN - -typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitCollectionBlock)(UITraitCollection *traitCollection); -typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(CGSize windowSize); - -/** - * ASDKViewController allows you to have a completely node backed hierarchy. It automatically - * handles @c ASVisibilityDepth, automatic range mode and propogating @c ASDisplayTraits to contained nodes. - * - * You can opt-out of node backed hierarchy and use it like a normal UIViewController. - * More importantly, you can use it as a base class for all of your view controllers among which some use a node hierarchy and some don't. - * See examples/ASDKgram project for actual implementation. - */ -@interface ASDKViewController<__covariant DisplayNodeType : ASDisplayNode *> : UIViewController - -/** - * ASDKViewController initializer. - * - * @param node An ASDisplayNode which will provide the root view (self.view) - * @return An ASDKViewController instance whose root view will be backed by the provided ASDisplayNode. - * - * @see ASVisibilityDepth - */ -- (instancetype)initWithNode:(DisplayNodeType)node NS_DESIGNATED_INITIALIZER; - -/** -* ASDKViewController initializer. Useful for interoperability with normal UIViewControllers. -* -* @return An ASDKViewController instance with a nil node whose root view will be backed by a standard UIView as with a normal UIViewController. -* -* @see ASVisibilityDepth -*/ -- (instancetype)init NS_DESIGNATED_INITIALIZER; - -NS_ASSUME_NONNULL_END - -/** - * @return node Returns the ASDisplayNode which provides the backing view to the view controller. - */ -@property (nonatomic, readonly, null_unspecified) DisplayNodeType node; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Set this block to customize the ASDisplayTraits returned when the VC transitions to the given traitCollection. - */ -@property (nonatomic, copy) ASDisplayTraitsForTraitCollectionBlock overrideDisplayTraitsWithTraitCollection; - -/** - * Set this block to customize the ASDisplayTraits returned when the VC transitions to the given window size. - */ -@property (nonatomic, copy) ASDisplayTraitsForTraitWindowSizeBlock overrideDisplayTraitsWithWindowSize ASDISPLAYNODE_DEPRECATED_MSG("This property is actually never accessed inside the framework"); - -/** - * @abstract Passthrough property to the the .interfaceState of the node. - * @return The current ASInterfaceState of the node, indicating whether it is visible and other situational properties. - * @see ASInterfaceState - */ -@property (nonatomic, readonly) ASInterfaceState interfaceState; - - -// AsyncDisplayKit 2.0 BETA: This property is still being tested, but it allows -// blocking as a view controller becomes visible to ensure no placeholders flash onscreen. -// Refer to examples/SynchronousConcurrency, AsyncViewController.m -@property (nonatomic) BOOL neverShowPlaceholders; - -/* Custom container UIViewController subclasses can use this property to add to the overlay - that UIViewController calculates for the safeAreaInsets for contained view controllers. - */ -@property(nonatomic) UIEdgeInsets additionalSafeAreaInsets; - -@end - -@interface ASDKViewController (ASRangeControllerUpdateRangeProtocol) - -/** - * Automatically adjust range mode based on view events. If you set this to YES, the view controller or its node - * must conform to the ASRangeControllerUpdateRangeProtocol. - * - * Default value is YES *if* node or view controller conform to ASRangeControllerUpdateRangeProtocol otherwise it is NO. - */ -@property (nonatomic) BOOL automaticallyAdjustRangeModeBasedOnViewEvents; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASDKViewController.mm b/Source/ASDKViewController.mm deleted file mode 100644 index da4a19e821..0000000000 --- a/Source/ASDKViewController.mm +++ /dev/null @@ -1,358 +0,0 @@ -// -// ASDKViewController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import -#import - -@implementation ASDKViewController -{ - BOOL _ensureDisplayed; - BOOL _automaticallyAdjustRangeModeBasedOnViewEvents; - BOOL _parentManagesVisibilityDepth; - NSInteger _visibilityDepth; - BOOL _selfConformsToRangeModeProtocol; - BOOL _nodeConformsToRangeModeProtocol; - UIEdgeInsets _fallbackAdditionalSafeAreaInsets; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-designated-initializers" - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - if (!(self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { - return nil; - } - - [self _initializeInstance]; - - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - if (!(self = [super initWithCoder:aDecoder])) { - return nil; - } - - [self _initializeInstance]; - - return self; -} - -#pragma clang diagnostic pop - -- (instancetype)initWithNode:(ASDisplayNode *)node -{ - if (!(self = [super initWithNibName:nil bundle:nil])) { - return nil; - } - - _node = node; - [self _initializeInstance]; - - return self; -} - -- (instancetype)init -{ - if (!(self = [super initWithNibName:nil bundle:nil])) { - return nil; - } - - [self _initializeInstance]; - - return self; -} - -- (void)_initializeInstance -{ - if (_node == nil) { - return; - } - - _node.viewControllerRoot = YES; - - _selfConformsToRangeModeProtocol = [self conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; - _nodeConformsToRangeModeProtocol = [_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; - _automaticallyAdjustRangeModeBasedOnViewEvents = _selfConformsToRangeModeProtocol || _nodeConformsToRangeModeProtocol; - - _fallbackAdditionalSafeAreaInsets = UIEdgeInsetsZero; - - // In case the node will get loaded - if (_node.nodeLoaded) { - // Node already loaded the view - [self view]; - } else { - // If the node didn't load yet add ourselves as on did load observer to load the view in case the node gets loaded - // before the view controller - __weak __typeof__(self) weakSelf = self; - [_node onDidLoad:^(__kindof ASDisplayNode * _Nonnull node) { - if ([weakSelf isViewLoaded] == NO) { - [weakSelf view]; - } - }]; - } -} - -- (void)loadView -{ - // Apple applies a frame and autoresizing masks we need. Allocating a view is not - // nearly as expensive as adding and removing it from a hierarchy, and fortunately - // we can avoid that here. Enabling layerBacking on a single node in the hierarchy - // will have a greater performance benefit than the impact of this transient view. - [super loadView]; - - if (_node == nil) { - return; - } - - ASDisplayNodeAssertTrue(!_node.layerBacked); - - UIView *view = self.view; - CGRect frame = view.frame; - UIViewAutoresizing autoresizingMask = view.autoresizingMask; - - // We have what we need, so now create and assign the view we actually want. - view = _node.view; - _node.frame = frame; - _node.autoresizingMask = autoresizingMask; - self.view = view; - - // ensure that self.node has a valid trait collection before a subclass's implementation of viewDidLoad. - // Any subnodes added in viewDidLoad will then inherit the proper environment. - ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; - [self propagateNewTraitCollection:traitCollection]; -} - -- (void)viewWillLayoutSubviews -{ - [super viewWillLayoutSubviews]; - - // Before layout, make sure that our trait collection containerSize actually matches the size of our bounds. - // If not, we need to update the traits and propagate them. - - CGSize boundsSize = self.view.bounds.size; - if (CGSizeEqualToSize(self.node.primitiveTraitCollection.containerSize, boundsSize) == NO) { - [UIView performWithoutAnimation:^{ - ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; - traitCollection.containerSize = boundsSize; - - // this method will call measure - [self propagateNewTraitCollection:traitCollection]; - }]; - } else { - // Call layoutThatFits: to let the node prepare for a layout that will happen shortly in the layout pass of the view. - // If the node's constrained size didn't change between the last layout pass it's a no-op - [_node layoutThatFits:[self nodeConstrainedSize]]; - } -} - -- (void)viewDidLayoutSubviews -{ - if (_ensureDisplayed && self.neverShowPlaceholders) { - _ensureDisplayed = NO; - [_node recursivelyEnsureDisplaySynchronously:YES]; - } - [super viewDidLayoutSubviews]; - - if (!AS_AT_LEAST_IOS11) { - [self _updateNodeFallbackSafeArea]; - } -} - -- (void)_updateNodeFallbackSafeArea -{ - UIEdgeInsets safeArea = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0); - UIEdgeInsets additionalInsets = self.additionalSafeAreaInsets; - - safeArea = ASConcatInsets(safeArea, additionalInsets); - - _node.fallbackSafeAreaInsets = safeArea; -} - -ASVisibilityDidMoveToParentViewController; - -- (void)viewWillAppear:(BOOL)animated -{ - as_activity_create_for_scope("ASDKViewController will appear"); - os_log_debug(ASNodeLog(), "View controller %@ will appear", self); - - [super viewWillAppear:animated]; - - _ensureDisplayed = YES; - - // A layout pass is forced this early to get nodes like ASCollectionNode, ASTableNode etc. - // into the hierarchy before UIKit applies the scroll view inset adjustments, if automatic subnode management - // is enabled. Otherwise the insets would not be applied. - [_node.view layoutIfNeeded]; - - if (_parentManagesVisibilityDepth == NO) { - [self setVisibilityDepth:0]; - } -} - -ASVisibilitySetVisibilityDepth; - -ASVisibilityViewDidDisappearImplementation; - -ASVisibilityDepthImplementation; - -- (void)visibilityDepthDidChange -{ - ASLayoutRangeMode rangeMode = ASLayoutRangeModeForVisibilityDepth(self.visibilityDepth); -#if ASEnableVerboseLogging - NSString *rangeModeString; - switch (rangeMode) { - case ASLayoutRangeModeMinimum: - rangeModeString = @"Minimum"; - break; - - case ASLayoutRangeModeFull: - rangeModeString = @"Full"; - break; - - case ASLayoutRangeModeVisibleOnly: - rangeModeString = @"Visible Only"; - break; - - case ASLayoutRangeModeLowMemory: - rangeModeString = @"Low Memory"; - break; - - default: - break; - } - as_log_verbose(ASNodeLog(), "Updating visibility of %@ to: %@ (visibility depth: %zd)", self, rangeModeString, self.visibilityDepth); -#endif - [self updateCurrentRangeModeWithModeIfPossible:rangeMode]; -} - -#pragma mark - Automatic range mode - -- (BOOL)automaticallyAdjustRangeModeBasedOnViewEvents -{ - return _automaticallyAdjustRangeModeBasedOnViewEvents; -} - -- (void)setAutomaticallyAdjustRangeModeBasedOnViewEvents:(BOOL)automaticallyAdjustRangeModeBasedOnViewEvents -{ - if (automaticallyAdjustRangeModeBasedOnViewEvents != _automaticallyAdjustRangeModeBasedOnViewEvents) { - if (automaticallyAdjustRangeModeBasedOnViewEvents && _selfConformsToRangeModeProtocol == NO && _nodeConformsToRangeModeProtocol == NO) { - NSLog(@"Warning: automaticallyAdjustRangeModeBasedOnViewEvents set to YES in %@, but range mode updating is not possible because neither view controller nor node %@ conform to ASRangeControllerUpdateRangeProtocol.", self, _node); - } - _automaticallyAdjustRangeModeBasedOnViewEvents = automaticallyAdjustRangeModeBasedOnViewEvents; - } -} - -- (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode -{ - if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { - return; - } - - if (_selfConformsToRangeModeProtocol) { - id rangeUpdater = (id)self; - [rangeUpdater updateCurrentRangeWithMode:rangeMode]; - } - - if (_nodeConformsToRangeModeProtocol) { - id rangeUpdater = (id)_node; - [rangeUpdater updateCurrentRangeWithMode:rangeMode]; - } -} - -#pragma mark - Layout Helpers - -- (ASSizeRange)nodeConstrainedSize -{ - return ASSizeRangeMake(self.view.bounds.size); -} - -- (ASInterfaceState)interfaceState -{ - return _node.interfaceState; -} - -- (UIEdgeInsets)additionalSafeAreaInsets -{ - if (AS_AVAILABLE_IOS_TVOS(11.0, 11.0)) { - return super.additionalSafeAreaInsets; - } - - return _fallbackAdditionalSafeAreaInsets; -} - -- (void)setAdditionalSafeAreaInsets:(UIEdgeInsets)additionalSafeAreaInsets -{ - if (AS_AVAILABLE_IOS_TVOS(11.0, 11.0)) { - [super setAdditionalSafeAreaInsets:additionalSafeAreaInsets]; - } else { - _fallbackAdditionalSafeAreaInsets = additionalSafeAreaInsets; - [self _updateNodeFallbackSafeArea]; - } -} - -#pragma mark - ASTraitEnvironment - -- (ASPrimitiveTraitCollection)primitiveTraitCollectionForUITraitCollection:(UITraitCollection *)traitCollection -{ - if (self.overrideDisplayTraitsWithTraitCollection) { - ASTraitCollection *asyncTraitCollection = self.overrideDisplayTraitsWithTraitCollection(traitCollection); - return [asyncTraitCollection primitiveTraitCollection]; - } - - ASDisplayNodeAssertMainThread(); - ASPrimitiveTraitCollection asyncTraitCollection = ASPrimitiveTraitCollectionFromUITraitCollection(traitCollection); - asyncTraitCollection.containerSize = self.view.frame.size; - return asyncTraitCollection; -} - -- (void)propagateNewTraitCollection:(ASPrimitiveTraitCollection)traitCollection -{ - ASPrimitiveTraitCollection oldTraitCollection = self.node.primitiveTraitCollection; - - if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, oldTraitCollection) == NO) { - as_activity_scope_verbose(as_activity_create("Propagate ASDKViewController trait collection", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - os_log_debug(ASNodeLog(), "Propagating new traits for %@: %@", self, NSStringFromASPrimitiveTraitCollection(traitCollection)); - ASTraitCollectionPropagateDown(self.node, traitCollection); - - // Once we've propagated all the traits, layout this node. - // Remeasure the node with the latest constrained size – old constrained size may be incorrect. - as_activity_scope_verbose(as_activity_create("Layout ASDKViewController node with new traits", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); - [_node layoutThatFits:[self nodeConstrainedSize]]; - } -} - -- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection -{ - [super traitCollectionDidChange:previousTraitCollection]; - - ASPrimitiveTraitCollection traitCollection = [self primitiveTraitCollectionForUITraitCollection:self.traitCollection]; - traitCollection.containerSize = self.view.bounds.size; - [self propagateNewTraitCollection:traitCollection]; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation -{ - [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; - - ASPrimitiveTraitCollection traitCollection = _node.primitiveTraitCollection; - traitCollection.containerSize = self.view.bounds.size; - [self propagateNewTraitCollection:traitCollection]; -} -#pragma clang diagnostic pop - -@end diff --git a/Source/ASDisplayNode+Beta.h b/Source/ASDisplayNode+Beta.h index 882042a979..f860667a74 100644 --- a/Source/ASDisplayNode+Beta.h +++ b/Source/ASDisplayNode+Beta.h @@ -11,12 +11,6 @@ #import #import -#if YOGA - #import YOGA_HEADER_PATH - #import - #import -#endif - NS_ASSUME_NONNULL_BEGIN ASDK_EXTERN void ASPerformBlockOnMainThread(void (^block)(void)); @@ -49,7 +43,7 @@ typedef struct { * * This property defaults to NO. It will be removed in a future release. */ -+ (BOOL)suppressesInvalidCollectionUpdateExceptions AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Collection update exceptions are thrown if assertions are enabled."); ++ (BOOL)suppressesInvalidCollectionUpdateExceptions AS_WARN_UNUSED_RESULT; + (void)setSuppressesInvalidCollectionUpdateExceptions:(BOOL)suppresses; /** @@ -126,12 +120,6 @@ typedef struct { AS_CATEGORY_IMPLEMENTABLE - (void)hierarchyDisplayDidFinish NS_REQUIRES_SUPER; -/** - * Only called on the root during yoga layout. - */ -AS_CATEGORY_IMPLEMENTABLE -- (void)willCalculateLayout:(ASSizeRange)constrainedSize NS_REQUIRES_SUPER; - /** * Only ASLayoutRangeModeVisibleOnly or ASLayoutRangeModeLowMemory are recommended. Default is ASLayoutRangeModeVisibleOnly, * because this is the only way to ensure an application will not have blank / flashing views as the user navigates back after diff --git a/Source/ASDisplayNode+InterfaceState.h b/Source/ASDisplayNode+InterfaceState.h index 96ff703224..e3ef071180 100644 --- a/Source/ASDisplayNode+InterfaceState.h +++ b/Source/ASDisplayNode+InterfaceState.h @@ -117,15 +117,6 @@ typedef NS_OPTIONS(unsigned char, ASInterfaceState) - (void)hierarchyDisplayDidFinish; @optional -/** - * @abstract Called when the node is about to calculate layout. This is only called before - * Yoga-driven layouts. - * @discussion Can be used for operations that are performed after the node's view is available. - * @note This method is guaranteed to be called on main, but implementations should be careful not - * to attempt to ascend the node tree when handling this, as the root node is locked when this is - * called. - */ -- (void)nodeWillCalculateLayout:(ASSizeRange)constrainedSize; /** * @abstract Called when the node's layer is about to enter the hierarchy. diff --git a/Source/ASDisplayNode+Layout.mm b/Source/ASDisplayNode+Layout.mm index ab99d3ae1d..558b31aaab 100644 --- a/Source/ASDisplayNode+Layout.mm +++ b/Source/ASDisplayNode+Layout.mm @@ -15,7 +15,6 @@ #import #import #import -#import #import using AS::MutexLocker; @@ -58,12 +57,7 @@ - (ASLayoutElementStyle *)_locked_style { DISABLED_ASAssertLocked(__instanceLock__); if (_style == nil) { -#if YOGA - // In Yoga mode we use the delegate to inform the tree if properties changes - _style = [[ASLayoutElementStyle alloc] initWithDelegate:self]; -#else _style = [[ASLayoutElementStyle alloc] init]; -#endif } return _style; } @@ -87,7 +81,7 @@ - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize { - ASScopedLockSelfOrToRoot(); + ASLockScopeSelf(); // If one or multiple layout transitions are in flight it still can happen that layout information is requested // on other threads. As the pending and calculated layout to be updated in the layout transition in here just a @@ -169,21 +163,6 @@ - (NSString *)asciiArtName @implementation ASDisplayNode (ASLayout) -- (ASLayoutEngineType)layoutEngineType -{ -#if YOGA - MutexLocker l(__instanceLock__); - YGNodeRef yogaNode = _style.yogaNode; - BOOL hasYogaParent = (_yogaParent != nil); - BOOL hasYogaChildren = (_yogaChildren.count > 0); - if (yogaNode != NULL && (hasYogaParent || hasYogaChildren)) { - return ASLayoutEngineTypeYoga; - } -#endif - - return ASLayoutEngineTypeLayoutSpec; -} - - (ASLayout *)calculatedLayout { MutexLocker l(__instanceLock__); @@ -331,7 +310,7 @@ - (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size - (void)_u_measureNodeWithBoundsIfNecessary:(CGRect)bounds { DISABLED_ASAssertUnlocked(__instanceLock__); - ASScopedLockSelfOrToRoot(); + ASLockScopeSelf(); // Check if we are a subnode in a layout transition. // In this case no measurement is needed as it's part of the layout transition @@ -402,19 +381,9 @@ - (void)_u_measureNodeWithBoundsIfNecessary:(CGRect)bounds // Use the last known constrainedSize passed from a parent during layout (if never, use bounds). NSUInteger version = _layoutVersion; ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass]; -#if YOGA - // This flag indicates to the Texture+Yoga code that this next layout is intended to be - // displayed (vs. just for measurement). This will cause it to call setNeedsLayout on any nodes - // whose layout changes as a result of the Yoga recalculation. This is necessary because a - // change in one Yoga node can change the layout for any other node in the tree. - self.willApplyNextYogaCalculatedLayout = YES; -#endif ASLayout *layout = [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:boundsSizeForLayout]; -#if YOGA - self.willApplyNextYogaCalculatedLayout = NO; -#endif nextLayout = ASDisplayNodeLayout(layout, constrainedSize, boundsSizeForLayout, version); // Now that the constrained size of pending layout might have been reused, the layout is useless // Release it and any orphaned subnodes it retains @@ -675,7 +644,7 @@ - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize NSUInteger newLayoutVersion = self->_layoutVersion; ASLayout *newLayout; { - ASScopedLockSelfOrToRoot(); + ASLockScopeSelf(); ASLayoutElementContext *ctx = [[ASLayoutElementContext alloc] init]; ctx.transitionID = transitionID; @@ -1008,12 +977,6 @@ - (void)_assertSubnodeState - (void)_pendingLayoutTransitionDidComplete { - // This assertion introduces a breaking behavior for nodes that has ASM enabled but also manually manage some subnodes. - // Let's gate it behind YOGA flag. -#if YOGA - [self _assertSubnodeState]; -#endif - // Subclass hook // TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204 DISABLED_ASAssertUnlocked(__instanceLock__); @@ -1069,30 +1032,3 @@ - (void)_locked_setCalculatedDisplayNodeLayout:(const ASDisplayNodeLayout &)disp } @end - -#pragma mark - -#pragma mark - ASDisplayNode (YogaLayout) - -@implementation ASDisplayNode (YogaLayout) - -- (BOOL)locked_shouldLayoutFromYogaRoot { -#if YOGA - YGNodeRef yogaNode = _style.yogaNode; - BOOL hasYogaParent = (_yogaParent != nil); - BOOL hasYogaChildren = (_yogaChildren.count > 0); - BOOL usesYoga = (yogaNode != NULL && (hasYogaParent || hasYogaChildren)); - if (usesYoga) { - if ([self shouldHaveYogaMeasureFunc] == NO) { - return YES; - } else { - return NO; - } - } else { - return NO; - } -#else - return NO; -#endif -} - -@end diff --git a/Source/ASDisplayNode+Subclasses.h b/Source/ASDisplayNode+Subclasses.h index 9adb9336a0..475df8eed5 100644 --- a/Source/ASDisplayNode+Subclasses.h +++ b/Source/ASDisplayNode+Subclasses.h @@ -257,18 +257,6 @@ AS_CATEGORY_IMPLEMENTABLE */ - (nullable id)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer; -/** - * @abstract Indicates that the receiver is about to display. - * - * @discussion Deprecated in 2.5. - * - * @discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) is - * about to begin. - * - * @note Called on the main thread only - */ -- (void)displayWillStart ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use displayWillStartAsynchronously: instead."); - /** * @abstract Indicates that the receiver is about to display. * diff --git a/Source/ASDisplayNode+Yoga.h b/Source/ASDisplayNode+Yoga.h deleted file mode 100644 index 000bb90e01..0000000000 --- a/Source/ASDisplayNode+Yoga.h +++ /dev/null @@ -1,103 +0,0 @@ -// -// ASDisplayNode+Yoga.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if YOGA - -NS_ASSUME_NONNULL_BEGIN - -@class ASLayout; - -ASDK_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node)); - -@interface ASDisplayNode (Yoga) - -@property (copy) NSArray *yogaChildren; - -- (void)addYogaChild:(ASDisplayNode *)child; -- (void)removeYogaChild:(ASDisplayNode *)child; -- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index; - -- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute; - -@property BOOL yogaLayoutInProgress; -// TODO: Make this atomic (lock). -@property (nullable, nonatomic) ASLayout *yogaCalculatedLayout; -@property (nonatomic) BOOL willApplyNextYogaCalculatedLayout; - -// Will walk up the Yoga tree and returns the root node -- (ASDisplayNode *)yogaRoot; - - -@end - -@interface ASDisplayNode (YogaLocking) -/** - * @discussion Attempts(spinning) to lock all node up to root node when yoga is enabled. - * This will lock self when yoga is not enabled; - */ -- (ASLockSet)lockToRootIfNeededForLayout; - -@end - - -// These methods are intended to be used internally to Texture, and should not be called directly. -@interface ASDisplayNode (YogaInternal) - -/// For internal usage only -- (BOOL)shouldHaveYogaMeasureFunc; -/// For internal usage only -- (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize; -/// For internal usage only -- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize willApply:(BOOL)willApply; -/// For internal usage only -- (void)invalidateCalculatedYogaLayout; -/** - * @discussion return true only when yoga enabled and the node is in yoga tree and the node is - * not leaf that implemented measure function. - */ -- (BOOL)locked_shouldLayoutFromYogaRoot; - -@end - -@interface ASDisplayNode (YogaDebugging) - -- (NSString *)yogaTreeDescription; - -@end - -@interface ASLayoutElementStyle (Yoga) - -- (YGNodeRef)yogaNodeCreateIfNeeded; -- (void)destroyYogaNode; - -@property (readonly) YGNodeRef yogaNode; - -@property ASStackLayoutDirection flexDirection; -@property YGDirection direction; -@property ASStackLayoutJustifyContent justifyContent; -@property ASStackLayoutAlignItems alignItems; -@property YGPositionType positionType; -@property ASEdgeInsets position; -@property ASEdgeInsets margin; -@property ASEdgeInsets padding; -@property ASEdgeInsets border; -@property CGFloat aspectRatio; -@property YGWrap flexWrap; - -@end - -NS_ASSUME_NONNULL_END - -// When Yoga is enabled, there are several points where we want to lock the tree to the root but otherwise (without Yoga) -// will want to simply lock self. -#define ASScopedLockSelfOrToRoot() ASScopedLockSet lockSet = [self lockToRootIfNeededForLayout] -#else -#define ASScopedLockSelfOrToRoot() ASLockScopeSelf() -#endif diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm deleted file mode 100644 index 619f5a7801..0000000000 --- a/Source/ASDisplayNode+Yoga.mm +++ /dev/null @@ -1,489 +0,0 @@ -// -// ASDisplayNode+Yoga.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if YOGA /* YOGA */ - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import - -#define YOGA_LAYOUT_LOGGING 0 - -#pragma mark - ASDisplayNode+Yoga - -@interface ASDisplayNode (YogaPrivate) -@property (nonatomic, weak) ASDisplayNode *yogaParent; -- (ASSizeRange)_locked_constrainedSizeForLayoutPass; -@end - -@implementation ASDisplayNode (Yoga) - -- (ASDisplayNode *)yogaRoot -{ - ASDisplayNode *yogaRoot = self; - ASDisplayNode *yogaParent = nil; - while ((yogaParent = yogaRoot.yogaParent)) { - yogaRoot = yogaParent; - } - return yogaRoot; -} - -- (void)setYogaChildren:(NSArray *)yogaChildren -{ - ASScopedLockSelfOrToRoot(); - for (ASDisplayNode *child in [_yogaChildren copy]) { - // Make sure to un-associate the YGNodeRef tree before replacing _yogaChildren - // If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here. - [self _locked_removeYogaChild:child]; - } - _yogaChildren = nil; - for (ASDisplayNode *child in yogaChildren) { - [self _locked_addYogaChild:child]; - } -} - -- (NSArray *)yogaChildren -{ - ASLockScope(self.yogaRoot); - return [_yogaChildren copy] ?: @[]; -} - -- (void)addYogaChild:(ASDisplayNode *)child -{ - ASScopedLockSelfOrToRoot(); - [self _locked_addYogaChild:child]; -} - -- (void)_locked_addYogaChild:(ASDisplayNode *)child -{ - [self insertYogaChild:child atIndex:_yogaChildren.count]; -} - -- (void)removeYogaChild:(ASDisplayNode *)child -{ - ASScopedLockSelfOrToRoot(); - [self _locked_removeYogaChild:child]; -} - -- (void)_locked_removeYogaChild:(ASDisplayNode *)child -{ - if (child == nil) { - return; - } - - [_yogaChildren removeObjectIdenticalTo:child]; - - // YGNodeRef removal is done in setParent: - child.yogaParent = nil; - [self setNeedsLayout]; -} - -- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index -{ - ASScopedLockSelfOrToRoot(); - [self _locked_insertYogaChild:child atIndex:index]; -} - -- (void)_locked_insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index -{ - if (child == nil) { - return; - } - if (_yogaChildren == nil) { - _yogaChildren = [[NSMutableArray alloc] init]; - } - - // Clean up state in case this child had another parent. - [self _locked_removeYogaChild:child]; - - [_yogaChildren insertObject:child atIndex:index]; - - // YGNodeRef insertion is done in setParent: - child.yogaParent = self; - [self setNeedsLayout]; -} - -#pragma mark - Subclass Hooks - -- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute -{ - UIUserInterfaceLayoutDirection layoutDirection = - [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute]; - self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight - ? YGDirectionLTR : YGDirectionRTL); -} - -- (void)setYogaParent:(ASDisplayNode *)yogaParent -{ - ASLockScopeSelf(); - if (_yogaParent == yogaParent) { - return; - } - - YGNodeRef yogaNode = [self.style yogaNodeCreateIfNeeded]; - YGNodeRef oldParentRef = YGNodeGetParent(yogaNode); - if (oldParentRef != NULL) { - YGNodeRemoveChild(oldParentRef, yogaNode); - } - - _yogaParent = yogaParent; - if (yogaParent) { - YGNodeRef newParentRef = [yogaParent.style yogaNodeCreateIfNeeded]; - YGNodeInsertChild(newParentRef, yogaNode, YGNodeGetChildCount(newParentRef)); - } -} - -- (ASDisplayNode *)yogaParent -{ - return _yogaParent; -} - -- (void)setYogaCalculatedLayout:(ASLayout *)yogaCalculatedLayout -{ - _yogaCalculatedLayout = yogaCalculatedLayout; -} - -- (ASLayout *)yogaCalculatedLayout -{ - return _yogaCalculatedLayout; -} - -- (BOOL)willApplyNextYogaCalculatedLayout { - return _flags.willApplyNextYogaCalculatedLayout; -} - -- (void)setWillApplyNextYogaCalculatedLayout:(BOOL)willApplyNextYogaCalculatedLayout { - _flags.willApplyNextYogaCalculatedLayout = willApplyNextYogaCalculatedLayout; -} - -- (void)setYogaLayoutInProgress:(BOOL)yogaLayoutInProgress -{ - setFlag(YogaLayoutInProgress, yogaLayoutInProgress); - [self updateYogaMeasureFuncIfNeeded]; -} - -- (BOOL)yogaLayoutInProgress -{ - return checkFlag(YogaLayoutInProgress); -} - -- (ASLayout *)layoutForYogaNode -{ - YGNodeRef yogaNode = self.style.yogaNode; - - CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); - CGPoint position = CGPointMake(YGNodeLayoutGetLeft(yogaNode), YGNodeLayoutGetTop(yogaNode)); - - if (!ASIsCGSizeValidForSize(size)) { - size = CGSizeZero; - } - - if (!ASIsCGPositionValidForLayout(position)) { - position = CGPointZero; - } - return [ASLayout layoutWithLayoutElement:self size:size position:position sublayouts:nil]; -} - -- (void)setupYogaCalculatedLayoutAndSetNeedsLayoutForChangedNodes:(BOOL)setNeedsLayoutForChangedNodes -{ - ASScopedLockSelfOrToRoot(); - - YGNodeRef yogaNode = self.style.yogaNode; - uint32_t childCount = YGNodeGetChildCount(yogaNode); - ASDisplayNodeAssert(childCount == _yogaChildren.count, - @"Yoga tree should always be in sync with .yogaNodes array! %@", - _yogaChildren); - - ASLayout *rawSublayouts[childCount]; - int i = 0; - for (ASDisplayNode *subnode in _yogaChildren) { - rawSublayouts[i++] = [subnode layoutForYogaNode]; - } - const auto sublayouts = [NSArray arrayByTransferring:rawSublayouts count:childCount]; - - // The layout for self should have position CGPointNull, but include the calculated size. - CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); - if (!ASIsCGSizeValidForSize(size)) { - size = CGSizeZero; - } - ASLayout *layout = [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts]; - -#if ASDISPLAYNODE_ASSERTIONS_ENABLED - // Assert that the sublayout is already flattened. - for (ASLayout *sublayout in layout.sublayouts) { - if (sublayout.sublayouts.count > 0 || ASDynamicCast(sublayout.layoutElement, ASDisplayNode) == nil) { - ASDisplayNodeAssert(NO, @"Yoga sublayout is not flattened! %@, %@", self, sublayout); - } - } -#endif - - // Because this layout won't go through the rest of the logic in calculateLayoutThatFits:, flatten it now. - layout = [layout filteredNodeLayoutTree]; - - if ([self.yogaCalculatedLayout isEqual:layout] == NO) { - if (setNeedsLayoutForChangedNodes && !self.willApplyNextYogaCalculatedLayout) { - // This flag will be set when this layout is intended for immediate display. In this case, we - // want to ensure that we call setNeedsLayout on any other nodes. Note that we skip any nodes - // whose willApplyNextYogaCalculatedLayout flags are set, as those are the nodes that are - // already being laid out. - [self setNeedsLayout]; - } - self.yogaCalculatedLayout = layout; - } else { - layout = self.yogaCalculatedLayout; - ASYogaLog("-setupYogaCalculatedLayout: applying identical ASLayout: %@", layout); - } - - // Setup _pendingDisplayNodeLayout to reference the Yoga-calculated ASLayout, *unless* we are a leaf node. - // Leaf yoga nodes may have their own .sublayouts, if they use a layout spec (such as ASButtonNode). - // Their _pending variable is set after passing the Yoga checks at the start of -calculateLayoutThatFits: - - // For other Yoga nodes, there is no code that will set _pending unless we do it here. Why does it need to be set? - // When CALayer triggers the -[ASDisplayNode __layout] call, we will check if our current _pending layout - // has a size which matches our current bounds size. If it does, that layout will be used without recomputing it. - - // NOTE: Yoga does not make the constrainedSize available to intermediate nodes in the tree (e.g. not root or leaves). - // Although the size range provided here is not accurate, this will only affect caching of calls to layoutThatFits: - // These calls will behave as if they are not cached, starting a new Yoga layout pass, but this will tap into Yoga's - // own internal cache. - - if ([self shouldHaveYogaMeasureFunc] == NO) { - YGNodeRef parentNode = YGNodeGetParent(yogaNode); - CGSize parentSize = CGSizeZero; - if (parentNode) { - parentSize.width = YGNodeLayoutGetWidth(parentNode); - parentSize.height = YGNodeLayoutGetHeight(parentNode); - } - // For the root node in a Yoga tree, make sure to preserve the constrainedSize originally provided. - // This will be used for all relayouts triggered by children, since they escalate to root. - ASSizeRange range = parentNode ? ASSizeRangeUnconstrained : self.constrainedSizeForCalculatedLayout; - _pendingDisplayNodeLayout = ASDisplayNodeLayout(layout, range, parentSize, _layoutVersion); - } -} - -- (BOOL)shouldHaveYogaMeasureFunc -{ - ASLockScopeSelf(); - // Size calculation via calculateSizeThatFits: or layoutSpecThatFits: - // For these nodes, we assume they may need custom Baseline calculation too. - // This will be used for ASTextNode, as well as any other node that has no Yoga children - BOOL isLeafNode = (_yogaChildren.count == 0); - BOOL definesCustomLayout = [self implementsLayoutMethod]; - return (isLeafNode && definesCustomLayout); -} - -- (void)updateYogaMeasureFuncIfNeeded -{ - // We set the measure func only during layout. Otherwise, a cycle is created: - // The YGNodeRef Context will retain the ASDisplayNode, which retains the style, which owns the YGNodeRef. - BOOL shouldHaveMeasureFunc = ([self shouldHaveYogaMeasureFunc] && checkFlag(YogaLayoutInProgress)); - - ASLayoutElementYogaUpdateMeasureFunc(self.style.yogaNode, shouldHaveMeasureFunc ? self : nil); -} - -- (void)invalidateCalculatedYogaLayout -{ - ASLockScopeSelf(); - YGNodeRef yogaNode = self.style.yogaNode; - if (yogaNode && [self shouldHaveYogaMeasureFunc]) { - // Yoga internally asserts that MarkDirty() may only be called on nodes with a measurement function. - BOOL needsTemporaryMeasureFunc = (YGNodeGetMeasureFunc(yogaNode) == NULL); - if (needsTemporaryMeasureFunc) { - ASDisplayNodeAssert(self.yogaLayoutInProgress == NO, - @"shouldHaveYogaMeasureFunc == YES, and inside a layout pass, but no measure func pointer! %@", self); - YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc); - } - YGNodeMarkDirty(yogaNode); - if (needsTemporaryMeasureFunc) { - YGNodeSetMeasureFunc(yogaNode, NULL); - } - } - self.yogaCalculatedLayout = nil; -} - -- (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize -{ - AS::UniqueLock l(__instanceLock__); - - // There are several cases where Yoga could arrive here: - // - This node is not in a Yoga tree: it has neither a yogaParent nor yogaChildren. - // - This node is a Yoga tree root: it has no yogaParent, but has yogaChildren. - // - This node is a Yoga tree node: it has both a yogaParent and yogaChildren. - // - This node is a Yoga tree leaf: it has a yogaParent, but no yogaChidlren. - if ([self locked_shouldLayoutFromYogaRoot]) { - // If we're a yoga root, tree node, or leaf with no measure func (e.g. spacer), then - // initiate a new Yoga calculation pass from root. - as_activity_create_for_scope("Yoga layout calculation"); - if (self.yogaLayoutInProgress == NO) { - ASYogaLog("Calculating yoga layout from root %@, %@", self, - NSStringFromASSizeRange(constrainedSize)); - [self calculateLayoutFromYogaRoot:constrainedSize willApply:self.willApplyNextYogaCalculatedLayout]; - } else { - ASYogaLog("Reusing existing yoga layout %@", _yogaCalculatedLayout); - } - ASDisplayNodeAssert(_yogaCalculatedLayout, - @"Yoga node should have a non-nil layout at this stage: %@", self); - return _yogaCalculatedLayout; - } else { - // If we're a yoga leaf node with custom measurement function, proceed with normal layout so - // layoutSpecs can run (e.g. ASButtonNode). - ASYogaLog("PROCEEDING past Yoga check to calculate ASLayout for: %@", self); - } - - // Delegate to layout spec layout for nodes that do not support Yoga - return [self calculateLayoutLayoutSpec:constrainedSize]; -} - -- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize willApply:(BOOL)willApply -{ - ASScopedLockSet lockSet = [self lockToRootIfNeededForLayout]; - ASDisplayNode *yogaRoot = self.yogaRoot; - - if (self != yogaRoot) { - ASYogaLog("ESCALATING to Yoga root: %@", self); - // TODO(appleguy): Consider how to get the constrainedSize for the yogaRoot when escalating manually. - [yogaRoot calculateLayoutFromYogaRoot:ASSizeRangeUnconstrained willApply:willApply]; - return; - } - - if (ASSizeRangeEqualToSizeRange(rootConstrainedSize, ASSizeRangeUnconstrained)) { - rootConstrainedSize = [self _locked_constrainedSizeForLayoutPass]; - } - - [self willCalculateLayout:rootConstrainedSize]; - [self enumerateInterfaceStateDelegates:^(id _Nonnull delegate) { - if ([delegate respondsToSelector:@selector(nodeWillCalculateLayout:)]) { - [delegate nodeWillCalculateLayout:rootConstrainedSize]; - } - }]; - - // Prepare all children for the layout pass with the current Yoga tree configuration. - ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode *_Nonnull node) { - node.yogaLayoutInProgress = YES; - ASDisplayNode *yogaParent = node.yogaParent; - if (yogaParent) { - node.style.parentAlignStyle = yogaParent.style.alignItems; - } else { - node.style.parentAlignStyle = ASStackLayoutAlignItemsNotSet; - }; - }); - - ASYogaLog("CALCULATING at Yoga root with constraint = {%@, %@}: %@", - NSStringFromCGSize(rootConstrainedSize.min), NSStringFromCGSize(rootConstrainedSize.max), self); - - YGNodeRef rootYogaNode = self.style.yogaNode; - - // Apply the constrainedSize as a base, known frame of reference. - // If the root node also has style.*Size set, these will be overridden below. - // YGNodeCalculateLayout currently doesn't offer the ability to pass a minimum size (max is passed there). - - // TODO(appleguy): Reconcile the self.style.*Size properties with rootConstrainedSize - YGNodeStyleSetMinWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.width)); - YGNodeStyleSetMinHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.height)); - - // It is crucial to use yogaFloat... to convert CGFLOAT_MAX into YGUndefined here. - YGNodeCalculateLayout(rootYogaNode, - yogaFloatForCGFloat(rootConstrainedSize.max.width), - yogaFloatForCGFloat(rootConstrainedSize.max.height), - YGDirectionInherit); - - // Reset accessible elements, since layout may have changed. - ASPerformBlockOnMainThread(^{ - if (self.nodeLoaded && !self.isSynchronous) { - self.view.accessibilityElements = nil; - } - }); - - ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { - [node setupYogaCalculatedLayoutAndSetNeedsLayoutForChangedNodes:willApply]; - node.yogaLayoutInProgress = NO; - }); - -#if YOGA_LAYOUT_LOGGING /* YOGA_LAYOUT_LOGGING */ - // Concurrent layouts will interleave the NSLog messages unless we serialize. - // Use @synchornize rather than trampolining to the main thread so the tree state isn't changed. - @synchronized ([ASDisplayNode class]) { - NSLog(@"****************************************************************************"); - NSLog(@"******************** STARTING YOGA -> ASLAYOUT CREATION ********************"); - NSLog(@"****************************************************************************"); - ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { - NSLog(@"node = %@", node); - YGNodePrint(node.style.yogaNode, (YGPrintOptions)(YGPrintOptionsStyle | YGPrintOptionsLayout)); - NSCAssert(ASIsCGSizeValidForSize(node.yogaCalculatedLayout.size), @"Yoga layout returned an invalid size"); - NSLog(@" "); // Newline - }); - } -#endif /* YOGA_LAYOUT_LOGGING */ -} - -@end - -#pragma mark - ASDisplayNode (YogaLocking) - -@implementation ASDisplayNode (YogaLocking) - -- (ASLockSet)lockToRootIfNeededForLayout { - ASLockSet lockSet = ASLockSequence(^BOOL(ASAddLockBlock addLock) { - if (!addLock(self)) { - return NO; - } -#if YOGA - if (![self locked_shouldLayoutFromYogaRoot]) { - return YES; - } - if (self.nodeController && !addLock(self.nodeController)) { - return NO; - } - ASDisplayNode *parent = _supernode; - while (parent) { - if (!addLock(parent)) { - return NO; - } - if (parent.nodeController && !addLock(parent.nodeController)) { - return NO; - } - parent = parent->_supernode; - } -#endif - return true; - }); - return lockSet; -} - -@end - -@implementation ASDisplayNode (YogaDebugging) - -- (NSString *)yogaTreeDescription { - return [self _yogaTreeDescription:@""]; -} - -- (NSString *)_yogaTreeDescription:(NSString *)indent { - auto subtree = [NSMutableString stringWithFormat:@"%@%@\n", indent, self.description]; - for (ASDisplayNode *n in self.yogaChildren) { - [subtree appendString:[n _yogaTreeDescription:[indent stringByAppendingString:@"| "]]]; - } - return subtree; -} - -@end - -#endif /* YOGA */ diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index eff930ccf7..5ceefb2353 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -94,15 +94,7 @@ ASDK_EXTERN NSInteger const ASDefaultDrawingPriority; * */ -@interface ASDisplayNode : NSObject { -@public - /** - * The _displayNodeContext ivar is unused by Texture, but provided to enable advanced clients to make powerful extensions to base class functionality. - * For example, _displayNodeContext can be used to implement category methods on ASDisplayNode that add functionality to all node subclass types. - * Code demonstrating this technique can be found in the CatDealsCollectionView example. - */ - void *_displayNodeContext; -} +@interface ASDisplayNode : NSObject /** @name Initializing a node object */ @@ -266,26 +258,6 @@ ASDK_EXTERN NSInteger const ASDefaultDrawingPriority; */ @property (readonly) ASInterfaceState interfaceState; -/** - * @abstract Adds a delegate to receive notifications on interfaceState changes. - * - * @warning This must be called from the main thread. - * There is a hard limit on the number of delegates a node can have; see - * AS_MAX_INTERFACE_STATE_DELEGATES above. - * - * @see ASInterfaceState - */ -- (void)addInterfaceStateDelegate:(id )interfaceStateDelegate; - -/** - * @abstract Removes a delegate from receiving notifications on interfaceState changes. - * - * @warning This must be called from the main thread. - * - * @see ASInterfaceState - */ -- (void)removeInterfaceStateDelegate:(id )interfaceStateDelegate; - /** * @abstract Class property that allows to set a block that can be called on non-fatal errors. This * property can be useful for cases when Async Display Kit can recover from an abnormal behavior, but @@ -546,13 +518,6 @@ ASDK_EXTERN NSInteger const ASDefaultDrawingPriority; */ @property (readonly) BOOL supportsLayerBacking; -/** - * Whether or not the node layout should be automatically updated when it receives safeAreaInsetsDidChange. - * - * Defaults to NO. - */ -@property BOOL automaticallyRelayoutOnSafeAreaChanges; - /** * Whether or not the node layout should be automatically updated when it receives layoutMarginsDidChange. * @@ -753,19 +718,6 @@ ASDK_EXTERN NSInteger const ASDefaultDrawingPriority; @property BOOL preservesSuperviewLayoutMargins; // default is NO - set to enable pass-through or cascading behavior of margins from this view’s parent to its children - (void)layoutMarginsDidChange; -/** - * @abstract Safe area insets - * - * @discussion This property is bridged to its UIVIew counterpart. - * - * If your layout depends on this property, you should probably enable automaticallyRelayoutOnSafeAreaChanges to ensure - * that the layout gets automatically updated when the value of this property changes. Or you can override safeAreaInsetsDidChange - * and make all the necessary updates manually. - */ -@property (readonly) UIEdgeInsets safeAreaInsets; -@property BOOL insetsLayoutMarginsFromSafeArea; // Default: YES -- (void)safeAreaInsetsDidChange; - // UIResponder methods // By default these fall through to the underlying view, but can be overridden. @@ -776,16 +728,6 @@ ASDK_EXTERN NSInteger const ASDefaultDrawingPriority; - (BOOL)isFirstResponder; - (BOOL)canPerformAction:(SEL)action withSender:(id)sender; -#if TARGET_OS_TV -//Focus Engine -- (void)setNeedsFocusUpdate; -- (BOOL)canBecomeFocused; -- (void)updateFocusIfNeeded; -- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator; -- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context; -- (nullable UIView *)preferredFocusedView; -#endif - @end @interface ASDisplayNode (UIViewBridgeAccessibility) @@ -808,9 +750,6 @@ ASDK_EXTERN NSInteger const ASDefaultDrawingPriority; @property BOOL shouldGroupAccessibilityChildren; @property UIAccessibilityNavigationStyle accessibilityNavigationStyle; @property (nullable, copy) NSArray *accessibilityCustomActions API_AVAILABLE(ios(8.0),tvos(9.0)); -#if TARGET_OS_TV -@property (nullable, copy) NSArray *accessibilityHeaderElements; -#endif // Accessibility identification support @property (nullable, copy) NSString *accessibilityIdentifier; @@ -842,18 +781,8 @@ ASDK_EXTERN NSInteger const ASDefaultDrawingPriority; @end -typedef NS_ENUM(NSInteger, ASLayoutEngineType) { - ASLayoutEngineTypeLayoutSpec, - ASLayoutEngineTypeYoga -}; - @interface ASDisplayNode (ASLayout) -/** - * @abstract Returns the current layout type the node uses for layout the subtree. - */ -@property (readonly) ASLayoutEngineType layoutEngineType; - /** * @abstract Return the calculated size. * diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index abb339bdd4..2476d30df5 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -29,7 +29,6 @@ #import #import #import -#import #import #import #import @@ -243,9 +242,6 @@ + (void)initialize class_addMethod(self, @selector(hierarchyDisplayDidFinish), noArgsImp, "v@:"); class_addMethod(self, @selector(calculatedLayoutDidChange), noArgsImp, "v@:"); - auto type0 = "v@:" + std::string(@encode(ASSizeRange)); - class_addMethod(self, @selector(willCalculateLayout:), (IMP)StubImplementationWithSizeRange, type0.c_str()); - auto interfaceStateType = std::string(@encode(ASInterfaceState)); auto type1 = "v@:" + interfaceStateType + interfaceStateType; class_addMethod(self, @selector(interfaceStateDidChange:fromState:), (IMP)StubImplementationWithTwoInterfaceStates, type1.c_str()); @@ -304,14 +300,9 @@ - (void)_initializeInstance _flags.canClearContentsOfLayer = YES; _flags.canCallSetNeedsDisplayOfLayer = YES; - - _fallbackSafeAreaInsets = UIEdgeInsetsZero; - _flags.fallbackInsetsLayoutMarginsFromSafeArea = YES; + _flags.isViewControllerRoot = NO; - _flags.automaticallyRelayoutOnSafeAreaChanges = NO; - _flags.automaticallyRelayoutOnLayoutMarginsChanges = NO; - [self baseDidInit]; } @@ -808,67 +799,6 @@ - (void)nodeViewDidAddGestureRecognizer _flags.viewEverHadAGestureRecognizerAttached = YES; } -- (UIEdgeInsets)fallbackSafeAreaInsets -{ - MutexLocker l(__instanceLock__); - return _fallbackSafeAreaInsets; -} - -- (void)setFallbackSafeAreaInsets:(UIEdgeInsets)insets -{ - BOOL needsManualUpdate; - BOOL updatesLayoutMargins; - - { - MutexLocker l(__instanceLock__); - ASDisplayNodeAssertThreadAffinity(self); - - if (UIEdgeInsetsEqualToEdgeInsets(insets, _fallbackSafeAreaInsets)) { - return; - } - - _fallbackSafeAreaInsets = insets; - needsManualUpdate = !AS_AT_LEAST_IOS11 || _flags.layerBacked; - updatesLayoutMargins = needsManualUpdate && [self _locked_insetsLayoutMarginsFromSafeArea]; - } - - if (needsManualUpdate) { - [self safeAreaInsetsDidChange]; - } - - if (updatesLayoutMargins) { - [self layoutMarginsDidChange]; - } -} - -- (void)_fallbackUpdateSafeAreaOnChildren -{ - ASDisplayNodeAssertThreadAffinity(self); - - UIEdgeInsets insets = self.safeAreaInsets; - CGRect bounds = self.bounds; - - for (ASDisplayNode *child in self.subnodes) { - if (AS_AT_LEAST_IOS11 && !child.layerBacked) { - // In iOS 11 view-backed nodes already know what their safe area is. - continue; - } - - if (child.viewControllerRoot) { - // Its safe area is controlled by a view controller. Don't override it. - continue; - } - - CGRect childFrame = child.frame; - UIEdgeInsets childInsets = UIEdgeInsetsMake(MAX(insets.top - (CGRectGetMinY(childFrame) - CGRectGetMinY(bounds)), 0), - MAX(insets.left - (CGRectGetMinX(childFrame) - CGRectGetMinX(bounds)), 0), - MAX(insets.bottom - (CGRectGetMaxY(bounds) - CGRectGetMaxY(childFrame)), 0), - MAX(insets.right - (CGRectGetMaxX(bounds) - CGRectGetMaxX(childFrame)), 0)); - - child.fallbackSafeAreaInsets = childInsets; - } -} - - (BOOL)isViewControllerRoot { MutexLocker l(__instanceLock__); @@ -881,18 +811,6 @@ - (void)setViewControllerRoot:(BOOL)flag _flags.isViewControllerRoot = flag; } -- (BOOL)automaticallyRelayoutOnSafeAreaChanges -{ - MutexLocker l(__instanceLock__); - return _flags.automaticallyRelayoutOnSafeAreaChanges; -} - -- (void)setAutomaticallyRelayoutOnSafeAreaChanges:(BOOL)flag -{ - MutexLocker l(__instanceLock__); - _flags.automaticallyRelayoutOnSafeAreaChanges = flag; -} - - (BOOL)automaticallyRelayoutOnLayoutMarginsChanges { MutexLocker l(__instanceLock__); @@ -917,18 +835,6 @@ - (void)setPlaceholderEnabled:(BOOL)flag _flags.placeholderEnabled = flag; } -- (void)__setNodeController:(ASNodeController *)controller -{ - // See docs for why we don't lock. - if (controller.shouldInvertStrongReference) { - _strongNodeController = controller; - _weakNodeController = nil; - } else { - _weakNodeController = controller; - _strongNodeController = nil; - } -} - - (void)checkResponderCompatibility { #if ASDISPLAYNODE_ASSERTIONS_ENABLED @@ -985,10 +891,6 @@ - (void)invalidateCalculatedLayout _layoutVersion++; _unflattenedLayout = nil; - -#if YOGA - [self invalidateCalculatedYogaLayout]; -#endif } - (void)__layout @@ -1037,8 +939,6 @@ - (void)__layout [self _layoutDidFinish]; }); } - - [self _fallbackUpdateSafeAreaOnChildren]; } - (void)_layoutDidFinish @@ -1085,23 +985,7 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { __ASDisplayNodeCheckForLayoutMethodOverrides; - switch (self.layoutEngineType) { - case ASLayoutEngineTypeLayoutSpec: - return [self calculateLayoutLayoutSpec:constrainedSize]; -#if YOGA - case ASLayoutEngineTypeYoga: - return [self calculateLayoutYoga:constrainedSize]; -#endif - // If YOGA is not defined but for some reason the layout type engine is Yoga - // we explicitly fallthrough here - default: - break; - } - - // If this case is reached a layout type engine was defined for a node that is currently - // not supported. - ASDisplayNodeAssert(NO, @"No layout type determined"); - return nil; + return [self calculateLayoutLayoutSpec:constrainedSize]; } - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize @@ -1700,11 +1584,6 @@ - (void)setDisplaySuspended:(BOOL)flag - (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer asynchronously:(BOOL)asynchronously { // Subclass hook. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self displayWillStart]; -#pragma clang diagnostic pop - [self displayWillStartAsynchronously:asynchronously]; } @@ -1714,10 +1593,6 @@ - (void)didDisplayAsyncLayer:(_ASDisplayLayer *)layer [self displayDidFinish]; } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)displayWillStart {} -#pragma clang diagnostic pop - (void)displayWillStartAsynchronously:(BOOL)asynchronously { ASDisplayNodeAssertMainThread(); @@ -3104,10 +2979,6 @@ - (void)_didEnterVisibleState [self enumerateInterfaceStateDelegates:^(id del) { [del didEnterVisibleState]; }]; - -#if AS_ENABLE_TIPS - [ASTipsController.shared nodeDidAppear:self]; -#endif } - (void)_didExitVisibleState @@ -3192,7 +3063,7 @@ - (void)_didEnterPreloadState // (see -__layout and -_u_measureNodeWithBoundsIfNecessary:). This scenario is uncommon, // and running a measurement pass here is a fine trade-off because preloading any time after this point would be late. - if (self.automaticallyManagesSubnodes && !ASActivateExperimentalFeature(ASExperimentalDidEnterPreloadSkipASMLayout)) { + if (self.automaticallyManagesSubnodes) { [self layoutIfNeeded]; } [self enumerateInterfaceStateDelegates:^(id del) { diff --git a/Source/ASDisplayNodeExtras.h b/Source/ASDisplayNodeExtras.h index 03f2940437..42be03ed6d 100644 --- a/Source/ASDisplayNodeExtras.h +++ b/Source/ASDisplayNodeExtras.h @@ -138,16 +138,6 @@ ASDK_EXTERN void ASDisplayNodePerformBlockOnEveryNodeBFS(ASDisplayNode *node, vo */ ASDK_EXTERN void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, BOOL traverseSublayers, void(^block)(ASDisplayNode *node)); -/** - Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block. - */ -ASDK_EXTERN ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisplayNode *node)) AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use the `supernodes` property instead."); - -/** - Given a display node, traverses up the layer tree hierarchy, returning the first display node of kind class. - */ -ASDK_EXTERN __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use the `supernodeOfClass:includingSelf:` method instead."); - /** * Given a layer, find the window it lives in, if any. */ diff --git a/Source/ASExperimentalFeatures.h b/Source/ASExperimentalFeatures.h deleted file mode 100644 index 6407d4c709..0000000000 --- a/Source/ASExperimentalFeatures.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// ASExperimentalFeatures.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * A bit mask of features. Make sure to update configuration.json when you add entries. - */ -typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) { - // If AS_ENABLE_TEXTNODE=0 or TextNode2 subspec is used this setting is a no op and ASTextNode2 - // will be used in all cases - ASExperimentalTextNode = 1 << 0, // exp_text_node - ASExperimentalInterfaceStateCoalescing = 1 << 1, // exp_interface_state_coalesce - ASExperimentalLayerDefaults = 1 << 2, // exp_infer_layer_defaults - ASExperimentalCollectionTeardown = 1 << 3, // exp_collection_teardown - ASExperimentalFramesetterCache = 1 << 4, // exp_framesetter_cache - ASExperimentalSkipClearData = 1 << 5, // exp_skip_clear_data - ASExperimentalDidEnterPreloadSkipASMLayout = 1 << 6, // exp_did_enter_preload_skip_asm_layout - ASExperimentalDispatchApply = 1 << 7, // exp_dispatch_apply - ASExperimentalDrawingGlobal = 1 << 8, // exp_drawing_global - ASExperimentalOptimizeDataControllerPipeline = 1 << 9, // exp_optimize_data_controller_pipeline - ASExperimentalDisableGlobalTextkitLock = 1 << 10, // exp_disable_global_textkit_lock - ASExperimentalMainThreadOnlyDataController = 1 << 11, // exp_main_thread_only_data_controller - ASExperimentalFeatureAll = 0xFFFFFFFF -}; - -/// Convert flags -> name array. -ASDK_EXTERN NSArray *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags); - -/// Convert name array -> flags. -ASDK_EXTERN ASExperimentalFeatures ASExperimentalFeaturesFromArray(NSArray *array); - -NS_ASSUME_NONNULL_END diff --git a/Source/ASExperimentalFeatures.mm b/Source/ASExperimentalFeatures.mm deleted file mode 100644 index fe4ebc9bf3..0000000000 --- a/Source/ASExperimentalFeatures.mm +++ /dev/null @@ -1,51 +0,0 @@ -// -// ASExperimentalFeatures.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -NSArray *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags) -{ - NSArray *allNames = ASCreateOnce((@[@"exp_text_node", - @"exp_interface_state_coalesce", - @"exp_infer_layer_defaults", - @"exp_collection_teardown", - @"exp_framesetter_cache", - @"exp_skip_clear_data", - @"exp_did_enter_preload_skip_asm_layout", - @"exp_dispatch_apply", - @"exp_drawing_global", - @"exp_optimize_data_controller_pipeline", - @"exp_disable_global_textkit_lock", - @"exp_main_thread_only_data_controller"])); - if (flags == ASExperimentalFeatureAll) { - return allNames; - } - - // Go through all names, testing each bit. - NSUInteger i = 0; - return ASArrayByFlatMapping(allNames, NSString *name, ({ - (flags & (1 << i++)) ? name : nil; - })); -} - -// O(N^2) but with counts this small, it's probably faster -// than hashing the strings. -ASExperimentalFeatures ASExperimentalFeaturesFromArray(NSArray *array) -{ - NSArray *allNames = ASExperimentalFeaturesGetNames(ASExperimentalFeatureAll); - ASExperimentalFeatures result = kNilOptions; - for (NSString *str in array) { - NSUInteger i = [allNames indexOfObject:str]; - if (i != NSNotFound) { - result |= (1 << i); - } - } - return result; -} diff --git a/Source/ASImageNode+AnimatedImage.mm b/Source/ASImageNode+AnimatedImage.mm deleted file mode 100644 index 9716f8529e..0000000000 --- a/Source/ASImageNode+AnimatedImage.mm +++ /dev/null @@ -1,400 +0,0 @@ -// -// ASImageNode+AnimatedImage.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#define ASAnimatedImageDebug 0 - -@interface ASNetworkImageNode (Private) -- (void)_locked_setDefaultImage:(UIImage *)image; -@end - - -@implementation ASImageNode (AnimatedImage) - -#pragma mark - GIF support - -- (void)setAnimatedImage:(id )animatedImage -{ - ASLockScopeSelf(); - [self _locked_setAnimatedImage:animatedImage]; -} - -- (void)_locked_setAnimatedImage:(id )animatedImage -{ - DISABLED_ASAssertLocked(__instanceLock__); - - if (ASObjectIsEqual(_animatedImage, animatedImage) && (animatedImage == nil || animatedImage.playbackReady)) { - return; - } - - __block id previousAnimatedImage = _animatedImage; - - _animatedImage = animatedImage; - - if (animatedImage != nil) { - __weak ASImageNode *weakSelf = self; - if ([animatedImage respondsToSelector:@selector(setCoverImageReadyCallback:)]) { - animatedImage.coverImageReadyCallback = ^(UIImage *coverImage) { - // In this case the lock is already gone we have to call the unlocked version therefore - [weakSelf setCoverImageCompleted:coverImage]; - }; - } - - animatedImage.playbackReadyCallback = ^{ - // In this case the lock is already gone we have to call the unlocked version therefore - [weakSelf setShouldAnimate:YES]; - }; - if (animatedImage.playbackReady) { - [self _locked_setShouldAnimate:YES]; - } - } else { - // Clean up after ourselves. - - // Don't bother using a `_locked` version for setting contnst as it should be pretty safe calling it with - // reaquire the lock and would add overhead to introduce this version - self.contents = nil; - [self _locked_setCoverImage:nil]; - } - - // Push calling subclass to the next runloop cycle - // We have to schedule the block on the common modes otherwise the tracking mode will not be included and it will - // not fire e.g. while scrolling down - CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^(void) { - [self animatedImageSet:animatedImage previousAnimatedImage:previousAnimatedImage]; - }); - // Don't need to wakeup the runloop as the current is already running - // CFRunLoopWakeUp(runLoop); // Should not be necessary -} - -- (void)animatedImageSet:(id )newAnimatedImage previousAnimatedImage:(id )previousAnimatedImage -{ - // Subclass hook should not be called with the lock held - DISABLED_ASAssertUnlocked(__instanceLock__); - - // Subclasses may override -} - -- (id )animatedImage -{ - ASLockScopeSelf(); - return _animatedImage; -} - -- (void)setAnimatedImagePaused:(BOOL)animatedImagePaused -{ - ASLockScopeSelf(); - - _imageNodeFlags.animatedImagePaused = animatedImagePaused; - - [self _locked_setShouldAnimate:!animatedImagePaused]; -} - -- (BOOL)animatedImagePaused -{ - ASLockScopeSelf(); - return _imageNodeFlags.animatedImagePaused; -} - -- (void)setCoverImageCompleted:(UIImage *)coverImage -{ - if (ASInterfaceStateIncludesDisplay(self.interfaceState)) { - ASLockScopeSelf(); - [self _locked_setCoverImageCompleted:coverImage]; - } -} - -- (void)_locked_setCoverImageCompleted:(UIImage *)coverImage -{ - DISABLED_ASAssertLocked(__instanceLock__); - - _displayLinkLock.lock(); - BOOL setCoverImage = (_displayLink == nil) || _displayLink.paused; - _displayLinkLock.unlock(); - - if (setCoverImage) { - [self _locked_setCoverImage:coverImage]; - } -} - -- (void)setCoverImage:(UIImage *)coverImage -{ - ASLockScopeSelf(); - [self _locked_setCoverImage:coverImage]; -} - -- (void)_locked_setCoverImage:(UIImage *)coverImage -{ - DISABLED_ASAssertLocked(__instanceLock__); - - //If we're a network image node, we want to set the default image so - //that it will correctly be restored if it exits the range. -#if ASAnimatedImageDebug - NSLog(@"setting cover image: %p", self); -#endif - if ([self isKindOfClass:[ASNetworkImageNode class]]) { - [(ASNetworkImageNode *)self _locked_setDefaultImage:coverImage]; - } else if (_displayLink == nil || _displayLink.paused == YES) { - [self _locked_setImage:coverImage]; - } -} - -- (NSString *)animatedImageRunLoopMode -{ - AS::MutexLocker l(_displayLinkLock); - return _animatedImageRunLoopMode; -} - -- (void)setAnimatedImageRunLoopMode:(NSString *)runLoopMode -{ - AS::MutexLocker l(_displayLinkLock); - - if (runLoopMode == nil) { - runLoopMode = ASAnimatedImageDefaultRunLoopMode; - } - - if (_displayLink != nil) { - [_displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_animatedImageRunLoopMode]; - [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:runLoopMode]; - } - _animatedImageRunLoopMode = [runLoopMode copy]; -} - -- (void)setShouldAnimate:(BOOL)shouldAnimate -{ - ASLockScopeSelf(); - [self _locked_setShouldAnimate:shouldAnimate]; -} - -- (void)_locked_setShouldAnimate:(BOOL)shouldAnimate -{ - DISABLED_ASAssertLocked(__instanceLock__); - - // This test is explicitly done and not ASPerformBlockOnMainThread as this would perform the block immediately - // on main if called on main thread and we have to call methods locked or unlocked based on which thread we are on - if (ASDisplayNodeThreadIsMain()) { - if (shouldAnimate) { - [self _locked_startAnimating]; - } else { - [self _locked_stopAnimating]; - } - } else { - // We have to dispatch to the main thread and call the regular methods as the lock is already gone if the - // block is called - dispatch_async(dispatch_get_main_queue(), ^{ - if (shouldAnimate) { - [self startAnimating]; - } else { - [self stopAnimating]; - } - }); - } -} - -#pragma mark - Animating - -- (void)startAnimating -{ - ASDisplayNodeAssertMainThread(); - - ASLockScopeSelf(); - [self _locked_startAnimating]; -} - -- (void)_locked_startAnimating -{ - DISABLED_ASAssertLocked(__instanceLock__); - - // It should be safe to call self.interfaceState in this case as it will only grab the lock of the superclass - if (!ASInterfaceStateIncludesVisible(self.interfaceState)) { - return; - } - - if (_imageNodeFlags.animatedImagePaused) { - return; - } - - if (_animatedImage.playbackReady == NO) { - return; - } - -#if ASAnimatedImageDebug - NSLog(@"starting animation: %p", self); -#endif - - AS::MutexLocker l(_displayLinkLock); - if (_displayLink == nil) { - _playHead = 0; - _displayLink = [CADisplayLink displayLinkWithTarget:[ASWeakProxy weakProxyWithTarget:self] selector:@selector(displayLinkFired:)]; - _lastSuccessfulFrameIndex = NSUIntegerMax; - - [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:_animatedImageRunLoopMode]; - } else { - _displayLink.paused = NO; - } -} - -- (void)stopAnimating -{ - ASDisplayNodeAssertMainThread(); - - ASLockScopeSelf(); - [self _locked_stopAnimating]; -} - -- (void)_locked_stopAnimating -{ - ASDisplayNodeAssertMainThread(); - DISABLED_ASAssertLocked(__instanceLock__); - -#if ASAnimatedImageDebug - NSLog(@"stopping animation: %p", self); -#endif - ASDisplayNodeAssertMainThread(); - AS::MutexLocker l(_displayLinkLock); - _displayLink.paused = YES; - self.lastDisplayLinkFire = 0; - - [_animatedImage clearAnimatedImageCache]; -} - -#pragma mark - ASDisplayNode - -- (void)didEnterVisibleState -{ - ASDisplayNodeAssertMainThread(); - [super didEnterVisibleState]; - - if (self.animatedImage.coverImageReady) { - [self setCoverImage:self.animatedImage.coverImage]; - } - if (self.animatedImage.playbackReady) { - [self startAnimating]; - } -} - -- (void)didExitVisibleState -{ - ASDisplayNodeAssertMainThread(); - [super didExitVisibleState]; - - [self stopAnimating]; -} - -- (void)didExitDisplayState -{ - ASDisplayNodeAssertMainThread(); -#if ASAnimatedImageDebug - NSLog(@"exiting display state: %p", self); -#endif - - // Check to see if we're an animated image before calling super in case someone - // decides they want to clear out the animatedImage itself on exiting the display - // state - BOOL isAnimatedImage = self.animatedImage != nil; - [super didExitDisplayState]; - - // Also clear out the contents we've set to be good citizens, we'll put it back in when we become visible. - if (isAnimatedImage) { - self.contents = nil; - [self setCoverImage:nil]; - } -} - -#pragma mark - Display Link Callbacks - -- (void)displayLinkFired:(CADisplayLink *)displayLink -{ - ASDisplayNodeAssertMainThread(); - - CFTimeInterval timeBetweenLastFire; - if (self.lastDisplayLinkFire == 0) { - timeBetweenLastFire = 0; - } else if (AS_AVAILABLE_IOS_TVOS(10, 10)) { - timeBetweenLastFire = displayLink.targetTimestamp - displayLink.timestamp; - } else { - timeBetweenLastFire = CACurrentMediaTime() - self.lastDisplayLinkFire; - } - self.lastDisplayLinkFire = CACurrentMediaTime(); - - _playHead += timeBetweenLastFire; - - while (_playHead > self.animatedImage.totalDuration) { - // Set playhead to zero to keep from showing different frames on different playthroughs - _playHead = 0; - _playedLoops++; - } - - if (self.animatedImage.loopCount > 0 && _playedLoops >= self.animatedImage.loopCount) { - [self stopAnimating]; - return; - } - - NSUInteger frameIndex = [self frameIndexAtPlayHeadPosition:_playHead]; - if (frameIndex == _lastSuccessfulFrameIndex) { - return; - } - CGImageRef frameImage = [self.animatedImage imageAtIndex:frameIndex]; - - if (frameImage == nil) { - //Pause the display link until we get a file ready notification - displayLink.paused = YES; - self.lastDisplayLinkFire = 0; - } else { - self.contents = (__bridge id)frameImage; - _lastSuccessfulFrameIndex = frameIndex; - [self displayDidFinish]; - } -} - -- (NSUInteger)frameIndexAtPlayHeadPosition:(CFTimeInterval)playHead -{ - ASDisplayNodeAssertMainThread(); - NSUInteger frameIndex = 0; - for (NSUInteger durationIndex = 0; durationIndex < self.animatedImage.frameCount; durationIndex++) { - playHead -= [self.animatedImage durationAtIndex:durationIndex]; - if (playHead < 0) { - return frameIndex; - } - frameIndex++; - } - - return frameIndex; -} - -@end - -#pragma mark - ASImageNode(AnimatedImageInvalidation) - -@implementation ASImageNode(AnimatedImageInvalidation) - -- (void)invalidateAnimatedImage -{ - AS::MutexLocker l(_displayLinkLock); -#if ASAnimatedImageDebug - if (_displayLink) { - NSLog(@"invalidating display link"); - } -#endif - [_displayLink invalidate]; - _displayLink = nil; -} - -@end diff --git a/Source/ASImageNode.h b/Source/ASImageNode.h index dcdb11069e..c4137ff3d8 100644 --- a/Source/ASImageNode.h +++ b/Source/ASImageNode.h @@ -12,8 +12,6 @@ NS_ASSUME_NONNULL_BEGIN -@protocol ASAnimatedImageProtocol; - /** * Image modification block. Use to transform an image before display. * @@ -43,48 +41,6 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image, */ @property (nullable, copy) UIColor *placeholderColor; -/** - * @abstract Indicates whether efficient cropping of the receiver is enabled. - * - * @discussion Defaults to YES. See -setCropEnabled:recropImmediately:inBounds: for more - * information. - */ -@property (getter=isCropEnabled) BOOL cropEnabled; - -/** - * @abstract Indicates that efficient downsizing of backing store should *not* be enabled. - * - * @discussion Defaults to NO. @see ASCroppedImageBackingSizeAndDrawRectInBounds for more - * information. - */ -@property BOOL forceUpscaling; - -/** - * @abstract Forces image to be rendered at forcedSize. - * @discussion Defaults to CGSizeZero to indicate that the forcedSize should not be used. - * Setting forcedSize to non-CGSizeZero will force the backing of the layer contents to - * be forcedSize (automatically adjusted for contentsSize). - */ -@property CGSize forcedSize; - -/** - * @abstract Enables or disables efficient cropping. - * - * @param cropEnabled YES to efficiently crop the receiver's contents such that - * contents outside of its bounds are not included; NO otherwise. - * - * @param recropImmediately If the receiver has an image, YES to redisplay the - * receiver immediately; NO otherwise. - * - * @param cropBounds The bounds into which the receiver will be cropped. Useful - * if bounds are to change in response to cropping (but have not yet done so). - * - * @discussion Efficient cropping is only performed when the receiver's view's - * contentMode is UIViewContentModeScaleAspectFill. By default, cropping is - * enabled. The crop alignment may be controlled via cropAlignmentFactor. - */ -- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds; - /** * @abstract A value that controls how the receiver's efficient cropping is aligned. * @@ -107,106 +63,6 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image, */ @property (nullable) asimagenode_modification_block_t imageModificationBlock; -/** - * @abstract Marks the receiver as needing display and performs a block after - * display has finished. - * - * @param displayCompletionBlock The block to be performed after display has - * finished. Its `canceled` property will be YES if display was prevented or - * canceled (via displaySuspended); NO otherwise. - * - * @discussion displayCompletionBlock will be performed on the main-thread. If - * `displaySuspended` is YES, `displayCompletionBlock` is will be - * performed immediately and `YES` will be passed for `canceled`. - */ -- (void)setNeedsDisplayWithCompletion:(nullable void (^)(BOOL canceled))displayCompletionBlock; - -#if TARGET_OS_TV -/** - * A bool to track if the current appearance of the node - * is the default focus appearance. - * Exposed here so the category methods can set it. - */ -@property BOOL isDefaultFocusAppearance; -#endif - -@end - -#if TARGET_OS_TV -@interface ASImageNode (tvOS) -@end -#endif - -@interface ASImageNode (AnimatedImage) - -/** - * @abstract The animated image to playback - * - * @discussion Set this to an object which conforms to ASAnimatedImageProtocol - * to have the ASImageNode playback an animated image. - * @warning this method should not be overridden, it may not always be called as - * another method is used internally. If you need to know when the animatedImage - * is set, override @c animatedImageSet:previousAnimatedImage: - */ -@property (nullable) id animatedImage; - -/** - * @abstract Pause the playback of an animated image. - * - * @discussion Set to YES to pause playback of an animated image and NO to resume - * playback. - */ -@property BOOL animatedImagePaused; - -/** - * @abstract The runloop mode used to animate the image. - * - * @discussion Defaults to NSRunLoopCommonModes. Another commonly used mode is NSDefaultRunLoopMode. - * Setting NSDefaultRunLoopMode will cause animation to pause while scrolling (if the ASImageNode is - * in a scroll view), which may improve scroll performance in some use cases. - */ -@property (copy) NSString *animatedImageRunLoopMode; - -/** - * @abstract Method called when animated image has been set - * - * @discussion This method is for subclasses to override so they can know if an animated image - * has been set on the node. - */ -- (void)animatedImageSet:(nullable id )newAnimatedImage previousAnimatedImage:(nullable id )previousAnimatedImage ASDISPLAYNODE_REQUIRES_SUPER; - @end -@interface ASImageNode (Unavailable) - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock AS_UNAVAILABLE(); - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock AS_UNAVAILABLE(); - -@end - -/** - * @abstract Image modification block that rounds (and optionally adds a border to) an image. - * - * @param borderWidth The width of the round border to draw, or zero if no border is desired. - * @param borderColor What colour border to draw. - * - * @see - * - * @return An ASImageNode image modification block. - */ -ASDK_EXTERN asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor * _Nullable borderColor); - -/** - * @abstract Image modification block that applies a tint color à la UIImage configured with - * renderingMode set to UIImageRenderingModeAlwaysTemplate. - * - * @param color The color to tint the image. - * - * @see - * - * @return An ASImageNode image modification block. - */ -ASDK_EXTERN asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color); - NS_ASSUME_NONNULL_END diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index 95ac201434..ec5e9b1797 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -18,7 +18,7 @@ #import #import #import -#import +#import #import #import #import @@ -41,9 +41,6 @@ @interface ASImageNodeDrawParameters : NSObject { UIColor *_backgroundColor; UIColor *_tintColor; UIViewContentMode _contentMode; - BOOL _cropEnabled; - BOOL _forceUpscaling; - CGSize _forcedSize; CGRect _cropRect; CGRect _cropDisplayBounds; asimagenode_modification_block_t _imageModificationBlock; @@ -147,13 +144,10 @@ @implementation ASImageNode ASWeakMapEntry *_weakCacheEntry; // Holds a reference that keeps our contents in cache. UIColor *_placeholderColor; - void (^_displayCompletionBlock)(BOOL canceled); - // Drawing ASTextNode *_debugLabelNode; // Cropping. - CGSize _forcedSize; //Defaults to CGSizeZero, indicating no forced size. CGRect _cropRect; // Defaults to CGRectMake(0.5, 0.5, 0, 0) CGRect _cropDisplayBounds; // Defaults to CGRectNull } @@ -179,21 +173,17 @@ - (instancetype)init // initial value. With setting a explicit backgroundColor we can prevent that change. self.backgroundColor = [UIColor clearColor]; - _imageNodeFlags.cropEnabled = YES; - _imageNodeFlags.forceUpscaling = NO; - _imageNodeFlags.regenerateFromImageAsset = NO; + _regenerateFromImageAsset = NO; _cropRect = CGRectMake(0.5, 0.5, 0, 0); _cropDisplayBounds = CGRectNull; _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); - _animatedImageRunLoopMode = ASAnimatedImageDefaultRunLoopMode; return self; } - (void)dealloc { - // Invalidate all components around animated images - [self invalidateAnimatedImage]; + } #pragma mark - Placeholder @@ -295,8 +285,8 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer ASLockScopeSelf(); UIImage *drawImage = _image; if (AS_AVAILABLE_IOS_TVOS(13, 10)) { - if (_imageNodeFlags.regenerateFromImageAsset && drawImage != nil) { - _imageNodeFlags.regenerateFromImageAsset = NO; + if (_regenerateFromImageAsset && drawImage != nil) { + _regenerateFromImageAsset = NO; UITraitCollection *tc = [UITraitCollection traitCollectionWithUserInterfaceStyle:_primitiveTraitCollection.userInterfaceStyle]; UIImage *generatedImage = [drawImage.imageAsset imageWithTraitCollection:tc]; if ( generatedImage != nil ) { @@ -307,9 +297,6 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer drawParameters->_image = drawImage; drawParameters->_contentsScale = _contentsScaleForDisplay; - drawParameters->_cropEnabled = _imageNodeFlags.cropEnabled; - drawParameters->_forceUpscaling = _imageNodeFlags.forceUpscaling; - drawParameters->_forcedSize = _forcedSize; drawParameters->_cropRect = _cropRect; drawParameters->_cropDisplayBounds = _cropDisplayBounds; drawParameters->_imageModificationBlock = _imageModificationBlock; @@ -345,9 +332,6 @@ + (UIImage *)displayWithParameters:(id)parameter isCancelled:(NS_NOESC } CGRect drawParameterBounds = drawParameter->_bounds; - BOOL forceUpscaling = drawParameter->_forceUpscaling; - CGSize forcedSize = drawParameter->_forcedSize; - BOOL cropEnabled = drawParameter->_cropEnabled; BOOL isOpaque = drawParameter->_opaque; UIColor *backgroundColor = drawParameter->_backgroundColor; UIColor *tintColor = drawParameter->_tintColor; @@ -359,7 +343,7 @@ + (UIImage *)displayWithParameters:(id)parameter isCancelled:(NS_NOESC ASDisplayNodeContextModifier willDisplayNodeContentWithRenderingContext = drawParameter->_willDisplayNodeContentWithRenderingContext; ASDisplayNodeContextModifier didDisplayNodeContentWithRenderingContext = drawParameter->_didDisplayNodeContentWithRenderingContext; - BOOL hasValidCropBounds = cropEnabled && !CGRectIsEmpty(cropDisplayBounds); + BOOL hasValidCropBounds = !CGRectIsEmpty(cropDisplayBounds); CGRect bounds = (hasValidCropBounds ? cropDisplayBounds : drawParameterBounds); @@ -378,10 +362,6 @@ + (UIImage *)displayWithParameters:(id)parameter isCancelled:(NS_NOESC CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); CGSize boundsSizeInPixels = CGSizeMake(std::floor(bounds.size.width * contentsScale), std::floor(bounds.size.height * contentsScale)); - BOOL contentModeSupported = contentMode == UIViewContentModeScaleAspectFill || - contentMode == UIViewContentModeScaleAspectFit || - contentMode == UIViewContentModeCenter; - CGSize backingSize = CGSizeZero; CGRect imageDrawRect = CGRectZero; @@ -390,26 +370,12 @@ + (UIImage *)displayWithParameters:(id)parameter isCancelled:(NS_NOESC return nil; } - - // If we're not supposed to do any cropping, just decode image at original size - if (!cropEnabled || !contentModeSupported) { - backingSize = imageSizeInPixels; - imageDrawRect = (CGRect){.size = backingSize}; - } else { - if (CGSizeEqualToSize(CGSizeZero, forcedSize) == NO) { - //scale forced size - forcedSize.width *= contentsScale; - forcedSize.height *= contentsScale; - } - ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels, - boundsSizeInPixels, - contentMode, - cropRect, - forceUpscaling, - forcedSize, - &backingSize, - &imageDrawRect); - } + ASCroppedImageBackingSizeAndDrawRectInBounds(imageSizeInPixels, + boundsSizeInPixels, + contentMode, + cropRect, + &backingSize, + &imageDrawRect); if (backingSize.width <= 0.0f || backingSize.height <= 0.0f || imageDrawRect.size.width <= 0.0f || imageDrawRect.size.height <= 0.0f) { @@ -560,13 +526,6 @@ - (void)displayDidFinish __instanceLock__.lock(); UIImage *image = _image; - void (^displayCompletionBlock)(BOOL canceled) = _displayCompletionBlock; - BOOL shouldPerformDisplayCompletionBlock = (image && displayCompletionBlock); - - // Clear the ivar now. The block is retained and will be executed shortly. - if (shouldPerformDisplayCompletionBlock) { - _displayCompletionBlock = nil; - } BOOL hasDebugLabel = (_debugLabelNode != nil); __instanceLock__.unlock(); @@ -587,30 +546,6 @@ - (void)displayDidFinish _debugLabelNode.attributedText = nil; } } - - // If we've got a block to perform after displaying, do it. - if (shouldPerformDisplayCompletionBlock) { - displayCompletionBlock(NO); - } -} - -- (void)setNeedsDisplayWithCompletion:(void (^ _Nullable)(BOOL canceled))displayCompletionBlock -{ - if (self.displaySuspended) { - if (displayCompletionBlock) - displayCompletionBlock(YES); - return; - } - - // Stash the block and call-site queue. We'll invoke it in -displayDidFinish. - { - AS::MutexLocker l(__instanceLock__); - if (_displayCompletionBlock != displayCompletionBlock) { - _displayCompletionBlock = displayCompletionBlock; - } - } - - [self setNeedsDisplay]; } - (void)_setNeedsDisplayOnTemplatedImages @@ -652,42 +587,6 @@ - (void)clearContents #pragma mark - Cropping -- (BOOL)isCropEnabled -{ - AS::MutexLocker l(__instanceLock__); - return _imageNodeFlags.cropEnabled; -} - -- (void)setCropEnabled:(BOOL)cropEnabled -{ - [self setCropEnabled:cropEnabled recropImmediately:NO inBounds:self.bounds]; -} - -- (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediately inBounds:(CGRect)cropBounds -{ - __instanceLock__.lock(); - if (_imageNodeFlags.cropEnabled == cropEnabled) { - __instanceLock__.unlock(); - return; - } - - _imageNodeFlags.cropEnabled = cropEnabled; - _cropDisplayBounds = cropBounds; - - UIImage *image = _image; - __instanceLock__.unlock(); - - // If we have an image to display, display it, respecting our recrop flag. - if (image != nil) { - ASPerformBlockOnMainThread(^{ - if (recropImmediately) - [self displayImmediately]; - else - [self setNeedsDisplay]; - }); - } -} - - (CGRect)cropRect { AS::MutexLocker l(__instanceLock__); @@ -718,30 +617,6 @@ - (void)setCropRect:(CGRect)cropRect }); } -- (BOOL)forceUpscaling -{ - AS::MutexLocker l(__instanceLock__); - return _imageNodeFlags.forceUpscaling; -} - -- (void)setForceUpscaling:(BOOL)forceUpscaling -{ - AS::MutexLocker l(__instanceLock__); - _imageNodeFlags.forceUpscaling = forceUpscaling; -} - -- (CGSize)forcedSize -{ - AS::MutexLocker l(__instanceLock__); - return _forcedSize; -} - -- (void)setForcedSize:(CGSize)forcedSize -{ - AS::MutexLocker l(__instanceLock__); - _forcedSize = forcedSize; -} - - (asimagenode_modification_block_t)imageModificationBlock { AS::MutexLocker l(__instanceLock__); @@ -785,53 +660,10 @@ - (void)asyncTraitCollectionDidChangeWithPreviousTraitCollection:(ASPrimitiveTra // update image if userInterfaceStyle was changed (dark mode) if (_image != nil && _primitiveTraitCollection.userInterfaceStyle != previousTraitCollection.userInterfaceStyle) { - _imageNodeFlags.regenerateFromImageAsset = YES; + _regenerateFromImageAsset = YES; } } } @end - -#pragma mark - Extras - -asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor) -{ - return ^(UIImage *originalImage, ASPrimitiveTraitCollection traitCollection) { - return ASGraphicsCreateImage(traitCollection, originalImage.size, NO, originalImage.scale, originalImage, nil, ^{ - UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}]; - - // Make the image round - [roundOutline addClip]; - - // Draw the original image - [originalImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; - - // Draw a border on top. - if (borderWidth > 0.0) { - [borderColor setStroke]; - [roundOutline setLineWidth:borderWidth]; - [roundOutline stroke]; - } - }); - }; -} - -asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color) -{ - return ^(UIImage *originalImage, ASPrimitiveTraitCollection traitCollection) { - UIImage *modifiedImage = ASGraphicsCreateImage(traitCollection, originalImage.size, NO, originalImage.scale, originalImage, nil, ^{ - // Set color and render template - [color setFill]; - UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - [templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; - }); - - // if the original image was stretchy, keep it stretchy - if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) { - modifiedImage = [modifiedImage resizableImageWithCapInsets:originalImage.capInsets resizingMode:originalImage.resizingMode]; - } - - return modifiedImage; - }; -} diff --git a/Source/ASMapNode.h b/Source/ASMapNode.h deleted file mode 100644 index 9ddc2b6ffc..0000000000 --- a/Source/ASMapNode.h +++ /dev/null @@ -1,92 +0,0 @@ -// -// ASMapNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if TARGET_OS_IOS && AS_USE_MAPKIT -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Map Annotation options. - * The default behavior is to ignore the annotations' positions, use the region or options specified instead. - * Swift: to select the default behavior, use []. - */ -typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions) -{ - /** The annotations' positions are ignored, use the region or options specified instead. */ - ASMapNodeShowAnnotationsOptionsIgnored = 0, - /** The annotations' positions are used to calculate the region to show in the map, equivalent to showAnnotations:animated. */ - ASMapNodeShowAnnotationsOptionsZoomed = 1 << 0, - /** This will only have an effect if combined with the Zoomed state with liveMap turned on.*/ - ASMapNodeShowAnnotationsOptionsAnimated = 1 << 1 -}; - -@interface ASMapNode : ASImageNode - -/** - The current options of ASMapNode. This can be set at any time and ASMapNode will animate the change.

This property may be set from a background thread before the node is loaded, and will automatically be applied to define the behavior of the static snapshot (if .liveMap = NO) or the internal MKMapView (otherwise).

Changes to the region and camera options will only be animated when when the liveMap mode is enabled, otherwise these options will be applied statically to the new snapshot.

The options object is used to specify properties even when the liveMap mode is enabled, allowing seamless transitions between the snapshot and liveMap (as well as back to the snapshot). - */ -@property (nonatomic) MKMapSnapshotOptions *options; - -/** The region is simply the sub-field on the options object. If the objects object is reset, - this will in effect be overwritten and become the value of the .region property on that object. - Defaults to MKCoordinateRegionForMapRect(MKMapRectWorld). - */ -@property (nonatomic) MKCoordinateRegion region; - -/** - This is the MKMapView that is the live map part of ASMapNode. This will be nil if .liveMap = NO. Note, MKMapView is *not* thread-safe. - */ -@property (nullable, readonly) MKMapView *mapView; - -/** - Set this to YES to turn the snapshot into an interactive MKMapView and vice versa. Defaults to NO. This property may be set on a background thread before the node is loaded, and will automatically be actioned, once the node is loaded. - */ -@property (getter=isLiveMap) BOOL liveMap; - -/** - @abstract Whether ASMapNode should automatically request a new map snapshot to correspond to the new node size. - @default Default value is YES. - @discussion If mapSize is set then this will be set to NO, since the size will be the same in all orientations. - */ -@property BOOL needsMapReloadOnBoundsChange; - -/** - Set the delegate of the MKMapView. This can be set even before mapView is created and will be set on the map in the case that the liveMap mode is engaged. - - If the live map view has been created, this may only be set on the main thread. - */ -@property (nonatomic, weak) id mapDelegate; - -/** - * @abstract The annotations to display on the map. - */ -@property (copy) NSArray> *annotations; - -/** - * @abstract This property specifies how to show the annotations. - * @default Default value is ASMapNodeShowAnnotationsIgnored - */ -@property ASMapNodeShowAnnotationsOptions showAnnotationsOptions; - -/** - * @abstract The block which should return annotation image for static map based on provided annotation. - * @discussion This block is executed on an arbitrary serial queue. If this block is nil, standard pin is used. - */ -@property (nullable) UIImage * _Nullable (^imageForStaticMapAnnotationBlock)(id annotation, CGPoint *centerOffset); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/Source/ASMapNode.mm b/Source/ASMapNode.mm deleted file mode 100644 index 599851e91e..0000000000 --- a/Source/ASMapNode.mm +++ /dev/null @@ -1,440 +0,0 @@ -// -// ASMapNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if TARGET_OS_IOS && AS_USE_MAPKIT - -#import - -#import -#import -#import -#import -#import -#import - -@interface ASMapNode() -{ - MKMapSnapshotter *_snapshotter; - BOOL _snapshotAfterLayout; - NSArray *_annotations; -} -@end - -@implementation ASMapNode - -@synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange; -@synthesize mapDelegate = _mapDelegate; -@synthesize options = _options; -@synthesize liveMap = _liveMap; -@synthesize showAnnotationsOptions = _showAnnotationsOptions; - -#pragma mark - Lifecycle -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); - self.clipsToBounds = YES; - self.userInteractionEnabled = YES; - - _needsMapReloadOnBoundsChange = YES; - _liveMap = NO; - _annotations = @[]; - _showAnnotationsOptions = ASMapNodeShowAnnotationsOptionsIgnored; - return self; -} - -- (void)didLoad -{ - [super didLoad]; - if (self.isLiveMap) { - [self addLiveMap]; - } -} - -- (void)dealloc -{ - [self destroySnapshotter]; -} - -- (void)setLayerBacked:(BOOL)layerBacked -{ - ASDisplayNodeAssert(!self.isLiveMap, @"ASMapNode can not be layer backed whilst .liveMap = YES, set .liveMap = NO to use layer backing."); - [super setLayerBacked:layerBacked]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - ASPerformBlockOnMainThread(^{ - if (self.isLiveMap) { - [self addLiveMap]; - } else { - [self takeSnapshot]; - } - }); -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - ASPerformBlockOnMainThread(^{ - if (self.isLiveMap) { - [self removeLiveMap]; - } - }); -} - -#pragma mark - Settings - -- (BOOL)isLiveMap -{ - ASLockScopeSelf(); - return _liveMap; -} - -- (void)setLiveMap:(BOOL)liveMap -{ - ASDisplayNodeAssert(!self.isLayerBacked, @"ASMapNode can not use the interactive map feature whilst .isLayerBacked = YES, set .layerBacked = NO to use the interactive map feature."); - ASLockScopeSelf(); - if (liveMap == _liveMap) { - return; - } - _liveMap = liveMap; - if (self.nodeLoaded) { - liveMap ? [self addLiveMap] : [self removeLiveMap]; - } -} - -- (BOOL)needsMapReloadOnBoundsChange -{ - ASLockScopeSelf(); - return _needsMapReloadOnBoundsChange; -} - -- (void)setNeedsMapReloadOnBoundsChange:(BOOL)needsMapReloadOnBoundsChange -{ - ASLockScopeSelf(); - _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange; -} - -- (MKMapSnapshotOptions *)options -{ - ASLockScopeSelf(); - if (!_options) { - _options = [[MKMapSnapshotOptions alloc] init]; - _options.region = MKCoordinateRegionForMapRect(MKMapRectWorld); - CGSize calculatedSize = self.calculatedSize; - if (!CGSizeEqualToSize(calculatedSize, CGSizeZero)) { - _options.size = calculatedSize; - } - } - return _options; -} - -- (void)setOptions:(MKMapSnapshotOptions *)options -{ - ASLockScopeSelf(); - if (!_options || ![options isEqual:_options]) { - _options = options; - if (self.isLiveMap) { - [self applySnapshotOptions]; - } else if (_snapshotter) { - [self destroySnapshotter]; - [self takeSnapshot]; - } - } -} - -- (MKCoordinateRegion)region -{ - return self.options.region; -} - -- (void)setRegion:(MKCoordinateRegion)region -{ - MKMapSnapshotOptions * options = [self.options copy]; - options.region = region; - self.options = options; -} - -- (id)mapDelegate -{ - return ASLockedSelf(_mapDelegate); -} - -- (void)setMapDelegate:(id)mapDelegate { - ASLockScopeSelf(); - _mapDelegate = mapDelegate; - - if (_mapView) { - ASDisplayNodeAssertMainThread(); - _mapView.delegate = mapDelegate; - } -} - -#pragma mark - Snapshotter - -- (void)takeSnapshot -{ - // If our size is zero, we want to avoid calling a default sized snapshot. Set _snapshotAfterLayout to YES - // so if layout changes in the future, we'll try snapshotting again. - ASLayout *layout = self.calculatedLayout; - if (layout == nil || CGSizeEqualToSize(CGSizeZero, layout.size)) { - _snapshotAfterLayout = YES; - return; - } - - _snapshotAfterLayout = NO; - - if (!_snapshotter) { - [self setUpSnapshotter]; - } - - if (_snapshotter.isLoading) { - return; - } - - __weak __typeof__(self) weakSelf = self; - [_snapshotter startWithQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) - completionHandler:^(MKMapSnapshot *snapshot, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) { - return; - } - - if (!error) { - UIImage *image = snapshot.image; - NSArray *annotations = strongSelf.annotations; - if (annotations.count > 0) { - // Only create a graphics context if we have annotations to draw. - // The MKMapSnapshotter is currently not capable of rendering annotations automatically. - - CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); - - image = ASGraphicsCreateImage(strongSelf.primitiveTraitCollection, image.size, YES, image.scale, image, nil, ^{ - [image drawAtPoint:CGPointZero]; - - UIImage *pinImage; - CGPoint pinCenterOffset = CGPointZero; - - // Get a standard annotation view pin if there is no custom annotation block. - if (!strongSelf.imageForStaticMapAnnotationBlock) { - pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; - } - - for (id annotation in annotations) { - if (strongSelf.imageForStaticMapAnnotationBlock) { - // Get custom annotation image from custom annotation block. - pinImage = strongSelf.imageForStaticMapAnnotationBlock(annotation, &pinCenterOffset); - if (!pinImage) { - // just for case block returned nil, which can happen - pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; - } - } - - CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; - if (CGRectContainsPoint(finalImageRect, point)) { - CGSize pinSize = pinImage.size; - point.x -= pinSize.width / 2.0; - point.y -= pinSize.height / 2.0; - point.x += pinCenterOffset.x; - point.y += pinCenterOffset.y; - [pinImage drawAtPoint:point]; - } - } - }); - } - - strongSelf.image = image; - } - }]; -} - -+ (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset NS_RETURNS_RETAINED -{ - static MKAnnotationView *pin; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; - }); - *centerOffset = pin.centerOffset; - return pin.image; -} - -- (void)setUpSnapshotter -{ - _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; -} - -- (void)destroySnapshotter -{ - [_snapshotter cancel]; - _snapshotter = nil; -} - -- (void)applySnapshotOptions -{ - MKMapSnapshotOptions *options = self.options; - [_mapView setCamera:options.camera animated:YES]; - [_mapView setRegion:options.region animated:YES]; - [_mapView setMapType:options.mapType]; - _mapView.showsBuildings = options.showsBuildings; - _mapView.showsPointsOfInterest = options.showsPointsOfInterest; -} - -#pragma mark - Actions -- (void)addLiveMap -{ - ASDisplayNodeAssertMainThread(); - if (!_mapView) { - __weak ASMapNode *weakSelf = self; - _mapView = [[MKMapView alloc] initWithFrame:CGRectZero]; - _mapView.delegate = weakSelf.mapDelegate; - [weakSelf applySnapshotOptions]; - [_mapView addAnnotations:_annotations]; - [weakSelf setNeedsLayout]; - [weakSelf.view addSubview:_mapView]; - - ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; - } - } -} - -- (void)removeLiveMap -{ - [_mapView removeFromSuperview]; - _mapView = nil; -} - -- (NSArray *)annotations -{ - ASLockScopeSelf(); - return _annotations; -} - -- (void)setAnnotations:(NSArray *)annotations -{ - annotations = [annotations copy] ? : @[]; - - ASLockScopeSelf(); - _annotations = annotations; - ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (self.isLiveMap) { - [_mapView removeAnnotations:_mapView.annotations]; - [_mapView addAnnotations:annotations]; - - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; - } - } else { - if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - self.region = [self regionToFitAnnotations:annotations]; - } - else { - [self takeSnapshot]; - } - } -} - -- (MKCoordinateRegion)regionToFitAnnotations:(NSArray> *)annotations -{ - if([annotations count] == 0) - return MKCoordinateRegionForMapRect(MKMapRectWorld); - - CLLocationCoordinate2D topLeftCoord = CLLocationCoordinate2DMake(-90, 180); - CLLocationCoordinate2D bottomRightCoord = CLLocationCoordinate2DMake(90, -180); - - for (id annotation in annotations) { - topLeftCoord = CLLocationCoordinate2DMake(std::fmax(topLeftCoord.latitude, annotation.coordinate.latitude), - std::fmin(topLeftCoord.longitude, annotation.coordinate.longitude)); - bottomRightCoord = CLLocationCoordinate2DMake(std::fmin(bottomRightCoord.latitude, annotation.coordinate.latitude), - std::fmax(bottomRightCoord.longitude, annotation.coordinate.longitude)); - } - - MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5, - topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5), - MKCoordinateSpanMake(std::fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2, - std::fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2)); - - return region; -} - --(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - return ASLockedSelf(_showAnnotationsOptions); -} - --(void)setShowAnnotationsOptions:(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - ASLockScopeSelf(); - _showAnnotationsOptions = showAnnotationsOptions; -} - -#pragma mark - Layout -- (void)setSnapshotSizeWithReloadIfNeeded:(CGSize)snapshotSize -{ - if (snapshotSize.height > 0 && snapshotSize.width > 0 && !CGSizeEqualToSize(self.options.size, snapshotSize)) { - _options.size = snapshotSize; - if (_snapshotter) { - [self destroySnapshotter]; - [self takeSnapshot]; - } - } -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - // FIXME: Need a better way to allow maps to take up the right amount of space in a layout (sizeRange, etc) - // These fallbacks protect against inheriting a constrainedSize that contains a CGFLOAT_MAX value. - if (!ASIsCGSizeValidForLayout(constrainedSize)) { - //ASDisplayNodeAssert(NO, @"Invalid width or height in ASMapNode"); - constrainedSize = CGSizeZero; - } - [self setSnapshotSizeWithReloadIfNeeded:constrainedSize]; - return constrainedSize; -} - -- (void)calculatedLayoutDidChange -{ - [super calculatedLayoutDidChange]; - - if (_snapshotAfterLayout) { - [self takeSnapshot]; - } -} - -// -layout isn't usually needed over -layoutSpecThatFits, but this way we can avoid a needless node wrapper for MKMapView. -- (void)layout -{ - [super layout]; - if (self.isLiveMap) { - _mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); - } else { - // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter. - if (_needsMapReloadOnBoundsChange) { - [self setSnapshotSizeWithReloadIfNeeded:self.bounds.size]; - // FIXME: Adding a check for Preload here seems to cause intermittent map load failures, but shouldn't. - // if (ASInterfaceStateIncludesPreload(self.interfaceState)) { - } - } -} - -- (BOOL)supportsLayerBacking -{ - return NO; -} - -@end -#endif // TARGET_OS_IOS && AS_USE_MAPKIT diff --git a/Source/ASMultiplexImageNode.h b/Source/ASMultiplexImageNode.h deleted file mode 100644 index 72a913a908..0000000000 --- a/Source/ASMultiplexImageNode.h +++ /dev/null @@ -1,282 +0,0 @@ -// -// ASMultiplexImageNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_USE_PHOTOS -#import -#else -@class PHAsset; -@class PHImageManager; -@class PHImageRequestOptions; -#endif - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASMultiplexImageNodeDelegate; -@protocol ASMultiplexImageNodeDataSource; - -typedef id ASImageIdentifier; - -ASDK_EXTERN NSString *const ASMultiplexImageNodeErrorDomain; - -/** - * ASMultiplexImageNode error codes. - */ -typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) { - /** - * Indicates that the data source didn't provide a source for an image identifier. - */ - ASMultiplexImageNodeErrorCodeNoSourceForImage = 0, - - /** - * Indicates that the best image identifier changed before a download for a worse identifier began. - */ - ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged, - - /** - * Indicates that the Photos framework returned no image and no error. - * This may happen if the image is in iCloud and the user did not specify `allowsNetworkAccess` - * in their image request. - */ - ASMultiplexImageNodeErrorCodePhotosImageManagerFailedWithoutError, - - /** - * Indicates that the image node could not retrieve the PHAsset for a given asset identifier. - * This typically means that the user has not given Photos framework permissions yet or the asset - * has been removed from the device. - */ - ASMultiplexImageNodeErrorCodePHAssetIsUnavailable -}; - - -/** - * @abstract ASMultiplexImageNode is an image node that can load and display multiple versions of an image. For - * example, it can display a low-resolution version of an image while the high-resolution version is loading. - * - * @discussion ASMultiplexImageNode begins loading images when its resource can either return a UIImage directly, or a URL the image node should load. - */ -@interface ASMultiplexImageNode : ASImageNode - -/** - * @abstract The designated initializer. - * @param cache The object that implements a cache of images for the image node. - * @param downloader The object that implements image downloading for the image node. - * @discussion If `cache` is nil, the receiver will not attempt to retrieve images from a cache before downloading them. - * @return An initialized ASMultiplexImageNode. - */ -- (instancetype)initWithCache:(nullable id)cache downloader:(nullable id)downloader NS_DESIGNATED_INITIALIZER; - -/** - * @abstract The delegate, which must conform to the protocol. - */ -@property (nonatomic, weak) id delegate; - -/** - * @abstract The data source, which must conform to the protocol. - * @discussion This value is required for ASMultiplexImageNode to load images. - */ -@property (nonatomic, weak) id dataSource; - -/** - * @abstract Whether the receiver should download more than just its highest-quality image. Defaults to NO. - * - * @discussion ASMultiplexImageNode immediately loads and displays the first image specified in (its - * highest-quality image). If that image is not immediately available or cached, the node can download and display - * lesser-quality images. Set `downloadsIntermediateImages` to YES to enable this behaviour. - */ -@property (nonatomic) BOOL downloadsIntermediateImages; - -/** - * @abstract An array of identifiers representing various versions of an image for ASMultiplexImageNode to display. - * - * @discussion An identifier can be any object that conforms to NSObject and NSCopying. The array should be in - * decreasing order of image quality -- that is, the first identifier in the array represents the best version. - * - * @see for more information on the image loading process. - */ -@property (nonatomic, copy) NSArray *imageIdentifiers; - -/** - * @abstract Notify the receiver SSAA that its data source has new UIImages or NSURLs available for . - * - * @discussion If a higher-quality image than is currently displayed is now available, it will be loaded. - */ -- (void)reloadImageIdentifierSources; - -/** - * @abstract The identifier for the last image that the receiver loaded, or nil. - * - * @discussion This value may differ from if the image hasn't yet been displayed. - */ -@property (nullable, nonatomic, readonly) ASImageIdentifier loadedImageIdentifier; - -/** - * @abstract The identifier for the image that the receiver is currently displaying, or nil. - */ -@property (nullable, nonatomic, readonly) ASImageIdentifier displayedImageIdentifier; - -/** - * @abstract If the downloader implements progressive image rendering and this value is YES progressive renders of the - * image will be displayed as the image downloads. Regardless of this properties value, progress renders will - * only occur when the node is visible. Defaults to YES. - */ -@property (nonatomic) BOOL shouldRenderProgressImages; - -/** - * @abstract The image manager that this image node should use when requesting images from the Photos framework. If this is `nil` (the default), then `PHImageManager.defaultManager` is used. - - * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. - */ -@property (nullable, nonatomic) PHImageManager *imageManager API_AVAILABLE(ios(8.0), tvos(10.0)); - -@end - - -#pragma mark - -/** - * The methods declared by the ASMultiplexImageNodeDelegate protocol allow the adopting delegate to respond to - * notifications such as began, progressed and finished downloading, updated and displayed an image. - */ -@protocol ASMultiplexImageNodeDelegate - -@optional -/** - * @abstract Notification that the image node began downloading an image. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that is downloading. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode didStartDownloadOfImageWithIdentifier:(id)imageIdentifier; - -/** - * @abstract Notification that the image node's download progressed. - * @param imageNode The sender. - * @param downloadProgress The progress of the download. Value is between 0.0 and 1.0. - * @param imageIdentifier The identifier for the image that is downloading. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode - didUpdateDownloadProgress:(CGFloat)downloadProgress - forImageWithIdentifier:(ASImageIdentifier)imageIdentifier; - -/** - * @abstract Notification that the image node's download has finished. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that finished downloading. - * @param error The error that occurred while downloading, if one occurred; nil otherwise. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode -didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier - error:(nullable NSError *)error; - -/** - * @abstract Notification that the image node's image was updated. - * @param imageNode The sender. - * @param image The new image, ready for display. - * @param imageIdentifier The identifier for `image`. - * @param previousImage The old, previously-loaded image. - * @param previousImageIdentifier The identifier for `previousImage`. - * @note This method does not indicate that `image` has been displayed. - * @see <[ASMultiplexImageNodeDelegate multiplexImageNode:didDisplayUpdatedImage:withIdentifier:]>. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode - didUpdateImage:(nullable UIImage *)image - withIdentifier:(nullable ASImageIdentifier)imageIdentifier - fromImage:(nullable UIImage *)previousImage - withIdentifier:(nullable ASImageIdentifier)previousImageIdentifier; - -/** - * @abstract Notification that the image node displayed a new image. - * @param imageNode The sender. - * @param image The new image, now being displayed. - * @param imageIdentifier The identifier for `image`. - * @discussion This method is only called when `image` changes, and not on subsequent redisplays of the same image. - */ -- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode - didDisplayUpdatedImage:(nullable UIImage *)image - withIdentifier:(nullable ASImageIdentifier)imageIdentifier; - -/** - * @abstract Notification that the image node finished displaying an image. - * @param imageNode The sender. - * @discussion This method is called every time an image is displayed, whether or not it has changed. - */ -- (void)multiplexImageNodeDidFinishDisplay:(ASMultiplexImageNode *)imageNode; - -@end - - -#pragma mark - -/** - * The ASMultiplexImageNodeDataSource protocol is adopted by an object that provides the multiplex image node, - * for each image identifier, an image or a URL the image node should load. - */ -@protocol ASMultiplexImageNodeDataSource - -@optional -/** - * @abstract An image for the specified identifier. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that should be returned. - * @discussion If the image is already available to the data source, this method should be used in lieu of providing the - * URL to the image via -multiplexImageNode:URLForImageIdentifier:. - * @return A UIImage corresponding to `imageIdentifier`, or nil if none is available. - */ -- (nullable UIImage *)multiplexImageNode:(ASMultiplexImageNode *)imageNode imageForImageIdentifier:(ASImageIdentifier)imageIdentifier; - -/** - * @abstract An image URL for the specified identifier. - * @param imageNode The sender. - * @param imageIdentifier The identifier for the image that will be downloaded. - * @discussion Supported URLs include HTTP, HTTPS, AssetsLibrary, and FTP URLs as well as Photos framework URLs (see note). - * - * If the image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource - * multiplexImageNode:imageForImageIdentifier:]> instead. - * @return An NSURL for the image identified by `imageIdentifier`, or nil if none is available. - * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. - */ -- (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier; - -/** - * @abstract A PHAsset for the specific asset local identifier - * @param imageNode The sender. - * @param assetLocalIdentifier The local identifier for a PHAsset that this image node is loading. - * - * @discussion This optional method can improve image performance if your data source already has the PHAsset available. - * If this method is not implemented, or returns nil, the image node will request the asset from the Photos framework. - * @note This method may be called from any thread. - * @return A PHAsset corresponding to `assetLocalIdentifier`, or nil if none is available. - */ -- (nullable PHAsset *)multiplexImageNode:(ASMultiplexImageNode *)imageNode assetForLocalIdentifier:(NSString *)assetLocalIdentifier API_AVAILABLE(ios(8.0), tvos(10.0)); -@end - -#pragma mark - - -#if AS_USE_PHOTOS - -@interface NSURL (ASPhotosFrameworkURLs) - -/** - * @abstract Create an NSURL that specifies an image from the Photos framework. - * - * @discussion When implementing `-multiplexImageNode:URLForImageIdentifier:`, you can return a URL - * created by this method and the image node will attempt to load the image from the Photos framework. - * @note The `synchronous` flag in `options` is ignored. - * @note The `Opportunistic` delivery mode is not supported and will be treated as `HighQualityFormat`. - */ -+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier - targetSize:(CGSize)targetSize - contentMode:(PHImageContentMode)contentMode - options:(PHImageRequestOptions *)options NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT API_AVAILABLE(ios(8.0), tvos(10.0)); - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm deleted file mode 100644 index 55de28ddc8..0000000000 --- a/Source/ASMultiplexImageNode.mm +++ /dev/null @@ -1,940 +0,0 @@ -// -// ASMultiplexImageNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY -#import -#endif - -#import -#import -#import -#import -#import -#import -#import - -#if AS_USE_PHOTOS -#import -#endif - -#if AS_PIN_REMOTE_IMAGE -#import -#else -#import -#endif - -using AS::MutexLocker; - -NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain"; - -#if AS_USE_ASSETS_LIBRARY -static NSString *const kAssetsLibraryURLScheme = @"assets-library"; -#endif - -/** - @abstract Signature for the block to be performed after an image has loaded. - @param image The image that was loaded, or nil if no image was loaded. - @param imageIdentifier The identifier of the image that was loaded, or nil if no image was loaded. - @param error An error describing why an image couldn't be loaded, if it failed to load; nil otherwise. - */ -typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdentifier, NSError *error); - -@interface ASMultiplexImageNode () -{ -@private - // Core. - id _cache; - - id _downloader; - struct { - unsigned int downloaderImplementsSetProgress:1; - unsigned int downloaderImplementsSetPriority:1; - unsigned int downloaderImplementsDownloadWithPriority:1; - } _downloaderFlags; - - __weak id _delegate; - struct { - unsigned int downloadStart:1; - unsigned int downloadProgress:1; - unsigned int downloadFinish:1; - unsigned int updatedImageDisplayFinish:1; - unsigned int updatedImage:1; - unsigned int displayFinish:1; - } _delegateFlags; - - __weak id _dataSource; - struct { - unsigned int image:1; - unsigned int URL:1; - unsigned int asset:1; - } _dataSourceFlags; - - // Image flags. - BOOL _downloadsIntermediateImages; // Defaults to NO. - AS::Mutex _imageIdentifiersLock; - NSArray *_imageIdentifiers; - id _loadedImageIdentifier; - id _loadingImageIdentifier; - id _displayedImageIdentifier; - __weak NSOperation *_phImageRequestOperation; - - // Networking. - AS::RecursiveMutex _downloadIdentifierLock; - id _downloadIdentifier; - - // Properties - BOOL _shouldRenderProgressImages; - - //set on init only - BOOL _cacheSupportsClearing; -} - -//! @abstract Read-write redeclaration of property declared in ASMultiplexImageNode.h. -@property (nonatomic, copy) id loadedImageIdentifier; - -//! @abstract The image identifier that's being loaded by _loadNextImageWithCompletion:. -@property (nonatomic, copy) id loadingImageIdentifier; - -/** - @abstract Returns the next image identifier that should be downloaded. - @discussion This method obeys and reflects the value of `downloadsIntermediateImages`. - @result The next image identifier, from `_imageIdentifiers`, that should be downloaded, or nil if no image should be downloaded next. - */ -- (id)_nextImageIdentifierToDownload; - -/** - @abstract Returns the best image that is immediately available from our datasource without downloading or hitting the cache. - @param imageIdentifierOut Upon return, the image identifier for the returned image; nil otherwise. - @discussion This method exclusively uses the data source's -multiplexImageNode:imageForIdentifier: method to return images. It does not fetch from the cache or kick off downloading. - @result The best UIImage available immediately; nil if no image is immediately available. - */ -- (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierOut; - -/** - @abstract Loads and displays the next image in the receiver's loading sequence. - @discussion This method obeys `downloadsIntermediateImages`. This method has no effect if nothing further should be loaded, as indicated by `_nextImageIdentifierToDownload`. This method will load the next image from the data-source, if possible; otherwise, the session's image cache will be queried for the desired image, and as a last resort, the image will be downloaded. - */ -- (void)_loadNextImage; - -/** - @abstract Fetches the image corresponding to the given imageIdentifier from the given URL from the session's image cache. - @param imageIdentifier The identifier for the image to be fetched. May not be nil. - @param imageURL The URL of the image to fetch. May not be nil. - @param completionBlock The block to be performed when the image has been fetched from the cache, if possible. May not be nil. - @discussion This method queries both the session's in-memory and on-disk caches (with preference for the in-memory cache). - */ -- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock; - -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY -/** - @abstract Loads the image corresponding to the given assetURL from the device's Assets Library. - @param imageIdentifier The identifier for the image to be loaded. May not be nil. - @param assetURL The assets-library URL (e.g., "assets-library://identifier") of the image to load, from ALAsset. May not be nil. - @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. - */ -- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; -#endif - -#if AS_USE_PHOTOS -/** - @abstract Loads the image corresponding to the given image request from the Photos framework. - @param imageIdentifier The identifier for the image to be loaded. May not be nil. - @param request The photos image request to load. May not be nil. - @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. - */ -- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock API_AVAILABLE(ios(8.0), tvos(10.0)); -#endif - -/** - @abstract Downloads the image corresponding to the given imageIdentifier from the given URL. - @param imageIdentifier The identifier for the image to be downloaded. May not be nil. - @param imageURL The URL of the image to downloaded. May not be nil. - @param completionBlock The block to be performed when the image has been downloaded, if possible. May not be nil. - */ -- (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; - -@end - -@implementation ASMultiplexImageNode - -#pragma mark - Getting Started / Tearing Down -- (instancetype)initWithCache:(id)cache downloader:(id)downloader -{ - if (!(self = [super init])) - return nil; - - _cache = (id)cache; - _downloader = (id)downloader; - - _downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; - _downloaderFlags.downloaderImplementsDownloadWithPriority = [downloader respondsToSelector:@selector(downloadImageWithURL:priority:callbackQueue:downloadProgress:completion:)]; - - _cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; - - _shouldRenderProgressImages = YES; - - self.shouldBypassEnsureDisplay = YES; - - return self; -} - -- (instancetype)init -{ -#if AS_PIN_REMOTE_IMAGE - return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]]; -#else - return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; -#endif -} - -- (void)dealloc -{ - [_phImageRequestOperation cancel]; -} - -#pragma mark - ASDisplayNode Overrides - -- (void)clearContents -{ - [super clearContents]; // This actually clears the contents, so we need to do this first for our displayedImageIdentifier to be meaningful. - [self _setDisplayedImageIdentifier:nil withImage:nil]; - - // NOTE: We intentionally do not cancel image downloads until `clearPreloadedData`. -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - - [_phImageRequestOperation cancel]; - - [self _setDownloadIdentifier:nil]; - - if (_cacheSupportsClearing && self.loadedImageIdentifier != nil) { - NSURL *URL = [_dataSource multiplexImageNode:self URLForImageIdentifier:self.loadedImageIdentifier]; - if (URL != nil) { - [_cache clearFetchedImageFromCacheWithURL:URL]; - } - } - - // setting this to nil makes the node fetch images the next time its display starts - _loadedImageIdentifier = nil; - [self _setImage:nil]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - [self _loadImageIdentifiers]; -} - -- (void)displayDidFinish -{ - [super displayDidFinish]; - - // We may now be displaying the loaded identifier, if they're different. - UIImage *displayedImage = self.image; - if (displayedImage) { - if (!ASObjectIsEqual(_displayedImageIdentifier, _loadedImageIdentifier)) - [self _setDisplayedImageIdentifier:_loadedImageIdentifier withImage:displayedImage]; - - // Delegateify - if (_delegateFlags.displayFinish) { - if (ASDisplayNodeThreadIsMain()) - [_delegate multiplexImageNodeDidFinishDisplay:self]; - else { - __weak __typeof__(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - [strongSelf.delegate multiplexImageNodeDidFinishDisplay:strongSelf]; - }); - } - } - } -} - -- (BOOL)placeholderShouldPersist -{ - return (self.image == nil && self.animatedImage == nil && self.imageIdentifiers.count > 0); -} - -/* displayWillStartAsynchronously in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary - in ASNetworkImageNode as well. */ -- (void)displayWillStartAsynchronously:(BOOL)asynchronously -{ - [super displayWillStartAsynchronously:asynchronously]; - [self didEnterPreloadState]; - [self _updatePriorityOnDownloaderIfNeeded]; -} - -/* didEnterVisibleState / didExitVisibleState in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary - in ASNetworkImageNode as well. */ -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - [self _updatePriorityOnDownloaderIfNeeded]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - [self _updatePriorityOnDownloaderIfNeeded]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (void)didExitDisplayState -{ - [super didExitDisplayState]; - [self _updatePriorityOnDownloaderIfNeeded]; -} - -#pragma mark - Core - -- (void)setImage:(UIImage *)image -{ - ASDisplayNodeAssert(NO, @"Setting the image directly on an ASMultiplexImageNode is unsafe. It will be cleared in didExitPreloadRange and will have no way to restore in didEnterPreloadRange"); - super.image = image; -} - -- (void)_setImage:(UIImage *)image -{ - super.image = image; -} - -- (void)setDelegate:(id )delegate -{ - if (_delegate == delegate) - return; - - _delegate = delegate; - _delegateFlags.downloadStart = [_delegate respondsToSelector:@selector(multiplexImageNode:didStartDownloadOfImageWithIdentifier:)]; - _delegateFlags.downloadProgress = [_delegate respondsToSelector:@selector(multiplexImageNode:didUpdateDownloadProgress:forImageWithIdentifier:)]; - _delegateFlags.downloadFinish = [_delegate respondsToSelector:@selector(multiplexImageNode:didFinishDownloadingImageWithIdentifier:error:)]; - _delegateFlags.updatedImageDisplayFinish = [_delegate respondsToSelector:@selector(multiplexImageNode:didDisplayUpdatedImage:withIdentifier:)]; - _delegateFlags.updatedImage = [_delegate respondsToSelector:@selector(multiplexImageNode:didUpdateImage:withIdentifier:fromImage:withIdentifier:)]; - _delegateFlags.displayFinish = [_delegate respondsToSelector:@selector(multiplexImageNodeDidFinishDisplay:)]; -} - - -- (void)setDataSource:(id )dataSource -{ - if (_dataSource == dataSource) - return; - - _dataSource = dataSource; - _dataSourceFlags.image = [_dataSource respondsToSelector:@selector(multiplexImageNode:imageForImageIdentifier:)]; - _dataSourceFlags.URL = [_dataSource respondsToSelector:@selector(multiplexImageNode:URLForImageIdentifier:)]; - if (AS_AVAILABLE_IOS_TVOS(9, 10)) { - _dataSourceFlags.asset = [_dataSource respondsToSelector:@selector(multiplexImageNode:assetForLocalIdentifier:)]; - } -} - - -- (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages -{ - [self lock]; - if (shouldRenderProgressImages == _shouldRenderProgressImages) { - [self unlock]; - return; - } - - _shouldRenderProgressImages = shouldRenderProgressImages; - - [self unlock]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; -} - -- (BOOL)shouldRenderProgressImages -{ - return ASLockedSelf(_shouldRenderProgressImages); -} - -#pragma mark - - -#pragma mark - - -- (NSArray *)imageIdentifiers -{ - MutexLocker l(_imageIdentifiersLock); - return _imageIdentifiers; -} - -- (void)setImageIdentifiers:(NSArray *)imageIdentifiers -{ - { - MutexLocker l(_imageIdentifiersLock); - if (ASObjectIsEqual(_imageIdentifiers, imageIdentifiers)) { - return; - } - - _imageIdentifiers = [[NSArray alloc] initWithArray:imageIdentifiers copyItems:YES]; - } - - [self setNeedsPreload]; -} - -- (void)reloadImageIdentifierSources -{ - // setting this to nil makes the node think it has not downloaded any images - _loadedImageIdentifier = nil; - [self _loadImageIdentifiers]; -} - -#pragma mark - - - -#pragma mark - Core Internal -- (void)_setDisplayedImageIdentifier:(id)displayedImageIdentifier withImage:(UIImage *)image -{ - ASDisplayNodeAssertMainThread(); - - if (ASObjectIsEqual(_displayedImageIdentifier, displayedImageIdentifier)) { - return; - } - - _displayedImageIdentifier = displayedImageIdentifier; - - // Delegateify. - // Note that we're using the params here instead of self.image and _displayedImageIdentifier because those can change before the async block below executes. - if (_delegateFlags.updatedImageDisplayFinish) { - if (ASDisplayNodeThreadIsMain()) - [_delegate multiplexImageNode:self didDisplayUpdatedImage:image withIdentifier:displayedImageIdentifier]; - else { - __weak __typeof__(self) weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - [strongSelf.delegate multiplexImageNode:strongSelf didDisplayUpdatedImage:image withIdentifier:displayedImageIdentifier]; - }); - } - } -} - -- (void)_setDownloadIdentifier:(id)downloadIdentifier -{ - MutexLocker l(_downloadIdentifierLock); - if (ASObjectIsEqual(downloadIdentifier, _downloadIdentifier)) - return; - - if (_downloadIdentifier) { - [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; - } - _downloadIdentifier = downloadIdentifier; -} - -#pragma mark - Image Loading Machinery - -- (void)_loadImageIdentifiers -{ - // Grab the best possible image we can load right now. - id bestImmediatelyAvailableImageIdentifier = nil; - UIImage *bestImmediatelyAvailableImage = [self _bestImmediatelyAvailableImageFromDataSource:&bestImmediatelyAvailableImageIdentifier]; - as_log_verbose(ASImageLoadingLog(), "%@ Best immediately available image identifier is %@", self, bestImmediatelyAvailableImageIdentifier); - - // Load it. This kicks off cache fetching/downloading, as appropriate. - [self _finishedLoadingImage:bestImmediatelyAvailableImage forIdentifier:bestImmediatelyAvailableImageIdentifier error:nil]; -} - -- (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierOut -{ - MutexLocker l(_imageIdentifiersLock); - - // If we don't have any identifiers to load or don't implement the image DS method, bail. - if ([_imageIdentifiers count] == 0 || !_dataSourceFlags.image) { - return nil; - } - - // Grab the best available image from the data source. - UIImage *existingImage = self.image; - for (id imageIdentifier in _imageIdentifiers) { - // If this image is already loaded, don't request it from the data source again because - // the data source may generate a new instance of UIImage that returns NO for isEqual: - // and we'll end up in an infinite loading loop. - UIImage *image = ASObjectIsEqual(imageIdentifier, _loadedImageIdentifier) ? existingImage : [_dataSource multiplexImageNode:self imageForImageIdentifier:imageIdentifier]; - if (image) { - if (imageIdentifierOut) { - *imageIdentifierOut = imageIdentifier; - } - - return image; - } - } - - return nil; -} - -#pragma mark - - -- (void)_updatePriorityOnDownloaderIfNeeded -{ - DISABLED_ASAssertUnlocked(_downloadIdentifierLock); - - if (_downloaderFlags.downloaderImplementsSetPriority) { - // Read our interface state before locking so that we don't lock super while holding our lock. - ASInterfaceState interfaceState = self.interfaceState; - MutexLocker l(_downloadIdentifierLock); - - if (_downloadIdentifier != nil) { - ASImageDownloaderPriority priority = ASImageDownloaderPriorityWithInterfaceState(interfaceState); - [_downloader setPriority:priority withDownloadIdentifier:_downloadIdentifier]; - } - } -} - -- (void)_updateProgressImageBlockOnDownloaderIfNeeded -{ - DISABLED_ASAssertUnlocked(_downloadIdentifierLock); - - BOOL shouldRenderProgressImages = self.shouldRenderProgressImages; - - // Read our interface state before locking so that we don't lock super while holding our lock. - ASInterfaceState interfaceState = self.interfaceState; - MutexLocker l(_downloadIdentifierLock); - - if (!_downloaderFlags.downloaderImplementsSetProgress || _downloadIdentifier == nil) { - return; - } - - ASImageDownloaderProgressImage progress = nil; - if (shouldRenderProgressImages && ASInterfaceStateIncludesVisible(interfaceState)) { - __weak __typeof__(self) weakSelf = self; - progress = ^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) { - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - MutexLocker l(strongSelf->_downloadIdentifierLock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - [strongSelf _setImage:progressImage]; - }; - } - [_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier]; -} - -- (void)_clearImage -{ - [self _setImage:nil]; -} - -#pragma mark - -- (id)_nextImageIdentifierToDownload -{ - MutexLocker l(_imageIdentifiersLock); - - // If we've already loaded the best identifier, we've got nothing else to do. - id bestImageIdentifier = _imageIdentifiers.firstObject; - if (!bestImageIdentifier || ASObjectIsEqual(_loadedImageIdentifier, bestImageIdentifier)) { - return nil; - } - - id nextImageIdentifierToDownload = nil; - - // If we're not supposed to download intermediate images, load the best identifier we've got. - if (!_downloadsIntermediateImages) { - nextImageIdentifierToDownload = bestImageIdentifier; - } - // Otherwise, load progressively. - else { - NSUInteger loadedIndex = [_imageIdentifiers indexOfObject:_loadedImageIdentifier]; - - // If nothing has loaded yet, load the worst identifier. - if (loadedIndex == NSNotFound) { - nextImageIdentifierToDownload = [_imageIdentifiers lastObject]; - } - // Otherwise, load the next best identifier (if there is one) - else if (loadedIndex > 0) { - nextImageIdentifierToDownload = _imageIdentifiers[loadedIndex - 1]; - } - } - - return nextImageIdentifierToDownload; -} - -- (void)_loadNextImage -{ - // Determine the next identifier to load (if any). - id nextImageIdentifier = [self _nextImageIdentifierToDownload]; - if (!nextImageIdentifier) { - [self _finishedLoadingImage:nil forIdentifier:nil error:nil]; - return; - } - - as_activity_create_for_scope("Load next image for multiplex image node"); - as_log_verbose(ASImageLoadingLog(), "Loading image for %@ ident: %@", self, nextImageIdentifier); - self.loadingImageIdentifier = nextImageIdentifier; - - __weak __typeof__(self) weakSelf = self; - ASMultiplexImageLoadCompletionBlock finishedLoadingBlock = ^(UIImage *image, id imageIdentifier, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - // Only nil out the loading identifier if the loading identifier hasn't changed. - if (ASObjectIsEqual(strongSelf.loadingImageIdentifier, nextImageIdentifier)) { - strongSelf.loadingImageIdentifier = nil; - } - [strongSelf _finishedLoadingImage:image forIdentifier:imageIdentifier error:error]; - }; - - // Ask our data-source if it's got this image. - if (_dataSourceFlags.image) { - UIImage *image = [_dataSource multiplexImageNode:self imageForImageIdentifier:nextImageIdentifier]; - if (image) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from data source for %@ ident: %@", self, nextImageIdentifier); - finishedLoadingBlock(image, nextImageIdentifier, nil); - return; - } - } - - NSURL *nextImageURL = (_dataSourceFlags.URL) ? [_dataSource multiplexImageNode:self URLForImageIdentifier:nextImageIdentifier] : nil; - // If we fail to get a URL for the image, we have no source and can't proceed. - if (!nextImageURL) { - os_log_error(ASImageLoadingLog(), "Could not acquire URL %@ ident: (%@)", self, nextImageIdentifier); - finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeNoSourceForImage userInfo:nil]); - return; - } - -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY - // If it's an assets-library URL, we need to fetch it from the assets library. - if ([[nextImageURL scheme] isEqualToString:kAssetsLibraryURLScheme]) { - // Load the asset. - [self _loadALAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from assets library for %@ %@", weakSelf, nextImageIdentifier); - finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); - }]; - - return; - } -#endif - -#if AS_USE_PHOTOS - if (AS_AVAILABLE_IOS_TVOS(9, 10)) { - // Likewise, if it's a Photos asset, we need to fetch it accordingly. - if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) { - [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from Photos for %@ %@", weakSelf, nextImageIdentifier); - finishedLoadingBlock(image, nextImageIdentifier, error); - }]; - - return; - } - } -#endif - - // Otherwise, it's a web URL that we can download. - // First, check the cache. - [self _fetchImageWithIdentifierFromCache:nextImageIdentifier URL:nextImageURL completion:^(UIImage *imageFromCache) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - // If we had a cache-hit, we're done. - if (imageFromCache) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from cache for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, imageFromCache); - finishedLoadingBlock(imageFromCache, nextImageIdentifier, nil); - return; - } - - // If the next image to load has changed, bail. - if (!ASObjectIsEqual([strongSelf _nextImageIdentifierToDownload], nextImageIdentifier)) { - finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged userInfo:nil]); - return; - } - - // Otherwise, we've got to download it. - [strongSelf _downloadImageWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (downloadedImage) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from download for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, downloadedImage); - } else { - os_log_error(ASImageLoadingLog(), "Error downloading image for %@ id: %@ err: %@", strongSelf, nextImageIdentifier, error); - } - finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); - }]; - }]; -} -#if TARGET_OS_IOS && AS_USE_ASSETS_LIBRARY -- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - // ALAssetsLibrary was replaced in iOS 8 and deprecated in iOS 9. - // We'll drop support very soon. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init]; - - [assetLibrary assetForURL:assetURL resultBlock:^(ALAsset *asset) { - ALAssetRepresentation *representation = [asset defaultRepresentation]; - CGImageRef coreGraphicsImage = [representation fullScreenImage]; - - UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil); - completionBlock(downloadedImage, nil); - } failureBlock:^(NSError *error) { - completionBlock(nil, error); - }]; -#pragma clang diagnostic pop -} -#endif - -#if AS_USE_PHOTOS -- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(request, @"request is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - /* - * Locking rationale: - * As of iOS 9, Photos.framework will eventually deadlock if you hit it with concurrent fetch requests. rdar://22984886 - * Concurrent image requests are OK, but metadata requests aren't, so we limit ourselves to one at a time. - */ - static NSLock *phRequestLock; - static NSOperationQueue *phImageRequestQueue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - phRequestLock = [NSLock new]; - phImageRequestQueue = [NSOperationQueue new]; - phImageRequestQueue.maxConcurrentOperationCount = 10; - phImageRequestQueue.name = @"org.AsyncDisplayKit.MultiplexImageNode.phImageRequestQueue"; - }); - - // Each ASMultiplexImageNode can have max 1 inflight Photos image request operation - [_phImageRequestOperation cancel]; - - __weak __typeof(self) weakSelf = self; - NSOperation *newImageRequestOp = [NSBlockOperation blockOperationWithBlock:^{ - __strong __typeof(weakSelf) strongSelf = weakSelf; - if (strongSelf == nil) { return; } - - PHAsset *imageAsset = nil; - - // Try to get the asset immediately from the data source. - if (strongSelf->_dataSourceFlags.asset) { - imageAsset = [strongSelf.dataSource multiplexImageNode:strongSelf assetForLocalIdentifier:request.assetIdentifier]; - } - - // Fall back to locking and getting the PHAsset. - if (imageAsset == nil) { - [phRequestLock lock]; - // -[PHFetchResult dealloc] plays a role in the deadlock mentioned above, so we make sure the PHFetchResult is deallocated inside the critical section - @autoreleasepool { - imageAsset = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil].firstObject; - } - [phRequestLock unlock]; - } - - if (imageAsset == nil) { - NSError *error = [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodePHAssetIsUnavailable userInfo:nil]; - completionBlock(nil, error); - return; - } - - PHImageRequestOptions *options = [request.options copy]; - - // We don't support opportunistic delivery – one request, one image. - if (options.deliveryMode == PHImageRequestOptionsDeliveryModeOpportunistic) { - options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; - } - - if (options.deliveryMode == PHImageRequestOptionsDeliveryModeHighQualityFormat) { - // Without this flag the result will be delivered on the main queue, which is pointless - // But synchronous -> HighQualityFormat so we only use it if high quality format is specified - options.synchronous = YES; - } - - PHImageManager *imageManager = strongSelf.imageManager ? : PHImageManager.defaultManager; - [imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) { - NSError *error = info[PHImageErrorKey]; - - if (error == nil && image == nil) { - error = [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodePhotosImageManagerFailedWithoutError userInfo:nil]; - } - - if (NSThread.isMainThread) { - dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ - completionBlock(image, error); - }); - } else { - completionBlock(image, error); - } - }]; - }]; - // If you don't set this, iOS will sometimes infer NSQualityOfServiceUserInteractive and promote the entire queue to that level, damaging system responsiveness - newImageRequestOp.qualityOfService = NSQualityOfServiceUserInitiated; - _phImageRequestOperation = newImageRequestOp; - [phImageRequestQueue addOperation:newImageRequestOp]; -} -#endif - -- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(imageURL, @"imageURL is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - if (_cache) { - [_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(id imageContainer, __unused ASImageCacheType cacheType ) { - completionBlock([imageContainer asdk_image]); - }]; - } - // If we don't have a cache, just fail immediately. - else { - completionBlock(nil); - } -} - -- (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock -{ - ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(imageURL, @"imageURL is required"); - ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - // Delegate (start) - if (_delegateFlags.downloadStart) - [_delegate multiplexImageNode:self didStartDownloadOfImageWithIdentifier:imageIdentifier]; - - __weak __typeof__(self) weakSelf = self; - ASImageDownloaderProgress downloadProgressBlock = NULL; - if (_delegateFlags.downloadProgress) { - downloadProgressBlock = ^(CGFloat progress) { - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - [strongSelf.delegate multiplexImageNode:strongSelf didUpdateDownloadProgress:progress forImageWithIdentifier:imageIdentifier]; - }; - } - - ASImageDownloaderCompletion completion = ^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { - // We dereference iVars directly, so we can't have weakSelf going nil on us. - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - MutexLocker l(strongSelf->_downloadIdentifierLock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - completionBlock([imageContainer asdk_image], error); - - // Delegateify. - if (strongSelf->_delegateFlags.downloadFinish) - [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; - }; - - // Download! - ASPerformBlockOnBackgroundThread(^{ - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - dispatch_queue_t callbackQueue = dispatch_get_main_queue(); - - id downloadIdentifier; - if (strongSelf->_downloaderFlags.downloaderImplementsDownloadWithPriority) { - /* - Decide a priority based on the current interface state of this node. - It can happen that this method was called when the node entered preload state - but the interface state, at this point, tells us that the node is (going to be) visible, - If that's the case, we jump to a higher priority directly. - */ - ASImageDownloaderPriority priority = ASImageDownloaderPriorityWithInterfaceState(strongSelf.interfaceState); - downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL - priority:priority - callbackQueue:callbackQueue - downloadProgress:downloadProgressBlock - completion:completion]; - } else { - /* - Kick off a download with default priority. - The actual "default" value is decided by the downloader. - ASBasicImageDownloader and ASPINRemoteImageDownloader both use ASImageDownloaderPriorityImminent - which is mapped to NSURLSessionTaskPriorityDefault. - - This means that preload and display nodes use the same priority - and their requests are put into the same pool. - */ - downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL - callbackQueue:callbackQueue - downloadProgress:downloadProgressBlock - completion:completion]; - } - - [strongSelf _setDownloadIdentifier:downloadIdentifier]; - [strongSelf _updateProgressImageBlockOnDownloaderIfNeeded]; - }); -} - -#pragma mark - -- (void)_finishedLoadingImage:(UIImage *)image forIdentifier:(id)imageIdentifier error:(NSError *)error -{ - // If we failed to load, we stop the loading process. - // Note that if we bailed before we began downloading because the best identifier changed, we don't bail, but rather just begin loading the best image identifier. - if (error && !([error.domain isEqual:ASMultiplexImageNodeErrorDomain] && error.code == ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged)) - return; - - - _imageIdentifiersLock.lock(); - NSUInteger imageIdentifierCount = [_imageIdentifiers count]; - _imageIdentifiersLock.unlock(); - - // Update our image if we got one, or if we're not supposed to display one at all. - // We explicitly perform this check because our datasource often doesn't give back immediately available images, even though we might have downloaded one already. - // Because we seed this call with bestImmediatelyAvailableImageFromDataSource, we must be careful not to trample an existing image. - if (image || imageIdentifierCount == 0) { - as_log_verbose(ASImageLoadingLog(), "[%p] loaded -> displaying (%@, %@)", self, imageIdentifier, image); - id previousIdentifier = self.loadedImageIdentifier; - UIImage *previousImage = self.image; - - self.loadedImageIdentifier = imageIdentifier; - [self _setImage:image]; - - if (_delegateFlags.updatedImage) { - [_delegate multiplexImageNode:self didUpdateImage:image withIdentifier:imageIdentifier fromImage:previousImage withIdentifier:previousIdentifier]; - } - - } - - // Load our next image, if we have one to load. - if ([self _nextImageIdentifierToDownload]) - [self _loadNextImage]; -} - -@end - -#if AS_USE_PHOTOS -@implementation NSURL (ASPhotosFrameworkURLs) - -+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options NS_RETURNS_RETAINED -{ - ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:assetLocalIdentifier]; - request.options = options; - request.contentMode = contentMode; - request.targetSize = targetSize; - return request.url; -} - -@end -#endif diff --git a/Source/ASNavigationController.h b/Source/ASNavigationController.h deleted file mode 100644 index 0d259b24aa..0000000000 --- a/Source/ASNavigationController.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// ASNavigationController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * ASNavigationController - * - * @discussion ASNavigationController is a drop in replacement for UINavigationController - * which improves memory efficiency by implementing the @c ASManagesChildVisibilityDepth protocol. - * You can use ASNavigationController with regular UIViewControllers, as well as ASDKViewControllers. - * It is safe to subclass or use even where AsyncDisplayKit is not adopted. - * - * @see ASManagesChildVisibilityDepth - */ -@interface ASNavigationController : UINavigationController - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASNavigationController.mm b/Source/ASNavigationController.mm deleted file mode 100644 index 2fbc4880c7..0000000000 --- a/Source/ASNavigationController.mm +++ /dev/null @@ -1,113 +0,0 @@ -// -// ASNavigationController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@implementation ASNavigationController -{ - BOOL _parentManagesVisibilityDepth; - NSInteger _visibilityDepth; -} - -ASVisibilityDidMoveToParentViewController; - -ASVisibilityViewWillAppear; - -ASVisibilityViewDidDisappearImplementation; - -ASVisibilitySetVisibilityDepth; - -ASVisibilityDepthImplementation; - -- (void)visibilityDepthDidChange -{ - for (UIViewController *viewController in self.viewControllers) { - if ([viewController conformsToProtocol:@protocol(ASVisibilityDepth)]) { - [(id )viewController visibilityDepthDidChange]; - } - } -} - -- (NSInteger)visibilityDepthOfChildViewController:(UIViewController *)childViewController -{ - NSUInteger viewControllerIndex = [self.viewControllers indexOfObjectIdenticalTo:childViewController]; - if (viewControllerIndex == NSNotFound) { - //If childViewController is not actually a child, return NSNotFound which is also a really large number. - return NSNotFound; - } - - if (viewControllerIndex == self.viewControllers.count - 1) { - //view controller is at the top, just return our own visibility depth. - return [self visibilityDepth]; - } else if (viewControllerIndex == 0) { - //view controller is the root view controller. Can be accessed by holding the back button. - return [self visibilityDepth] + 1; - } - - return [self visibilityDepth] + self.viewControllers.count - 1 - viewControllerIndex; -} - -#pragma mark - UIKit overrides - -- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated -{ - as_activity_create_for_scope("Pop multiple from ASNavigationController"); - NSArray *viewControllers = [super popToViewController:viewController animated:animated]; - os_log_info(ASNodeLog(), "Popped %@ to %@, removing %@", self, viewController, ASGetDescriptionValueString(viewControllers)); - - [self visibilityDepthDidChange]; - return viewControllers; -} - -- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated -{ - as_activity_create_for_scope("Pop to root of ASNavigationController"); - NSArray *viewControllers = [super popToRootViewControllerAnimated:animated]; - os_log_info(ASNodeLog(), "Popped view controllers %@ from %@", ASGetDescriptionValueString(viewControllers), self); - - [self visibilityDepthDidChange]; - return viewControllers; -} - -- (void)setViewControllers:(NSArray *)viewControllers -{ - // NOTE: As of now this method calls through to setViewControllers:animated: so no need to log/activity here. - - [super setViewControllers:viewControllers]; - [self visibilityDepthDidChange]; -} - -- (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated -{ - as_activity_create_for_scope("Set view controllers of ASNavigationController"); - os_log_info(ASNodeLog(), "Set view controllers of %@ to %@ animated: %d", self, ASGetDescriptionValueString(viewControllers), animated); - [super setViewControllers:viewControllers animated:animated]; - [self visibilityDepthDidChange]; -} - -- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated -{ - as_activity_create_for_scope("Push view controller on ASNavigationController"); - os_log_info(ASNodeLog(), "Pushing %@ onto %@", viewController, self); - [super pushViewController:viewController animated:animated]; - [self visibilityDepthDidChange]; -} - -- (UIViewController *)popViewControllerAnimated:(BOOL)animated -{ - as_activity_create_for_scope("Pop view controller from ASNavigationController"); - UIViewController *viewController = [super popViewControllerAnimated:animated]; - os_log_info(ASNodeLog(), "Popped %@ from %@", viewController, self); - [self visibilityDepthDidChange]; - return viewController; -} - -@end diff --git a/Source/ASNetworkImageNode.h b/Source/ASNetworkImageNode.h index fa07a3d49a..d4c7cc4a22 100644 --- a/Source/ASNetworkImageNode.h +++ b/Source/ASNetworkImageNode.h @@ -48,11 +48,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, weak) id delegate; -/** - * The delegate will receive callbacks on main thread. Default to YES. - */ -@property (class) BOOL useMainThreadDelegateCallbacks; - /** * The image to display. * @@ -80,66 +75,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, copy) NSURL *URL; -/** - * An array of URLs of increasing cost to download. - * - * @discussion By setting an array of URLs, the image property of this node will be managed internally. This means previously - * directly set images to the image property will be cleared out and replaced by the placeholder () image - * while loading and the final image after the new image data was downloaded and processed. - * - * @deprecated This API has been removed for now due to the increased complexity to the class that it brought. - * Please use .URL instead. - */ -@property (nullable, copy) NSArray *URLs ASDISPLAYNODE_DEPRECATED_MSG("Please use URL instead."); - -/** - * Download and display a new image. - * - * @param URL The URL of a new image to download and display. - * @param reset Whether to display a placeholder () while loading the new image. - * - * @discussion By setting an URL, the image property of this node will be managed internally. This means previously - * directly set images to the image property will be cleared out and replaced by the placeholder () image - * while loading and the final image after the new image data was downloaded and processed. - */ -- (void)setURL:(nullable NSURL *)URL resetToDefault:(BOOL)reset; - -/** - * If is a local file, set this property to YES to take advantage of UIKit's image caching. Defaults to YES. - */ -@property BOOL shouldCacheImage; - -/** - * If the downloader implements progressive image rendering and this value is YES progressive renders of the - * image will be displayed as the image downloads. Regardless of this properties value, progress renders will - * only occur when the node is visible. Defaults to YES. - */ -@property BOOL shouldRenderProgressImages; - -/** - * The image quality of the current image. - * - * If the URL is set, this is a number between 0 and 1 and can be used to track - * progressive progress. Calculated by dividing number of bytes / expected number of total bytes. - * This is zero until the first progressive render or the final display. - * - * If the URL is unset, this is 1 if defaultImage or image is set to non-nil. - * - */ -@property (readonly) CGFloat currentImageQuality; - -/** - * The currentImageQuality (value between 0 and 1) of the last image that completed displaying. - */ -@property (readonly) CGFloat renderedImageQuality; - -/** - * Download progress of the current image. - * When downloading a network image, this value would be updated to track download progress (value between 0 and 1) - * This is 1 if image load from cache or network successfully. - */ -@property (readonly) CGFloat downloadProgress; - @end diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 085fc063c1..041e1e260b 100644 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -16,14 +16,9 @@ #import #import #import -#import #import #import -#if AS_PIN_REMOTE_IMAGE -#import -#endif - @interface ASNetworkImageNode () { // Only access any of these while locked. @@ -37,10 +32,6 @@ @interface ASNetworkImageNode () // The download identifier that we have set a progress block on, if any. id _downloadIdentifierForProgressBlock; - CGFloat _currentImageQuality; - CGFloat _renderedImageQuality; - CGFloat _downloadProgress; - // Immutable and set on init only. We don't need to lock in this case. __weak id _downloader; @@ -62,7 +53,6 @@ @interface ASNetworkImageNode () unsigned int downloaderImplementsSetProgress:1; unsigned int downloaderImplementsSetPriority:1; - unsigned int downloaderImplementsAnimatedImage:1; unsigned int downloaderImplementsCancelWithResume:1; unsigned int downloaderImplementsDownloadWithPriority:1; @@ -71,8 +61,6 @@ @interface ASNetworkImageNode () unsigned int imageLoaded:1; unsigned int imageWasSetExternally:1; - unsigned int shouldRenderProgressImages:1; - unsigned int shouldCacheImage:1; } _networkImageNodeFlags; } @@ -80,8 +68,6 @@ @interface ASNetworkImageNode () @implementation ASNetworkImageNode -static std::atomic_bool _useMainThreadDelegateCallbacks(true); - @dynamic image; - (instancetype)initWithCache:(id)cache downloader:(id)downloader @@ -94,15 +80,12 @@ - (instancetype)initWithCache:(id)cache downloader:(id)cache downloader:(id *)URLs -{ - [self setURL:[URLs firstObject]]; -} - -// Deprecated -- (NSArray *)URLs -{ - return @[self.URL]; -} - - (void)setURL:(NSURL *)URL -{ - [self setURL:URL resetToDefault:YES]; -} - -- (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset { { ASLockScopeSelf(); @@ -205,18 +161,14 @@ - (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset [self _locked_cancelImageDownloadWithResumePossibility:NO]; - [self _setDownloadProgress:0.0]; - _networkImageNodeFlags.imageLoaded = NO; _URL = URL; // If URL is nil and URL was not equal to _URL (checked at the top), then we previously had a URL but it's been nil'd out. BOOL hadURL = (URL == nil); - if (reset || hadURL) { - [self _setCurrentImageQuality:(hadURL ? 0.0 : 1.0)]; + if (hadURL) { [self _locked__setImage:_defaultImage]; - [self _locked_setAnimatedImage:nil]; } } @@ -244,7 +196,6 @@ - (void)_locked_setDefaultImage:(UIImage *)defaultImage _defaultImage = defaultImage; if (!_networkImageNodeFlags.imageLoaded) { - [self _setCurrentImageQuality:((_URL == nil) ? 0.0 : 1.0)]; [self _locked__setImage:defaultImage]; } } @@ -254,66 +205,6 @@ - (UIImage *)defaultImage return ASLockedSelf(_defaultImage); } -- (void)setCurrentImageQuality:(CGFloat)currentImageQuality -{ - ASLockScopeSelf(); - _currentImageQuality = currentImageQuality; -} - -- (CGFloat)currentImageQuality -{ - return ASLockedSelf(_currentImageQuality); -} - -/** - * Always use these methods internally to update the current image quality - * We want to maintain the order that currentImageQuality is set regardless of the calling thread, - * so we always have to dispatch to the main thread to ensure that we queue the operations in the correct order. - * (see comment in displayDidFinish) - */ -- (void)_setCurrentImageQuality:(CGFloat)imageQuality -{ - dispatch_async(dispatch_get_main_queue(), ^{ - self.currentImageQuality = imageQuality; - }); -} - -- (void)setDownloadProgress:(CGFloat)downloadProgress -{ - ASLockScopeSelf(); - _downloadProgress = downloadProgress; -} - -- (CGFloat)downloadProgress -{ - return ASLockedSelf(_downloadProgress); -} - -/** - * Always use these methods internally to update the current download progress - * We want to maintain the order that downloadProgress is set regardless of the calling thread, - * so we always have to dispatch to the main thread to ensure that we queue the operations in the correct order. - * (see comment in displayDidFinish) - */ -- (void)_setDownloadProgress:(CGFloat)downloadProgress -{ - dispatch_async(dispatch_get_main_queue(), ^{ - self.downloadProgress = downloadProgress; - }); -} - -- (void)setRenderedImageQuality:(CGFloat)renderedImageQuality -{ - ASLockScopeSelf(); - _renderedImageQuality = renderedImageQuality; -} - -- (CGFloat)renderedImageQuality -{ - ASLockScopeSelf(); - return _renderedImageQuality; -} - - (void)setDelegate:(id)delegate { ASLockScopeSelf(); @@ -337,34 +228,10 @@ - (void)setDelegate:(id)delegate return _delegate; } -- (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages -{ - if (ASLockedSelfCompareAssign(_networkImageNodeFlags.shouldRenderProgressImages, shouldRenderProgressImages)) { - [self _updateProgressImageBlockOnDownloaderIfNeeded]; - } -} - -- (BOOL)shouldRenderProgressImages -{ - ASLockScopeSelf(); - return _networkImageNodeFlags.shouldRenderProgressImages; -} - -- (void)setShouldCacheImage:(BOOL)shouldCacheImage -{ - ASLockedSelfCompareAssign(_networkImageNodeFlags.shouldCacheImage, shouldCacheImage); -} - -- (BOOL)shouldCacheImage -{ - ASLockScopeSelf(); - return _networkImageNodeFlags.shouldCacheImage; -} - - (BOOL)placeholderShouldPersist { ASLockScopeSelf(); - return (self.image == nil && self.animatedImage == nil && _URL != nil); + return (self.image == nil && _URL != nil); } /* displayWillStartAsynchronously: in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary @@ -391,8 +258,6 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously if (_networkImageNodeFlags.imageLoaded == NO && url && _downloadIdentifier == nil) { UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image]; if (result) { - [self _setCurrentImageQuality:1.0]; - [self _setDownloadProgress:1.0]; [self _locked__setImage:result]; _networkImageNodeFlags.imageLoaded = YES; @@ -456,16 +321,6 @@ - (void)didEnterPreloadState [self _lazilyLoadImageIfNecessary]; } -+ (void)setUseMainThreadDelegateCallbacks:(BOOL)useMainThreadDelegateCallbacks -{ - _useMainThreadDelegateCallbacks = useMainThreadDelegateCallbacks; -} - -+ (BOOL)useMainThreadDelegateCallbacks -{ - return _useMainThreadDelegateCallbacks; -} - #pragma mark - Progress - (void)_updateDownloadedProgress:(CGFloat)progress @@ -476,7 +331,6 @@ - (void)_updateDownloadedProgress:(CGFloat)progress if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { return; } - [self _setDownloadProgress:progress]; } - (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress downloadIdentifier:(nullable id)downloadIdentifier @@ -489,7 +343,6 @@ - (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress } as_log_verbose(ASImageLoadingLog(), "Received progress image for %@ q: %.2g id: %@", self, progress, progressImage); - [self _setCurrentImageQuality:progress]; [self _locked__setImage:progressImage]; } @@ -514,7 +367,7 @@ - (void)_updateProgressImageBlockOnDownloaderIfNeeded // Read state. [self lock]; - BOOL shouldRender = _networkImageNodeFlags.shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState); + BOOL shouldRender = ASInterfaceStateIncludesVisible(_interfaceState); id oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock; id newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil; BOOL clearAndReattempt = NO; @@ -573,9 +426,6 @@ - (void)_locked_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResu [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; - [self _locked_setAnimatedImage:nil]; - [self _setCurrentImageQuality:0.0]; - [self _setDownloadProgress:0.0]; [self _locked__setImage:_defaultImage]; _networkImageNodeFlags.imageLoaded = NO; @@ -665,7 +515,7 @@ - (void)_downloadImageWithCompletion:(void (^)(id ima /* Kick off a download with default priority. The actual "default" value is decided by the downloader. - ASBasicImageDownloader and ASPINRemoteImageDownloader both use ASImageDownloaderPriorityImminent + ASBasicImageDownloader use ASImageDownloaderPriorityImminent which is mapped to NSURLSessionTaskPriorityDefault. This means that preload and display nodes use the same priority @@ -733,48 +583,10 @@ - (void)_lazilyLoadImageIfNecessary return; } - if (self->_networkImageNodeFlags.shouldCacheImage) { - [self _locked__setImage:[UIImage imageNamed:URL.path.lastPathComponent]]; - } else { - // First try to load the path directly, for efficiency assuming a developer who - // doesn't want caching is trying to be as minimal as possible. - auto nonAnimatedImage = [[UIImage alloc] initWithContentsOfFile:URL.path]; - if (nonAnimatedImage == nil) { - // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the - // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. - NSString *filename = [[NSBundle mainBundle] pathForResource:URL.path.lastPathComponent ofType:nil]; - if (filename != nil) { - nonAnimatedImage = [[UIImage alloc] initWithContentsOfFile:filename]; - // Update URL to point to newly-resolved file URL for animated image load. - URL = nonAnimatedImage ? [NSURL URLWithString:filename] : URL; - } - } - - // If the file may be an animated gif and then created an animated image. - id animatedImage = nil; - if (self->_networkImageNodeFlags.downloaderImplementsAnimatedImage) { - const auto data = [[NSData alloc] initWithContentsOfURL:URL]; - if (data != nil) { - animatedImage = [self->_downloader animatedImageWithData:data]; - - if ([animatedImage respondsToSelector:@selector(isDataSupported:)] && [animatedImage isDataSupported:data] == NO) { - animatedImage = nil; - } - } - } - - if (animatedImage != nil) { - [self _locked_setAnimatedImage:animatedImage]; - } else { - [self _locked__setImage:nonAnimatedImage]; - } - } + [self _locked__setImage:[UIImage imageNamed:URL.path.lastPathComponent]]; self->_networkImageNodeFlags.imageLoaded = YES; - [self _setCurrentImageQuality:1.0]; - [self _setDownloadProgress:1.0]; - if (self->_networkImageNodeFlags.delegateDidLoadImageWithInfo) { ASUnlockScope(self); const auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:ASNetworkImageSourceFileURL downloadIdentifier:nil userInfo:nil]; @@ -812,16 +624,8 @@ - (void)_lazilyLoadImageIfNecessary UIImage *newImage; if (imageContainer != nil) { - [strongSelf _setCurrentImageQuality:1.0]; - [strongSelf _setDownloadProgress:1.0]; - NSData *animatedImageData = [imageContainer asdk_animatedImageData]; - if (animatedImageData && strongSelf->_networkImageNodeFlags.downloaderImplementsAnimatedImage) { - id animatedImage = [strongSelf->_downloader animatedImageWithData:animatedImageData]; - [strongSelf _locked_setAnimatedImage:animatedImage]; - } else { - newImage = [imageContainer asdk_image]; - [strongSelf _locked__setImage:newImage]; - } + newImage = [imageContainer asdk_image]; + [strongSelf _locked__setImage:newImage]; strongSelf->_networkImageNodeFlags.imageLoaded = YES; } @@ -848,15 +652,11 @@ - (void)_lazilyLoadImageIfNecessary } if (calloutBlock) { - if (ASNetworkImageNode.useMainThreadDelegateCallbacks) { - ASPerformBlockOnMainThread(^{ - if (auto strongSelf = weakSelf) { - calloutBlock(strongSelf); - } - }); - } else { - calloutBlock(strongSelf); - } + ASPerformBlockOnMainThread(^{ + if (auto strongSelf = weakSelf) { + calloutBlock(strongSelf); + } + }); } }); }; @@ -874,7 +674,7 @@ - (void)_lazilyLoadImageIfNecessary return; } - if ([imageContainer asdk_image] == nil && [imageContainer asdk_animatedImageData] == nil && self->_downloader != nil) { + if ([imageContainer asdk_image] == nil && self->_downloader != nil) { if (delegateDidFailToLoadImageFromCache) { [delegate imageNodeDidFailToLoadImageFromCache:self]; } @@ -929,8 +729,6 @@ - (void)displayDidFinish but before the displayDidFinish of the previous display pass is called. In this situation, displayDidFinish would be called and we would set _renderedImageQuality to the new _currentImageQuality, but the actual quality of the rendered image should be the previous value stored in _currentImageQuality. */ - - _renderedImageQuality = _currentImageQuality; // Assign the delegate to be used delegate = _delegate; diff --git a/Source/ASNodeController+Beta.h b/Source/ASNodeController+Beta.h deleted file mode 100644 index 6501a114ac..0000000000 --- a/Source/ASNodeController+Beta.h +++ /dev/null @@ -1,62 +0,0 @@ -// -// ASNodeController+Beta.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import // for ASInterfaceState protocol - -/* ASNodeController is currently beta and open to change in the future */ -@interface ASNodeController<__covariant DisplayNodeType : ASDisplayNode *> - : NSObject - -@property (nonatomic, strong /* may be weak! */) DisplayNodeType node; - -// Until an ASNodeController can be provided in place of an ASCellNode, some apps may prefer to have -// nodes keep their controllers alive (and a weak reference from controller to node) - -@property (nonatomic) BOOL shouldInvertStrongReference; - -- (void)loadNode; - -// for descriptions see definition -- (void)nodeDidLoad ASDISPLAYNODE_REQUIRES_SUPER; -- (void)nodeDidLayout ASDISPLAYNODE_REQUIRES_SUPER; - -// This is only called during Yoga-driven layouts. -- (void)nodeWillCalculateLayout:(ASSizeRange)constrainedSize ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER; -- (void)didExitVisibleState ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)didEnterDisplayState ASDISPLAYNODE_REQUIRES_SUPER; -- (void)didExitDisplayState ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)didEnterPreloadState ASDISPLAYNODE_REQUIRES_SUPER; -- (void)didExitPreloadState ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)interfaceStateDidChange:(ASInterfaceState)newState - fromState:(ASInterfaceState)oldState ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)hierarchyDisplayDidFinish ASDISPLAYNODE_REQUIRES_SUPER; - -- (void)didEnterHierarchy ASDISPLAYNODE_REQUIRES_SUPER; -- (void)didExitHierarchy ASDISPLAYNODE_REQUIRES_SUPER; - -/** - * @discussion Attempts (via ASLockSequence, a backing-off spinlock similar to - * std::lock()) to lock both the node and its ASNodeController, if one exists. - */ -- (ASLockSet)lockPair; - -@end - -@interface ASDisplayNode (ASNodeController) - -@property(nonatomic, readonly) ASNodeController *nodeController; - -@end diff --git a/Source/ASNodeController+Beta.mm b/Source/ASNodeController+Beta.mm deleted file mode 100644 index 0245a7d4d2..0000000000 --- a/Source/ASNodeController+Beta.mm +++ /dev/null @@ -1,136 +0,0 @@ -// -// ASNodeController+Beta.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#define _node (_shouldInvertStrongReference ? _weakNode : _strongNode) - -@implementation ASNodeController -{ - ASDisplayNode *_strongNode; - __weak ASDisplayNode *_weakNode; - AS::RecursiveMutex __instanceLock__; -} - -- (void)loadNode -{ - ASLockScopeSelf(); - self.node = [[ASDisplayNode alloc] init]; -} - -- (ASDisplayNode *)node -{ - ASLockScopeSelf(); - if (_node == nil) { - [self loadNode]; - } - return _node; -} - -- (void)setupReferencesWithNode:(ASDisplayNode *)node -{ - ASLockScopeSelf(); - if (_shouldInvertStrongReference) { - // The node should own the controller; weak reference from controller to node. - _weakNode = node; - _strongNode = nil; - } else { - // The controller should own the node; weak reference from node to controller. - _strongNode = node; - _weakNode = nil; - } - - [node __setNodeController:self]; -} - -- (void)setNode:(ASDisplayNode *)node -{ - ASLockScopeSelf(); - if (node == _node) { - return; - } - [self setupReferencesWithNode:node]; - [node addInterfaceStateDelegate:self]; -} - -- (void)setShouldInvertStrongReference:(BOOL)shouldInvertStrongReference -{ - ASLockScopeSelf(); - if (_shouldInvertStrongReference != shouldInvertStrongReference) { - // Because the BOOL controls which ivar we access, get the node before toggling. - ASDisplayNode *node = _node; - _shouldInvertStrongReference = shouldInvertStrongReference; - [self setupReferencesWithNode:node]; - } -} - -// subclass overrides -- (void)nodeDidLoad {} -- (void)nodeDidLayout {} -- (void)nodeWillCalculateLayout:(ASSizeRange)constrainedSize {} - -- (void)didEnterVisibleState {} -- (void)didExitVisibleState {} - -- (void)didEnterDisplayState {} -- (void)didExitDisplayState {} - -- (void)didEnterPreloadState {} -- (void)didExitPreloadState {} - -- (void)interfaceStateDidChange:(ASInterfaceState)newState - fromState:(ASInterfaceState)oldState {} - -- (void)hierarchyDisplayDidFinish {} - -- (void)didEnterHierarchy {} -- (void)didExitHierarchy {} - -- (ASLockSet)lockPair { - ASLockSet lockSet = ASLockSequence(^BOOL(ASAddLockBlock addLock) { - if (!addLock(_node)) { - return NO; - } - if (!addLock(self)) { - return NO; - } - return YES; - }); - - return lockSet; -} - -#pragma mark NSLocking - -- (void)lock -{ - __instanceLock__.lock(); -} - -- (void)unlock -{ - __instanceLock__.unlock(); -} - -- (BOOL)tryLock -{ - return __instanceLock__.try_lock(); -} - -@end - -@implementation ASDisplayNode (ASNodeController) - -- (ASNodeController *)nodeController -{ - return _weakNodeController ?: _strongNodeController; -} - -@end diff --git a/Source/ASPagerFlowLayout.h b/Source/ASPagerFlowLayout.h deleted file mode 100644 index 17c61d6c47..0000000000 --- a/Source/ASPagerFlowLayout.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// ASPagerFlowLayout.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASPagerFlowLayout : UICollectionViewFlowLayout - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASPagerFlowLayout.mm b/Source/ASPagerFlowLayout.mm deleted file mode 100644 index 301cea244b..0000000000 --- a/Source/ASPagerFlowLayout.mm +++ /dev/null @@ -1,110 +0,0 @@ -// -// ASPagerFlowLayout.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@interface ASPagerFlowLayout () { - __weak ASCellNode *_currentCellNode; -} - -@end - -//TODO make this an ASCollectionViewLayout -@implementation ASPagerFlowLayout - -- (ASCollectionView *)asCollectionView -{ - // Dynamic cast is too slow and not worth it. - return (ASCollectionView *)self.collectionView; -} - -- (void)prepareLayout -{ - [super prepareLayout]; - if (_currentCellNode == nil) { - [self _updateCurrentNode]; - } -} - -- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset -{ - // Don't mess around if the user is interacting with the page node. Although if just a rotation happened we should - // try to use the current index path to not end up setting the target content offset to something in between pages - if (!self.collectionView.decelerating && !self.collectionView.tracking) { - NSIndexPath *indexPath = [self.asCollectionView indexPathForNode:_currentCellNode]; - if (indexPath) { - return [self _targetContentOffsetForItemAtIndexPath:indexPath proposedContentOffset:proposedContentOffset]; - } - } - - return [super targetContentOffsetForProposedContentOffset:proposedContentOffset]; -} - -- (CGPoint)_targetContentOffsetForItemAtIndexPath:(NSIndexPath *)indexPath proposedContentOffset:(CGPoint)proposedContentOffset -{ - if ([self _dataSourceIsEmpty]) { - return proposedContentOffset; - } - - UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; - if (attributes == nil) { - return proposedContentOffset; - } - - CGFloat xOffset = (CGRectGetWidth(self.collectionView.bounds) - CGRectGetWidth(attributes.frame)) / 2.0; - return CGPointMake(attributes.frame.origin.x - xOffset, proposedContentOffset.y); -} - -- (BOOL)_dataSourceIsEmpty -{ - return ([self.collectionView numberOfSections] == 0 || - [self.collectionView numberOfItemsInSection:0] == 0); -} - -- (void)_updateCurrentNode -{ - // Never change node during an animated bounds change (rotation) - // NOTE! Listening for -prepareForAnimatedBoundsChange and -finalizeAnimatedBoundsChange - // isn't sufficient here! It's broken! - NSArray *animKeys = self.collectionView.layer.animationKeys; - for (NSString *key in animKeys) { - if ([key hasPrefix:@"bounds"]) { - return; - } - } - - CGRect bounds = self.collectionView.bounds; - CGRect rect = CGRectMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds), 1, 1); - - NSIndexPath *indexPath = [self layoutAttributesForElementsInRect:rect].firstObject.indexPath; - if (indexPath) { - ASCellNode *node = [self.asCollectionView nodeForItemAtIndexPath:indexPath]; - if (node) { - _currentCellNode = node; - } - } -} - -- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds -{ - [self _updateCurrentNode]; - return [super shouldInvalidateLayoutForBoundsChange:newBounds]; -} - -- (UICollectionViewLayoutInvalidationContext *)invalidationContextForBoundsChange:(CGRect)newBounds -{ - UICollectionViewFlowLayoutInvalidationContext *ctx = (UICollectionViewFlowLayoutInvalidationContext *)[super invalidationContextForBoundsChange:newBounds]; - ctx.invalidateFlowLayoutDelegateMetrics = YES; - ctx.invalidateFlowLayoutAttributes = YES; - return ctx; -} - -@end diff --git a/Source/ASPagerNode+Beta.h b/Source/ASPagerNode+Beta.h deleted file mode 100644 index a768f6be3f..0000000000 --- a/Source/ASPagerNode+Beta.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// ASPagerNode+Beta.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASPagerNode (Beta) - -- (instancetype)initUsingAsyncCollectionLayout; - -@end diff --git a/Source/ASPagerNode.h b/Source/ASPagerNode.h deleted file mode 100644 index 9fb99775f5..0000000000 --- a/Source/ASPagerNode.h +++ /dev/null @@ -1,133 +0,0 @@ -// -// ASPagerNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@class ASPagerNode; -@class ASPagerFlowLayout; - -NS_ASSUME_NONNULL_BEGIN - -#define ASPagerNodeDataSource ASPagerDataSource -@protocol ASPagerDataSource - -/** - * This method replaces -collectionView:numberOfItemsInSection: - * - * @param pagerNode The sender. - * @return The total number of pages that can display in the pagerNode. - */ -- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode; - -@optional - -/** - * This method replaces -collectionView:nodeForItemAtIndexPath: - * - * @param pagerNode The sender. - * @param index The index of the requested node. - * @return a node for display at this index. This will be called on the main thread and should - * not implement reuse (it will be called once per row). Unlike UICollectionView's version, - * this method is not called when the row is about to display. - */ -- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index; - -/** - * This method replaces -collectionView:nodeBlockForItemAtIndexPath: - * This method takes precedence over pagerNode:nodeAtIndex: if implemented. - * - * @param pagerNode The sender. - * @param index The index of the requested node. - * @return a block that creates the node for display at this index. - * Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). - */ -- (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; - -@end - -@protocol ASPagerDelegate -@end - -/** - * A horizontal, paging collection node. - */ -@interface ASPagerNode : ASCollectionNode - -/** - * Configures a default horizontal, paging flow layout with 0 inter-item spacing. - */ -- (instancetype)init; - -/** - * Initializer with custom-configured flow layout properties. - * - * NOTE: The flow layout must have a horizontal scroll direction. - */ -- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; - -/** - * Data Source is required, and uses a different protocol from ASCollectionNode. - */ -- (void)setDataSource:(nullable id )dataSource; -- (nullable id )dataSource AS_WARN_UNUSED_RESULT; - -/** - * Delegate is optional. - * This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay... - */ -- (void)setDelegate:(nullable id )delegate; -- (nullable id )delegate AS_WARN_UNUSED_RESULT; - -/** - * The underlying ASCollectionView object. - */ -@property (readonly) ASCollectionView *view; - -/** - * Returns the current page index. Main thread only. - */ -@property (nonatomic, readonly) NSInteger currentPageIndex; - -/** - * Scroll the contents of the receiver to ensure that the page is visible - */ -- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated; - -/** - * Returns the node for the passed page index - */ -- (ASCellNode *)nodeForPageAtIndex:(NSInteger)index AS_WARN_UNUSED_RESULT; - -/** - * Returns the index of the page for the cell passed or NSNotFound - */ -- (NSInteger)indexOfPageWithNode:(ASCellNode *)node; - -/** - * Tells the pager node to allow its view controller to automatically adjust its content insets. - * - * @see UIViewController.automaticallyAdjustsScrollViewInsets - * - * @discussion ASPagerNode should usually not have its content insets automatically adjusted - * because it scrolls horizontally, and flow layout will log errors because the pages - * do not fit between the top & bottom insets of the collection view. - * - * The default value is NO, which means that ASPagerNode expects that its view controller will - * have automaticallyAdjustsScrollViewInsets=NO. - * - * If this property is NO, but your view controller has automaticallyAdjustsScrollViewInsets=YES, - * the pager node will set the property on the view controller to NO and log a warning message. In the future, - * the pager node will just log the warning, and you'll need to configure your view controller on your own. - */ -@property (nonatomic) BOOL allowsAutomaticInsetsAdjustment; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASPagerNode.mm b/Source/ASPagerNode.mm deleted file mode 100644 index 7ad8f1ad04..0000000000 --- a/Source/ASPagerNode.mm +++ /dev/null @@ -1,230 +0,0 @@ -// -// ASPagerNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import - -@interface ASPagerNode () -{ - __weak id _pagerDataSource; - ASPagerNodeProxy *_proxyDataSource; - struct { - unsigned nodeBlockAtIndex:1; - unsigned nodeAtIndex:1; - } _pagerDataSourceFlags; - BOOL _allowsAutomaticInsetsAdjustment; - - __weak id _pagerDelegate; - ASPagerNodeProxy *_proxyDelegate; -} - -@end - -@implementation ASPagerNode - -@dynamic view, delegate, dataSource; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - ASPagerFlowLayout *flowLayout = [[ASPagerFlowLayout alloc] init]; - flowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; - flowLayout.minimumInteritemSpacing = 0; - flowLayout.minimumLineSpacing = 0; - - return [self initWithCollectionViewLayout:flowLayout]; -} - -- (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; -{ - ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout."); - ASDisplayNodeAssertTrue(flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal); - self = [super initWithCollectionViewLayout:flowLayout]; - return self; -} - -- (instancetype)initUsingAsyncCollectionLayout -{ - ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionHorizontalDirections]; - self = [super initWithLayoutDelegate:layoutDelegate layoutFacilitator:nil]; - if (self) { - layoutDelegate.propertiesProvider = self; - } - return self; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - ASCollectionView *cv = self.view; - cv.asyncDataSource = (id)_proxyDataSource ?: self; - cv.asyncDelegate = (id)_proxyDelegate ?: self; -#if TARGET_OS_IOS - cv.pagingEnabled = YES; - cv.scrollsToTop = NO; -#endif - cv.allowsSelection = NO; - cv.showsVerticalScrollIndicator = NO; - cv.showsHorizontalScrollIndicator = NO; - - ASRangeTuningParameters minimumRenderParams = { .leadingBufferScreenfuls = 0.0, .trailingBufferScreenfuls = 0.0 }; - ASRangeTuningParameters minimumPreloadParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; - [self setTuningParameters:minimumRenderParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypeDisplay]; - [self setTuningParameters:minimumPreloadParams forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; - - ASRangeTuningParameters fullRenderParams = { .leadingBufferScreenfuls = 1.0, .trailingBufferScreenfuls = 1.0 }; - ASRangeTuningParameters fullPreloadParams = { .leadingBufferScreenfuls = 2.0, .trailingBufferScreenfuls = 2.0 }; - [self setTuningParameters:fullRenderParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypeDisplay]; - [self setTuningParameters:fullPreloadParams forRangeMode:ASLayoutRangeModeFull rangeType:ASLayoutRangeTypePreload]; -} - -#pragma mark - Getters / Setters - -- (NSInteger)currentPageIndex -{ - return (self.view.contentOffset.x / [self pageSize].width); -} - -- (CGSize)pageSize -{ - UIEdgeInsets contentInset = self.contentInset; - CGSize pageSize = self.bounds.size; - pageSize.height -= (contentInset.top + contentInset.bottom); - return pageSize; -} - -#pragma mark - Helpers - -- (void)scrollToPageAtIndex:(NSInteger)index animated:(BOOL)animated -{ - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0]; - [self scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionLeft animated:animated]; -} - -- (ASCellNode *)nodeForPageAtIndex:(NSInteger)index -{ - return [self nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:index inSection:0]]; -} - -- (NSInteger)indexOfPageWithNode:(ASCellNode *)node -{ - NSIndexPath *indexPath = [self indexPathForNode:node]; - if (!indexPath) { - return NSNotFound; - } - return indexPath.row; -} - -#pragma mark - ASCollectionGalleryLayoutPropertiesProviding - -- (CGSize)galleryLayoutDelegate:(nonnull ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(nonnull ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - return [self pageSize]; -} - -#pragma mark - ASCollectionDataSource - -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath -{ - if (_pagerDataSourceFlags.nodeBlockAtIndex) { - return [_pagerDataSource pagerNode:self nodeBlockAtIndex:indexPath.item]; - } else if (_pagerDataSourceFlags.nodeAtIndex) { - ASCellNode *node = [_pagerDataSource pagerNode:self nodeAtIndex:indexPath.item]; - return ^{ return node; }; - } else { - ASDisplayNodeFailAssert(@"Pager data source must implement either %@ or %@. Data source: %@", NSStringFromSelector(@selector(pagerNode:nodeBlockAtIndex:)), NSStringFromSelector(@selector(pagerNode:nodeAtIndex:)), _pagerDataSource); - return ^{ - return [[ASCellNode alloc] init]; - }; - } -} - -- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section -{ - ASDisplayNodeAssert(_pagerDataSource != nil, @"ASPagerNode must have a data source to load nodes to display"); - return [_pagerDataSource numberOfPagesInPagerNode:self]; -} - -#pragma mark - ASCollectionDelegate - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - return ASSizeRangeMake([self pageSize]); -} - -#pragma mark - Data Source Proxy - -- (id )dataSource -{ - return _pagerDataSource; -} - -- (void)setDataSource:(id )dataSource -{ - if (dataSource != _pagerDataSource) { - _pagerDataSource = dataSource; - - if (dataSource == nil) { - memset(&_pagerDataSourceFlags, 0, sizeof(_pagerDataSourceFlags)); - } else { - _pagerDataSourceFlags.nodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]; - _pagerDataSourceFlags.nodeAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]; - } - - _proxyDataSource = dataSource ? [[ASPagerNodeProxy alloc] initWithTarget:dataSource interceptor:self] : nil; - - super.dataSource = (id )_proxyDataSource; - } -} - -- (void)setDelegate:(id)delegate -{ - if (delegate != _pagerDelegate) { - _pagerDelegate = delegate; - _proxyDelegate = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil; - super.delegate = (id )_proxyDelegate; - } -} - -- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy -{ - [self setDataSource:nil]; - [self setDelegate:nil]; -} - -- (void)didEnterHierarchy -{ - [super didEnterHierarchy]; - - // Check that our view controller does not automatically set our content insets - // In every use case I can imagine, the pager is not hosted inside a range-managed node. - if (_allowsAutomaticInsetsAdjustment == NO) { - UIViewController *vc = [self.view asdk_associatedViewController]; - if (vc.automaticallyAdjustsScrollViewInsets) { - NSLog(@"AsyncDisplayKit: ASPagerNode is setting automaticallyAdjustsScrollViewInsets=NO on its owning view controller %@. This automatic behavior will be disabled in the future. Set allowsAutomaticInsetsAdjustment=YES on the pager node to suppress this behavior.", vc); - vc.automaticallyAdjustsScrollViewInsets = NO; - } - } -} - -@end diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index 8c917b07af..62fb8da816 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -8,7 +8,6 @@ // #import -#import #import #import #import @@ -424,7 +423,7 @@ - (BOOL)isEmpty - (BOOL)isEnabled { - return ASActivateExperimentalFeature(ASExperimentalInterfaceStateCoalescing); + return false; } @end diff --git a/Source/ASScrollNode.mm b/Source/ASScrollNode.mm index bb9c826d8c..99efa15845 100644 --- a/Source/ASScrollNode.mm +++ b/Source/ASScrollNode.mm @@ -15,7 +15,6 @@ #import #import #import -#import @interface ASScrollView : UIScrollView @end @@ -81,7 +80,7 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize restrictedToSize:(ASLayoutElementSize)size relativeToParentSize:(CGSize)parentSize { - ASScopedLockSelfOrToRoot(); + ASLockScopeSelf(); ASSizeRange contentConstrainedSize = constrainedSize; if (ASScrollDirectionContainsVerticalDirection(_scrollableDirections)) { diff --git a/Source/ASSectionController.h b/Source/ASSectionController.h deleted file mode 100644 index 6807596138..0000000000 --- a/Source/ASSectionController.h +++ /dev/null @@ -1,86 +0,0 @@ -// -// ASSectionController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASBatchContext; - -/** - * A protocol that your section controllers should conform to, in order to be used with Texture. - * - * @note Your supplementary view source should conform to @c ASSupplementaryNodeSource. - */ -@protocol ASSectionController - -@optional - -/** - * A method to provide the node block for the item at the given index. - * The node block you return will be run asynchronously off the main thread, - * so it's important to retrieve any objects from your section _outside_ the block - * because by the time the block is run, the array may have changed. - * - * @param index The index of the item. - * @return A block to be run concurrently to build the node for this item. - * @see collectionNode:nodeBlockForItemAtIndexPath: - */ -- (ASCellNodeBlock)nodeBlockForItemAtIndex:(NSInteger)index; - -/** - * Similar to -collectionView:cellForItemAtIndexPath:. - * - * Note: only called if nodeBlockForItemAtIndex: returns nil. - * - * @param index The index of the item. - * - * @return A node to display for the given item. This will be called on the main thread and should - * not implement reuse (it will be called once per item). Unlike UICollectionView's version, - * this method is not called when the item is about to display. - */ -- (ASCellNode *)nodeForItemAtIndex:(NSInteger)index; - -/** - * Asks the section controller whether it should batch fetch because the user is - * near the end of the current data set. - * - * @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of - * objects that can be fetched or no network connection. - * - * If not implemented, the assumed return value is @c YES. - */ -- (BOOL)shouldBatchFetch; - -/** - * Asks the section controller to begin fetching more content (tail loading) because - * the user is near the end of the current data set. - * - * @param context A context object that must be notified when the batch fetch is completed. - * - * @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future - * notifications to do batch fetches. This method is called on a background queue. - */ -- (void)beginBatchFetchWithContext:(ASBatchContext *)context; - -/** - * A method to provide the size range used for measuring the item - * at the given index. - * - * @param index The index of the item. - * @return A size range used for asynchronously measuring the node at this index. - * @see collectionNode:constrainedSizeForItemAtIndexPath: - */ -- (ASSizeRange)sizeRangeForItemAtIndex:(NSInteger)index; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASSupplementaryNodeSource.h b/Source/ASSupplementaryNodeSource.h deleted file mode 100644 index 20bedddeec..0000000000 --- a/Source/ASSupplementaryNodeSource.h +++ /dev/null @@ -1,51 +0,0 @@ -// -// ASSupplementaryNodeSource.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASSupplementaryNodeSource - -@optional - -/** - * A method to provide the node-block for the supplementary element. - * - * @param elementKind The kind of supplementary element. - * @param index The index of the item. - * @return A node block for the supplementary element. - * @see collectionNode:nodeForSupplementaryElementOfKind:atIndexPath: - */ -- (ASCellNodeBlock)nodeBlockForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index; - -/** - * Asks the controller to provide a node to display for the given supplementary element. - * - * @param kind The kind of supplementary element. - * @param index The index of the item. - */ -- (ASCellNode *)nodeForSupplementaryElementOfKind:(NSString *)kind atIndex:(NSInteger)index; - -/** - * A method to provide the size range used for measuring the supplementary - * element of the given kind at the given index. - * - * @param elementKind The kind of supplementary element. - * @param index The index of the item. - * @return A size range used for asynchronously measuring the node. - * @see collectionNode:constrainedSizeForSupplementaryElementOfKind:atIndexPath: - */ -- (ASSizeRange)sizeRangeForSupplementaryElementOfKind:(NSString *)elementKind atIndex:(NSInteger)index; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASTabBarController.h b/Source/ASTabBarController.h deleted file mode 100644 index faa67a041c..0000000000 --- a/Source/ASTabBarController.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// ASTabBarController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * ASTabBarController - * - * @discussion ASTabBarController is a drop in replacement for UITabBarController - * which implements the memory efficiency improving @c ASManagesChildVisibilityDepth protocol. - * - * @see ASManagesChildVisibilityDepth - */ -@interface ASTabBarController : UITabBarController - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/ASTabBarController.mm b/Source/ASTabBarController.mm deleted file mode 100644 index 63867f1a42..0000000000 --- a/Source/ASTabBarController.mm +++ /dev/null @@ -1,84 +0,0 @@ -// -// ASTabBarController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@implementation ASTabBarController -{ - BOOL _parentManagesVisibilityDepth; - NSInteger _visibilityDepth; -} - -ASVisibilityDidMoveToParentViewController; - -ASVisibilityViewWillAppear; - -ASVisibilityViewDidDisappearImplementation; - -ASVisibilitySetVisibilityDepth; - -ASVisibilityDepthImplementation; - -- (void)visibilityDepthDidChange -{ - for (UIViewController *viewController in self.viewControllers) { - if ([viewController conformsToProtocol:@protocol(ASVisibilityDepth)]) { - [(id )viewController visibilityDepthDidChange]; - } - } -} - -- (NSInteger)visibilityDepthOfChildViewController:(UIViewController *)childViewController -{ - NSUInteger viewControllerIndex = [self.viewControllers indexOfObjectIdenticalTo:childViewController]; - if (viewControllerIndex == NSNotFound) { - //If childViewController is not actually a child, return NSNotFound which is also a really large number. - return NSNotFound; - } - - if (self.selectedViewController == childViewController) { - return [self visibilityDepth]; - } - return [self visibilityDepth] + 1; -} - -#pragma mark - UIKit overrides - -- (void)setViewControllers:(NSArray<__kindof UIViewController *> *)viewControllers -{ - [super setViewControllers:viewControllers]; - [self visibilityDepthDidChange]; -} - -- (void)setViewControllers:(NSArray<__kindof UIViewController *> *)viewControllers animated:(BOOL)animated -{ - [super setViewControllers:viewControllers animated:animated]; - [self visibilityDepthDidChange]; -} - -- (void)setSelectedIndex:(NSUInteger)selectedIndex -{ - as_activity_create_for_scope("Set selected index of ASTabBarController"); - os_log_info(ASNodeLog(), "Selected tab %tu of %@", selectedIndex, self); - - [super setSelectedIndex:selectedIndex]; - [self visibilityDepthDidChange]; -} - -- (void)setSelectedViewController:(__kindof UIViewController *)selectedViewController -{ - as_activity_create_for_scope("Set selected view controller of ASTabBarController"); - os_log_info(ASNodeLog(), "Selected view controller %@ of %@", selectedViewController, self); - - [super setSelectedViewController:selectedViewController]; - [self visibilityDepthDidChange]; -} - -@end diff --git a/Source/ASTableNode.h b/Source/ASTableNode.h index 772f3a7ec1..3c3d59538f 100644 --- a/Source/ASTableNode.h +++ b/Source/ASTableNode.h @@ -530,51 +530,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASCellNode *)tableNode:(ASTableNode *)tableNode nodeForRowAtIndexPath:(NSIndexPath *)indexPath; -/** - * Similar to -tableView:cellForRowAtIndexPath:. - * - * @param tableView The sender. - * - * @param indexPath The index path of the requested node. - * - * @return a node for display at this indexpath. This will be called on the main thread and should not implement reuse (it will be called once per row). Unlike UITableView's version, this method - * is not called when the row is about to display. - */ -- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Similar to -tableView:nodeForRowAtIndexPath: - * This method takes precedence over tableView:nodeForRowAtIndexPath: if implemented. - * @param tableView The sender. - * - * @param indexPath The index path of the requested node. - * - * @return a block that creates the node for display at this indexpath. - * Must be thread-safe (can be called on the main thread or a background - * queue) and should not implement reuse (it will be called once per row). - */ -- (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Indicator to lock the data source for data fetching in async mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception - * due to the data access in async mode. - * - * @param tableView The sender. - * @deprecated The data source is always accessed on the main thread, and this method will not be called. - */ -- (void)tableViewLockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called."); - -/** - * Indicator to unlock the data source for data fetching in asyn mode. - * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception - * due to the data access in async mode. - * - * @param tableView The sender. - * @deprecated The data source is always accessed on the main thread, and this method will not be called. - */ -- (void)tableViewUnlockDataSource:(ASTableView *)tableView ASDISPLAYNODE_DEPRECATED_MSG("Data source accesses are on the main thread. Method will not be called."); - /** * Generate a unique identifier for an element in a table. This helps state restoration persist the scroll position * of a table view even when the data in that table changes. See the documentation for UIDataSourceModelAssociation for more information. @@ -669,95 +624,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode; -/** - * Informs the delegate that the table view will add the given node - * at the given index path to the view hierarchy. - * - * @param tableView The sender. - * @param node The node that will be displayed. - * @param indexPath The index path of the row that will be displayed. - * - * @warning AsyncDisplayKit processes table view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - */ -- (void)tableView:(ASTableView *)tableView willDisplayNode:(ASCellNode *)node forRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Informs the delegate that the table view did remove the provided node from the view hierarchy. - * This may be caused by the node scrolling out of view, or by deleting the row - * or its containing section with @c deleteRowsAtIndexPaths:withRowAnimation: or @c deleteSections:withRowAnimation: . - * - * @param tableView The sender. - * @param node The node which was removed from the view hierarchy. - * @param indexPath The index path at which the node was located before the removal. - * - * @warning AsyncDisplayKit processes table view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - */ -- (void)tableView:(ASTableView *)tableView didEndDisplayingNode:(ASCellNode *)node forRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Receive a message that the tableView is near the end of its data set and more data should be fetched if necessary. - * - * @param tableView The sender. - * @param context A context object that must be notified when the batch fetch is completed. - * - * @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future - * notifications to do batch fetches. This method is called on a background queue. - * - * ASTableView currently only supports batch events for tail loads. If you require a head load, consider implementing a - * UIRefreshControl. - */ -- (void)tableView:(ASTableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Tell the tableView if batch fetching should begin. - * - * @param tableView The sender. - * - * @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of - * objects that can be fetched or no network connection. - * - * If not implemented, the tableView assumes that it should notify its asyncDelegate when batch fetching - * should occur. - */ -- (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Provides the constrained size range for measuring the row at the index path. - * Note: the widths in the returned size range are ignored! - * - * @param tableView The sender. - * - * @param indexPath The index path of the node. - * - * @return A constrained size range for layout the node at this index path. - */ -- (ASSizeRange)tableView:(ASTableView *)tableView constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -/** - * Informs the delegate that the table view will add the node - * at the given index path to the view hierarchy. - * - * @param tableView The sender. - * @param indexPath The index path of the row that will be displayed. - * - * @warning AsyncDisplayKit processes table view edits asynchronously. The index path - * passed into this method may not correspond to the same item in your data source - * if your data source has been updated since the last edit was processed. - * - * This method is deprecated. Use @c tableView:willDisplayNode:forRowAtIndexPath: instead. - */ -- (void)tableView:(ASTableView *)tableView willDisplayNodeForRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's method instead."); - -@end - -@interface ASTableNode (Deprecated) - -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed."); - @end NS_ASSUME_NONNULL_END diff --git a/Source/ASTableNode.mm b/Source/ASTableNode.mm index d8acc8a328..f7f0da4f2d 100644 --- a/Source/ASTableNode.mm +++ b/Source/ASTableNode.mm @@ -873,14 +873,6 @@ - (void)waitUntilAllUpdatesAreProcessed } } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)waitUntilAllUpdatesAreCommitted -{ - [self waitUntilAllUpdatesAreProcessed]; -} -#pragma clang diagnostic pop - #pragma mark - Debugging (Private) - (NSMutableArray *)propertiesForDebugDescription diff --git a/Source/ASTableView.h b/Source/ASTableView.h index a2258b3207..4d0ac00cdb 100644 --- a/Source/ASTableView.h +++ b/Source/ASTableView.h @@ -30,216 +30,17 @@ NS_ASSUME_NONNULL_BEGIN /// The corresponding table node, or nil if one does not exist. @property (nonatomic, weak, readonly) ASTableNode *tableNode; -/** - * Retrieves the node for the row at the given index path. - */ -- (nullable ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; - -@end - -@interface ASTableView (Deprecated) - -@property (nonatomic, weak) id asyncDelegate ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's .delegate property instead."); -@property (nonatomic, weak) id asyncDataSource ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode .dataSource property instead."); - -/** - * Initializer. - * - * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. - * The frame of the table view changes as table cells are added and deleted. - * - * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - */ -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style ASDISPLAYNODE_DEPRECATED_MSG("Please use ASTableNode instead of ASTableView."); - -/** - * The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called. - * - * Defaults to two screenfuls. - */ -@property (nonatomic) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/** - * The distance that the content view is inset from the table view edges. Defaults to UIEdgeInsetsZero. - */ -@property (nonatomic) UIEdgeInsets contentInset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead"); - -/** - * The offset of the content view's origin from the table node's origin. Defaults to CGPointZero. - */ -@property (nonatomic) CGPoint contentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/** - * YES to automatically adjust the contentOffset when cells are inserted or deleted above - * visible cells, maintaining the users' visible scroll position. - * - * @note This is only applied to non-animated updates. For animated updates, there is no way to - * synchronize or "cancel out" the appearance of a scroll due to UITableView API limitations. - * - * default is NO. - */ -@property (nonatomic) BOOL automaticallyAdjustsContentOffset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/* - * A Boolean value that determines whether the nodes that the data source renders will be flipped. - */ -@property (nonatomic) BOOL inverted ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -@property (nonatomic, readonly, nullable) NSIndexPath *indexPathForSelectedRow ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -@property (nonatomic, readonly, nullable) NSArray *indexPathsForSelectedRows ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -@property (nonatomic, readonly, nullable) NSArray *indexPathsForVisibleRows ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); - -/** - * Tuning parameters for a range type in full mode. - * - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in full mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Set the tuning parameters for a range type in full mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Tuning parameters for a range type in the specified mode. - * - * @param rangeMode The range mode to get the running parameters for. - * @param rangeType The range type to get the tuning parameters for. - * - * @return A tuning parameter value for the given range type in the given mode. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Set the tuning parameters for a range type in the specified mode. - * - * @param tuningParameters The tuning parameters to store for a range type. - * @param rangeMode The range mode to set the running parameters for. - * @param rangeType The range type to set the tuning parameters for. - * - * @see ASLayoutRangeMode - * @see ASLayoutRangeType - */ -- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)selectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (nullable NSArray *)indexPathsForRowsInRect:(CGRect)rect ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Similar to -visibleCells. - * - * @return an array containing the cell nodes being displayed on screen. - */ -- (NSArray *)visibleNodes AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Similar to -indexPathForCell:. - * - * @param cellNode a cellNode part of the table view - * - * @return an indexPath for this cellNode - */ -- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @param completion block to run on completion of asynchronous loading or nil. If supplied, the block is run on - * the main thread. - * @warning This method is substantially more expensive than UITableView's version. - */ --(void)reloadDataWithCompletion:(void (^ _Nullable)(void))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UITableView's version. - */ -- (void)reloadData ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -/** - * Triggers a relayout of all nodes. - * - * @discussion This method invalidates and lays out every cell node in the table view. - */ -- (void)relayoutItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)beginUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); - -- (void)endUpdates ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); - -/** - * Concludes a series of method calls that insert, delete, select, or reload rows and sections of the table view. - * You call this method to bracket a series of method calls that begins with beginUpdates and that consists of operations - * to insert, delete, select, and reload rows and sections of the table view. When you call endUpdates, ASTableView begins animating - * the operations simultaneously. This method is must be called from the main thread. It's important to remember that the ASTableView will - * be processing the updates asynchronously after this call and are not guaranteed to be reflected in the ASTableView until - * the completion block is executed. - * - * @param animated NO to disable all animations. - * @param completion A completion handler block to execute when all of the operations are finished. This block takes a single - * Boolean parameter that contains the value YES if all of the related animations completed successfully or - * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. - */ -- (void)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL completed))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); - /** * See ASTableNode.h for full documentation of these methods. */ @property (nonatomic, readonly) BOOL isProcessingUpdates; - (void)onDidFinishProcessingUpdates:(void (^)(void))completion; -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASTableNode waitUntilAllUpdatesAreProcessed] instead."); - -- (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); -- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); - -@end - -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASTableDataSource.") -@protocol ASTableViewDataSource -@end +/** + * Retrieves the node for the row at the given index path. + */ +- (nullable ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT; -ASDISPLAYNODE_DEPRECATED_MSG("Renamed to ASTableDelegate.") -@protocol ASTableViewDelegate @end NS_ASSUME_NONNULL_END diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 7bfa46b1ea..7cf84f2ba3 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -16,7 +16,6 @@ #import #import #import -#import #import #import #import @@ -228,46 +227,26 @@ @interface ASTableView () )asyncDataSource _asyncDataSource = asyncDataSource; _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - _asyncDataSourceFlags.numberOfSectionsInTableView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableView:)]; _asyncDataSourceFlags.numberOfSectionsInTableNode = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInTableNode:)]; - _asyncDataSourceFlags.tableViewNumberOfRowsInSection = [_asyncDataSource respondsToSelector:@selector(tableView:numberOfRowsInSection:)]; _asyncDataSourceFlags.tableNodeNumberOfRowsInSection = [_asyncDataSource respondsToSelector:@selector(tableNode:numberOfRowsInSection:)]; - _asyncDataSourceFlags.tableViewNodeForRow = [_asyncDataSource respondsToSelector:@selector(tableView:nodeForRowAtIndexPath:)]; _asyncDataSourceFlags.tableNodeNodeForRow = [_asyncDataSource respondsToSelector:@selector(tableNode:nodeForRowAtIndexPath:)]; - _asyncDataSourceFlags.tableViewNodeBlockForRow = [_asyncDataSource respondsToSelector:@selector(tableView:nodeBlockForRowAtIndexPath:)]; _asyncDataSourceFlags.tableNodeNodeBlockForRow = [_asyncDataSource respondsToSelector:@selector(tableNode:nodeBlockForRowAtIndexPath:)]; _asyncDataSourceFlags.tableViewCanMoveRow = [_asyncDataSource respondsToSelector:@selector(tableView:canMoveRowAtIndexPath:)]; _asyncDataSourceFlags.tableViewMoveRow = [_asyncDataSource respondsToSelector:@selector(tableView:moveRowAtIndexPath:toIndexPath:)]; _asyncDataSourceFlags.sectionIndexMethods = [_asyncDataSource respondsToSelector:@selector(sectionIndexTitlesForTableView:)] && [_asyncDataSource respondsToSelector:@selector(tableView:sectionForSectionIndexTitle:atIndex:)]; _asyncDataSourceFlags.modelIdentifierMethods = [_asyncDataSource respondsToSelector:@selector(modelIdentifierForElementAtIndexPath:inNode:)] && [_asyncDataSource respondsToSelector:@selector(indexPathForElementWithModelIdentifier:inNode:)]; - ASDisplayNodeAssert(_asyncDataSourceFlags.tableViewNodeBlockForRow - || _asyncDataSourceFlags.tableViewNodeForRow - || _asyncDataSourceFlags.tableNodeNodeBlockForRow + ASDisplayNodeAssert(_asyncDataSourceFlags.tableNodeNodeBlockForRow || _asyncDataSourceFlags.tableNodeNodeForRow, @"Data source must implement tableNode:nodeBlockForRowAtIndexPath: or tableNode:nodeForRowAtIndexPath:"); - ASDisplayNodeAssert(_asyncDataSourceFlags.tableNodeNumberOfRowsInSection || _asyncDataSourceFlags.tableViewNumberOfRowsInSection, @"Data source must implement tableNode:numberOfRowsInSection:"); + ASDisplayNodeAssert(_asyncDataSourceFlags.tableNodeNumberOfRowsInSection, @"Data source must implement tableNode:numberOfRowsInSection:"); } _dataController.validationErrorSource = asyncDataSource; @@ -468,43 +439,25 @@ - (void)setAsyncDelegate:(id)asyncDelegate _asyncDelegateFlags.scrollViewDidScroll = [_asyncDelegate respondsToSelector:@selector(scrollViewDidScroll:)]; - _asyncDelegateFlags.tableViewWillDisplayNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNode:forRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeWillDisplayNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:willDisplayRowWithNode:)]; - if (_asyncDelegateFlags.tableViewWillDisplayNodeForRow == NO) { - _asyncDelegateFlags.tableViewWillDisplayNodeForRowDeprecated = [_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]; - } - _asyncDelegateFlags.tableViewDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:didEndDisplayingNode:forRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeDidEndDisplayingNodeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didEndDisplayingRowWithNode:)]; _asyncDelegateFlags.scrollViewWillEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]; _asyncDelegateFlags.scrollViewDidEndDecelerating = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]; - _asyncDelegateFlags.tableViewWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)]; _asyncDelegateFlags.tableNodeWillBeginBatchFetch = [_asyncDelegate respondsToSelector:@selector(tableNode:willBeginBatchFetchWithContext:)]; - _asyncDelegateFlags.shouldBatchFetchForTableView = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]; _asyncDelegateFlags.shouldBatchFetchForTableNode = [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableNode:)]; _asyncDelegateFlags.scrollViewWillBeginDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]; _asyncDelegateFlags.scrollViewDidEndDragging = [_asyncDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]; - _asyncDelegateFlags.tableViewConstrainedSizeForRow = [_asyncDelegate respondsToSelector:@selector(tableView:constrainedSizeForRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeConstrainedSizeForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:constrainedSizeForRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewWillSelectRow = [_asyncDelegate respondsToSelector:@selector(tableView:willSelectRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeWillSelectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:willSelectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidSelectRow = [_asyncDelegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeDidSelectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didSelectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewWillDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableView:willDeselectRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeWillDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:willDeselectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableView:didDeselectRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeDidDeselectRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didDeselectRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewShouldHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableView:shouldHighlightRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeShouldHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableNode:shouldHighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableView:didHighlightRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeDidHighlightRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didHighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewDidUnhighlightRow = [_asyncDelegate respondsToSelector:@selector(tableView:didUnhighlightRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeDidUnhighlightRow = [_asyncDelegate respondsToSelector:@selector(tableNode:didUnhighlightRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewShouldShowMenuForRow = [_asyncDelegate respondsToSelector:@selector(tableView:shouldShowMenuForRowAtIndexPath:)]; _asyncDelegateFlags.tableNodeShouldShowMenuForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:shouldShowMenuForRowAtIndexPath:)]; - _asyncDelegateFlags.tableViewCanPerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableView:canPerformAction:forRowAtIndexPath:withSender:)]; _asyncDelegateFlags.tableNodeCanPerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:canPerformAction:forRowAtIndexPath:withSender:)]; - _asyncDelegateFlags.tableViewPerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableView:performAction:forRowAtIndexPath:withSender:)]; _asyncDelegateFlags.tableNodePerformActionForRow = [_asyncDelegate respondsToSelector:@selector(tableNode:performAction:forRowAtIndexPath:withSender:)]; } @@ -516,7 +469,7 @@ - (void)_asyncDelegateOrDataSourceDidChange { ASDisplayNodeAssertMainThread(); - if (_asyncDataSource == nil && _asyncDelegate == nil && !ASActivateExperimentalFeature(ASExperimentalSkipClearData)) { + if (_asyncDataSource == nil && _asyncDelegate == nil) { [_dataController clearData]; } } @@ -1032,14 +985,7 @@ - (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)c if (_asyncDelegateFlags.tableNodeWillDisplayNodeForRow) { GET_TABLENODE_OR_RETURN(tableNode, (void)0); [_asyncDelegate tableNode:tableNode willDisplayRowWithNode:cellNode]; - } else if (_asyncDelegateFlags.tableViewWillDisplayNodeForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self willDisplayNode:cellNode forRowAtIndexPath:indexPath]; - } else if (_asyncDelegateFlags.tableViewWillDisplayNodeForRowDeprecated) { - [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; - } -#pragma clang diagnostic pop + } [_rangeController setNeedsUpdate]; @@ -1068,11 +1014,6 @@ - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCel if (ASTableNode *tableNode = self.tableNode) { [_asyncDelegate tableNode:tableNode didEndDisplayingRowWithNode:cellNode]; } - } else if (_asyncDelegateFlags.tableViewDidEndDisplayingNodeForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didEndDisplayingNode:cellNode forRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop } [_cellsForVisibilityUpdates removeObject:cell]; @@ -1093,11 +1034,6 @@ - (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(no result = [self convertIndexPathFromTableNode:result waitingIfNeeded:YES]; return result; } - } else if (_asyncDelegateFlags.tableViewWillSelectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self willSelectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop } else { return indexPath; } @@ -1111,11 +1047,6 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIn if (indexPath != nil) { [_asyncDelegate tableNode:tableNode didSelectRowAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.tableViewDidSelectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didSelectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop } } @@ -1132,11 +1063,6 @@ - (NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:( result = [self convertIndexPathFromTableNode:result waitingIfNeeded:YES]; return result; } - } else if (_asyncDelegateFlags.tableViewWillDeselectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self willDeselectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop } return indexPath; } @@ -1149,11 +1075,6 @@ - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(nonnull NS if (indexPath != nil) { [_asyncDelegate tableNode:tableNode didDeselectRowAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.tableViewDidDeselectRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didDeselectRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop } } @@ -1165,11 +1086,6 @@ - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(nonnul if (indexPath != nil) { return [_asyncDelegate tableNode:tableNode shouldHighlightRowAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.tableViewShouldHighlightRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self shouldHighlightRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop } return YES; } @@ -1182,11 +1098,6 @@ - (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(nonnull N if (indexPath != nil) { return [_asyncDelegate tableNode:tableNode didHighlightRowAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.tableViewDidHighlightRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didHighlightRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop } } @@ -1198,11 +1109,6 @@ - (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(nonnull if (indexPath != nil) { return [_asyncDelegate tableNode:tableNode didUnhighlightRowAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.tableViewDidUnhighlightRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self didUnhighlightRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop } } @@ -1214,11 +1120,6 @@ - (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(nonn if (indexPath != nil) { return [_asyncDelegate tableNode:tableNode shouldShowMenuForRowAtIndexPath:indexPath]; } - } else if (_asyncDelegateFlags.tableViewShouldShowMenuForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self shouldShowMenuForRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop } return NO; } @@ -1231,11 +1132,6 @@ - (BOOL)tableView:(UITableView *)tableView canPerformAction:(nonnull SEL)action if (indexPath != nil) { return [_asyncDelegate tableNode:tableNode canPerformAction:action forRowAtIndexPath:indexPath withSender:sender]; } - } else if (_asyncDelegateFlags.tableViewCanPerformActionForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate tableView:self canPerformAction:action forRowAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop } return NO; } @@ -1248,11 +1144,6 @@ - (void)tableView:(UITableView *)tableView performAction:(nonnull SEL)action for if (indexPath != nil) { [_asyncDelegate tableNode:tableNode performAction:action forRowAtIndexPath:indexPath withSender:sender]; } - } else if (_asyncDelegateFlags.tableViewPerformActionForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [_asyncDelegate tableView:self performAction:action forRowAtIndexPath:indexPath withSender:sender]; -#pragma clang diagnostic pop } } @@ -1441,15 +1332,10 @@ - (ASBatchContext *)batchContext - (BOOL)canBatchFetch { // if the delegate does not respond to this method, there is no point in starting to fetch - BOOL canFetch = _asyncDelegateFlags.tableNodeWillBeginBatchFetch || _asyncDelegateFlags.tableViewWillBeginBatchFetch; + BOOL canFetch = _asyncDelegateFlags.tableNodeWillBeginBatchFetch; if (canFetch && _asyncDelegateFlags.shouldBatchFetchForTableNode) { GET_TABLENODE_OR_RETURN(tableNode, NO); return [_asyncDelegate shouldBatchFetchForTableNode:tableNode]; - } else if (canFetch && _asyncDelegateFlags.shouldBatchFetchForTableView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDelegate shouldBatchFetchForTableView:self]; -#pragma clang diagnostic pop } else { return canFetch; } @@ -1499,13 +1385,6 @@ - (void)_beginBatchFetching GET_TABLENODE_OR_RETURN(tableNode, (void)0); [self->_asyncDelegate tableNode:tableNode willBeginBatchFetchWithContext:self->_batchContext]; }); - } else if (_asyncDelegateFlags.tableViewWillBeginBatchFetch) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self->_asyncDelegate tableView:self willBeginBatchFetchWithContext:self->_batchContext]; -#pragma clang diagnostic pop - }); } } @@ -1749,20 +1628,6 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt } else { ASDisplayNodeFailAssert(@"Data source returned invalid node from tableNode:nodeForRowAtIndexPath:. Node: %@", node); } - } else if (_asyncDataSourceFlags.tableViewNodeBlockForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - block = [_asyncDataSource tableView:self nodeBlockForRowAtIndexPath:indexPath]; - } else if (_asyncDataSourceFlags.tableViewNodeForRow) { - ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - if ([node isKindOfClass:[ASCellNode class]]) { - block = ^{ - return node; - }; - } else { - ASDisplayNodeFailAssert(@"Data source returned invalid node from tableView:nodeForRowAtIndexPath:. Node: %@", node); - } } // Handle nil node block @@ -1801,14 +1666,6 @@ - (ASSizeRange)dataController:(ASDataController *)dataController constrainedSize // ignore widths in the returned size range (for TableView) constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.min.height), CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.max.height)); - } else if (_asyncDelegateFlags.tableViewConstrainedSizeForRow) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - ASSizeRange delegateConstrainedSize = [_asyncDelegate tableView:self constrainedSizeForRowAtIndexPath:indexPath]; -#pragma clang diagnostic pop - // ignore widths in the returned size range (for TableView) - constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.min.height), - CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.max.height)); } else { constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, 0), CGSizeMake(_nodesConstrainedWidth, CGFLOAT_MAX)); @@ -1821,11 +1678,6 @@ - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(N if (_asyncDataSourceFlags.tableNodeNumberOfRowsInSection) { GET_TABLENODE_OR_RETURN(tableNode, 0); return [_asyncDataSource tableNode:tableNode numberOfRowsInSection:section]; - } else if (_asyncDataSourceFlags.tableViewNumberOfRowsInSection) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource tableView:self numberOfRowsInSection:section]; -#pragma clang diagnostic pop } else { return 0; } @@ -1836,11 +1688,6 @@ - (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataControlle if (_asyncDataSourceFlags.numberOfSectionsInTableNode) { GET_TABLENODE_OR_RETURN(tableNode, 0); return [_asyncDataSource numberOfSectionsInTableNode:tableNode]; - } else if (_asyncDataSourceFlags.numberOfSectionsInTableView) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [_asyncDataSource numberOfSectionsInTableView:self]; -#pragma clang diagnostic pop } else { return 1; // default section number } diff --git a/Source/ASTableViewProtocols.h b/Source/ASTableViewProtocols.h index ea392e2f4d..91dbab5275 100644 --- a/Source/ASTableViewProtocols.h +++ b/Source/ASTableViewProtocols.h @@ -20,10 +20,6 @@ NS_ASSUME_NONNULL_BEGIN @optional -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:numberOfRowsInSection: instead."); - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView ASDISPLAYNODE_DEPRECATED_MSG("Implement numberOfSectionsInTableNode: instead."); - - (nullable NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; - (nullable NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section; @@ -63,19 +59,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath; -- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:shouldHighlightRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didHighlightRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didUnhighlightRowAtIndexPath: instead."); - -- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:willSelectRowAtIndexPath: instead."); -- (nullable NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:willDeselectRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didSelectRowAtIndexPath: instead."); -- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:didDeselectRowAtIndexPath: instead."); - - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath; - (nullable NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath; #if TARGET_OS_IOS -- (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath API_DEPRECATED_WITH_REPLACEMENT("tableView:trailingSwipeActionsConfigurationForRowAtIndexPath:", ios(8.0, 13.0)); - (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)); - (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)); - (nullable UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0)); @@ -92,10 +78,6 @@ NS_ASSUME_NONNULL_BEGIN - (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath; -- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:shouldShowMenuForRowAtIndexPath: instead."); -- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:canPerformAction:forRowAtIndexPath:withSender: instead."); -- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(nullable id)sender ASDISPLAYNODE_DEPRECATED_MSG("Implement -tableNode:performAction:forRowAtIndexPath:withSender: instead."); - @end NS_ASSUME_NONNULL_END diff --git a/Source/ASTextNode.h b/Source/ASTextNode.h index b05a1d54ad..5afc0ecc6f 100644 --- a/Source/ASTextNode.h +++ b/Source/ASTextNode.h @@ -13,13 +13,6 @@ #import #import -#if (!AS_ENABLE_TEXTNODE) - -// Pull in ASTextNode2 to replace ASTextNode with ASTextNode2 -#import - -#else - NS_ASSUME_NONNULL_BEGIN /** @@ -43,13 +36,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, copy) NSAttributedString *truncationAttributedText; -/** - @summary The second attributed string appended for truncation. - @discussion This string will be highlighted on touches. - @default nil - */ -@property (nullable, copy) NSAttributedString *additionalTruncationMessage; - /** @abstract Determines how the text is truncated to fit within the receiver's maximum size. @discussion Defaults to NSLineBreakByWordWrapping. @@ -73,37 +59,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (readonly) NSUInteger lineCount; -/** - * An array of path objects representing the regions where text should not be displayed. - * - * @discussion The default value of this property is an empty array. You can - * assign an array of UIBezierPath objects to exclude text from one or more regions in - * the text node's bounds. You can use this property to have text wrap around images, - * shapes or other text like a fancy magazine. - */ -@property (nullable, copy) NSArray *exclusionPaths; - -#pragma mark - Placeholders - -/** - * @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES. - * - * @discussion Defaults to NO. When YES, it draws rectangles for each line of text, - * following the true shape of the text's wrapping. This visually mirrors the overall - * shape and weight of paragraphs, making the appearance of the finished text less jarring. - */ -@property BOOL placeholderEnabled; - -/** - @abstract The placeholder color. - */ -@property (nullable, copy) UIColor *placeholderColor; - -/** - @abstract Inset each line of the placeholder. - */ -@property UIEdgeInsets placeholderInsets; - #pragma mark - Shadow /** @@ -232,47 +187,4 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASTextNode (Unavailable) - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -@end - -/** - * @abstract Text node unsupported properties - */ -@interface ASTextNode (Unsupported) - -@property (nullable, nonatomic) id textContainerLinePositionModifier; - -@end - -/** - * @abstract Text node deprecated properties - */ -@interface ASTextNode (Deprecated) - -/** - The attributedString and attributedText properties are equivalent, but attributedText is now the standard API - name in order to match UILabel and ASEditableTextNode. - - @see attributedText - */ -@property (nullable, copy) NSAttributedString *attributedString ASDISPLAYNODE_DEPRECATED_MSG("Use .attributedText instead."); - - -/** - The truncationAttributedString and truncationAttributedText properties are equivalent, but truncationAttributedText is now the - standard API name in order to match UILabel and ASEditableTextNode. - - @see truncationAttributedText - */ -@property (nullable, copy) NSAttributedString *truncationAttributedString ASDISPLAYNODE_DEPRECATED_MSG("Use .truncationAttributedText instead."); - -@end - NS_ASSUME_NONNULL_END - -#endif diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index 2620cc5369..5207eb1418 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -8,11 +8,6 @@ // #import - -#if AS_ENABLE_TEXTNODE - -#import - #import #import @@ -193,17 +188,13 @@ @implementation ASTextNode { CGColorRef _shadowColor; UIColor *_cachedShadowUIColor; UIColor *_cachedTintColor; - UIColor *_placeholderColor; CGFloat _shadowOpacity; CGFloat _shadowRadius; UIEdgeInsets _textContainerInset; - NSArray *_exclusionPaths; - NSAttributedString *_attributedText; NSAttributedString *_truncationAttributedText; - NSAttributedString *_additionalTruncationMessage; NSAttributedString *_composedTruncationText; NSArray *_pointSizeScaleFactors; NSLineBreakMode _truncationMode; @@ -221,7 +212,6 @@ @implementation ASTextNode { BOOL _passthroughNonlinkTouches; BOOL _alwaysHandleTruncationTokenTap; } -@dynamic placeholderEnabled; static NSArray *DefaultLinkAttributeNames() { static NSArray *names; @@ -257,12 +247,6 @@ - (instancetype)init self.isAccessibilityElement = YES; self.accessibilityTraits = self.defaultAccessibilityTraits; - // Placeholders - // Disabled by default in ASDisplayNode, but add a few options for those who toggle - // on the special placeholder behavior of ASTextNode. - _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); - _placeholderInsets = UIEdgeInsetsMake(1.0, 0.0, 1.0, 0.0); - // Tint color is applied when text nodes are within controls and indicate user action // Most text nodes do not require interaction and this matches the default value of UILabel _textColorFollowsTintColor = NO; @@ -385,7 +369,6 @@ - (ASTextKitAttributes)_locked_rendererAttributes .truncationAttributedString = [self _locked_composedTruncationText], .lineBreakMode = _truncationMode, .maximumNumberOfLines = _maximumNumberOfLines, - .exclusionPaths = _exclusionPaths, // use the property getter so a subclass can provide these scale factors on demand if desired .pointSizeScaleFactors = self.pointSizeScaleFactors, .shadowOffset = _shadowOffset, @@ -533,21 +516,6 @@ - (void)setAttributedText:(NSAttributedString *)attributedText #endif } -#pragma mark - Text Layout - -- (void)setExclusionPaths:(NSArray *)exclusionPaths -{ - if (ASLockedSelfCompareAssignCopy(_exclusionPaths, exclusionPaths)) { - [self setNeedsLayout]; - [self setNeedsDisplay]; - } -} - -- (NSArray *)exclusionPaths -{ - return ASLockedSelf(_exclusionPaths); -} - #pragma mark - Drawing - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer @@ -1009,55 +977,6 @@ - (void)didEnterHierarchy [self _setNeedsDisplayOnTintedTextColor]; } -#pragma mark - Placeholders - -- (UIColor *)placeholderColor -{ - return ASLockedSelf(_placeholderColor); -} - -- (void)setPlaceholderColor:(UIColor *)placeholderColor -{ - if (ASLockedSelfCompareAssignCopy(_placeholderColor, placeholderColor)) { - self.placeholderEnabled = CGColorGetAlpha(placeholderColor.CGColor) > 0; - } -} - -- (UIImage *)placeholderImage -{ - // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set. - // This would completely eliminate the memory and performance cost of the backing store. - CGSize size = self.calculatedSize; - if ((size.width * size.height) < CGFLOAT_EPSILON) { - return nil; - } - - ASLockScopeSelf(); - - UIGraphicsBeginImageContextWithOptions(size, NO, 1.0); - [self.placeholderColor setFill]; - - ASTextKitRenderer *renderer = [self _locked_renderer]; - NSRange visibleRange = renderer.firstVisibleRange; - - // cap height is both faster and creates less subpixel blending - NSArray *lineRects = [self _rectsForTextRange:visibleRange measureOption:ASTextKitRendererMeasureOptionLineHeight]; - - // fill each line with the placeholder color - for (NSValue *rectValue in lineRects) { - CGRect lineRect = [rectValue CGRectValue]; - CGRect fillBounds = CGRectIntegral(UIEdgeInsetsInsetRect(lineRect, self.placeholderInsets)); - - if (fillBounds.size.width > 0.0 && fillBounds.size.height > 0.0) { - UIRectFill(fillBounds); - } - } - - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; -} - #pragma mark - Touch Handling - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event @@ -1117,13 +1036,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); if (inAdditionalTruncationMessage) { - NSRange visibleRange = NSMakeRange(0, 0); - { - ASLockScopeSelf(); - visibleRange = [self _locked_renderer].firstVisibleRange; - } - NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange]; - [self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES]; + } else if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { [self _setHighlightRange:range forAttributeName:linkAttributeName value:linkAttributeValue animated:YES]; } @@ -1314,19 +1227,6 @@ - (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedTe } } -- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage -{ - if (ASLockedSelfCompareAssignCopy(_additionalTruncationMessage, additionalTruncationMessage)) { - [self _invalidateTruncationText]; - [self setNeedsDisplay]; - } -} - -- (NSAttributedString *)additionalTruncationMessage -{ - return ASLockedSelf(_additionalTruncationMessage); -} - - (void)setTruncationMode:(NSLineBreakMode)truncationMode { if (ASLockedSelfCompareAssign(_truncationMode, truncationMode)) { @@ -1396,28 +1296,6 @@ - (void)_locked_invalidateTruncationText _composedTruncationText = nil; } -/** - * @return the additional truncation message range within the as-rendered text. - * Must be called from main thread - */ -- (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange -{ - ASLockScopeSelf(); - - // Check if we even have an additional truncation message. - if (!_additionalTruncationMessage) { - return NSMakeRange(NSNotFound, 0); - } - - // Character location of the unicode ellipsis (the first index after the visible range) - NSInteger truncationTokenIndex = NSMaxRange(visibleRange); - - NSUInteger additionalTruncationMessageLength = _additionalTruncationMessage.length; - // We get the location of the truncation token, then add the length of the - // truncation attributed string +1 for the space between. - return NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength); -} - /** * @return the truncation message for the string. If there are both an * additional truncation message and a truncation attributed string, they will @@ -1427,15 +1305,8 @@ - (NSAttributedString *)_locked_composedTruncationText { DISABLED_ASAssertLocked(__instanceLock__); if (_composedTruncationText == nil) { - if (_truncationAttributedText != nil && _additionalTruncationMessage != nil) { - NSMutableAttributedString *newComposedTruncationString = [[NSMutableAttributedString alloc] initWithAttributedString:_truncationAttributedText]; - [newComposedTruncationString.mutableString appendString:@" "]; - [newComposedTruncationString appendAttributedString:_additionalTruncationMessage]; - _composedTruncationText = newComposedTruncationString; - } else if (_truncationAttributedText != nil) { + if (_truncationAttributedText != nil) { _composedTruncationText = _truncationAttributedText; - } else if (_additionalTruncationMessage != nil) { - _composedTruncationText = _additionalTruncationMessage; } else { _composedTruncationText = DefaultTruncationAttributedString(); } @@ -1495,70 +1366,4 @@ + (void)_registerAttributedText:(NSAttributedString *)str } #endif -// All direct descendants of ASTextNode get their superclass replaced by ASTextNode2. -+ (void)initialize -{ - // Texture requires that node subclasses call [super initialize] - [super initialize]; - - if (class_getSuperclass(self) == [ASTextNode class] - && ASActivateExperimentalFeature(ASExperimentalTextNode)) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - class_setSuperclass(self, [ASTextNode2 class]); -#pragma clang diagnostic pop - } -} - -// For direct allocations of ASTextNode itself, we override allocWithZone: -+ (id)allocWithZone:(struct _NSZone *)zone -{ - if (ASActivateExperimentalFeature(ASExperimentalTextNode)) { - return (ASTextNode *)[ASTextNode2 allocWithZone:zone]; - } else { - return [super allocWithZone:zone]; - } -} - @end - -@implementation ASTextNode (Unsupported) - -- (void)setTextContainerLinePositionModifier:(id)textContainerLinePositionModifier -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); -} - -- (id)textContainerLinePositionModifier -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return nil; -} - -@end - -@implementation ASTextNode (Deprecated) - -- (void)setAttributedString:(NSAttributedString *)attributedString -{ - self.attributedText = attributedString; -} - -- (NSAttributedString *)attributedString -{ - return self.attributedText; -} - -- (void)setTruncationAttributedString:(NSAttributedString *)truncationAttributedString -{ - self.truncationAttributedText = truncationAttributedString; -} - -- (NSAttributedString *)truncationAttributedString -{ - return self.truncationAttributedText; -} - -@end - -#endif diff --git a/Source/ASTextNode2.h b/Source/ASTextNode2.h deleted file mode 100644 index 5848adc375..0000000000 --- a/Source/ASTextNode2.h +++ /dev/null @@ -1,256 +0,0 @@ -// -// ASTextNode2.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@protocol ASTextLinePositionModifier; - -NS_ASSUME_NONNULL_BEGIN - -/** - @abstract Draws interactive rich text. - @discussion Backed by the code in TextExperiment folder, on top of CoreText. - */ -#if AS_ENABLE_TEXTNODE -@interface ASTextNode2 : ASControlNode -#else -@interface ASTextNode : ASControlNode -#endif - -/** - @abstract The styled text displayed by the node. - @discussion Defaults to nil, no text is shown. - For inline image attachments, add an attribute of key NSAttachmentAttributeName, with a value of an NSTextAttachment. - */ -@property (nullable, copy) NSAttributedString *attributedText; - -#pragma mark - Truncation - -/** - @abstract The attributedText to use when the text must be truncated. - @discussion Defaults to a localized ellipsis character. - */ -@property (nullable, copy) NSAttributedString *truncationAttributedText; - -/** - @summary The second attributed string appended for truncation. - @discussion This string will be highlighted on touches. - @default nil - */ -@property (nullable, copy) NSAttributedString *additionalTruncationMessage; - -/** - @abstract Determines how the text is truncated to fit within the receiver's maximum size. - @discussion Defaults to NSLineBreakByWordWrapping. - @note Setting a truncationMode in attributedString will override the truncation mode set here. - */ -@property NSLineBreakMode truncationMode; - -/** - @abstract If the text node is truncated. Text must have been sized first. - */ -@property (readonly, getter=isTruncated) BOOL truncated; - -/** - @abstract The maximum number of lines to render of the text before truncation. - @default 0 (No limit) - */ -@property NSUInteger maximumNumberOfLines; - -/** - @abstract The number of lines in the text. Text must have been sized first. - */ -@property (readonly) NSUInteger lineCount; - -/** - * An array of path objects representing the regions where text should not be displayed. - * - * @discussion The default value of this property is an empty array. You can - * assign an array of UIBezierPath objects to exclude text from one or more regions in - * the text node's bounds. You can use this property to have text wrap around images, - * shapes or other text like a fancy magazine. - */ -@property (nullable, copy) NSArray *exclusionPaths; - -#pragma mark - Placeholders - -/** - * @abstract ASTextNode has a special placeholder behavior when placeholderEnabled is YES. - * - * @discussion Defaults to NO. When YES, it draws rectangles for each line of text, - * following the true shape of the text's wrapping. This visually mirrors the overall - * shape and weight of paragraphs, making the appearance of the finished text less jarring. - */ -@property BOOL placeholderEnabled; - -/** - @abstract The placeholder color. - */ -@property (nullable, copy) UIColor *placeholderColor; - -/** - @abstract Inset each line of the placeholder. - */ -@property UIEdgeInsets placeholderInsets; - -#pragma mark - Shadow - -/** - @abstract When you set these ASDisplayNode properties, they are composited into the bitmap instead of being applied by CA. - - @property (nonatomic) CGColorRef shadowColor; - @property (nonatomic) CGFloat shadowOpacity; - @property (nonatomic) CGSize shadowOffset; - @property (nonatomic) CGFloat shadowRadius; - */ - -/** - @abstract The number of pixels used for shadow padding on each side of the receiver. - @discussion Each inset will be less than or equal to zero, so that applying - UIEdgeInsetsRect(boundingRectForText, shadowPadding) - will return a CGRect large enough to fit both the text and the appropriate shadow padding. - */ -@property (nonatomic, readonly) UIEdgeInsets shadowPadding; - -#pragma mark - Positioning - -/** - @abstract Returns an array of rects bounding the characters in a given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion Use this method to detect all the different rectangles a given range of text occupies. - The rects returned are not guaranteed to be contiguous (for example, if the given text range spans - a line break, the rects returned will be on opposite sides and different lines). The rects returned - are in the coordinate system of the receiver. - */ -- (NSArray *)rectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns an array of rects used for highlighting the characters in a given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion Use this method to detect all the different rectangles the highlights of a given range of text occupies. - The rects returned are not guaranteed to be contiguous (for example, if the given text range spans - a line break, the rects returned will be on opposite sides and different lines). The rects returned - are in the coordinate system of the receiver. This method is useful for visual coordination with a - highlighted range of text. - */ -- (NSArray *)highlightRectsForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns a bounding rect for the given text range. - @param textRange A range of text. Must be valid for the receiver's string. - @discussion The height of the frame returned is that of the receiver's line-height; adjustment for - cap-height and descenders is not performed. This method raises an exception if textRange is not - a valid substring range of the receiver's string. - */ -- (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; - -/** - @abstract Returns the trailing rectangle of space in the receiver, after the final character. - @discussion Use this method to detect which portion of the receiver is not occupied by characters. - The rect returned is in the coordinate system of the receiver. - */ -- (CGRect)trailingRect AS_WARN_UNUSED_RESULT; - - -#pragma mark - Actions - -/** - @abstract The set of attribute names to consider links. Defaults to NSLinkAttributeName. - */ -@property (nonatomic, copy) NSArray *linkAttributeNames; - -/** - @abstract Indicates whether the receiver has an entity at a given point. - @param point The point, in the receiver's coordinate system. - @param attributeNameOut The name of the attribute at the point. Can be NULL. - @param rangeOut The ultimate range of the found text. Can be NULL. - @result YES if an entity exists at `point`; NO otherwise. - */ -- (nullable id)linkAttributeValueAtPoint:(CGPoint)point attributeName:(out NSString * _Nullable * _Nullable)attributeNameOut range:(out NSRange * _Nullable)rangeOut AS_WARN_UNUSED_RESULT; - -/** - @abstract The style to use when highlighting text. - */ -@property (nonatomic) ASTextNodeHighlightStyle highlightStyle; - -/** - @abstract The range of text highlighted by the receiver. Changes to this property are not animated by default. - */ -@property (nonatomic) NSRange highlightRange; - -/** - @abstract Set the range of text to highlight, with optional animation. - - @param highlightRange The range of text to highlight. - - @param animated Whether the text should be highlighted with an animation. - */ -- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated; - -/** - @abstract Responds to actions from links in the text node. - @discussion The delegate must be set before the node is loaded, and implement - textNode:longPressedLinkAttribute:value:atPoint:textRange: in order for - the long press gesture recognizer to be installed. - */ -@property (weak) id delegate; - -/** - @abstract If YES and a long press is recognized, touches are cancelled. Default is NO - */ -@property (nonatomic) BOOL longPressCancelsTouches; - -/** - @abstract if YES will not intercept touches for non-link areas of the text. Default is NO. - @discussion If you still want to handle tap truncation action when passthroughNonlinkTouches is YES, - you should set the alwaysHandleTruncationTokenTap to YES. - */ -@property (nonatomic) BOOL passthroughNonlinkTouches; - -/** - @abstract Always handle tap truncationAction, even the passthroughNonlinkTouches is YES. Default is NO. - @discussion if this is set to YES, the [ASTextNodeDelegate textNodeTappedTruncationToken:] callback will be called. - */ -@property (nonatomic) BOOL alwaysHandleTruncationTokenTap; - -/** - @abstract if YES will use the value of `self.tintColor` if the foreground color of text is not defined. - @discussion This is mainly used from ASButtonNode since by default text nodes do not respect tintColor settings unless contained within a interactive control - */ -@property (nonatomic) BOOL textColorFollowsTintColor; - -+ (void)enableDebugging; - -#pragma mark - Layout and Sizing - -@property (nullable, nonatomic) id textContainerLinePositionModifier; - -@end - -#if AS_ENABLE_TEXTNODE -@interface ASTextNode2 (Unavailable) -#else -@interface ASTextNode (Unavailable) -#endif - -- (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -@end - -#if (!AS_ENABLE_TEXTNODE) -// For the time beeing remap ASTextNode2 to ASTextNode -#define ASTextNode2 ASTextNode -#endif - -NS_ASSUME_NONNULL_END - - diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm deleted file mode 100644 index 57c8fad633..0000000000 --- a/Source/ASTextNode2.mm +++ /dev/null @@ -1,1513 +0,0 @@ -// -// ASTextNode2.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import // Definition of ASTextNodeDelegate - -#import -#import - -#import -#import -#import -#import -#import -#import - -#import -#import - -#import - -@interface ASTextCacheValue : NSObject { - @package - AS::Mutex _m; - std::deque> _layouts; -} -@end -@implementation ASTextCacheValue -@end - -/** - * If set, we will record all values set to attributedText into an array - * and once we get 2000, we'll write them all out into a plist file. - * - * This is useful for gathering realistic text data sets from apps for performance - * testing. - */ -#define AS_TEXTNODE2_RECORD_ATTRIBUTED_STRINGS 0 - -/** - * If it can't find a compatible layout, this method creates one. - * - * NOTE: Be careful to copy `text` if needed. - */ -static NS_RETURNS_RETAINED ASTextLayout *ASTextNodeCompatibleLayoutWithContainerAndText(ASTextContainer *container, NSAttributedString *text) { - static dispatch_once_t onceToken; - static AS::Mutex *layoutCacheLock; - static NSCache *textLayoutCache; - dispatch_once(&onceToken, ^{ - layoutCacheLock = new AS::Mutex(); - textLayoutCache = [[NSCache alloc] init]; - }); - - layoutCacheLock->lock(); - - ASTextCacheValue *cacheValue = [textLayoutCache objectForKey:text]; - if (cacheValue == nil) { - cacheValue = [[ASTextCacheValue alloc] init]; - [textLayoutCache setObject:cacheValue forKey:[text copy]]; - } - - // Lock the cache item for the rest of the method. Only after acquiring can we release the NSCache. - AS::MutexLocker lock(cacheValue->_m); - layoutCacheLock->unlock(); - - CGRect containerBounds = (CGRect){ .size = container.size }; - { - for (const auto &t : cacheValue->_layouts) { - CGSize constrainedSize = std::get<0>(t); - ASTextLayout *layout = std::get<1>(t); - - CGSize layoutSize = layout.textBoundingSize; - // 1. CoreText can return frames that are narrower than the constrained width, for obvious reasons. - // 2. CoreText can return frames that are slightly wider than the constrained width, for some reason. - // We have to trust that somehow it's OK to try and draw within our size constraint, despite the return value. - // 3. Thus, those two values (constrained width & returned width) form a range, where - // intermediate values in that range will be snapped. Thus, we can use a given layout as long as our - // width is in that range, between the min and max of those two values. - CGRect minRect = CGRectMake(0, 0, MIN(layoutSize.width, constrainedSize.width), MIN(layoutSize.height, constrainedSize.height)); - if (!CGRectContainsRect(containerBounds, minRect)) { - continue; - } - CGRect maxRect = CGRectMake(0, 0, MAX(layoutSize.width, constrainedSize.width), MAX(layoutSize.height, constrainedSize.height)); - if (!CGRectContainsRect(maxRect, containerBounds)) { - continue; - } - if (!CGSizeEqualToSize(container.size, constrainedSize)) { - continue; - } - - // Now check container params. - ASTextContainer *otherContainer = layout.container; - if (!UIEdgeInsetsEqualToEdgeInsets(container.insets, otherContainer.insets)) { - continue; - } - if (!ASObjectIsEqual(container.exclusionPaths, otherContainer.exclusionPaths)) { - continue; - } - if (container.maximumNumberOfRows != otherContainer.maximumNumberOfRows) { - continue; - } - if (container.truncationType != otherContainer.truncationType) { - continue; - } - if (!ASObjectIsEqual(container.truncationToken, otherContainer.truncationToken)) { - continue; - } - // TODO: When we get a cache hit, move this entry to the front (LRU). - return layout; - } - } - - // Cache Miss. Compute the text layout. - ASTextLayout *layout = [ASTextLayout layoutWithContainer:container text:text]; - - // Store the result in the cache. - { - // This is a critical section. However we also must hold the lock until this point, in case - // another thread requests this cache item while a layout is being calculated, so they don't race. - cacheValue->_layouts.push_front(std::make_tuple(container.size, layout)); - if (cacheValue->_layouts.size() > 3) { - cacheValue->_layouts.pop_back(); - } - } - - return layout; -} - -static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15; -static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1; -static const CGFloat ASTextNodeHighlightLightOpacity = 0.11; -static const CGFloat ASTextNodeHighlightDarkOpacity = 0.22; -static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncationAttribute"; - -#if AS_ENABLE_TEXTNODE -#define AS_TN2_CLASSNAME ASTextNode2 -#else -#define AS_TN2_CLASSNAME ASTextNode -#endif - -@interface AS_TN2_CLASSNAME () - -@end - -@implementation AS_TN2_CLASSNAME { - ASTextContainer *_textContainer; - - CGSize _shadowOffset; - CGColorRef _shadowColor; - CGFloat _shadowOpacity; - CGFloat _shadowRadius; - - NSAttributedString *_attributedText; - NSAttributedString *_truncationAttributedText; - NSAttributedString *_additionalTruncationMessage; - NSArray *_pointSizeScaleFactors; - NSLineBreakMode _truncationMode; - - NSString *_highlightedLinkAttributeName; - id _highlightedLinkAttributeValue; - NSRange _highlightRange; - ASHighlightOverlayLayer *_activeHighlightLayer; - UIColor *_placeholderColor; - - UILongPressGestureRecognizer *_longPressGestureRecognizer; - ASTextNodeHighlightStyle _highlightStyle; - BOOL _longPressCancelsTouches; - BOOL _passthroughNonlinkTouches; - BOOL _alwaysHandleTruncationTokenTap; -} -@dynamic placeholderEnabled; - -static NSArray *DefaultLinkAttributeNames() { - static NSArray *names; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - names = @[ NSLinkAttributeName ]; - }); - return names; -} - -- (instancetype)init -{ - if (self = [super init]) { - _textContainer = [[ASTextContainer alloc] init]; - // Load default values from superclass. - _shadowOffset = [super shadowOffset]; - _shadowColor = CGColorRetain([super shadowColor]); - _shadowOpacity = [super shadowOpacity]; - _shadowRadius = [super shadowRadius]; - - // Disable user interaction for text node by default. - self.userInteractionEnabled = NO; - self.needsDisplayOnBoundsChange = YES; - - _textContainer.truncationType = ASTextTruncationTypeEnd; - - // The common case is for a text node to be non-opaque and blended over some background. - self.opaque = NO; - self.backgroundColor = [UIColor clearColor]; - - self.linkAttributeNames = DefaultLinkAttributeNames(); - - // Accessibility - self.isAccessibilityElement = YES; - self.accessibilityTraits = self.defaultAccessibilityTraits; - - // Placeholders - // Disabled by default in ASDisplayNode, but add a few options for those who toggle - // on the special placeholder behavior of ASTextNode. - _placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); - _placeholderInsets = UIEdgeInsetsMake(1.0, 0.0, 1.0, 0.0); - } - - return self; -} - -- (void)dealloc -{ - CGColorRelease(_shadowColor); -} - -#pragma mark - Description - -- (NSString *)_plainStringForDescription -{ - NSString *plainString = [[self.attributedText string] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - if (plainString.length > 50) { - plainString = [[plainString substringToIndex:50] stringByAppendingString:@"…"]; - } - return plainString; -} - -- (NSMutableArray *)propertiesForDescription -{ - NSMutableArray *result = [super propertiesForDescription]; - NSString *plainString = [self _plainStringForDescription]; - if (plainString.length > 0) { - [result insertObject:@{ @"text" : ASStringWithQuotesIfMultiword(plainString) } atIndex:0]; - } - return result; -} - -- (NSMutableArray *)propertiesForDebugDescription -{ - NSMutableArray *result = [super propertiesForDebugDescription]; - NSString *plainString = [self _plainStringForDescription]; - if (plainString.length > 0) { - [result insertObject:@{ @"text" : ASStringWithQuotesIfMultiword(plainString) } atIndex:0]; - } - return result; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - // If we are view-backed and the delegate cares, support the long-press callback. - // Locking is not needed, as all instance variables used are main-thread-only. - SEL longPressCallback = @selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:); - if (!self.isLayerBacked && [self.delegate respondsToSelector:longPressCallback]) { - _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_handleLongPress:)]; - _longPressGestureRecognizer.cancelsTouchesInView = self.longPressCancelsTouches; - _longPressGestureRecognizer.delegate = self; - [self.view addGestureRecognizer:_longPressGestureRecognizer]; - } -} - -- (BOOL)supportsLayerBacking -{ - if (!super.supportsLayerBacking) { - return NO; - } - - ASLockScopeSelf(); - // If the text contains any links, return NO. - NSAttributedString *attributedText = _attributedText; - NSRange range = NSMakeRange(0, attributedText.length); - for (NSString *linkAttributeName in _linkAttributeNames) { - __block BOOL hasLink = NO; - [attributedText enumerateAttribute:linkAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - if (value == nil) { - return; - } - hasLink = YES; - *stop = YES; - }]; - if (hasLink) { - return NO; - } - } - return YES; -} - -- (NSString *)defaultAccessibilityLabel -{ - ASLockScopeSelf(); - return _attributedText.string; -} - -- (UIAccessibilityTraits)defaultAccessibilityTraits -{ - return UIAccessibilityTraitStaticText; -} - -#pragma mark - Layout and Sizing - -- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset -{ - ASLockScopeSelf(); - if (ASCompareAssignCustom(_textContainer.insets, textContainerInset, UIEdgeInsetsEqualToEdgeInsets)) { - [self setNeedsLayout]; - } -} - -- (UIEdgeInsets)textContainerInset -{ - // textContainer is invariant and has an atomic accessor. - return _textContainer.insets; -} - -- (void)setTextContainerLinePositionModifier:(id)modifier -{ - ASLockedSelfCompareAssignObjects(_textContainer.linePositionModifier, modifier); -} - -- (id)textContainerLinePositionModifier -{ - ASLockScopeSelf(); - return _textContainer.linePositionModifier; -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width); - ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height); - - ASLockScopeSelf(); - - _textContainer.size = constrainedSize; - [self _ensureTruncationText]; - - // If the constrained size has a max/inf value on the text's forward direction, the text node is calculating its intrinsic size. - // Need to consider both width and height when determining if it is calculating instrinsic size. Even the constrained width is provided, the height can be inf - // it may provide a text that is longer than the width and require a wordWrapping line break mode and looking for the height to be calculated. - BOOL isCalculatingIntrinsicSize = (_textContainer.size.width >= ASTextContainerMaxSize.width) || (_textContainer.size.height >= ASTextContainerMaxSize.height); - - NSMutableAttributedString *mutableText = [_attributedText mutableCopy]; - [self prepareAttributedString:mutableText isForIntrinsicSize:isCalculatingIntrinsicSize]; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(_textContainer, mutableText); - if (layout.truncatedLine != nil && layout.truncatedLine.size.width > layout.textBoundingSize.width) { - return (CGSize) {MIN(constrainedSize.width, layout.truncatedLine.size.width), layout.textBoundingSize.height}; - } - - return layout.textBoundingSize; -} - -#pragma mark - Modifying User Text - -// Returns the ascender of the first character in attributedString by also including the line height if specified in paragraph style. -+ (CGFloat)ascenderWithAttributedString:(NSAttributedString *)attributedString -{ - UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL]; - NSParagraphStyle *paragraphStyle = [attributedString attribute:NSParagraphStyleAttributeName atIndex:0 effectiveRange:NULL]; - if (!paragraphStyle) { - return font.ascender; - } - CGFloat lineHeight = MAX(font.lineHeight, paragraphStyle.minimumLineHeight); - if (paragraphStyle.maximumLineHeight > 0) { - lineHeight = MIN(lineHeight, paragraphStyle.maximumLineHeight); - } - return lineHeight + font.descender; -} - -- (NSAttributedString *)attributedText -{ - ASLockScopeSelf(); - return _attributedText; -} - -- (void)setAttributedText:(NSAttributedString *)attributedText -{ - if (attributedText == nil) { - attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:nil]; - } - - // Many accessors in this method will acquire the lock (including ASDisplayNode methods). - // Holding it for the duration of the method is more efficient in this case. - ASLockScopeSelf(); - - NSAttributedString *oldAttributedText = _attributedText; - if (!ASCompareAssignCopy(_attributedText, attributedText)) { - return; - } - - // Since truncation text matches style of attributedText, invalidate it now. - [self _locked_invalidateTruncationText]; - - NSUInteger length = attributedText.length; - if (length > 0) { - ASLayoutElementStyle *style = [self _locked_style]; - style.ascender = [[self class] ascenderWithAttributedString:attributedText]; - style.descender = [[attributedText attribute:NSFontAttributeName atIndex:attributedText.length - 1 effectiveRange:NULL] descender]; - } - - // Tell the display node superclasses that the cached layout is incorrect now - [self setNeedsLayout]; - - // Force display to create renderer with new size and redisplay with new string - [self setNeedsDisplay]; - - // Accessiblity - self.accessibilityLabel = self.defaultAccessibilityLabel; - - // We update the isAccessibilityElement setting if this node is not switching between strings. - if (oldAttributedText.length == 0 || length == 0) { - // We're an accessibility element by default if there is a string. - self.isAccessibilityElement = (length != 0); - } - -#if AS_TEXTNODE2_RECORD_ATTRIBUTED_STRINGS - [ASTextNode _registerAttributedText:_attributedText]; -#endif -} - -#pragma mark - Text Layout - -- (void)setExclusionPaths:(NSArray *)exclusionPaths -{ - ASLockScopeSelf(); - _textContainer.exclusionPaths = exclusionPaths; - - [self setNeedsLayout]; - [self setNeedsDisplay]; -} - -- (NSArray *)exclusionPaths -{ - ASLockScopeSelf(); - return _textContainer.exclusionPaths; -} - -- (void)prepareAttributedString:(NSMutableAttributedString *)attributedString isForIntrinsicSize:(BOOL)isForIntrinsicSize -{ - ASLockScopeSelf(); - NSLineBreakMode innerMode; - switch (_truncationMode) { - case NSLineBreakByWordWrapping: - case NSLineBreakByCharWrapping: - case NSLineBreakByClipping: - innerMode = _truncationMode; - break; - default: - innerMode = NSLineBreakByWordWrapping; - } - - // Apply/Fix paragraph style if needed - [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:NSMakeRange(0, attributedString.length) options:kNilOptions usingBlock:^(NSParagraphStyle *style, NSRange range, BOOL * _Nonnull stop) { - - BOOL applyTruncationMode = YES; - NSMutableParagraphStyle *paragraphStyle = nil; - // Only "left" and "justified" alignments are supported while calculating intrinsic size. - // Other alignments like "right", "center" and "natural" cause the size to be bigger than needed and thus should be ignored/overridden. - const BOOL forceLeftAlignment = (style != nil - && isForIntrinsicSize - && style.alignment != NSTextAlignmentLeft - && style.alignment != NSTextAlignmentJustified); - if (style != nil) { - if (innerMode == style.lineBreakMode) { - applyTruncationMode = NO; - } - paragraphStyle = [style mutableCopy]; - } else { - if (innerMode == NSLineBreakByWordWrapping) { - applyTruncationMode = NO; - } - paragraphStyle = [NSMutableParagraphStyle new]; - } - if (!applyTruncationMode && !forceLeftAlignment) { - return; - } - paragraphStyle.lineBreakMode = innerMode; - - if (applyTruncationMode) { - paragraphStyle.lineBreakMode = _truncationMode; - } - if (forceLeftAlignment) { - paragraphStyle.alignment = NSTextAlignmentLeft; - } - [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; - }]; - - // Apply shadow if needed - if (_shadowOpacity > 0 && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero)) && CGColorGetAlpha(_shadowColor) > 0) { - NSShadow *shadow = [[NSShadow alloc] init]; - if (_shadowOpacity != 1) { - CGColorRef shadowColorRef = CGColorCreateCopyWithAlpha(_shadowColor, _shadowOpacity * CGColorGetAlpha(_shadowColor)); - shadow.shadowColor = [UIColor colorWithCGColor:shadowColorRef]; - CGColorRelease(shadowColorRef); - } else { - shadow.shadowColor = [UIColor colorWithCGColor:_shadowColor]; - } - shadow.shadowOffset = _shadowOffset; - shadow.shadowBlurRadius = _shadowRadius; - [attributedString addAttribute:NSShadowAttributeName value:shadow range:NSMakeRange(0, attributedString.length)]; - } -} - -#pragma mark - Drawing - -- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - ASTextContainer *copiedContainer; - NSMutableAttributedString *mutableText; - BOOL needsTintColor; - id bgColor; - { - // Wrapping all the other access here, because we can't lock while accessing tintColor. - ASLockScopeSelf(); - [self _ensureTruncationText]; - - // Unlike layout, here we must copy the container since drawing is asynchronous. - copiedContainer = [_textContainer copy]; - copiedContainer.size = self.bounds.size; - [copiedContainer makeImmutable]; - mutableText = [_attributedText mutableCopy] ?: [[NSMutableAttributedString alloc] init]; - - [self prepareAttributedString:mutableText isForIntrinsicSize:NO]; - needsTintColor = self.textColorFollowsTintColor && mutableText.length > 0; - bgColor = self.backgroundColor ?: [NSNull null]; - } - - // After all other attributes are set, apply tint color if needed and foreground color is not already specified - if (needsTintColor) { - // Apply tint color if specified and if foreground color is undefined for attributedString - NSRange limit = NSMakeRange(0, mutableText.length); - // Look for previous attributes that define foreground color - UIColor *attributeValue = (UIColor *)[mutableText attribute:NSForegroundColorAttributeName atIndex:limit.location effectiveRange:NULL]; - - // we need to unlock before accessing tintColor - UIColor *tintColor = self.tintColor; - if (attributeValue == nil && tintColor) { - // None are found, apply tint color if available. Fallback to "black" text color - [mutableText addAttributes:@{ NSForegroundColorAttributeName : tintColor } range:limit]; - } - } - - return @{ - @"container": copiedContainer, - @"text": mutableText, - @"bgColor": bgColor - }; -} - -+ (void)drawRect:(CGRect)bounds withParameters:(NSDictionary *)layoutDict isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing -{ - ASTextContainer *container = layoutDict[@"container"]; - NSAttributedString *text = layoutDict[@"text"]; - UIColor *bgColor = layoutDict[@"bgColor"]; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(container, text); - - if (isCancelledBlock()) { - return; - } - - // Fill background color. - if (bgColor == (id)[NSNull null]) { - bgColor = nil; - } - - // They may have already drawn into this context in the pre-context block - // so unfortunately we have to use the normal blend mode, not copy. - if (bgColor && CGColorGetAlpha(bgColor.CGColor) > 0) { - [bgColor setFill]; - UIRectFillUsingBlendMode(bounds, kCGBlendModeNormal); - } - - CGContextRef context = UIGraphicsGetCurrentContext(); - ASDisplayNodeAssert(context, @"This is no good without a context."); - - [layout drawInContext:context size:bounds.size point:bounds.origin view:nil layer:nil debug:[ASTextDebugOption sharedDebugOption] cancel:isCancelledBlock]; -} - -#pragma mark - Tint Color - -- (void)tintColorDidChange -{ - [super tintColorDidChange]; - - [self _setNeedsDisplayOnTintedTextColor]; -} - -- (void)_setNeedsDisplayOnTintedTextColor -{ - BOOL textColorFollowsTintColor = NO; - { - AS::MutexLocker l(__instanceLock__); - textColorFollowsTintColor = _textColorFollowsTintColor; - } - - if (textColorFollowsTintColor) { - [self setNeedsDisplay]; - } -} - - -#pragma mark Interface State - -- (void)didEnterHierarchy -{ - [super didEnterHierarchy]; - - [self _setNeedsDisplayOnTintedTextColor]; -} - -#pragma mark - Attributes - -- (id)linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString **)attributeNameOut - range:(out NSRange *)rangeOut -{ - return [self _linkAttributeValueAtPoint:point - attributeName:attributeNameOut - range:rangeOut - inAdditionalTruncationMessage:NULL - forHighlighting:NO]; -} - -- (id)_linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString **)attributeNameOut - range:(out NSRange *)rangeOut - inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut - forHighlighting:(BOOL)highlighting -{ - ASLockScopeSelf(); - - // TODO: The copy and application of size shouldn't be required, but it is currently. - // See discussion in https://github.com/TextureGroup/Texture/pull/396 - ASTextContainer *containerCopy = [_textContainer copy]; - containerCopy.size = self.calculatedSize; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(containerCopy, _attributedText); - - if ([self _locked_pointInsideAdditionalTruncationMessage:point withLayout:layout]) { - if (inAdditionalTruncationMessageOut != NULL) { - *inAdditionalTruncationMessageOut = YES; - } - return nil; - } - - NSRange visibleRange = layout.visibleRange; - NSRange clampedRange = NSIntersectionRange(visibleRange, NSMakeRange(0, _attributedText.length)); - - // Search the 9 points of a 44x44 square around the touch until we find a link. - // Start from center, then do sides, then do top/bottom, then do corners. - static constexpr CGSize kRectOffsets[9] = { - { 0, 0 }, - { -22, 0 }, { 22, 0 }, - { 0, -22 }, { 0, 22 }, - { -22, -22 }, { -22, 22 }, - { 22, -22 }, { 22, 22 } - }; - - for (const CGSize &offset : kRectOffsets) { - const CGPoint testPoint = CGPointMake(point.x + offset.width, - point.y + offset.height); - ASTextPosition *pos = [layout closestPositionToPoint:testPoint]; - if (!pos || !NSLocationInRange(pos.offset, clampedRange)) { - continue; - } - for (NSString *attributeName in _linkAttributeNames) { - NSRange effectiveRange = NSMakeRange(0, 0); - id value = [_attributedText attribute:attributeName atIndex:pos.offset - longestEffectiveRange:&effectiveRange inRange:clampedRange]; - if (value == nil) { - // Didn't find any links specified with this attribute. - continue; - } - - // If highlighting, check with delegate first. If not implemented, assume YES. - if (highlighting - && [_delegate respondsToSelector:@selector(textNode:shouldHighlightLinkAttribute:value:atPoint:)] - && ![_delegate textNode:(ASTextNode *)self shouldHighlightLinkAttribute:attributeName - value:value atPoint:point]) { - continue; - } - - *rangeOut = NSIntersectionRange(visibleRange, effectiveRange); - - if (attributeNameOut != NULL) { - *attributeNameOut = attributeName; - } - - return value; - } - } - - return nil; -} - -- (BOOL)_locked_pointInsideAdditionalTruncationMessage:(CGPoint)point withLayout:(ASTextLayout *)layout -{ - // Check if the range is within the additional truncation range - BOOL inAdditionalTruncationMessage = NO; - - CTLineRef truncatedCTLine = layout.truncatedLine.CTLine; - if (truncatedCTLine != NULL && _additionalTruncationMessage != nil) { - CFIndex stringIndexForPosition = CTLineGetStringIndexForPosition(truncatedCTLine, point); - if (stringIndexForPosition != kCFNotFound) { - CFIndex truncatedCTLineGlyphCount = CTLineGetGlyphCount(truncatedCTLine); - - CTLineRef truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef)_truncationAttributedText); - CFIndex truncationTokenLineGlyphCount = truncationTokenLine ? CTLineGetGlyphCount(truncationTokenLine) : 0; - - if (truncationTokenLine) { - CFRelease(truncationTokenLine); - } - - CTLineRef additionalTruncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef)_additionalTruncationMessage); - CFIndex additionalTruncationTokenLineGlyphCount = additionalTruncationTokenLine ? CTLineGetGlyphCount(additionalTruncationTokenLine) : 0; - - if (additionalTruncationTokenLine) { - CFRelease(additionalTruncationTokenLine); - } - - switch (_textContainer.truncationType) { - case ASTextTruncationTypeStart: { - CFIndex composedTruncationTextLineGlyphCount = truncationTokenLineGlyphCount + additionalTruncationTokenLineGlyphCount; - if (stringIndexForPosition > truncationTokenLineGlyphCount && - stringIndexForPosition < composedTruncationTextLineGlyphCount) { - inAdditionalTruncationMessage = YES; - } - break; - } - case ASTextTruncationTypeMiddle: { - CFIndex composedTruncationTextLineGlyphCount = truncationTokenLineGlyphCount + additionalTruncationTokenLineGlyphCount; - CFIndex firstTruncatedTokenIndex = (truncatedCTLineGlyphCount - composedTruncationTextLineGlyphCount) / 2.0; - if ((firstTruncatedTokenIndex + truncationTokenLineGlyphCount) < stringIndexForPosition && - stringIndexForPosition < (firstTruncatedTokenIndex + composedTruncationTextLineGlyphCount)) { - inAdditionalTruncationMessage = YES; - } - break; - } - case ASTextTruncationTypeEnd: { - if (stringIndexForPosition > (truncatedCTLineGlyphCount - additionalTruncationTokenLineGlyphCount)) { - inAdditionalTruncationMessage = YES; - } - break; - } - default: - // For now, assume that a tap inside this text, but outside the text range is a tap on the - // truncation token. - if (![layout textRangeAtPoint:point]) { - inAdditionalTruncationMessage = YES; - } - break; - } - } - } - - return inAdditionalTruncationMessage; -} - -#pragma mark - UIGestureRecognizerDelegate - -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - ASDisplayNodeAssertMainThread(); - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - - if (gestureRecognizer == _longPressGestureRecognizer) { - // Don't allow long press on truncation message - if ([self _pendingTruncationTap]) { - return NO; - } - - // Ask our delegate if a long-press on an attribute is relevant - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(textNode:shouldLongPressLinkAttribute:value:atPoint:)]) { - return [delegate textNode:(ASTextNode *)self - shouldLongPressLinkAttribute:_highlightedLinkAttributeName - value:_highlightedLinkAttributeValue - atPoint:[gestureRecognizer locationInView:self.view]]; - } - - // Otherwise we are good to go. - return YES; - } - - if (([self _pendingLinkTap] || [self _pendingTruncationTap]) - && [gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] - && CGRectContainsPoint(self.threadSafeBounds, [gestureRecognizer locationInView:self.view])) { - return NO; - } - - return [super gestureRecognizerShouldBegin:gestureRecognizer]; -} - -#pragma mark - Highlighting - -- (ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - return _highlightStyle; -} - -- (void)setHighlightStyle:(ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - _highlightStyle = highlightStyle; -} - -- (NSRange)highlightRange -{ - ASLockScopeSelf(); - - return _highlightRange; -} - -- (void)setHighlightRange:(NSRange)highlightRange -{ - [self setHighlightRange:highlightRange animated:NO]; -} - -- (void)setHighlightRange:(NSRange)highlightRange animated:(BOOL)animated -{ - [self _setHighlightRange:highlightRange forAttributeName:nil value:nil animated:animated]; -} - -- (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *)highlightedAttributeName value:(id)highlightedAttributeValue animated:(BOOL)animated -{ - ASDisplayNodeAssertMainThread(); - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - - // Set these so that link tapping works. - _highlightedLinkAttributeName = highlightedAttributeName; - _highlightedLinkAttributeValue = highlightedAttributeValue; - - if (!NSEqualRanges(highlightRange, _highlightRange) && ((0 != highlightRange.length) || (0 != _highlightRange.length))) { - - _highlightRange = highlightRange; - - if (_activeHighlightLayer) { - if (animated) { - __weak CALayer *weakHighlightLayer = _activeHighlightLayer; - _activeHighlightLayer = nil; - - weakHighlightLayer.opacity = 0.0; - - CFTimeInterval beginTime = CACurrentMediaTime(); - CABasicAnimation *possibleFadeIn = (CABasicAnimation *)[weakHighlightLayer animationForKey:@"opacity"]; - if (possibleFadeIn) { - // Calculate when we should begin fading out based on the end of the fade in animation, - // Also check to make sure that the new begin time hasn't already passed - CGFloat newBeginTime = (possibleFadeIn.beginTime + possibleFadeIn.duration); - if (newBeginTime > beginTime) { - beginTime = newBeginTime; - } - } - - CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"]; - fadeOut.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - fadeOut.fromValue = possibleFadeIn.toValue ? : @(((CALayer *)weakHighlightLayer.presentationLayer).opacity); - fadeOut.toValue = @0.0; - fadeOut.fillMode = kCAFillModeBoth; - fadeOut.duration = ASTextNodeHighlightFadeOutDuration; - fadeOut.beginTime = beginTime; - - dispatch_block_t prev = [CATransaction completionBlock]; - [CATransaction setCompletionBlock:^{ - [weakHighlightLayer removeFromSuperlayer]; - }]; - - [weakHighlightLayer addAnimation:fadeOut forKey:fadeOut.keyPath]; - - [CATransaction setCompletionBlock:prev]; - - } else { - [_activeHighlightLayer removeFromSuperlayer]; - _activeHighlightLayer = nil; - } - } - if (0 != highlightRange.length) { - // Find layer in hierarchy that allows us to draw highlighting on. - CALayer *highlightTargetLayer = self.layer; - while (highlightTargetLayer != nil) { - if (highlightTargetLayer.as_allowsHighlightDrawing) { - break; - } - highlightTargetLayer = highlightTargetLayer.superlayer; - } - - if (highlightTargetLayer != nil) { - // TODO: The copy and application of size shouldn't be required, but it is currently. - // See discussion in https://github.com/TextureGroup/Texture/pull/396 - ASTextContainer *textContainerCopy = [_textContainer copy]; - textContainerCopy.size = self.calculatedSize; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(textContainerCopy, _attributedText); - - NSArray *highlightRects = [layout selectionRectsWithoutStartAndEndForRange:[ASTextRange rangeWithRange:highlightRange]]; - NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count]; - - CALayer *layer = self.layer; - UIEdgeInsets shadowPadding = self.shadowPadding; - for (ASTextSelectionRect *rectValue in highlightRects) { - // Adjust shadow padding - CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.rect, shadowPadding); - CGRect highlightedRect = [layer convertRect:rendererRect toLayer:highlightTargetLayer]; - - // We set our overlay layer's frame to the bounds of the highlight target layer. - // Offset highlight rects to avoid double-counting target layer's bounds.origin. - highlightedRect.origin.x -= highlightTargetLayer.bounds.origin.x; - highlightedRect.origin.y -= highlightTargetLayer.bounds.origin.y; - [converted addObject:[NSValue valueWithCGRect:highlightedRect]]; - } - - ASHighlightOverlayLayer *overlayLayer = [[ASHighlightOverlayLayer alloc] initWithRects:converted]; - overlayLayer.highlightColor = [[self class] _highlightColorForStyle:self.highlightStyle]; - overlayLayer.frame = highlightTargetLayer.bounds; - overlayLayer.masksToBounds = NO; - overlayLayer.opacity = [[self class] _highlightOpacityForStyle:self.highlightStyle]; - [highlightTargetLayer addSublayer:overlayLayer]; - - if (animated) { - CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:@"opacity"]; - fadeIn.fromValue = @0.0; - fadeIn.toValue = @(overlayLayer.opacity); - fadeIn.duration = ASTextNodeHighlightFadeInDuration; - fadeIn.beginTime = CACurrentMediaTime(); - - [overlayLayer addAnimation:fadeIn forKey:fadeIn.keyPath]; - } - - [overlayLayer setNeedsDisplay]; - - _activeHighlightLayer = overlayLayer; - } - } - } -} - -- (void)_clearHighlightIfNecessary -{ - ASDisplayNodeAssertMainThread(); - - if ([self _pendingLinkTap] || [self _pendingTruncationTap]) { - [self setHighlightRange:NSMakeRange(0, 0) animated:YES]; - } -} - -+ (CGColorRef)_highlightColorForStyle:(ASTextNodeHighlightStyle)style -{ - return [UIColor colorWithWhite:(style == ASTextNodeHighlightStyleLight ? 0.0 : 1.0) alpha:1.0].CGColor; -} - -+ (CGFloat)_highlightOpacityForStyle:(ASTextNodeHighlightStyle)style -{ - return (style == ASTextNodeHighlightStyleLight) ? ASTextNodeHighlightLightOpacity : ASTextNodeHighlightDarkOpacity; -} - -#pragma mark - Text rects - -static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UIEdgeInsets shadowPadding) { - rendererRect.origin.x -= shadowPadding.left; - rendererRect.origin.y -= shadowPadding.top; - return rendererRect; -} - -- (NSArray *)rectsForTextRange:(NSRange)textRange -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return @[]; -} - -- (NSArray *)highlightRectsForTextRange:(NSRange)textRange -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return @[]; -} - -- (CGRect)trailingRect -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return CGRectZero; -} - -- (CGRect)frameForTextRange:(NSRange)textRange -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return CGRectZero; -} - -#pragma mark - Placeholders - -- (UIColor *)placeholderColor -{ - return ASLockedSelf(_placeholderColor); -} - -- (void)setPlaceholderColor:(UIColor *)placeholderColor -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_placeholderColor, placeholderColor)) { - self.placeholderEnabled = CGColorGetAlpha(placeholderColor.CGColor) > 0; - } -} - -- (UIImage *)placeholderImage -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return nil; -} - -#pragma mark - Touch Handling - -- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - ASLockScopeSelf(); // Protect usage of _passthroughNonlinkTouches and _alwaysHandleTruncationTokenTap ivars. - - if (!_passthroughNonlinkTouches) { - return [super pointInside:point withEvent:event]; - } - - if (_alwaysHandleTruncationTokenTap) { - return YES; - } - - NSRange range = NSMakeRange(0, 0); - NSString *linkAttributeName = nil; - BOOL inAdditionalTruncationMessage = NO; - - id linkAttributeValue = [self _linkAttributeValueAtPoint:point - attributeName:&linkAttributeName - range:&range - inAdditionalTruncationMessage:&inAdditionalTruncationMessage - forHighlighting:YES]; - - NSUInteger lastCharIndex = NSIntegerMax; - BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); - - if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { - return YES; - } else { - return NO; - } -} - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - - [super touchesBegan:touches withEvent:event]; - - CGPoint point = [[touches anyObject] locationInView:self.view]; - - NSRange range = NSMakeRange(0, 0); - NSString *linkAttributeName = nil; - BOOL inAdditionalTruncationMessage = NO; - - id linkAttributeValue = [self _linkAttributeValueAtPoint:point - attributeName:&linkAttributeName - range:&range - inAdditionalTruncationMessage:&inAdditionalTruncationMessage - forHighlighting:YES]; - - NSUInteger lastCharIndex = NSIntegerMax; - BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1); - - if (inAdditionalTruncationMessage) { - NSRange visibleRange = NSMakeRange(0, 0); - { - ASLockScopeSelf(); - // TODO: The copy and application of size shouldn't be required, but it is currently. - // See discussion in https://github.com/TextureGroup/Texture/pull/396 - ASTextContainer *containerCopy = [_textContainer copy]; - containerCopy.size = self.calculatedSize; - ASTextLayout *layout = ASTextNodeCompatibleLayoutWithContainerAndText(containerCopy, _attributedText); - visibleRange = layout.visibleRange; - } - NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange]; - [self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES]; - } else if (range.length > 0 && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) { - [self _setHighlightRange:range forAttributeName:linkAttributeName value:linkAttributeValue animated:YES]; - } - - return; -} - - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesCancelled:touches withEvent:event]; - - [self _clearHighlightIfNecessary]; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesEnded:touches withEvent:event]; - - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - id delegate = self.delegate; - if ([self _pendingLinkTap] && [delegate respondsToSelector:@selector(textNode:tappedLinkAttribute:value:atPoint:textRange:)]) { - CGPoint point = [[touches anyObject] locationInView:self.view]; - [delegate textNode:(ASTextNode *)self tappedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:point textRange:_highlightRange]; - } - - if ([self _pendingTruncationTap]) { - if ([_delegate respondsToSelector:@selector(textNodeTappedTruncationToken:atPoint:)]) { - CGPoint point = [[touches anyObject] locationInView:self.view]; - [_delegate textNodeTappedTruncationToken:(ASTextNode *)self atPoint:point]; - } - } - - [self _clearHighlightIfNecessary]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - ASDisplayNodeAssertMainThread(); - [super touchesMoved:touches withEvent:event]; - - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - UITouch *touch = [touches anyObject]; - CGPoint locationInView = [touch locationInView:self.view]; - // on 3D Touch enabled phones, this gets fired with changes in force, and usually will get fired immediately after touchesBegan:withEvent: - if (CGPointEqualToPoint([touch previousLocationInView:self.view], locationInView)) - return; - - // If touch has moved out of the current highlight range, clear the highlight. - if (_highlightRange.length > 0) { - NSRange range = NSMakeRange(0, 0); - [self _linkAttributeValueAtPoint:locationInView - attributeName:NULL - range:&range - inAdditionalTruncationMessage:NULL - forHighlighting:YES]; - - if (!NSEqualRanges(_highlightRange, range)) { - [self _clearHighlightIfNecessary]; - } - } -} - -- (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer -{ - ASDisplayNodeAssertMainThread(); - - // Respond to long-press when it begins, not when it ends. - if (longPressRecognizer.state == UIGestureRecognizerStateBegan) { - id delegate = self.delegate; - if ([delegate respondsToSelector:@selector(textNode:longPressedLinkAttribute:value:atPoint:textRange:)]) { - ASLockScopeSelf(); // Protect usage of _highlight* ivars. - CGPoint touchPoint = [_longPressGestureRecognizer locationInView:self.view]; - [delegate textNode:(ASTextNode *)self longPressedLinkAttribute:_highlightedLinkAttributeName value:_highlightedLinkAttributeValue atPoint:touchPoint textRange:_highlightRange]; - } - } -} - -- (BOOL)_pendingLinkTap -{ - ASLockScopeSelf(); - - return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && self.delegate != nil; -} - -- (BOOL)_pendingTruncationTap -{ - return [ASLockedSelf(_highlightedLinkAttributeName) isEqualToString:ASTextNodeTruncationTokenAttributeName]; -} - -- (BOOL)alwaysHandleTruncationTokenTap -{ - ASLockScopeSelf(); - return _alwaysHandleTruncationTokenTap; -} - -- (void)setAlwaysHandleTruncationTokenTap:(BOOL)alwaysHandleTruncationTokenTap -{ - ASLockScopeSelf(); - _alwaysHandleTruncationTokenTap = alwaysHandleTruncationTokenTap; -} - -#pragma mark - Shadow Properties - -/** - * Note about shadowed text: - * - * Shadowed text is pretty rare, and we are a framework that targets serious developers. - * We should probably ignore these properties and tell developers to set the shadow into their attributed text instead. - */ -- (CGColorRef)shadowColor -{ - return ASLockedSelf(_shadowColor); -} - -- (void)setShadowColor:(CGColorRef)shadowColor -{ - ASLockScopeSelf(); - if (_shadowColor != shadowColor && CGColorEqualToColor(shadowColor, _shadowColor) == NO) { - CGColorRelease(_shadowColor); - _shadowColor = CGColorRetain(shadowColor); - [self setNeedsDisplay]; - } -} - -- (CGSize)shadowOffset -{ - return ASLockedSelf(_shadowOffset); -} - -- (void)setShadowOffset:(CGSize)shadowOffset -{ - ASLockScopeSelf(); - if (ASCompareAssignCustom(_shadowOffset, shadowOffset, CGSizeEqualToSize)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowOpacity -{ - return ASLockedSelf(_shadowOpacity); -} - -- (void)setShadowOpacity:(CGFloat)shadowOpacity -{ - ASLockScopeSelf(); - if (ASCompareAssign(_shadowOpacity, shadowOpacity)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowRadius -{ - return ASLockedSelf(_shadowRadius); -} - -- (void)setShadowRadius:(CGFloat)shadowRadius -{ - ASLockScopeSelf(); - if (ASCompareAssign(_shadowRadius, shadowRadius)) { - [self setNeedsDisplay]; - } -} - -- (UIEdgeInsets)shadowPadding -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return UIEdgeInsetsZero; -} - -- (void)setPointSizeScaleFactors:(NSArray *)scaleFactors -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - ASLockScopeSelf(); - if (ASCompareAssignCopy(_pointSizeScaleFactors, scaleFactors)) { - [self setNeedsLayout]; - } -} - -- (NSArray *)pointSizeScaleFactors -{ - return ASLockedSelf(_pointSizeScaleFactors); -} - -#pragma mark - Truncation Message - -static NSAttributedString *DefaultTruncationAttributedString() -{ - static NSAttributedString *defaultTruncationAttributedString; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultTruncationAttributedString = [[NSAttributedString alloc] initWithString:NSLocalizedString(@"\u2026", @"Default truncation string")]; - }); - return defaultTruncationAttributedString; -} - -- (void)_ensureTruncationText -{ - ASLockScopeSelf(); - if (_textContainer.truncationToken == nil) { - _textContainer.truncationToken = [self _locked_composedTruncationText]; - } -} - -- (NSAttributedString *)truncationAttributedText -{ - return ASLockedSelf(_truncationAttributedText); -} - -- (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_truncationAttributedText, truncationAttributedText)) { - [self _invalidateTruncationText]; - } -} - -- (NSAttributedString *)additionalTruncationMessage -{ - return ASLockedSelf(_additionalTruncationMessage); -} - -- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_additionalTruncationMessage, additionalTruncationMessage)) { - [self _invalidateTruncationText]; - } -} - -- (NSLineBreakMode)truncationMode -{ - return ASLockedSelf(_truncationMode); -} - -- (void)setTruncationMode:(NSLineBreakMode)truncationMode -{ - ASLockScopeSelf(); - if (ASCompareAssign(_truncationMode, truncationMode)) { - ASTextTruncationType truncationType; - switch (truncationMode) { - case NSLineBreakByTruncatingHead: - truncationType = ASTextTruncationTypeStart; - break; - case NSLineBreakByTruncatingTail: - truncationType = ASTextTruncationTypeEnd; - break; - case NSLineBreakByTruncatingMiddle: - truncationType = ASTextTruncationTypeMiddle; - break; - default: - truncationType = ASTextTruncationTypeNone; - } - - _textContainer.truncationType = truncationType; - - [self setNeedsDisplay]; - } -} - -- (BOOL)isTruncated -{ - return ASLockedSelf([self locked_textLayoutForSize:[self _locked_threadSafeBounds].size].truncatedLine != nil); -} - -- (BOOL)shouldTruncateForConstrainedSize:(ASSizeRange)constrainedSize -{ - return ASLockedSelf([self locked_textLayoutForSize:constrainedSize.max].truncatedLine != nil); -} - -- (ASTextLayout *)locked_textLayoutForSize:(CGSize)size -{ - ASTextContainer *container = [_textContainer copy]; - container.size = size; - return ASTextNodeCompatibleLayoutWithContainerAndText(container, _attributedText); -} - -- (NSUInteger)maximumNumberOfLines -{ - // _textContainer is invariant and this is just atomic access. - return _textContainer.maximumNumberOfRows; -} - -- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines -{ - ASLockScopeSelf(); - if (ASCompareAssign(_textContainer.maximumNumberOfRows, maximumNumberOfLines)) { - [self setNeedsDisplay]; - } -} - -- (NSUInteger)lineCount -{ - ASLockScopeSelf(); - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - return 0; -} - -#pragma mark - Truncation Message - -- (void)_invalidateTruncationText -{ - ASLockScopeSelf(); - [self _locked_invalidateTruncationText]; - [self setNeedsDisplay]; -} - -- (void)_locked_invalidateTruncationText -{ - _textContainer.truncationToken = nil; -} - -/** - * @return the additional truncation message range within the as-rendered text. - * Must be called from main thread - */ -- (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange -{ - ASLockScopeSelf(); - - // Check if we even have an additional truncation message. - if (!_additionalTruncationMessage) { - return NSMakeRange(NSNotFound, 0); - } - - // Character location of the unicode ellipsis (the first index after the visible range) - NSInteger truncationTokenIndex = NSMaxRange(visibleRange); - - NSUInteger additionalTruncationMessageLength = _additionalTruncationMessage.length; - // We get the location of the truncation token, then add the length of the - // truncation attributed string +1 for the space between. - return NSMakeRange(truncationTokenIndex + _truncationAttributedText.length + 1, additionalTruncationMessageLength); -} - -/** - * @return the truncation message for the string. If there are both an - * additional truncation message and a truncation attributed string, they will - * be properly composed. - */ -- (NSAttributedString *)_locked_composedTruncationText -{ - DISABLED_ASAssertLocked(__instanceLock__); - NSAttributedString *composedTruncationText = nil; - if (_truncationAttributedText != nil && _additionalTruncationMessage != nil) { - NSMutableAttributedString *newComposedTruncationString = [[NSMutableAttributedString alloc] initWithAttributedString:_truncationAttributedText]; - [newComposedTruncationString.mutableString appendString:@" "]; - [newComposedTruncationString appendAttributedString:_additionalTruncationMessage]; - composedTruncationText = newComposedTruncationString; - } else if (_truncationAttributedText != nil) { - composedTruncationText = _truncationAttributedText; - } else if (_additionalTruncationMessage != nil) { - composedTruncationText = _additionalTruncationMessage; - } else { - composedTruncationText = DefaultTruncationAttributedString(); - } - return [self _locked_prepareTruncationStringForDrawing:composedTruncationText]; -} - -/** - * - cleanses it of core text attributes so TextKit doesn't crash - * - Adds whole-string attributes so the truncation message matches the styling - * of the body text - */ -- (NSAttributedString *)_locked_prepareTruncationStringForDrawing:(NSAttributedString *)truncationString -{ - DISABLED_ASAssertLocked(__instanceLock__); - NSMutableAttributedString *truncationMutableString = [truncationString mutableCopy]; - // Grab the attributes from the full string - if (_attributedText.length > 0) { - NSAttributedString *originalString = _attributedText; - NSInteger originalStringLength = _attributedText.length; - // Add any of the original string's attributes to the truncation string, - // but don't overwrite any of the truncation string's attributes - NSDictionary *originalStringAttributes = [originalString attributesAtIndex:originalStringLength-1 effectiveRange:NULL]; - [truncationString enumerateAttributesInRange:NSMakeRange(0, truncationString.length) options:0 usingBlock: - ^(NSDictionary *attributes, NSRange range, BOOL *stop) { - NSMutableDictionary *futureTruncationAttributes = [originalStringAttributes mutableCopy]; - [futureTruncationAttributes addEntriesFromDictionary:attributes]; - [truncationMutableString setAttributes:futureTruncationAttributes range:range]; - }]; - } - return truncationMutableString; -} - -#if AS_TEXTNODE2_RECORD_ATTRIBUTED_STRINGS -+ (void)_registerAttributedText:(NSAttributedString *)str -{ - static NSMutableArray *array; - static NSLock *lock; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - lock = [NSLock new]; - array = [NSMutableArray new]; - }); - [lock lock]; - [array addObject:str]; - if (array.count % 20 == 0) { - NSLog(@"Got %d strings", (int)array.count); - } - if (array.count == 2000) { - NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"AttributedStrings.plist"]; - NSAssert([NSKeyedArchiver archiveRootObject:array toFile:path], nil); - NSLog(@"Saved to %@", path); - } - [lock unlock]; -} -#endif - -+ (void)enableDebugging -{ - ASTextDebugOption *debugOption = [[ASTextDebugOption alloc] init]; - debugOption.CTLineFillColor = [UIColor colorWithRed:0 green:0.3 blue:1 alpha:0.1]; - [ASTextDebugOption setSharedDebugOption:debugOption]; -} - -- (BOOL)usingExperiment -{ - return YES; -} - -@end diff --git a/Source/ASVideoNode.h b/Source/ASVideoNode.h deleted file mode 100644 index 440c71c5ae..0000000000 --- a/Source/ASVideoNode.h +++ /dev/null @@ -1,171 +0,0 @@ -// -// ASVideoNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_USE_VIDEO - -@class AVAsset, AVPlayer, AVPlayerLayer, AVPlayerItem, AVVideoComposition, AVAudioMix; -@protocol ASVideoNodeDelegate; - -typedef NS_ENUM(NSInteger, ASVideoNodePlayerState) { - ASVideoNodePlayerStateUnknown, - ASVideoNodePlayerStateInitialLoading, - ASVideoNodePlayerStateReadyToPlay, - ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying, - ASVideoNodePlayerStatePlaying, - ASVideoNodePlayerStateLoading, - ASVideoNodePlayerStatePaused, - ASVideoNodePlayerStateFinished -}; - -NS_ASSUME_NONNULL_BEGIN - -// IMPORTANT NOTES: -// 1. Applications using ASVideoNode must link AVFoundation! (this provides the AV* classes below) -// 2. This is a relatively new component of AsyncDisplayKit. It has many useful features, but -// there is room for further expansion and optimization. Please report any issues or requests -// in an issue on GitHub: https://github.com/facebook/AsyncDisplayKit/issues - -@interface ASVideoNode : ASNetworkImageNode - -- (void)play; -- (void)pause; -- (BOOL)isPlaying; -- (void)resetToPlaceholder; - -// TODO: copy -@property (nullable) AVAsset *asset; - -/** - ** @abstract The URL with which the asset was initialized. - ** @discussion Setting the URL will override the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset. - ** @return Current URL the asset was initialized or nil if no URL was given. - **/ -@property (nullable, copy) NSURL *assetURL; - -// TODO: copy both of these. -@property (nullable) AVVideoComposition *videoComposition; -@property (nullable) AVAudioMix *audioMix; - -@property (nullable, readonly) AVPlayer *player; - -// TODO: copy -@property (nullable, readonly) AVPlayerItem *currentItem; - -@property (nullable, nonatomic, readonly) AVPlayerLayer *playerLayer; - - -/** - * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. - * If it leaves the visible interfaceState it will pause but will resume once it has returned. - */ -@property BOOL shouldAutoplay; -@property BOOL shouldAutorepeat; - -@property BOOL muted; -@property BOOL shouldAggressivelyRecoverFromStall; - -@property (readonly) ASVideoNodePlayerState playerState; -//! Defaults to 10000 -@property int32_t periodicTimeObserverTimescale; - -//! Defaults to AVLayerVideoGravityResizeAspect -@property (null_resettable, copy) NSString *gravity; - -@property (nullable, weak) id delegate; - -@end - -@protocol ASVideoNodeDelegate -@optional -/** - * @abstract Delegate method invoked when the node's video has played to its end time. - * @param videoNode The video node has played to its end time. - */ -- (void)videoDidPlayToEnd:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked the node is tapped. - * @param videoNode The video node that was tapped. - * @discussion The video's play state is toggled if this method is not implemented. - */ -- (void)didTapVideoNode:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when player changes state. - * @param videoNode The video node. - * @param state player state before this change. - * @param toState player new state. - * @discussion This method is called after each state change - */ -- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState; -/** - * @abstract Asks delegate if state change is allowed - * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused. - * asks delegate if state change is allowed. - * @param videoNode The video node. - * @param state player state that is going to be set. - * @discussion Delegate method invoked when player changes it's state to - * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused - * and asks delegate if state change is valid - */ -- (BOOL)videoNode:(ASVideoNode*)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state; -/** - * @abstract Delegate method invoked when player playback time is updated. - * @param videoNode The video node. - * @param timeInterval current playback time in seconds. - */ -- (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval; -/** - * @abstract Delegate method invoked when the video player stalls. - * @param videoNode The video node that has experienced the stall - * @param timeInterval Current playback time when the stall happens - */ -- (void)videoNode:(ASVideoNode *)videoNode didStallAtTimeInterval:(NSTimeInterval)timeInterval; -/** - * @abstract Delegate method invoked when the video player starts the inital asset loading - * @param videoNode The videoNode - */ -- (void)videoNodeDidStartInitialLoading:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when the video is done loading the asset and can start the playback - * @param videoNode The videoNode - */ -- (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when the AVPlayerItem for the asset has been set up and can be accessed throught currentItem. - * @param videoNode The videoNode. - * @param currentItem The AVPlayerItem that was constructed from the asset. - */ -- (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem; -/** - * @abstract Delegate method invoked when the video node has recovered from the stall - * @param videoNode The videoNode - */ -- (void)videoNodeDidRecoverFromStall:(ASVideoNode *)videoNode; -/** - * @abstract Delegate method invoked when an error occurs while trying to load an asset - * @param videoNode The videoNode. - * @param key The key of value that failed to load. - * @param asset The asset. - * @param error The error that occurs. - */ -- (void)videoNode:(ASVideoNode *)videoNode didFailToLoadValueForKey:(NSString *)key asset:(AVAsset *)asset error:(NSError *)error; - -@end - -@interface ASVideoNode (Unavailable) - -- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/Source/ASVideoNode.mm b/Source/ASVideoNode.mm deleted file mode 100644 index 141ca2eaca..0000000000 --- a/Source/ASVideoNode.mm +++ /dev/null @@ -1,861 +0,0 @@ -// -// ASVideoNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_USE_VIDEO - -#import -#import -#import -#import -#import -#import - -static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { - return ASObjectIsEqual(asset1, asset2) - || ([asset1 isKindOfClass:[AVURLAsset class]] - && [asset2 isKindOfClass:[AVURLAsset class]] - && ASObjectIsEqual(((AVURLAsset *)asset1).URL, ((AVURLAsset *)asset2).URL)); -} - -static UIViewContentMode ASContentModeFromVideoGravity(NSString *videoGravity) { - if ([videoGravity isEqualToString:AVLayerVideoGravityResizeAspectFill]) { - return UIViewContentModeScaleAspectFill; - } else if ([videoGravity isEqualToString:AVLayerVideoGravityResize]) { - return UIViewContentModeScaleToFill; - } else { - return UIViewContentModeScaleAspectFit; - } -} - -static void *ASVideoNodeContext = &ASVideoNodeContext; -static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp"; -static NSString * const kplaybackBufferEmpty = @"playbackBufferEmpty"; -static NSString * const kStatus = @"status"; -static NSString * const kRate = @"rate"; - -@interface ASVideoNode () -{ - struct { - unsigned int delegateVideNodeShouldChangePlayerStateTo:1; - unsigned int delegateVideoDidPlayToEnd:1; - unsigned int delegateDidTapVideoNode:1; - unsigned int delegateVideoNodeWillChangePlayerStateToState:1; - unsigned int delegateVideoNodeDidPlayToTimeInterval:1; - unsigned int delegateVideoNodeDidStartInitialLoading:1; - unsigned int delegateVideoNodeDidFinishInitialLoading:1; - unsigned int delegateVideoNodeDidSetCurrentItem:1; - unsigned int delegateVideoNodeDidStallAtTimeInterval:1; - unsigned int delegateVideoNodeDidRecoverFromStall:1; - unsigned int delegateVideoNodeDidFailToLoadValueForKey:1; - } _delegateFlags; - - BOOL _shouldBePlaying; - - BOOL _shouldAutorepeat; - BOOL _shouldAutoplay; - BOOL _shouldAggressivelyRecoverFromStall; - BOOL _muted; - - ASVideoNodePlayerState _playerState; - - AVAsset *_asset; - NSURL *_assetURL; - AVVideoComposition *_videoComposition; - AVAudioMix *_audioMix; - - AVPlayerItem *_currentPlayerItem; - AVPlayer *_player; - - id _timeObserver; - int32_t _periodicTimeObserverTimescale; - CMTime _timeObserverInterval; - - CMTime _lastPlaybackTime; - - ASDisplayNode *_playerNode; - NSString *_gravity; -} - -@end - -@implementation ASVideoNode - -@dynamic delegate; - -// TODO: Support preview images with HTTP Live Streaming videos. - -#pragma mark - Construction and Layout - -- (instancetype)initWithCache:(id)cache downloader:(id)downloader -{ - if (!(self = [super initWithCache:cache downloader:downloader])) { - return nil; - } - - self.gravity = AVLayerVideoGravityResizeAspect; - _periodicTimeObserverTimescale = 10000; - [self addTarget:self action:@selector(tapped) forControlEvents:ASControlNodeEventTouchUpInside]; - _lastPlaybackTime = kCMTimeZero; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; - - return self; -} - -- (ASDisplayNode *)constructPlayerNode -{ - ASVideoNode * __weak weakSelf = self; - - return [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ - AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; - playerLayer.player = weakSelf.player; - playerLayer.videoGravity = weakSelf.gravity; - return playerLayer; - }]; -} - -- (AVPlayerItem *)constructPlayerItem -{ - ASDisplayNodeAssertMainThread(); - ASLockScopeSelf(); - - AVPlayerItem *playerItem = nil; - if (_assetURL != nil) { - playerItem = [[AVPlayerItem alloc] initWithURL:_assetURL]; - _asset = [playerItem asset]; - } else { - playerItem = [[AVPlayerItem alloc] initWithAsset:_asset]; - } - - playerItem.videoComposition = _videoComposition; - playerItem.audioMix = _audioMix; - return playerItem; -} - -- (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys -{ - ASDisplayNodeAssertMainThread(); - - for (NSString *key in requestedKeys) { - NSError *error = nil; - AVKeyValueStatus keyStatus = [asset statusOfValueForKey:key error:&error]; - if (keyStatus == AVKeyValueStatusFailed) { - NSLog(@"Asset loading failed with error: %@", error); - if (_delegateFlags.delegateVideoNodeDidFailToLoadValueForKey) { - [self.delegate videoNode:self didFailToLoadValueForKey:key asset:asset error:error]; - } - } - } - - if ([asset isPlayable] == NO) { - NSLog(@"Asset is not playable."); - return; - } - - AVPlayerItem *playerItem = [self constructPlayerItem]; - [self setCurrentItem:playerItem]; - - if (_player != nil) { - [_player replaceCurrentItemWithPlayerItem:playerItem]; - } else { - self.player = [AVPlayer playerWithPlayerItem:playerItem]; - } - - if (_delegateFlags.delegateVideoNodeDidSetCurrentItem) { - [self.delegate videoNode:self didSetCurrentItem:playerItem]; - } - - if (self.image == nil && self.URL == nil) { - [self generatePlaceholderImage]; - } -} - -- (void)addPlayerItemObservers:(AVPlayerItem *)playerItem -{ - if (playerItem == nil) { - return; - } - - [playerItem addObserver:self forKeyPath:kStatus options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - [playerItem addObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - [playerItem addObserver:self forKeyPath:kplaybackBufferEmpty options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter addObserver:self selector:@selector(didPlayToEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(videoNodeDidStall:) name:AVPlayerItemPlaybackStalledNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; - [notificationCenter addObserver:self selector:@selector(errorWhilePlaying:) name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; -} - -- (void)removePlayerItemObservers:(AVPlayerItem *)playerItem -{ - @try { - [playerItem removeObserver:self forKeyPath:kStatus context:ASVideoNodeContext]; - [playerItem removeObserver:self forKeyPath:kPlaybackLikelyToKeepUpKey context:ASVideoNodeContext]; - [playerItem removeObserver:self forKeyPath:kplaybackBufferEmpty context:ASVideoNodeContext]; - } - @catch (NSException * __unused exception) { - NSLog(@"Unnecessary KVO removal"); - } - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; - [notificationCenter removeObserver:self name: AVPlayerItemPlaybackStalledNotification object:playerItem]; - [notificationCenter removeObserver:self name:AVPlayerItemFailedToPlayToEndTimeNotification object:playerItem]; - [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; -} - -- (void)addPlayerObservers:(AVPlayer *)player -{ - if (player == nil) { - return; - } - - [player addObserver:self forKeyPath:kRate options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; - - __weak __typeof(self) weakSelf = self; - _timeObserverInterval = CMTimeMake(1, _periodicTimeObserverTimescale); - _timeObserver = [player addPeriodicTimeObserverForInterval:_timeObserverInterval queue:NULL usingBlock:^(CMTime time){ - [weakSelf periodicTimeObserver:time]; - }]; -} - -- (void) removePlayerObservers:(AVPlayer *)player -{ - if (_timeObserver != nil) { - [player removeTimeObserver:_timeObserver]; - _timeObserver = nil; - } - - @try { - [player removeObserver:self forKeyPath:kRate context:ASVideoNodeContext]; - } - @catch (NSException * __unused exception) { - NSLog(@"Unnecessary KVO removal"); - } -} - -- (void)layout -{ - [super layout]; - // The _playerNode wraps AVPlayerLayer, and therefore should extend across the entire bounds. - _playerNode.frame = self.bounds; -} - -- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize -{ - ASDisplayNode *playerNode = ASLockedSelf(_playerNode); - - CGSize calculatedSize = constrainedSize; - - // Prevent crashes through if infinite width or height - if (isinf(calculatedSize.width) || isinf(calculatedSize.height)) { - ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoNode"); - calculatedSize = CGSizeZero; - } - - if (playerNode != nil) { - playerNode.style.preferredSize = calculatedSize; - [playerNode layoutThatFits:ASSizeRangeMake(CGSizeZero, calculatedSize)]; - } - - return calculatedSize; -} - -- (void)generatePlaceholderImage -{ - ASVideoNode * __weak weakSelf = self; - AVAsset *asset = self.asset; - - [self imageAtTime:kCMTimeZero completionHandler:^(UIImage *image) { - ASPerformBlockOnMainThread(^{ - // Ensure the asset hasn't changed since the image request was made - if (ASAssetIsEqual(weakSelf.asset, asset)) { - [weakSelf setVideoPlaceholderImage:image]; - } - }); - }]; -} - -- (void)imageAtTime:(CMTime)imageTime completionHandler:(void(^)(UIImage *image))completionHandler -{ - ASPerformBlockOnBackgroundThread(^{ - AVAsset *asset = self->_asset; - - // Skip the asset image generation if we don't have any tracks available that are capable of supporting it - NSArray* visualAssetArray = [asset tracksWithMediaCharacteristic:AVMediaCharacteristicVisual]; - if (visualAssetArray.count == 0) { - completionHandler(nil); - return; - } - - AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; - previewImageGenerator.appliesPreferredTrackTransform = YES; - previewImageGenerator.videoComposition = self->_videoComposition; - - [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]] - completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { - if (error != nil && result != AVAssetImageGeneratorCancelled) { - NSLog(@"Asset preview image generation failed with error: %@", error); - } - completionHandler(image ? [UIImage imageWithCGImage:image] : nil); - }]; - }); -} - -- (void)setVideoPlaceholderImage:(UIImage *)image -{ - NSString *gravity = self.gravity; - - if (image != nil) { - self.contentMode = ASContentModeFromVideoGravity(gravity); - } - self.image = image; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - AS::UniqueLock l(__instanceLock__); - - if (object == _currentPlayerItem) { - if ([keyPath isEqualToString:kStatus]) { - if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { - if (self.playerState != ASVideoNodePlayerStatePlaying) { - self.playerState = ASVideoNodePlayerStateReadyToPlay; - if (_shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { - l.unlock(); - [self play]; - l.lock(); - } - } - // If we don't yet have a placeholder image update it now that we should have data available for it - if (self.image == nil && self.URL == nil) { - [self generatePlaceholderImage]; - } - } - } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { - BOOL likelyToKeepUp = [change[NSKeyValueChangeNewKey] boolValue]; - if (likelyToKeepUp && self.playerState == ASVideoNodePlayerStatePlaying) { - return; - } - if (!likelyToKeepUp) { - self.playerState = ASVideoNodePlayerStateLoading; - } else if (self.playerState != ASVideoNodePlayerStateFinished) { - self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; - } - if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || likelyToKeepUp) && ASInterfaceStateIncludesVisible(self.interfaceState)) { - if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { - [self.delegate videoNodeDidRecoverFromStall:self]; - } - - l.unlock(); - [self play]; // autoresume after buffer catches up - // NOTE: Early return without re-locking. - return; - } - } else if ([keyPath isEqualToString:kplaybackBufferEmpty]) { - if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == YES && ASInterfaceStateIncludesVisible(self.interfaceState)) { - self.playerState = ASVideoNodePlayerStateLoading; - } - } - } else if (object == _player) { - if ([keyPath isEqualToString:kRate]) { - if ([change[NSKeyValueChangeNewKey] floatValue] == 0.0) { - if (self.playerState == ASVideoNodePlayerStatePlaying) { - self.playerState = ASVideoNodePlayerStatePaused; - } - } else { - self.playerState = ASVideoNodePlayerStatePlaying; - } - } - } - - // NOTE: Early return above. -} - -- (void)tapped -{ - if (_delegateFlags.delegateDidTapVideoNode) { - [self.delegate didTapVideoNode:self]; - - } else { - if (_shouldBePlaying) { - [self pause]; - } else { - [self play]; - } - } -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - ASLockScopeSelf(); - AVAsset *asset = self.asset; - // Return immediately if the asset is nil; - if (asset == nil || self.playerState != ASVideoNodePlayerStateUnknown) { - return; - } - - self.playerState = ASVideoNodePlayerStateLoading; - if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) { - [self.delegate videoNodeDidStartInitialLoading:self]; - } - - NSArray *requestedKeys = @[@"playable"]; - [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{ - ASPerformBlockOnMainThread(^{ - if (self->_delegateFlags.delegateVideoNodeDidFinishInitialLoading) { - [self.delegate videoNodeDidFinishInitialLoading:self]; - } - [self prepareToPlayAsset:asset withKeys:requestedKeys]; - }); - }]; -} - -- (void)periodicTimeObserver:(CMTime)time -{ - NSTimeInterval timeInSeconds = CMTimeGetSeconds(time); - if (timeInSeconds <= 0) { - return; - } - - if (_delegateFlags.delegateVideoNodeDidPlayToTimeInterval) { - [self.delegate videoNode:self didPlayToTimeInterval:timeInSeconds]; - - } -} - -- (void)didExitPreloadState -{ - [super didExitPreloadState]; - - { - ASLockScopeSelf(); - - self.player = nil; - self.currentItem = nil; - self.playerState = ASVideoNodePlayerStateUnknown; - } -} - -- (void)didEnterVisibleState -{ - [super didEnterVisibleState]; - - BOOL shouldPlay = NO; - { - ASLockScopeSelf(); - if (_shouldBePlaying || _shouldAutoplay) { - if (_player != nil && CMTIME_IS_VALID(_lastPlaybackTime)) { - [_player seekToTime:_lastPlaybackTime]; - } - shouldPlay = YES; - } - } - - if (shouldPlay) { - [self play]; - } -} - -- (void)didExitVisibleState -{ - [super didExitVisibleState]; - - ASLockScopeSelf(); - - if (_shouldBePlaying) { - [self pause]; - if (_player != nil && CMTIME_IS_VALID(_player.currentTime)) { - _lastPlaybackTime = _player.currentTime; - } - _shouldBePlaying = YES; - } -} - -#pragma mark - Video Properties - -- (void)setPlayerState:(ASVideoNodePlayerState)playerState -{ - ASLockScopeSelf(); - - ASVideoNodePlayerState oldState = _playerState; - - if (oldState == playerState) { - return; - } - - if (_delegateFlags.delegateVideoNodeWillChangePlayerStateToState) { - [self.delegate videoNode:self willChangePlayerState:oldState toState:playerState]; - } - - _playerState = playerState; -} - -- (void)setAssetURL:(NSURL *)assetURL -{ - ASDisplayNodeAssertMainThread(); - - if (ASObjectIsEqual(assetURL, self.assetURL) == NO) { - [self setAndFetchAsset:[AVURLAsset assetWithURL:assetURL] url:assetURL]; - } -} - -- (NSURL *)assetURL -{ - ASLockScopeSelf(); - - if (_assetURL != nil) { - return _assetURL; - } else if ([_asset isKindOfClass:AVURLAsset.class]) { - return ((AVURLAsset *)_asset).URL; - } - - return nil; -} - -- (void)setAsset:(AVAsset *)asset -{ - ASDisplayNodeAssertMainThread(); - - if (ASAssetIsEqual(asset, self.asset) == NO) { - [self setAndFetchAsset:asset url:nil]; - } -} - -- (AVAsset *)asset -{ - ASLockScopeSelf(); - return _asset; -} - -- (void)setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL -{ - ASDisplayNodeAssertMainThread(); - - [self didExitPreloadState]; - - { - ASLockScopeSelf(); - self.videoPlaceholderImage = nil; - _asset = asset; - _assetURL = assetURL; - } - - [self setNeedsPreload]; -} - -- (void)setVideoComposition:(AVVideoComposition *)videoComposition -{ - ASLockScopeSelf(); - - _videoComposition = videoComposition; - _currentPlayerItem.videoComposition = videoComposition; -} - -- (AVVideoComposition *)videoComposition -{ - ASLockScopeSelf(); - return _videoComposition; -} - -- (void)setAudioMix:(AVAudioMix *)audioMix -{ - ASLockScopeSelf(); - - _audioMix = audioMix; - _currentPlayerItem.audioMix = audioMix; -} - -- (AVAudioMix *)audioMix -{ - ASLockScopeSelf(); - return _audioMix; -} - -- (AVPlayer *)player -{ - ASLockScopeSelf(); - return _player; -} - -- (AVPlayerLayer *)playerLayer -{ - ASLockScopeSelf(); - return (AVPlayerLayer *)_playerNode.layer; -} - -- (void)setDelegate:(id)delegate -{ - [super setDelegate:delegate]; - - if (delegate == nil) { - memset(&_delegateFlags, 0, sizeof(_delegateFlags)); - } else { - _delegateFlags.delegateVideNodeShouldChangePlayerStateTo = [delegate respondsToSelector:@selector(videoNode:shouldChangePlayerStateTo:)]; - _delegateFlags.delegateVideoDidPlayToEnd = [delegate respondsToSelector:@selector(videoDidPlayToEnd:)]; - _delegateFlags.delegateDidTapVideoNode = [delegate respondsToSelector:@selector(didTapVideoNode:)]; - _delegateFlags.delegateVideoNodeWillChangePlayerStateToState = [delegate respondsToSelector:@selector(videoNode:willChangePlayerState:toState:)]; - _delegateFlags.delegateVideoNodeDidPlayToTimeInterval = [delegate respondsToSelector:@selector(videoNode:didPlayToTimeInterval:)]; - _delegateFlags.delegateVideoNodeDidStartInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidStartInitialLoading:)]; - _delegateFlags.delegateVideoNodeDidFinishInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidFinishInitialLoading:)]; - _delegateFlags.delegateVideoNodeDidSetCurrentItem = [delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)]; - _delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)]; - _delegateFlags.delegateVideoNodeDidRecoverFromStall = [delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)]; - _delegateFlags.delegateVideoNodeDidFailToLoadValueForKey = [delegate respondsToSelector:@selector(videoNode:didFailToLoadValueForKey:asset:error:)]; - } -} - -- (void)setGravity:(NSString *)gravity -{ - ASLockScopeSelf(); - if (!gravity) { - gravity = AVLayerVideoGravityResizeAspect; - } - - if (!ASCompareAssignObjects(_gravity, gravity)) { - return; - } - - if (_playerNode.isNodeLoaded) { - ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity; - } - self.contentMode = ASContentModeFromVideoGravity(gravity); - _gravity = gravity; -} - -- (NSString *)gravity -{ - ASLockScopeSelf(); - return _gravity; -} - -- (BOOL)muted -{ - ASLockScopeSelf(); - return _muted; -} - -- (void)setMuted:(BOOL)muted -{ - ASLockScopeSelf(); - - _player.muted = muted; - _muted = muted; -} - -#pragma mark - Video Playback - -- (void)play -{ - ASLockScopeSelf(); - - if (![self isStateChangeValid:ASVideoNodePlayerStatePlaying]) { - return; - } - - if (_player == nil) { - ASUnlockScope(self); - [self setNeedsPreload]; - } - - if (_playerNode == nil) { - _playerNode = [self constructPlayerNode]; - - { - ASUnlockScope(self); - [self addSubnode:_playerNode]; - } - - [self setNeedsLayout]; - } - - - [_player play]; - _shouldBePlaying = YES; -} - -- (BOOL)ready -{ - return _currentPlayerItem.status == AVPlayerItemStatusReadyToPlay; -} - -- (void)pause -{ - ASLockScopeSelf(); - if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) { - return; - } - [_player pause]; - _shouldBePlaying = NO; -} - -- (BOOL)isPlaying -{ - ASLockScopeSelf(); - - return (_player.rate > 0 && !_player.error); -} - -- (BOOL)isStateChangeValid:(ASVideoNodePlayerState)state -{ - if (_delegateFlags.delegateVideNodeShouldChangePlayerStateTo) { - if (![self.delegate videoNode:self shouldChangePlayerStateTo:state]) { - return NO; - } - } - return YES; -} - -- (void)resetToPlaceholder -{ - ASLockScopeSelf(); - - if (_playerNode != nil) { - [_playerNode removeFromSupernode]; - _playerNode = nil; - } - - [_player seekToTime:kCMTimeZero]; - [self pause]; -} - - -#pragma mark - Playback observers - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - if (self.shouldBePlaying && self.isVisible) { - [self play]; - } -} - -- (void)didPlayToEnd:(NSNotification *)notification -{ - self.playerState = ASVideoNodePlayerStateFinished; - if (_delegateFlags.delegateVideoDidPlayToEnd) { - [self.delegate videoDidPlayToEnd:self]; - } - - if (_shouldAutorepeat) { - [_player seekToTime:kCMTimeZero]; - [self play]; - } else { - [self pause]; - } -} - -- (void)videoNodeDidStall:(NSNotification *)notification -{ - self.playerState = ASVideoNodePlayerStateLoading; - if (_delegateFlags.delegateVideoNodeDidStallAtTimeInterval) { - [self.delegate videoNode:self didStallAtTimeInterval:CMTimeGetSeconds(_player.currentItem.currentTime)]; - } -} - -- (void)errorWhilePlaying:(NSNotification *)notification -{ - if ([notification.name isEqualToString:AVPlayerItemFailedToPlayToEndTimeNotification]) { - NSLog(@"Failed to play video"); - } else if ([notification.name isEqualToString:AVPlayerItemNewErrorLogEntryNotification]) { - AVPlayerItem *item = (AVPlayerItem *)notification.object; - AVPlayerItemErrorLogEvent *logEvent = item.errorLog.events.lastObject; - NSLog(@"AVPlayerItem error log entry added for video with error %@ status %@", item.error, - (item.status == AVPlayerItemStatusFailed ? @"FAILED" : [NSString stringWithFormat:@"%ld", (long)item.status])); - NSLog(@"Item is %@", item); - - if (logEvent) { - NSLog(@"Log code %ld domain %@ comment %@", (long)logEvent.errorStatusCode, logEvent.errorDomain, logEvent.errorComment); - } - } -} - -#pragma mark - Internal Properties - -- (AVPlayerItem *)currentItem -{ - ASLockScopeSelf(); - return _currentPlayerItem; -} - -- (void)setCurrentItem:(AVPlayerItem *)currentItem -{ - ASLockScopeSelf(); - - [self removePlayerItemObservers:_currentPlayerItem]; - - _currentPlayerItem = currentItem; - - if (currentItem != nil) { - [self addPlayerItemObservers:currentItem]; - } -} - -- (ASDisplayNode *)playerNode -{ - ASLockScopeSelf(); - return _playerNode; -} - -- (void)setPlayerNode:(ASDisplayNode *)playerNode -{ - { - ASLockScopeSelf(); - _playerNode = playerNode; - } - - [self setNeedsLayout]; -} - -- (void)setPlayer:(AVPlayer *)player -{ - ASLockScopeSelf(); - - [self removePlayerObservers:_player]; - - _player = player; - player.muted = _muted; - ((AVPlayerLayer *)_playerNode.layer).player = player; - - if (player != nil) { - [self addPlayerObservers:player]; - } -} - -- (BOOL)shouldBePlaying -{ - ASLockScopeSelf(); - return _shouldBePlaying; -} - -- (void)setShouldBePlaying:(BOOL)shouldBePlaying -{ - ASLockScopeSelf(); - _shouldBePlaying = shouldBePlaying; -} - -#pragma mark - Lifecycle - -- (void)dealloc -{ - [self removePlayerItemObservers:_currentPlayerItem]; - [self removePlayerObservers:_player]; - - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; -} - -@end - -#endif diff --git a/Source/ASVideoPlayerNode.h b/Source/ASVideoPlayerNode.h deleted file mode 100644 index 4483f601eb..0000000000 --- a/Source/ASVideoPlayerNode.h +++ /dev/null @@ -1,225 +0,0 @@ -// -// ASVideoPlayerNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_USE_VIDEO - -#if TARGET_OS_IOS -#import -#import -#import -#import - -@class AVAsset; -@class ASButtonNode; -@protocol ASVideoPlayerNodeDelegate; - -typedef NS_ENUM(NSInteger, ASVideoPlayerNodeControlType) { - ASVideoPlayerNodeControlTypePlaybackButton, - ASVideoPlayerNodeControlTypeElapsedText, - ASVideoPlayerNodeControlTypeDurationText, - ASVideoPlayerNodeControlTypeScrubber, - ASVideoPlayerNodeControlTypeFullScreenButton, - ASVideoPlayerNodeControlTypeFlexGrowSpacer, -}; - -NS_ASSUME_NONNULL_BEGIN - -@interface ASVideoPlayerNode : ASDisplayNode - -@property (nullable, nonatomic, weak) id delegate; - -@property (nonatomic, readonly) CMTime duration; - -@property (nonatomic) BOOL controlsDisabled; - -#pragma mark - ASVideoNode property proxy -/** - * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. - * If it leaves the visible interfaceState it will pause but will resume once it has returned. - */ -@property (nonatomic) BOOL shouldAutoPlay; -@property (nonatomic) BOOL shouldAutoRepeat; -@property (nonatomic) BOOL muted; -@property (nonatomic, readonly) ASVideoNodePlayerState playerState; -@property (nonatomic) BOOL shouldAggressivelyRecoverFromStall; -@property (nullable, nonatomic) NSURL *placeholderImageURL; - -@property (nullable, nonatomic) AVAsset *asset; -/** - ** @abstract The URL with which the asset was initialized. - ** @discussion Setting the URL will override the current asset with a newly created AVURLAsset created from the given URL, and AVAsset *asset will point to that newly created AVURLAsset. Please don't set both assetURL and asset. - ** @return Current URL the asset was initialized or nil if no URL was given. - **/ -@property (nullable, nonatomic) NSURL *assetURL; - -/// You should never set any value on the backing video node. Use exclusivively the video player node to set properties -@property (nonatomic, readonly) ASVideoNode *videoNode; - -//! Defaults to 10000 -@property (nonatomic) int32_t periodicTimeObserverTimescale; -//! Defaults to AVLayerVideoGravityResizeAspect -@property (nonatomic, copy) NSString *gravity; - -#pragma mark - Lifecycle -- (instancetype)initWithURL:(NSURL *)URL; -- (instancetype)initWithAsset:(AVAsset *)asset; -- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix; - -#pragma mark - Public API -- (void)seekToTime:(CGFloat)percentComplete; -- (void)play; -- (void)pause; -- (BOOL)isPlaying; -- (void)resetToPlaceholder; - -@end - -#pragma mark - ASVideoPlayerNodeDelegate - -@protocol ASVideoPlayerNodeDelegate -@optional -/** - * @abstract Delegate method invoked before creating controlbar controls - * @param videoPlayer The sender - */ -- (NSArray *)videoPlayerNodeNeededDefaultControls:(ASVideoPlayerNode*)videoPlayer; - -/** - * @abstract Delegate method invoked before creating default controls, asks delegate for custom controls dictionary. - * This dictionary must constain only ASDisplayNode subclass objects. - * @param videoPlayer The sender - * @discussion - This method is invoked only when developer implements videoPlayerNodeLayoutSpec:forControls:forMaximumSize: - * and gives ability to add custom constrols to ASVideoPlayerNode, for example mute button. - */ -- (NSDictionary *)videoPlayerNodeCustomControls:(ASVideoPlayerNode*)videoPlayer; - -/** - * @abstract Delegate method invoked in layoutSpecThatFits: - * @param videoPlayer The sender - * @param controls - Dictionary of controls which are used in videoPlayer; Dictionary keys are ASVideoPlayerNodeControlType - * @param maxSize - Maximum size for ASVideoPlayerNode - * @discussion - Developer can layout whole ASVideoPlayerNode as he wants. ASVideoNode is locked and it can't be changed - */ -- (ASLayoutSpec *)videoPlayerNodeLayoutSpec:(ASVideoPlayerNode *)videoPlayer - forControls:(NSDictionary *)controls - forMaximumSize:(CGSize)maxSize; - -#pragma mark Text delegate methods -/** - * @abstract Delegate method invoked before creating ASVideoPlayerNodeControlTypeElapsedText and ASVideoPlayerNodeControlTypeDurationText - * @param videoPlayer The sender - * @param timeLabelType The of the time label - */ -- (NSDictionary *)videoPlayerNodeTimeLabelAttributes:(ASVideoPlayerNode *)videoPlayer timeLabelType:(ASVideoPlayerNodeControlType)timeLabelType; -- (NSString *)videoPlayerNode:(ASVideoPlayerNode *)videoPlayerNode - timeStringForTimeLabelType:(ASVideoPlayerNodeControlType)timeLabelType - forTime:(CMTime)time; - -#pragma mark Scrubber delegate methods -- (UIColor *)videoPlayerNodeScrubberMaximumTrackTint:(ASVideoPlayerNode *)videoPlayer; -- (UIColor *)videoPlayerNodeScrubberMinimumTrackTint:(ASVideoPlayerNode *)videoPlayer; -- (UIColor *)videoPlayerNodeScrubberThumbTint:(ASVideoPlayerNode *)videoPlayer; -- (UIImage *)videoPlayerNodeScrubberThumbImage:(ASVideoPlayerNode *)videoPlayer; - -#pragma mark - Spinner delegate methods -- (UIColor *)videoPlayerNodeSpinnerTint:(ASVideoPlayerNode *)videoPlayer; -- (UIActivityIndicatorViewStyle)videoPlayerNodeSpinnerStyle:(ASVideoPlayerNode *)videoPlayer; - -#pragma mark - Playback button delegate methods -- (UIColor *)videoPlayerNodePlaybackButtonTint:(ASVideoPlayerNode *)videoPlayer; - -#pragma mark - Fullscreen button delegate methods - -- (UIImage *)videoPlayerNodeFullScreenButtonImage:(ASVideoPlayerNode *)videoPlayer; - - -#pragma mark ASVideoNodeDelegate proxy methods -/** - * @abstract Delegate method invoked when ASVideoPlayerNode is taped. - * @param videoPlayer The ASVideoPlayerNode that was tapped. - */ -- (void)didTapVideoPlayerNode:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when fullcreen button is taped. - * @param buttonNode The fullscreen button node that was tapped. - */ -- (void)didTapFullScreenButtonNode:(ASButtonNode *)buttonNode; - -/** - * @abstract Delegate method invoked when ASVideoNode playback time is updated. - * @param videoPlayer The video player node - * @param time current playback time. - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didPlayToTime:(CMTime)time; - -/** - * @abstract Delegate method invoked when ASVideoNode changes state. - * @param videoPlayer The ASVideoPlayerNode whose ASVideoNode is changing state. - * @param state ASVideoNode state before this change. - * @param toState ASVideoNode new state. - * @discussion This method is called after each state change - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer willChangeVideoNodeState:(ASVideoNodePlayerState)state toVideoNodeState:(ASVideoNodePlayerState)toState; - -/** - * @abstract Delegate method is invoked when ASVideoNode decides to change state. - * @param videoPlayer The ASVideoPlayerNode whose ASVideoNode is changing state. - * @param state ASVideoNode that is going to be set. - * @discussion Delegate method invoked when player changes it's state to - * ASVideoNodePlayerStatePlaying or ASVideoNodePlayerStatePaused - * and asks delegate if state change is valid - */ -- (BOOL)videoPlayerNode:(ASVideoPlayerNode*)videoPlayer shouldChangeVideoNodeStateTo:(ASVideoNodePlayerState)state; - -/** - * @abstract Delegate method invoked when the ASVideoNode has played to its end time. - * @param videoPlayer The video node has played to its end time. - */ -- (void)videoPlayerNodeDidPlayToEnd:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when the ASVideoNode has constructed its AVPlayerItem for the asset. - * @param videoPlayer The video player node. - * @param currentItem The AVPlayerItem that was constructed from the asset. - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didSetCurrentItem:(AVPlayerItem *)currentItem; - -/** - * @abstract Delegate method invoked when the ASVideoNode stalls. - * @param videoPlayer The video player node that has experienced the stall - * @param timeInterval Current playback time when the stall happens - */ -- (void)videoPlayerNode:(ASVideoPlayerNode *)videoPlayer didStallAtTimeInterval:(NSTimeInterval)timeInterval; - -/** - * @abstract Delegate method invoked when the ASVideoNode starts the inital asset loading - * @param videoPlayer The videoPlayer - */ -- (void)videoPlayerNodeDidStartInitialLoading:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when the ASVideoNode is done loading the asset and can start the playback - * @param videoPlayer The videoPlayer - */ -- (void)videoPlayerNodeDidFinishInitialLoading:(ASVideoPlayerNode *)videoPlayer; - -/** - * @abstract Delegate method invoked when the ASVideoNode has recovered from the stall - * @param videoPlayer The videoplayer - */ -- (void)videoPlayerNodeDidRecoverFromStall:(ASVideoPlayerNode *)videoPlayer; - - -@end -NS_ASSUME_NONNULL_END -#endif // TARGET_OS_IOS - -#endif diff --git a/Source/ASVideoPlayerNode.mm b/Source/ASVideoPlayerNode.mm deleted file mode 100644 index 36b4a3fb53..0000000000 --- a/Source/ASVideoPlayerNode.mm +++ /dev/null @@ -1,1017 +0,0 @@ -// -// ASVideoPlayerNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_USE_VIDEO -#if TARGET_OS_IOS - -#import - -#import -#import -#import - -static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; - -@interface ASVideoPlayerNode() -{ - __weak id _delegate; - - struct { - unsigned int delegateNeededDefaultControls:1; - unsigned int delegateCustomControls:1; - unsigned int delegateSpinnerTintColor:1; - unsigned int delegateSpinnerStyle:1; - unsigned int delegatePlaybackButtonTint:1; - unsigned int delegateFullScreenButtonImage:1; - unsigned int delegateScrubberMaximumTrackTintColor:1; - unsigned int delegateScrubberMinimumTrackTintColor:1; - unsigned int delegateScrubberThumbTintColor:1; - unsigned int delegateScrubberThumbImage:1; - unsigned int delegateTimeLabelAttributes:1; - unsigned int delegateTimeLabelAttributedString:1; - unsigned int delegateLayoutSpecForControls:1; - unsigned int delegateVideoNodeDidPlayToTime:1; - unsigned int delegateVideoNodeWillChangeState:1; - unsigned int delegateVideoNodeShouldChangeState:1; - unsigned int delegateVideoNodePlaybackDidFinish:1; - unsigned int delegateDidTapVideoPlayerNode:1; - unsigned int delegateDidTapFullScreenButtonNode:1; - unsigned int delegateVideoPlayerNodeDidSetCurrentItem:1; - unsigned int delegateVideoPlayerNodeDidStallAtTimeInterval:1; - unsigned int delegateVideoPlayerNodeDidStartInitialLoading:1; - unsigned int delegateVideoPlayerNodeDidFinishInitialLoading:1; - unsigned int delegateVideoPlayerNodeDidRecoverFromStall:1; - } _delegateFlags; - - // The asset passed in the initializer will be assigned as pending asset. As soon as the first - // preload state happened all further asset handling is made by using the asset of the backing - // video node - AVAsset *_pendingAsset; - - // The backing video node. Ideally this is the source of truth and the video player node should - // not handle anything related to asset management - ASVideoNode *_videoNode; - - NSArray *_neededDefaultControls; - - NSMutableDictionary *_cachedControls; - - ASDefaultPlaybackButton *_playbackButtonNode; - ASButtonNode *_fullScreenButtonNode; - ASTextNode *_elapsedTextNode; - ASTextNode *_durationTextNode; - ASDisplayNode *_scrubberNode; - ASStackLayoutSpec *_controlFlexGrowSpacerSpec; - ASDisplayNode *_spinnerNode; - - BOOL _isSeeking; - CMTime _duration; - - BOOL _controlsDisabled; - - BOOL _shouldAutoPlay; - BOOL _shouldAutoRepeat; - BOOL _muted; - int32_t _periodicTimeObserverTimescale; - NSString *_gravity; - - BOOL _shouldAggressivelyRecoverFromStall; - - UIColor *_defaultControlsColor; -} - -@end - -@implementation ASVideoPlayerNode - -@dynamic placeholderImageURL; - -#pragma mark - Lifecycle - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - [self _initControlsAndVideoNode]; - - return self; -} - -- (instancetype)initWithAsset:(AVAsset *)asset -{ - if (!(self = [self init])) { - return nil; - } - - _pendingAsset = asset; - - return self; -} - -- (instancetype)initWithURL:(NSURL *)URL -{ - return [self initWithAsset:[AVAsset assetWithURL:URL]]; -} - -- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix -{ - if (!(self = [self initWithAsset:asset])) { - return nil; - } - - _videoNode.videoComposition = videoComposition; - _videoNode.audioMix = audioMix; - - return self; -} - -- (void)_initControlsAndVideoNode -{ - _defaultControlsColor = [UIColor whiteColor]; - _cachedControls = [[NSMutableDictionary alloc] init]; - - _videoNode = [[ASVideoNode alloc] init]; - _videoNode.delegate = self; - [self addSubnode:_videoNode]; -} - -#pragma mark - Setter / Getter - -- (void)setAssetURL:(NSURL *)assetURL -{ - ASDisplayNodeAssertMainThread(); - - self.asset = [AVAsset assetWithURL:assetURL]; -} - -- (NSURL *)assetURL -{ - NSURL *url = nil; - { - ASLockScopeSelf(); - if ([_pendingAsset isKindOfClass:AVURLAsset.class]) { - url = ((AVURLAsset *)_pendingAsset).URL; - } - } - - return url ?: _videoNode.assetURL; -} - -- (void)setAsset:(AVAsset *)asset -{ - ASDisplayNodeAssertMainThread(); - - [self lock]; - - // Clean out pending asset - _pendingAsset = nil; - - // Set asset based on interface state - if ((ASInterfaceStateIncludesPreload(self.interfaceState))) { - // Don't hold the lock while accessing the subnode - [self unlock]; - _videoNode.asset = asset; - return; - } - - _pendingAsset = asset; - [self unlock]; -} - -- (AVAsset *)asset -{ - return ASLockedSelf(_pendingAsset) ?: _videoNode.asset; -} - -#pragma mark - ASDisplayNode - -- (void)didLoad -{ - [super didLoad]; - - [self createControls]; -} - -- (void)didEnterPreloadState -{ - [super didEnterPreloadState]; - - AVAsset *pendingAsset = nil; - { - ASLockScopeSelf(); - pendingAsset = _pendingAsset; - _pendingAsset = nil; - } - - // If we enter preload state we apply the pending asset to load to the video node so it can start and fetch the asset - if (pendingAsset != nil && _videoNode.asset != pendingAsset) { - _videoNode.asset = pendingAsset; - } -} - -- (BOOL)supportsLayerBacking -{ - return NO; -} - -#pragma mark - UI - -- (void)createControls -{ - { - ASLockScopeSelf(); - - if (_controlsDisabled) { - return; - } - - if (_neededDefaultControls == nil) { - _neededDefaultControls = [self createDefaultControlElementArray]; - } - - if (_cachedControls == nil) { - _cachedControls = [[NSMutableDictionary alloc] init]; - } - - for (id object in _neededDefaultControls) { - ASVideoPlayerNodeControlType type = (ASVideoPlayerNodeControlType)[object integerValue]; - switch (type) { - case ASVideoPlayerNodeControlTypePlaybackButton: - [self _locked_createPlaybackButton]; - break; - case ASVideoPlayerNodeControlTypeElapsedText: - [self _locked_createElapsedTextField]; - break; - case ASVideoPlayerNodeControlTypeDurationText: - [self _locked_createDurationTextField]; - break; - case ASVideoPlayerNodeControlTypeScrubber: - [self _locked_createScrubber]; - break; - case ASVideoPlayerNodeControlTypeFullScreenButton: - [self _locked_createFullScreenButton]; - break; - case ASVideoPlayerNodeControlTypeFlexGrowSpacer: - [self _locked_createControlFlexGrowSpacer]; - break; - default: - break; - } - } - - if (_delegateFlags.delegateCustomControls && _delegateFlags.delegateLayoutSpecForControls) { - NSDictionary *customControls = [_delegate videoPlayerNodeCustomControls:self]; - std::vector subnodes; - for (id key in customControls) { - id node = customControls[key]; - if (![node isKindOfClass:[ASDisplayNode class]]) { - continue; - } - - subnodes.push_back(node); - [_cachedControls setObject:node forKey:key]; - } - - { - ASUnlockScope(self); - for (ASDisplayNode *subnode : subnodes) { - [self addSubnode:subnode]; - } - } - } - } - - ASPerformBlockOnMainThread(^{ - [self setNeedsLayout]; - }); -} - -- (NSArray *)createDefaultControlElementArray -{ - if (_delegateFlags.delegateNeededDefaultControls) { - return [_delegate videoPlayerNodeNeededDefaultControls:self]; - } - - return @[ @(ASVideoPlayerNodeControlTypePlaybackButton), - @(ASVideoPlayerNodeControlTypeElapsedText), - @(ASVideoPlayerNodeControlTypeScrubber), - @(ASVideoPlayerNodeControlTypeDurationText) ]; -} - -- (void)removeControls -{ - NSMutableDictionary *cachedControls = nil; - { - ASLockScope(self); - - // Grab the cached controls for removing it - cachedControls = [_cachedControls copy]; - [self _locked_cleanCachedControls]; - } - - for (ASDisplayNode *node in [cachedControls objectEnumerator]) { - [node removeFromSupernode]; - } -} - -- (void)_locked_cleanCachedControls -{ - [_cachedControls removeAllObjects]; - - _playbackButtonNode = nil; - _fullScreenButtonNode = nil; - _elapsedTextNode = nil; - _durationTextNode = nil; - _scrubberNode = nil; -} - -- (void)_locked_createPlaybackButton -{ - DISABLED_ASAssertLocked(__instanceLock__); - - if (_playbackButtonNode == nil) { - _playbackButtonNode = [[ASDefaultPlaybackButton alloc] init]; - _playbackButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0); - - if (_delegateFlags.delegatePlaybackButtonTint) { - _playbackButtonNode.tintColor = [_delegate videoPlayerNodePlaybackButtonTint:self]; - } else { - _playbackButtonNode.tintColor = _defaultControlsColor; - } - - if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { - _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; - } - - [_playbackButtonNode addTarget:self action:@selector(didTapPlaybackButton:) forControlEvents:ASControlNodeEventTouchUpInside]; - [_cachedControls setObject:_playbackButtonNode forKey:@(ASVideoPlayerNodeControlTypePlaybackButton)]; - } - - { - ASUnlockScope(self); - [self addSubnode:_playbackButtonNode]; - } -} - -- (void)_locked_createFullScreenButton -{ - DISABLED_ASAssertLocked(__instanceLock__); - - if (_fullScreenButtonNode == nil) { - _fullScreenButtonNode = [[ASButtonNode alloc] init]; - _fullScreenButtonNode.style.preferredSize = CGSizeMake(16.0, 22.0); - - if (_delegateFlags.delegateFullScreenButtonImage) { - [_fullScreenButtonNode setImage:[_delegate videoPlayerNodeFullScreenButtonImage:self] forState:UIControlStateNormal]; - } - - [_fullScreenButtonNode addTarget:self action:@selector(didTapFullScreenButton:) forControlEvents:ASControlNodeEventTouchUpInside]; - [_cachedControls setObject:_fullScreenButtonNode forKey:@(ASVideoPlayerNodeControlTypeFullScreenButton)]; - } - - { - ASUnlockScope(self); - [self addSubnode:_fullScreenButtonNode]; - } -} - -- (void)_locked_createElapsedTextField -{ - DISABLED_ASAssertLocked(__instanceLock__); - - if (_elapsedTextNode == nil) { - _elapsedTextNode = [[ASTextNode alloc] init]; - _elapsedTextNode.attributedText = [self timeLabelAttributedStringForString:@"00:00" - forControlType:ASVideoPlayerNodeControlTypeElapsedText]; - _elapsedTextNode.truncationMode = NSLineBreakByClipping; - - [_cachedControls setObject:_elapsedTextNode forKey:@(ASVideoPlayerNodeControlTypeElapsedText)]; - } - { - ASUnlockScope(self); - [self addSubnode:_elapsedTextNode]; - } -} - -- (void)_locked_createDurationTextField -{ - DISABLED_ASAssertLocked(__instanceLock__); - - if (_durationTextNode == nil) { - _durationTextNode = [[ASTextNode alloc] init]; - _durationTextNode.attributedText = [self timeLabelAttributedStringForString:@"00:00" - forControlType:ASVideoPlayerNodeControlTypeDurationText]; - _durationTextNode.truncationMode = NSLineBreakByClipping; - - [_cachedControls setObject:_durationTextNode forKey:@(ASVideoPlayerNodeControlTypeDurationText)]; - } - [self updateDurationTimeLabel]; - { - ASUnlockScope(self); - [self addSubnode:_durationTextNode]; - } -} - -- (void)_locked_createScrubber -{ - DISABLED_ASAssertLocked(__instanceLock__); - - if (_scrubberNode == nil) { - __weak __typeof__(self) weakSelf = self; - _scrubberNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull { - __typeof__(self) strongSelf = weakSelf; - - UISlider *slider = [[UISlider alloc] initWithFrame:CGRectZero]; - slider.minimumValue = 0.0; - slider.maximumValue = 1.0; - - if (strongSelf->_delegateFlags.delegateScrubberMinimumTrackTintColor) { - slider.minimumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMinimumTrackTint:strongSelf]; - } - - if (strongSelf->_delegateFlags.delegateScrubberMaximumTrackTintColor) { - slider.maximumTrackTintColor = [strongSelf.delegate videoPlayerNodeScrubberMaximumTrackTint:strongSelf]; - } - - if (strongSelf->_delegateFlags.delegateScrubberThumbTintColor) { - slider.thumbTintColor = [strongSelf.delegate videoPlayerNodeScrubberThumbTint:strongSelf]; - } - - if (strongSelf->_delegateFlags.delegateScrubberThumbImage) { - UIImage *thumbImage = [strongSelf.delegate videoPlayerNodeScrubberThumbImage:strongSelf]; - [slider setThumbImage:thumbImage forState:UIControlStateNormal]; - } - - - [slider addTarget:strongSelf action:@selector(beginSeek) forControlEvents:UIControlEventTouchDown]; - [slider addTarget:strongSelf action:@selector(endSeek) forControlEvents:UIControlEventTouchUpInside|UIControlEventTouchUpOutside|UIControlEventTouchCancel]; - [slider addTarget:strongSelf action:@selector(seekTimeDidChange:) forControlEvents:UIControlEventValueChanged]; - - return slider; - }]; - - _scrubberNode.style.flexShrink = 1; - - [_cachedControls setObject:_scrubberNode forKey:@(ASVideoPlayerNodeControlTypeScrubber)]; - } - { - ASUnlockScope(self); - [self addSubnode:_scrubberNode]; - } -} - -- (void)_locked_createControlFlexGrowSpacer -{ - DISABLED_ASAssertLocked(__instanceLock__); - - if (_controlFlexGrowSpacerSpec == nil) { - _controlFlexGrowSpacerSpec = [[ASStackLayoutSpec alloc] init]; - _controlFlexGrowSpacerSpec.style.flexGrow = 1.0; - } - - [_cachedControls setObject:_controlFlexGrowSpacerSpec forKey:@(ASVideoPlayerNodeControlTypeFlexGrowSpacer)]; -} - -- (void)updateDurationTimeLabel -{ - if (!_durationTextNode) { - return; - } - NSString *formattedDuration = [self timeStringForCMTime:_duration forTimeLabelType:ASVideoPlayerNodeControlTypeDurationText]; - _durationTextNode.attributedText = [self timeLabelAttributedStringForString:formattedDuration forControlType:ASVideoPlayerNodeControlTypeDurationText]; -} - -- (void)updateElapsedTimeLabel:(NSTimeInterval)seconds -{ - if (!_elapsedTextNode) { - return; - } - NSString *formattedElapsed = [self timeStringForCMTime:CMTimeMakeWithSeconds( seconds, _videoNode.periodicTimeObserverTimescale ) forTimeLabelType:ASVideoPlayerNodeControlTypeElapsedText]; - _elapsedTextNode.attributedText = [self timeLabelAttributedStringForString:formattedElapsed forControlType:ASVideoPlayerNodeControlTypeElapsedText]; -} - -- (NSAttributedString*)timeLabelAttributedStringForString:(NSString*)string forControlType:(ASVideoPlayerNodeControlType)controlType -{ - NSDictionary *options; - if (_delegateFlags.delegateTimeLabelAttributes) { - options = [_delegate videoPlayerNodeTimeLabelAttributes:self timeLabelType:controlType]; - } else { - options = @{ - NSFontAttributeName : [UIFont systemFontOfSize:12.0], - NSForegroundColorAttributeName: _defaultControlsColor - }; - } - - - NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string attributes:options]; - - return attributedString; -} - -#pragma mark - ASVideoNodeDelegate -- (void)videoNode:(ASVideoNode *)videoNode willChangePlayerState:(ASVideoNodePlayerState)state toState:(ASVideoNodePlayerState)toState -{ - if (_delegateFlags.delegateVideoNodeWillChangeState) { - [_delegate videoPlayerNode:self willChangeVideoNodeState:state toVideoNodeState:toState]; - } - - if (toState == ASVideoNodePlayerStateReadyToPlay) { - _duration = _videoNode.currentItem.duration; - [self updateDurationTimeLabel]; - } - - if (toState == ASVideoNodePlayerStatePlaying) { - _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePause; - [self removeSpinner]; - } else if (toState != ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying && toState != ASVideoNodePlayerStateReadyToPlay) { - _playbackButtonNode.buttonType = ASDefaultPlaybackButtonTypePlay; - } - - if (toState == ASVideoNodePlayerStateLoading || toState == ASVideoNodePlayerStateInitialLoading) { - [self showSpinner]; - } - - if (toState == ASVideoNodePlayerStateReadyToPlay || toState == ASVideoNodePlayerStatePaused || toState == ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying) { - [self removeSpinner]; - } -} - -- (BOOL)videoNode:(ASVideoNode *)videoNode shouldChangePlayerStateTo:(ASVideoNodePlayerState)state -{ - if (_delegateFlags.delegateVideoNodeShouldChangeState) { - return [_delegate videoPlayerNode:self shouldChangeVideoNodeStateTo:state]; - } - return YES; -} - -- (void)videoNode:(ASVideoNode *)videoNode didPlayToTimeInterval:(NSTimeInterval)timeInterval -{ - if (_delegateFlags.delegateVideoNodeDidPlayToTime) { - [_delegate videoPlayerNode:self didPlayToTime:_videoNode.player.currentTime]; - } - - if (_isSeeking) { - return; - } - - if (_elapsedTextNode) { - [self updateElapsedTimeLabel:timeInterval]; - } - - if (_scrubberNode) { - [(UISlider*)_scrubberNode.view setValue:( timeInterval / CMTimeGetSeconds(_duration) ) animated:NO]; - } -} - -- (void)videoDidPlayToEnd:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoNodePlaybackDidFinish) { - [_delegate videoPlayerNodeDidPlayToEnd:self]; - } -} - -- (void)didTapVideoNode:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateDidTapVideoPlayerNode) { - [_delegate didTapVideoPlayerNode:self]; - } else { - [self togglePlayPause]; - } -} - -- (void)videoNode:(ASVideoNode *)videoNode didSetCurrentItem:(AVPlayerItem *)currentItem -{ - if (_delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem) { - [_delegate videoPlayerNode:self didSetCurrentItem:currentItem]; - } -} - -- (void)videoNode:(ASVideoNode *)videoNode didStallAtTimeInterval:(NSTimeInterval)timeInterval -{ - if (_delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval) { - [_delegate videoPlayerNode:self didStallAtTimeInterval:timeInterval]; - } -} - -- (void)videoNodeDidStartInitialLoading:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading) { - [_delegate videoPlayerNodeDidStartInitialLoading:self]; - } -} - -- (void)videoNodeDidFinishInitialLoading:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading) { - [_delegate videoPlayerNodeDidFinishInitialLoading:self]; - } -} - -- (void)videoNodeDidRecoverFromStall:(ASVideoNode *)videoNode -{ - if (_delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall) { - [_delegate videoPlayerNodeDidRecoverFromStall:self]; - } -} - -#pragma mark - Actions -- (void)togglePlayPause -{ - if (_videoNode.playerState == ASVideoNodePlayerStatePlaying) { - [_videoNode pause]; - } else { - [_videoNode play]; - } -} - -- (void)showSpinner -{ - ASLockScopeSelf(); - - if (!_spinnerNode) { - __weak __typeof__(self) weakSelf = self; - _spinnerNode = [[ASDisplayNode alloc] initWithViewBlock:^UIView *{ - __typeof__(self) strongSelf = weakSelf; - UIActivityIndicatorView *spinnnerView = [[UIActivityIndicatorView alloc] init]; - spinnnerView.backgroundColor = [UIColor clearColor]; - - if (strongSelf->_delegateFlags.delegateSpinnerTintColor) { - spinnnerView.color = [strongSelf->_delegate videoPlayerNodeSpinnerTint:strongSelf]; - } else { - spinnnerView.color = strongSelf->_defaultControlsColor; - } - - if (strongSelf->_delegateFlags.delegateSpinnerStyle) { - spinnnerView.activityIndicatorViewStyle = [strongSelf->_delegate videoPlayerNodeSpinnerStyle:strongSelf]; - } - - return spinnnerView; - }]; - _spinnerNode.style.preferredSize = CGSizeMake(44.0, 44.0); - - const auto spinnerNode = _spinnerNode; - { - ASUnlockScope(self); - [self addSubnode:spinnerNode]; - [self setNeedsLayout]; - } - } - [(UIActivityIndicatorView *)_spinnerNode.view startAnimating]; -} - -- (void)removeSpinner -{ - ASDisplayNode *spinnerNode = nil; - { - ASLockScopeSelf(); - if (!_spinnerNode) { - return; - } - - spinnerNode = _spinnerNode; - _spinnerNode = nil; - } - - [spinnerNode removeFromSupernode]; -} - -- (void)didTapPlaybackButton:(ASControlNode*)node -{ - [self togglePlayPause]; -} - -- (void)didTapFullScreenButton:(ASButtonNode*)node -{ - if (_delegateFlags.delegateDidTapFullScreenButtonNode) { - [_delegate didTapFullScreenButtonNode:node]; - } -} - -- (void)beginSeek -{ - _isSeeking = YES; -} - -- (void)endSeek -{ - _isSeeking = NO; -} - -- (void)seekTimeDidChange:(UISlider*)slider -{ - CGFloat percentage = slider.value * 100; - [self seekToTime:percentage]; -} - -#pragma mark - Public API -- (void)seekToTime:(CGFloat)percentComplete -{ - CGFloat seconds = ( CMTimeGetSeconds(_duration) * percentComplete ) / 100; - - [self updateElapsedTimeLabel:seconds]; - [_videoNode.player seekToTime:CMTimeMakeWithSeconds(seconds, _videoNode.periodicTimeObserverTimescale)]; - - if (_videoNode.playerState != ASVideoNodePlayerStatePlaying) { - [self togglePlayPause]; - } -} - -- (void)play -{ - [_videoNode play]; -} - -- (void)pause -{ - [_videoNode pause]; -} - -- (BOOL)isPlaying -{ - return [_videoNode isPlaying]; -} - -- (void)resetToPlaceholder -{ - [_videoNode resetToPlaceholder]; -} - -- (NSArray *)controlsForLayoutSpec -{ - NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:_cachedControls.count]; - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypePlaybackButton) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeElapsedText) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeScrubber) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeDurationText) ]]; - } - - if (_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]) { - [controls addObject:_cachedControls[ @(ASVideoPlayerNodeControlTypeFullScreenButton) ]]; - } - - return controls; -} - - -#pragma mark - Layout - -- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize -{ - CGSize maxSize = constrainedSize.max; - - // Prevent crashes through if infinite width or height - if (isinf(maxSize.width) || isinf(maxSize.height)) { - ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoPlayerNode"); - maxSize = CGSizeZero; - } - - _videoNode.style.preferredSize = maxSize; - - ASLayoutSpec *layoutSpec; - if (_delegateFlags.delegateLayoutSpecForControls) { - layoutSpec = [_delegate videoPlayerNodeLayoutSpec:self forControls:_cachedControls forMaximumSize:maxSize]; - } else { - layoutSpec = [self defaultLayoutSpecThatFits:maxSize]; - } - - NSMutableArray *children = [[NSMutableArray alloc] init]; - - if (_spinnerNode) { - ASCenterLayoutSpec *centerLayoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:_spinnerNode]; - centerLayoutSpec.style.preferredSize = maxSize; - [children addObject:centerLayoutSpec]; - } - - ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec]; - overlaySpec.style.preferredSize = maxSize; - [children addObject:overlaySpec]; - - return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:children]; -} - -- (ASLayoutSpec *)defaultLayoutSpecThatFits:(CGSize)maxSize -{ - _scrubberNode.style.preferredSize = CGSizeMake(maxSize.width, 44.0); - - ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; - spacer.style.flexGrow = 1.0; - - ASStackLayoutSpec *controlbarSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:10.0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsCenter - children: [self controlsForLayoutSpec] ]; - controlbarSpec.style.alignSelf = ASStackLayoutAlignSelfStretch; - - UIEdgeInsets insets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); - - ASInsetLayoutSpec *controlbarInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:controlbarSpec]; - - controlbarInsetSpec.style.alignSelf = ASStackLayoutAlignSelfStretch; - - ASStackLayoutSpec *mainVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical - spacing:0.0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - children:@[spacer,controlbarInsetSpec]]; - - return mainVerticalStack; -} - -#pragma mark - Properties -- (id)delegate -{ - return _delegate; -} - -- (void)setDelegate:(id)delegate -{ - if (delegate == _delegate) { - return; - } - - _delegate = delegate; - - if (_delegate == nil) { - memset(&_delegateFlags, 0, sizeof(_delegateFlags)); - } else { - _delegateFlags.delegateNeededDefaultControls = [_delegate respondsToSelector:@selector(videoPlayerNodeNeededDefaultControls:)]; - _delegateFlags.delegateCustomControls = [_delegate respondsToSelector:@selector(videoPlayerNodeCustomControls:)]; - _delegateFlags.delegateSpinnerTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerTint:)]; - _delegateFlags.delegateSpinnerStyle = [_delegate respondsToSelector:@selector(videoPlayerNodeSpinnerStyle:)]; - _delegateFlags.delegateScrubberMaximumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMaximumTrackTint:)]; - _delegateFlags.delegateScrubberMinimumTrackTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberMinimumTrackTint:)]; - _delegateFlags.delegateScrubberThumbTintColor = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbTint:)]; - _delegateFlags.delegateScrubberThumbImage = [_delegate respondsToSelector:@selector(videoPlayerNodeScrubberThumbImage:)]; - _delegateFlags.delegateTimeLabelAttributes = [_delegate respondsToSelector:@selector(videoPlayerNodeTimeLabelAttributes:timeLabelType:)]; - _delegateFlags.delegateLayoutSpecForControls = [_delegate respondsToSelector:@selector(videoPlayerNodeLayoutSpec:forControls:forMaximumSize:)]; - _delegateFlags.delegateVideoNodeDidPlayToTime = [_delegate respondsToSelector:@selector(videoPlayerNode:didPlayToTime:)]; - _delegateFlags.delegateVideoNodeWillChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:willChangeVideoNodeState:toVideoNodeState:)]; - _delegateFlags.delegateVideoNodePlaybackDidFinish = [_delegate respondsToSelector:@selector(videoPlayerNodeDidPlayToEnd:)]; - _delegateFlags.delegateVideoNodeShouldChangeState = [_delegate respondsToSelector:@selector(videoPlayerNode:shouldChangeVideoNodeStateTo:)]; - _delegateFlags.delegateTimeLabelAttributedString = [_delegate respondsToSelector:@selector(videoPlayerNode:timeStringForTimeLabelType:forTime:)]; - _delegateFlags.delegatePlaybackButtonTint = [_delegate respondsToSelector:@selector(videoPlayerNodePlaybackButtonTint:)]; - _delegateFlags.delegateFullScreenButtonImage = [_delegate respondsToSelector:@selector(videoPlayerNodeFullScreenButtonImage:)]; - _delegateFlags.delegateDidTapVideoPlayerNode = [_delegate respondsToSelector:@selector(didTapVideoPlayerNode:)]; - _delegateFlags.delegateDidTapFullScreenButtonNode = [_delegate respondsToSelector:@selector(didTapFullScreenButtonNode:)]; - _delegateFlags.delegateVideoPlayerNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoPlayerNode:didSetCurrentItem:)]; - _delegateFlags.delegateVideoPlayerNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoPlayerNode:didStallAtTimeInterval:)]; - _delegateFlags.delegateVideoPlayerNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidStartInitialLoading:)]; - _delegateFlags.delegateVideoPlayerNodeDidFinishInitialLoading = [_delegate respondsToSelector:@selector(videoPlayerNodeDidFinishInitialLoading:)]; - _delegateFlags.delegateVideoPlayerNodeDidRecoverFromStall = [_delegate respondsToSelector:@selector(videoPlayerNodeDidRecoverFromStall:)]; - } -} - -- (void)setControlsDisabled:(BOOL)controlsDisabled -{ - if (_controlsDisabled == controlsDisabled) { - return; - } - - _controlsDisabled = controlsDisabled; - - if (_controlsDisabled && _cachedControls.count > 0) { - [self removeControls]; - } else if (!_controlsDisabled) { - [self createControls]; - } -} - -- (void)setShouldAutoPlay:(BOOL)shouldAutoPlay -{ - if (_shouldAutoPlay == shouldAutoPlay) { - return; - } - _shouldAutoPlay = shouldAutoPlay; - _videoNode.shouldAutoplay = _shouldAutoPlay; -} - -- (void)setShouldAutoRepeat:(BOOL)shouldAutoRepeat -{ - if (_shouldAutoRepeat == shouldAutoRepeat) { - return; - } - _shouldAutoRepeat = shouldAutoRepeat; - _videoNode.shouldAutorepeat = _shouldAutoRepeat; -} - -- (void)setMuted:(BOOL)muted -{ - if (_muted == muted) { - return; - } - _muted = muted; - _videoNode.muted = _muted; -} - -- (void)setPeriodicTimeObserverTimescale:(int32_t)periodicTimeObserverTimescale -{ - if (_periodicTimeObserverTimescale == periodicTimeObserverTimescale) { - return; - } - _periodicTimeObserverTimescale = periodicTimeObserverTimescale; - _videoNode.periodicTimeObserverTimescale = _periodicTimeObserverTimescale; -} - -- (NSString *)gravity -{ - if (_gravity == nil) { - _gravity = _videoNode.gravity; - } - return _gravity; -} - -- (void)setGravity:(NSString *)gravity -{ - if (_gravity == gravity) { - return; - } - _gravity = gravity; - _videoNode.gravity = _gravity; -} - -- (ASVideoNodePlayerState)playerState -{ - return _videoNode.playerState; -} - -- (BOOL)shouldAggressivelyRecoverFromStall -{ - return _videoNode.shouldAggressivelyRecoverFromStall; -} - -- (void) setPlaceholderImageURL:(NSURL *)placeholderImageURL -{ - _videoNode.URL = placeholderImageURL; -} - -- (NSURL*) placeholderImageURL -{ - return _videoNode.URL; -} - -- (ASVideoNode*)videoNode -{ - return _videoNode; -} - -- (void)setShouldAggressivelyRecoverFromStall:(BOOL)shouldAggressivelyRecoverFromStall -{ - if (_shouldAggressivelyRecoverFromStall == shouldAggressivelyRecoverFromStall) { - return; - } - _shouldAggressivelyRecoverFromStall = shouldAggressivelyRecoverFromStall; - _videoNode.shouldAggressivelyRecoverFromStall = _shouldAggressivelyRecoverFromStall; -} - -#pragma mark - Helpers -- (NSString *)timeStringForCMTime:(CMTime)time forTimeLabelType:(ASVideoPlayerNodeControlType)type -{ - if (!CMTIME_IS_VALID(time)) { - return @"00:00"; - } - if (_delegateFlags.delegateTimeLabelAttributedString) { - return [_delegate videoPlayerNode:self timeStringForTimeLabelType:type forTime:time]; - } - - NSUInteger dTotalSeconds = CMTimeGetSeconds(time); - - NSUInteger dHours = floor(dTotalSeconds / 3600); - NSUInteger dMinutes = floor(dTotalSeconds % 3600 / 60); - NSUInteger dSeconds = floor(dTotalSeconds % 3600 % 60); - - NSString *videoDurationText; - if (dHours > 0) { - videoDurationText = [NSString stringWithFormat:@"%i:%02i:%02i", (int)dHours, (int)dMinutes, (int)dSeconds]; - } else { - videoDurationText = [NSString stringWithFormat:@"%02i:%02i", (int)dMinutes, (int)dSeconds]; - } - return videoDurationText; -} - -@end - -#endif // TARGET_OS_IOS - -#endif diff --git a/Source/ASVisibilityProtocols.h b/Source/ASVisibilityProtocols.h deleted file mode 100644 index b9f5c7ba36..0000000000 --- a/Source/ASVisibilityProtocols.h +++ /dev/null @@ -1,145 +0,0 @@ -// -// ASVisibilityProtocols.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class UIViewController; - -ASDK_EXTERN ASLayoutRangeMode ASLayoutRangeModeForVisibilityDepth(NSUInteger visibilityDepth); - -/** - * ASVisibilityDepth - * - * @discussion "Visibility Depth" represents the number of user actions required to make an ASDisplayNode or - * ASDKViewController visibile. AsyncDisplayKit uses this information to intelligently manage memory and focus - * resources where they are most visible to the user. - * - * The ASVisibilityDepth protocol describes how custom view controllers can integrate with this system. - * - * Parent view controllers should also implement @c ASManagesChildVisibilityDepth - * - * @see ASManagesChildVisibilityDepth - */ - -@protocol ASVisibilityDepth - -/** - * Visibility depth - * - * @discussion Represents the number of user actions necessary to reach the view controller. An increased visibility - * depth indicates a higher number of user interactions for the view controller to be visible again. For example, - * an onscreen navigation controller's top view controller should have a visibility depth of 0. The view controller - * one from the top should have a visibility depth of 1 as should the root view controller in the stack (because - * the user can hold the back button to pop to the root view controller). - * - * Visibility depth is used to automatically adjust ranges on range controllers (and thus free up memory) and can - * be used to reduce memory usage of other items as well. - */ -- (NSInteger)visibilityDepth; - -/** - * Called when visibility depth changes - * - * @discussion @c visibilityDepthDidChange is called whenever the visibility depth of the represented view controller - * has changed. - * - * If implemented by a view controller container, use this method to notify child view controllers that their view - * depth has changed @see ASNavigationController.m - * - * If implemented on an ASDKViewController, use this method to reduce or increase the resources that your - * view controller uses. A higher visibility depth view controller should decrease it's resource usage, a lower - * visibility depth controller should pre-warm resources in preperation for a display at 0 depth. - * - * ASDKViewController implements this method and reduces / increases range mode of supporting nodes (such as ASCollectionNode - * and ASTableNode). - * - * @see visibilityDepth - */ -- (void)visibilityDepthDidChange; - -@end - -/** - * ASManagesChildVisibilityDepth - * - * @discussion A protocol which should be implemented by container view controllers to allow proper - * propagation of visibility depth - * - * @see ASVisibilityDepth - */ -@protocol ASManagesChildVisibilityDepth - -/** - * @abstract Container view controllers should adopt this protocol to indicate that they will manage their child's - * visibilityDepth. For example, ASNavigationController adopts this protocol and manages its childrens visibility - * depth. - * - * If you adopt this protocol, you *must* also emit visibilityDepthDidChange messages to child view controllers. - * - * @param childViewController Expected to return the visibility depth of the child view controller. - */ -- (NSInteger)visibilityDepthOfChildViewController:(UIViewController *)childViewController; - -@end - -#define ASVisibilitySetVisibilityDepth \ -- (void)setVisibilityDepth:(NSUInteger)visibilityDepth \ -{ \ - if (_visibilityDepth == visibilityDepth) { \ - return; \ - } \ - _visibilityDepth = visibilityDepth; \ - [self visibilityDepthDidChange]; \ -} - -#define ASVisibilityDepthImplementation \ -- (NSInteger)visibilityDepth \ -{ \ - if (self.parentViewController && _parentManagesVisibilityDepth == NO) { \ - _parentManagesVisibilityDepth = [self.parentViewController conformsToProtocol:@protocol(ASManagesChildVisibilityDepth)]; \ - } \ - \ - if (_parentManagesVisibilityDepth) { \ - return [(id )self.parentViewController visibilityDepthOfChildViewController:self]; \ - } \ - return _visibilityDepth; \ -} - -#define ASVisibilityViewDidDisappearImplementation \ -- (void)viewDidDisappear:(BOOL)animated \ -{ \ - [super viewDidDisappear:animated]; \ - \ - if (_parentManagesVisibilityDepth == NO) { \ - [self setVisibilityDepth:1]; \ - } \ -} - -#define ASVisibilityViewWillAppear \ -- (void)viewWillAppear:(BOOL)animated \ -{ \ - [super viewWillAppear:animated]; \ - \ - if (_parentManagesVisibilityDepth == NO) { \ - [self setVisibilityDepth:0]; \ - } \ -} - -#define ASVisibilityDidMoveToParentViewController \ -- (void)didMoveToParentViewController:(UIViewController *)parent \ -{ \ - [super didMoveToParentViewController:parent]; \ - _parentManagesVisibilityDepth = NO; \ - [self visibilityDepthDidChange]; \ -} - -NS_ASSUME_NONNULL_END diff --git a/Source/ASVisibilityProtocols.mm b/Source/ASVisibilityProtocols.mm deleted file mode 100644 index b13a683bea..0000000000 --- a/Source/ASVisibilityProtocols.mm +++ /dev/null @@ -1,22 +0,0 @@ -// -// ASVisibilityProtocols.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -ASLayoutRangeMode ASLayoutRangeModeForVisibilityDepth(NSUInteger visibilityDepth) -{ - if (visibilityDepth == 0) { - return ASLayoutRangeModeFull; - } else if (visibilityDepth == 1) { - return ASLayoutRangeModeMinimum; - } else if (visibilityDepth == 2) { - return ASLayoutRangeModeVisibleOnly; - } - return ASLayoutRangeModeLowMemory; -} diff --git a/Source/AsyncDisplayKit+IGListKitMethods.h b/Source/AsyncDisplayKit+IGListKitMethods.h deleted file mode 100644 index aa0de42510..0000000000 --- a/Source/AsyncDisplayKit+IGListKitMethods.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// AsyncDisplayKit+IGListKitMethods.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * If you are using AsyncDisplayKit with IGListKit, you should use - * these methods to provide implementations for methods like - * -cellForItemAtIndex: that don't apply when used with AsyncDisplayKit. - * - * Your section controllers should also conform to @c ASSectionController and your - * supplementary view sources should conform to @c ASSupplementaryNodeSource. - */ - -AS_SUBCLASSING_RESTRICTED -@interface ASIGListSectionControllerMethods : NSObject - -/** - * Call this for your section controller's @c cellForItemAtIndex: method. - */ -+ (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController; - -/** - * Call this for your section controller's @c sizeForItemAtIndex: method. - */ -+ (CGSize)sizeForItemAtIndex:(NSInteger)index; - -@end - -AS_SUBCLASSING_RESTRICTED -@interface ASIGListSupplementaryViewSourceMethods : NSObject - -/** - * Call this for your supplementary source's @c viewForSupplementaryElementOfKind:atIndex: method. - */ -+ (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind - atIndex:(NSInteger)index - sectionController:(IGListSectionController *)sectionController; - -/** - * Call this for your supplementary source's @c sizeForSupplementaryViewOfKind:atIndex: method. - */ -+ (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/Source/AsyncDisplayKit+IGListKitMethods.mm b/Source/AsyncDisplayKit+IGListKitMethods.mm deleted file mode 100644 index 50d7a219f8..0000000000 --- a/Source/AsyncDisplayKit+IGListKitMethods.mm +++ /dev/null @@ -1,53 +0,0 @@ -// -// AsyncDisplayKit+IGListKitMethods.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import "AsyncDisplayKit+IGListKitMethods.h" -#import -#import -#import - - -@implementation ASIGListSectionControllerMethods - -+ (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index sectionController:(IGListSectionController *)sectionController -{ - // Cast to id for backwards-compatibility until 3.0.0 is officially released – IGListSectionType was removed. This is safe. - return [sectionController.collectionContext dequeueReusableCellOfClass:[_ASCollectionViewCell class] forSectionController:(id)sectionController atIndex:index]; -} - -+ (CGSize)sizeForItemAtIndex:(NSInteger)index -{ - ASDisplayNodeFailAssert(@"Did not expect %@ to be called.", NSStringFromSelector(_cmd)); - return CGSizeZero; -} - -@end - -@implementation ASIGListSupplementaryViewSourceMethods - -+ (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind - atIndex:(NSInteger)index - sectionController:(IGListSectionController *)sectionController -{ - return [sectionController.collectionContext dequeueReusableSupplementaryViewOfKind:elementKind forSectionController:(id)sectionController class:[_ASCollectionReusableView class] atIndex:index]; -} - -+ (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind atIndex:(NSInteger)index -{ - ASDisplayNodeFailAssert(@"Did not expect %@ to be called.", NSStringFromSelector(_cmd)); - return CGSizeZero; -} - -@end - -#endif // AS_IG_LIST_KIT diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index ebe0c1a97b..c0a060cdfd 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -13,28 +13,17 @@ #import #import #import -#import -#import -#import #import #import #import -#import #import #import -#import - -#import -#import #import #import -#import -#import #import #import -#import #import #import @@ -42,31 +31,15 @@ #import #import #import -#import #import #import #import #import #import -#import -#import -#import -#import - -#import -#import #import -#import -#import -#import - -#import -#import -#import -#import #import #import @@ -107,10 +80,8 @@ #import #import #import -#import #import #import -#import #import #import #import @@ -123,12 +94,8 @@ #import #import #import -#import #import #import -#import -#import -#import #import diff --git a/Source/Base/ASAvailability.h b/Source/Base/ASAvailability.h index b210397fc1..d63e768433 100644 --- a/Source/Base/ASAvailability.h +++ b/Source/Base/ASAvailability.h @@ -17,27 +17,6 @@ #define AS_TLS_AVAILABLE 1 #endif -#ifndef AS_ENABLE_TEXTNODE - #define AS_ENABLE_TEXTNODE 1 // Enable old TextNode by default -#endif - -// This needs to stay in sync with Weaver -#ifndef AS_USE_VIDEO - #define AS_USE_VIDEO 0 -#endif - -#ifndef AS_USE_PHOTOS - #define AS_USE_PHOTOS 0 -#endif - -#ifndef AS_USE_MAPKIT - #define AS_USE_MAPKIT 0 -#endif - -#ifndef AS_USE_ASSETS_LIBRARY - #define AS_USE_ASSETS_LIBRARY 0 -#endif - #ifndef kCFCoreFoundationVersionNumber_iOS_10_0 #define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00 #endif @@ -68,28 +47,3 @@ #define AS_AVAILABLE_TVOS(ver) (TARGET_OS_TV && AS_AT_LEAST_IOS##ver) #define AS_AVAILABLE_IOS_TVOS(ver1, ver2) (AS_AVAILABLE_IOS(ver1) || AS_AVAILABLE_TVOS(ver2)) #endif - -// If Yoga is available, make it available anywhere we use ASAvailability. -// This reduces Yoga-specific code in other files. -// NOTE: Yoga integration is experimental and not fully tested. Use with caution and test layouts carefully. -#ifndef YOGA_HEADER_PATH - #define YOGA_HEADER_PATH -#endif - -#ifndef YOGA - #define YOGA __has_include(YOGA_HEADER_PATH) -#endif - -#ifdef ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE - #error "ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE is unavailable. See ASConfiguration.h." -#endif - -#define AS_PIN_REMOTE_IMAGE __has_include() -#define AS_IG_LIST_KIT __has_include() -#define AS_IG_LIST_DIFF_KIT __has_include() - -/** - * For IGListKit versions < 3.0, you have to use IGListCollectionView. - * For 3.0 and later, that class is removed and you use UICollectionView. - */ -#define IG_LIST_COLLECTION_VIEW __has_include() diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 53ea66a03a..7b7a112fe9 100644 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -37,30 +37,6 @@ # endif #endif -#ifndef ASDISPLAYNODE_WARN_DEPRECATED -# define ASDISPLAYNODE_WARN_DEPRECATED 1 -#endif - -#ifndef ASDISPLAYNODE_DEPRECATED -# if ASDISPLAYNODE_GNUC (3, 0) && ASDISPLAYNODE_WARN_DEPRECATED -# define ASDISPLAYNODE_DEPRECATED __attribute__ ((deprecated)) -# else -# define ASDISPLAYNODE_DEPRECATED -# endif -#endif - -#ifndef ASDISPLAYNODE_DEPRECATED_MSG -# if ASDISPLAYNODE_GNUC (3, 0) && ASDISPLAYNODE_WARN_DEPRECATED -# define ASDISPLAYNODE_DEPRECATED_MSG(msg) __deprecated_msg(msg) -# else -# define ASDISPLAYNODE_DEPRECATED_MSG(msg) -# endif -#endif - -#ifndef AS_ENABLE_TIPS -#define AS_ENABLE_TIPS 0 -#endif - #ifndef __has_feature // Optional. #define __has_feature(x) 0 // Compatibility with non-clang compilers. #endif diff --git a/Source/Base/ASLog.h b/Source/Base/ASLog.h index fb0eb574af..91f7e60e29 100644 --- a/Source/Base/ASLog.h +++ b/Source/Base/ASLog.h @@ -58,7 +58,7 @@ ASDK_EXTERN os_log_t ASDisplayLog(void); #define ASCollectionLogEnabled 1 ASDK_EXTERN os_log_t ASCollectionLog(void); -/// Log for ASNetworkImageNode and ASMultiplexImageNode events. +/// Log for ASNetworkImageNode events. #define ASImageLoadingLogEnabled 1 ASDK_EXTERN os_log_t ASImageLoadingLog(void); diff --git a/Source/Details/ASBasicImageDownloader.h b/Source/Details/ASBasicImageDownloader.h index 7667bf8f9c..d7855f9b94 100644 --- a/Source/Details/ASBasicImageDownloader.h +++ b/Source/Details/ASBasicImageDownloader.h @@ -21,9 +21,8 @@ NS_ASSUME_NONNULL_BEGIN * The userInfo provided by this downloader is `nil`. * * This is a very basic image downloader. It does not support caching, progressive downloading and likely - * isn't something you should use in production. If you'd like something production ready, see @c ASPINRemoteImageDownloader + * isn't something you should use in production. * - * @note It is strongly recommended you include PINRemoteImage and use @c ASPINRemoteImageDownloader instead. */ @property (class, readonly) ASBasicImageDownloader *sharedImageDownloader; + (ASBasicImageDownloader *)sharedImageDownloader NS_RETURNS_RETAINED; diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.h b/Source/Details/ASCollectionFlowLayoutDelegate.h deleted file mode 100644 index d22e9bbaab..0000000000 --- a/Source/Details/ASCollectionFlowLayoutDelegate.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// ASCollectionFlowLayoutDelegate.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED - -/** - * A thread-safe, high performant layout delegate that arranges items into a flow layout. - * It uses a concurrent and multi-line ASStackLayoutSpec under the hood. Thus, per-child flex properties (i.e alignSelf, - * flexShrink, flexGrow, etc - see @ASStackLayoutElement) can be set directly on cell nodes to be used - * to calculate the final collection layout. - */ -@interface ASCollectionFlowLayoutDelegate : NSObject - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections NS_DESIGNATED_INITIALIZER; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.mm b/Source/Details/ASCollectionFlowLayoutDelegate.mm deleted file mode 100644 index 7a2796ca95..0000000000 --- a/Source/Details/ASCollectionFlowLayoutDelegate.mm +++ /dev/null @@ -1,78 +0,0 @@ -// -// ASCollectionFlowLayoutDelegate.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import - -@implementation ASCollectionFlowLayoutDelegate { - ASScrollDirection _scrollableDirections; -} - -- (instancetype)init -{ - return [self initWithScrollableDirections:ASScrollDirectionVerticalDirections]; -} - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections -{ - self = [super init]; - if (self) { - _scrollableDirections = scrollableDirections; - } - return self; -} - -- (ASScrollDirection)scrollableDirections -{ - ASDisplayNodeAssertMainThread(); - return _scrollableDirections; -} - -- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - return nil; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - NSArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); - if (children.count == 0) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal - spacing:0 - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - flexWrap:ASStackLayoutFlexWrapWrap - alignContent:ASStackLayoutAlignContentStart - children:children]; - stackSpec.concurrent = YES; - - ASSizeRange sizeRange = ASSizeRangeForCollectionLayoutThatFitsViewportSize(context.viewportSize, context.scrollableDirections); - ASLayout *layout = [stackSpec layoutThatFits:sizeRange]; - - return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nullable(ASLayout * _Nonnull sublayout) { - ASCellNode *node = ASDynamicCast(sublayout.layoutElement, ASCellNode); - return node ? node.collectionElement : nil; - }]; -} - -@end diff --git a/Source/Details/ASCollectionGalleryLayoutDelegate.h b/Source/Details/ASCollectionGalleryLayoutDelegate.h deleted file mode 100644 index 064891c54d..0000000000 --- a/Source/Details/ASCollectionGalleryLayoutDelegate.h +++ /dev/null @@ -1,106 +0,0 @@ -// -// ASCollectionGalleryLayoutDelegate.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@class ASElementMap; -@class ASCollectionGalleryLayoutDelegate; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionGalleryLayoutPropertiesProviding - -/** - * Returns the fixed size of each and every element. - * - * @discussion This method will only be called on main thread. - * - * @param delegate The calling object. - * - * @param elements All elements to be sized. - * - * @return The elements' size - */ -- (CGSize)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(ASElementMap *)elements; - -@optional - -/** - * Returns the minumum spacing to use between lines of items. - * - * @discussion This method will only be called on main thread. - * - * @discussion For a vertically scrolling layout, this value represents the minimum spacing between rows. - * For a horizontally scrolling one, it represents the minimum spacing between columns. - * It is not applied between the first line and the header, or between the last line and the footer. - * This is the same behavior as UICollectionViewFlowLayout's minimumLineSpacing. - * - * @param delegate The calling object. - * - * @param elements All elements in the layout. - * - * @return The interitem spacing - */ -- (CGFloat)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate minimumLineSpacingForElements:(ASElementMap *)elements; - -/** - * Returns the minumum spacing to use between items in the same row or column, depending on the scroll directions. - * - * @discussion This method will only be called on main thread. - * - * @discussion For a vertically scrolling layout, this value represents the minimum spacing between items in the same row. - * For a horizontally scrolling one, it represents the minimum spacing between items in the same column. - * It is considered while fitting items into lines, but the actual final spacing between some items might be larger. - * This is the same behavior as UICollectionViewFlowLayout's minimumInteritemSpacing. - * - * @param delegate The calling object. - * - * @param elements All elements in the layout. - * - * @return The interitem spacing - */ -- (CGFloat)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate minimumInteritemSpacingForElements:(ASElementMap *)elements; - -/** - * Returns the margins of each section. - * - * @discussion This method will only be called on main thread. - * - * @param delegate The calling object. - * - * @param elements All elements in the layout. - * - * @return The margins used to layout content in a section - */ -- (UIEdgeInsets)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sectionInsetForElements:(ASElementMap *)elements; - -@end - -/** - * A thread-safe layout delegate that arranges items with the same size into a flow layout. - * - * @note Supplemenraty elements are not supported. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionGalleryLayoutDelegate : NSObject - -@property (nonatomic, weak) id propertiesProvider; - -/** - * Designated initializer. - * - * @param scrollableDirections The scrollable directions of this layout. Must be either vertical or horizontal directions. - */ -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections NS_DESIGNATED_INITIALIZER; - -- (instancetype)init __unavailable; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionGalleryLayoutDelegate.mm b/Source/Details/ASCollectionGalleryLayoutDelegate.mm deleted file mode 100644 index 5ba74d61ac..0000000000 --- a/Source/Details/ASCollectionGalleryLayoutDelegate.mm +++ /dev/null @@ -1,135 +0,0 @@ -// -// ASCollectionGalleryLayoutDelegate.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#pragma mark - ASCollectionGalleryLayoutDelegate - -@implementation ASCollectionGalleryLayoutDelegate { - ASScrollDirection _scrollableDirections; - - struct { - unsigned int minimumLineSpacingForElements:1; - unsigned int minimumInteritemSpacingForElements:1; - unsigned int sectionInsetForElements:1; - } _propertiesProviderFlags; -} - -- (instancetype)initWithScrollableDirections:(ASScrollDirection)scrollableDirections -{ - self = [super init]; - if (self) { - // Scrollable directions must be either vertical or horizontal, but not both - ASDisplayNodeAssertTrue(ASScrollDirectionContainsVerticalDirection(scrollableDirections) - || ASScrollDirectionContainsHorizontalDirection(scrollableDirections)); - ASDisplayNodeAssertFalse(ASScrollDirectionContainsVerticalDirection(scrollableDirections) - && ASScrollDirectionContainsHorizontalDirection(scrollableDirections)); - _scrollableDirections = scrollableDirections; - } - return self; -} - -- (ASScrollDirection)scrollableDirections -{ - ASDisplayNodeAssertMainThread(); - return _scrollableDirections; -} - -- (void)setPropertiesProvider:(id)propertiesProvider -{ - ASDisplayNodeAssertMainThread(); - if (propertiesProvider == nil) { - _propertiesProvider = nil; - _propertiesProviderFlags = {}; - } else { - _propertiesProvider = propertiesProvider; - _propertiesProviderFlags.minimumLineSpacingForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:minimumLineSpacingForElements:)]; - _propertiesProviderFlags.minimumInteritemSpacingForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:minimumInteritemSpacingForElements:)]; - _propertiesProviderFlags.sectionInsetForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:sectionInsetForElements:)]; - } -} - -- (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - id propertiesProvider = _propertiesProvider; - if (propertiesProvider == nil) { - return nil; - } - - CGSize itemSize = [propertiesProvider galleryLayoutDelegate:self sizeForElements:elements]; - UIEdgeInsets sectionInset = _propertiesProviderFlags.sectionInsetForElements ? [propertiesProvider galleryLayoutDelegate:self sectionInsetForElements:elements] : UIEdgeInsetsZero; - CGFloat lineSpacing = _propertiesProviderFlags.minimumLineSpacingForElements ? [propertiesProvider galleryLayoutDelegate:self minimumLineSpacingForElements:elements] : 0.0; - CGFloat interitemSpacing = _propertiesProviderFlags.minimumInteritemSpacingForElements ? [propertiesProvider galleryLayoutDelegate:self minimumInteritemSpacingForElements:elements] : 0.0; - return [[_ASCollectionGalleryLayoutInfo alloc] initWithItemSize:itemSize - minimumLineSpacing:lineSpacing - minimumInteritemSpacing:interitemSpacing - sectionInset:sectionInset]; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - CGSize pageSize = context.viewportSize; - ASScrollDirection scrollableDirections = context.scrollableDirections; - - _ASCollectionGalleryLayoutInfo *info = ASDynamicCast(context.additionalInfo, _ASCollectionGalleryLayoutInfo); - CGSize itemSize = info.itemSize; - if (info == nil || CGSizeEqualToSize(CGSizeZero, itemSize)) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - NSArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, - ASCollectionElement *element, - [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); - if (children.count == 0) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - // Use a stack spec to calculate layout content size and frames of all elements without actually measuring each element - ASStackLayoutDirection stackDirection = ASScrollDirectionContainsVerticalDirection(scrollableDirections) - ? ASStackLayoutDirectionHorizontal - : ASStackLayoutDirectionVertical; - ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:stackDirection - spacing:info.minimumInteritemSpacing - justifyContent:ASStackLayoutJustifyContentStart - alignItems:ASStackLayoutAlignItemsStart - flexWrap:ASStackLayoutFlexWrapWrap - alignContent:ASStackLayoutAlignContentStart - lineSpacing:info.minimumLineSpacing - children:children]; - stackSpec.concurrent = YES; - - ASLayoutSpec *finalSpec = stackSpec; - UIEdgeInsets sectionInset = info.sectionInset; - if (UIEdgeInsetsEqualToEdgeInsets(sectionInset, UIEdgeInsetsZero) == NO) { - finalSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:sectionInset child:stackSpec]; - } - - ASLayout *layout = [finalSpec layoutThatFits:ASSizeRangeForCollectionLayoutThatFitsViewportSize(pageSize, scrollableDirections)]; - - return [[ASCollectionLayoutState alloc] initWithContext:context layout:layout getElementBlock:^ASCollectionElement * _Nullable(ASLayout * _Nonnull sublayout) { - _ASGalleryLayoutItem *item = ASDynamicCast(sublayout.layoutElement, _ASGalleryLayoutItem); - return item ? item.collectionElement : nil; - }]; -} - -@end diff --git a/Source/Details/ASCollectionInternal.h b/Source/Details/ASCollectionInternal.h index 42f651ae62..1b0e7b3304 100644 --- a/Source/Details/ASCollectionInternal.h +++ b/Source/Details/ASCollectionInternal.h @@ -11,13 +11,12 @@ NS_ASSUME_NONNULL_BEGIN -@protocol ASCollectionViewLayoutFacilitatorProtocol; @class ASCollectionNode; @class ASDataController; @class ASRangeController; @interface ASCollectionView () -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator owningNode:(nullable ASCollectionNode *)owningNode; +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout owningNode:(nullable ASCollectionNode *)owningNode; @property (nonatomic, weak) ASCollectionNode *collectionNode; @property (nonatomic, readonly) ASDataController *dataController; @@ -28,11 +27,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, nullable, readonly) _ASHierarchyChangeSet *changeSet; -/** - * @see ASCollectionNode+Beta.h for full documentation. - */ -@property (nonatomic) ASCellLayoutMode cellLayoutMode; - /** * Attempt to get the view-layer index path for the item with the given index path. * diff --git a/Source/Details/ASCollectionLayoutContext.h b/Source/Details/ASCollectionLayoutContext.h deleted file mode 100644 index 4c25838d2f..0000000000 --- a/Source/Details/ASCollectionLayoutContext.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// ASCollectionLayoutContext.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@class ASElementMap; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionLayoutContext : NSObject - -@property (nonatomic, readonly) CGSize viewportSize; -@property (nonatomic, readonly) CGPoint initialContentOffset; -@property (nonatomic, readonly) ASScrollDirection scrollableDirections; -@property (nonatomic, weak, readonly) ASElementMap *elements; -@property (nullable, nonatomic, readonly) id additionalInfo; - -- (instancetype)init NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionLayoutContext.mm b/Source/Details/ASCollectionLayoutContext.mm deleted file mode 100644 index 6f2ba7186b..0000000000 --- a/Source/Details/ASCollectionLayoutContext.mm +++ /dev/null @@ -1,102 +0,0 @@ -// -// ASCollectionLayoutContext.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#import -#import -#import -#import -#import - -@implementation ASCollectionLayoutContext { - Class _layoutDelegateClass; - __weak ASCollectionLayoutCache *_layoutCache; -} - -- (instancetype)initWithViewportSize:(CGSize)viewportSize - initialContentOffset:(CGPoint)initialContentOffset - scrollableDirections:(ASScrollDirection)scrollableDirections - elements:(ASElementMap *)elements - layoutDelegateClass:(Class)layoutDelegateClass - layoutCache:(ASCollectionLayoutCache *)layoutCache - additionalInfo:(id)additionalInfo -{ - self = [super init]; - if (self) { - _viewportSize = viewportSize; - _initialContentOffset = initialContentOffset; - _scrollableDirections = scrollableDirections; - _elements = elements; - _layoutDelegateClass = layoutDelegateClass; - _layoutCache = layoutCache; - _additionalInfo = additionalInfo; - } - return self; -} - -- (Class)layoutDelegateClass -{ - return _layoutDelegateClass; -} - -- (ASCollectionLayoutCache *)layoutCache -{ - return _layoutCache; -} - -// NOTE: Some properties, like initialContentOffset and layoutCache are ignored in -isEqualToContext: and -hash. -// That is because contexts can be equal regardless of the content offsets or layout caches. -- (BOOL)isEqualToContext:(ASCollectionLayoutContext *)context -{ - if (context == nil) { - return NO; - } - - // NOTE: ASObjectIsEqual returns YES when both objects are nil. - // So don't use ASObjectIsEqual on _elements. - // It is a weak property and 2 layouts generated from different sets of elements - // should never be considered the same even if they are nil now. - return CGSizeEqualToSize(_viewportSize, context.viewportSize) - && _scrollableDirections == context.scrollableDirections - && [_elements isEqual:context.elements] - && _layoutDelegateClass == context.layoutDelegateClass - && ASObjectIsEqual(_additionalInfo, context.additionalInfo); -} - -- (BOOL)isEqual:(id)other -{ - if (self == other) { - return YES; - } - if (! [other isKindOfClass:[ASCollectionLayoutContext class]]) { - return NO; - } - return [self isEqualToContext:other]; -} - -- (NSUInteger)hash -{ - struct { - CGSize viewportSize; - ASScrollDirection scrollableDirections; - NSUInteger elementsHash; - NSUInteger layoutDelegateClassHash; - NSUInteger additionalInfoHash; - } data = { - _viewportSize, - _scrollableDirections, - _elements.hash, - _layoutDelegateClass.hash, - [_additionalInfo hash] - }; - return ASHashBytes(&data, sizeof(data)); -} - -@end diff --git a/Source/Details/ASCollectionLayoutDelegate.h b/Source/Details/ASCollectionLayoutDelegate.h deleted file mode 100644 index c53e22a0d4..0000000000 --- a/Source/Details/ASCollectionLayoutDelegate.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// ASCollectionLayoutDelegate.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@class ASElementMap, ASCollectionLayoutContext, ASCollectionLayoutState; - -NS_ASSUME_NONNULL_BEGIN - -@protocol ASCollectionLayoutDelegate - -/** - * @abstract Returns the scrollable directions of the coming layout (@see @c -calculateLayoutWithContext:). - * It will be available in the context parameter in +calculateLayoutWithContext: - * - * @return The scrollable directions. - * - * @discusstion This method will be called on main thread. - */ -- (ASScrollDirection)scrollableDirections; - -/** - * @abstract Returns any additional information needed for a coming layout pass (@see @c -calculateLayoutWithContext:) with the given elements. - * - * @param elements The elements to be laid out later. - * - * @discussion The returned object must support equality and hashing (i.e `-isEqual:` and `-hash` must be properly implemented). - * It should contain all the information needed for the layout pass to perform. It will be available in the context parameter in +calculateLayoutWithContext: - * - * This method will be called on main thread. - */ -- (nullable id)additionalInfoForLayoutWithElements:(ASElementMap *)elements; - -/** - * @abstract Prepares and returns a new layout for given context. - * - * @param context A context that contains all elements to be laid out and any additional information needed. - * - * @return The new layout calculated for the given context. - * - * @discussion This method is called ahead of time, i.e before the underlying collection/table view is aware of the provided elements. - * As a result, clients must solely rely on the given context and should not reach out to other objects for information not available in the context. - * - * This method can be called on background theads. It must be thread-safe and should not change any internal state of this delegate. - * It must block the calling thread but can dispatch to other theads to reduce total blocking time. - */ -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionLayoutState.h b/Source/Details/ASCollectionLayoutState.h deleted file mode 100644 index c3620330c9..0000000000 --- a/Source/Details/ASCollectionLayoutState.h +++ /dev/null @@ -1,111 +0,0 @@ -// -// ASCollectionLayoutState.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@class ASCollectionLayoutContext, ASLayout, ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -typedef ASCollectionElement * _Nullable (^ASCollectionLayoutStateGetElementBlock)(ASLayout *); - -@interface NSMapTable (ASCollectionLayoutConvenience) - -+ (NSMapTable *)elementToLayoutAttributesTable; - -@end - -AS_SUBCLASSING_RESTRICTED - -/// An immutable state of the collection layout -@interface ASCollectionLayoutState : NSObject - -/// The context used to calculate this object -@property (readonly) ASCollectionLayoutContext *context; - -/// The final content size of the collection's layout -@property (readonly) CGSize contentSize; - -- (instancetype)init NS_UNAVAILABLE; - -/** - * Designated initializer. - * - * @param context The context used to calculate this object - * - * @param contentSize The content size of the collection's layout - * - * @param table A map between elements to their layout attributes. It must contain all elements. - * It should have NSMapTableObjectPointerPersonality and NSMapTableWeakMemory as key options. - */ -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - contentSize:(CGSize)contentSize - elementToLayoutAttributesTable:(NSMapTable *)table NS_DESIGNATED_INITIALIZER; - -/** - * Convenience initializer. Returns an object with zero content size and an empty table. - * - * @param context The context used to calculate this object - */ -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context; - -/** - * Convenience initializer. - * - * @param context The context used to calculate this object - * - * @param layout The layout describes size and position of all elements. - * - * @param getElementBlock A block that can retrieve the collection element from a sublayout of the root layout. - */ -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - layout:(ASLayout *)layout - getElementBlock:(ASCollectionLayoutStateGetElementBlock)getElementBlock; - -/** - * Returns all layout attributes present in this object. - */ -- (NSArray *)allLayoutAttributes; - -/** - * Returns layout attributes of elements in the specified rect. - * - * @param rect The rect containing the target elements. - */ -- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect; - -/** - * Returns layout attributes of the element at the specified index path. - * - * @param indexPath The index path of the item. - */ -- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; - -/** - * Returns layout attributes of the specified supplementary element. - * - * @param kind A string that identifies the type of the supplementary element. - * - * @param indexPath The index path of the element. - */ -- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind - atIndexPath:(NSIndexPath *)indexPath; - -/** - * Returns layout attributes of the specified element. - * - * @element The element. - */ -- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForElement:(ASCollectionElement *)element; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionLayoutState.mm b/Source/Details/ASCollectionLayoutState.mm deleted file mode 100644 index 0f98b215ab..0000000000 --- a/Source/Details/ASCollectionLayoutState.mm +++ /dev/null @@ -1,253 +0,0 @@ -// -// ASCollectionLayoutState.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import - -#import - -@implementation NSMapTable (ASCollectionLayoutConvenience) - -+ (NSMapTable *)elementToLayoutAttributesTable -{ - return [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableStrongMemory]; -} - -@end - -@implementation ASCollectionLayoutState { - AS::Mutex __instanceLock__; - CGSize _contentSize; - ASCollectionLayoutContext *_context; - NSMapTable *_elementToLayoutAttributesTable; - ASPageToLayoutAttributesTable *_pageToLayoutAttributesTable; - ASPageToLayoutAttributesTable *_unmeasuredPageToLayoutAttributesTable; -} - -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context -{ - return [self initWithContext:context - contentSize:CGSizeZero -elementToLayoutAttributesTable:[NSMapTable elementToLayoutAttributesTable]]; -} - -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - layout:(ASLayout *)layout - getElementBlock:(ASCollectionLayoutStateGetElementBlock)getElementBlock -{ - ASElementMap *elements = context.elements; - NSMapTable *table = [NSMapTable elementToLayoutAttributesTable]; - - // Traverse the given layout tree in breadth first fashion. Generate layout attributes for all included elements along the way. - struct Context { - ASLayout *layout; - CGPoint absolutePosition; - }; - - std::queue queue; - queue.push({layout, CGPointZero}); - - while (!queue.empty()) { - Context context = queue.front(); - queue.pop(); - - ASLayout *layout = context.layout; - const CGPoint absolutePosition = context.absolutePosition; - - ASCollectionElement *element = getElementBlock(layout); - if (element != nil) { - NSIndexPath *indexPath = [elements indexPathForElement:element]; - NSString *supplementaryElementKind = element.supplementaryElementKind; - - UICollectionViewLayoutAttributes *attrs; - if (supplementaryElementKind == nil) { - attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; - } else { - attrs = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:supplementaryElementKind withIndexPath:indexPath]; - } - - CGRect frame = layout.frame; - frame.origin = absolutePosition; - attrs.frame = frame; - [table setObject:attrs forKey:element]; - } - - // Add all sublayouts to process in next step - for (ASLayout *sublayout in layout.sublayouts) { - queue.push({sublayout, absolutePosition + sublayout.position}); - } - } - - return [self initWithContext:context contentSize:layout.size elementToLayoutAttributesTable:table]; -} - -- (instancetype)initWithContext:(ASCollectionLayoutContext *)context - contentSize:(CGSize)contentSize - elementToLayoutAttributesTable:(NSMapTable *)table -{ - self = [super init]; - if (self) { - _context = context; - _contentSize = contentSize; - _elementToLayoutAttributesTable = [table copy]; // Copy the given table to make sure clients can't mutate it after this point. - CGSize pageSize = context.viewportSize; - _pageToLayoutAttributesTable = [ASPageTable pageTableWithLayoutAttributes:table.objectEnumerator contentSize:contentSize pageSize:pageSize]; - _unmeasuredPageToLayoutAttributesTable = [ASCollectionLayoutState _unmeasuredLayoutAttributesTableFromTable:table contentSize:contentSize pageSize:pageSize]; - } - return self; -} - -- (ASCollectionLayoutContext *)context -{ - return _context; -} - -- (CGSize)contentSize -{ - return _contentSize; -} - -- (NSArray *)allLayoutAttributes -{ - return [_elementToLayoutAttributesTable.objectEnumerator allObjects]; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = [_context.elements elementForItemAtIndexPath:indexPath]; - return [_elementToLayoutAttributesTable objectForKey:element]; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)elementKind - atIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = [_context.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath]; - return [_elementToLayoutAttributesTable objectForKey:element]; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForElement:(ASCollectionElement *)element -{ - return [_elementToLayoutAttributesTable objectForKey:element]; -} - -- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect -{ - CGSize pageSize = _context.viewportSize; - NSPointerArray *pages = ASPageCoordinatesForPagesThatIntersectRect(rect, _contentSize, pageSize); - if (pages.count == 0) { - return @[]; - } - - // Use a set here because some items may span multiple pages - const auto result = [[NSMutableSet alloc] init]; - for (id pagePtr in pages) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSArray *allAttrs = [_pageToLayoutAttributesTable objectForPage:page]; - if (allAttrs.count > 0) { - CGRect pageRect = ASPageCoordinateGetPageRect(page, pageSize); - - if (CGRectContainsRect(rect, pageRect)) { - [result addObjectsFromArray:allAttrs]; - } else { - for (UICollectionViewLayoutAttributes *attrs in allAttrs) { - if (CGRectIntersectsRect(rect, attrs.frame)) { - [result addObject:attrs]; - } - } - } - } - } - - return [result allObjects]; -} - -- (ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect -{ - CGSize pageSize = _context.viewportSize; - CGSize contentSize = _contentSize; - - AS::MutexLocker l(__instanceLock__); - if (_unmeasuredPageToLayoutAttributesTable.count == 0 || CGRectIsNull(rect) || CGRectIsEmpty(rect) || CGSizeEqualToSize(CGSizeZero, contentSize) || CGSizeEqualToSize(CGSizeZero, pageSize)) { - return nil; - } - - // Step 1: Determine all the pages that intersect the specified rect - NSPointerArray *pagesInRect = ASPageCoordinatesForPagesThatIntersectRect(rect, contentSize, pageSize); - if (pagesInRect.count == 0) { - return nil; - } - - // Step 2: Filter out attributes in these pages that intersect the specified rect. - ASPageToLayoutAttributesTable *result = nil; - for (id pagePtr in pagesInRect) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSMutableArray *attrsInPage = [_unmeasuredPageToLayoutAttributesTable objectForPage:page]; - if (attrsInPage.count == 0) { - continue; - } - - NSMutableArray *intersectingAttrsInPage = nil; - CGRect pageRect = ASPageCoordinateGetPageRect(page, pageSize); - if (CGRectContainsRect(rect, pageRect)) { - // This page fits well within the specified rect. Simply return all of its attributes. - intersectingAttrsInPage = attrsInPage; - } else { - // The page intersects the specified rect. Some attributes in this page are returned, some are not. - for (UICollectionViewLayoutAttributes *attrs in attrsInPage) { - if (CGRectIntersectsRect(rect, attrs.frame)) { - if (intersectingAttrsInPage == nil) { - intersectingAttrsInPage = [[NSMutableArray alloc] init]; - } - [intersectingAttrsInPage addObject:attrs]; - } - } - } - - if (intersectingAttrsInPage.count > 0) { - if (attrsInPage.count == intersectingAttrsInPage.count) { - [_unmeasuredPageToLayoutAttributesTable removeObjectForPage:page]; - } else { - [attrsInPage removeObjectsInArray:intersectingAttrsInPage]; - } - if (result == nil) { - result = [ASPageTable pageTableForStrongObjectPointers]; - } - [result setObject:intersectingAttrsInPage forPage:page]; - } - } - - return result; -} - -#pragma mark - Private methods - -+ (ASPageToLayoutAttributesTable *)_unmeasuredLayoutAttributesTableFromTable:(NSMapTable *)table - contentSize:(CGSize)contentSize - pageSize:(CGSize)pageSize -{ - NSMutableArray *unmeasuredAttrs = [[NSMutableArray alloc] init]; - for (ASCollectionElement *element in table) { - UICollectionViewLayoutAttributes *attrs = [table objectForKey:element]; - if (element.nodeIfAllocated == nil || CGSizeEqualToSize(element.nodeIfAllocated.calculatedSize, attrs.frame.size) == NO) { - [unmeasuredAttrs addObject:attrs]; - } - } - - return [ASPageTable pageTableWithLayoutAttributes:unmeasuredAttrs contentSize:contentSize pageSize:pageSize]; -} - -@end diff --git a/Source/Details/ASCollectionViewLayoutInspector.h b/Source/Details/ASCollectionViewLayoutInspector.h index 4e58890f22..29766448d6 100644 --- a/Source/Details/ASCollectionViewLayoutInspector.h +++ b/Source/Details/ASCollectionViewLayoutInspector.h @@ -57,16 +57,6 @@ ASDK_EXTERN ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView * */ - (void)didChangeCollectionViewDataSource:(nullable id)dataSource; -#pragma mark Deprecated Methods - -/** - * Asks the inspector for the number of supplementary sections in the collection view for the given kind. - * - * @deprecated This method will not be called, and it is only deprecated as a reminder to remove it. - * Supplementary elements must exist in the same sections as regular collection view items i.e. -numberOfSectionsInCollectionView: - */ -- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode's method instead."); - @end /** diff --git a/Source/Details/ASCollectionViewLayoutInspector.mm b/Source/Details/ASCollectionViewLayoutInspector.mm index 1747c4642a..6af8939899 100644 --- a/Source/Details/ASCollectionViewLayoutInspector.mm +++ b/Source/Details/ASCollectionViewLayoutInspector.mm @@ -34,7 +34,6 @@ ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionVi @implementation ASCollectionViewLayoutInspector { struct { - unsigned int implementsConstrainedSizeForNodeAtIndexPathDeprecated:1; unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; } _delegateFlags; } @@ -46,7 +45,6 @@ - (void)didChangeCollectionViewDelegate:(id)delegate if (delegate == nil) { memset(&_delegateFlags, 0, sizeof(_delegateFlags)); } else { - _delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; _delegateFlags.implementsConstrainedSizeForNodeAtIndexPath = [delegate respondsToSelector:@selector(collectionNode:constrainedSizeForItemAtIndexPath:)]; } } @@ -55,14 +53,9 @@ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSize { if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPath) { return [collectionView.asyncDelegate collectionNode:collectionView.collectionNode constrainedSizeForItemAtIndexPath:indexPath]; - } else if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - return [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; -#pragma clang diagnostic pop } else { // With 2.0 `collectionView:constrainedSizeForNodeAtIndexPath:` was moved to the delegate. Assert if not implemented on the delegate but on the data source - ASDisplayNodeAssert([collectionView.asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] == NO, @"collectionView:constrainedSizeForNodeAtIndexPath: was moved from the ASCollectionDataSource to the ASCollectionDelegate."); + ASDisplayNodeAssert(YES, @"collectionView:constrainedSizeForNodeAtIndexPath: was moved from the ASCollectionDataSource to the ASCollectionDelegate."); } return NodeConstrainedSizeForScrollDirection(collectionView); diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index 10442da62a..084dbf73a7 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -13,7 +13,6 @@ #import #import #import -#import #import #import #import @@ -568,12 +567,10 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet os_log_debug(ASCollectionLog(), "performBatchUpdates %@ %@", ASViewToDisplayNode(ASDynamicCast(self.dataSource, UIView)), changeSet); } - if (!ASActivateExperimentalFeature(ASExperimentalOptimizeDataControllerPipeline)) { - NSTimeInterval transactionQueueFlushDuration = 0.0f; - { - AS::ScopeTimer t(transactionQueueFlushDuration); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - } + NSTimeInterval transactionQueueFlushDuration = 0.0f; + { + AS::ScopeTimer t(transactionQueueFlushDuration); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); } // If the initial reloadData has not been called, just bail because we don't have our old data source counts. @@ -660,29 +657,12 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet } }; - // 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); - } + step3(NO); // Step 4: Inform the delegate on main thread [self->_mainSerialQueue performBlockOnMainThread:^{ diff --git a/Source/Details/ASGraphicsContext.h b/Source/Details/ASGraphicsContext.h index 5bd153764b..e378347db6 100644 --- a/Source/Details/ASGraphicsContext.h +++ b/Source/Details/ASGraphicsContext.h @@ -13,23 +13,6 @@ NS_ASSUME_NONNULL_BEGIN -/** - * A wrapper for the UIKit drawing APIs. If you are in ASExperimentalDrawingGlobal, and you have iOS >= 10, we will create - * a UIGraphicsRenderer with an appropriate format. Otherwise, we will use UIGraphicsBeginImageContext et al. - * - * @param size The size of the context. - * @param opaque Whether the context should be opaque or not. - * @param scale The scale of the context. 0 uses main screen scale. - * @param sourceImage If you are planning to render a UIImage into this context, provide it here and we will use its - * preferred renderer format if we are using UIGraphicsImageRenderer. - * @param isCancelled An optional block for canceling the drawing before forming the image. Only takes effect under - * the legacy code path, as UIGraphicsRenderer does not support cancellation. - * @param work A block, wherein the current UIGraphics context is set based on the arguments. - * - * @return The rendered image. You can also render intermediary images using UIGraphicsGetImageFromCurrentImageContext. - */ -ASDK_EXTERN UIImage *ASGraphicsCreateImageWithOptions(CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, asdisplaynode_iscancelled_block_t NS_NOESCAPE _Nullable isCancelled, void (NS_NOESCAPE ^work)(void)) ASDISPLAYNODE_DEPRECATED_MSG("Use ASGraphicsCreateImageWithTraitCollectionAndOptions instead"); - /** * A wrapper for the UIKit drawing APIs. If you are in ASExperimentalDrawingGlobal, and you have iOS >= 10, we will create * a UIGraphicsRenderer with an appropriate format. Otherwise, we will use UIGraphicsBeginImageContext et al. @@ -47,19 +30,4 @@ ASDK_EXTERN UIImage *ASGraphicsCreateImageWithOptions(CGSize size, BOOL opaque, */ ASDK_EXTERN UIImage *ASGraphicsCreateImage(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, asdisplaynode_iscancelled_block_t _Nullable NS_NOESCAPE isCancelled, void (NS_NOESCAPE ^work)(void)); -/** -* A wrapper for the UIKit drawing APIs. -* -* @param traitCollection Trait collection. The `work` block will be executed with this trait collection, so it will affect dynamic colors, etc. -* @param size The size of the context. -* @param opaque Whether the context should be opaque or not. -* @param scale The scale of the context. 0 uses main screen scale. -* @param sourceImage If you are planning to render a UIImage into this context, provide it here and we will use its -* preferred renderer format if we are using UIGraphicsImageRenderer. -* @param work A block, wherein the current UIGraphics context is set based on the arguments. -* -* @return The rendered image. You can also render intermediary images using UIGraphicsGetImageFromCurrentImageContext. -*/ -ASDK_EXTERN UIImage *ASGraphicsCreateImageWithTraitCollectionAndOptions(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * _Nullable sourceImage, void (NS_NOESCAPE ^work)(void)) ASDISPLAYNODE_DEPRECATED_MSG("Use ASGraphicsCreateImage instead"); - NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASGraphicsContext.mm b/Source/Details/ASGraphicsContext.mm index 8f543e06f9..cb27250bb3 100644 --- a/Source/Details/ASGraphicsContext.mm +++ b/Source/Details/ASGraphicsContext.mm @@ -8,7 +8,6 @@ #import #import -#import #import #import @@ -29,16 +28,6 @@ NS_AVAILABLE_IOS(10) -NS_INLINE void ASConfigureExtendedRange(UIGraphicsImageRendererFormat *format) -{ - if (AS_AVAILABLE_IOS_TVOS(12, 12)) { - // nop. We always use automatic range on iOS >= 12. - } else { - // Currently we never do wide color. One day we could pipe this information through from the ASImageNode if it was worth it. - format.prefersExtendedRange = NO; - } -} - UIImage *ASGraphicsCreateImageWithOptions(CGSize size, BOOL opaque, CGFloat scale, UIImage *sourceImage, asdisplaynode_iscancelled_block_t NS_NOESCAPE isCancelled, void (^NS_NOESCAPE work)()) @@ -47,79 +36,6 @@ NS_INLINE void ASConfigureExtendedRange(UIGraphicsImageRendererFormat *format) } UIImage *ASGraphicsCreateImage(ASPrimitiveTraitCollection traitCollection, CGSize size, BOOL opaque, CGFloat scale, UIImage * sourceImage, asdisplaynode_iscancelled_block_t NS_NOESCAPE isCancelled, void (NS_NOESCAPE ^work)()) { - if (AS_AVAILABLE_IOS_TVOS(10, 10)) { - if (ASActivateExperimentalFeature(ASExperimentalDrawingGlobal)) { - // If they used default scale, reuse one of two preferred formats. - static UIGraphicsImageRendererFormat *defaultFormat; - static UIGraphicsImageRendererFormat *opaqueFormat; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - if (AS_AVAILABLE_IOS_TVOS(11, 11)) { - defaultFormat = [UIGraphicsImageRendererFormat preferredFormat]; - opaqueFormat = [UIGraphicsImageRendererFormat preferredFormat]; - } else { - defaultFormat = [UIGraphicsImageRendererFormat defaultFormat]; - opaqueFormat = [UIGraphicsImageRendererFormat defaultFormat]; - } - opaqueFormat.opaque = YES; - ASConfigureExtendedRange(defaultFormat); - ASConfigureExtendedRange(opaqueFormat); - }); - - UIGraphicsImageRendererFormat *format; - if (sourceImage) { - if (sourceImage.renderingMode == UIImageRenderingModeAlwaysTemplate) { - // Template images will be black and transparent, so if we use - // sourceImage.imageRenderFormat it will assume a grayscale color space. - // This is not good because a template image should be able to tint to any color, - // so we'll just use the default here. - if (AS_AVAILABLE_IOS_TVOS(11, 11)) { - format = [UIGraphicsImageRendererFormat preferredFormat]; - } else { - format = [UIGraphicsImageRendererFormat defaultFormat]; - } - } else { - format = sourceImage.imageRendererFormat; - } - // We only want the private bits (color space and bits per component) from the image. - // We have our own ideas about opacity and scale. - format.opaque = opaque; - format.scale = scale; - } else if (scale == 0 || scale == ASScreenScale()) { - format = opaque ? opaqueFormat : defaultFormat; - } else { - if (AS_AVAILABLE_IOS_TVOS(11, 11)) { - format = [UIGraphicsImageRendererFormat preferredFormat]; - } else { - format = [UIGraphicsImageRendererFormat defaultFormat]; - } - if (opaque) format.opaque = YES; - format.scale = scale; - ASConfigureExtendedRange(format); - } - - // Avoid using the imageWithActions: method because it does not support cancellation at the - // last moment i.e. before actually creating the resulting image. - __block UIImage *image; - NSError *error; - [[[UIGraphicsImageRenderer alloc] initWithSize:size format:format] - runDrawingActions:^(UIGraphicsImageRendererContext *rendererContext) { - ASDisplayNodeCAssert(UIGraphicsGetCurrentContext(), @"Should have a context!"); - ASPerformBlockWithTraitCollection(work, traitCollection); - } - completionActions:^(UIGraphicsImageRendererContext *rendererContext) { - if (isCancelled == nil || !isCancelled()) { - image = rendererContext.currentImage; - } - } - error:&error]; - if (error) { - NSCAssert(NO, @"Error drawing: %@", error); - } - return image; - } - } - // Bad OS or experiment flag. Use UIGraphics* API. UIGraphicsBeginImageContextWithOptions(size, opaque, scale); ASPerformBlockWithTraitCollection(work, traitCollection) diff --git a/Source/Details/ASImageContainerProtocolCategories.mm b/Source/Details/ASImageContainerProtocolCategories.mm index c9316c32ab..e79fcd8943 100644 --- a/Source/Details/ASImageContainerProtocolCategories.mm +++ b/Source/Details/ASImageContainerProtocolCategories.mm @@ -16,11 +16,6 @@ - (UIImage *)asdk_image return self; } -- (NSData *)asdk_animatedImageData -{ - return nil; -} - @end @implementation NSData (ASImageContainerProtocol) @@ -30,9 +25,4 @@ - (UIImage *)asdk_image return nil; } -- (NSData *)asdk_animatedImageData -{ - return self; -} - @end diff --git a/Source/Details/ASImageProtocols.h b/Source/Details/ASImageProtocols.h index d91b8a7784..41e24d6278 100644 --- a/Source/Details/ASImageProtocols.h +++ b/Source/Details/ASImageProtocols.h @@ -11,12 +11,9 @@ NS_ASSUME_NONNULL_BEGIN -@protocol ASAnimatedImageProtocol; - @protocol ASImageContainerProtocol - (nullable UIImage *)asdk_image; -- (nullable NSData *)asdk_animatedImageData; @end @@ -143,12 +140,6 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { */ - (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier; -/** - @abstract Return an object that conforms to ASAnimatedImageProtocol - @param animatedImageData Data that represents an animated image. - */ -- (nullable id )animatedImageWithData:(NSData *)animatedImageData; - /** @abstract Sets block to be called when a progress image is available. @@ -172,74 +163,4 @@ withDownloadIdentifier:(id)downloadIdentifier; @end -@protocol ASAnimatedImageProtocol - -@optional - -/** - @abstract A block which receives the cover image. Should be called when the objects cover image is ready. - */ -@property (nonatomic) void (^coverImageReadyCallback)(UIImage *coverImage); - -/** - @abstract Returns whether the supplied data contains a supported animated image format. - @param data the data to check if contains a supported animated image. - */ -- (BOOL)isDataSupported:(NSData *)data; - - -@required - -/** - @abstract Return the objects's cover image. - */ -@property (nonatomic, readonly) UIImage *coverImage; -/** - @abstract Return a boolean to indicate that the cover image is ready. - */ -@property (nonatomic, readonly) BOOL coverImageReady; -/** - @abstract Return the total duration of the animated image's playback. - */ -@property (nonatomic, readonly) CFTimeInterval totalDuration; -/** - @abstract Return the interval at which playback should occur. Will be set to a CADisplayLink's frame interval. - */ -@property (nonatomic, readonly) NSUInteger frameInterval; -/** - @abstract Return the total number of loops the animated image should play or 0 to loop infinitely. - */ -@property (nonatomic, readonly) size_t loopCount; -/** - @abstract Return the total number of frames in the animated image. - */ -@property (nonatomic, readonly) size_t frameCount; -/** - @abstract Return YES when playback is ready to occur. - */ -@property (nonatomic, readonly) BOOL playbackReady; -/** - @abstract Return any error that has occured. Playback will be paused if this returns non-nil. - */ -@property (nonatomic, readonly) NSError *error; -/** - @abstract Should be called when playback is ready. - */ -@property (nonatomic) dispatch_block_t playbackReadyCallback; - -/** - @abstract Return the image at a given index. - */ -- (CGImageRef)imageAtIndex:(NSUInteger)index; -/** - @abstract Return the duration at a given index. - */ -- (CFTimeInterval)durationAtIndex:(NSUInteger)index; -/** - @abstract Clear any cached data. Called when playback is paused. - */ -- (void)clearAnimatedImageCache; - -@end - NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASMutableAttributedStringBuilder.h b/Source/Details/ASMutableAttributedStringBuilder.h deleted file mode 100644 index d531549a0e..0000000000 --- a/Source/Details/ASMutableAttributedStringBuilder.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// ASMutableAttributedStringBuilder.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/* - * Use this class to compose new attributed strings. You may use the normal - * attributed string calls on this the same way you would on a normal mutable - * attributed string, but it coalesces your changes into transactions on the - * actual string allowing improvements in performance. - * - * @discussion This is a use-once and throw away class for each string you make. - * Since this class is designed for increasing performance, we actually hand - * back the internally managed mutable attributed string in the - * `composedAttributedString` call. So once you make that call, any more - * changes will actually modify the string that was handed back to you in that - * method. - * - * Combination of multiple calls into single attribution is managed through - * merging of attribute dictionaries over ranges. For best performance, call - * collections of attributions over a single range together. So for instance, - * don't call addAttributes for range1, then range2, then range1 again. Group - * them together so you call addAttributes for both range1 together, and then - * range2. - * - * Also please note that switching between addAttribute and setAttributes in the - * middle of composition is a bad idea for performance because they have - * semantically different meanings, and trigger a commit of the pending - * attributes. - * - * Please note that ALL of the standard NSString methods are left unimplemented. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASMutableAttributedStringBuilder : NSMutableAttributedString - -- (instancetype)initWithString:(NSString *)str attributes:(nullable NSDictionary *)attrs; -- (instancetype)initWithAttributedString:(NSAttributedString *)attrStr; - -- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str; -- (void)setAttributes:(nullable NSDictionary *)attrs range:(NSRange)range; - -- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range; -- (void)addAttributes:(NSDictionary *)attrs range:(NSRange)range; -- (void)removeAttribute:(NSString *)name range:(NSRange)range; - -- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(NSAttributedString *)attrString; -- (void)insertAttributedString:(NSAttributedString *)attrString atIndex:(NSUInteger)loc; -- (void)appendAttributedString:(NSAttributedString *)attrString; -- (void)deleteCharactersInRange:(NSRange)range; -- (void)setAttributedString:(NSAttributedString *)attrString; - -- (NSMutableAttributedString *)composedAttributedString; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASMutableAttributedStringBuilder.mm b/Source/Details/ASMutableAttributedStringBuilder.mm deleted file mode 100644 index b393fe1118..0000000000 --- a/Source/Details/ASMutableAttributedStringBuilder.mm +++ /dev/null @@ -1,254 +0,0 @@ -// -// ASMutableAttributedStringBuilder.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@implementation ASMutableAttributedStringBuilder { - // Flag for the type of the current transaction (set or add) - BOOL _setRange; - // The range over which the currently pending transaction will occur - NSRange _pendingRange; - // The actual attribute dictionary that is being composed - NSMutableDictionary *_pendingRangeAttributes; - NSMutableAttributedString *_attrStr; - - // We delay initialization of the _attrStr until we need to - NSString *_initString; -} - -- (instancetype)init -{ - if (self = [super init]) { - _attrStr = [[NSMutableAttributedString alloc] init]; - _pendingRange.location = NSNotFound; - } - return self; -} - -- (instancetype)initWithString:(NSString *)str -{ - return [self initWithString:str attributes:@{}]; -} - -- (instancetype)initWithString:(NSString *)str attributes:(NSDictionary *)attrs -{ - if (self = [super init]) { - // We cache this in an ivar that we can lazily construct the attributed - // string with when we get to a forced commit point. - _initString = str; - // Triggers a creation of the _pendingRangeAttributes dictionary which then - // is filled with entries from the given attrs dict. - [[self _pendingRangeAttributes] addEntriesFromDictionary:attrs]; - _setRange = NO; - _pendingRange = NSMakeRange(0, _initString.length); - } - return self; -} - -- (instancetype)initWithAttributedString:(NSAttributedString *)attrStr -{ - if (self = [super init]) { - _attrStr = [[NSMutableAttributedString alloc] initWithAttributedString:attrStr]; - _pendingRange.location = NSNotFound; - } - return self; -} - -- (NSMutableAttributedString *)_attributedString -{ - if (_attrStr == nil && _initString != nil) { - // We can lazily construct the attributed string if it hasn't already been - // created with the existing pending attributes. This is significantly - // faster if more attributes are added after initializing this instance - // and the new attributions are for the entire string anyway. - _attrStr = [[NSMutableAttributedString alloc] initWithString:_initString attributes:_pendingRangeAttributes]; - _pendingRangeAttributes = nil; - _pendingRange.location = NSNotFound; - _initString = nil; - } - - return _attrStr; -} - -#pragma mark - Pending attribution - -- (NSMutableDictionary *)_pendingRangeAttributes -{ - // Lazy dictionary creation. Call this if you want to force initialization, - // otherwise just use the ivar. - if (_pendingRangeAttributes == nil) { - _pendingRangeAttributes = [[NSMutableDictionary alloc] init]; - } - return _pendingRangeAttributes; -} - -- (void)_applyPendingRangeAttributions -{ - if (_attrStr == nil) { - // Trigger its creation if it doesn't exist. - [self _attributedString]; - } - - if (_pendingRangeAttributes.count == 0) { - return; - } - - if (_pendingRange.location == NSNotFound) { - return; - } - - if (_setRange) { - [[self _attributedString] setAttributes:_pendingRangeAttributes range:_pendingRange]; - } else { - [[self _attributedString] addAttributes:_pendingRangeAttributes range:_pendingRange]; - } - _pendingRangeAttributes = nil; - _pendingRange.location = NSNotFound; -} - -#pragma mark - Editing - -- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] replaceCharactersInRange:range withString:str]; -} - -- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(NSAttributedString *)attrString -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] replaceCharactersInRange:range withAttributedString:attrString]; -} - -- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range -{ - if (_setRange) { - [self _applyPendingRangeAttributions]; - _setRange = NO; - } - - if (!NSEqualRanges(_pendingRange, range)) { - [self _applyPendingRangeAttributions]; - _pendingRange = range; - } - - NSMutableDictionary *pendingAttributes = [self _pendingRangeAttributes]; - pendingAttributes[name] = value; -} - -- (void)addAttributes:(NSDictionary *)attrs range:(NSRange)range -{ - if (_setRange) { - [self _applyPendingRangeAttributions]; - _setRange = NO; - } - - if (!NSEqualRanges(_pendingRange, range)) { - [self _applyPendingRangeAttributions]; - _pendingRange = range; - } - - NSMutableDictionary *pendingAttributes = [self _pendingRangeAttributes]; - [pendingAttributes addEntriesFromDictionary:attrs]; -} - -- (void)insertAttributedString:(NSAttributedString *)attrString atIndex:(NSUInteger)loc -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] insertAttributedString:attrString atIndex:loc]; -} - -- (void)appendAttributedString:(NSAttributedString *)attrString -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] appendAttributedString:attrString]; -} - -- (void)deleteCharactersInRange:(NSRange)range -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] deleteCharactersInRange:range]; -} - -- (void)setAttributedString:(NSAttributedString *)attrString -{ - [self _applyPendingRangeAttributions]; - [[self _attributedString] setAttributedString:attrString]; -} - -- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range -{ - if (!_setRange) { - [self _applyPendingRangeAttributions]; - _setRange = YES; - } - - if (!NSEqualRanges(_pendingRange, range)) { - [self _applyPendingRangeAttributions]; - _pendingRange = range; - } - - NSMutableDictionary *pendingAttributes = [self _pendingRangeAttributes]; - [pendingAttributes addEntriesFromDictionary:attrs]; -} - -- (void)removeAttribute:(NSString *)name range:(NSRange)range -{ - // This call looks like the other set/add functions, but in order for this - // function to perform as advertised we MUST first add the attributes we - // currently have pending. - [self _applyPendingRangeAttributions]; - - [[self _attributedString] removeAttribute:name range:range]; -} - -#pragma mark - Output - -- (NSMutableAttributedString *)composedAttributedString -{ - if (_pendingRangeAttributes.count > 0) { - [self _applyPendingRangeAttributions]; - } - return [self _attributedString]; -} - -#pragma mark - Forwarding - -- (NSUInteger)length -{ - // If we just want a length call, no need to lazily construct the attributed string - return _attrStr ? _attrStr.length : _initString.length; -} - -- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRangePointer)range -{ - return [[self _attributedString] attributesAtIndex:location effectiveRange:range]; -} - -- (NSString *)string -{ - return _attrStr ? _attrStr.string : _initString; -} - -- (NSMutableString *)mutableString -{ - return [[self _attributedString] mutableString]; -} - -- (void)beginEditing -{ - [[self _attributedString] beginEditing]; -} - -- (void)endEditing -{ - [[self _attributedString] endEditing]; -} - -@end diff --git a/Source/Details/ASPINRemoteImageDownloader.h b/Source/Details/ASPINRemoteImageDownloader.h deleted file mode 100644 index cbbe8574cb..0000000000 --- a/Source/Details/ASPINRemoteImageDownloader.h +++ /dev/null @@ -1,80 +0,0 @@ -// -// ASPINRemoteImageDownloader.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_PIN_REMOTE_IMAGE - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class PINRemoteImageManager; -@protocol PINRemoteImageCaching; - -@interface ASPINRemoteImageDownloader : NSObject - -/** - * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes. - * The userInfo provided by this downloader is an instance of `PINRemoteImageManagerResult`. - * - * This is the default downloader used by network backed image nodes if PINRemoteImage and PINCache are - * available. It uses PINRemoteImage's features to provide caching and progressive image downloads. - */ -+ (ASPINRemoteImageDownloader *)sharedDownloader NS_RETURNS_RETAINED; - -/** - * Sets the default NSURLSessionConfiguration that will be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes - * while loading images off the network. This must be specified early in the application lifecycle before - * `sharedDownloader` is accessed. - * - * @param configuration The session configuration that will be used by `sharedDownloader` - * - */ -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration; - -/** - * Sets the default NSURLSessionConfiguration that will be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes - * while loading images off the network. This must be specified early in the application lifecycle before - * `sharedDownloader` is accessed. - * - * @param configuration The session configuration that will be used by `sharedDownloader` - * @param imageCache The cache to be used by PINRemoteImage - nil will set up a default cache: PINCache - * if it is available, PINRemoteImageBasicCache (NSCache) if not. - * - */ -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration - imageCache:(nullable id)imageCache; - -/** - * Sets a custom preconfigured PINRemoteImageManager that will be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes - * while loading images off the network. This must be specified early in the application lifecycle before - * `sharedDownloader` is accessed. - * - * @param preconfiguredPINRemoteImageManager The preconfigured remote image manager that will be used by `sharedDownloader` - */ -+ (void)setSharedPreconfiguredRemoteImageManager:(PINRemoteImageManager *)preconfiguredPINRemoteImageManager; - -/** - * The shared instance of a @c PINRemoteImageManager used by all @c ASPINRemoteImageDownloaders - * - * @discussion you can use this method to access the shared manager. This is useful to share a cache - * and resources if you need to download images outside of an @c ASNetworkImageNode or - * @c ASMultiplexImageNode. It's also useful to access the memoryCache and diskCache to set limits - * or handle authentication challenges. - * - * @return An instance of a @c PINRemoteImageManager - */ -- (PINRemoteImageManager *)sharedPINRemoteImageManager; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/Source/Details/ASPINRemoteImageDownloader.mm b/Source/Details/ASPINRemoteImageDownloader.mm deleted file mode 100644 index df538f019a..0000000000 --- a/Source/Details/ASPINRemoteImageDownloader.mm +++ /dev/null @@ -1,390 +0,0 @@ -// -// ASPINRemoteImageDownloader.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_PIN_REMOTE_IMAGE -#import - -#import -#import -#import - -#if __has_include () -#define PIN_ANIMATED_AVAILABLE 1 -#import -#import -#else -#define PIN_ANIMATED_AVAILABLE 0 -#endif - -#if __has_include() -#define PIN_WEBP_AVAILABLE 1 -#else -#define PIN_WEBP_AVAILABLE 0 -#endif - -#import -#import -#import - -static inline PINRemoteImageManagerPriority PINRemoteImageManagerPriorityWithASImageDownloaderPriority(ASImageDownloaderPriority priority) { - switch (priority) { - case ASImageDownloaderPriorityPreload: - return PINRemoteImageManagerPriorityLow; - - case ASImageDownloaderPriorityImminent: - return PINRemoteImageManagerPriorityDefault; - - case ASImageDownloaderPriorityVisible: - return PINRemoteImageManagerPriorityHigh; - } -} - -#if PIN_ANIMATED_AVAILABLE - -@interface ASPINRemoteImageDownloader () -@end - -@interface PINCachedAnimatedImage (ASPINRemoteImageDownloader) -@end - -@implementation PINCachedAnimatedImage (ASPINRemoteImageDownloader) - -- (BOOL)isDataSupported:(NSData *)data -{ - if ([data pin_isGIF]) { - return YES; - } -#if PIN_WEBP_AVAILABLE - else if ([data pin_isAnimatedWebP]) { - return YES; - } -#endif - return NO; -} - -@end -#endif - -// Declare two key methods on PINCache objects, avoiding a direct dependency on PINCache.h -@protocol ASPINCache -- (id)diskCache; -@end - -@protocol ASPINDiskCache -@property NSUInteger byteLimit; -@end - -@interface ASPINRemoteImageManager : PINRemoteImageManager -@end - -@implementation ASPINRemoteImageManager - -//Share image cache with sharedImageManager image cache. -+ (id )defaultImageCache -{ - static dispatch_once_t onceToken; - static id cache = nil; - dispatch_once(&onceToken, ^{ - cache = [[PINRemoteImageManager sharedImageManager] cache]; - if ([cache respondsToSelector:@selector(diskCache)]) { - id diskCache = [(id )cache diskCache]; - if ([diskCache respondsToSelector:@selector(setByteLimit:)]) { - // Set a default byteLimit. PINCache recently implemented a 50MB default (PR #201). - // Ensure that older versions of PINCache also have a byteLimit applied. - // NOTE: Using 20MB limit while large cache initialization is being optimized (Issue #144). - ((id )diskCache).byteLimit = 20 * 1024 * 1024; - } - } - }); - return cache; -} - -@end - -static ASPINRemoteImageDownloader *sharedDownloader = nil; -static PINRemoteImageManager *sharedPINRemoteImageManager = nil; - -@interface ASPINRemoteImageDownloader () -@end - -@implementation ASPINRemoteImageDownloader - -+ (ASPINRemoteImageDownloader *)sharedDownloader NS_RETURNS_RETAINED -{ - static dispatch_once_t onceToken = 0; - dispatch_once(&onceToken, ^{ - sharedDownloader = [[ASPINRemoteImageDownloader alloc] init]; - }); - return sharedDownloader; -} - -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration -{ - NSAssert(sharedDownloader == nil, @"Singleton has been created and session can no longer be configured."); - PINRemoteImageManager *sharedManager = [self PINRemoteImageManagerWithConfiguration:configuration imageCache:nil]; - [self setSharedPreconfiguredRemoteImageManager:sharedManager]; -} - -+ (void)setSharedImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration - imageCache:(nullable id)imageCache -{ - NSAssert(sharedDownloader == nil, @"Singleton has been created and session can no longer be configured."); - PINRemoteImageManager *sharedManager = [self PINRemoteImageManagerWithConfiguration:configuration imageCache:imageCache]; - [self setSharedPreconfiguredRemoteImageManager:sharedManager]; -} - -static dispatch_once_t shared_init_predicate; - -+ (void)setSharedPreconfiguredRemoteImageManager:(PINRemoteImageManager *)preconfiguredPINRemoteImageManager -{ - NSAssert(preconfiguredPINRemoteImageManager != nil, @"setSharedPreconfiguredRemoteImageManager requires a non-nil parameter"); - NSAssert1(sharedPINRemoteImageManager == nil, @"An instance of %@ has been set. Either configuration or preconfigured image manager can be set at a time and only once.", [[sharedPINRemoteImageManager class] description]); - - dispatch_once(&shared_init_predicate, ^{ - sharedPINRemoteImageManager = preconfiguredPINRemoteImageManager; - }); -} - -+ (PINRemoteImageManager *)PINRemoteImageManagerWithConfiguration:(nullable NSURLSessionConfiguration *)configuration imageCache:(nullable id)imageCache -{ - PINRemoteImageManager *manager = nil; -#if DEBUG - // Check that Carthage users have linked both PINRemoteImage & PINCache by testing for one file each - if (!(NSClassFromString(@"PINRemoteImageManager"))) { - NSException *e = [NSException - exceptionWithName:@"FrameworkSetupException" - reason:@"Missing the path to the PINRemoteImage framework." - userInfo:nil]; - @throw e; - } - if (!(NSClassFromString(@"PINCache"))) { - NSException *e = [NSException - exceptionWithName:@"FrameworkSetupException" - reason:@"Missing the path to the PINCache framework." - userInfo:nil]; - @throw e; - } -#endif -#if PIN_ANIMATED_AVAILABLE - manager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration - alternativeRepresentationProvider:[self sharedDownloader] - imageCache:imageCache]; -#else - manager = [[ASPINRemoteImageManager alloc] initWithSessionConfiguration:configuration - alternativeRepresentationProvider:nil - imageCache:imageCache]; -#endif - return manager; -} - -- (PINRemoteImageManager *)sharedPINRemoteImageManager -{ - dispatch_once(&shared_init_predicate, ^{ - sharedPINRemoteImageManager = [ASPINRemoteImageDownloader PINRemoteImageManagerWithConfiguration:nil imageCache:nil]; - }); - return sharedPINRemoteImageManager; -} - -- (BOOL)sharedImageManagerSupportsMemoryRemoval -{ - static BOOL sharedImageManagerSupportsMemoryRemoval = NO; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedImageManagerSupportsMemoryRemoval = [[[self sharedPINRemoteImageManager] cache] respondsToSelector:@selector(removeObjectForKeyFromMemory:)]; - }); - return sharedImageManagerSupportsMemoryRemoval; -} - -#pragma mark ASImageProtocols - -#if PIN_ANIMATED_AVAILABLE -- (nullable id )animatedImageWithData:(NSData *)animatedImageData -{ - return [[PINCachedAnimatedImage alloc] initWithAnimatedImageData:animatedImageData]; -} -#endif - -- (id )synchronouslyFetchedCachedImageWithURL:(NSURL *)URL; -{ - PINRemoteImageManager *manager = [self sharedPINRemoteImageManager]; - PINRemoteImageManagerResult *result = [manager synchronousImageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode]; - -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - return result.alternativeRepresentation; - } -#endif - return result.image; -} - -- (void)cachedImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - completion:(ASImageCacherCompletion)completion -{ - [[self sharedPINRemoteImageManager] imageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult * _Nonnull result) { - [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ - ASImageCacheType cacheType = (result.resultType == PINRemoteImageResultTypeMemoryCache ? ASImageCacheTypeSynchronous : ASImageCacheTypeAsynchronous); -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation, cacheType); - } else { - completion(result.image, cacheType); - } -#else - completion(result.image, cacheType); -#endif - }]; - }]; -} - -- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL -{ - if ([self sharedImageManagerSupportsMemoryRemoval]) { - PINRemoteImageManager *manager = [self sharedPINRemoteImageManager]; - NSString *key = [manager cacheKeyForURL:URL processorKey:nil]; - [[manager cache] removeObjectForKeyFromMemory:key]; - } -} - -- (nullable id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion; -{ - return [self downloadImageWithURL:URL - priority:ASImageDownloaderPriorityImminent // maps to default priority - callbackQueue:callbackQueue - downloadProgress:downloadProgress - completion:completion]; -} - -- (nullable id)downloadImageWithURL:(NSURL *)URL - priority:(ASImageDownloaderPriority)priority - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion -{ - PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityWithASImageDownloaderPriority(priority); - - PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) { - if (downloadProgress == nil) { return; } - - [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ - downloadProgress(completedBytes / (CGFloat)totalBytes); - }]; - }; - - PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) { - [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation, result.error, result.UUID, result); - } else { - completion(result.image, result.error, result.UUID, result); - } -#else - completion(result.image, result.error, result.UUID, result); -#endif - }]; - }; - - // add "IgnoreCache" option since we have a caching API so we already checked it, not worth checking again. - // PINRemoteImage is responsible for coalescing downloads, and even if it wasn't, the tiny probability of - // 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. - return [[self sharedPINRemoteImageManager] downloadImageWithURL:URL - options:PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache - priority:pi_priority - progressImage:nil - progressDownload:progressDownload - completion:imageCompletion]; -} - -- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:NO]; -} - -- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:YES]; -} - -- (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - - if (progressBlock) { - [[self sharedPINRemoteImageManager] setProgressImageCallback:^(PINRemoteImageManagerResult * _Nonnull result) { - dispatch_async(callbackQueue, ^{ - progressBlock(result.image, result.renderedImageQuality, result.UUID); - }); - } ofTaskWithUUID:downloadIdentifier]; - } else { - [[self sharedPINRemoteImageManager] setProgressImageCallback:nil ofTaskWithUUID:downloadIdentifier]; - } -} - -- (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier -{ - ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - - PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityWithASImageDownloaderPriority(priority); - [[self sharedPINRemoteImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier]; -} - -#pragma mark - PINRemoteImageManagerAlternateRepresentationProvider - -- (id)alternateRepresentationWithData:(NSData *)data options:(PINRemoteImageManagerDownloadOptions)options -{ -#if PIN_ANIMATED_AVAILABLE - if ([data pin_isAnimatedGIF]) { - return data; - } -#if PIN_WEBP_AVAILABLE - else if ([data pin_isAnimatedWebP]) { - return data; - } -#endif - -#endif - return nil; -} - -#pragma mark - Private - -/** - * If on main thread and queue is main, perform now. - * If queue is nil, assert and perform now. - * Otherwise, dispatch async to queue. - */ -+ (void)_performWithCallbackQueue:(dispatch_queue_t)queue work:(void (^)(void))work -{ - if (work == nil) { - // No need to assert here, really. We aren't expecting any feedback from this method. - return; - } - - if (ASDisplayNodeThreadIsMain() && queue == dispatch_get_main_queue()) { - work(); - } else if (queue == nil) { - ASDisplayNodeFailAssert(@"Callback queue should not be nil."); - work(); - } else { - dispatch_async(queue, work); - } -} - -@end -#endif diff --git a/Source/Details/ASPageTable.h b/Source/Details/ASPageTable.h deleted file mode 100644 index e9cd11a250..0000000000 --- a/Source/Details/ASPageTable.h +++ /dev/null @@ -1,120 +0,0 @@ -// -// ASPageTable.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import - -@class ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Represents x and y coordinates of a page. - */ -typedef uintptr_t ASPageCoordinate; - -/** - * Returns a page coordinate with the given x and y values. Both of them must be less than 65,535. - */ -ASDK_EXTERN ASPageCoordinate ASPageCoordinateMake(uint16_t x, uint16_t y) AS_WARN_UNUSED_RESULT; - -/** - * Returns coordinate of the page that contains the specified point. - * Similar to CGRectContainsPoint, a point is considered inside a page if its lie inside the page or on the minimum X or minimum Y edge. - * - * @param point The point that the page at the returned should contain. Any negative of the point will be corrected to 0.0 - * - * @param pageSize The size of each page. - */ -ASDK_EXTERN ASPageCoordinate ASPageCoordinateForPageThatContainsPoint(CGPoint point, CGSize pageSize) AS_WARN_UNUSED_RESULT; - -ASDK_EXTERN uint16_t ASPageCoordinateGetX(ASPageCoordinate pageCoordinate) AS_WARN_UNUSED_RESULT; - -ASDK_EXTERN uint16_t ASPageCoordinateGetY(ASPageCoordinate pageCoordinate) AS_WARN_UNUSED_RESULT; - -ASDK_EXTERN CGRect ASPageCoordinateGetPageRect(ASPageCoordinate pageCoordinate, CGSize pageSize) AS_WARN_UNUSED_RESULT; - -/** - * Returns coordinate pointers for pages that intersect the specified rect. For each pointer, use ASPageCoordinateFromPointer() to get the original coordinate. - * The specified rect is restricted to the bounds of a content rect that has an origin of {0, 0} and a size of the given contentSize. - * - * @param rect The rect intersecting the target pages. - * - * @param contentSize The combined size of all pages. - * - * @param pageSize The size of each page. - */ -ASDK_EXTERN NSPointerArray * _Nullable ASPageCoordinatesForPagesThatIntersectRect(CGRect rect, CGSize contentSize, CGSize pageSize) AS_WARN_UNUSED_RESULT; - -/** - * An alias for an NSMapTable created to store objects using ASPageCoordinates as keys. - * - * You should not call -objectForKey:, -setObject:forKey:, or -removeObjectForKey: - * on these objects. - */ -typedef NSMapTable ASPageTable; - -/** - * A page to array of layout attributes table. - */ -typedef ASPageTable *> ASPageToLayoutAttributesTable; - -/** - * A category for creating & using map tables meant for storing objects using ASPage as keys. - */ -@interface NSMapTable (ASPageTableMethods) - -/** - * Creates a new page table with (NSMapTableStrongMemory | NSMapTableObjectPointerPersonality) for values. - */ -+ (ASPageTable *)pageTableForStrongObjectPointers NS_RETURNS_RETAINED; - -/** - * Creates a new page table with (NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) for values. - */ -+ (ASPageTable *)pageTableForWeakObjectPointers NS_RETURNS_RETAINED; - -/** - * Builds a new page to layout attributes from the given layout attributes. - * - * @param layoutAttributesEnumerator The layout attributes to build from - * - * @param contentSize The combined size of all pages. - * - * @param pageSize The size of each page. - */ -+ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize NS_RETURNS_RETAINED; - -/** - * Retrieves the object for a given page, or nil if the page is not found. - * - * @param page A page to lookup the object for. - */ -- (nullable ObjectType)objectForPage:(ASPageCoordinate)page; - -/** - * Sets the given object for the associated page. - * - * @param object The object to store as value. - * - * @param page The page to use for the rect. - */ -- (void)setObject:(ObjectType)object forPage:(ASPageCoordinate)page; - -/** - * Removes the object for the given page, if one exists. - * - * @param page The page to remove. - */ -- (void)removeObjectForPage:(ASPageCoordinate)page; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASPageTable.mm b/Source/Details/ASPageTable.mm deleted file mode 100644 index fde810830a..0000000000 --- a/Source/Details/ASPageTable.mm +++ /dev/null @@ -1,147 +0,0 @@ -// -// ASPageTable.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -ASPageCoordinate ASPageCoordinateMake(uint16_t x, uint16_t y) -{ - // Add 1 to the end result because 0 is not accepted by NSArray and NSMapTable. - // To avoid overflow after adding, x and y can't be UINT16_MAX (0xFFFF) **at the same time**. - // But for API simplification, we enforce the same restriction to both values. - ASDisplayNodeCAssert(x < UINT16_MAX, @"x coordinate must be less than 65,535"); - ASDisplayNodeCAssert(y < UINT16_MAX, @"y coordinate must be less than 65,535"); - return (x << 16) + y + 1; -} - -ASPageCoordinate ASPageCoordinateForPageThatContainsPoint(CGPoint point, CGSize pageSize) -{ - return ASPageCoordinateMake((MAX(0.0, point.x) / pageSize.width), (MAX(0.0, point.y) / pageSize.height)); -} - -uint16_t ASPageCoordinateGetX(ASPageCoordinate pageCoordinate) -{ - return (pageCoordinate - 1) >> 16; -} - -uint16_t ASPageCoordinateGetY(ASPageCoordinate pageCoordinate) -{ - return (pageCoordinate - 1) & ~(0xFFFF<<16); -} - -CGRect ASPageCoordinateGetPageRect(ASPageCoordinate pageCoordinate, CGSize pageSize) -{ - CGFloat pageWidth = pageSize.width; - CGFloat pageHeight = pageSize.height; - return CGRectMake(ASPageCoordinateGetX(pageCoordinate) * pageWidth, ASPageCoordinateGetY(pageCoordinate) * pageHeight, pageWidth, pageHeight); -} - -NSPointerArray *ASPageCoordinatesForPagesThatIntersectRect(CGRect rect, CGSize contentSize, CGSize pageSize) -{ - CGRect contentRect = CGRectMake(0.0, 0.0, contentSize.width, contentSize.height); - // Make sure the specified rect is within contentRect - rect = CGRectIntersection(rect, contentRect); - if (CGRectIsNull(rect) || CGRectIsEmpty(rect)) { - return nil; - } - - NSPointerArray *result = [NSPointerArray pointerArrayWithOptions:(NSPointerFunctionsIntegerPersonality | NSPointerFunctionsOpaqueMemory)]; - - ASPageCoordinate minPage = ASPageCoordinateForPageThatContainsPoint(CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect)), pageSize); - ASPageCoordinate maxPage = ASPageCoordinateForPageThatContainsPoint(CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect)), pageSize); - if (minPage == maxPage) { - [result addPointer:(void *)minPage]; - return result; - } - - NSUInteger minX = ASPageCoordinateGetX(minPage); - NSUInteger minY = ASPageCoordinateGetY(minPage); - NSUInteger maxX = ASPageCoordinateGetX(maxPage); - NSUInteger maxY = ASPageCoordinateGetY(maxPage); - - for (NSUInteger x = minX; x <= maxX; x++) { - for (NSUInteger y = minY; y <= maxY; y++) { - ASPageCoordinate page = ASPageCoordinateMake(x, y); - [result addPointer:(void *)page]; - } - } - - return result; -} - -@implementation NSMapTable (ASPageTableMethods) - -+ (instancetype)pageTableWithValuePointerFunctions:(NSPointerFunctions *)valueFuncs NS_RETURNS_RETAINED -{ - static NSPointerFunctions *pageCoordinatesFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pageCoordinatesFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsIntegerPersonality | NSPointerFunctionsOpaqueMemory]; - }); - - return [[NSMapTable alloc] initWithKeyPointerFunctions:pageCoordinatesFuncs valuePointerFunctions:valueFuncs capacity:0]; -} - -+ (ASPageTable *)pageTableForStrongObjectPointers NS_RETURNS_RETAINED -{ - static NSPointerFunctions *strongObjectPointerFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - strongObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory]; - }); - return [self pageTableWithValuePointerFunctions:strongObjectPointerFuncs]; -} - -+ (ASPageTable *)pageTableForWeakObjectPointers NS_RETURNS_RETAINED -{ - static NSPointerFunctions *weakObjectPointerFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - weakObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsWeakMemory]; - }); - return [self pageTableWithValuePointerFunctions:weakObjectPointerFuncs]; -} - -+ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize NS_RETURNS_RETAINED -{ - ASPageToLayoutAttributesTable *result = [ASPageTable pageTableForStrongObjectPointers]; - for (UICollectionViewLayoutAttributes *attrs in layoutAttributesEnumerator) { - // This attrs may span multiple pages. Make sure it's registered to all of them - NSPointerArray *pages = ASPageCoordinatesForPagesThatIntersectRect(attrs.frame, contentSize, pageSize); - - for (id pagePtr in pages) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSMutableArray *attrsInPage = [result objectForPage:page]; - if (attrsInPage == nil) { - attrsInPage = [[NSMutableArray alloc] init]; - [result setObject:attrsInPage forPage:page]; - } - [attrsInPage addObject:attrs]; - } - } - return result; -} - -- (id)objectForPage:(ASPageCoordinate)page -{ - unowned id key = (__bridge id)(void *)page; - return [self objectForKey:key]; -} - -- (void)setObject:(id)object forPage:(ASPageCoordinate)page -{ - unowned id key = (__bridge id)(void *)page; - [self setObject:object forKey:key]; -} - -- (void)removeObjectForPage:(ASPageCoordinate)page -{ - unowned id key = (__bridge id)(void *)page; - [self removeObjectForKey:key]; -} - -@end diff --git a/Source/Details/ASPhotosFrameworkImageRequest.h b/Source/Details/ASPhotosFrameworkImageRequest.h deleted file mode 100644 index 90f04fa23e..0000000000 --- a/Source/Details/ASPhotosFrameworkImageRequest.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// ASPhotosFrameworkImageRequest.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_USE_PHOTOS - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -ASDK_EXTERN NSString *const ASPhotosURLScheme; - -/** - @abstract Use ASPhotosFrameworkImageRequest to encapsulate all the information needed to request an image from - the Photos framework and store it in a URL. - */ -API_AVAILABLE(ios(8.0), tvos(10.0)) -@interface ASPhotosFrameworkImageRequest : NSObject - -- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER; - -/** - @return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL. - */ -+ (nullable ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url; - -/** - @abstract The asset identifier for this image request provided during initialization. - */ -@property (nonatomic, readonly) NSString *assetIdentifier; - -/** - @abstract The target size for this image request. Defaults to `PHImageManagerMaximumSize`. - */ -@property (nonatomic) CGSize targetSize; - -/** - @abstract The content mode for this image request. Defaults to `PHImageContentModeDefault`. - - @see `PHImageManager` - */ -@property (nonatomic) PHImageContentMode contentMode; - -/** - @abstract The options specified for this request. Default value is the result of `[PHImageRequestOptions new]`. - - @discussion Some properties of this object are ignored when converting this request into a URL. - As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`. - */ -@property (nonatomic) PHImageRequestOptions *options; - -/** - @return A new URL converted from this request. - */ -@property (nonatomic, readonly) NSURL *url; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_USE_PHOTOS diff --git a/Source/Details/ASPhotosFrameworkImageRequest.mm b/Source/Details/ASPhotosFrameworkImageRequest.mm deleted file mode 100644 index ac1d4773b1..0000000000 --- a/Source/Details/ASPhotosFrameworkImageRequest.mm +++ /dev/null @@ -1,157 +0,0 @@ -// -// ASPhotosFrameworkImageRequest.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_USE_PHOTOS - -NSString *const ASPhotosURLScheme = @"ph"; - -static NSString *const _ASPhotosURLQueryKeyWidth = @"width"; -static NSString *const _ASPhotosURLQueryKeyHeight = @"height"; - -// value is PHImageContentMode value -static NSString *const _ASPhotosURLQueryKeyContentMode = @"contentmode"; - -// value is PHImageRequestOptionsResizeMode value -static NSString *const _ASPhotosURLQueryKeyResizeMode = @"resizemode"; - -// value is PHImageRequestOptionsDeliveryMode value -static NSString *const _ASPhotosURLQueryKeyDeliveryMode = @"deliverymode"; - -// value is PHImageRequestOptionsVersion value -static NSString *const _ASPhotosURLQueryKeyVersion = @"version"; - -// value is 0 or 1 -static NSString *const _ASPhotosURLQueryKeyAllowNetworkAccess = @"network"; - -static NSString *const _ASPhotosURLQueryKeyCropOriginX = @"crop_x"; -static NSString *const _ASPhotosURLQueryKeyCropOriginY = @"crop_y"; -static NSString *const _ASPhotosURLQueryKeyCropWidth = @"crop_w"; -static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; - -@implementation ASPhotosFrameworkImageRequest - -- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier -{ - self = [super init]; - if (self) { - _assetIdentifier = assetIdentifier; - _options = [PHImageRequestOptions new]; - _contentMode = PHImageContentModeDefault; - _targetSize = PHImageManagerMaximumSize; - } - return self; -} - -#pragma mark NSCopying - -- (id)copyWithZone:(NSZone *)zone -{ - ASPhotosFrameworkImageRequest *copy = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier]; - copy.options = [self.options copy]; - copy.targetSize = self.targetSize; - copy.contentMode = self.contentMode; - return copy; -} - -#pragma mark Converting to URL - -- (NSURL *)url -{ - NSURLComponents *comp = [NSURLComponents new]; - comp.scheme = ASPhotosURLScheme; - comp.host = _assetIdentifier; - NSMutableArray *queryItems = [NSMutableArray arrayWithObjects: - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyWidth value:@(_targetSize.width).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyHeight value:@(_targetSize.height).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyVersion value:@(_options.version).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyContentMode value:@(_contentMode).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyAllowNetworkAccess value:@(_options.networkAccessAllowed).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyDeliveryMode value:@(_options.deliveryMode).stringValue] - , nil]; - - CGRect cropRect = _options.normalizedCropRect; - if (!CGRectIsEmpty(cropRect)) { - [queryItems addObjectsFromArray:@[ - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginX value:@(cropRect.origin.x).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginY value:@(cropRect.origin.y).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropWidth value:@(cropRect.size.width).stringValue], - [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropHeight value:@(cropRect.size.height).stringValue] - ]]; - } - comp.queryItems = queryItems; - return comp.URL; -} - -#pragma mark Converting from URL - -+ (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url -{ - // not a photos URL - if (![url.scheme isEqualToString:ASPhotosURLScheme]) { - return nil; - } - - NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - - ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:url.host]; - - CGRect cropRect = CGRectZero; - CGSize targetSize = PHImageManagerMaximumSize; - for (NSURLQueryItem *item in comp.queryItems) { - if ([_ASPhotosURLQueryKeyAllowNetworkAccess isEqualToString:item.name]) { - request.options.networkAccessAllowed = item.value.boolValue; - } else if ([_ASPhotosURLQueryKeyWidth isEqualToString:item.name]) { - targetSize.width = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyHeight isEqualToString:item.name]) { - targetSize.height = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyContentMode isEqualToString:item.name]) { - request.contentMode = (PHImageContentMode)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyVersion isEqualToString:item.name]) { - request.options.version = (PHImageRequestOptionsVersion)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyCropOriginX isEqualToString:item.name]) { - cropRect.origin.x = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropOriginY isEqualToString:item.name]) { - cropRect.origin.y = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropWidth isEqualToString:item.name]) { - cropRect.size.width = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyCropHeight isEqualToString:item.name]) { - cropRect.size.height = item.value.doubleValue; - } else if ([_ASPhotosURLQueryKeyResizeMode isEqualToString:item.name]) { - request.options.resizeMode = (PHImageRequestOptionsResizeMode)item.value.integerValue; - } else if ([_ASPhotosURLQueryKeyDeliveryMode isEqualToString:item.name]) { - request.options.deliveryMode = (PHImageRequestOptionsDeliveryMode)item.value.integerValue; - } - } - request.targetSize = targetSize; - request.options.normalizedCropRect = cropRect; - return request; -} - -#pragma mark NSObject - -- (BOOL)isEqual:(id)object -{ - if (![object isKindOfClass:ASPhotosFrameworkImageRequest.class]) { - return NO; - } - ASPhotosFrameworkImageRequest *other = object; - return [other.assetIdentifier isEqualToString:self.assetIdentifier] && - other.contentMode == self.contentMode && - CGSizeEqualToSize(other.targetSize, self.targetSize) && - CGRectEqualToRect(other.options.normalizedCropRect, self.options.normalizedCropRect) && - other.options.resizeMode == self.options.resizeMode && - other.options.version == self.options.version; -} - -@end - -#endif // AS_USE_PHOTOS diff --git a/Source/Details/ASThread.h b/Source/Details/ASThread.h index 0ef3d325ac..794db5decc 100644 --- a/Source/Details/ASThread.h +++ b/Source/Details/ASThread.h @@ -15,7 +15,6 @@ #import #import #import -#import #import #import #import diff --git a/Source/Details/UIView+ASConvenience.h b/Source/Details/UIView+ASConvenience.h index 870793bdb7..0b667e3893 100644 --- a/Source/Details/UIView+ASConvenience.h +++ b/Source/Details/UIView+ASConvenience.h @@ -71,7 +71,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, getter=asyncdisplaykit_isAsyncTransactionContainer, setter = asyncdisplaykit_setAsyncTransactionContainer:) BOOL asyncdisplaykit_asyncTransactionContainer; @property (nonatomic) UIEdgeInsets layoutMargins; @property (nonatomic) BOOL preservesSuperviewLayoutMargins; -@property (nonatomic) BOOL insetsLayoutMarginsFromSafeArea; /** Following properties of the UIAccessibility informal protocol are supported as well. diff --git a/Source/Details/_ASCollectionReusableView.mm b/Source/Details/_ASCollectionReusableView.mm index 0ca4b8115c..daee423afb 100644 --- a/Source/Details/_ASCollectionReusableView.mm +++ b/Source/Details/_ASCollectionReusableView.mm @@ -63,27 +63,3 @@ - (void)layoutSubviews } @end - -/** - * A category that makes _ASCollectionReusableView conform to IGListBindable. - * - * We don't need to do anything to bind the view model – the cell node - * serves the same purpose. - */ -#if __has_include() - -#import - -@interface _ASCollectionReusableView (IGListBindable) -@end - -@implementation _ASCollectionReusableView (IGListBindable) - -- (void)bindViewModel:(id)viewModel -{ - // nop -} - -@end - -#endif diff --git a/Source/Details/_ASCollectionViewCell.mm b/Source/Details/_ASCollectionViewCell.mm index 93f4626a49..c54509f4b6 100644 --- a/Source/Details/_ASCollectionViewCell.mm +++ b/Source/Details/_ASCollectionViewCell.mm @@ -125,27 +125,3 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event } @end - -/** - * A category that makes _ASCollectionViewCell conform to IGListBindable. - * - * We don't need to do anything to bind the view model – the cell node - * serves the same purpose. - */ -#if __has_include() - -#import - -@interface _ASCollectionViewCell (IGListBindable) -@end - -@implementation _ASCollectionViewCell (IGListBindable) - -- (void)bindViewModel:(id)viewModel -{ - // nop -} - -@end - -#endif diff --git a/Source/Details/_ASDisplayView.mm b/Source/Details/_ASDisplayView.mm index 7741e4447a..ad3b0d7247 100644 --- a/Source/Details/_ASDisplayView.mm +++ b/Source/Details/_ASDisplayView.mm @@ -15,7 +15,6 @@ #import #import #import -#import #pragma mark - _ASDisplayView @@ -174,9 +173,7 @@ - (void)didMoveToSuperview // This is only to help detect issues when a root-of-view-controller node is reused separately from its view controller. // Avoid overhead in release. if (superview && node.viewControllerRoot) { - UIViewController *vc = [node closestViewController]; - - ASDisplayNodeAssert(vc != nil && [vc isKindOfClass:[ASDKViewController class]] && ((ASDKViewController*)vc).node == node, @"This node was once used as a view controller's node. You should not reuse it without its view controller."); + ASDisplayNodeAssert(false, @"This node was once used as a view controller's node. You should not reuse it without its view controller."); } #endif @@ -474,14 +471,6 @@ - (void)layoutMarginsDidChange [node layoutMarginsDidChange]; } -- (void)safeAreaInsetsDidChange -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - [super safeAreaInsetsDidChange]; - - [node safeAreaInsetsDidChange]; -} - - (id)forwardingTargetForSelector:(SEL)aSelector { // Ideally, we would implement -targetForAction:withSender: and simply return the node where we don't respond personally. @@ -491,42 +480,4 @@ - (id)forwardingTargetForSelector:(SEL)aSelector return node; } -#if TARGET_OS_TV -#pragma mark - tvOS -- (BOOL)canBecomeFocused -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node canBecomeFocused]; -} - -- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node didUpdateFocusInContext:context withAnimationCoordinator:coordinator]; -} - -- (void)setNeedsFocusUpdate -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node setNeedsFocusUpdate]; -} - -- (void)updateFocusIfNeeded -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node updateFocusIfNeeded]; -} - -- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node shouldUpdateFocusInContext:context]; -} - -- (UIView *)preferredFocusedView -{ - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node preferredFocusedView]; -} -#endif @end diff --git a/Source/IGListAdapter+AsyncDisplayKit.h b/Source/IGListAdapter+AsyncDisplayKit.h deleted file mode 100644 index 76b53c1933..0000000000 --- a/Source/IGListAdapter+AsyncDisplayKit.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// IGListAdapter+AsyncDisplayKit.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionNode; - -@interface IGListAdapter (AsyncDisplayKit) - -/** - * Connect this list adapter to the given collection node. - * - * @param collectionNode The collection node to drive with this list adapter. - * - * @note This method may only be called once per list adapter, - * and it must be called on the main thread. -[UIViewController init] - * is a good place to call it. This method does not retain the collection node. - */ -- (void)setASDKCollectionNode:(ASCollectionNode *)collectionNode; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_IG_LIST_KIT diff --git a/Source/IGListAdapter+AsyncDisplayKit.mm b/Source/IGListAdapter+AsyncDisplayKit.mm deleted file mode 100644 index bcfb246746..0000000000 --- a/Source/IGListAdapter+AsyncDisplayKit.mm +++ /dev/null @@ -1,51 +0,0 @@ -// -// IGListAdapter+AsyncDisplayKit.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import -#import - -@implementation IGListAdapter (AsyncDisplayKit) - -- (void)setASDKCollectionNode:(ASCollectionNode *)collectionNode -{ - ASDisplayNodeAssertMainThread(); - - // Attempt to retrieve previous data source. - ASIGListAdapterBasedDataSource *dataSource = objc_getAssociatedObject(self, _cmd); - // Bomb if we already made one. - if (dataSource != nil) { - ASDisplayNodeFailAssert(@"Attempt to call %@ multiple times on the same list adapter. Not currently allowed!", NSStringFromSelector(_cmd)); - return; - } - - // Make a data source and retain it. - dataSource = [[ASIGListAdapterBasedDataSource alloc] initWithListAdapter:self collectionDelegate:collectionNode.delegate]; - objc_setAssociatedObject(self, _cmd, dataSource, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - - // Attach the data source to the collection node. - collectionNode.dataSource = dataSource; - collectionNode.delegate = dataSource; - __weak IGListAdapter *weakSelf = self; - [collectionNode onDidLoad:^(__kindof ASCollectionNode * _Nonnull collectionNode) { -#if IG_LIST_COLLECTION_VIEW - // We manually set the superclass of ASCollectionView to IGListCollectionView at runtime if needed. - weakSelf.collectionView = (IGListCollectionView *)collectionNode.view; -#else - weakSelf.collectionView = collectionNode.view; -#endif - }]; -} - -@end - -#endif // AS_IG_LIST_KIT diff --git a/Source/Layout/ASDimension.h b/Source/Layout/ASDimension.h index ab0717bad6..2f882412ed 100644 --- a/Source/Layout/ASDimension.h +++ b/Source/Layout/ASDimension.h @@ -290,26 +290,4 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASSizeRangeEqualToSizeRange(ASSi */ ASDK_EXTERN AS_WARN_UNUSED_RESULT NSString *NSStringFromASSizeRange(ASSizeRange sizeRange); -#if YOGA - -#pragma mark - ASEdgeInsets - -typedef struct { - ASDimension top; - ASDimension left; - ASDimension bottom; - ASDimension right; - ASDimension start; - ASDimension end; - ASDimension horizontal; - ASDimension vertical; - ASDimension all; -} ASEdgeInsets; - -ASDK_EXTERN ASEdgeInsets const ASEdgeInsetsZero; - -ASDK_EXTERN ASEdgeInsets ASEdgeInsetsMake(UIEdgeInsets edgeInsets); - -#endif - NS_ASSUME_NONNULL_END diff --git a/Source/Layout/ASDimension.mm b/Source/Layout/ASDimension.mm index 10af17cc64..08839eceaf 100644 --- a/Source/Layout/ASDimension.mm +++ b/Source/Layout/ASDimension.mm @@ -106,18 +106,3 @@ ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRan 17, sizeRange.max.width, 17, sizeRange.max.height]; } - -#if YOGA -#pragma mark - Yoga - ASEdgeInsets -ASEdgeInsets const ASEdgeInsetsZero = {}; - -ASEdgeInsets ASEdgeInsetsMake(UIEdgeInsets edgeInsets) -{ - ASEdgeInsets asEdgeInsets = ASEdgeInsetsZero; - asEdgeInsets.top = ASDimensionMake(edgeInsets.top); - asEdgeInsets.left = ASDimensionMake(edgeInsets.left); - asEdgeInsets.bottom = ASDimensionMake(edgeInsets.bottom); - asEdgeInsets.right = ASDimensionMake(edgeInsets.right); - return asEdgeInsets; -} -#endif diff --git a/Source/Layout/ASLayout+IGListDiffKit.h b/Source/Layout/ASLayout+IGListDiffKit.h deleted file mode 100644 index 34636efa28..0000000000 --- a/Source/Layout/ASLayout+IGListDiffKit.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// ASLayout+IGListDiffKit.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#if AS_IG_LIST_DIFF_KIT -#import -#import - -@interface ASLayout(IGListDiffKit) -@end -#endif // AS_IG_LIST_DIFF_KIT diff --git a/Source/Layout/ASLayout+IGListDiffKit.mm b/Source/Layout/ASLayout+IGListDiffKit.mm deleted file mode 100644 index d6e57b2154..0000000000 --- a/Source/Layout/ASLayout+IGListDiffKit.mm +++ /dev/null @@ -1,30 +0,0 @@ -// -// ASLayout+IGListDiffKit.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// -#import -#if AS_IG_LIST_DIFF_KIT -#import "ASLayout+IGListDiffKit.h" - -@interface ASLayout() { -@public - id _layoutElement; -} -@end - -@implementation ASLayout(IGListDiffKit) - -- (id )diffIdentifier -{ - return self->_layoutElement; -} - -- (BOOL)isEqualToDiffableObject:(id )other -{ - return [self isEqual:other]; -} -@end -#endif // AS_IG_LIST_DIFF_KIT diff --git a/Source/Layout/ASLayoutElement.mm b/Source/Layout/ASLayoutElement.mm index 38d50d6cb4..13a29004b9 100644 --- a/Source/Layout/ASLayoutElement.mm +++ b/Source/Layout/ASLayoutElement.mm @@ -13,11 +13,6 @@ using AS::MutexLocker; -#if YOGA - #import YOGA_HEADER_PATH - #import -#endif - #pragma mark - ASLayoutElementContext @implementation ASLayoutElementContext @@ -120,21 +115,6 @@ void ASLayoutElementPopContext() NSString * const ASLayoutElementStyleLayoutPositionProperty = @"ASLayoutElementStyleLayoutPositionProperty"; -#if YOGA -NSString * const ASYogaFlexWrapProperty = @"ASLayoutElementStyleLayoutFlexWrapProperty"; -NSString * const ASYogaFlexDirectionProperty = @"ASYogaFlexDirectionProperty"; -NSString * const ASYogaDirectionProperty = @"ASYogaDirectionProperty"; -NSString * const ASYogaSpacingProperty = @"ASYogaSpacingProperty"; -NSString * const ASYogaJustifyContentProperty = @"ASYogaJustifyContentProperty"; -NSString * const ASYogaAlignItemsProperty = @"ASYogaAlignItemsProperty"; -NSString * const ASYogaPositionTypeProperty = @"ASYogaPositionTypeProperty"; -NSString * const ASYogaPositionProperty = @"ASYogaPositionProperty"; -NSString * const ASYogaMarginProperty = @"ASYogaMarginProperty"; -NSString * const ASYogaPaddingProperty = @"ASYogaPaddingProperty"; -NSString * const ASYogaBorderProperty = @"ASYogaBorderProperty"; -NSString * const ASYogaAspectRatioProperty = @"ASYogaAspectRatioProperty"; -#endif - #define ASLayoutElementStyleSetSizeWithScope(x) \ ({ \ __instanceLock__.lock(); \ @@ -169,22 +149,6 @@ @implementation ASLayoutElementStyle { std::atomic _ascender; std::atomic _descender; std::atomic _layoutPosition; - -#if YOGA - YGNodeRef _yogaNode; - std::atomic _flexWrap; - std::atomic _flexDirection; - std::atomic _direction; - std::atomic _justifyContent; - std::atomic _alignItems; - std::atomic _positionType; - std::atomic _position; - std::atomic _margin; - std::atomic _padding; - std::atomic _border; - std::atomic _aspectRatio; - ASStackLayoutAlignItems _parentAlignStyle; -#endif } @dynamic width, height, minWidth, maxWidth, minHeight, maxHeight; @@ -207,12 +171,6 @@ - (instancetype)init if (self) { std::atomic_init(&_size, ASLayoutElementSizeMake()); std::atomic_init(&_flexBasis, ASDimensionAuto); -#if YOGA - _parentAlignStyle = ASStackLayoutAlignItemsNotSet; - std::atomic_init(&_flexDirection, ASStackLayoutDirectionVertical); - std::atomic_init(&_alignItems, ASStackLayoutAlignItemsStretch); - std::atomic_init(&_aspectRatio, static_cast(YGUndefined)); -#endif } return self; } @@ -656,229 +614,6 @@ - (NSString *)description - (void)propertyDidChange:(NSString *)propertyName { -#if YOGA - /* TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT - void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow); - void YGNodeStyleSetFlex(YGNodeRef node, float flex); - */ - - if (_yogaNode == NULL) { - return; - } - // Because the NSStrings used to identify each property are const, use efficient pointer comparison. - if (propertyName == ASLayoutElementStyleWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, Width, self.width); - } - else if (propertyName == ASLayoutElementStyleMinWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MinWidth, self.minWidth); - } - else if (propertyName == ASLayoutElementStyleMaxWidthProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MaxWidth, self.maxWidth); - } - else if (propertyName == ASLayoutElementStyleHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, Height, self.height); - } - else if (propertyName == ASLayoutElementStyleMinHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MinHeight, self.minHeight); - } - else if (propertyName == ASLayoutElementStyleMaxHeightProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, MaxHeight, self.maxHeight); - } - else if (propertyName == ASLayoutElementStyleFlexGrowProperty) { - YGNodeStyleSetFlexGrow(_yogaNode, self.flexGrow); - } - else if (propertyName == ASLayoutElementStyleFlexShrinkProperty) { - YGNodeStyleSetFlexShrink(_yogaNode, self.flexShrink); - } - else if (propertyName == ASLayoutElementStyleFlexBasisProperty) { - YGNODE_STYLE_SET_DIMENSION(_yogaNode, FlexBasis, self.flexBasis); - } - else if (propertyName == ASLayoutElementStyleAlignSelfProperty) { - YGNodeStyleSetAlignSelf(_yogaNode, yogaAlignSelf(self.alignSelf)); - } - else if (propertyName == ASYogaFlexWrapProperty) { - YGNodeStyleSetFlexWrap(_yogaNode, self.flexWrap); - } - else if (propertyName == ASYogaFlexDirectionProperty) { - YGNodeStyleSetFlexDirection(_yogaNode, yogaFlexDirection(self.flexDirection)); - } - else if (propertyName == ASYogaDirectionProperty) { - YGNodeStyleSetDirection(_yogaNode, self.direction); - } - else if (propertyName == ASYogaJustifyContentProperty) { - YGNodeStyleSetJustifyContent(_yogaNode, yogaJustifyContent(self.justifyContent)); - } - else if (propertyName == ASYogaAlignItemsProperty) { - ASStackLayoutAlignItems alignItems = self.alignItems; - if (alignItems != ASStackLayoutAlignItemsNotSet) { - YGNodeStyleSetAlignItems(_yogaNode, yogaAlignItems(alignItems)); - } - } - else if (propertyName == ASYogaPositionTypeProperty) { - YGNodeStyleSetPositionType(_yogaNode, self.positionType); - } - else if (propertyName == ASYogaPositionProperty) { - ASEdgeInsets position = self.position; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Position, dimensionForEdgeWithEdgeInsets(edge, position), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaMarginProperty) { - ASEdgeInsets margin = self.margin; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Margin, dimensionForEdgeWithEdgeInsets(edge, margin), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaPaddingProperty) { - ASEdgeInsets padding = self.padding; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(_yogaNode, Padding, dimensionForEdgeWithEdgeInsets(edge, padding), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaBorderProperty) { - ASEdgeInsets border = self.border; - YGEdge edge = YGEdgeLeft; - for (int i = 0; i < YGEdgeAll + 1; ++i) { - YGNODE_STYLE_SET_FLOAT_WITH_EDGE(_yogaNode, Border, dimensionForEdgeWithEdgeInsets(edge, border), edge); - edge = (YGEdge)(edge + 1); - } - } - else if (propertyName == ASYogaAspectRatioProperty) { - CGFloat aspectRatio = self.aspectRatio; - if (aspectRatio > FLT_EPSILON && aspectRatio < CGFLOAT_MAX / 2.0) { - YGNodeStyleSetAspectRatio(_yogaNode, aspectRatio); - } - } -#endif -} - -#pragma mark - Yoga Flexbox Properties - -#if YOGA - -+ (void)initialize -{ - [super initialize]; - YGConfigSetPointScaleFactor(YGConfigGetDefault(), ASScreenScale()); - // Yoga recommends using Web Defaults for all new projects. This will be enabled for Texture very soon. - //YGConfigSetUseWebDefaults(YGConfigGetDefault(), true); -} - -- (YGNodeRef)yogaNode -{ - return _yogaNode; -} - -- (YGNodeRef)yogaNodeCreateIfNeeded -{ - if (_yogaNode == NULL) { - _yogaNode = YGNodeNew(); - } - return _yogaNode; -} - -- (void)destroyYogaNode -{ - if (_yogaNode != NULL) { - // Release the __bridge_retained Context object. - ASLayoutElementYogaUpdateMeasureFunc(_yogaNode, nil); - YGNodeFree(_yogaNode); - _yogaNode = NULL; - } -} - -- (void)dealloc -{ - [self destroyYogaNode]; -} - -- (YGWrap)flexWrap { return _flexWrap.load(); } -- (ASStackLayoutDirection)flexDirection { return _flexDirection.load(); } -- (YGDirection)direction { return _direction.load(); } -- (ASStackLayoutJustifyContent)justifyContent { return _justifyContent.load(); } -- (ASStackLayoutAlignItems)alignItems { return _alignItems.load(); } -- (YGPositionType)positionType { return _positionType.load(); } -- (ASEdgeInsets)position { return _position.load(); } -- (ASEdgeInsets)margin { return _margin.load(); } -- (ASEdgeInsets)padding { return _padding.load(); } -- (ASEdgeInsets)border { return _border.load(); } -- (CGFloat)aspectRatio { return _aspectRatio.load(); } -// private (ASLayoutElementStylePrivate.h) -- (ASStackLayoutAlignItems)parentAlignStyle { - return _parentAlignStyle; -} - -- (void)setFlexWrap:(YGWrap)flexWrap { - if (_flexWrap.exchange(flexWrap) != flexWrap) { - ASLayoutElementStyleCallDelegate(ASYogaFlexWrapProperty); - } -} -- (void)setFlexDirection:(ASStackLayoutDirection)flexDirection { - if (_flexDirection.exchange(flexDirection) != flexDirection) { - ASLayoutElementStyleCallDelegate(ASYogaFlexDirectionProperty); - } -} -- (void)setDirection:(YGDirection)direction { - if (_direction.exchange(direction) != direction) { - ASLayoutElementStyleCallDelegate(ASYogaDirectionProperty); - } } -- (void)setJustifyContent:(ASStackLayoutJustifyContent)justify { - if (_justifyContent.exchange(justify) != justify) { - ASLayoutElementStyleCallDelegate(ASYogaJustifyContentProperty); - } -} -- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems { - if (_alignItems.exchange(alignItems) != alignItems) { - ASLayoutElementStyleCallDelegate(ASYogaAlignItemsProperty); - } -} -- (void)setPositionType:(YGPositionType)positionType { - if (_positionType.exchange(positionType) != positionType) { - ASLayoutElementStyleCallDelegate(ASYogaPositionTypeProperty); - } -} -/// TODO: smart compare ASEdgeInsets instead of memory compare. -- (void)setPosition:(ASEdgeInsets)position { - ASEdgeInsets oldValue = _position.exchange(position); - if (0 != memcmp(&position, &oldValue, sizeof(ASEdgeInsets))) { - ASLayoutElementStyleCallDelegate(ASYogaPositionProperty); - } -} -- (void)setMargin:(ASEdgeInsets)margin { - ASEdgeInsets oldValue = _margin.exchange(margin); - if (0 != memcmp(&margin, &oldValue, sizeof(ASEdgeInsets))) { - ASLayoutElementStyleCallDelegate(ASYogaMarginProperty); - } -} -- (void)setPadding:(ASEdgeInsets)padding { - ASEdgeInsets oldValue = _padding.exchange(padding); - if (0 != memcmp(&padding, &oldValue, sizeof(ASEdgeInsets))) { - ASLayoutElementStyleCallDelegate(ASYogaPaddingProperty); - } -} -- (void)setBorder:(ASEdgeInsets)border { - ASEdgeInsets oldValue = _border.exchange(border); - if (0 != memcmp(&border, &oldValue, sizeof(ASEdgeInsets))) { - ASLayoutElementStyleCallDelegate(ASYogaBorderProperty); - } -} -- (void)setAspectRatio:(CGFloat)aspectRatio { - if (_aspectRatio.exchange(aspectRatio) != aspectRatio) { - ASLayoutElementStyleCallDelegate(ASYogaAspectRatioProperty); - } -} -// private (ASLayoutElementStylePrivate.h) -- (void)setParentAlignStyle:(ASStackLayoutAlignItems)style { - _parentAlignStyle = style; -} - -#endif /* YOGA */ @end diff --git a/Source/Layout/ASStackLayoutDefines.h b/Source/Layout/ASStackLayoutDefines.h index de0543907f..0f17970092 100644 --- a/Source/Layout/ASStackLayoutDefines.h +++ b/Source/Layout/ASStackLayoutDefines.h @@ -15,12 +15,6 @@ typedef NS_ENUM(unsigned char, ASStackLayoutDirection) { ASStackLayoutDirectionVertical, /** Children are stacked horizontally */ ASStackLayoutDirectionHorizontal, -#if YOGA - /** Children are stacked vertically, but in reverse. Only used by Yoga spec. */ - ASStackLayoutDirectionVerticalReverse, - /** Children are stacked horizontally, but in reverse. Only used by Yoga spec. */ - ASStackLayoutDirectionHorizontalReverse, -#endif }; /** If no children are flexible, how should this spec justify its children in the available space? */ @@ -117,14 +111,6 @@ typedef NS_ENUM(unsigned char, ASHorizontalAlignment) { ASHorizontalAlignmentMiddle, /** Right aligned */ ASHorizontalAlignmentRight, - - // After 2.0 has landed, we'll add ASDISPLAYNODE_DEPRECATED here - for now, avoid triggering errors for projects with -Werror - /** @deprecated Use ASHorizontalAlignmentLeft instead */ - ASAlignmentLeft ASDISPLAYNODE_DEPRECATED = ASHorizontalAlignmentLeft, - /** @deprecated Use ASHorizontalAlignmentMiddle instead */ - ASAlignmentMiddle ASDISPLAYNODE_DEPRECATED = ASHorizontalAlignmentMiddle, - /** @deprecated Use ASHorizontalAlignmentRight instead */ - ASAlignmentRight ASDISPLAYNODE_DEPRECATED = ASHorizontalAlignmentRight, }; /** Orientation of children along vertical axis */ @@ -137,12 +123,4 @@ typedef NS_ENUM(unsigned char, ASVerticalAlignment) { ASVerticalAlignmentCenter, /** Bottom aligned */ ASVerticalAlignmentBottom, - - // After 2.0 has landed, we'll add ASDISPLAYNODE_DEPRECATED here - for now, avoid triggering errors for projects with -Werror - /** @deprecated Use ASVerticalAlignmentTop instead */ - ASAlignmentTop ASDISPLAYNODE_DEPRECATED = ASVerticalAlignmentTop, - /** @deprecated Use ASVerticalAlignmentCenter instead */ - ASAlignmentCenter ASDISPLAYNODE_DEPRECATED = ASVerticalAlignmentCenter, - /** @deprecated Use ASVerticalAlignmentBottom instead */ - ASAlignmentBottom ASDISPLAYNODE_DEPRECATED = ASVerticalAlignmentBottom, }; diff --git a/Source/Layout/ASStackLayoutSpec.mm b/Source/Layout/ASStackLayoutSpec.mm index 976cc02932..65bdf65497 100644 --- a/Source/Layout/ASStackLayoutSpec.mm +++ b/Source/Layout/ASStackLayoutSpec.mm @@ -190,13 +190,6 @@ - (void)resolveVerticalAlignment case ASStackLayoutDirectionHorizontal: [result insertObject:@{ (id)kCFNull: @"horizontal" } atIndex:0]; break; -#if YOGA - case ASStackLayoutDirectionVerticalReverse: - case ASStackLayoutDirectionHorizontalReverse: - // Currently not handled. - ASDisplayNodeFailAssert(@"Reverse directions not implemented."); - break; -#endif } return result; diff --git a/Source/Layout/ASYogaUtilities.h b/Source/Layout/ASYogaUtilities.h deleted file mode 100644 index d5529fc45f..0000000000 --- a/Source/Layout/ASYogaUtilities.h +++ /dev/null @@ -1,78 +0,0 @@ -// -// ASYogaUtilities.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if YOGA /* YOGA */ - -#import -#import -#import - -// Should pass a string literal, not an NSString as the first argument to ASYogaLog -#define ASYogaLog(x, ...) as_log_verbose(ASLayoutLog(), x, ##__VA_ARGS__); - -@interface ASDisplayNode (YogaHelpers) - -+ (ASDisplayNode *)yogaNode; -+ (ASDisplayNode *)yogaSpacerNode; -+ (ASDisplayNode *)yogaVerticalStack; -+ (ASDisplayNode *)yogaHorizontalStack; - -@end - -// pre-order, depth-first -ASDK_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode *node, void(^block)(ASDisplayNode *node)); - -#pragma mark - Yoga Type Conversion Helpers - -ASDK_EXTERN YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems); -ASDK_EXTERN YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent); -ASDK_EXTERN YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf); -ASDK_EXTERN YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction); -ASDK_EXTERN float yogaFloatForCGFloat(CGFloat value); -ASDK_EXTERN float yogaDimensionToPoints(ASDimension dimension); -ASDK_EXTERN float yogaDimensionToPercent(ASDimension dimension); -ASDK_EXTERN ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets); - -ASDK_EXTERN void ASLayoutElementYogaUpdateMeasureFunc(YGNodeRef yogaNode, id layoutElement); -ASDK_EXTERN float ASLayoutElementYogaBaselineFunc(YGNodeRef yogaNode, const float width, const float height); -ASDK_EXTERN YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, - float width, YGMeasureMode widthMode, - float height, YGMeasureMode heightMode); - -#pragma mark - Yoga Style Setter Helpers - -#define YGNODE_STYLE_SET_DIMENSION(yogaNode, property, dimension) \ - if (dimension.unit == ASDimensionUnitPoints) { \ - YGNodeStyleSet##property(yogaNode, yogaDimensionToPoints(dimension)); \ - } else if (dimension.unit == ASDimensionUnitFraction) { \ - YGNodeStyleSet##property##Percent(yogaNode, yogaDimensionToPercent(dimension)); \ - } else { \ - YGNodeStyleSet##property(yogaNode, YGUndefined); \ - }\ - -#define YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, property, dimension, edge) \ - if (dimension.unit == ASDimensionUnitPoints) { \ - YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \ - } else if (dimension.unit == ASDimensionUnitFraction) { \ - YGNodeStyleSet##property##Percent(yogaNode, edge, yogaDimensionToPercent(dimension)); \ - } else { \ - YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \ - } \ - -#define YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, property, dimension, edge) \ - if (dimension.unit == ASDimensionUnitPoints) { \ - YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \ - } else if (dimension.unit == ASDimensionUnitFraction) { \ - ASDisplayNodeAssert(NO, @"Unexpected Fraction value in applying ##property## values to YGNode"); \ - } else { \ - YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \ - } \ - -#endif /* YOGA */ diff --git a/Source/Layout/ASYogaUtilities.mm b/Source/Layout/ASYogaUtilities.mm deleted file mode 100644 index 68beede1ad..0000000000 --- a/Source/Layout/ASYogaUtilities.mm +++ /dev/null @@ -1,249 +0,0 @@ -// -// ASYogaUtilities.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#if YOGA /* YOGA */ - -@implementation ASDisplayNode (YogaHelpers) - -+ (ASDisplayNode *)yogaNode -{ - ASDisplayNode *node = [[ASDisplayNode alloc] init]; - node.automaticallyManagesSubnodes = YES; - [node.style yogaNodeCreateIfNeeded]; - return node; -} - -+ (ASDisplayNode *)yogaSpacerNode -{ - ASDisplayNode *node = [ASDisplayNode yogaNode]; - node.style.flexGrow = 1.0f; - return node; -} - -+ (ASDisplayNode *)yogaVerticalStack -{ - ASDisplayNode *node = [self yogaNode]; - node.style.flexDirection = ASStackLayoutDirectionVertical; - return node; -} - -+ (ASDisplayNode *)yogaHorizontalStack -{ - ASDisplayNode *node = [self yogaNode]; - node.style.flexDirection = ASStackLayoutDirectionHorizontal; - return node; -} - -@end - -void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode *node, void(^block)(ASDisplayNode *node)) -{ - if (node == nil) { - return; - } - block(node); - // We use the accessor here despite the copy, because the block may modify the yoga tree e.g. - // replacing a node. - for (ASDisplayNode *child in node.yogaChildren) { - ASDisplayNodePerformBlockOnEveryYogaChild(child, block); - } -} - -#pragma mark - Yoga Type Conversion Helpers - -YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems) -{ - switch (alignItems) { - case ASStackLayoutAlignItemsNotSet: return YGAlignAuto; - case ASStackLayoutAlignItemsStart: return YGAlignFlexStart; - case ASStackLayoutAlignItemsEnd: return YGAlignFlexEnd; - case ASStackLayoutAlignItemsCenter: return YGAlignCenter; - case ASStackLayoutAlignItemsStretch: return YGAlignStretch; - case ASStackLayoutAlignItemsBaselineFirst: return YGAlignBaseline; - // FIXME: WARNING, Yoga does not currently support last-baseline item alignment. - case ASStackLayoutAlignItemsBaselineLast: return YGAlignBaseline; - } -} - -YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent) -{ - switch (justifyContent) { - case ASStackLayoutJustifyContentStart: return YGJustifyFlexStart; - case ASStackLayoutJustifyContentCenter: return YGJustifyCenter; - case ASStackLayoutJustifyContentEnd: return YGJustifyFlexEnd; - case ASStackLayoutJustifyContentSpaceBetween: return YGJustifySpaceBetween; - case ASStackLayoutJustifyContentSpaceAround: return YGJustifySpaceAround; - } -} - -YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf) -{ - switch (alignSelf) { - case ASStackLayoutAlignSelfStart: return YGAlignFlexStart; - case ASStackLayoutAlignSelfCenter: return YGAlignCenter; - case ASStackLayoutAlignSelfEnd: return YGAlignFlexEnd; - case ASStackLayoutAlignSelfStretch: return YGAlignStretch; - case ASStackLayoutAlignSelfAuto: return YGAlignAuto; - } -} - -YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction) -{ - switch (direction) { - case ASStackLayoutDirectionVertical: - return YGFlexDirectionColumn; - case ASStackLayoutDirectionVerticalReverse: - return YGFlexDirectionColumnReverse; - case ASStackLayoutDirectionHorizontal: - return YGFlexDirectionRow; - case ASStackLayoutDirectionHorizontalReverse: - return YGFlexDirectionRowReverse; - } -} - -float yogaFloatForCGFloat(CGFloat value) -{ - if (value < CGFLOAT_MAX / 2) { - return value; - } else { - return YGUndefined; - } -} - -CGFloat cgFloatForYogaFloat(float yogaFloat, CGFloat undefinedDefault) -{ - return YGFloatIsUndefined(yogaFloat) ? undefinedDefault : yogaFloat; -} - -float yogaDimensionToPoints(ASDimension dimension) -{ - ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitPoints, - @"Dimensions should not be type Fraction for this method: %f", dimension.value); - return yogaFloatForCGFloat(dimension.value); -} - -float yogaDimensionToPercent(ASDimension dimension) -{ - ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitFraction, - @"Dimensions should not be type Points for this method: %f", dimension.value); - return 100.0 * yogaFloatForCGFloat(dimension.value); - -} - -ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets) -{ - switch (edge) { - case YGEdgeLeft: return insets.left; - case YGEdgeTop: return insets.top; - case YGEdgeRight: return insets.right; - case YGEdgeBottom: return insets.bottom; - case YGEdgeStart: return insets.start; - case YGEdgeEnd: return insets.end; - case YGEdgeHorizontal: return insets.horizontal; - case YGEdgeVertical: return insets.vertical; - case YGEdgeAll: return insets.all; - default: ASDisplayNodeCAssert(NO, @"YGEdge other than ASEdgeInsets is not supported."); - return ASDimensionAuto; - } -} - -void ASLayoutElementYogaUpdateMeasureFunc(YGNodeRef yogaNode, id layoutElement) -{ - if (yogaNode == NULL) { - return; - } - - BOOL shouldHaveMeasureFunc = [layoutElement implementsLayoutMethod]; - // How expensive is it to set a baselineFunc on all (leaf) nodes? - BOOL shouldHaveBaselineFunc = YES; - - if (layoutElement != nil) { - if (shouldHaveMeasureFunc || shouldHaveBaselineFunc) { - // Retain the Context object. This must be explicitly released with a - // __bridge_transfer - YGNodeFree() is not sufficient. - YGNodeSetContext(yogaNode, (__bridge_retained void *)layoutElement); - } - if (shouldHaveMeasureFunc) { - YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc); - } - if (shouldHaveBaselineFunc) { - YGNodeSetBaselineFunc(yogaNode, &ASLayoutElementYogaBaselineFunc); - } - ASDisplayNodeCAssert(YGNodeGetContext(yogaNode) == (__bridge void *)layoutElement, - @"Yoga node context should contain layoutElement: %@", layoutElement); - } else { - // If we lack any of the conditions above, and currently have a measureFn/baselineFn/context, - // get rid of it. - // Release the __bridge_retained Context object. - __unused id element = (__bridge_transfer id)YGNodeGetContext(yogaNode); - YGNodeSetContext(yogaNode, NULL); - YGNodeSetMeasureFunc(yogaNode, NULL); - YGNodeSetBaselineFunc(yogaNode, NULL); - } -} - -float ASLayoutElementYogaBaselineFunc(YGNodeRef yogaNode, const float width, const float height) -{ - id layoutElement = (__bridge id)YGNodeGetContext(yogaNode); - ASDisplayNodeCAssert([layoutElement conformsToProtocol:@protocol(ASLayoutElement)], - @"Yoga context must be "); - - ASDisplayNode *displayNode = ASDynamicCast(layoutElement, ASDisplayNode); - - switch (displayNode.style.parentAlignStyle) { - case ASStackLayoutAlignItemsBaselineFirst: - return layoutElement.style.ascender; - case ASStackLayoutAlignItemsBaselineLast: - return height + layoutElement.style.descender; - default: - return 0; - } -} - -YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasureMode widthMode, - float height, YGMeasureMode heightMode) -{ - id layoutElement = (__bridge id )YGNodeGetContext(yogaNode); - ASDisplayNodeCAssert([layoutElement conformsToProtocol:@protocol(ASLayoutElement)], @"Yoga context must be "); - - width = cgFloatForYogaFloat(width, CGFLOAT_MAX); - height = cgFloatForYogaFloat(height, CGFLOAT_MAX); - - ASSizeRange sizeRange; - sizeRange.min = CGSizeZero; - sizeRange.max = CGSizeMake(width, height); - if (widthMode == YGMeasureModeExactly) { - sizeRange.min.width = sizeRange.max.width; - } else { - // Mode is (YGMeasureModeAtMost | YGMeasureModeUndefined) - ASDimension minWidth = layoutElement.style.minWidth; - sizeRange.min.width = (minWidth.unit == ASDimensionUnitPoints ? yogaDimensionToPoints(minWidth) : 0.0); - } - if (heightMode == YGMeasureModeExactly) { - sizeRange.min.height = sizeRange.max.height; - } else { - // Mode is (YGMeasureModeAtMost | YGMeasureModeUndefined) - ASDimension minHeight = layoutElement.style.minHeight; - sizeRange.min.height = (minHeight.unit == ASDimensionUnitPoints ? yogaDimensionToPoints(minHeight) : 0.0); - } - - ASDisplayNodeCAssert(isnan(sizeRange.min.width) == NO && isnan(sizeRange.min.height) == NO, @"Yoga size range for measurement should not have NaN in minimum"); - if (isnan(sizeRange.max.width)) { - sizeRange.max.width = CGFLOAT_MAX; - } - if (isnan(sizeRange.max.height)) { - sizeRange.max.height = CGFLOAT_MAX; - } - - CGSize size = [[layoutElement layoutThatFits:sizeRange] size]; - return (YGSize){ .width = (float)size.width, .height = (float)size.height }; -} - -#endif /* YOGA */ diff --git a/Source/Private/ASCollectionLayout.h b/Source/Private/ASCollectionLayout.h deleted file mode 100644 index c31114e691..0000000000 --- a/Source/Private/ASCollectionLayout.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// ASCollectionLayout.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@protocol ASCollectionLayoutDelegate; -@class ASElementMap, ASCollectionLayout, ASCollectionNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionLayout : UICollectionViewLayout - -/** - * The collection node object currently using this layout object. - * - * @discussion The collection node object sets the value of this property when a new layout object is assigned to it. - * - * @discussion To get the truth on the current state of the collection, call methods on the collection node or the data source rather than the collection view because: - * 1. The view might not yet be allocated. - * 2. The collection node and data source are thread-safe. - */ -@property (nonatomic, weak) ASCollectionNode *collectionNode; - -@property (nonatomic, readonly) id layoutDelegate; - -/** - * Initializes with a layout delegate. - * - * @discussion For developers' convenience, the delegate is retained by this layout object, similar to UICollectionView retains its UICollectionViewLayout object. - * - * @discussion For simplicity, the delegate is read-only. If a new layout delegate is needed, construct a new layout object with that delegate and notify ASCollectionView about it. - * This ensures the underlying UICollectionView purges its cache and properly loads the new layout. - */ -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate NS_DESIGNATED_INITIALIZER; - -- (instancetype)init __unavailable; - -- (instancetype)initWithCoder:(NSCoder *)aDecoder __unavailable; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASCollectionLayout.mm b/Source/Private/ASCollectionLayout.mm deleted file mode 100644 index e69baa4818..0000000000 --- a/Source/Private/ASCollectionLayout.mm +++ /dev/null @@ -1,396 +0,0 @@ -// -// ASCollectionLayout.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import - -static const ASRangeTuningParameters kASDefaultMeasureRangeTuningParameters = { - .leadingBufferScreenfuls = 2.0, - .trailingBufferScreenfuls = 2.0 -}; - -static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRight | ASScrollDirectionDown); - -@interface ASCollectionLayout () { - ASCollectionLayoutCache *_layoutCache; - ASCollectionLayoutState *_layout; // Main thread only. - - struct { - unsigned int implementsAdditionalInfoForLayoutWithElements:1; - } _layoutDelegateFlags; -} - -@end - -@implementation ASCollectionLayout - -- (instancetype)initWithLayoutDelegate:(id)layoutDelegate -{ - self = [super init]; - if (self) { - ASDisplayNodeAssertNotNil(layoutDelegate, @"Collection layout delegate cannot be nil"); - _layoutDelegate = layoutDelegate; - _layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements = [layoutDelegate respondsToSelector:@selector(additionalInfoForLayoutWithElements:)]; - _layoutCache = [[ASCollectionLayoutCache alloc] init]; - } - return self; -} - -#pragma mark - ASDataControllerLayoutDelegate - -- (ASCollectionLayoutContext *)layoutContextWithElements:(ASElementMap *)elements -{ - ASDisplayNodeAssertMainThread(); - - Class layoutDelegateClass = [_layoutDelegate class]; - ASCollectionLayoutCache *layoutCache = _layoutCache; - ASCollectionNode *collectionNode = _collectionNode; - if (collectionNode == nil) { - return [[ASCollectionLayoutContext alloc] initWithViewportSize:CGSizeZero - initialContentOffset:CGPointZero - scrollableDirections:ASScrollDirectionNone - elements:[[ASElementMap alloc] init] - layoutDelegateClass:layoutDelegateClass - layoutCache:layoutCache - additionalInfo:nil]; - } - - ASScrollDirection scrollableDirections = [_layoutDelegate scrollableDirections]; - CGSize viewportSize = [ASCollectionLayout _viewportSizeForCollectionNode:collectionNode scrollableDirections:scrollableDirections]; - CGPoint contentOffset = collectionNode.contentOffset; - - id additionalInfo = nil; - if (_layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements) { - additionalInfo = [_layoutDelegate additionalInfoForLayoutWithElements:elements]; - } - - return [[ASCollectionLayoutContext alloc] initWithViewportSize:viewportSize - initialContentOffset:contentOffset - scrollableDirections:scrollableDirections - elements:elements - layoutDelegateClass:layoutDelegateClass - layoutCache:layoutCache - additionalInfo:additionalInfo]; -} - -+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context -{ - if (context.elements == nil) { - return [[ASCollectionLayoutState alloc] initWithContext:context]; - } - - ASCollectionLayoutState *layout = [context.layoutDelegateClass calculateLayoutWithContext:context]; - [context.layoutCache setLayout:layout forContext:context]; - - // Measure elements in the measure range ahead of time - CGSize viewportSize = context.viewportSize; - CGPoint contentOffset = context.initialContentOffset; - CGRect initialRect = CGRectMake(contentOffset.x, contentOffset.y, viewportSize.width, viewportSize.height); - CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(initialRect, - kASDefaultMeasureRangeTuningParameters, - context.scrollableDirections, - kASStaticScrollDirection); - // The first call to -layoutAttributesForElementsInRect: will be with a rect that is way bigger than initialRect here. - // If we only block on initialRect, a few elements that are outside of initialRect but inside measureRect - // may not be available by the time -layoutAttributesForElementsInRect: is called. - // Since this method is usually run off main, let's spawn more threads to measure and block on all elements in measureRect. - [self _measureElementsInRect:measureRect blockingRect:measureRect layout:layout]; - - return layout; -} - -#pragma mark - UICollectionViewLayout overrides - -- (void)prepareLayout -{ - ASDisplayNodeAssertMainThread(); - [super prepareLayout]; - - ASCollectionLayoutContext *context = [self layoutContextWithElements:_collectionNode.visibleElements]; - if (_layout != nil && ASObjectIsEqual(_layout.context, context)) { - // The existing layout is still valid. No-op - return; - } - - if (ASCollectionLayoutState *cachedLayout = [_layoutCache layoutForContext:context]) { - _layout = cachedLayout; - } else { - // A new layout is needed now. Calculate and apply it immediately - _layout = [ASCollectionLayout calculateLayoutWithContext:context]; - } -} - -- (void)invalidateLayout -{ - ASDisplayNodeAssertMainThread(); - [super invalidateLayout]; - if (_layout != nil) { - [_layoutCache removeLayoutForContext:_layout.context]; - _layout = nil; - } -} - -/** - * NOTE: It is suggested practice on the Web to override invalidationContextForInteractivelyMovingItems… and call out to the - * data source to move the item (so that if e.g. the item size depends on the data, you get the data you expect). However, as of iOS 11 this - * doesn't work, because UICV machinery will also call out to the data source to move the item after the interaction is done. The result is - * that your data source state will be incorrect due to this last move call. Plus it's just an API violation. - * - * Things tried: - * - Doing the speculative data source moves, and then UNDOING the last one in invalidationContextForEndingInteractiveMovementOfItems… - * but this does not work because the UICV machinery informs its data source before it calls that method on us, so we are too late. - * - * The correct practice is to use the UIDataSourceTranslating API introduced in iOS 11. Currently Texture does not support this API but we can - * build it if there is demand. We could add an id field onto the layout context object, and the layout client can - * use data source index paths when it reads nodes or other data source data. - */ - -- (CGSize)collectionViewContentSize -{ - ASDisplayNodeAssertMainThread(); - // The content size can be queried right after a layout invalidation (https://github.com/TextureGroup/Texture/pull/509). - // In that case, return zero. - return _layout ? _layout.contentSize : CGSizeZero; -} - -- (NSArray *)layoutAttributesForElementsInRect:(CGRect)blockingRect -{ - ASDisplayNodeAssertMainThread(); - if (CGRectIsEmpty(blockingRect)) { - return nil; - } - - // Measure elements in the measure range, block on the requested rect - CGRect measureRect = CGRectExpandToRangeWithScrollableDirections(blockingRect, - kASDefaultMeasureRangeTuningParameters, - _layout.context.scrollableDirections, - kASStaticScrollDirection); - [ASCollectionLayout _measureElementsInRect:measureRect blockingRect:blockingRect layout:_layout]; - - NSArray *result = [_layout layoutAttributesForElementsInRect:blockingRect]; - - ASElementMap *elements = _layout.context.elements; - for (UICollectionViewLayoutAttributes *attrs in result) { - ASCollectionElement *element = [elements elementForLayoutAttributes:attrs]; - ASCollectionLayoutSetSizeToElement(attrs.frame.size, element); - } - - return result; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertMainThread(); - - ASCollectionElement *element = [_layout.context.elements elementForItemAtIndexPath:indexPath]; - UICollectionViewLayoutAttributes *attrs = [_layout layoutAttributesForElement:element]; - - ASCellNode *node = element.node; - CGSize elementSize = attrs.frame.size; - if (! CGSizeEqualToSize(elementSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(elementSize)]; - } - - ASCollectionLayoutSetSizeToElement(attrs.frame.size, element); - return attrs; -} - -- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath -{ - ASCollectionElement *element = [_layout.context.elements supplementaryElementOfKind:elementKind atIndexPath:indexPath]; - UICollectionViewLayoutAttributes *attrs = [_layout layoutAttributesForElement:element]; - - ASCellNode *node = element.node; - CGSize elementSize = attrs.frame.size; - if (! CGSizeEqualToSize(elementSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(elementSize)]; - } - - ASCollectionLayoutSetSizeToElement(attrs.frame.size, element); - return attrs; -} - -- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds -{ - return (! CGSizeEqualToSize([ASCollectionLayout _boundsForCollectionNode:_collectionNode], newBounds.size)); -} - -#pragma mark - Private methods - -+ (CGSize)_boundsForCollectionNode:(nonnull ASCollectionNode *)collectionNode -{ - if (collectionNode == nil) { - return CGSizeZero; - } - - if (!collectionNode.isNodeLoaded) { - // TODO consider calculatedSize as well - return collectionNode.threadSafeBounds.size; - } - - ASDisplayNodeAssertMainThread(); - return collectionNode.view.bounds.size; -} - -+ (CGSize)_viewportSizeForCollectionNode:(nonnull ASCollectionNode *)collectionNode scrollableDirections:(ASScrollDirection)scrollableDirections -{ - if (collectionNode == nil) { - return CGSizeZero; - } - - CGSize result = [ASCollectionLayout _boundsForCollectionNode:collectionNode]; - // TODO: Consider using adjustedContentInset on iOS 11 and later, to include the safe area of the scroll view - UIEdgeInsets contentInset = collectionNode.contentInset; - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { - result.height -= (contentInset.top + contentInset.bottom); - } else { - result.width -= (contentInset.left + contentInset.right); - } - return result; -} - -/** - * Measures all elements in the specified rect and blocks the calling thread while measuring those in the blocking rect. - */ -+ (void)_measureElementsInRect:(CGRect)rect blockingRect:(CGRect)blockingRect layout:(ASCollectionLayoutState *)layout -{ - if (CGRectIsEmpty(rect) || layout.context.elements == nil) { - return; - } - BOOL hasBlockingRect = !CGRectIsEmpty(blockingRect); - if (hasBlockingRect && CGRectContainsRect(rect, blockingRect) == NO) { - ASDisplayNodeCAssert(NO, @"Blocking rect, if specified, must be within the other (outer) rect"); - return; - } - - // Step 1: Clamp the specified rects between the bounds of content rect - CGSize contentSize = layout.contentSize; - CGRect contentRect = CGRectMake(0, 0, contentSize.width, contentSize.height); - rect = CGRectIntersection(contentRect, rect); - if (CGRectIsNull(rect)) { - return; - } - if (hasBlockingRect) { - blockingRect = CGRectIntersection(contentRect, blockingRect); - hasBlockingRect = !CGRectIsNull(blockingRect); - } - - // Step 2: Get layout attributes of all elements within the specified outer rect - ASPageToLayoutAttributesTable *attrsTable = [layout getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:rect]; - if (attrsTable.count == 0) { - // No elements in this rect! Bail early - return; - } - - // Step 3: Split all those attributes into blocking and non-blocking buckets - // Use ordered sets here because some items may span multiple pages, and the sets will be accessed by indexes later on. - ASCollectionLayoutContext *context = layout.context; - CGSize pageSize = context.viewportSize; - NSMutableOrderedSet *blockingAttrs = hasBlockingRect ? [NSMutableOrderedSet orderedSet] : nil; - NSMutableOrderedSet *nonBlockingAttrs = [NSMutableOrderedSet orderedSet]; - for (id pagePtr in attrsTable) { - ASPageCoordinate page = (ASPageCoordinate)pagePtr; - NSArray *attrsInPage = [attrsTable objectForPage:page]; - // Calculate the page's rect but only if it's going to be used. - CGRect pageRect = hasBlockingRect ? ASPageCoordinateGetPageRect(page, pageSize) : CGRectZero; - - if (hasBlockingRect && CGRectContainsRect(blockingRect, pageRect)) { - // The page fits well within the blocking rect. All attributes in this page are blocking. - [blockingAttrs addObjectsFromArray:attrsInPage]; - } else if (hasBlockingRect && CGRectIntersectsRect(blockingRect, pageRect)) { - // The page intersects the blocking rect. Some elements in this page are blocking, some are not. - for (UICollectionViewLayoutAttributes *attrs in attrsInPage) { - if (CGRectIntersectsRect(blockingRect, attrs.frame)) { - [blockingAttrs addObject:attrs]; - } else { - [nonBlockingAttrs addObject:attrs]; - } - } - } else { - // The page doesn't intersect the blocking rect. All elements in this page are non-blocking. - [nonBlockingAttrs addObjectsFromArray:attrsInPage]; - } - } - - // Step 4: Allocate and measure blocking elements' node - ASElementMap *elements = context.elements; - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - if (NSUInteger count = blockingAttrs.count) { - ASDispatchApply(count, queue, 0, ^(size_t i) { - UICollectionViewLayoutAttributes *attrs = blockingAttrs[i]; - ASCellNode *node; - if (attrs.representedElementKind == nil) { - node = [elements elementForItemAtIndexPath:attrs.indexPath].node; - } else { - node = [elements supplementaryElementOfKind:attrs.representedElementKind atIndexPath:attrs.indexPath].node; - } - CGSize expectedSize = attrs.frame.size; - if (! CGSizeEqualToSize(expectedSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(expectedSize)]; - } - }); - } - - // Step 5: Allocate and measure non-blocking ones - if (NSUInteger count = nonBlockingAttrs.count) { - __weak ASElementMap *weakElements = elements; - ASDispatchAsync(count, queue, 0, ^(size_t i) { - __strong ASElementMap *strongElements = weakElements; - if (strongElements) { - UICollectionViewLayoutAttributes *attrs = nonBlockingAttrs[i]; - ASCellNode *node; - if (attrs.representedElementKind == nil) { - node = [elements elementForItemAtIndexPath:attrs.indexPath].node; - } else { - node = [elements supplementaryElementOfKind:attrs.representedElementKind atIndexPath:attrs.indexPath].node; - } - CGSize expectedSize = attrs.frame.size; - if (! CGSizeEqualToSize(expectedSize, node.calculatedSize)) { - [node layoutThatFits:ASCollectionLayoutElementSizeRangeFromSize(expectedSize)]; - } - } - }); - } -} - -# pragma mark - Convenient inline functions - -ASDISPLAYNODE_INLINE ASSizeRange ASCollectionLayoutElementSizeRangeFromSize(CGSize size) -{ - // The layout delegate consulted us that this element must fit within this size, - // and the only way to achieve that without asking it again is to use an exact size range here. - return ASSizeRangeMake(size); -} - -ASDISPLAYNODE_INLINE void ASCollectionLayoutSetSizeToElement(CGSize size, ASCollectionElement *element) -{ - if (ASCellNode *node = element.node) { - if (! CGSizeEqualToSize(size, node.frame.size)) { - CGRect frame = CGRectZero; - frame.size = size; - node.frame = frame; - } - } -} - -@end diff --git a/Source/Private/ASCollectionLayoutCache.h b/Source/Private/ASCollectionLayoutCache.h deleted file mode 100644 index 3efad85d77..0000000000 --- a/Source/Private/ASCollectionLayoutCache.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// ASCollectionLayoutCache.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class ASCollectionLayoutContext, ASCollectionLayoutState; - -/// A thread-safe cache for ASCollectionLayoutContext-ASCollectionLayoutState pairs -AS_SUBCLASSING_RESTRICTED -@interface ASCollectionLayoutCache : NSObject - -- (nullable ASCollectionLayoutState *)layoutForContext:(ASCollectionLayoutContext *)context; - -- (void)setLayout:(ASCollectionLayoutState *)layout forContext:(ASCollectionLayoutContext *)context; - -- (void)removeLayoutForContext:(ASCollectionLayoutContext *)context; - -- (void)removeAllLayouts; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASCollectionLayoutCache.mm b/Source/Private/ASCollectionLayoutCache.mm deleted file mode 100644 index a5d6d111ee..0000000000 --- a/Source/Private/ASCollectionLayoutCache.mm +++ /dev/null @@ -1,88 +0,0 @@ -// -// ASCollectionLayoutCache.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import - -using AS::MutexLocker; - -@implementation ASCollectionLayoutCache { - AS::Mutex __instanceLock__; - - /** - * The underlying data structure of this cache. - * - * The outer map table is a weak to strong table. That is because ASCollectionLayoutContext doesn't (and shouldn't) - * hold a strong reference on its element map. As a result, this cache should handle the case in which - * an element map no longer exists and all contexts and layouts associated with it should be cleared. - * - * The inner map table is a standard strong to strong map. - * Since different ASCollectionLayoutContext objects with the same content are considered equal, - * "object pointer personality" can't be used as a key option. - */ - NSMapTable *> *_map; -} - -- (instancetype)init -{ - self = [super init]; - if (self) { - _map = [NSMapTable mapTableWithKeyOptions:(NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) valueOptions:NSMapTableStrongMemory]; - } - return self; -} - -- (ASCollectionLayoutState *)layoutForContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - if (elements == nil) { - return nil; - } - - MutexLocker l(__instanceLock__); - return [[_map objectForKey:elements] objectForKey:context]; -} - -- (void)setLayout:(ASCollectionLayoutState *)layout forContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - if (layout == nil || elements == nil) { - return; - } - - MutexLocker l(__instanceLock__); - auto innerMap = [_map objectForKey:elements]; - if (innerMap == nil) { - innerMap = [NSMapTable strongToStrongObjectsMapTable]; - [_map setObject:innerMap forKey:elements]; - } - [innerMap setObject:layout forKey:context]; -} - -- (void)removeLayoutForContext:(ASCollectionLayoutContext *)context -{ - ASElementMap *elements = context.elements; - if (elements == nil) { - return; - } - - MutexLocker l(__instanceLock__); - [[_map objectForKey:elements] removeObjectForKey:context]; -} - -- (void)removeAllLayouts -{ - MutexLocker l(__instanceLock__); - [_map removeAllObjects]; -} - -@end diff --git a/Source/Private/ASCollectionLayoutContext+Private.h b/Source/Private/ASCollectionLayoutContext+Private.h deleted file mode 100644 index 12e1d9db8c..0000000000 --- a/Source/Private/ASCollectionLayoutContext+Private.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASCollectionLayoutContext+Private.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@class ASCollectionLayoutCache; -@protocol ASCollectionLayoutDelegate; - -NS_ASSUME_NONNULL_BEGIN - -@interface ASCollectionLayoutContext (Private) - -@property (nonatomic, readonly) Class layoutDelegateClass; -@property (nonatomic, weak, readonly) ASCollectionLayoutCache *layoutCache; - -- (instancetype)initWithViewportSize:(CGSize)viewportSize - initialContentOffset:(CGPoint)initialContentOffset - scrollableDirections:(ASScrollDirection)scrollableDirections - elements:(ASElementMap *)elements - layoutDelegateClass:(Class)layoutDelegateClass - layoutCache:(ASCollectionLayoutCache *)layoutCache - additionalInfo:(nullable id)additionalInfo; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASCollectionLayoutDefines.h b/Source/Private/ASCollectionLayoutDefines.h deleted file mode 100644 index 6c7ff9d9d1..0000000000 --- a/Source/Private/ASCollectionLayoutDefines.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASCollectionLayoutDefines.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -ASDK_EXTERN ASSizeRange ASSizeRangeForCollectionLayoutThatFitsViewportSize(CGSize viewportSize, ASScrollDirection scrollableDirections) AS_WARN_UNUSED_RESULT; - -NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASCollectionLayoutDefines.mm b/Source/Private/ASCollectionLayoutDefines.mm deleted file mode 100644 index e386575c5d..0000000000 --- a/Source/Private/ASCollectionLayoutDefines.mm +++ /dev/null @@ -1,23 +0,0 @@ -// -// ASCollectionLayoutDefines.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -ASSizeRange ASSizeRangeForCollectionLayoutThatFitsViewportSize(CGSize viewportSize, ASScrollDirection scrollableDirections) -{ - ASSizeRange sizeRange = ASSizeRangeUnconstrained; - if (ASScrollDirectionContainsVerticalDirection(scrollableDirections) == NO) { - sizeRange.min.height = viewportSize.height; - sizeRange.max.height = viewportSize.height; - } - if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections) == NO) { - sizeRange.min.width = viewportSize.width; - sizeRange.max.width = viewportSize.width; - } - return sizeRange; -} diff --git a/Source/Private/ASCollectionLayoutState+Private.h b/Source/Private/ASCollectionLayoutState+Private.h deleted file mode 100644 index c964cf392b..0000000000 --- a/Source/Private/ASCollectionLayoutState+Private.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// ASCollectionLayoutState+Private.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASCollectionLayoutState (Private) - -/** - * Remove and returns layout attributes for unmeasured elements that intersect the specified rect - * - * @discussion This method is atomic and thread-safe - */ -- (nullable ASPageToLayoutAttributesTable *)getAndRemoveUnmeasuredLayoutAttributesPageTableInRect:(CGRect)rect; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASCollectionViewFlowLayoutInspector.mm b/Source/Private/ASCollectionViewFlowLayoutInspector.mm index 52d341c866..ee8a273092 100644 --- a/Source/Private/ASCollectionViewFlowLayoutInspector.mm +++ b/Source/Private/ASCollectionViewFlowLayoutInspector.mm @@ -24,10 +24,7 @@ @interface ASCollectionViewFlowLayoutInspector () @implementation ASCollectionViewFlowLayoutInspector { struct { unsigned int implementsSizeRangeForHeader:1; - unsigned int implementsReferenceSizeForHeader:1; unsigned int implementsSizeRangeForFooter:1; - unsigned int implementsReferenceSizeForFooter:1; - unsigned int implementsConstrainedSizeForNodeAtIndexPathDeprecated:1; unsigned int implementsConstrainedSizeForItemAtIndexPath:1; } _delegateFlags; } @@ -53,10 +50,7 @@ - (void)didChangeCollectionViewDelegate:(id)delegate; memset(&_delegateFlags, 0, sizeof(_delegateFlags)); } else { _delegateFlags.implementsSizeRangeForHeader = [delegate respondsToSelector:@selector(collectionNode:sizeRangeForHeaderInSection:)]; - _delegateFlags.implementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; _delegateFlags.implementsSizeRangeForFooter = [delegate respondsToSelector:@selector(collectionNode:sizeRangeForFooterInSection:)]; - _delegateFlags.implementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; - _delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; _delegateFlags.implementsConstrainedSizeForItemAtIndexPath = [delegate respondsToSelector:@selector(collectionNode:constrainedSizeForItemAtIndexPath:)]; } } @@ -66,14 +60,9 @@ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSize ASSizeRange result = ASSizeRangeUnconstrained; if (_delegateFlags.implementsConstrainedSizeForItemAtIndexPath) { result = [collectionView.asyncDelegate collectionNode:collectionView.collectionNode constrainedSizeForItemAtIndexPath:indexPath]; - } else if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPathDeprecated) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - result = [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; -#pragma clang diagnostic pop } else { // With 2.0 `collectionView:constrainedSizeForNodeAtIndexPath:` was moved to the delegate. Assert if not implemented on the delegate but on the data source - ASDisplayNodeAssert([collectionView.asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] == NO, @"collectionView:constrainedSizeForNodeAtIndexPath: was moved from the ASCollectionDataSource to the ASCollectionDelegate."); + ASDisplayNodeAssert(YES, @"collectionView:constrainedSizeForNodeAtIndexPath: was moved from the ASCollectionDataSource to the ASCollectionDelegate."); } // If we got no size range: @@ -97,24 +86,12 @@ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSize if (ASObjectIsEqual(kind, UICollectionElementKindSectionHeader)) { if (_delegateFlags.implementsSizeRangeForHeader) { result = [[self delegateForCollectionView:collectionView] collectionNode:collectionView.collectionNode sizeRangeForHeaderInSection:indexPath.section]; - } else if (_delegateFlags.implementsReferenceSizeForHeader) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize exactSize = [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForHeaderInSection:indexPath.section]; -#pragma clang diagnostic pop - result = ASSizeRangeMake(exactSize); } else { result = ASSizeRangeMake(_layout.headerReferenceSize); } } else if (ASObjectIsEqual(kind, UICollectionElementKindSectionFooter)) { if (_delegateFlags.implementsSizeRangeForFooter) { result = [[self delegateForCollectionView:collectionView] collectionNode:collectionView.collectionNode sizeRangeForFooterInSection:indexPath.section]; - } else if (_delegateFlags.implementsReferenceSizeForFooter) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGSize exactSize = [[self delegateForCollectionView:collectionView] collectionView:collectionView layout:_layout referenceSizeForFooterInSection:indexPath.section]; -#pragma clang diagnostic pop - result = ASSizeRangeMake(exactSize); } else { result = ASSizeRangeMake(_layout.footerReferenceSize); } diff --git a/Source/Private/ASControlNode+Private.h b/Source/Private/ASControlNode+Private.h deleted file mode 100644 index 02f54a20ec..0000000000 --- a/Source/Private/ASControlNode+Private.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// ASControlNode+Private.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASControlNode (Private) - -#if TARGET_OS_TV -- (void)_pressDown; -#endif - -@end diff --git a/Source/Private/ASDefaultPlayButton.h b/Source/Private/ASDefaultPlayButton.h deleted file mode 100644 index e3bc15246d..0000000000 --- a/Source/Private/ASDefaultPlayButton.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// ASDefaultPlayButton.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface ASDefaultPlayButton : ASButtonNode - -@end diff --git a/Source/Private/ASDefaultPlayButton.mm b/Source/Private/ASDefaultPlayButton.mm deleted file mode 100644 index 589bda90c2..0000000000 --- a/Source/Private/ASDefaultPlayButton.mm +++ /dev/null @@ -1,66 +0,0 @@ -// -// ASDefaultPlayButton.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@implementation ASDefaultPlayButton - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - self.opaque = NO; - - return self; -} - -+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing -{ - CGFloat originX = bounds.size.width/4; - CGRect buttonBounds = CGRectMake(originX, bounds.size.height/4, bounds.size.width/2, bounds.size.height/2); - CGFloat widthHeight = buttonBounds.size.width; - - //When the video isn't a square, the lower bound should be used to figure out the circle size - if (bounds.size.width < bounds.size.height) { - widthHeight = bounds.size.width/2; - originX = (bounds.size.width - widthHeight)/2; - buttonBounds = CGRectMake(originX, (bounds.size.height - widthHeight)/2, widthHeight, widthHeight); - } - if (bounds.size.width > bounds.size.height) { - widthHeight = bounds.size.height/2; - originX = (bounds.size.width - widthHeight)/2; - buttonBounds = CGRectMake(originX, (bounds.size.height - widthHeight)/2, widthHeight, widthHeight); - } - - CGContextRef context = UIGraphicsGetCurrentContext(); - - // Circle Drawing - UIBezierPath *ovalPath = [UIBezierPath bezierPathWithOvalInRect: buttonBounds]; - [[UIColor colorWithWhite:0.0 alpha:0.5] setFill]; - [ovalPath fill]; - - // Triangle Drawing - CGContextSaveGState(context); - - UIBezierPath *trianglePath = [UIBezierPath bezierPath]; - [trianglePath moveToPoint:CGPointMake(originX + widthHeight/3, bounds.size.height/4 + (bounds.size.height/2)/4)]; - [trianglePath addLineToPoint:CGPointMake(originX + widthHeight/3, bounds.size.height - bounds.size.height/4 - (bounds.size.height/2)/4)]; - [trianglePath addLineToPoint:CGPointMake(bounds.size.width - originX - widthHeight/4, bounds.size.height/2)]; - - [trianglePath closePath]; - [[UIColor colorWithWhite:0.9 alpha:0.9] setFill]; - [trianglePath fill]; - - CGContextRestoreGState(context); -} - -@end diff --git a/Source/Private/ASDefaultPlaybackButton.h b/Source/Private/ASDefaultPlaybackButton.h deleted file mode 100644 index ae7e245dc0..0000000000 --- a/Source/Private/ASDefaultPlaybackButton.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// ASDefaultPlaybackButton.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -typedef NS_ENUM(NSInteger, ASDefaultPlaybackButtonType) { - ASDefaultPlaybackButtonTypePlay, - ASDefaultPlaybackButtonTypePause -}; - -@interface ASDefaultPlaybackButton : ASControlNode -@property (nonatomic) ASDefaultPlaybackButtonType buttonType; -@end diff --git a/Source/Private/ASDefaultPlaybackButton.mm b/Source/Private/ASDefaultPlaybackButton.mm deleted file mode 100644 index deda7e8716..0000000000 --- a/Source/Private/ASDefaultPlaybackButton.mm +++ /dev/null @@ -1,84 +0,0 @@ -// -// ASDefaultPlaybackButton.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@interface ASDefaultPlaybackButton() -{ - ASDefaultPlaybackButtonType _buttonType; -} -@end - -@implementation ASDefaultPlaybackButton -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - - self.opaque = NO; - - return self; -} - -- (void)setButtonType:(ASDefaultPlaybackButtonType)buttonType -{ - ASDefaultPlaybackButtonType oldType = _buttonType; - _buttonType = buttonType; - - if (oldType != _buttonType) { - [self setNeedsDisplay]; - } -} - -- (nullable NSDictionary *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer -{ - return @{ - @"buttonType" : @(self.buttonType), - @"color" : self.tintColor - }; -} - -+ (void)drawRect:(CGRect)bounds withParameters:(NSDictionary *)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing -{ - ASDefaultPlaybackButtonType buttonType = (ASDefaultPlaybackButtonType)[parameters[@"buttonType"] intValue]; - UIColor *color = parameters[@"color"]; - - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSaveGState(context); - UIBezierPath* bezierPath = [UIBezierPath bezierPath]; - if (buttonType == ASDefaultPlaybackButtonTypePlay) { - [bezierPath moveToPoint: CGPointMake(0, 0)]; - [bezierPath addLineToPoint: CGPointMake(0, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(bounds.size.width, bounds.size.height/2)]; - [bezierPath addLineToPoint: CGPointMake(0, 0)]; - [bezierPath closePath]; - } else if (buttonType == ASDefaultPlaybackButtonTypePause) { - CGFloat pauseSingleLineWidth = bounds.size.width / 3.0; - [bezierPath moveToPoint: CGPointMake(0, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth, 0)]; - [bezierPath addLineToPoint: CGPointMake(0, 0)]; - [bezierPath addLineToPoint: CGPointMake(0, bounds.size.height)]; - [bezierPath closePath]; - [bezierPath moveToPoint: CGPointMake(pauseSingleLineWidth * 2, 0)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth * 2, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(bounds.size.width, bounds.size.height)]; - [bezierPath addLineToPoint: CGPointMake(bounds.size.width, 0)]; - [bezierPath addLineToPoint: CGPointMake(pauseSingleLineWidth * 2, 0)]; - [bezierPath closePath]; - } - - [color setFill]; - [bezierPath fill]; - - CGContextRestoreGState(context); -} -@end diff --git a/Source/Private/ASDispatch.mm b/Source/Private/ASDispatch.mm index b82032da73..1f3184672c 100644 --- a/Source/Private/ASDispatch.mm +++ b/Source/Private/ASDispatch.mm @@ -7,7 +7,6 @@ // #import -#import // Prefer C atomics in this file because ObjC blocks can't capture C++ atomics well. #import diff --git a/Source/Private/ASDisplayNode+FrameworkPrivate.h b/Source/Private/ASDisplayNode+FrameworkPrivate.h index ce53e79e9c..753962aced 100644 --- a/Source/Private/ASDisplayNode+FrameworkPrivate.h +++ b/Source/Private/ASDisplayNode+FrameworkPrivate.h @@ -230,15 +230,6 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarc */ - (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState; -/** - * @abstract safeAreaInsets will fallback to this value if the corresponding UIKit property is not available - * (due to an old iOS version). - * - * @discussion This should be set by the owning view controller based on it's layout guides. - * If this is not a view controllet's node the value will be calculated automatically by the parent node. - */ -@property (nonatomic) UIEdgeInsets fallbackSafeAreaInsets; - /** * @abstract Indicates if this node is a view controller's root node. Defaults to NO. * diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index 8be28957fd..4c727c454e 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -85,46 +85,6 @@ ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNo */ @implementation ASDisplayNode (UIViewBridge) -#if TARGET_OS_TV -// Focus Engine -- (BOOL)canBecomeFocused -{ - return NO; -} - -- (void)setNeedsFocusUpdate -{ - ASDisplayNodeAssertMainThread(); - [_view setNeedsFocusUpdate]; -} - -- (void)updateFocusIfNeeded -{ - ASDisplayNodeAssertMainThread(); - [_view updateFocusIfNeeded]; -} - -- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context -{ - return NO; -} - -- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator -{ - -} - -- (UIView *)preferredFocusedView -{ - if (self.nodeLoaded) { - return _view; - } - else { - return nil; - } -} -#endif - - (BOOL)canBecomeFirstResponder { if (_view == nil) { @@ -952,9 +912,6 @@ - (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentA { _bridge_prologue_write; _setToViewOnly(semanticContentAttribute, semanticContentAttribute); -#if YOGA - [self semanticContentAttributeDidChange:semanticContentAttribute]; -#endif } - (UIEdgeInsets)layoutMargins @@ -963,11 +920,6 @@ - (UIEdgeInsets)layoutMargins ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); UIEdgeInsets margins = _getFromViewOnly(layoutMargins); - if (!AS_AT_LEAST_IOS11 && self.insetsLayoutMarginsFromSafeArea) { - UIEdgeInsets safeArea = self.safeAreaInsets; - margins = ASConcatInsets(margins, safeArea); - } - return margins; } @@ -1001,48 +953,6 @@ - (void)layoutMarginsDidChange } } -- (UIEdgeInsets)safeAreaInsets -{ - _bridge_prologue_read; - - if (AS_AVAILABLE_IOS_TVOS(11.0, 11.0)) { - if (!_flags.layerBacked && _loaded(self)) { - return self.view.safeAreaInsets; - } - } - return _fallbackSafeAreaInsets; -} - -- (BOOL)insetsLayoutMarginsFromSafeArea -{ - _bridge_prologue_read; - - return [self _locked_insetsLayoutMarginsFromSafeArea]; -} - -- (void)setInsetsLayoutMarginsFromSafeArea:(BOOL)insetsLayoutMarginsFromSafeArea -{ - ASDisplayNodeAssertThreadAffinity(self); - BOOL shouldNotifyAboutUpdate; - { - _bridge_prologue_write; - - _flags.fallbackInsetsLayoutMarginsFromSafeArea = insetsLayoutMarginsFromSafeArea; - - if (AS_AVAILABLE_IOS_TVOS(11.0, 11.0)) { - if (!_flags.layerBacked) { - _setToViewOnly(insetsLayoutMarginsFromSafeArea, insetsLayoutMarginsFromSafeArea); - } - } - - shouldNotifyAboutUpdate = _loaded(self) && (!AS_AT_LEAST_IOS11 || _flags.layerBacked); - } - - if (shouldNotifyAboutUpdate) { - [self layoutMarginsDidChange]; - } -} - - (NSDictionary> *)actions { _bridge_prologue_read; @@ -1055,17 +965,6 @@ - (void)setActions:(NSDictionary> *)actions _setToLayer(actions, actions); } -- (void)safeAreaInsetsDidChange -{ - ASDisplayNodeAssertMainThread(); - - if (self.automaticallyRelayoutOnSafeAreaChanges) { - [self setNeedsLayout]; - } - - [self _fallbackUpdateSafeAreaOnChildren]; -} - @end @implementation ASDisplayNode (InternalPropertyBridge) @@ -1103,17 +1002,6 @@ - (void)setLayerMaskedCorners:(CACornerMask)newLayerMaskedCorners } } -- (BOOL)_locked_insetsLayoutMarginsFromSafeArea -{ - DISABLED_ASAssertLocked(__instanceLock__); - if (AS_AVAILABLE_IOS_TVOS(11.0, 11.0)) { - if (!_flags.layerBacked) { - return _getFromViewOnly(insetsLayoutMarginsFromSafeArea); - } - } - return _flags.fallbackInsetsLayoutMarginsFromSafeArea; -} - @end #pragma mark - UIViewBridgeAccessibility @@ -1357,20 +1245,6 @@ - (NSArray *)accessibilityCustomActions return _getAccessibilityFromViewOrProperty(_accessibilityCustomActions, accessibilityCustomActions); } -#if TARGET_OS_TV -- (void)setAccessibilityHeaderElements:(NSArray *)accessibilityHeaderElements -{ - _bridge_prologue_write; - _setAccessibilityToViewAndProperty(_accessibilityHeaderElements, accessibilityHeaderElements, accessibilityHeaderElements, accessibilityHeaderElements); -} - -- (NSArray *)accessibilityHeaderElements -{ - _bridge_prologue_read; - return _getAccessibilityFromViewOrProperty(_accessibilityHeaderElements, accessibilityHeaderElements); -} -#endif - - (void)setAccessibilityActivationPoint:(CGPoint)accessibilityActivationPoint { _bridge_prologue_write; diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 22f36483b0..cc38464e03 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -27,7 +27,6 @@ NS_ASSUME_NONNULL_BEGIN @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; @class _ASPendingState; -@class ASNodeController; struct ASDisplayNodeFlags; BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); @@ -121,9 +120,6 @@ static constexpr CACornerMask kASCACornerAllCorners = unsigned visibilityNotificationsDisabled:VISIBILITY_NOTIFICATIONS_DISABLED_BITS; unsigned isDeallocating:1; -#if YOGA - unsigned willApplyNextYogaCalculatedLayout:1; -#endif // Automatically manages subnodes unsigned automaticallyManagesSubnodes:1; // Main thread only unsigned placeholderEnabled:1; @@ -133,8 +129,6 @@ static constexpr CACornerMask kASCACornerAllCorners = unsigned accessibilityViewIsModal:1; unsigned shouldGroupAccessibilityChildren:1; unsigned isAccessibilityContainer:1; - unsigned fallbackInsetsLayoutMarginsFromSafeArea:1; - unsigned automaticallyRelayoutOnSafeAreaChanges:1; unsigned automaticallyRelayoutOnLayoutMarginsChanges:1; unsigned isViewControllerRoot:1; unsigned hasHadInterfaceStateDelegates:1; @@ -157,9 +151,6 @@ static constexpr CACornerMask kASCACornerAllCorners = ASDisplayNode * __weak _supernode; NSMutableArray *_subnodes; - ASNodeController *_strongNodeController; - __weak ASNodeController *_weakNodeController; - // Set this to nil whenever you modify _subnodes NSArray *_cachedSubnodes; @@ -178,15 +169,6 @@ static constexpr CACornerMask kASCACornerAllCorners = ASLayoutSpecBlock _layoutSpecBlock; NSString *_debugName; -#if YOGA - // Only ASDisplayNodes are supported in _yogaChildren currently. This means that it is necessary to - // create ASDisplayNodes to make a stack layout when using Yoga. - // However, the implementation is mostly ready for id , with a few areas requiring updates. - NSMutableArray *_yogaChildren; - __weak ASDisplayNode *_yogaParent; - ASLayout *_yogaCalculatedLayout; -#endif - // Layout Transition _ASTransitionContext *_pendingLayoutTransitionContext; NSTimeInterval _defaultLayoutTransitionDuration; @@ -254,11 +236,6 @@ static constexpr CACornerMask kASCACornerAllCorners = UIBezierPath *_accessibilityPath; - // Safe Area support - // These properties are used on iOS 10 and lower, where safe area is not supported by UIKit. - UIEdgeInsets _fallbackSafeAreaInsets; - - #pragma mark - ASDisplayNode (Debugging) ASLayout *_unflattenedLayout; @@ -293,16 +270,6 @@ static constexpr CACornerMask kASCACornerAllCorners = */ - (void)__setNeedsDisplay; -/** - * Setup the node -> controller reference. Strong or weak is based on - * the "shouldInvertStrongReference" property of the controller. - * - * Note: To prevent lock-ordering deadlocks, this method does not take the node's lock. - * In practice, changing the node controller of a node multiple times is not - * supported behavior. - */ -- (void)__setNodeController:(ASNodeController *)controller; - /** * Called whenever the node needs to layout its subnodes and, if it's already loaded, its subviews. Executes the layout pass for the node * @@ -367,9 +334,6 @@ static constexpr CACornerMask kASCACornerAllCorners = */ - (void)nodeViewDidAddGestureRecognizer; -// Recalculates fallbackSafeAreaInsets for the subnodes -- (void)_fallbackUpdateSafeAreaOnChildren; - @end @interface ASDisplayNode (InternalPropertyBridge) @@ -379,7 +343,6 @@ static constexpr CACornerMask kASCACornerAllCorners = /// NOTE: Changing this to non-default under iOS < 11 will make an assertion (for the end user to see.) @property (nonatomic) CACornerMask layerMaskedCorners; -- (BOOL)_locked_insetsLayoutMarginsFromSafeArea; @end diff --git a/Source/Private/ASDisplayNodeTipState.h b/Source/Private/ASDisplayNodeTipState.h deleted file mode 100644 index 1e1ef20726..0000000000 --- a/Source/Private/ASDisplayNodeTipState.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// ASDisplayNodeTipState.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@class ASDisplayNode, ASTipNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASDisplayNodeTipState : NSObject - -- (instancetype)initWithNode:(ASDisplayNode *)node NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -/// Unsafe because once the node is deallocated, we will not be able to access the tip state. -@property (nonatomic, unsafe_unretained, readonly) ASDisplayNode *node; - -/// Main-thread-only. -@property (nonatomic, nullable) ASTipNode *tipNode; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASDisplayNodeTipState.mm b/Source/Private/ASDisplayNodeTipState.mm deleted file mode 100644 index b5a4231998..0000000000 --- a/Source/Private/ASDisplayNodeTipState.mm +++ /dev/null @@ -1,25 +0,0 @@ -// -// ASDisplayNodeTipState.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASDisplayNodeTipState.h" - -@interface ASDisplayNodeTipState () -@end - -@implementation ASDisplayNodeTipState - -- (instancetype)initWithNode:(ASDisplayNode *)node -{ - if (self = [super init]) { - _node = node; - } - return self; -} - -@end diff --git a/Source/Private/ASIGListAdapterBasedDataSource.h b/Source/Private/ASIGListAdapterBasedDataSource.h deleted file mode 100644 index 5be2185f2f..0000000000 --- a/Source/Private/ASIGListAdapterBasedDataSource.h +++ /dev/null @@ -1,29 +0,0 @@ -// -// ASIGListAdapterBasedDataSource.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASIGListAdapterBasedDataSource : NSObject - -- (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter collectionDelegate:(nullable id)collectionDelegate; - -@end - -#endif - -NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASIGListAdapterBasedDataSource.mm b/Source/Private/ASIGListAdapterBasedDataSource.mm deleted file mode 100644 index 10f51f37d6..0000000000 --- a/Source/Private/ASIGListAdapterBasedDataSource.mm +++ /dev/null @@ -1,405 +0,0 @@ -// -// ASIGListAdapterBasedDataSource.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_IG_LIST_KIT - -#import "ASIGListAdapterBasedDataSource.h" -#import -#import - -typedef IGListSectionController ASIGSectionController; - -/// The optional methods that a class implements from ASSectionController. -/// Note: Bitfields are not supported by NSValue so we can't use them. -typedef struct { - BOOL sizeRangeForItem; - BOOL shouldBatchFetch; - BOOL beginBatchFetchWithContext; -} ASSectionControllerOverrides; - -/// The optional methods that a class implements from ASSupplementaryNodeSource. -/// Note: Bitfields are not supported by NSValue so we can't use them. -typedef struct { - BOOL sizeRangeForSupplementary; -} ASSupplementarySourceOverrides; - -@protocol ASIGSupplementaryNodeSource -@end - -@interface ASIGListAdapterBasedDataSource () -@property (nonatomic, weak, readonly) IGListAdapter *listAdapter; -@property (nonatomic, readonly) id delegate; -@property (nonatomic, readonly) id dataSource; -@property (nonatomic, weak, readonly) id collectionDelegate; - -/** - * The section controller that we will forward beginBatchFetchWithContext: to. - * Since shouldBatchFetch: is called on main, we capture the last section controller in there, - * and then we use it and clear it in beginBatchFetchWithContext: (on default queue). - * - * It is safe to use it without a lock in this limited way, since those two methods will - * never execute in parallel. - */ -@property (nonatomic, weak) ASIGSectionController *sectionControllerForBatchFetching; -@end - -@implementation ASIGListAdapterBasedDataSource - -- (instancetype)initWithListAdapter:(IGListAdapter *)listAdapter collectionDelegate:(nullable id)collectionDelegate -{ - if (self = [super init]) { -#if IG_LIST_COLLECTION_VIEW - [ASIGListAdapterBasedDataSource setASCollectionViewSuperclass]; -#endif - [ASIGListAdapterBasedDataSource configureUpdater:listAdapter.updater]; - - ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDataSource)], @"Expected IGListAdapter to conform to UICollectionViewDataSource."); - ASDisplayNodeAssert([listAdapter conformsToProtocol:@protocol(UICollectionViewDelegateFlowLayout)], @"Expected IGListAdapter to conform to UICollectionViewDelegateFlowLayout."); - _listAdapter = listAdapter; - _collectionDelegate = collectionDelegate; - } - return self; -} - -- (id)dataSource -{ - return (id)_listAdapter; -} - -- (id)delegate -{ - return (id)_listAdapter; -} - -#pragma mark - ASCollectionDelegate - -- (void)collectionNode:(ASCollectionNode *)collectionNode didSelectItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionNode.view didSelectItemAtIndexPath:indexPath]; -} - -- (void)collectionNode:(ASCollectionNode *)collectionNode didDeselectItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionNode.view didDeselectItemAtIndexPath:indexPath]; -} - -- (void)collectionNode:(ASCollectionNode *)collectionNode didHighlightItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionNode.view didHighlightItemAtIndexPath:indexPath]; -} - -- (void)collectionNode:(ASCollectionNode *)collectionNode didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionNode.view didUnhighlightItemAtIndexPath:indexPath]; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - [self.delegate scrollViewDidScroll:scrollView]; -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView -{ - [self.delegate scrollViewWillBeginDragging:scrollView]; -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset -{ - // IGListAdapter doesn't implement scrollViewWillEndDragging yet (pending pull request), so we need this check for now. Doesn't hurt to have it anyways :) - if ([self.delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { - [self.delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate -{ - [self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -{ - [self.delegate scrollViewDidEndDecelerating:scrollView]; -} - -- (BOOL)shouldBatchFetchForCollectionNode:(ASCollectionNode *)collectionNode -{ - if ([_collectionDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionNode:)]) { - return [_collectionDelegate shouldBatchFetchForCollectionNode:collectionNode]; - } - - NSInteger sectionCount = [self numberOfSectionsInCollectionNode:collectionNode]; - if (sectionCount == 0) { - return NO; - } - - // If they implement shouldBatchFetch, call it. Otherwise, just say YES if they implement beginBatchFetch. - ASIGSectionController *ctrl = [self sectionControllerForSection:sectionCount - 1]; - ASSectionControllerOverrides o = [ASIGListAdapterBasedDataSource overridesForSectionControllerClass:ctrl.class]; - BOOL result = (o.shouldBatchFetch ? [ctrl shouldBatchFetch] : o.beginBatchFetchWithContext); - if (result) { - self.sectionControllerForBatchFetching = ctrl; - } - return result; -} - -- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context -{ - if ([_collectionDelegate respondsToSelector:@selector(collectionNode:willBeginBatchFetchWithContext:)]) { - [_collectionDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:context]; - return; - } - - ASIGSectionController *ctrl = self.sectionControllerForBatchFetching; - self.sectionControllerForBatchFetching = nil; - [ctrl beginBatchFetchWithContext:context]; -} - -- (void)collectionNode:(ASCollectionNode *)collectionNode willDisplayItemWithNode:(ASCellNode *)node -{ - NSIndexPath *indexPath = [collectionNode.view indexPathForNode:node]; - UIView *contentView = node.view.superview; - UICollectionViewCell *cell = (UICollectionViewCell *)contentView.superview; - - if (cell == nil || indexPath == nil) { - return; - } - - [self.delegate collectionView:collectionNode.view willDisplayCell:cell forItemAtIndexPath:indexPath]; -} - -- (void)collectionNode:(ASCollectionNode *)collectionNode didEndDisplayingItemWithNode:(ASCellNode *)node -{ - NSIndexPath *indexPath = [collectionNode.view indexPathForNode:node]; - UIView *contentView = node.view.superview; - UICollectionViewCell *cell = (UICollectionViewCell *)contentView.superview; - - if (cell == nil || indexPath == nil) { - return; - } - - [self.delegate collectionView:collectionNode.view didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; -} - -/** - * Note: It is not documented that ASCollectionNode will forward these UIKit delegate calls if they are implemented. - * It is not considered harmful to do so, and adding them to documentation will confuse most users, who should - * instead using the ASCollectionDelegate callbacks. - */ -#pragma mark - ASCollectionDelegateInterop - -- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath]; -} - -- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath -{ - [self.delegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath]; -} - -#pragma mark - ASCollectionDelegateFlowLayout - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForHeaderInSection:(NSInteger)section -{ - id src = [self supplementaryElementSourceForSection:section]; - if ([ASIGListAdapterBasedDataSource overridesForSupplementarySourceClass:[src class]].sizeRangeForSupplementary) { - return [src sizeRangeForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndex:0]; - } else { - return ASSizeRangeZero; - } -} - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode sizeRangeForFooterInSection:(NSInteger)section -{ - id src = [self supplementaryElementSourceForSection:section]; - if ([ASIGListAdapterBasedDataSource overridesForSupplementarySourceClass:[src class]].sizeRangeForSupplementary) { - return [src sizeRangeForSupplementaryElementOfKind:UICollectionElementKindSectionFooter atIndex:0]; - } else { - return ASSizeRangeZero; - } -} - -- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section -{ - return [self.delegate collectionView:collectionView layout:collectionViewLayout insetForSectionAtIndex:section]; -} - -- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section -{ - return [self.delegate collectionView:collectionView layout:collectionViewLayout minimumLineSpacingForSectionAtIndex:section]; -} - -- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section -{ - return [self.delegate collectionView:collectionView layout:collectionViewLayout minimumInteritemSpacingForSectionAtIndex:section]; -} - -#pragma mark - ASCollectionDataSource - -- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section -{ - return [self.dataSource collectionView:collectionNode.view numberOfItemsInSection:section]; -} - -- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode -{ - return [self.dataSource numberOfSectionsInCollectionView:collectionNode.view]; -} - -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeBlockForItemAtIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeBlockForItemAtIndex:)), ctrl); - return [ctrl nodeBlockForItemAtIndex:indexPath.item]; -} - -- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeForItemAtIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeForItemAtIndex:)), ctrl); - return [ctrl nodeForItemAtIndex:indexPath.item]; -} - -- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:indexPath.section]; - if ([ASIGListAdapterBasedDataSource overridesForSectionControllerClass:ctrl.class].sizeRangeForItem) { - return [ctrl sizeRangeForItemAtIndex:indexPath.item]; - } else { - return ASSizeRangeUnconstrained; - } -} - -- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - id ctrl = [self supplementaryElementSourceForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeBlockForSupplementaryElementOfKind:atIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeBlockForSupplementaryElementOfKind:atIndex:)), ctrl); - return [ctrl nodeBlockForSupplementaryElementOfKind:kind atIndex:indexPath.item]; -} - -- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - id ctrl = [self supplementaryElementSourceForSection:indexPath.section]; - ASDisplayNodeAssert([ctrl respondsToSelector:@selector(nodeForSupplementaryElementOfKind:atIndex:)], @"Expected section controller to respond to to %@. Controller: %@", NSStringFromSelector(@selector(nodeForSupplementaryElementOfKind:atIndex:)), ctrl); - return [ctrl nodeForSupplementaryElementOfKind:kind atIndex:indexPath.item]; -} - -- (NSArray *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section -{ - return [[self supplementaryElementSourceForSection:section] supportedElementKinds]; -} - -#pragma mark - ASCollectionDataSourceInterop - -- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath -{ - return [self.dataSource collectionView:collectionView cellForItemAtIndexPath:indexPath]; -} - -- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath -{ - return [self.dataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; -} - -+ (BOOL)dequeuesCellsForNodeBackedItems -{ - return YES; -} - -#pragma mark - Helpers - -- (id)supplementaryElementSourceForSection:(NSInteger)section -{ - ASIGSectionController *ctrl = [self sectionControllerForSection:section]; - id src = (id)ctrl.supplementaryViewSource; - ASDisplayNodeAssert(src == nil || [src conformsToProtocol:@protocol(ASSupplementaryNodeSource)], @"Supplementary view source should conform to %@", NSStringFromProtocol(@protocol(ASSupplementaryNodeSource))); - return src; -} - -- (ASIGSectionController *)sectionControllerForSection:(NSInteger)section -{ - id object = [_listAdapter objectAtSection:section]; - ASIGSectionController *ctrl = (ASIGSectionController *)[_listAdapter sectionControllerForObject:object]; - ASDisplayNodeAssert([ctrl conformsToProtocol:@protocol(ASSectionController)], @"Expected section controller to conform to %@. Controller: %@", NSStringFromProtocol(@protocol(ASSectionController)), ctrl); - return ctrl; -} - -/// If needed, set ASCollectionView's superclass to IGListCollectionView (IGListKit < 3.0). -#if IG_LIST_COLLECTION_VIEW -+ (void)setASCollectionViewSuperclass -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - class_setSuperclass([ASCollectionView class], [IGListCollectionView class]); - }); -#pragma clang diagnostic pop -} -#endif - -/// Ensure updater won't call reloadData on us. -+ (void)configureUpdater:(id)updater -{ - // Cast to NSObject will be removed after https://github.com/Instagram/IGListKit/pull/435 - if ([(id)updater isKindOfClass:[IGListAdapterUpdater class]]) { - [(IGListAdapterUpdater *)updater setAllowsBackgroundReloading:NO]; - } else { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSLog(@"WARNING: Use of non-%@ updater with AsyncDisplayKit is discouraged. Updater: %@", NSStringFromClass([IGListAdapterUpdater class]), updater); - }); - } -} - -+ (ASSupplementarySourceOverrides)overridesForSupplementarySourceClass:(Class)c -{ - static NSCache *cache; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - cache = [[NSCache alloc] init]; - }); - NSValue *obj = [cache objectForKey:c]; - ASSupplementarySourceOverrides o; - if (obj == nil) { - o.sizeRangeForSupplementary = [c instancesRespondToSelector:@selector(sizeRangeForSupplementaryElementOfKind:atIndex:)]; - obj = [NSValue valueWithBytes:&o objCType:@encode(ASSupplementarySourceOverrides)]; - [cache setObject:obj forKey:c]; - } else { - [obj getValue:&o]; - } - return o; -} - -+ (ASSectionControllerOverrides)overridesForSectionControllerClass:(Class)c -{ - static NSCache *cache; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - cache = [[NSCache alloc] init]; - }); - NSValue *obj = [cache objectForKey:c]; - ASSectionControllerOverrides o; - if (obj == nil) { - o.sizeRangeForItem = [c instancesRespondToSelector:@selector(sizeRangeForItemAtIndex:)]; - o.beginBatchFetchWithContext = [c instancesRespondToSelector:@selector(beginBatchFetchWithContext:)]; - o.shouldBatchFetch = [c instancesRespondToSelector:@selector(shouldBatchFetch)]; - obj = [NSValue valueWithBytes:&o objCType:@encode(ASSectionControllerOverrides)]; - [cache setObject:obj forKey:c]; - } else { - [obj getValue:&o]; - } - return o; -} - -@end - -#endif // AS_IG_LIST_KIT diff --git a/Source/Private/ASImageNode+AnimatedImagePrivate.h b/Source/Private/ASImageNode+AnimatedImagePrivate.h deleted file mode 100644 index 917eb6fbb4..0000000000 --- a/Source/Private/ASImageNode+AnimatedImagePrivate.h +++ /dev/null @@ -1,50 +0,0 @@ -// -// ASImageNode+AnimatedImagePrivate.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#define ASAnimatedImageDefaultRunLoopMode NSRunLoopCommonModes - -@interface ASImageNode () -{ - AS::Mutex _displayLinkLock; - id _animatedImage; - NSString *_animatedImageRunLoopMode; - CADisplayLink *_displayLink; - NSUInteger _lastSuccessfulFrameIndex; - - //accessed on main thread only - CFTimeInterval _playHead; - NSUInteger _playedLoops; - - // Group the BOOLs into a bitfield struct to save memory. - struct { - unsigned int animatedImagePaused:1; - unsigned int cropEnabled:1; // Defaults to YES. - unsigned int forceUpscaling:1; //Defaults to NO. - unsigned int regenerateFromImageAsset:1; //Defaults to NO. - } _imageNodeFlags; -} - -@property (nonatomic) CFTimeInterval lastDisplayLinkFire; - -@end - -@interface ASImageNode (AnimatedImagePrivate) - -- (void)_locked_setAnimatedImage:(id )animatedImage; - -@end - - -@interface ASImageNode (AnimatedImageInvalidation) - -- (void)invalidateAnimatedImage; - -@end diff --git a/Source/Private/ASImageNode+CGExtras.h b/Source/Private/ASImageNode+CGExtras.h index dd529a3abb..50ce73fe7f 100644 --- a/Source/Private/ASImageNode+CGExtras.h +++ b/Source/Private/ASImageNode+CGExtras.h @@ -16,16 +16,12 @@ @param boundsSize The bounds in which the image will be displayed. @param contentMode The mode that defines how image will be scaled and cropped to fit. Supported values are UIViewContentModeScaleToAspectFill and UIViewContentModeScaleToAspectFit. @param cropRect A rectangle that is to be featured by the cropped image. The rectangle is specified as a "unit rectangle," using fractions of the source image's width and height, e.g. CGRectMake(0.5, 0, 0.5, 1.0) will feature the full right half a photo. If the cropRect is empty, the contentMode will be used to determine the drawRect's size, and only the cropRect's origin will be used for positioning. - @param forceUpscaling A boolean that indicates you would *not* like the backing size to be downscaled if the image is smaller than the destination size. Setting this to YES will result in higher memory usage when images are smaller than their destination. - @param forcedSize A CGSize, that if non-CGSizeZero, indicates that the backing size should be forcedSize and not calculated based on boundsSize. @discussion If the image is smaller than the size and UIViewContentModeScaleToAspectFill is specified, we suggest the input size so it will be efficiently upscaled on the GPU by the displaying layer at composite time. */ ASDK_EXTERN void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, CGSize boundsSize, UIViewContentMode contentMode, CGRect cropRect, - BOOL forceUpscaling, - CGSize forcedSize, CGSize *outBackingSize, CGRect *outDrawRect ); diff --git a/Source/Private/ASImageNode+CGExtras.mm b/Source/Private/ASImageNode+CGExtras.mm index b7550c5491..106ae0950c 100644 --- a/Source/Private/ASImageNode+CGExtras.mm +++ b/Source/Private/ASImageNode+CGExtras.mm @@ -37,8 +37,6 @@ void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, CGSize boundsSize, UIViewContentMode contentMode, CGRect cropRect, - BOOL forceUpscaling, - CGSize forcedSize, CGSize *outBackingSize, CGRect *outDrawRect ) @@ -66,10 +64,7 @@ void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, // If fitting the desired aspect ratio to the image size actually results in a larger buffer, use the input values. // However, if there is a pixel savings (e.g. we would have to upscale the image), override the function arguments. - if (CGSizeEqualToSize(CGSizeZero, forcedSize) == NO) { - destinationWidth = (size_t)round(forcedSize.width); - destinationHeight = (size_t)round(forcedSize.height); - } else if (forceUpscaling == NO && (minimumDestinationSize.width * minimumDestinationSize.height) < (destinationWidth * destinationHeight)) { + if ((minimumDestinationSize.width * minimumDestinationSize.height) < (destinationWidth * destinationHeight)) { destinationWidth = (size_t)round(minimumDestinationSize.width); destinationHeight = (size_t)round(minimumDestinationSize.height); if (destinationWidth == 0 || destinationHeight == 0) { diff --git a/Source/Private/ASImageNode+Private.h b/Source/Private/ASImageNode+Private.h index 8de78c8784..2693ce02ef 100644 --- a/Source/Private/ASImageNode+Private.h +++ b/Source/Private/ASImageNode+Private.h @@ -7,6 +7,13 @@ // Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 // +@interface ASImageNode () +{ + unsigned int _regenerateFromImageAsset:1; //Defaults to NO. +} + +@end + #pragma once @interface ASImageNode (Private) diff --git a/Source/Private/ASInternalHelpers.mm b/Source/Private/ASInternalHelpers.mm index 4d6e1fde82..1a88d40be9 100644 --- a/Source/Private/ASInternalHelpers.mm +++ b/Source/Private/ASInternalHelpers.mm @@ -9,7 +9,6 @@ #import -#import #import #import #import @@ -67,14 +66,9 @@ void ASInitializeFrameworkMainThread(void) dispatch_once(&onceToken, ^{ ASDisplayNodeCAssertMainThread(); // Ensure these values are cached on the main thread before needed in the background. - if (ASActivateExperimentalFeature(ASExperimentalLayerDefaults)) { - // Nop. We will gather default values on-demand in ASDefaultAllowsGroupOpacity and ASDefaultAllowsEdgeAntialiasing - } else { - CALayer *layer = [[[UIView alloc] init] layer]; - allowsGroupOpacityFromUIKitOrNil = @(layer.allowsGroupOpacity); - allowsEdgeAntialiasingFromUIKitOrNil = @(layer.allowsEdgeAntialiasing); - } - ASNotifyInitialized(); + CALayer *layer = [[[UIView alloc] init] layer]; + allowsGroupOpacityFromUIKitOrNil = @(layer.allowsGroupOpacity); + allowsEdgeAntialiasingFromUIKitOrNil = @(layer.allowsEdgeAntialiasing); #if AS_SIGNPOST_ENABLE _ASInitializeSignpostObservers(); #endif diff --git a/Source/Private/ASLayerBackingTipProvider.h b/Source/Private/ASLayerBackingTipProvider.h deleted file mode 100644 index 0cc3d03b61..0000000000 --- a/Source/Private/ASLayerBackingTipProvider.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// ASLayerBackingTipProvider.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTipProvider.h" -#import - -#if AS_ENABLE_TIPS - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASLayerBackingTipProvider : ASTipProvider - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/ASLayerBackingTipProvider.mm b/Source/Private/ASLayerBackingTipProvider.mm deleted file mode 100644 index 19d1850ab7..0000000000 --- a/Source/Private/ASLayerBackingTipProvider.mm +++ /dev/null @@ -1,45 +0,0 @@ -// -// ASLayerBackingTipProvider.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASLayerBackingTipProvider.h" - -#if AS_ENABLE_TIPS - -#import -#import -#import -#import -#import - -@implementation ASLayerBackingTipProvider - -- (ASTip *)tipForNode:(ASDisplayNode *)node -{ - // Already layer-backed. - if (node.layerBacked) { - return nil; - } - - // TODO: Avoid revisiting nodes we already visited - ASDisplayNode *failNode = ASDisplayNodeFindFirstNode(node, ^BOOL(ASDisplayNode * _Nonnull node) { - return !node.supportsLayerBacking; - }); - if (failNode != nil) { - return nil; - } - - ASTip *result = [[ASTip alloc] initWithNode:node - kind:ASTipKindEnableLayerBacking - format:@"Enable layer backing to improve performance"]; - return result; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/ASLayoutTransition.mm b/Source/Private/ASLayoutTransition.mm index 588b275651..b854e39357 100644 --- a/Source/Private/ASLayoutTransition.mm +++ b/Source/Private/ASLayoutTransition.mm @@ -16,10 +16,6 @@ #import -#if AS_IG_LIST_DIFF_KIT -#import -#endif - using AS::MutexLocker; /** @@ -159,21 +155,6 @@ - (void)calculateSubnodeOperationsIfNeeded ASLayout *pendingLayout = _pendingLayout.layout; if (previousLayout) { -#if AS_IG_LIST_DIFF_KIT - // IGListDiff completes in linear time O(m+n), so use it if we have it: - IGListIndexSetResult *result = IGListDiff(previousLayout.sublayouts, pendingLayout.sublayouts, IGListDiffEquality); - _insertedSubnodePositions = findNodesInLayoutAtIndexes(pendingLayout, result.inserts, &_insertedSubnodes); - findNodesInLayoutAtIndexes(previousLayout, result.deletes, &_removedSubnodes); - for (IGListMoveIndex *move in result.moves) { - _subnodeMoves.emplace_back(previousLayout.sublayouts[move.from].layoutElement, move.to); - } - - // Sort by ascending order of move destinations, this will allow easy loop of `insertSubnode:AtIndex` later. - std::sort(_subnodeMoves.begin(), _subnodeMoves.end(), [](std::pair, NSUInteger> a, - std::pair b) { - return a.second < b.second; - }); -#else NSIndexSet *insertions, *deletions; NSArray *moves; NSArray *previousNodes = [previousLayout.sublayouts valueForKey:@"layoutElement"]; @@ -190,7 +171,6 @@ - (void)calculateSubnodeOperationsIfNeeded _subnodeMoves.emplace_back(previousLayout.sublayouts[([move indexAtPosition:0])].layoutElement, [move indexAtPosition:1]); } -#endif } else { NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pendingLayout.sublayouts count])]; _insertedSubnodePositions = findNodesInLayoutAtIndexes(pendingLayout, indexes, &_insertedSubnodes); diff --git a/Source/Private/ASTip.h b/Source/Private/ASTip.h deleted file mode 100644 index 5ac6ac18bc..0000000000 --- a/Source/Private/ASTip.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// ASTip.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_ENABLE_TIPS - -NS_ASSUME_NONNULL_BEGIN - -@class ASDisplayNode; - -typedef NS_ENUM (NSInteger, ASTipKind) { - ASTipKindEnableLayerBacking -}; - -AS_SUBCLASSING_RESTRICTED -@interface ASTip : NSObject - -- (instancetype)initWithNode:(ASDisplayNode *)node - kind:(ASTipKind)kind - format:(NSString *)format, ... NS_FORMAT_FUNCTION(3, 4); - -/** - * The kind of tip this is. - */ -@property (nonatomic, readonly) ASTipKind kind; - -/** - * The node that this tip applies to. - */ -@property (nonatomic, readonly) ASDisplayNode *node; - -/** - * The text to show the user. - */ -@property (nonatomic, readonly) NSString *text; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/ASTip.mm b/Source/Private/ASTip.mm deleted file mode 100644 index af1d299221..0000000000 --- a/Source/Private/ASTip.mm +++ /dev/null @@ -1,35 +0,0 @@ -// -// ASTip.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TIPS - -#import - -@implementation ASTip - -- (instancetype)initWithNode:(ASDisplayNode *)node - kind:(ASTipKind)kind - format:(NSString *)format, ... -{ - if (self = [super init]) { - _node = node; - _kind = kind; - va_list args; - va_start(args, format); - _text = [[NSString alloc] initWithFormat:format arguments:args]; - va_end(args); - } - return self; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/ASTipNode.h b/Source/Private/ASTipNode.h deleted file mode 100644 index d01637d869..0000000000 --- a/Source/Private/ASTipNode.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// ASTipNode.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_ENABLE_TIPS - -@class ASTip; - -NS_ASSUME_NONNULL_BEGIN - -/** - * ASTipNode will send these up the responder chain. - */ -@protocol ASTipNodeActions -- (void)didTapTipNode:(id)sender; -@end - -AS_SUBCLASSING_RESTRICTED -@interface ASTipNode : ASControlNode - -- (instancetype)initWithTip:(ASTip *)tip NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -@property (nonatomic, readonly) ASTip *tip; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/ASTipNode.mm b/Source/Private/ASTipNode.mm deleted file mode 100644 index dcca908b8a..0000000000 --- a/Source/Private/ASTipNode.mm +++ /dev/null @@ -1,28 +0,0 @@ -// -// ASTipNode.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTipNode.h" - -#if AS_ENABLE_TIPS - -@implementation ASTipNode - -- (instancetype)initWithTip:(ASTip *)tip -{ - if (self = [super init]) { - self.backgroundColor = [UIColor colorWithRed:0 green:0.7 blue:0.2 alpha:0.3]; - _tip = tip; - [self addTarget:nil action:@selector(didTapTipNode:) forControlEvents:ASControlNodeEventTouchUpInside]; - } - return self; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/ASTipProvider.h b/Source/Private/ASTipProvider.h deleted file mode 100644 index e2aba6c5d9..0000000000 --- a/Source/Private/ASTipProvider.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// ASTipProvider.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_ENABLE_TIPS - -@class ASDisplayNode, ASTip; - -NS_ASSUME_NONNULL_BEGIN - -/** - * An abstract superclass for all tip providers. - */ -@interface ASTipProvider : NSObject - -/** - * The provider looks at the node's current situation and - * generates a tip, if any, to add to the node. - * - * Subclasses must override this. - */ -- (nullable ASTip *)tipForNode:(ASDisplayNode *)node; - -@end - -@interface ASTipProvider (Lookup) - -@property (class, nonatomic, copy, readonly) NSArray<__kindof ASTipProvider *> *all; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/ASTipProvider.mm b/Source/Private/ASTipProvider.mm deleted file mode 100644 index 237e83cda0..0000000000 --- a/Source/Private/ASTipProvider.mm +++ /dev/null @@ -1,43 +0,0 @@ -// -// ASTipProvider.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TIPS - -#import - -// Concrete classes -#import - -@implementation ASTipProvider - -- (ASTip *)tipForNode:(ASDisplayNode *)node -{ - ASDisplayNodeFailAssert(@"Subclasses must override %@", NSStringFromSelector(_cmd)); - return nil; -} - -@end - -@implementation ASTipProvider (Lookup) - -+ (NSArray *)all -{ - static NSArray *providers; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - providers = @[ [ASLayerBackingTipProvider new] ]; - }); - return providers; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/ASTipsController.h b/Source/Private/ASTipsController.h deleted file mode 100644 index fceeb92f60..0000000000 --- a/Source/Private/ASTipsController.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// ASTipsController.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_ENABLE_TIPS - -@class ASDisplayNode; - -NS_ASSUME_NONNULL_BEGIN - -AS_SUBCLASSING_RESTRICTED -@interface ASTipsController : NSObject - -/** - * The shared tip controller instance. - */ -@property (class, readonly) ASTipsController *shared; - -#pragma mark - Node Event Hooks - -/** - * Informs the controller that the sender did enter the visible range. - * - * The controller will run a pass with its tip providers, adding tips as needed. - */ -- (void)nodeDidAppear:(ASDisplayNode *)node; - -@end - -NS_ASSUME_NONNULL_END - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/ASTipsController.mm b/Source/Private/ASTipsController.mm deleted file mode 100644 index e30a064d6c..0000000000 --- a/Source/Private/ASTipsController.mm +++ /dev/null @@ -1,185 +0,0 @@ -// -// ASTipsController.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#if AS_ENABLE_TIPS - -#import -#import -#import -#import -#import -#import - -@interface ASTipsController () - -/// Nil on init, updates to most recent visible window. -@property (nonatomic) UIWindow *appVisibleWindow; - -/// Nil until an application window has become visible. -@property (nonatomic) ASTipsWindow *tipWindow; - -/// Main-thread-only. -@property (nonatomic, readonly) NSMapTable *nodeToTipStates; - -@property (nonatomic) NSMutableArray *nodesThatAppearedDuringRunLoop; - -@end - -@implementation ASTipsController - -#pragma mark - Singleton - -__attribute__((constructor)) static void ASLoadTipsControllerNotification(void) -{ - [NSNotificationCenter.defaultCenter addObserver:ASTipsController.shared - selector:@selector(windowDidBecomeVisibleWithNotification:) - name:UIWindowDidBecomeVisibleNotification - object:nil]; -} - -+ (ASTipsController *)shared -{ - static ASTipsController *ctrl; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - ctrl = [[ASTipsController alloc] init]; - }); - return ctrl; -} - -#pragma mark - Lifecycle - -- (instancetype)init -{ - ASDisplayNodeAssertMainThread(); - if (self = [super init]) { - _nodeToTipStates = [NSMapTable mapTableWithKeyOptions:(NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality) valueOptions:NSPointerFunctionsStrongMemory]; - _nodesThatAppearedDuringRunLoop = [NSMutableArray array]; - } - return self; -} - -#pragma mark - Event Handling - -- (void)nodeDidAppear:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - // If they disabled tips on this class, bail. - if (![[node class] enableTips]) { - return; - } - - // If this node appeared in some other window (like our tips window) ignore it. - if (ASFindWindowOfLayer(node.layer) != self.appVisibleWindow) { - return; - } - - [_nodesThatAppearedDuringRunLoop addObject:node]; -} - -// If this is a main window, start watching it and clear out our tip window. -- (void)windowDidBecomeVisibleWithNotification:(NSNotification *)notification -{ - ASDisplayNodeAssertMainThread(); - UIWindow *window = notification.object; - - // If this is the same window we're already watching, bail. - if (window == self.appVisibleWindow) { - return; - } - - // Ignore windows that are not at the normal level or have empty bounds - if (window.windowLevel != UIWindowLevelNormal || CGRectIsEmpty(window.bounds)) { - return; - } - - self.appVisibleWindow = window; - - // Create the tip window if needed. - [self createTipWindowIfNeededWithFrame:window.bounds]; - - // Clear out our tip window and reset our states. - self.tipWindow.mainWindow = window; - [_nodeToTipStates removeAllObjects]; -} - -- (void)runLoopDidTick -{ - NSArray *nodes = [_nodesThatAppearedDuringRunLoop copy]; - [_nodesThatAppearedDuringRunLoop removeAllObjects]; - - // Go through the old array, removing any that have tips but aren't still visible. - for (ASDisplayNode *node in [_nodeToTipStates copy]) { - if (!node.visible) { - [_nodeToTipStates removeObjectForKey:node]; - } - } - - for (ASDisplayNode *node in nodes) { - // Get the tip state for the node. - ASDisplayNodeTipState *tipState = [_nodeToTipStates objectForKey:node]; - - // If the node already has a tip, bail. This could change. - if (tipState.tipNode != nil) { - return; - } - - for (ASTipProvider *provider in ASTipProvider.all) { - ASTip *tip = [provider tipForNode:node]; - if (!tip) { continue; } - - if (!tipState) { - tipState = [self createTipStateForNode:node]; - } - tipState.tipNode = [[ASTipNode alloc] initWithTip:tip]; - } - } - self.tipWindow.nodeToTipStates = _nodeToTipStates; - [self.tipWindow setNeedsLayout]; -} - -#pragma mark - Internal - -- (void)createTipWindowIfNeededWithFrame:(CGRect)tipWindowFrame -{ - // Lots of property accesses, but simple safe code, only run once. - if (self.tipWindow == nil) { - self.tipWindow = [[ASTipsWindow alloc] initWithFrame:tipWindowFrame]; - self.tipWindow.hidden = NO; - [self setupRunLoopObserver]; - } -} - -/** - * In order to keep the UI updated, the tips controller registers a run loop observer. - * Before the transaction commit happens, the tips controller calls -setNeedsLayout - * on the view controller's view. It will then layout the main window, and then update the frames - * for tip nodes accordingly. - */ -- (void)setupRunLoopObserver -{ - CFRunLoopObserverRef o = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { - [self runLoopDidTick]; - }); - CFRunLoopAddObserver(CFRunLoopGetMain(), o, kCFRunLoopCommonModes); -} - -- (ASDisplayNodeTipState *)createTipStateForNode:(ASDisplayNode *)node -{ - ASDisplayNodeAssertMainThread(); - ASDisplayNodeTipState *tipState = [[ASDisplayNodeTipState alloc] initWithNode:node]; - [_nodeToTipStates setObject:tipState forKey:node]; - return tipState; -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/ASTipsWindow.h b/Source/Private/ASTipsWindow.h deleted file mode 100644 index 5354cc91a8..0000000000 --- a/Source/Private/ASTipsWindow.h +++ /dev/null @@ -1,36 +0,0 @@ -// -// ASTipsWindow.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#if AS_ENABLE_TIPS - -@class ASDisplayNode, ASDisplayNodeTipState; - -NS_ASSUME_NONNULL_BEGIN - -/** - * A window that shows tips. This was originally meant to be a view controller - * but UIKit will not manage view controllers in non-key windows correctly AT ALL - * as of the time of this writing. - */ -AS_SUBCLASSING_RESTRICTED -@interface ASTipsWindow : UIWindow - -/// The main application window that the tips are tracking. -@property (nonatomic, weak) UIWindow *mainWindow; - -@property (nonatomic, copy, nullable) NSMapTable *nodeToTipStates; - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/Source/Private/ASTipsWindow.mm b/Source/Private/ASTipsWindow.mm deleted file mode 100644 index 010932613a..0000000000 --- a/Source/Private/ASTipsWindow.mm +++ /dev/null @@ -1,98 +0,0 @@ -// -// ASTipsWindow.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#if AS_ENABLE_TIPS - -#import -#import -#import -#import - -@interface ASTipsWindow () -@property (nonatomic, readonly) ASDisplayNode *node; -@end - -@implementation ASTipsWindow - -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) { - /** - * UIKit throws an exception if you don't add a root view controller to a window, - * but if the window isn't key, then it doesn't manage the root view controller correctly! - * - * So we set a dummy root view controller and hide it. - */ - self.rootViewController = [UIViewController new]; - self.rootViewController.view.hidden = YES; - - _node = [[ASDisplayNode alloc] init]; - [self addSubnode:_node]; - - self.windowLevel = UIWindowLevelNormal + 1; - self.opaque = NO; - } - return self; -} - -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -{ - UIView *result = [super hitTest:point withEvent:event]; - // Ignore touches unless they hit one of my node's subnodes - if (result == _node.view) { - return nil; - } - return result; -} - -- (void)setMainWindow:(UIWindow *)mainWindow -{ - _mainWindow = mainWindow; - for (ASDisplayNode *node in _node.subnodes) { - [node removeFromSupernode]; - } -} - -- (void)didTapTipNode:(ASTipNode *)tipNode -{ - ASDisplayNode.tipDisplayBlock(tipNode.tip.node, tipNode.tip.text); -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - _node.frame = self.bounds; - - // Ensure the main window is laid out first. - [self.mainWindow layoutIfNeeded]; - - NSMutableSet *tipNodesToRemove = [NSMutableSet setWithArray:_node.subnodes]; - for (ASDisplayNodeTipState *tipState in [_nodeToTipStates objectEnumerator]) { - ASDisplayNode *node = tipState.node; - ASTipNode *tipNode = tipState.tipNode; - [tipNodesToRemove removeObject:tipNode]; - CGRect rect = node.bounds; - rect = [node.view convertRect:rect toView:nil]; - rect = [self convertRect:rect fromView:nil]; - tipNode.frame = rect; - if (tipNode.supernode != _node) { - [_node addSubnode:tipNode]; - } - } - - // Clean up any tip nodes whose target nodes have disappeared. - for (ASTipNode *tipNode in tipNodesToRemove) { - [tipNode removeFromSupernode]; - } -} - -@end - -#endif // AS_ENABLE_TIPS diff --git a/Source/Private/_ASCollectionGalleryLayoutInfo.h b/Source/Private/_ASCollectionGalleryLayoutInfo.h deleted file mode 100644 index 6d7c91e5e5..0000000000 --- a/Source/Private/_ASCollectionGalleryLayoutInfo.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// _ASCollectionGalleryLayoutInfo.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@interface _ASCollectionGalleryLayoutInfo : NSObject - -// Read-only properties -@property (nonatomic, readonly) CGSize itemSize; -@property (nonatomic, readonly) CGFloat minimumLineSpacing; -@property (nonatomic, readonly) CGFloat minimumInteritemSpacing; -@property (nonatomic, readonly) UIEdgeInsets sectionInset; - -- (instancetype)initWithItemSize:(CGSize)itemSize - minimumLineSpacing:(CGFloat)minimumLineSpacing - minimumInteritemSpacing:(CGFloat)minimumInteritemSpacing - sectionInset:(UIEdgeInsets)sectionInset NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -@end diff --git a/Source/Private/_ASCollectionGalleryLayoutInfo.mm b/Source/Private/_ASCollectionGalleryLayoutInfo.mm deleted file mode 100644 index fec0b52872..0000000000 --- a/Source/Private/_ASCollectionGalleryLayoutInfo.mm +++ /dev/null @@ -1,68 +0,0 @@ -// -// _ASCollectionGalleryLayoutInfo.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@implementation _ASCollectionGalleryLayoutInfo - -- (instancetype)initWithItemSize:(CGSize)itemSize - minimumLineSpacing:(CGFloat)minimumLineSpacing - minimumInteritemSpacing:(CGFloat)minimumInteritemSpacing - sectionInset:(UIEdgeInsets)sectionInset -{ - self = [super init]; - if (self) { - _itemSize = itemSize; - _minimumLineSpacing = minimumLineSpacing; - _minimumInteritemSpacing = minimumInteritemSpacing; - _sectionInset = sectionInset; - } - return self; -} - -- (BOOL)isEqualToInfo:(_ASCollectionGalleryLayoutInfo *)info -{ - if (info == nil) { - return NO; - } - - return CGSizeEqualToSize(_itemSize, info.itemSize) - && _minimumLineSpacing == info.minimumLineSpacing - && _minimumInteritemSpacing == info.minimumInteritemSpacing - && UIEdgeInsetsEqualToEdgeInsets(_sectionInset, info.sectionInset); -} - -- (BOOL)isEqual:(id)other -{ - if (self == other) { - return YES; - } - if (! [other isKindOfClass:[_ASCollectionGalleryLayoutInfo class]]) { - return NO; - } - return [self isEqualToInfo:other]; -} - -- (NSUInteger)hash -{ - struct { - CGSize itemSize; - CGFloat minimumLineSpacing; - CGFloat minimumInteritemSpacing; - UIEdgeInsets sectionInset; - } data = { - _itemSize, - _minimumLineSpacing, - _minimumInteritemSpacing, - _sectionInset, - }; - return ASHashBytes(&data, sizeof(data)); -} - -@end diff --git a/Source/Private/_ASCollectionGalleryLayoutItem.h b/Source/Private/_ASCollectionGalleryLayoutItem.h deleted file mode 100644 index ce071bb371..0000000000 --- a/Source/Private/_ASCollectionGalleryLayoutItem.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// _ASCollectionGalleryLayoutItem.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@class ASCollectionElement; - -NS_ASSUME_NONNULL_BEGIN - -/** - * A dummy item that represents a collection element to participate in the collection layout calculation process - * without triggering measurement on the actual node of the collection element. - * - * This item always has a fixed size that is the item size passed to it. - */ -AS_SUBCLASSING_RESTRICTED -@interface _ASGalleryLayoutItem : NSObject - -@property (nonatomic, readonly) CGSize itemSize; -@property (nonatomic, weak, readonly) ASCollectionElement *collectionElement; - -- (instancetype)initWithItemSize:(CGSize)itemSize collectionElement:(ASCollectionElement *)collectionElement; -- (instancetype)init __unavailable; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Private/_ASCollectionGalleryLayoutItem.mm b/Source/Private/_ASCollectionGalleryLayoutItem.mm deleted file mode 100644 index 3e4320e3ce..0000000000 --- a/Source/Private/_ASCollectionGalleryLayoutItem.mm +++ /dev/null @@ -1,82 +0,0 @@ -// -// _ASCollectionGalleryLayoutItem.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import - -@interface _ASGalleryLayoutItem () -@property ASPrimitiveTraitCollection primitiveTraitCollection; - -@end - -@implementation _ASGalleryLayoutItem - -@synthesize style; - -- (instancetype)initWithItemSize:(CGSize)itemSize collectionElement:(ASCollectionElement *)collectionElement -{ - self = [super init]; - if (self) { - ASDisplayNodeAssert(! CGSizeEqualToSize(CGSizeZero, itemSize), @"Item size should not be zero"); - ASDisplayNodeAssertNotNil(collectionElement, @"Collection element should not be nil"); - _itemSize = itemSize; - _collectionElement = collectionElement; - } - return self; -} - -ASLayoutElementStyleExtensibilityForwarding - -- (ASTraitCollection *)asyncTraitCollection -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (ASLayoutElementType)layoutElementType -{ - return ASLayoutElementTypeLayoutSpec; -} - -- (NSArray> *)sublayoutElements -{ - ASDisplayNodeAssertNotSupported(); - return nil; -} - -- (BOOL)implementsLayoutMethod -{ - return YES; -} - -ASLayoutElementLayoutCalculationDefaults - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - ASDisplayNodeAssert(CGSizeEqualToSize(_itemSize, ASSizeRangeClamp(constrainedSize, _itemSize)), - @"Item size %@ can't fit within the bounds of constrained size %@", NSStringFromCGSize(_itemSize), NSStringFromASSizeRange(constrainedSize)); - return [ASLayout layoutWithLayoutElement:self size:_itemSize]; -} - -#pragma mark - ASLayoutElementAsciiArtProtocol - -- (NSString *)asciiArtString -{ - return [ASLayoutSpec asciiArtStringForChildren:@[] parentName:[self asciiArtName]]; -} - -- (NSString *)asciiArtName -{ - return [NSMutableString stringWithCString:object_getClassName(self) encoding:NSASCIIStringEncoding]; -} - -@end diff --git a/Source/Private/_ASCoreAnimationExtras.h b/Source/Private/_ASCoreAnimationExtras.h index 182bc35329..04516cd43d 100644 --- a/Source/Private/_ASCoreAnimationExtras.h +++ b/Source/Private/_ASCoreAnimationExtras.h @@ -79,6 +79,3 @@ ASDK_EXTERN UIImage *ASDisplayNodeStretchableBoxContentsWithColor(UIColor *color @return YES if the layer has ongoing animations, otherwise NO */ ASDK_EXTERN BOOL ASDisplayNodeLayerHasAnimations(CALayer *layer); - -// This function is a less generalized version of ASDisplayNodeSetResizableContents. -ASDK_EXTERN void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UIImage *image) ASDISPLAYNODE_DEPRECATED; diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index a9d760e9e8..7682e12a04 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -82,13 +82,11 @@ int setAccessibilityIdentifier:1; int setAccessibilityNavigationStyle:1; int setAccessibilityCustomActions:1; - int setAccessibilityHeaderElements:1; int setAccessibilityActivationPoint:1; int setAccessibilityPath:1; int setSemanticContentAttribute:1; int setLayoutMargins:1; int setPreservesSuperviewLayoutMargins:1; - int setInsetsLayoutMarginsFromSafeArea:1; int setActions:1; int setMaskedCorners : 1; } ASPendingStateFlags; @@ -149,7 +147,6 @@ @implementation _ASPendingState struct { unsigned int asyncTransactionContainer:1; unsigned int preservesSuperviewLayoutMargins:1; - unsigned int insetsLayoutMarginsFromSafeArea:1; unsigned int isAccessibilityElement:1; unsigned int accessibilityElementsHidden:1; unsigned int accessibilityViewIsModal:1; @@ -223,7 +220,6 @@ ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *sta @synthesize semanticContentAttribute=semanticContentAttribute; @synthesize layoutMargins=layoutMargins; @synthesize preservesSuperviewLayoutMargins=preservesSuperviewLayoutMargins; -@synthesize insetsLayoutMarginsFromSafeArea=insetsLayoutMarginsFromSafeArea; @synthesize actions=actions; @synthesize maskedCorners = maskedCorners; @@ -280,7 +276,6 @@ - (instancetype)init borderColor = blackColorRef; layoutMargins = UIEdgeInsetsMake(8, 8, 8, 8); _flags.preservesSuperviewLayoutMargins = NO; - _flags.insetsLayoutMarginsFromSafeArea = YES; _flags.isAccessibilityElement = NO; accessibilityLabel = nil; accessibilityAttributedLabel = nil; @@ -658,17 +653,6 @@ - (BOOL)preservesSuperviewLayoutMargins return _flags.preservesSuperviewLayoutMargins; } -- (void)setInsetsLayoutMarginsFromSafeArea:(BOOL)flag -{ - _flags.insetsLayoutMarginsFromSafeArea = flag; - _stateToApplyFlags.setInsetsLayoutMarginsFromSafeArea = YES; -} - -- (BOOL)insetsLayoutMarginsFromSafeArea -{ - return _flags.insetsLayoutMarginsFromSafeArea; -} - - (void)setSemanticContentAttribute:(UISemanticContentAttribute)attribute API_AVAILABLE(ios(9.0), tvos(9.0)) { semanticContentAttribute = attribute; _stateToApplyFlags.setSemanticContentAttribute = YES; @@ -884,22 +868,6 @@ - (void)setAccessibilityCustomActions:(NSArray *)newAccessibilityCustomActions } } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (NSArray *)accessibilityHeaderElements -{ - return accessibilityHeaderElements; -} - -- (void)setAccessibilityHeaderElements:(NSArray *)newAccessibilityHeaderElements -{ - _stateToApplyFlags.setAccessibilityHeaderElements = YES; - if (accessibilityHeaderElements != newAccessibilityHeaderElements) { - accessibilityHeaderElements = [newAccessibilityHeaderElements copy]; - } -} -#pragma clang diagnostic pop - - (CGPoint)accessibilityActivationPoint { if (_stateToApplyFlags.setAccessibilityActivationPoint) { @@ -1182,12 +1150,6 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setPreservesSuperviewLayoutMargins) view.preservesSuperviewLayoutMargins = _flags.preservesSuperviewLayoutMargins; - if (AS_AVAILABLE_IOS_TVOS(11.0, 11.0)) { - if (flags.setInsetsLayoutMarginsFromSafeArea) { - view.insetsLayoutMarginsFromSafeArea = _flags.insetsLayoutMarginsFromSafeArea; - } - } - if (flags.setSemanticContentAttribute) { view.semanticContentAttribute = semanticContentAttribute; } @@ -1243,11 +1205,6 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setAccessibilityCustomActions) { view.accessibilityCustomActions = accessibilityCustomActions; } - -#if TARGET_OS_TV - if (flags.setAccessibilityHeaderElements) - view.accessibilityHeaderElements = accessibilityHeaderElements; -#endif if (flags.setAccessibilityActivationPoint) view.accessibilityActivationPoint = accessibilityActivationPoint; @@ -1360,9 +1317,6 @@ + (_ASPendingState *)pendingViewStateFromView:(UIView *)view pendingState.semanticContentAttribute = view.semanticContentAttribute; pendingState.layoutMargins = view.layoutMargins; pendingState.preservesSuperviewLayoutMargins = view.preservesSuperviewLayoutMargins; - if (AS_AVAILABLE_IOS_TVOS(11, 11)) { - pendingState.insetsLayoutMarginsFromSafeArea = view.insetsLayoutMarginsFromSafeArea; - } pendingState.isAccessibilityElement = view.isAccessibilityElement; pendingState.accessibilityLabel = view.accessibilityLabel; pendingState.accessibilityHint = view.accessibilityHint; @@ -1381,9 +1335,6 @@ + (_ASPendingState *)pendingViewStateFromView:(UIView *)view pendingState.accessibilityIdentifier = view.accessibilityIdentifier; pendingState.accessibilityNavigationStyle = view.accessibilityNavigationStyle; pendingState.accessibilityCustomActions = view.accessibilityCustomActions; -#if TARGET_OS_TV - pendingState.accessibilityHeaderElements = view.accessibilityHeaderElements; -#endif pendingState.accessibilityActivationPoint = view.accessibilityActivationPoint; pendingState.accessibilityPath = view.accessibilityPath; return pendingState; diff --git a/Source/TextExperiment/Component/ASTextDebugOption.h b/Source/TextExperiment/Component/ASTextDebugOption.h deleted file mode 100644 index efa7808c87..0000000000 --- a/Source/TextExperiment/Component/ASTextDebugOption.h +++ /dev/null @@ -1,92 +0,0 @@ -// -// ASTextDebugOption.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -@class ASTextDebugOption; - -NS_ASSUME_NONNULL_BEGIN - -/** - The ASTextDebugTarget protocol defines the method a debug target should implement. - A debug target can be add to the global container to receive the shared debug - option changed notification. - */ -@protocol ASTextDebugTarget - -@required -/** - When the shared debug option changed, this method would be called on main thread. - It should return as quickly as possible. The option's property should not be changed - in this method. - - @param option The shared debug option. - */ -- (void)setDebugOption:(nullable ASTextDebugOption *)option; -@end - - - -/** - The debug option for ASText. - */ -@interface ASTextDebugOption : NSObject -@property (nullable, nonatomic) UIColor *baselineColor; ///< baseline color -@property (nullable, nonatomic) UIColor *CTFrameBorderColor; ///< CTFrame path border color -@property (nullable, nonatomic) UIColor *CTFrameFillColor; ///< CTFrame path fill color -@property (nullable, nonatomic) UIColor *CTLineBorderColor; ///< CTLine bounds border color -@property (nullable, nonatomic) UIColor *CTLineFillColor; ///< CTLine bounds fill color -@property (nullable, nonatomic) UIColor *CTLineNumberColor; ///< CTLine line number color -@property (nullable, nonatomic) UIColor *CTRunBorderColor; ///< CTRun bounds border color -@property (nullable, nonatomic) UIColor *CTRunFillColor; ///< CTRun bounds fill color -@property (nullable, nonatomic) UIColor *CTRunNumberColor; ///< CTRun number color -@property (nullable, nonatomic) UIColor *CGGlyphBorderColor; ///< CGGlyph bounds border color -@property (nullable, nonatomic) UIColor *CGGlyphFillColor; ///< CGGlyph bounds fill color - -- (BOOL)needDrawDebug; ///< `YES`: at least one debug color is visible. `NO`: all debug color is invisible/nil. -- (void)clear; ///< Set all debug color to nil. - -/** - Add a debug target. - - @discussion When `setSharedDebugOption:` is called, all added debug target will - receive `setDebugOption:` in main thread. It maintains an unsafe_unretained - reference to this target. The target must to removed before dealloc. - - @param target A debug target. - */ -+ (void)addDebugTarget:(id)target; - -/** - Remove a debug target which is added by `addDebugTarget:`. - - @param target A debug target. - */ -+ (void)removeDebugTarget:(id)target; - -/** - Returns the shared debug option. - - @return The shared debug option, default is nil. - */ -+ (nullable ASTextDebugOption *)sharedDebugOption; - -/** - Set a debug option as shared debug option. - This method must be called on main thread. - - @discussion When call this method, the new option will set to all debug target - which is added by `addDebugTarget:`. - - @param option A new debug option (nil is valid). - */ -+ (void)setSharedDebugOption:(nullable ASTextDebugOption *)option; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/TextExperiment/Component/ASTextDebugOption.mm b/Source/TextExperiment/Component/ASTextDebugOption.mm deleted file mode 100644 index 2565b903c9..0000000000 --- a/Source/TextExperiment/Component/ASTextDebugOption.mm +++ /dev/null @@ -1,135 +0,0 @@ -// -// ASTextDebugOption.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTextDebugOption.h" -#import - -static pthread_mutex_t _sharedDebugLock; -static CFMutableSetRef _sharedDebugTargets = nil; -static ASTextDebugOption *_sharedDebugOption = nil; - -static const void* _as_sharedDebugSetRetain(CFAllocatorRef allocator, const void *value) { - return value; -} - -static void _as_sharedDebugSetRelease(CFAllocatorRef allocator, const void *value) { -} - -void _as_sharedDebugSetFunction(const void *value, void *context) { - id target = (__bridge id)(value); - [target setDebugOption:_sharedDebugOption]; -} - -static void _initSharedDebug() { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - pthread_mutex_init(&_sharedDebugLock, NULL); - CFSetCallBacks callbacks = kCFTypeSetCallBacks; - callbacks.retain = _as_sharedDebugSetRetain; - callbacks.release = _as_sharedDebugSetRelease; - _sharedDebugTargets = CFSetCreateMutable(CFAllocatorGetDefault(), 0, &callbacks); - }); -} - -static void _setSharedDebugOption(ASTextDebugOption *option) { - _initSharedDebug(); - pthread_mutex_lock(&_sharedDebugLock); - _sharedDebugOption = option.copy; - CFSetApplyFunction(_sharedDebugTargets, _as_sharedDebugSetFunction, NULL); - pthread_mutex_unlock(&_sharedDebugLock); -} - -static ASTextDebugOption *_getSharedDebugOption() { - _initSharedDebug(); - pthread_mutex_lock(&_sharedDebugLock); - ASTextDebugOption *op = _sharedDebugOption; - pthread_mutex_unlock(&_sharedDebugLock); - return op; -} - -static void _addDebugTarget(id target) { - _initSharedDebug(); - pthread_mutex_lock(&_sharedDebugLock); - CFSetAddValue(_sharedDebugTargets, (__bridge const void *)(target)); - pthread_mutex_unlock(&_sharedDebugLock); -} - -static void _removeDebugTarget(id target) { - _initSharedDebug(); - pthread_mutex_lock(&_sharedDebugLock); - CFSetRemoveValue(_sharedDebugTargets, (__bridge const void *)(target)); - pthread_mutex_unlock(&_sharedDebugLock); -} - - -@implementation ASTextDebugOption - -- (id)copyWithZone:(NSZone *)zone { - ASTextDebugOption *op = [self.class new]; - op.baselineColor = self.baselineColor; - op.CTFrameBorderColor = self.CTFrameBorderColor; - op.CTFrameFillColor = self.CTFrameFillColor; - op.CTLineBorderColor = self.CTLineBorderColor; - op.CTLineFillColor = self.CTLineFillColor; - op.CTLineNumberColor = self.CTLineNumberColor; - op.CTRunBorderColor = self.CTRunBorderColor; - op.CTRunFillColor = self.CTRunFillColor; - op.CTRunNumberColor = self.CTRunNumberColor; - op.CGGlyphBorderColor = self.CGGlyphBorderColor; - op.CGGlyphFillColor = self.CGGlyphFillColor; - return op; -} - -- (BOOL)needDrawDebug { - if (self.baselineColor || - self.CTFrameBorderColor || - self.CTFrameFillColor || - self.CTLineBorderColor || - self.CTLineFillColor || - self.CTLineNumberColor || - self.CTRunBorderColor || - self.CTRunFillColor || - self.CTRunNumberColor || - self.CGGlyphBorderColor || - self.CGGlyphFillColor) return YES; - return NO; -} - -- (void)clear { - self.baselineColor = nil; - self.CTFrameBorderColor = nil; - self.CTFrameFillColor = nil; - self.CTLineBorderColor = nil; - self.CTLineFillColor = nil; - self.CTLineNumberColor = nil; - self.CTRunBorderColor = nil; - self.CTRunFillColor = nil; - self.CTRunNumberColor = nil; - self.CGGlyphBorderColor = nil; - self.CGGlyphFillColor = nil; -} - -+ (void)addDebugTarget:(id)target { - if (target) _addDebugTarget(target); -} - -+ (void)removeDebugTarget:(id)target { - if (target) _removeDebugTarget(target); -} - -+ (ASTextDebugOption *)sharedDebugOption { - return _getSharedDebugOption(); -} - -+ (void)setSharedDebugOption:(ASTextDebugOption *)option { - NSAssert([NSThread isMainThread], @"This method must be called on the main thread"); - _setSharedDebugOption(option); -} - -@end - diff --git a/Source/TextExperiment/Component/ASTextInput.h b/Source/TextExperiment/Component/ASTextInput.h deleted file mode 100644 index 9a6cbd13d1..0000000000 --- a/Source/TextExperiment/Component/ASTextInput.h +++ /dev/null @@ -1,85 +0,0 @@ -// -// ASTextInput.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - Text position affinity. For example, the offset appears after the last - character on a line is backward affinity, before the first character on - the following line is forward affinity. - */ -typedef NS_ENUM(NSInteger, ASTextAffinity) { - ASTextAffinityForward = 0, ///< offset appears before the character - ASTextAffinityBackward = 1, ///< offset appears after the character -}; - - -/** - A ASTextPosition object represents a position in a text container; in other words, - it is an index into the backing string in a text-displaying view. - - ASTextPosition has the same API as Apple's implementation in UITextView/UITextField, - so you can alse use it to interact with UITextView/UITextField. - */ -@interface ASTextPosition : UITextPosition - -@property (nonatomic, readonly) NSInteger offset; -@property (nonatomic, readonly) ASTextAffinity affinity; - -+ (instancetype)positionWithOffset:(NSInteger)offset NS_RETURNS_RETAINED; -+ (instancetype)positionWithOffset:(NSInteger)offset affinity:(ASTextAffinity) affinity NS_RETURNS_RETAINED; - -- (NSComparisonResult)compare:(id)otherPosition; - -@end - - -/** - A ASTextRange object represents a range of characters in a text container; in other words, - it identifies a starting index and an ending index in string backing a text-displaying view. - - ASTextRange has the same API as Apple's implementation in UITextView/UITextField, - so you can alse use it to interact with UITextView/UITextField. - */ -@interface ASTextRange : UITextRange - -@property (nonatomic, readonly) ASTextPosition *start; -@property (nonatomic, readonly) ASTextPosition *end; -@property (nonatomic, readonly, getter=isEmpty) BOOL empty; - -+ (instancetype)rangeWithRange:(NSRange)range NS_RETURNS_RETAINED; -+ (instancetype)rangeWithRange:(NSRange)range affinity:(ASTextAffinity) affinity NS_RETURNS_RETAINED; -+ (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end NS_RETURNS_RETAINED; -+ (instancetype)defaultRange NS_RETURNS_RETAINED; ///< <{0,0} Forward> - -- (NSRange)asRange; - -@end - - -/** - A ASTextSelectionRect object encapsulates information about a selected range of - text in a text-displaying view. - - ASTextSelectionRect has the same API as Apple's implementation in UITextView/UITextField, - so you can alse use it to interact with UITextView/UITextField. - */ -@interface ASTextSelectionRect : UITextSelectionRect - -@property (nonatomic) CGRect rect; -@property (nonatomic) UITextWritingDirection writingDirection; -@property (nonatomic) BOOL containsStart; -@property (nonatomic) BOOL containsEnd; -@property (nonatomic) BOOL isVertical; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/TextExperiment/Component/ASTextInput.mm b/Source/TextExperiment/Component/ASTextInput.mm deleted file mode 100644 index 1cdfe73858..0000000000 --- a/Source/TextExperiment/Component/ASTextInput.mm +++ /dev/null @@ -1,150 +0,0 @@ -// -// ASTextInput.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - - -@implementation ASTextPosition - -+ (instancetype)positionWithOffset:(NSInteger)offset NS_RETURNS_RETAINED { - return [self positionWithOffset:offset affinity:ASTextAffinityForward]; -} - -+ (instancetype)positionWithOffset:(NSInteger)offset affinity:(ASTextAffinity)affinity NS_RETURNS_RETAINED { - ASTextPosition *p = [self new]; - p->_offset = offset; - p->_affinity = affinity; - return p; -} - -- (instancetype)copyWithZone:(NSZone *)zone { - return [self.class positionWithOffset:_offset affinity:_affinity]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p> (%@%@)", self.class, self, @(_offset), _affinity == ASTextAffinityForward ? @"F":@"B"]; -} - -- (NSUInteger)hash { - return _offset * 2 + (_affinity == ASTextAffinityForward ? 1 : 0); -} - -- (BOOL)isEqual:(ASTextPosition *)object { - if (!object) return NO; - return _offset == object.offset && _affinity == object.affinity; -} - -- (NSComparisonResult)compare:(ASTextPosition *)otherPosition { - if (!otherPosition) return NSOrderedAscending; - if (_offset < otherPosition.offset) return NSOrderedAscending; - if (_offset > otherPosition.offset) return NSOrderedDescending; - if (_affinity == ASTextAffinityBackward && otherPosition.affinity == ASTextAffinityForward) return NSOrderedAscending; - if (_affinity == ASTextAffinityForward && otherPosition.affinity == ASTextAffinityBackward) return NSOrderedDescending; - return NSOrderedSame; -} - -@end - - - -@implementation ASTextRange { - ASTextPosition *_start; - ASTextPosition *_end; -} - -- (instancetype)init { - self = [super init]; - if (!self) return nil; - _start = [ASTextPosition positionWithOffset:0]; - _end = [ASTextPosition positionWithOffset:0]; - return self; -} - -- (ASTextPosition *)start { - return _start; -} - -- (ASTextPosition *)end { - return _end; -} - -- (BOOL)isEmpty { - return _start.offset == _end.offset; -} - -- (NSRange)asRange { - return NSMakeRange(_start.offset, _end.offset - _start.offset); -} - -+ (instancetype)rangeWithRange:(NSRange)range NS_RETURNS_RETAINED { - return [self rangeWithRange:range affinity:ASTextAffinityForward]; -} - -+ (instancetype)rangeWithRange:(NSRange)range affinity:(ASTextAffinity)affinity NS_RETURNS_RETAINED { - ASTextPosition *start = [ASTextPosition positionWithOffset:range.location affinity:affinity]; - ASTextPosition *end = [ASTextPosition positionWithOffset:range.location + range.length affinity:affinity]; - return [self rangeWithStart:start end:end]; -} - -+ (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end NS_RETURNS_RETAINED { - if (!start || !end) return nil; - if ([start compare:end] == NSOrderedDescending) { - ASTEXT_SWAP(start, end); - } - ASTextRange *range = [ASTextRange new]; - range->_start = start; - range->_end = end; - return range; -} - -+ (instancetype)defaultRange NS_RETURNS_RETAINED { - return [self new]; -} - -- (instancetype)copyWithZone:(NSZone *)zone { - return [self.class rangeWithStart:_start end:_end]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p> (%@, %@)%@", self.class, self, @(_start.offset), @(_end.offset - _start.offset), _end.affinity == ASTextAffinityForward ? @"F":@"B"]; -} - -- (NSUInteger)hash { - return (sizeof(NSUInteger) == 8 ? OSSwapInt64(_start.hash) : OSSwapInt32(_start.hash)) + _end.hash; -} - -- (BOOL)isEqual:(ASTextRange *)object { - if (!object) return NO; - return [_start isEqual:object.start] && [_end isEqual:object.end]; -} - -@end - - - -@implementation ASTextSelectionRect - -@synthesize rect = _rect; -@synthesize writingDirection = _writingDirection; -@synthesize containsStart = _containsStart; -@synthesize containsEnd = _containsEnd; -@synthesize isVertical = _isVertical; - -- (id)copyWithZone:(NSZone *)zone { - ASTextSelectionRect *one = [self.class new]; - one.rect = _rect; - one.writingDirection = _writingDirection; - one.containsStart = _containsStart; - one.containsEnd = _containsEnd; - one.isVertical = _isVertical; - return one; -} - -@end diff --git a/Source/TextExperiment/Component/ASTextLayout.h b/Source/TextExperiment/Component/ASTextLayout.h deleted file mode 100644 index 55ac417f12..0000000000 --- a/Source/TextExperiment/Component/ASTextLayout.h +++ /dev/null @@ -1,547 +0,0 @@ -// -// ASTextLayout.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#import "ASTextDebugOption.h" -#import "ASTextLine.h" -#import "ASTextInput.h" - -@protocol ASTextLinePositionModifier; - -NS_ASSUME_NONNULL_BEGIN - -/** - The max text container size in layout. - */ -ASDK_EXTERN const CGSize ASTextContainerMaxSize; - -/** - The ASTextContainer class defines a region in which text is laid out. - ASTextLayout class uses one or more ASTextContainer objects to generate layouts. - - A ASTextContainer defines rectangular regions (`size` and `insets`) or - nonrectangular shapes (`path`), and you can define exclusion paths inside the - text container's bounding rectangle so that text flows around the exclusion - path as it is laid out. - - All methods in this class is thread-safe. - - Example: - - ┌─────────────────────────────┐ <------- container - │ │ - │ asdfasdfasdfasdfasdfa <------------ container insets - │ asdfasdfa asdfasdfa │ - │ asdfas asdasd │ - │ asdfa <----------------------- container exclusion path - │ asdfas adfasd │ - │ asdfasdfa asdfasdfa │ - │ asdfasdfasdfasdfasdfa │ - │ │ - └─────────────────────────────┘ - */ -@interface ASTextContainer : NSObject - -/// Creates a container with the specified size. @param size The size. -+ (instancetype)containerWithSize:(CGSize)size NS_RETURNS_RETAINED; - -/// Creates a container with the specified size and insets. @param size The size. @param insets The text insets. -+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets NS_RETURNS_RETAINED; - -/// Creates a container with the specified path. @param path The path. -+ (instancetype)containerWithPath:(nullable UIBezierPath *)path NS_RETURNS_RETAINED; - -/// Mark this immutable, so you get free copies going forward. -- (void)makeImmutable; - -/// The constrained size. (if the size is larger than ASTextContainerMaxSize, it will be clipped) -@property CGSize size; - -/// The insets for constrained size. The inset value should not be negative. Default is UIEdgeInsetsZero. -@property UIEdgeInsets insets; - -/// Custom constrained path. Set this property to ignore `size` and `insets`. Default is nil. -@property (nullable, copy) UIBezierPath *path; - -/// An array of `UIBezierPath` for path exclusion. Default is nil. -@property (nullable, copy) NSArray *exclusionPaths; - -/// Path line width. Default is 0; -@property CGFloat pathLineWidth; - -/// YES:(PathFillEvenOdd) Text is filled in the area that would be painted if the path were given to CGContextEOFillPath. -/// NO: (PathFillWindingNumber) Text is fill in the area that would be painted if the path were given to CGContextFillPath. -/// Default is YES; -@property (getter=isPathFillEvenOdd) BOOL pathFillEvenOdd; - -/// Whether the text is vertical form (may used for CJK text layout). Default is NO. -@property (getter=isVerticalForm) BOOL verticalForm; - -/// Maximum number of rows, 0 means no limit. Default is 0. -@property NSUInteger maximumNumberOfRows; - -/// The line truncation type, default is none. -@property ASTextTruncationType truncationType; - -/// The truncation token. If nil, the layout will use "…" instead. Default is nil. -@property (nullable, copy) NSAttributedString *truncationToken; - -/// This modifier is applied to the lines before the layout is completed, -/// give you a chance to modify the line position. Default is nil. -@property (nullable, copy) id linePositionModifier; -@end - - -/** - The ASTextLinePositionModifier protocol declares the required method to modify - the line position in text layout progress. See `ASTextLinePositionSimpleModifier` for example. - */ -@protocol ASTextLinePositionModifier -@required -/** - This method will called before layout is completed. The method should be thread-safe. - @param lines An array of ASTextLine. - @param text The full text. - @param container The layout container. - */ -- (void)modifyLines:(NSArray *)lines fromText:(NSAttributedString *)text inContainer:(ASTextContainer *)container; -@end - - -/** - A simple implementation of `ASTextLinePositionModifier`. It can fix each line's position - to a specified value, lets each line of height be the same. - */ -@interface ASTextLinePositionSimpleModifier : NSObject -@property CGFloat fixedLineHeight; ///< The fixed line height (distance between two baseline). -@end - - - -/** - ASTextLayout class is a readonly class stores text layout result. - All the property in this class is readonly, and should not be changed. - The methods in this class is thread-safe (except some of the draw methods). - - example: (layout with a circle exclusion path) - - ┌──────────────────────────┐ <------ container - │ [--------Line0--------] │ <- Row0 - │ [--------Line1--------] │ <- Row1 - │ [-Line2-] [-Line3-] │ <- Row2 - │ [-Line4] [Line5-] │ <- Row3 - │ [-Line6-] [-Line7-] │ <- Row4 - │ [--------Line8--------] │ <- Row5 - │ [--------Line9--------] │ <- Row6 - └──────────────────────────┘ - */ -@interface ASTextLayout : NSObject - - -#pragma mark - Generate text layout -///============================================================================= -/// @name Generate text layout -///============================================================================= - -/** - Generate a layout with the given container size and text. - - @param size The text container's size - @param text The text (if nil, returns nil). - @return A new layout, or nil when an error occurs. - */ -+ (nullable ASTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text; - -/** - Generate a layout with the given container and text. - - @param container The text container (if nil, returns nil). - @param text The text (if nil, returns nil). - @return A new layout, or nil when an error occurs. - */ -+ (nullable ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text; - -/** - Generate a layout with the given container and text. - - @param container The text container (if nil, returns nil). - @param text The text (if nil, returns nil). - @param range The text range (if out of range, returns nil). If the - length of the range is 0, it means the length is no limit. - @return A new layout, or nil when an error occurs. - */ -+ (nullable ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range; - -/** - Generate layouts with the given containers and text. - - @param containers An array of ASTextContainer object (if nil, returns nil). - @param text The text (if nil, returns nil). - @return An array of ASTextLayout object (the count is same as containers), - or nil when an error occurs. - */ -+ (nullable NSArray *)layoutWithContainers:(NSArray *)containers - text:(NSAttributedString *)text; - -/** - Generate layouts with the given containers and text. - - @param containers An array of ASTextContainer object (if nil, returns nil). - @param text The text (if nil, returns nil). - @param range The text range (if out of range, returns nil). If the - length of the range is 0, it means the length is no limit. - @return An array of ASTextLayout object (the count is same as containers), - or nil when an error occurs. - */ -+ (nullable NSArray *)layoutWithContainers:(NSArray *)containers - text:(NSAttributedString *)text - range:(NSRange)range; - -- (instancetype)init UNAVAILABLE_ATTRIBUTE; -+ (instancetype)new UNAVAILABLE_ATTRIBUTE; - - -#pragma mark - Text layout attributes -///============================================================================= -/// @name Text layout attributes -///============================================================================= - -///< The text container -@property (nonatomic, readonly) ASTextContainer *container; -///< The full text -@property (nonatomic, readonly) NSAttributedString *text; -///< The text range in full text -@property (nonatomic, readonly) NSRange range; -///< CTFrame -@property (nonatomic, readonly) CTFrameRef frame; -///< Array of `ASTextLine`, no truncated -@property (nonatomic, readonly) NSArray *lines; -///< ASTextLine with truncated token, or nil -@property (nullable, nonatomic, readonly) ASTextLine *truncatedLine; -///< Array of `ASTextAttachment` -@property (nullable, nonatomic, readonly) NSArray *attachments; -///< Array of NSRange(wrapped by NSValue) in text -@property (nullable, nonatomic, readonly) NSArray *attachmentRanges; -///< Array of CGRect(wrapped by NSValue) in container -@property (nullable, nonatomic, readonly) NSArray *attachmentRects; -///< Set of Attachment (UIImage/UIView/CALayer) -@property (nullable, nonatomic, readonly) NSSet *attachmentContentsSet; -///< Number of rows -@property (nonatomic, readonly) NSUInteger rowCount; -///< Visible text range -@property (nonatomic, readonly) NSRange visibleRange; -///< Bounding rect (glyphs) -@property (nonatomic, readonly) CGRect textBoundingRect; -///< Bounding size (glyphs and insets, ceil to pixel) -@property (nonatomic, readonly) CGSize textBoundingSize; -///< Has highlight attribute -@property (nonatomic, readonly) BOOL containsHighlight; -///< Has block border attribute -@property (nonatomic, readonly) BOOL needDrawBlockBorder; -///< Has background border attribute -@property (nonatomic, readonly) BOOL needDrawBackgroundBorder; -///< Has shadow attribute -@property (nonatomic, readonly) BOOL needDrawShadow; -///< Has underline attribute -@property (nonatomic, readonly) BOOL needDrawUnderline; -///< Has visible text -@property (nonatomic, readonly) BOOL needDrawText; -///< Has attachment attribute -@property (nonatomic, readonly) BOOL needDrawAttachment; -///< Has inner shadow attribute -@property (nonatomic, readonly) BOOL needDrawInnerShadow; -///< Has strickthrough attribute -@property (nonatomic, readonly) BOOL needDrawStrikethrough; -///< Has border attribute -@property (nonatomic, readonly) BOOL needDrawBorder; - - -#pragma mark - Query information from text layout -///============================================================================= -/// @name Query information from text layout -///============================================================================= - -/** - The first line index for row. - - @param row A row index. - @return The line index, or NSNotFound if not found. - */ -- (NSUInteger)lineIndexForRow:(NSUInteger)row; - -/** - The number of lines for row. - - @param row A row index. - @return The number of lines, or NSNotFound when an error occurs. - */ -- (NSUInteger)lineCountForRow:(NSUInteger)row; - -/** - The row index for line. - - @param line A row index. - - @return The row index, or NSNotFound if not found. - */ -- (NSUInteger)rowIndexForLine:(NSUInteger)line; - -/** - The line index for a specified point. - - @discussion It returns NSNotFound if there's no text at the point. - - @param point A point in the container. - @return The line index, or NSNotFound if not found. - */ -- (NSUInteger)lineIndexForPoint:(CGPoint)point; - -/** - The line index closest to a specified point. - - @param point A point in the container. - @return The line index, or NSNotFound if no line exist in layout. - */ -- (NSUInteger)closestLineIndexForPoint:(CGPoint)point; - -/** - The offset in container for a text position in a specified line. - - @discussion The offset is the text position's baseline point.x. - If the container is vertical form, the offset is the baseline point.y; - - @param position The text position in string. - @param lineIndex The line index. - @return The offset in container, or CGFLOAT_MAX if not found. - */ -- (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex; - -/** - The text position for a point in a specified line. - - @discussion This method just call CTLineGetStringIndexForPosition() and does - NOT consider the emoji, line break character, binding text... - - @param point A point in the container. - @param lineIndex The line index. - @return The text position, or NSNotFound if not found. - */ -- (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex; - -/** - The closest text position to a specified point. - - @discussion This method takes into account the restrict of emoji, line break - character, binding text and text affinity. - - @param point A point in the container. - @return A text position, or nil if not found. - */ -- (nullable ASTextPosition *)closestPositionToPoint:(CGPoint)point; - -/** - Returns the new position when moving selection grabber in text view. - - @discussion There are two grabber in the text selection period, user can only - move one grabber at the same time. - - @param point A point in the container. - @param oldPosition The old text position for the moving grabber. - @param otherPosition The other position in text selection view. - - @return A text position, or nil if not found. - */ -- (nullable ASTextPosition *)positionForPoint:(CGPoint)point - oldPosition:(ASTextPosition *)oldPosition - otherPosition:(ASTextPosition *)otherPosition; - -/** - Returns the character or range of characters that is at a given point in the container. - If there is no text at the point, returns nil. - - @discussion This method takes into account the restrict of emoji, line break - character, binding text and text affinity. - - @param point A point in the container. - @return An object representing a range that encloses a character (or characters) - at point. Or nil if not found. - */ -- (nullable ASTextRange *)textRangeAtPoint:(CGPoint)point; - -/** - Returns the closest character or range of characters that is at a given point in - the container. - - @discussion This method takes into account the restrict of emoji, line break - character, binding text and text affinity. - - @param point A point in the container. - @return An object representing a range that encloses a character (or characters) - at point. Or nil if not found. - */ -- (nullable ASTextRange *)closestTextRangeAtPoint:(CGPoint)point; - -/** - If the position is inside an emoji, composed character sequences, line break '\\r\\n' - or custom binding range, then returns the range by extend the position. Otherwise, - returns a zero length range from the position. - - @param position A text-position object that identifies a location in layout. - - @return A text-range object that extend the position. Or nil if an error occurs - */ -- (nullable ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position; - -/** - Returns a text range at a given offset in a specified direction from another - text position to its farthest extent in a certain direction of layout. - - @param position A text-position object that identifies a location in layout. - @param direction A constant that indicates a direction of layout (right, left, up, down). - @param offset A character offset from position. - - @return A text-range object that represents the distance from position to the - farthest extent in direction. Or nil if an error occurs. - */ -- (nullable ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position - inDirection:(UITextLayoutDirection)direction - offset:(NSInteger)offset; - -/** - Returns the line index for a given text position. - - @discussion This method takes into account the text affinity. - - @param position A text-position object that identifies a location in layout. - @return The line index, or NSNotFound if not found. - */ -- (NSUInteger)lineIndexForPosition:(ASTextPosition *)position; - -/** - Returns the baseline position for a given text position. - - @param position An object that identifies a location in the layout. - @return The baseline position for text, or CGPointZero if not found. - */ -- (CGPoint)linePositionForPosition:(ASTextPosition *)position; - -/** - Returns a rectangle used to draw the caret at a given insertion point. - - @param position An object that identifies a location in the layout. - @return A rectangle that defines the area for drawing the caret. The width is - always zero in normal container, the height is always zero in vertical form container. - If not found, it returns CGRectNull. - */ -- (CGRect)caretRectForPosition:(ASTextPosition *)position; - -/** - Returns the first rectangle that encloses a range of text in the layout. - - @param range An object that represents a range of text in layout. - - @return The first rectangle in a range of text. You might use this rectangle to - draw a correction rectangle. The "first" in the name refers the rectangle - enclosing the first line when the range encompasses multiple lines of text. - If not found, it returns CGRectNull. - */ -- (CGRect)firstRectForRange:(ASTextRange *)range; - -/** - Returns the rectangle union that encloses a range of text in the layout. - - @param range An object that represents a range of text in layout. - - @return A rectangle that defines the area than encloses the range. - If not found, it returns CGRectNull. - */ -- (CGRect)rectForRange:(ASTextRange *)range; - -/** - Returns an array of selection rects corresponding to the range of text. - The start and end rect can be used to show grabber. - - @param range An object representing a range in text. - @return An array of `ASTextSelectionRect` objects that encompass the selection. - If not found, the array is empty. - */ -- (NSArray *)selectionRectsForRange:(ASTextRange *)range; - -/** - Returns an array of selection rects corresponding to the range of text. - - @param range An object representing a range in text. - @return An array of `ASTextSelectionRect` objects that encompass the selection. - If not found, the array is empty. - */ -- (NSArray *)selectionRectsWithoutStartAndEndForRange:(ASTextRange *)range; - -/** - Returns the start and end selection rects corresponding to the range of text. - The start and end rect can be used to show grabber. - - @param range An object representing a range in text. - @return An array of `ASTextSelectionRect` objects contains the start and end to - the selection. If not found, the array is empty. - */ -- (NSArray *)selectionRectsWithOnlyStartAndEndForRange:(ASTextRange *)range; - - -#pragma mark - Draw text layout -///============================================================================= -/// @name Draw text layout -///============================================================================= - -/** - Draw the layout and show the attachments. - - @discussion If the `view` parameter is not nil, then the attachment views will - add to this `view`, and if the `layer` parameter is not nil, then the attachment - layers will add to this `layer`. - - @warning This method should be called on main thread if `view` or `layer` parameter - is not nil and there's UIView or CALayer attachments in layout. - Otherwise, it can be called on any thread. - - @param context The draw context. Pass nil to avoid text and image drawing. - @param size The context size. - @param point The point at which to draw the layout. - @param view The attachment views will add to this view. - @param layer The attachment layers will add to this layer. - @param debug The debug option. Pass nil to avoid debug drawing. - @param cancel The cancel checker block. It will be called in drawing progress. - If it returns YES, the further draw progress will be canceled. - Pass nil to ignore this feature. - */ -- (void)drawInContext:(nullable CGContextRef)context - size:(CGSize)size - point:(CGPoint)point - view:(nullable UIView *)view - layer:(nullable CALayer *)layer - debug:(nullable ASTextDebugOption *)debug - cancel:(nullable BOOL (^)(void))cancel; - -/** - Draw the layout text and image (without view or layer attachments). - - @discussion This method is thread safe and can be called on any thread. - - @param context The draw context. Pass nil to avoid text and image drawing. - @param size The context size. - @param debug The debug option. Pass nil to avoid debug drawing. - */ -- (void)drawInContext:(nullable CGContextRef)context - size:(CGSize)size - debug:(nullable ASTextDebugOption *)debug; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/TextExperiment/Component/ASTextLayout.mm b/Source/TextExperiment/Component/ASTextLayout.mm deleted file mode 100644 index 1567322ba9..0000000000 --- a/Source/TextExperiment/Component/ASTextLayout.mm +++ /dev/null @@ -1,3483 +0,0 @@ -// -// ASTextLayout.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import -#import -#import - -const CGSize ASTextContainerMaxSize = (CGSize){0x100000, 0x100000}; - -typedef struct { - CGFloat head; - CGFloat foot; -} ASRowEdge; - -static inline CGSize ASTextClipCGSize(CGSize size) { - if (size.width > ASTextContainerMaxSize.width) size.width = ASTextContainerMaxSize.width; - if (size.height > ASTextContainerMaxSize.height) size.height = ASTextContainerMaxSize.height; - return size; -} - -static inline UIEdgeInsets UIEdgeInsetRotateVertical(UIEdgeInsets insets) { - UIEdgeInsets one; - one.top = insets.left; - one.left = insets.bottom; - one.bottom = insets.right; - one.right = insets.top; - return one; -} - -/** - Sometimes CoreText may convert CGColor to UIColor for `kCTForegroundColorAttributeName` - attribute in iOS7. This should be a bug of CoreText, and may cause crash. Here's a workaround. - */ -static CGColorRef ASTextGetCGColor(CGColorRef color) { - static UIColor *defaultColor; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - defaultColor = [UIColor blackColor]; - }); - if (!color) return defaultColor.CGColor; - if ([((__bridge NSObject *)color) respondsToSelector:@selector(CGColor)]) { - return ((__bridge UIColor *)color).CGColor; - } - return color; -} - -@implementation ASTextLinePositionSimpleModifier -- (void)modifyLines:(NSArray *)lines fromText:(NSAttributedString *)text inContainer:(ASTextContainer *)container { - if (container.verticalForm) { - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - CGPoint pos = line.position; - pos.x = container.size.width - container.insets.right - line.row * _fixedLineHeight - _fixedLineHeight * 0.9; - line.position = pos; - } - } else { - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - CGPoint pos = line.position; - pos.y = line.row * _fixedLineHeight + _fixedLineHeight * 0.9 + container.insets.top; - line.position = pos; - } - } -} - -- (id)copyWithZone:(NSZone *)zone { - ASTextLinePositionSimpleModifier *one = [self.class new]; - one.fixedLineHeight = _fixedLineHeight; - return one; -} -@end - - -@implementation ASTextContainer { - @package - BOOL _readonly; ///< used only in ASTextLayout.implementation - dispatch_semaphore_t _lock; - - CGSize _size; - UIEdgeInsets _insets; - UIBezierPath *_path; - NSArray *_exclusionPaths; - BOOL _pathFillEvenOdd; - CGFloat _pathLineWidth; - BOOL _verticalForm; - NSUInteger _maximumNumberOfRows; - ASTextTruncationType _truncationType; - NSAttributedString *_truncationToken; - id _linePositionModifier; -} - -- (NSString *)description -{ - return [NSString - stringWithFormat:@"immutable: %@, insets: %@, size: %@", self->_readonly ? @"YES" : @"NO", - NSStringFromUIEdgeInsets(self->_insets), NSStringFromCGSize(self->_size)]; -} - -+ (instancetype)containerWithSize:(CGSize)size NS_RETURNS_RETAINED { - return [self containerWithSize:size insets:UIEdgeInsetsZero]; -} - -+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets NS_RETURNS_RETAINED { - ASTextContainer *one = [self new]; - one.size = ASTextClipCGSize(size); - one.insets = insets; - return one; -} - -+ (instancetype)containerWithPath:(UIBezierPath *)path NS_RETURNS_RETAINED { - ASTextContainer *one = [self new]; - one.path = path; - return one; -} - -- (instancetype)init { - self = [super init]; - if (!self) return nil; - _lock = dispatch_semaphore_create(1); - _pathFillEvenOdd = YES; - return self; -} - -- (id)copyForced:(BOOL)forceCopy -{ - dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); - if (_readonly && !forceCopy) { - dispatch_semaphore_signal(_lock); - return self; - } - - ASTextContainer *one = [self.class new]; - one->_size = _size; - one->_insets = _insets; - one->_path = _path; - one->_exclusionPaths = [_exclusionPaths copy]; - one->_pathFillEvenOdd = _pathFillEvenOdd; - one->_pathLineWidth = _pathLineWidth; - one->_verticalForm = _verticalForm; - one->_maximumNumberOfRows = _maximumNumberOfRows; - one->_truncationType = _truncationType; - one->_truncationToken = [_truncationToken copy]; - one->_linePositionModifier = [(NSObject *)_linePositionModifier copy]; - dispatch_semaphore_signal(_lock); - return one; -} - -- (id)copyWithZone:(NSZone *)zone { - return [self copyForced:NO]; -} - -- (id)mutableCopyWithZone:(NSZone *)zone { - return [self copyForced:YES]; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:[NSValue valueWithCGSize:_size] forKey:@"size"]; - [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:_insets] forKey:@"insets"]; - [aCoder encodeObject:_path forKey:@"path"]; - [aCoder encodeObject:_exclusionPaths forKey:@"exclusionPaths"]; - [aCoder encodeBool:_pathFillEvenOdd forKey:@"pathFillEvenOdd"]; - [aCoder encodeDouble:_pathLineWidth forKey:@"pathLineWidth"]; - [aCoder encodeBool:_verticalForm forKey:@"verticalForm"]; - [aCoder encodeInteger:_maximumNumberOfRows forKey:@"maximumNumberOfRows"]; - [aCoder encodeInteger:_truncationType forKey:@"truncationType"]; - [aCoder encodeObject:_truncationToken forKey:@"truncationToken"]; - if ([_linePositionModifier respondsToSelector:@selector(encodeWithCoder:)] && - [_linePositionModifier respondsToSelector:@selector(initWithCoder:)]) { - [aCoder encodeObject:_linePositionModifier forKey:@"linePositionModifier"]; - } -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [self init]; - _size = ((NSValue *)[aDecoder decodeObjectForKey:@"size"]).CGSizeValue; - _insets = ((NSValue *)[aDecoder decodeObjectForKey:@"insets"]).UIEdgeInsetsValue; - _path = [aDecoder decodeObjectForKey:@"path"]; - _exclusionPaths = [aDecoder decodeObjectForKey:@"exclusionPaths"]; - _pathFillEvenOdd = [aDecoder decodeBoolForKey:@"pathFillEvenOdd"]; - _pathLineWidth = [aDecoder decodeDoubleForKey:@"pathLineWidth"]; - _verticalForm = [aDecoder decodeBoolForKey:@"verticalForm"]; - _maximumNumberOfRows = [aDecoder decodeIntegerForKey:@"maximumNumberOfRows"]; - _truncationType = (ASTextTruncationType)[aDecoder decodeIntegerForKey:@"truncationType"]; - _truncationToken = [aDecoder decodeObjectForKey:@"truncationToken"]; - _linePositionModifier = [aDecoder decodeObjectForKey:@"linePositionModifier"]; - return self; -} - -- (void)makeImmutable -{ - dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); - _readonly = YES; - dispatch_semaphore_signal(_lock); -} - -#define Getter(...) \ -dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ -__VA_ARGS__; \ -dispatch_semaphore_signal(_lock); - -#define Setter(...) \ -dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \ -if (__builtin_expect(_readonly, NO)) { \ - ASDisplayNodeFailAssert(@"Attempt to modify immutable text container."); \ - dispatch_semaphore_signal(_lock); \ - return; \ -} \ -__VA_ARGS__; \ -dispatch_semaphore_signal(_lock); - -- (CGSize)size { - Getter(CGSize size = _size) return size; -} - -- (void)setSize:(CGSize)size { - Setter(if(!_path) _size = ASTextClipCGSize(size)); -} - -- (UIEdgeInsets)insets { - Getter(UIEdgeInsets insets = _insets) return insets; -} - -- (void)setInsets:(UIEdgeInsets)insets { - Setter(if(!_path){ - if (insets.top < 0) insets.top = 0; - if (insets.left < 0) insets.left = 0; - if (insets.bottom < 0) insets.bottom = 0; - if (insets.right < 0) insets.right = 0; - _insets = insets; - }); -} - -- (UIBezierPath *)path { - Getter(UIBezierPath *path = _path) return path; -} - -- (void)setPath:(UIBezierPath *)path { - Setter( - _path = path.copy; - if (_path) { - CGRect bounds = _path.bounds; - CGSize size = bounds.size; - UIEdgeInsets insets = UIEdgeInsetsZero; - if (bounds.origin.x < 0) size.width += bounds.origin.x; - if (bounds.origin.x > 0) insets.left = bounds.origin.x; - if (bounds.origin.y < 0) size.height += bounds.origin.y; - if (bounds.origin.y > 0) insets.top = bounds.origin.y; - _size = size; - _insets = insets; - } - ); -} - -- (NSArray *)exclusionPaths { - Getter(NSArray *paths = _exclusionPaths) return paths; -} - -- (void)setExclusionPaths:(NSArray *)exclusionPaths { - Setter(_exclusionPaths = exclusionPaths.copy); -} - -- (BOOL)isPathFillEvenOdd { - Getter(BOOL is = _pathFillEvenOdd) return is; -} - -- (void)setPathFillEvenOdd:(BOOL)pathFillEvenOdd { - Setter(_pathFillEvenOdd = pathFillEvenOdd); -} - -- (CGFloat)pathLineWidth { - Getter(CGFloat width = _pathLineWidth) return width; -} - -- (void)setPathLineWidth:(CGFloat)pathLineWidth { - Setter(_pathLineWidth = pathLineWidth); -} - -- (BOOL)isVerticalForm { - Getter(BOOL v = _verticalForm) return v; -} - -- (void)setVerticalForm:(BOOL)verticalForm { - Setter(_verticalForm = verticalForm); -} - -- (NSUInteger)maximumNumberOfRows { - Getter(NSUInteger num = _maximumNumberOfRows) return num; -} - -- (void)setMaximumNumberOfRows:(NSUInteger)maximumNumberOfRows { - Setter(_maximumNumberOfRows = maximumNumberOfRows); -} - -- (ASTextTruncationType)truncationType { - Getter(ASTextTruncationType type = _truncationType) return type; -} - -- (void)setTruncationType:(ASTextTruncationType)truncationType { - Setter(_truncationType = truncationType); -} - -- (NSAttributedString *)truncationToken { - Getter(NSAttributedString *token = _truncationToken) return token; -} - -- (void)setTruncationToken:(NSAttributedString *)truncationToken { - Setter(_truncationToken = truncationToken.copy); -} - -- (void)setLinePositionModifier:(id)linePositionModifier { - Setter(_linePositionModifier = [(NSObject *)linePositionModifier copy]); -} - -- (id)linePositionModifier { - Getter(id m = _linePositionModifier) return m; -} - -#undef Getter -#undef Setter -@end - - - - -@interface ASTextLayout () - -@property (nonatomic) ASTextContainer *container; -@property (nonatomic) NSAttributedString *text; -@property (nonatomic) NSRange range; - -@property (nonatomic) CTFrameRef frame; -@property (nonatomic) NSArray *lines; -@property (nonatomic) ASTextLine *truncatedLine; -@property (nonatomic) NSArray *attachments; -@property (nonatomic) NSArray *attachmentRanges; -@property (nonatomic) NSArray *attachmentRects; -@property (nonatomic) NSSet *attachmentContentsSet; -@property (nonatomic) NSUInteger rowCount; -@property (nonatomic) NSRange visibleRange; -@property (nonatomic) CGRect textBoundingRect; -@property (nonatomic) CGSize textBoundingSize; - -@property (nonatomic) BOOL containsHighlight; -@property (nonatomic) BOOL needDrawBlockBorder; -@property (nonatomic) BOOL needDrawBackgroundBorder; -@property (nonatomic) BOOL needDrawShadow; -@property (nonatomic) BOOL needDrawUnderline; -@property (nonatomic) BOOL needDrawText; -@property (nonatomic) BOOL needDrawAttachment; -@property (nonatomic) BOOL needDrawInnerShadow; -@property (nonatomic) BOOL needDrawStrikethrough; -@property (nonatomic) BOOL needDrawBorder; - -@property (nonatomic) NSUInteger *lineRowsIndex; -@property (nonatomic) ASRowEdge *lineRowsEdge; ///< top-left origin - -@end - - - -@implementation ASTextLayout - -#pragma mark - Layout - -- (instancetype)_init { - self = [super init]; - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"lines: %ld, visibleRange:%@, textBoundingRect:%@", - (long)[self.lines count], - NSStringFromRange(self.visibleRange), - NSStringFromCGRect(self.textBoundingRect)]; -} - -+ (ASTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text { - ASTextContainer *container = [ASTextContainer containerWithSize:size]; - return [self layoutWithContainer:container text:text]; -} - -+ (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text { - return [self layoutWithContainer:container text:text range:NSMakeRange(0, text.length)]; -} - -+ (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range { - ASTextLayout *layout = NULL; - CGPathRef cgPath = nil; - CGRect cgPathBox = {0}; - BOOL isVerticalForm = NO; - BOOL rowMaySeparated = NO; - NSMutableDictionary *frameAttrs = nil; - CTFramesetterRef ctSetter = NULL; - CTFrameRef ctFrame = NULL; - CFArrayRef ctLines = nil; - CGPoint *lineOrigins = NULL; - NSUInteger lineCount = 0; - NSMutableArray *lines = nil; - NSMutableArray *attachments = nil; - NSMutableArray *attachmentRanges = nil; - NSMutableArray *attachmentRects = nil; - NSMutableSet *attachmentContentsSet = nil; - BOOL needTruncation = NO; - NSAttributedString *truncationToken = nil; - ASTextLine *truncatedLine = nil; - ASRowEdge *lineRowsEdge = NULL; - NSUInteger *lineRowsIndex = NULL; - NSRange visibleRange; - NSUInteger maximumNumberOfRows = 0; - BOOL constraintSizeIsExtended = NO; - CGRect constraintRectBeforeExtended = {0}; -#define FAIL_AND_RETURN {\ - if (cgPath) CFRelease(cgPath); \ - if (ctSetter) CFRelease(ctSetter); \ - if (ctFrame) CFRelease(ctFrame); \ - if (lineOrigins) free(lineOrigins); \ - if (lineRowsEdge) free(lineRowsEdge); \ - if (lineRowsIndex) free(lineRowsIndex); \ - return nil; } - - container = [container copy]; - if (!text || !container) return nil; - if (range.location + range.length > text.length) return nil; - [container makeImmutable]; - maximumNumberOfRows = container.maximumNumberOfRows; - - // It may use larger constraint size when create CTFrame with - // CTFramesetterCreateFrame in iOS 10. - BOOL needFixLayoutSizeBug = AS_AT_LEAST_IOS10; - - layout = [[ASTextLayout alloc] _init]; - layout.text = text; - layout.container = container; - layout.range = range; - isVerticalForm = container.verticalForm; - - // set cgPath and cgPathBox - if (container.path == nil && container.exclusionPaths.count == 0) { - if (container.size.width <= 0 || container.size.height <= 0) FAIL_AND_RETURN - CGRect rect = (CGRect) {CGPointZero, container.size }; - if (needFixLayoutSizeBug) { - constraintSizeIsExtended = YES; - constraintRectBeforeExtended = UIEdgeInsetsInsetRect(rect, container.insets); - constraintRectBeforeExtended = CGRectStandardize(constraintRectBeforeExtended); - if (container.isVerticalForm) { - rect.size.width = ASTextContainerMaxSize.width; - } else { - rect.size.height = ASTextContainerMaxSize.height; - } - } - rect = UIEdgeInsetsInsetRect(rect, container.insets); - rect = CGRectStandardize(rect); - cgPathBox = rect; - rect = CGRectApplyAffineTransform(rect, CGAffineTransformMakeScale(1, -1)); - cgPath = CGPathCreateWithRect(rect, NULL); // let CGPathIsRect() returns true - } else if (container.path && CGPathIsRect(container.path.CGPath, &cgPathBox) && container.exclusionPaths.count == 0) { - CGRect rect = CGRectApplyAffineTransform(cgPathBox, CGAffineTransformMakeScale(1, -1)); - cgPath = CGPathCreateWithRect(rect, NULL); // let CGPathIsRect() returns true - } else { - rowMaySeparated = YES; - CGMutablePathRef path = NULL; - if (container.path) { - path = CGPathCreateMutableCopy(container.path.CGPath); - } else { - CGRect rect = (CGRect) {CGPointZero, container.size }; - rect = UIEdgeInsetsInsetRect(rect, container.insets); - CGPathRef rectPath = CGPathCreateWithRect(rect, NULL); - if (rectPath) { - path = CGPathCreateMutableCopy(rectPath); - CGPathRelease(rectPath); - } - } - if (path) { - [layout.container.exclusionPaths enumerateObjectsUsingBlock: ^(UIBezierPath *onePath, NSUInteger idx, BOOL *stop) { - CGPathAddPath(path, NULL, onePath.CGPath); - }]; - - cgPathBox = CGPathGetPathBoundingBox(path); - CGAffineTransform trans = CGAffineTransformMakeScale(1, -1); - CGMutablePathRef transPath = CGPathCreateMutableCopyByTransformingPath(path, &trans); - CGPathRelease(path); - path = transPath; - } - cgPath = path; - } - if (!cgPath) FAIL_AND_RETURN - - // frame setter config - frameAttrs = [[NSMutableDictionary alloc] init]; - if (container.isPathFillEvenOdd == NO) { - frameAttrs[(id)kCTFramePathFillRuleAttributeName] = @(kCTFramePathFillWindingNumber); - } - if (container.pathLineWidth > 0) { - frameAttrs[(id)kCTFramePathWidthAttributeName] = @(container.pathLineWidth); - } - if (container.isVerticalForm == YES) { - frameAttrs[(id)kCTFrameProgressionAttributeName] = @(kCTFrameProgressionRightToLeft); - } - - /* - * Framesetter cache. - * Framesetters can only be used by one thread at a time. - * Create a CFSet with no callbacks (raw pointers) to keep track of which - * framesetters are in use on other threads. If the one for our string is already in use, - * just create a new one. This should be pretty rare. - */ - static pthread_mutex_t busyFramesettersLock = PTHREAD_MUTEX_INITIALIZER; - static NSCache *framesetterCache; - static CFMutableSetRef busyFramesetters; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - if (ASActivateExperimentalFeature(ASExperimentalFramesetterCache)) { - framesetterCache = [[NSCache alloc] init]; - framesetterCache.name = @"org.TextureGroup.Texture.framesetterCache"; - busyFramesetters = CFSetCreateMutable(NULL, 0, NULL); - } - }); - - BOOL haveCached = NO, useCached = NO; - if (framesetterCache) { - // Check if there's one in the cache. - ctSetter = (__bridge_retained CTFramesetterRef)[framesetterCache objectForKey:text]; - - if (ctSetter) { - haveCached = YES; - - // Check-and-set busy on the cached one. - pthread_mutex_lock(&busyFramesettersLock); - BOOL busy = CFSetContainsValue(busyFramesetters, ctSetter); - if (!busy) { - CFSetAddValue(busyFramesetters, ctSetter); - useCached = YES; - } - pthread_mutex_unlock(&busyFramesettersLock); - - // Release if it was busy. - if (busy) { - CFRelease(ctSetter); - ctSetter = NULL; - } - } - } - - // Create a framesetter if needed. - if (!ctSetter) { - ctSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)text); - } - - if (!ctSetter) FAIL_AND_RETURN - ctFrame = CTFramesetterCreateFrame(ctSetter, ASTextCFRangeFromNSRange(range), cgPath, (CFDictionaryRef)frameAttrs); - - // Return to cache. - if (framesetterCache) { - if (useCached) { - // If reused: mark available. - pthread_mutex_lock(&busyFramesettersLock); - CFSetRemoveValue(busyFramesetters, ctSetter); - pthread_mutex_unlock(&busyFramesettersLock); - } else if (!haveCached) { - // If first framesetter, add to cache. - [framesetterCache setObject:(__bridge id)ctSetter forKey:text]; - } - } - - if (!ctFrame) FAIL_AND_RETURN - lines = [NSMutableArray new]; - ctLines = CTFrameGetLines(ctFrame); - lineCount = CFArrayGetCount(ctLines); - if (lineCount > 0) { - lineOrigins = (CGPoint *)malloc(lineCount * sizeof(CGPoint)); - if (lineOrigins == NULL) FAIL_AND_RETURN - CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, lineCount), lineOrigins); - } - - CGRect textBoundingRect = CGRectZero; - CGSize textBoundingSize = CGSizeZero; - NSInteger rowIdx = -1; - NSUInteger rowCount = 0; - CGRect lastRect = CGRectMake(0, -FLT_MAX, 0, 0); - CGPoint lastPosition = CGPointMake(0, -FLT_MAX); - if (isVerticalForm) { - lastRect = CGRectMake(FLT_MAX, 0, 0, 0); - lastPosition = CGPointMake(FLT_MAX, 0); - } - - // calculate line frame - NSUInteger lineCurrentIdx = 0; - BOOL measuringBeyondConstraints = NO; - for (NSUInteger i = 0; i < lineCount; i++) { - CTLineRef ctLine = (CTLineRef)CFArrayGetValueAtIndex(ctLines, i); - CFArrayRef ctRuns = CTLineGetGlyphRuns(ctLine); - if (!ctRuns || CFArrayGetCount(ctRuns) == 0) continue; - - // CoreText coordinate system - CGPoint ctLineOrigin = lineOrigins[i]; - - // UIKit coordinate system - CGPoint position; - position.x = cgPathBox.origin.x + ctLineOrigin.x; - position.y = cgPathBox.size.height + cgPathBox.origin.y - ctLineOrigin.y; - - ASTextLine *line = [ASTextLine lineWithCTLine:ctLine position:position vertical:isVerticalForm]; - - [lines addObject:line]; - } - - // Give user a chance to modify the line's position. - [container.linePositionModifier modifyLines:lines fromText:text inContainer:container]; - - BOOL first = YES; - for (ASTextLine *line in lines) { - CGPoint position = line.position; - CGRect rect = line.bounds; - if (constraintSizeIsExtended) { - if (isVerticalForm) { - if (rect.origin.x + rect.size.width > - constraintRectBeforeExtended.origin.x + - constraintRectBeforeExtended.size.width) { - measuringBeyondConstraints = YES; - } - } else { - if (rect.origin.y + rect.size.height > - constraintRectBeforeExtended.origin.y + - constraintRectBeforeExtended.size.height) { - measuringBeyondConstraints = YES; - } - } - } - - BOOL newRow = !measuringBeyondConstraints; - if (newRow && rowMaySeparated && position.x != lastPosition.x) { - if (isVerticalForm) { - if (rect.size.width > lastRect.size.width) { - if (rect.origin.x > lastPosition.x && lastPosition.x > rect.origin.x - rect.size.width) newRow = NO; - } else { - if (lastRect.origin.x > position.x && position.x > lastRect.origin.x - lastRect.size.width) newRow = NO; - } - } else { - if (rect.size.height > lastRect.size.height) { - if (rect.origin.y < lastPosition.y && lastPosition.y < rect.origin.y + rect.size.height) newRow = NO; - } else { - if (lastRect.origin.y < position.y && position.y < lastRect.origin.y + lastRect.size.height) newRow = NO; - } - } - } - - if (newRow) rowIdx++; - lastRect = rect; - lastPosition = position; - - line.index = lineCurrentIdx; - line.row = rowIdx; - - rowCount = rowIdx + 1; - lineCurrentIdx ++; - - if (first) { - first = NO; - textBoundingRect = rect; - } else if (!measuringBeyondConstraints) { - if (maximumNumberOfRows == 0 || rowIdx < maximumNumberOfRows) { - textBoundingRect = CGRectUnion(textBoundingRect, rect); - } - } - } - - { - NSMutableArray *removedLines = [NSMutableArray new]; - if (rowCount > 0) { - if (maximumNumberOfRows > 0) { - if (rowCount > maximumNumberOfRows) { - needTruncation = YES; - rowCount = maximumNumberOfRows; - do { - ASTextLine *line = lines.lastObject; - if (!line) break; - if (line.row < rowCount) break; // we have removed down to an allowed # of lines now - [lines removeLastObject]; - [removedLines addObject:line]; - } while (1); - } - } - ASTextLine *lastLine = rowCount < lines.count ? lines[rowCount - 1] : lines.lastObject; - if (!needTruncation && lastLine.range.location + lastLine.range.length < text.length) { - needTruncation = YES; - while (lines.count > rowCount) { - ASTextLine *line = lines.lastObject; - [lines removeLastObject]; - [removedLines addObject:line]; - } - } - - lineRowsEdge = (ASRowEdge *) calloc(rowCount, sizeof(ASRowEdge)); - if (lineRowsEdge == NULL) FAIL_AND_RETURN - lineRowsIndex = (NSUInteger *) calloc(rowCount, sizeof(NSUInteger)); - if (lineRowsIndex == NULL) FAIL_AND_RETURN - NSInteger lastRowIdx = -1; - CGFloat lastHead = 0; - CGFloat lastFoot = 0; - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - CGRect rect = line.bounds; - if ((NSInteger) line.row != lastRowIdx) { - if (lastRowIdx >= 0) { - lineRowsEdge[lastRowIdx] = (ASRowEdge) {.head = lastHead, .foot = lastFoot}; - } - lastRowIdx = line.row; - lineRowsIndex[lastRowIdx] = i; - if (isVerticalForm) { - lastHead = rect.origin.x + rect.size.width; - lastFoot = lastHead - rect.size.width; - } else { - lastHead = rect.origin.y; - lastFoot = lastHead + rect.size.height; - } - } else { - if (isVerticalForm) { - lastHead = MAX(lastHead, rect.origin.x + rect.size.width); - lastFoot = MIN(lastFoot, rect.origin.x); - } else { - lastHead = MIN(lastHead, rect.origin.y); - lastFoot = MAX(lastFoot, rect.origin.y + rect.size.height); - } - } - } - lineRowsEdge[lastRowIdx] = (ASRowEdge) {.head = lastHead, .foot = lastFoot}; - - for (NSUInteger i = 1; i < rowCount; i++) { - ASRowEdge v0 = lineRowsEdge[i - 1]; - ASRowEdge v1 = lineRowsEdge[i]; - lineRowsEdge[i - 1].foot = lineRowsEdge[i].head = (v0.foot + v1.head) * 0.5; - } - } - - { // calculate bounding size - CGRect rect = textBoundingRect; - if (container.path) { - if (container.pathLineWidth > 0) { - CGFloat inset = container.pathLineWidth / 2; - rect = CGRectInset(rect, -inset, -inset); - } - } else { - rect = UIEdgeInsetsInsetRect(rect, ASTextUIEdgeInsetsInvert(container.insets)); - } - rect = CGRectStandardize(rect); - CGSize size = rect.size; - if (container.verticalForm) { - size.width += container.size.width - (rect.origin.x + rect.size.width); - } else { - size.width += rect.origin.x; - } - size.height += rect.origin.y; - if (size.width < 0) size.width = 0; - if (size.height < 0) size.height = 0; - size.width = ceil(size.width); - size.height = ceil(size.height); - textBoundingSize = size; - } - - visibleRange = ASTextNSRangeFromCFRange(CTFrameGetVisibleStringRange(ctFrame)); - if (needTruncation) { - ASTextLine *lastLine = lines.lastObject; - NSRange lastRange = lastLine.range; - visibleRange.length = lastRange.location + lastRange.length - visibleRange.location; - - // create truncated line - if (container.truncationType != ASTextTruncationTypeNone) { - CTLineRef truncationTokenLine = NULL; - if (container.truncationToken) { - truncationToken = container.truncationToken; - truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef) truncationToken); - } else { - CFArrayRef runs = CTLineGetGlyphRuns(lastLine.CTLine); - NSUInteger runCount = CFArrayGetCount(runs); - NSMutableDictionary *attrs = nil; - if (runCount > 0) { - CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex(runs, runCount - 1); - attrs = (id) CTRunGetAttributes(run); - attrs = attrs ? attrs.mutableCopy : [NSMutableArray new]; - [attrs removeObjectsForKeys:[NSMutableAttributedString as_allDiscontinuousAttributeKeys]]; - CTFontRef font = (__bridge CTFontRef) attrs[(id) kCTFontAttributeName]; - CGFloat fontSize = font ? CTFontGetSize(font) : 12.0; - UIFont *uiFont = [UIFont systemFontOfSize:fontSize * 0.9]; - if (uiFont) { - font = CTFontCreateWithName((__bridge CFStringRef) uiFont.fontName, uiFont.pointSize, NULL); - } else { - font = NULL; - } - if (font) { - attrs[(id) kCTFontAttributeName] = (__bridge id) (font); - uiFont = nil; - CFRelease(font); - } - CGColorRef color = (__bridge CGColorRef) (attrs[(id) kCTForegroundColorAttributeName]); - if (color && CFGetTypeID(color) == CGColorGetTypeID() && CGColorGetAlpha(color) == 0) { - // ignore clear color - [attrs removeObjectForKey:(id) kCTForegroundColorAttributeName]; - } - if (!attrs) attrs = [NSMutableDictionary new]; - } - truncationToken = [[NSAttributedString alloc] initWithString:ASTextTruncationToken attributes:attrs]; - truncationTokenLine = CTLineCreateWithAttributedString((CFAttributedStringRef) truncationToken); - } - if (truncationTokenLine) { - CTLineTruncationType type = kCTLineTruncationEnd; - if (container.truncationType == ASTextTruncationTypeStart) { - type = kCTLineTruncationStart; - } else if (container.truncationType == ASTextTruncationTypeMiddle) { - type = kCTLineTruncationMiddle; - } - NSMutableAttributedString *lastLineText = [text attributedSubstringFromRange:lastLine.range].mutableCopy; - CGFloat truncatedWidth = lastLine.width; - CGFloat atLeastOneLine = lastLine.width; - CGRect cgPathRect = CGRectZero; - if (CGPathIsRect(cgPath, &cgPathRect)) { - if (isVerticalForm) { - truncatedWidth = cgPathRect.size.height; - } else { - truncatedWidth = cgPathRect.size.width; - } - } - int i = 0; - if (type != kCTLineTruncationStart) { // Middle or End/Tail wants to collect some text (at least one line's - // worth) preceding the truncated content, with which to construct a "truncated line". - i = (int)removedLines.count - 1; - while (atLeastOneLine < truncatedWidth && i >= 0) { - if (lastLineText.length > 0 && [lastLineText.string characterAtIndex:lastLineText.string.length - 1] == '\n') { // Explicit newlines are always "long enough". - [lastLineText deleteCharactersInRange:NSMakeRange(lastLineText.string.length - 1, 1)]; - break; - } - [lastLineText appendAttributedString:[text attributedSubstringFromRange:removedLines[i].range]]; - atLeastOneLine += removedLines[i--].width; - } - [lastLineText appendAttributedString:truncationToken]; - } - if (type != kCTLineTruncationEnd && removedLines.count > 0) { // Middle or Start/Head wants to collect some - // text following the truncated content. - i = 0; - atLeastOneLine = removedLines[i].width; - while (atLeastOneLine < truncatedWidth && i < removedLines.count) { - atLeastOneLine += removedLines[i++].width; - } - for (i--; i >= 0; i--) { - NSAttributedString *nextLine = [text attributedSubstringFromRange:removedLines[i].range]; - if ([nextLine.string characterAtIndex:nextLine.string.length - 1] == '\n') { // Explicit newlines are always "long enough". - lastLineText = [NSMutableAttributedString new]; - } else { - [lastLineText appendAttributedString:nextLine]; - } - } - if (type == kCTLineTruncationStart) { - [lastLineText insertAttributedString:truncationToken atIndex:0]; - } - } - - CTLineRef ctLastLineExtend = CTLineCreateWithAttributedString((CFAttributedStringRef) lastLineText); - if (ctLastLineExtend) { - CTLineRef ctTruncatedLine = CTLineCreateTruncatedLine(ctLastLineExtend, truncatedWidth, type, truncationTokenLine); - CFRelease(ctLastLineExtend); - if (ctTruncatedLine) { - truncatedLine = [ASTextLine lineWithCTLine:ctTruncatedLine position:lastLine.position vertical:isVerticalForm]; - truncatedLine.index = lastLine.index; - truncatedLine.row = lastLine.row; - CFRelease(ctTruncatedLine); - } - } - CFRelease(truncationTokenLine); - } - } - } - } - - if (isVerticalForm) { - NSCharacterSet *rotateCharset = ASTextVerticalFormRotateCharacterSet(); - NSCharacterSet *rotateMoveCharset = ASTextVerticalFormRotateAndMoveCharacterSet(); - - void (^lineBlock)(ASTextLine *) = ^(ASTextLine *line){ - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - if (!runs) return; - NSUInteger runCount = CFArrayGetCount(runs); - if (runCount == 0) return; - NSMutableArray *lineRunRanges = [NSMutableArray new]; - line.verticalRotateRange = lineRunRanges; - for (NSUInteger r = 0; r < runCount; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - NSMutableArray *runRanges = [NSMutableArray new]; - [lineRunRanges addObject:runRanges]; - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - CFIndex runStrIdx[glyphCount + 1]; - CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx); - CFRange runStrRange = CTRunGetStringRange(run); - runStrIdx[glyphCount] = runStrRange.location + runStrRange.length; - CFDictionaryRef runAttrs = CTRunGetAttributes(run); - CTFontRef font = (CTFontRef)CFDictionaryGetValue(runAttrs, kCTFontAttributeName); - BOOL isColorGlyph = ASTextCTFontContainsColorBitmapGlyphs(font); - - NSUInteger prevIdx = 0; - ASTextRunGlyphDrawMode prevMode = ASTextRunGlyphDrawModeHorizontal; - NSString *layoutStr = layout.text.string; - for (NSUInteger g = 0; g < glyphCount; g++) { - BOOL glyphRotate = 0, glyphRotateMove = NO; - CFIndex runStrLen = runStrIdx[g + 1] - runStrIdx[g]; - if (isColorGlyph) { - glyphRotate = YES; - } else if (runStrLen == 1) { - unichar c = [layoutStr characterAtIndex:runStrIdx[g]]; - glyphRotate = [rotateCharset characterIsMember:c]; - if (glyphRotate) glyphRotateMove = [rotateMoveCharset characterIsMember:c]; - } else if (runStrLen > 1){ - NSString *glyphStr = [layoutStr substringWithRange:NSMakeRange(runStrIdx[g], runStrLen)]; - BOOL glyphRotate = [glyphStr rangeOfCharacterFromSet:rotateCharset].location != NSNotFound; - if (glyphRotate) glyphRotateMove = [glyphStr rangeOfCharacterFromSet:rotateMoveCharset].location != NSNotFound; - } - - ASTextRunGlyphDrawMode mode = glyphRotateMove ? ASTextRunGlyphDrawModeVerticalRotateMove : (glyphRotate ? ASTextRunGlyphDrawModeVerticalRotate : ASTextRunGlyphDrawModeHorizontal); - if (g == 0) { - prevMode = mode; - } else if (mode != prevMode) { - ASTextRunGlyphRange *aRange = [ASTextRunGlyphRange rangeWithRange:NSMakeRange(prevIdx, g - prevIdx) drawMode:prevMode]; - [runRanges addObject:aRange]; - prevIdx = g; - prevMode = mode; - } - } - if (prevIdx < glyphCount) { - ASTextRunGlyphRange *aRange = [ASTextRunGlyphRange rangeWithRange:NSMakeRange(prevIdx, glyphCount - prevIdx) drawMode:prevMode]; - [runRanges addObject:aRange]; - } - - } - }; - for (ASTextLine *line in lines) { - lineBlock(line); - } - if (truncatedLine) lineBlock(truncatedLine); - } - - if (visibleRange.length > 0) { - layout.needDrawText = YES; - - void (^block)(NSDictionary *attrs, NSRange range, BOOL *stop) = ^(NSDictionary *attrs, NSRange range, BOOL *stop) { - if (attrs[ASTextHighlightAttributeName]) layout.containsHighlight = YES; - if (attrs[ASTextBlockBorderAttributeName]) layout.needDrawBlockBorder = YES; - if (attrs[ASTextBackgroundBorderAttributeName]) layout.needDrawBackgroundBorder = YES; - if (attrs[ASTextShadowAttributeName] || attrs[NSShadowAttributeName]) layout.needDrawShadow = YES; - if (attrs[ASTextUnderlineAttributeName]) layout.needDrawUnderline = YES; - if (attrs[ASTextAttachmentAttributeName]) layout.needDrawAttachment = YES; - if (attrs[ASTextInnerShadowAttributeName]) layout.needDrawInnerShadow = YES; - if (attrs[ASTextStrikethroughAttributeName]) layout.needDrawStrikethrough = YES; - if (attrs[ASTextBorderAttributeName]) layout.needDrawBorder = YES; - }; - - [layout.text enumerateAttributesInRange:visibleRange options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:block]; - if (truncatedLine) { - [truncationToken enumerateAttributesInRange:NSMakeRange(0, truncationToken.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:block]; - } - } - for (NSUInteger i = 0, max = lines.count; i < max; i++) { - ASTextLine *line = lines[i]; - if (truncatedLine && line.index == truncatedLine.index) line = truncatedLine; - if (line.attachments.count > 0) { - if (!attachments) { - attachments = [[NSMutableArray alloc] init]; - attachmentRanges = [[NSMutableArray alloc] init]; - attachmentRects = [[NSMutableArray alloc] init]; - attachmentContentsSet = [[NSMutableSet alloc] init]; - } - [attachments addObjectsFromArray:line.attachments]; - [attachmentRanges addObjectsFromArray:line.attachmentRanges]; - [attachmentRects addObjectsFromArray:line.attachmentRects]; - for (ASTextAttachment *attachment in line.attachments) { - if (attachment.content) { - [attachmentContentsSet addObject:attachment.content]; - } - } - } - } - - layout.frame = ctFrame; - layout.lines = lines; - layout.truncatedLine = truncatedLine; - layout.attachments = attachments; - layout.attachmentRanges = attachmentRanges; - layout.attachmentRects = attachmentRects; - layout.attachmentContentsSet = attachmentContentsSet; - layout.rowCount = rowCount; - layout.visibleRange = visibleRange; - layout.textBoundingRect = textBoundingRect; - layout.textBoundingSize = textBoundingSize; - layout.lineRowsEdge = lineRowsEdge; - layout.lineRowsIndex = lineRowsIndex; - CFRelease(cgPath); - CFRelease(ctSetter); - CFRelease(ctFrame); - if (lineOrigins) free(lineOrigins); - return layout; -} - -+ (NSArray *)layoutWithContainers:(NSArray *)containers text:(NSAttributedString *)text { - return [self layoutWithContainers:containers text:text range:NSMakeRange(0, text.length)]; -} - -+ (NSArray *)layoutWithContainers:(NSArray *)containers text:(NSAttributedString *)text range:(NSRange)range { - if (!containers || !text) return nil; - if (range.location + range.length > text.length) return nil; - NSMutableArray *layouts = [[NSMutableArray alloc] init]; - for (NSUInteger i = 0, max = containers.count; i < max; i++) { - ASTextContainer *container = containers[i]; - ASTextLayout *layout = [self layoutWithContainer:container text:text range:range]; - if (!layout) return nil; - NSInteger length = (NSInteger)range.length - (NSInteger)layout.visibleRange.length; - if (length <= 0) { - range.length = 0; - range.location = text.length; - } else { - range.length = length; - range.location += layout.visibleRange.length; - } - } - return layouts; -} - -- (void)setFrame:(CTFrameRef)frame { - if (_frame != frame) { - if (frame) CFRetain(frame); - if (_frame) CFRelease(_frame); - _frame = frame; - } -} - -- (void)dealloc { - if (_frame) CFRelease(_frame); - if (_lineRowsIndex) free(_lineRowsIndex); - if (_lineRowsEdge) free(_lineRowsEdge); -} - -#pragma mark - Copying - -- (id)copyWithZone:(NSZone *)zone { - return self; // readonly object -} - - -#pragma mark - Query - -/** - Get the row index with 'edge' distance. - - @param edge The distance from edge to the point. - If vertical form, the edge is left edge, otherwise the edge is top edge. - - @return Returns NSNotFound if there's no row at the point. - */ -- (NSUInteger)_rowIndexForEdge:(CGFloat)edge { - if (_rowCount == 0) return NSNotFound; - BOOL isVertical = _container.verticalForm; - NSUInteger lo = 0, hi = _rowCount - 1, mid = 0; - NSUInteger rowIdx = NSNotFound; - while (lo <= hi) { - mid = (lo + hi) / 2; - ASRowEdge oneEdge = _lineRowsEdge[mid]; - if (isVertical ? - (oneEdge.foot <= edge && edge <= oneEdge.head) : - (oneEdge.head <= edge && edge <= oneEdge.foot)) { - rowIdx = mid; - break; - } - if ((isVertical ? (edge > oneEdge.head) : (edge < oneEdge.head))) { - if (mid == 0) break; - hi = mid - 1; - } else { - lo = mid + 1; - } - } - return rowIdx; -} - -/** - Get the closest row index with 'edge' distance. - - @param edge The distance from edge to the point. - If vertical form, the edge is left edge, otherwise the edge is top edge. - - @return Returns NSNotFound if there's no line. - */ -- (NSUInteger)_closestRowIndexForEdge:(CGFloat)edge { - if (_rowCount == 0) return NSNotFound; - NSUInteger rowIdx = [self _rowIndexForEdge:edge]; - if (rowIdx == NSNotFound) { - if (_container.verticalForm) { - if (edge > _lineRowsEdge[0].head) { - rowIdx = 0; - } else if (edge < _lineRowsEdge[_rowCount - 1].foot) { - rowIdx = _rowCount - 1; - } - } else { - if (edge < _lineRowsEdge[0].head) { - rowIdx = 0; - } else if (edge > _lineRowsEdge[_rowCount - 1].foot) { - rowIdx = _rowCount - 1; - } - } - } - return rowIdx; -} - -/** - Get a CTRun from a line position. - - @param line The text line. - @param position The position in the whole text. - - @return Returns NULL if not found (no CTRun at the position). - */ -- (CTRunRef)_runForLine:(ASTextLine *)line position:(ASTextPosition *)position { - if (!line || !position) return NULL; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger i = 0, max = CFArrayGetCount(runs); i < max; i++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i); - CFRange range = CTRunGetStringRange(run); - if (position.affinity == ASTextAffinityBackward) { - if (range.location < position.offset && position.offset <= range.location + range.length) { - return run; - } - } else { - if (range.location <= position.offset && position.offset < range.location + range.length) { - return run; - } - } - } - return NULL; -} - -/** - Whether the position is inside a composed character sequence. - - @param line The text line. - @param position Text text position in whole text. - @param block The block to be executed before returns YES. - left: left X offset - right: right X offset - prev: left position - next: right position - */ -- (BOOL)_insideComposedCharacterSequences:(ASTextLine *)line position:(NSUInteger)position block:(void (^)(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next))block { - NSRange range = line.range; - if (range.length == 0) return NO; - __block BOOL inside = NO; - __block NSUInteger _prev, _next; - [_text.string enumerateSubstringsInRange:range options:NSStringEnumerationByComposedCharacterSequences usingBlock: ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { - NSUInteger prev = substringRange.location; - NSUInteger next = substringRange.location + substringRange.length; - if (prev == position || next == position) { - *stop = YES; - } - if (prev < position && position < next) { - inside = YES; - _prev = prev; - _next = next; - *stop = YES; - } - }]; - if (inside && block) { - CGFloat left = [self offsetForTextPosition:_prev lineIndex:line.index]; - CGFloat right = [self offsetForTextPosition:_next lineIndex:line.index]; - block(left, right, _prev, _next); - } - return inside; -} - -/** - Whether the position is inside an emoji (such as National Flag Emoji). - - @param line The text line. - @param position Text text position in whole text. - @param block Yhe block to be executed before returns YES. - left: emoji's left X offset - right: emoji's right X offset - prev: emoji's left position - next: emoji's right position - */ -- (BOOL)_insideEmoji:(ASTextLine *)line position:(NSUInteger)position block:(void (^)(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next))block { - if (!line) return NO; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - CFRange range = CTRunGetStringRange(run); - if (range.length <= 1) continue; - if (position <= range.location || position >= range.location + range.length) continue; - CFDictionaryRef attrs = CTRunGetAttributes(run); - CTFontRef font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName); - if (!ASTextCTFontContainsColorBitmapGlyphs(font)) continue; - - // Here's Emoji runs (larger than 1 unichar), and position is inside the range. - CFIndex indices[glyphCount]; - CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), indices); - for (NSUInteger g = 0; g < glyphCount; g++) { - CFIndex prev = indices[g]; - CFIndex next = g + 1 < glyphCount ? indices[g + 1] : range.location + range.length; - if (position == prev) break; // Emoji edge - if (prev < position && position < next) { // inside an emoji (such as National Flag Emoji) - CGPoint pos = CGPointZero; - CGSize adv = CGSizeZero; - CTRunGetPositions(run, CFRangeMake(g, 1), &pos); - CTRunGetAdvances(run, CFRangeMake(g, 1), &adv); - if (block) { - block(line.position.x + pos.x, - line.position.x + pos.x + adv.width, - prev, next); - } - return YES; - } - } - } - return NO; -} -/** - Whether the write direction is RTL at the specified point - - @param line The text line - @param point The point in layout. - - @return YES if RTL. - */ -- (BOOL)_isRightToLeftInLine:(ASTextLine *)line atPoint:(CGPoint)point { - if (!line) return NO; - // get write direction - BOOL RTL = NO; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, max = CFArrayGetCount(runs); r < max; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CGPoint glyphPosition; - CTRunGetPositions(run, CFRangeMake(0, 1), &glyphPosition); - if (_container.verticalForm) { - CGFloat runX = glyphPosition.x; - runX += line.position.y; - CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - if (runX <= point.y && point.y <= runX + runWidth) { - if (CTRunGetStatus(run) & kCTRunStatusRightToLeft) RTL = YES; - break; - } - } else { - CGFloat runX = glyphPosition.x; - runX += line.position.x; - CGFloat runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - if (runX <= point.x && point.x <= runX + runWidth) { - if (CTRunGetStatus(run) & kCTRunStatusRightToLeft) RTL = YES; - break; - } - } - } - return RTL; -} - -/** - Correct the range's edge. - */ -- (ASTextRange *)_correctedRangeWithEdge:(ASTextRange *)range { - NSRange visibleRange = self.visibleRange; - ASTextPosition *start = range.start; - ASTextPosition *end = range.end; - - if (start.offset == visibleRange.location && start.affinity == ASTextAffinityBackward) { - start = [ASTextPosition positionWithOffset:start.offset affinity:ASTextAffinityForward]; - } - - if (end.offset == visibleRange.location + visibleRange.length && start.affinity == ASTextAffinityForward) { - end = [ASTextPosition positionWithOffset:end.offset affinity:ASTextAffinityBackward]; - } - - if (start != range.start || end != range.end) { - range = [ASTextRange rangeWithStart:start end:end]; - } - return range; -} - -- (NSUInteger)lineIndexForRow:(NSUInteger)row { - if (row >= _rowCount) return NSNotFound; - return _lineRowsIndex[row]; -} - -- (NSUInteger)lineCountForRow:(NSUInteger)row { - if (row >= _rowCount) return NSNotFound; - if (row == _rowCount - 1) { - return _lines.count - _lineRowsIndex[row]; - } else { - return _lineRowsIndex[row + 1] - _lineRowsIndex[row]; - } -} - -- (NSUInteger)rowIndexForLine:(NSUInteger)line { - if (line >= _lines.count) return NSNotFound; - return ((ASTextLine *)_lines[line]).row; -} - -- (NSUInteger)lineIndexForPoint:(CGPoint)point { - if (_lines.count == 0 || _rowCount == 0) return NSNotFound; - NSUInteger rowIdx = [self _rowIndexForEdge:_container.verticalForm ? point.x : point.y]; - if (rowIdx == NSNotFound) return NSNotFound; - - NSUInteger lineIdx0 = _lineRowsIndex[rowIdx]; - NSUInteger lineIdx1 = rowIdx == _rowCount - 1 ? _lines.count - 1 : _lineRowsIndex[rowIdx + 1] - 1; - for (NSUInteger i = lineIdx0; i <= lineIdx1; i++) { - CGRect bounds = ((ASTextLine *)_lines[i]).bounds; - if (CGRectContainsPoint(bounds, point)) return i; - } - - return NSNotFound; -} - -- (NSUInteger)closestLineIndexForPoint:(CGPoint)point { - BOOL isVertical = _container.verticalForm; - if (_lines.count == 0 || _rowCount == 0) return NSNotFound; - NSUInteger rowIdx = [self _closestRowIndexForEdge:isVertical ? point.x : point.y]; - if (rowIdx == NSNotFound) return NSNotFound; - - NSUInteger lineIdx0 = _lineRowsIndex[rowIdx]; - NSUInteger lineIdx1 = rowIdx == _rowCount - 1 ? _lines.count - 1 : _lineRowsIndex[rowIdx + 1] - 1; - if (lineIdx0 == lineIdx1) return lineIdx0; - - CGFloat minDistance = CGFLOAT_MAX; - NSUInteger minIndex = lineIdx0; - for (NSUInteger i = lineIdx0; i <= lineIdx1; i++) { - CGRect bounds = ((ASTextLine *)_lines[i]).bounds; - if (isVertical) { - if (bounds.origin.y <= point.y && point.y <= bounds.origin.y + bounds.size.height) return i; - CGFloat distance; - if (point.y < bounds.origin.y) { - distance = bounds.origin.y - point.y; - } else { - distance = point.y - (bounds.origin.y + bounds.size.height); - } - if (distance < minDistance) { - minDistance = distance; - minIndex = i; - } - } else { - if (bounds.origin.x <= point.x && point.x <= bounds.origin.x + bounds.size.width) return i; - CGFloat distance; - if (point.x < bounds.origin.x) { - distance = bounds.origin.x - point.x; - } else { - distance = point.x - (bounds.origin.x + bounds.size.width); - } - if (distance < minDistance) { - minDistance = distance; - minIndex = i; - } - } - } - return minIndex; -} - -- (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex { - if (lineIndex >= _lines.count) return CGFLOAT_MAX; - ASTextLine *line = _lines[lineIndex]; - CFRange range = CTLineGetStringRange(line.CTLine); - if (position < range.location || position > range.location + range.length) return CGFLOAT_MAX; - - CGFloat offset = CTLineGetOffsetForStringIndex(line.CTLine, position, NULL); - return _container.verticalForm ? (offset + line.position.y) : (offset + line.position.x); -} - -- (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex { - if (lineIndex >= _lines.count) return NSNotFound; - ASTextLine *line = _lines[lineIndex]; - if (_container.verticalForm) { - point.x = point.y - line.position.y; - point.y = 0; - } else { - point.x -= line.position.x; - point.y = 0; - } - CFIndex idx = CTLineGetStringIndexForPosition(line.CTLine, point); - if (idx == kCFNotFound) return NSNotFound; - - /* - If the emoji contains one or more variant form (such as ☔️ "\u2614\uFE0F") - and the font size is smaller than 379/15, then each variant form ("\uFE0F") - will rendered as a single blank glyph behind the emoji glyph. Maybe it's a - bug in CoreText? Seems iOS8.3 fixes this problem. - - If the point hit the blank glyph, the CTLineGetStringIndexForPosition() - returns the position before the emoji glyph, but it should returns the - position after the emoji and variant form. - - Here's a workaround. - */ - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, max = CFArrayGetCount(runs); r < max; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFRange range = CTRunGetStringRange(run); - if (range.location <= idx && idx < range.location + range.length) { - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) break; - CFDictionaryRef attrs = CTRunGetAttributes(run); - CTFontRef font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName); - if (!ASTextCTFontContainsColorBitmapGlyphs(font)) break; - - CFIndex indices[glyphCount]; - CGPoint positions[glyphCount]; - CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), indices); - CTRunGetPositions(run, CFRangeMake(0, glyphCount), positions); - for (NSUInteger g = 0; g < glyphCount; g++) { - NSUInteger gIdx = indices[g]; - if (gIdx == idx && g + 1 < glyphCount) { - CGFloat right = positions[g + 1].x; - if (point.x < right) break; - NSUInteger next = indices[g + 1]; - do { - if (next == range.location + range.length) break; - unichar c = [_text.string characterAtIndex:next]; - if ((c == 0xFE0E || c == 0xFE0F)) { // unicode variant form for emoji style - next++; - } else break; - } - while (1); - if (next != indices[g + 1]) idx = next; - break; - } - } - break; - } - } - return idx; -} - -- (ASTextPosition *)closestPositionToPoint:(CGPoint)point { - BOOL isVertical = _container.verticalForm; - // When call CTLineGetStringIndexForPosition() on ligature such as 'fi', - // and the point `hit` the glyph's left edge, it may get the ligature inside offset. - // I don't know why, maybe it's a bug of CoreText. Try to avoid it. - if (isVertical) point.y += 0.00001234; - else point.x += 0.00001234; - - NSUInteger lineIndex = [self closestLineIndexForPoint:point]; - if (lineIndex == NSNotFound) return nil; - ASTextLine *line = _lines[lineIndex]; - __block NSUInteger position = [self textPositionForPoint:point lineIndex:lineIndex]; - if (position == NSNotFound) position = line.range.location; - if (position <= _visibleRange.location) { - return [ASTextPosition positionWithOffset:_visibleRange.location affinity:ASTextAffinityForward]; - } else if (position >= _visibleRange.location + _visibleRange.length) { - return [ASTextPosition positionWithOffset:_visibleRange.location + _visibleRange.length affinity:ASTextAffinityBackward]; - } - - ASTextAffinity finalAffinity = ASTextAffinityForward; - BOOL finalAffinityDetected = NO; - - // binding range - NSRange bindingRange; - ASTextBinding *binding = [_text attribute:ASTextBindingAttributeName atIndex:position longestEffectiveRange:&bindingRange inRange:NSMakeRange(0, _text.length)]; - if (binding && bindingRange.length > 0) { - NSUInteger headLineIdx = [self lineIndexForPosition:[ASTextPosition positionWithOffset:bindingRange.location]]; - NSUInteger tailLineIdx = [self lineIndexForPosition:[ASTextPosition positionWithOffset:bindingRange.location + bindingRange.length affinity:ASTextAffinityBackward]]; - if (headLineIdx == lineIndex && lineIndex == tailLineIdx) { // all in same line - CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:lineIndex]; - CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:lineIndex]; - if (left != CGFLOAT_MAX && right != CGFLOAT_MAX) { - if (_container.isVerticalForm) { - if (fabs(point.y - left) < fabs(point.y - right)) { - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - } else { - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - } - } else { - if (fabs(point.x - left) < fabs(point.x - right)) { - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - } else { - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - } - } - } else if (left != CGFLOAT_MAX) { - position = left; - finalAffinity = ASTextAffinityForward; - } else if (right != CGFLOAT_MAX) { - position = right; - finalAffinity = ASTextAffinityBackward; - } - finalAffinityDetected = YES; - } else if (headLineIdx == lineIndex) { - CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:lineIndex]; - if (left != CGFLOAT_MAX) { - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - finalAffinityDetected = YES; - } - } else if (tailLineIdx == lineIndex) { - CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:lineIndex]; - if (right != CGFLOAT_MAX) { - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - finalAffinityDetected = YES; - } - } else { - BOOL onLeft = NO, onRight = NO; - if (headLineIdx != NSNotFound && tailLineIdx != NSNotFound) { - if (abs((int)headLineIdx - (int)lineIndex) < abs((int)tailLineIdx - (int)lineIndex)) onLeft = YES; - else onRight = YES; - } else if (headLineIdx != NSNotFound) { - onLeft = YES; - } else if (tailLineIdx != NSNotFound) { - onRight = YES; - } - - if (onLeft) { - CGFloat left = [self offsetForTextPosition:bindingRange.location lineIndex:headLineIdx]; - if (left != CGFLOAT_MAX) { - lineIndex = headLineIdx; - line = _lines[headLineIdx]; - position = bindingRange.location; - finalAffinity = ASTextAffinityForward; - finalAffinityDetected = YES; - } - } else if (onRight) { - CGFloat right = [self offsetForTextPosition:bindingRange.location + bindingRange.length lineIndex:tailLineIdx]; - if (right != CGFLOAT_MAX) { - lineIndex = tailLineIdx; - line = _lines[tailLineIdx]; - position = bindingRange.location + bindingRange.length; - finalAffinity = ASTextAffinityBackward; - finalAffinityDetected = YES; - } - } - } - } - - // empty line - if (line.range.length == 0) { - BOOL behind = (_lines.count > 1 && lineIndex == _lines.count - 1); //end line - return [ASTextPosition positionWithOffset:line.range.location affinity:behind ? ASTextAffinityBackward:ASTextAffinityForward]; - } - - // detect weather the line is a linebreak token - if (line.range.length <= 2) { - NSString *str = [_text.string substringWithRange:line.range]; - if (ASTextIsLinebreakString(str)) { // an empty line ("\r", "\n", "\r\n") - return [ASTextPosition positionWithOffset:line.range.location]; - } - } - - // above whole text frame - if (lineIndex == 0 && (isVertical ? (point.x > line.right) : (point.y < line.top))) { - position = 0; - finalAffinity = ASTextAffinityForward; - finalAffinityDetected = YES; - } - // below whole text frame - if (lineIndex == _lines.count - 1 && (isVertical ? (point.x < line.left) : (point.y > line.bottom))) { - position = line.range.location + line.range.length; - finalAffinity = ASTextAffinityBackward; - finalAffinityDetected = YES; - } - - // There must be at least one non-linebreak char, - // ignore the linebreak characters at line end if exists. - if (position >= line.range.location + line.range.length - 1) { - if (position > line.range.location) { - unichar c1 = [_text.string characterAtIndex:position - 1]; - if (ASTextIsLinebreakChar(c1)) { - position--; - if (position > line.range.location) { - unichar c0 = [_text.string characterAtIndex:position - 1]; - if (ASTextIsLinebreakChar(c0)) { - position--; - } - } - } - } - } - if (position == line.range.location) { - return [ASTextPosition positionWithOffset:position]; - } - if (position == line.range.location + line.range.length) { - return [ASTextPosition positionWithOffset:position affinity:ASTextAffinityBackward]; - } - - [self _insideComposedCharacterSequences:line position:position block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - if (isVertical) { - position = fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next); - } else { - position = fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next); - } - }]; - - [self _insideEmoji:line position:position block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - if (isVertical) { - position = fabs(left - point.y) < fabs(right - point.y) < (right ? prev : next); - } else { - position = fabs(left - point.x) < fabs(right - point.x) < (right ? prev : next); - } - }]; - - if (position < _visibleRange.location) position = _visibleRange.location; - else if (position > _visibleRange.location + _visibleRange.length) position = _visibleRange.location + _visibleRange.length; - - if (!finalAffinityDetected) { - CGFloat ofs = [self offsetForTextPosition:position lineIndex:lineIndex]; - if (ofs != CGFLOAT_MAX) { - BOOL RTL = [self _isRightToLeftInLine:line atPoint:point]; - if (position >= line.range.location + line.range.length) { - finalAffinity = RTL ? ASTextAffinityForward : ASTextAffinityBackward; - } else if (position <= line.range.location) { - finalAffinity = RTL ? ASTextAffinityBackward : ASTextAffinityForward; - } else { - finalAffinity = (ofs < (isVertical ? point.y : point.x) && !RTL) ? ASTextAffinityForward : ASTextAffinityBackward; - } - } - } - - return [ASTextPosition positionWithOffset:position affinity:finalAffinity]; -} - -- (ASTextPosition *)positionForPoint:(CGPoint)point - oldPosition:(ASTextPosition *)oldPosition - otherPosition:(ASTextPosition *)otherPosition { - if (!oldPosition || !otherPosition) { - return oldPosition; - } - ASTextPosition *newPos = [self closestPositionToPoint:point]; - if (!newPos) return oldPosition; - if ([newPos compare:otherPosition] == [oldPosition compare:otherPosition] && - newPos.offset != otherPosition.offset) { - return newPos; - } - NSUInteger lineIndex = [self lineIndexForPosition:otherPosition]; - if (lineIndex == NSNotFound) return oldPosition; - ASTextLine *line = _lines[lineIndex]; - ASRowEdge vertical = _lineRowsEdge[line.row]; - if (_container.verticalForm) { - point.x = (vertical.head + vertical.foot) * 0.5; - } else { - point.y = (vertical.head + vertical.foot) * 0.5; - } - newPos = [self closestPositionToPoint:point]; - if ([newPos compare:otherPosition] == [oldPosition compare:otherPosition] && - newPos.offset != otherPosition.offset) { - return newPos; - } - - if (_container.isVerticalForm) { - if ([oldPosition compare:otherPosition] == NSOrderedAscending) { // search backward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionUp offset:1]; - if (range) return range.start; - } else { // search forward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionDown offset:1]; - if (range) return range.end; - } - } else { - if ([oldPosition compare:otherPosition] == NSOrderedAscending) { // search backward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionLeft offset:1]; - if (range) return range.start; - } else { // search forward - ASTextRange *range = [self textRangeByExtendingPosition:otherPosition inDirection:UITextLayoutDirectionRight offset:1]; - if (range) return range.end; - } - } - - return oldPosition; -} - -- (ASTextRange *)textRangeAtPoint:(CGPoint)point { - NSUInteger lineIndex = [self lineIndexForPoint:point]; - if (lineIndex == NSNotFound) return nil; - NSUInteger textPosition = [self textPositionForPoint:point lineIndex:[self lineIndexForPoint:point]]; - if (textPosition == NSNotFound) return nil; - ASTextPosition *pos = [self closestPositionToPoint:point]; - if (!pos) return nil; - - // get write direction - BOOL RTL = [self _isRightToLeftInLine:_lines[lineIndex] atPoint:point]; - CGRect rect = [self caretRectForPosition:pos]; - if (CGRectIsNull(rect)) return nil; - - if (_container.verticalForm) { - ASTextRange *range = [self textRangeByExtendingPosition:pos inDirection:(rect.origin.y >= point.y && !RTL) ? UITextLayoutDirectionUp:UITextLayoutDirectionDown offset:1]; - return range; - } else { - ASTextRange *range = [self textRangeByExtendingPosition:pos inDirection:(rect.origin.x >= point.x && !RTL) ? UITextLayoutDirectionLeft:UITextLayoutDirectionRight offset:1]; - return range; - } -} - -- (ASTextRange *)closestTextRangeAtPoint:(CGPoint)point { - ASTextPosition *pos = [self closestPositionToPoint:point]; - if (!pos) return nil; - NSUInteger lineIndex = [self lineIndexForPosition:pos]; - if (lineIndex == NSNotFound) return nil; - ASTextLine *line = _lines[lineIndex]; - BOOL RTL = [self _isRightToLeftInLine:line atPoint:point]; - CGRect rect = [self caretRectForPosition:pos]; - if (CGRectIsNull(rect)) return nil; - - UITextLayoutDirection direction = UITextLayoutDirectionRight; - if (pos.offset >= line.range.location + line.range.length) { - if (direction != RTL) { - direction = _container.verticalForm ? UITextLayoutDirectionUp : UITextLayoutDirectionLeft; - } else { - direction = _container.verticalForm ? UITextLayoutDirectionDown : UITextLayoutDirectionRight; - } - } else if (pos.offset <= line.range.location) { - if (direction != RTL) { - direction = _container.verticalForm ? UITextLayoutDirectionDown : UITextLayoutDirectionRight; - } else { - direction = _container.verticalForm ? UITextLayoutDirectionUp : UITextLayoutDirectionLeft; - } - } else { - if (_container.verticalForm) { - direction = (rect.origin.y >= point.y && !RTL) ? UITextLayoutDirectionUp:UITextLayoutDirectionDown; - } else { - direction = (rect.origin.x >= point.x && !RTL) ? UITextLayoutDirectionLeft:UITextLayoutDirectionRight; - } - } - - ASTextRange *range = [self textRangeByExtendingPosition:pos inDirection:direction offset:1]; - return range; -} - -- (ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position { - NSUInteger visibleStart = _visibleRange.location; - NSUInteger visibleEnd = _visibleRange.location + _visibleRange.length; - - if (!position) return nil; - if (position.offset < visibleStart || position.offset > visibleEnd) return nil; - - // head or tail, returns immediately - if (position.offset == visibleStart) { - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0)]; - } else if (position.offset == visibleEnd) { - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:ASTextAffinityBackward]; - } - - // binding range - NSRange tRange; - ASTextBinding *binding = [_text attribute:ASTextBindingAttributeName atIndex:position.offset longestEffectiveRange:&tRange inRange:_visibleRange]; - if (binding && tRange.length > 0 && tRange.location < position.offset) { - return [ASTextRange rangeWithRange:tRange]; - } - - // inside emoji or composed character sequences - NSUInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex != NSNotFound) { - __block NSUInteger _prev, _next; - BOOL emoji = NO, seq = NO; - - ASTextLine *line = _lines[lineIndex]; - emoji = [self _insideEmoji:line position:position.offset block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - _prev = prev; - _next = next; - }]; - if (!emoji) { - seq = [self _insideComposedCharacterSequences:line position:position.offset block: ^(CGFloat left, CGFloat right, NSUInteger prev, NSUInteger next) { - _prev = prev; - _next = next; - }]; - } - if (emoji || seq) { - return [ASTextRange rangeWithRange:NSMakeRange(_prev, _next - _prev)]; - } - } - - // inside linebreak '\r\n' - if (position.offset > visibleStart && position.offset < visibleEnd) { - unichar c0 = [_text.string characterAtIndex:position.offset - 1]; - if ((c0 == '\r') && position.offset < visibleEnd) { - unichar c1 = [_text.string characterAtIndex:position.offset]; - if (c1 == '\n') { - return [ASTextRange rangeWithStart:[ASTextPosition positionWithOffset:position.offset - 1] end:[ASTextPosition positionWithOffset:position.offset + 1]]; - } - } - if (ASTextIsLinebreakChar(c0) && position.affinity == ASTextAffinityBackward) { - NSString *str = [_text.string substringToIndex:position.offset]; - NSUInteger len = ASTextLinebreakTailLength(str); - return [ASTextRange rangeWithStart:[ASTextPosition positionWithOffset:position.offset - len] end:[ASTextPosition positionWithOffset:position.offset]]; - } - } - - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:position.affinity]; -} - -- (ASTextRange *)textRangeByExtendingPosition:(ASTextPosition *)position - inDirection:(UITextLayoutDirection)direction - offset:(NSInteger)offset { - NSInteger visibleStart = _visibleRange.location; - NSInteger visibleEnd = _visibleRange.location + _visibleRange.length; - - if (!position) return nil; - if (position.offset < visibleStart || position.offset > visibleEnd) return nil; - if (offset == 0) return [self textRangeByExtendingPosition:position]; - - BOOL isVerticalForm = _container.verticalForm; - BOOL verticalMove, forwardMove; - - if (isVerticalForm) { - verticalMove = direction == UITextLayoutDirectionLeft || direction == UITextLayoutDirectionRight; - forwardMove = direction == UITextLayoutDirectionLeft || direction == UITextLayoutDirectionDown; - } else { - verticalMove = direction == UITextLayoutDirectionUp || direction == UITextLayoutDirectionDown; - forwardMove = direction == UITextLayoutDirectionDown || direction == UITextLayoutDirectionRight; - } - - if (offset < 0) { - forwardMove = !forwardMove; - offset = -offset; - } - - // head or tail, returns immediately - if (!forwardMove && position.offset == visibleStart) { - return [ASTextRange rangeWithRange:NSMakeRange(_visibleRange.location, 0)]; - } else if (forwardMove && position.offset == visibleEnd) { - return [ASTextRange rangeWithRange:NSMakeRange(position.offset, 0) affinity:ASTextAffinityBackward]; - } - - // extend from position - ASTextRange *fromRange = [self textRangeByExtendingPosition:position]; - if (!fromRange) return nil; - ASTextRange *allForward = [ASTextRange rangeWithStart:fromRange.start end:[ASTextPosition positionWithOffset:visibleEnd]]; - ASTextRange *allBackward = [ASTextRange rangeWithStart:[ASTextPosition positionWithOffset:visibleStart] end:fromRange.end]; - - if (verticalMove) { // up/down in text layout - NSInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex == NSNotFound) return nil; - - ASTextLine *line = _lines[lineIndex]; - NSInteger moveToRowIndex = (NSInteger)line.row + (forwardMove ? offset : -offset); - if (moveToRowIndex < 0) return allBackward; - else if (moveToRowIndex >= (NSInteger)_rowCount) return allForward; - - CGFloat ofs = [self offsetForTextPosition:position.offset lineIndex:lineIndex]; - if (ofs == CGFLOAT_MAX) return nil; - - NSUInteger moveToLineFirstIndex = [self lineIndexForRow:moveToRowIndex]; - NSUInteger moveToLineCount = [self lineCountForRow:moveToRowIndex]; - if (moveToLineFirstIndex == NSNotFound || moveToLineCount == NSNotFound || moveToLineCount == 0) return nil; - CGFloat mostLeft = CGFLOAT_MAX, mostRight = -CGFLOAT_MAX; - ASTextLine *mostLeftLine = nil, *mostRightLine = nil; - NSUInteger insideIndex = NSNotFound; - for (NSUInteger i = 0; i < moveToLineCount; i++) { - NSUInteger lineIndex = moveToLineFirstIndex + i; - ASTextLine *line = _lines[lineIndex]; - if (isVerticalForm) { - if (line.top <= ofs && ofs <= line.bottom) { - insideIndex = line.index; - break; - } - if (line.top < mostLeft) { - mostLeft = line.top; - mostLeftLine = line; - } - if (line.bottom > mostRight) { - mostRight = line.bottom; - mostRightLine = line; - } - } else { - if (line.left <= ofs && ofs <= line.right) { - insideIndex = line.index; - break; - } - if (line.left < mostLeft) { - mostLeft = line.left; - mostLeftLine = line; - } - if (line.right > mostRight) { - mostRight = line.right; - mostRightLine = line; - } - } - } - BOOL afinityEdge = NO; - if (insideIndex == NSNotFound) { - if (ofs <= mostLeft) { - insideIndex = mostLeftLine.index; - } else { - insideIndex = mostRightLine.index; - } - afinityEdge = YES; - } - ASTextLine *insideLine = _lines[insideIndex]; - NSUInteger pos; - if (isVerticalForm) { - pos = [self textPositionForPoint:CGPointMake(insideLine.position.x, ofs) lineIndex:insideIndex]; - } else { - pos = [self textPositionForPoint:CGPointMake(ofs, insideLine.position.y) lineIndex:insideIndex]; - } - if (pos == NSNotFound) return nil; - ASTextPosition *extPos; - if (afinityEdge) { - if (pos == insideLine.range.location + insideLine.range.length) { - NSString *subStr = [_text.string substringWithRange:insideLine.range]; - NSUInteger lineBreakLen = ASTextLinebreakTailLength(subStr); - extPos = [ASTextPosition positionWithOffset:pos - lineBreakLen]; - } else { - extPos = [ASTextPosition positionWithOffset:pos]; - } - } else { - extPos = [ASTextPosition positionWithOffset:pos]; - } - ASTextRange *ext = [self textRangeByExtendingPosition:extPos]; - if (!ext) return nil; - if (forwardMove) { - return [ASTextRange rangeWithStart:fromRange.start end:ext.end]; - } else { - return [ASTextRange rangeWithStart:ext.start end:fromRange.end]; - } - - } else { // left/right in text layout - ASTextPosition *toPosition = [ASTextPosition positionWithOffset:position.offset + (forwardMove ? offset : -offset)]; - if (toPosition.offset <= visibleStart) return allBackward; - else if (toPosition.offset >= visibleEnd) return allForward; - - ASTextRange *toRange = [self textRangeByExtendingPosition:toPosition]; - if (!toRange) return nil; - - NSInteger start = MIN(fromRange.start.offset, toRange.start.offset); - NSInteger end = MAX(fromRange.end.offset, toRange.end.offset); - return [ASTextRange rangeWithRange:NSMakeRange(start, end - start)]; - } -} - -- (NSUInteger)lineIndexForPosition:(ASTextPosition *)position { - if (!position) return NSNotFound; - if (_lines.count == 0) return NSNotFound; - NSUInteger location = position.offset; - NSInteger lo = 0, hi = _lines.count - 1, mid = 0; - if (position.affinity == ASTextAffinityBackward) { - while (lo <= hi) { - mid = (lo + hi) / 2; - ASTextLine *line = _lines[mid]; - NSRange range = line.range; - if (range.location < location && location <= range.location + range.length) { - return mid; - } - if (location <= range.location) { - hi = mid - 1; - } else { - lo = mid + 1; - } - } - } else { - while (lo <= hi) { - mid = (lo + hi) / 2; - ASTextLine *line = _lines[mid]; - NSRange range = line.range; - if (range.location <= location && location < range.location + range.length) { - return mid; - } - if (location < range.location) { - hi = mid - 1; - } else { - lo = mid + 1; - } - } - } - return NSNotFound; -} - -- (CGPoint)linePositionForPosition:(ASTextPosition *)position { - NSUInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex == NSNotFound) return CGPointZero; - ASTextLine *line = _lines[lineIndex]; - CGFloat offset = [self offsetForTextPosition:position.offset lineIndex:lineIndex]; - if (offset == CGFLOAT_MAX) return CGPointZero; - if (_container.verticalForm) { - return CGPointMake(line.position.x, offset); - } else { - return CGPointMake(offset, line.position.y); - } -} - -- (CGRect)caretRectForPosition:(ASTextPosition *)position { - NSUInteger lineIndex = [self lineIndexForPosition:position]; - if (lineIndex == NSNotFound) return CGRectNull; - ASTextLine *line = _lines[lineIndex]; - CGFloat offset = [self offsetForTextPosition:position.offset lineIndex:lineIndex]; - if (offset == CGFLOAT_MAX) return CGRectNull; - if (_container.verticalForm) { - return CGRectMake(line.bounds.origin.x, offset, line.bounds.size.width, 0); - } else { - return CGRectMake(offset, line.bounds.origin.y, 0, line.bounds.size.height); - } -} - -- (CGRect)firstRectForRange:(ASTextRange *)range { - range = [self _correctedRangeWithEdge:range]; - - NSUInteger startLineIndex = [self lineIndexForPosition:range.start]; - NSUInteger endLineIndex = [self lineIndexForPosition:range.end]; - if (startLineIndex == NSNotFound || endLineIndex == NSNotFound) return CGRectNull; - if (startLineIndex > endLineIndex) return CGRectNull; - ASTextLine *startLine = _lines[startLineIndex]; - ASTextLine *endLine = _lines[endLineIndex]; - NSMutableArray *lines = [NSMutableArray new]; - for (NSUInteger i = startLineIndex; i <= startLineIndex; i++) { - ASTextLine *line = _lines[i]; - if (line.row != startLine.row) break; - [lines addObject:line]; - } - if (_container.verticalForm) { - if (lines.count == 1) { - CGFloat top = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat bottom; - if (startLine == endLine) { - bottom = [self offsetForTextPosition:range.end.offset lineIndex:startLineIndex]; - } else { - bottom = startLine.bottom; - } - if (top == CGFLOAT_MAX || bottom == CGFLOAT_MAX) return CGRectNull; - if (top > bottom) ASTEXT_SWAP(top, bottom); - return CGRectMake(startLine.left, top, startLine.width, bottom - top); - } else { - CGFloat top = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat bottom = startLine.bottom; - if (top == CGFLOAT_MAX || bottom == CGFLOAT_MAX) return CGRectNull; - if (top > bottom) ASTEXT_SWAP(top, bottom); - CGRect rect = CGRectMake(startLine.left, top, startLine.width, bottom - top); - for (NSUInteger i = 1; i < lines.count; i++) { - ASTextLine *line = lines[i]; - rect = CGRectUnion(rect, line.bounds); - } - return rect; - } - } else { - if (lines.count == 1) { - CGFloat left = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat right; - if (startLine == endLine) { - right = [self offsetForTextPosition:range.end.offset lineIndex:startLineIndex]; - } else { - right = startLine.right; - } - if (left == CGFLOAT_MAX || right == CGFLOAT_MAX) return CGRectNull; - if (left > right) ASTEXT_SWAP(left, right); - return CGRectMake(left, startLine.top, right - left, startLine.height); - } else { - CGFloat left = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat right = startLine.right; - if (left == CGFLOAT_MAX || right == CGFLOAT_MAX) return CGRectNull; - if (left > right) ASTEXT_SWAP(left, right); - CGRect rect = CGRectMake(left, startLine.top, right - left, startLine.height); - for (NSUInteger i = 1; i < lines.count; i++) { - ASTextLine *line = lines[i]; - rect = CGRectUnion(rect, line.bounds); - } - return rect; - } - } -} - -- (CGRect)rectForRange:(ASTextRange *)range { - NSArray *rects = [self selectionRectsForRange:range]; - if (rects.count == 0) return CGRectNull; - CGRect rectUnion = ((ASTextSelectionRect *)rects.firstObject).rect; - for (NSUInteger i = 1; i < rects.count; i++) { - ASTextSelectionRect *rect = rects[i]; - rectUnion = CGRectUnion(rectUnion, rect.rect); - } - return rectUnion; -} - -- (NSArray *)selectionRectsForRange:(ASTextRange *)range { - range = [self _correctedRangeWithEdge:range]; - - BOOL isVertical = _container.verticalForm; - NSMutableArray *rects = [[NSMutableArray alloc] init]; - if (!range) return rects; - - NSUInteger startLineIndex = [self lineIndexForPosition:range.start]; - NSUInteger endLineIndex = [self lineIndexForPosition:range.end]; - if (startLineIndex == NSNotFound || endLineIndex == NSNotFound) return rects; - if (startLineIndex > endLineIndex) ASTEXT_SWAP(startLineIndex, endLineIndex); - ASTextLine *startLine = _lines[startLineIndex]; - ASTextLine *endLine = _lines[endLineIndex]; - CGFloat offsetStart = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CGFloat offsetEnd = [self offsetForTextPosition:range.end.offset lineIndex:endLineIndex]; - - ASTextSelectionRect *start = [ASTextSelectionRect new]; - if (isVertical) { - start.rect = CGRectMake(startLine.left, offsetStart, startLine.width, 0); - } else { - start.rect = CGRectMake(offsetStart, startLine.top, 0, startLine.height); - } - start.containsStart = YES; - start.isVertical = isVertical; - [rects addObject:start]; - - ASTextSelectionRect *end = [ASTextSelectionRect new]; - if (isVertical) { - end.rect = CGRectMake(endLine.left, offsetEnd, endLine.width, 0); - } else { - end.rect = CGRectMake(offsetEnd, endLine.top, 0, endLine.height); - } - end.containsEnd = YES; - end.isVertical = isVertical; - [rects addObject:end]; - - if (startLine.row == endLine.row) { // same row - if (offsetStart > offsetEnd) ASTEXT_SWAP(offsetStart, offsetEnd); - ASTextSelectionRect *rect = [ASTextSelectionRect new]; - if (isVertical) { - rect.rect = CGRectMake(startLine.bounds.origin.x, offsetStart, MAX(startLine.width, endLine.width), offsetEnd - offsetStart); - } else { - rect.rect = CGRectMake(offsetStart, startLine.bounds.origin.y, offsetEnd - offsetStart, MAX(startLine.height, endLine.height)); - } - rect.isVertical = isVertical; - [rects addObject:rect]; - - } else { // more than one row - - // start line select rect - ASTextSelectionRect *topRect = [ASTextSelectionRect new]; - topRect.isVertical = isVertical; - CGFloat topOffset = [self offsetForTextPosition:range.start.offset lineIndex:startLineIndex]; - CTRunRef topRun = [self _runForLine:startLine position:range.start]; - if (topRun && (CTRunGetStatus(topRun) & kCTRunStatusRightToLeft)) { - if (isVertical) { - topRect.rect = CGRectMake(startLine.left, _container.path ? startLine.top : _container.insets.top, startLine.width, topOffset - startLine.top); - } else { - topRect.rect = CGRectMake(_container.path ? startLine.left : _container.insets.left, startLine.top, topOffset - startLine.left, startLine.height); - } - topRect.writingDirection = UITextWritingDirectionRightToLeft; - } else { - if (isVertical) { - topRect.rect = CGRectMake(startLine.left, topOffset, startLine.width, (_container.path ? startLine.bottom : _container.size.height - _container.insets.bottom) - topOffset); - } else { - // TODO: Fixes highlighting first row only to the end of the text and not highlight - // the while line to the end. Needs to brought over to multiline support - topRect.rect = CGRectMake(topOffset, startLine.top, (_container.path ? startLine.right : _container.size.width - _container.insets.right) - topOffset - (_container.size.width - _container.insets.right - startLine.right), startLine.height); - } - } - [rects addObject:topRect]; - - // end line select rect - ASTextSelectionRect *bottomRect = [ASTextSelectionRect new]; - bottomRect.isVertical = isVertical; - CGFloat bottomOffset = [self offsetForTextPosition:range.end.offset lineIndex:endLineIndex]; - CTRunRef bottomRun = [self _runForLine:endLine position:range.end]; - if (bottomRun && (CTRunGetStatus(bottomRun) & kCTRunStatusRightToLeft)) { - if (isVertical) { - bottomRect.rect = CGRectMake(endLine.left, bottomOffset, endLine.width, (_container.path ? endLine.bottom : _container.size.height - _container.insets.bottom) - bottomOffset); - } else { - bottomRect.rect = CGRectMake(bottomOffset, endLine.top, (_container.path ? endLine.right : _container.size.width - _container.insets.right) - bottomOffset, endLine.height); - } - bottomRect.writingDirection = UITextWritingDirectionRightToLeft; - } else { - if (isVertical) { - CGFloat top = _container.path ? endLine.top : _container.insets.top; - bottomRect.rect = CGRectMake(endLine.left, top, endLine.width, bottomOffset - top); - } else { - CGFloat left = _container.path ? endLine.left : _container.insets.left; - bottomRect.rect = CGRectMake(left, endLine.top, bottomOffset - left, endLine.height); - } - } - [rects addObject:bottomRect]; - - if (endLineIndex - startLineIndex >= 2) { - CGRect r = CGRectZero; - BOOL startLineDetected = NO; - for (NSUInteger l = startLineIndex + 1; l < endLineIndex; l++) { - ASTextLine *line = _lines[l]; - if (line.row == startLine.row || line.row == endLine.row) continue; - if (!startLineDetected) { - r = line.bounds; - startLineDetected = YES; - } else { - r = CGRectUnion(r, line.bounds); - } - } - if (startLineDetected) { - if (isVertical) { - if (!_container.path) { - r.origin.y = _container.insets.top; - r.size.height = _container.size.height - _container.insets.bottom - _container.insets.top; - } - r.size.width = CGRectGetMinX(topRect.rect) - CGRectGetMaxX(bottomRect.rect); - r.origin.x = CGRectGetMaxX(bottomRect.rect); - } else { - if (!_container.path) { - r.origin.x = _container.insets.left; - r.size.width = _container.size.width - _container.insets.right - _container.insets.left; - } - r.origin.y = CGRectGetMaxY(topRect.rect); - r.size.height = bottomRect.rect.origin.y - r.origin.y; - } - - ASTextSelectionRect *rect = [ASTextSelectionRect new]; - rect.rect = r; - rect.isVertical = isVertical; - [rects addObject:rect]; - } - } else { - if (isVertical) { - CGRect r0 = bottomRect.rect; - CGRect r1 = topRect.rect; - CGFloat mid = (CGRectGetMaxX(r0) + CGRectGetMinX(r1)) * 0.5; - r0.size.width = mid - r0.origin.x; - CGFloat r1ofs = r1.origin.x - mid; - r1.origin.x -= r1ofs; - r1.size.width += r1ofs; - topRect.rect = r1; - bottomRect.rect = r0; - } else { - CGRect r0 = topRect.rect; - CGRect r1 = bottomRect.rect; - CGFloat mid = (CGRectGetMaxY(r0) + CGRectGetMinY(r1)) * 0.5; - r0.size.height = mid - r0.origin.y; - CGFloat r1ofs = r1.origin.y - mid; - r1.origin.y -= r1ofs; - r1.size.height += r1ofs; - topRect.rect = r0; - bottomRect.rect = r1; - } - } - } - return rects; -} - -- (NSArray *)selectionRectsWithoutStartAndEndForRange:(ASTextRange *)range { - NSMutableArray *rects = [self selectionRectsForRange:range].mutableCopy; - for (NSInteger i = 0, max = rects.count; i < max; i++) { - ASTextSelectionRect *rect = rects[i]; - if (rect.containsStart || rect.containsEnd) { - [rects removeObjectAtIndex:i]; - i--; - max--; - } - } - return rects; -} - -- (NSArray *)selectionRectsWithOnlyStartAndEndForRange:(ASTextRange *)range { - NSMutableArray *rects = [self selectionRectsForRange:range].mutableCopy; - for (NSInteger i = 0, max = rects.count; i < max; i++) { - ASTextSelectionRect *rect = rects[i]; - if (!rect.containsStart && !rect.containsEnd) { - [rects removeObjectAtIndex:i]; - i--; - max--; - } - } - return rects; -} - - -#pragma mark - Draw - - -typedef NS_OPTIONS(NSUInteger, ASTextDecorationType) { - ASTextDecorationTypeUnderline = 1 << 0, - ASTextDecorationTypeStrikethrough = 1 << 1, -}; - -typedef NS_OPTIONS(NSUInteger, ASTextBorderType) { - ASTextBorderTypeBackgound = 1 << 0, - ASTextBorderTypeNormal = 1 << 1, -}; - -static CGRect ASTextMergeRectInSameLine(CGRect rect1, CGRect rect2, BOOL isVertical) { - if (isVertical) { - CGFloat top = MIN(rect1.origin.y, rect2.origin.y); - CGFloat bottom = MAX(rect1.origin.y + rect1.size.height, rect2.origin.y + rect2.size.height); - CGFloat width = MAX(rect1.size.width, rect2.size.width); - return CGRectMake(rect1.origin.x, top, width, bottom - top); - } else { - CGFloat left = MIN(rect1.origin.x, rect2.origin.x); - CGFloat right = MAX(rect1.origin.x + rect1.size.width, rect2.origin.x + rect2.size.width); - CGFloat height = MAX(rect1.size.height, rect2.size.height); - return CGRectMake(left, rect1.origin.y, right - left, height); - } -} - -static void ASTextGetRunsMaxMetric(CFArrayRef runs, CGFloat *xHeight, CGFloat *underlinePosition, CGFloat *lineThickness) { - CGFloat maxXHeight = 0; - CGFloat maxUnderlinePos = 0; - CGFloat maxLineThickness = 0; - for (NSUInteger i = 0, max = CFArrayGetCount(runs); i < max; i++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, i); - CFDictionaryRef attrs = CTRunGetAttributes(run); - if (attrs) { - CTFontRef font = (CTFontRef)CFDictionaryGetValue(attrs, kCTFontAttributeName); - if (font) { - CGFloat xHeight = CTFontGetXHeight(font); - if (xHeight > maxXHeight) maxXHeight = xHeight; - CGFloat underlinePos = CTFontGetUnderlinePosition(font); - if (underlinePos < maxUnderlinePos) maxUnderlinePos = underlinePos; - CGFloat lineThickness = CTFontGetUnderlineThickness(font); - if (lineThickness > maxLineThickness) maxLineThickness = lineThickness; - } - } - } - if (xHeight) *xHeight = maxXHeight; - if (underlinePosition) *underlinePosition = maxUnderlinePos; - if (lineThickness) *lineThickness = maxLineThickness; -} - -static void ASTextDrawRun(ASTextLine *line, CTRunRef run, CGContextRef context, CGSize size, BOOL isVertical, NSArray *runRanges, CGFloat verticalOffset) { - CGAffineTransform runTextMatrix = CTRunGetTextMatrix(run); - BOOL runTextMatrixIsID = CGAffineTransformIsIdentity(runTextMatrix); - - CFDictionaryRef runAttrs = CTRunGetAttributes(run); - NSValue *glyphTransformValue = (NSValue *)CFDictionaryGetValue(runAttrs, (__bridge const void *)(ASTextGlyphTransformAttributeName)); - if (!isVertical && !glyphTransformValue) { // draw run - if (!runTextMatrixIsID) { - CGContextSaveGState(context); - CGAffineTransform trans = CGContextGetTextMatrix(context); - CGContextSetTextMatrix(context, CGAffineTransformConcat(trans, runTextMatrix)); - } - CTRunDraw(run, context, CFRangeMake(0, 0)); - if (!runTextMatrixIsID) { - CGContextRestoreGState(context); - } - } else { // draw glyph - CTFontRef runFont = (CTFontRef)CFDictionaryGetValue(runAttrs, kCTFontAttributeName); - if (!runFont) return; - NSUInteger glyphCount = CTRunGetGlyphCount(run); - if (glyphCount <= 0) return; - - CGGlyph glyphs[glyphCount]; - CGPoint glyphPositions[glyphCount]; - CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); - CTRunGetPositions(run, CFRangeMake(0, 0), glyphPositions); - - CGColorRef fillColor = (CGColorRef)CFDictionaryGetValue(runAttrs, kCTForegroundColorAttributeName); - fillColor = ASTextGetCGColor(fillColor); - NSNumber *strokeWidth = (NSNumber *)CFDictionaryGetValue(runAttrs, kCTStrokeWidthAttributeName); - - CGContextSaveGState(context); { - CGContextSetFillColorWithColor(context, fillColor); - if (!strokeWidth || strokeWidth.floatValue == 0) { - CGContextSetTextDrawingMode(context, kCGTextFill); - } else { - CGColorRef strokeColor = (CGColorRef)CFDictionaryGetValue(runAttrs, kCTStrokeColorAttributeName); - if (!strokeColor) strokeColor = fillColor; - CGContextSetStrokeColorWithColor(context, strokeColor); - CGContextSetLineWidth(context, CTFontGetSize(runFont) * fabs(strokeWidth.floatValue * 0.01)); - if (strokeWidth.floatValue > 0) { - CGContextSetTextDrawingMode(context, kCGTextStroke); - } else { - CGContextSetTextDrawingMode(context, kCGTextFillStroke); - } - } - - if (isVertical) { - CFIndex runStrIdx[glyphCount + 1]; - CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx); - CFRange runStrRange = CTRunGetStringRange(run); - runStrIdx[glyphCount] = runStrRange.location + runStrRange.length; - CGSize glyphAdvances[glyphCount]; - CTRunGetAdvances(run, CFRangeMake(0, 0), glyphAdvances); - CGFloat ascent = CTFontGetAscent(runFont); - CGFloat descent = CTFontGetDescent(runFont); - CGAffineTransform glyphTransform = glyphTransformValue.CGAffineTransformValue; - CGPoint zeroPoint = CGPointZero; - - for (ASTextRunGlyphRange *oneRange in runRanges) { - NSRange range = oneRange.glyphRangeInRun; - NSUInteger rangeMax = range.location + range.length; - ASTextRunGlyphDrawMode mode = oneRange.drawMode; - - for (NSUInteger g = range.location; g < rangeMax; g++) { - CGContextSaveGState(context); { - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - if (glyphTransformValue) { - CGContextSetTextMatrix(context, glyphTransform); - } - 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 y = -line.position.y + size.height - glyphPositions[g].x - (ofs + w); - if (mode == ASTextRunGlyphDrawModeVerticalRotateMove) { - x += w; - y += w; - } - CGContextSetTextPosition(context, x, y); - } else { - CGContextRotateCTM(context, -M_PI_2); - CGContextSetTextPosition(context, - line.position.y - size.height + glyphPositions[g].x, - line.position.x + verticalOffset + glyphPositions[g].y); - } - - if (ASTextCTFontContainsColorBitmapGlyphs(runFont)) { - CTFontDrawGlyphs(runFont, glyphs + g, &zeroPoint, 1, context); - } else { - CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL); - CGContextSetFont(context, cgFont); - CGContextSetFontSize(context, CTFontGetSize(runFont)); - CGContextShowGlyphsAtPositions(context, glyphs + g, &zeroPoint, 1); - CGFontRelease(cgFont); - } - } CGContextRestoreGState(context); - } - } - } else { // not vertical - if (glyphTransformValue) { - CFIndex runStrIdx[glyphCount + 1]; - CTRunGetStringIndices(run, CFRangeMake(0, 0), runStrIdx); - CFRange runStrRange = CTRunGetStringRange(run); - runStrIdx[glyphCount] = runStrRange.location + runStrRange.length; - CGSize glyphAdvances[glyphCount]; - CTRunGetAdvances(run, CFRangeMake(0, 0), glyphAdvances); - CGAffineTransform glyphTransform = glyphTransformValue.CGAffineTransformValue; - CGPoint zeroPoint = CGPointZero; - - for (NSUInteger g = 0; g < glyphCount; g++) { - CGContextSaveGState(context); { - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextMatrix(context, glyphTransform); - CGContextSetTextPosition(context, - line.position.x + glyphPositions[g].x, - size.height - (line.position.y + glyphPositions[g].y)); - - if (ASTextCTFontContainsColorBitmapGlyphs(runFont)) { - CTFontDrawGlyphs(runFont, glyphs + g, &zeroPoint, 1, context); - } else { - CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL); - CGContextSetFont(context, cgFont); - CGContextSetFontSize(context, CTFontGetSize(runFont)); - CGContextShowGlyphsAtPositions(context, glyphs + g, &zeroPoint, 1); - CGFontRelease(cgFont); - } - } CGContextRestoreGState(context); - } - } else { - if (ASTextCTFontContainsColorBitmapGlyphs(runFont)) { - CTFontDrawGlyphs(runFont, glyphs, glyphPositions, glyphCount, context); - } else { - CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL); - CGContextSetFont(context, cgFont); - CGContextSetFontSize(context, CTFontGetSize(runFont)); - CGContextShowGlyphsAtPositions(context, glyphs, glyphPositions, glyphCount); - CGFontRelease(cgFont); - } - } - } - - } CGContextRestoreGState(context); - } -} - -static void ASTextSetLinePatternInContext(ASTextLineStyle style, CGFloat width, CGFloat phase, CGContextRef context){ - CGContextSetLineWidth(context, width); - CGContextSetLineCap(context, kCGLineCapButt); - CGContextSetLineJoin(context, kCGLineJoinMiter); - - CGFloat dash = 12, dot = 5, space = 3; - NSUInteger pattern = style & 0xF00; - if (pattern == ASTextLineStylePatternSolid) { - CGContextSetLineDash(context, phase, NULL, 0); - } else if (pattern == ASTextLineStylePatternDot) { - CGFloat lengths[2] = {width * dot, width * space}; - CGContextSetLineDash(context, phase, lengths, 2); - } else if (pattern == ASTextLineStylePatternDash) { - CGFloat lengths[2] = {width * dash, width * space}; - CGContextSetLineDash(context, phase, lengths, 2); - } else if (pattern == ASTextLineStylePatternDashDot) { - CGFloat lengths[4] = {width * dash, width * space, width * dot, width * space}; - CGContextSetLineDash(context, phase, lengths, 4); - } else if (pattern == ASTextLineStylePatternDashDotDot) { - CGFloat lengths[6] = {width * dash, width * space,width * dot, width * space, width * dot, width * space}; - CGContextSetLineDash(context, phase, lengths, 6); - } else if (pattern == ASTextLineStylePatternCircleDot) { - CGFloat lengths[2] = {width * 0, width * 3}; - CGContextSetLineDash(context, phase, lengths, 2); - CGContextSetLineCap(context, kCGLineCapRound); - CGContextSetLineJoin(context, kCGLineJoinRound); - } -} - - -static void ASTextDrawBorderRects(CGContextRef context, CGSize size, ASTextBorder *border, NSArray *rects, BOOL isVertical) { - if (rects.count == 0) return; - - ASTextShadow *shadow = border.shadow; - if (shadow.color) { - CGContextSaveGState(context); - CGContextSetShadowWithColor(context, shadow.offset, shadow.radius, shadow.color.CGColor); - CGContextBeginTransparencyLayer(context, NULL); - } - - NSMutableArray *paths = [NSMutableArray new]; - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - if (isVertical) { - rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(border.insets)); - } else { - rect = UIEdgeInsetsInsetRect(rect, border.insets); - } - rect = ASTextCGRectPixelRound(rect); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius]; - [path closePath]; - [paths addObject:path]; - } - - if (border.fillColor) { - CGContextSaveGState(context); - CGContextSetFillColorWithColor(context, border.fillColor.CGColor); - for (UIBezierPath *path in paths) { - CGContextAddPath(context, path.CGPath); - } - CGContextFillPath(context); - CGContextRestoreGState(context); - } - - if (border.strokeColor && border.lineStyle > 0 && border.strokeWidth > 0) { - - //-------------------------- single line ------------------------------// - CGContextSaveGState(context); - for (UIBezierPath *path in paths) { - CGRect bounds = CGRectUnion(path.bounds, (CGRect){CGPointZero, size}); - bounds = CGRectInset(bounds, -2 * border.strokeWidth, -2 * border.strokeWidth); - CGContextAddRect(context, bounds); - CGContextAddPath(context, path.CGPath); - CGContextEOClip(context); - } - [border.strokeColor setStroke]; - ASTextSetLinePatternInContext(border.lineStyle, border.strokeWidth, 0, context); - CGFloat inset = -border.strokeWidth * 0.5; - if ((border.lineStyle & 0xFF) == ASTextLineStyleThick) { - inset *= 2; - CGContextSetLineWidth(context, border.strokeWidth * 2); - } - CGFloat radiusDelta = -inset; - if (border.cornerRadius <= 0) { - radiusDelta = 0; - } - CGContextSetLineJoin(context, border.lineJoin); - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - if (isVertical) { - rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(border.insets)); - } else { - rect = UIEdgeInsetsInsetRect(rect, border.insets); - } - rect = CGRectInset(rect, inset, inset); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + radiusDelta]; - [path closePath]; - CGContextAddPath(context, path.CGPath); - } - CGContextStrokePath(context); - CGContextRestoreGState(context); - - //------------------------- second line ------------------------------// - if ((border.lineStyle & 0xFF) == ASTextLineStyleDouble) { - CGContextSaveGState(context); - CGFloat inset = -border.strokeWidth * 2; - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - rect = UIEdgeInsetsInsetRect(rect, border.insets); - rect = CGRectInset(rect, inset, inset); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + 2 * border.strokeWidth]; - [path closePath]; - - CGRect bounds = CGRectUnion(path.bounds, (CGRect){CGPointZero, size}); - bounds = CGRectInset(bounds, -2 * border.strokeWidth, -2 * border.strokeWidth); - CGContextAddRect(context, bounds); - CGContextAddPath(context, path.CGPath); - CGContextEOClip(context); - } - CGContextSetStrokeColorWithColor(context, border.strokeColor.CGColor); - ASTextSetLinePatternInContext(border.lineStyle, border.strokeWidth, 0, context); - CGContextSetLineJoin(context, border.lineJoin); - inset = -border.strokeWidth * 2.5; - radiusDelta = border.strokeWidth * 2; - if (border.cornerRadius <= 0) { - radiusDelta = 0; - } - for (NSValue *value in rects) { - CGRect rect = value.CGRectValue; - rect = UIEdgeInsetsInsetRect(rect, border.insets); - rect = CGRectInset(rect, inset, inset); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:border.cornerRadius + radiusDelta]; - [path closePath]; - CGContextAddPath(context, path.CGPath); - } - CGContextStrokePath(context); - CGContextRestoreGState(context); - } - } - - if (shadow.color) { - CGContextEndTransparencyLayer(context); - CGContextRestoreGState(context); - } -} - -static void ASTextDrawLineStyle(CGContextRef context, CGFloat length, CGFloat lineWidth, ASTextLineStyle style, CGPoint position, CGColorRef color, BOOL isVertical) { - NSUInteger styleBase = style & 0xFF; - if (styleBase == 0) return; - - CGContextSaveGState(context); { - if (isVertical) { - CGFloat x, y1, y2, w; - y1 = ASRoundPixelValue(position.y); - y2 = ASRoundPixelValue(position.y + length); - w = (styleBase == ASTextLineStyleThick ? lineWidth * 2 : lineWidth); - - CGFloat linePixel = ASTextCGFloatToPixel(w); - if (fabs(linePixel - floor(linePixel)) < 0.1) { - int iPixel = linePixel; - if (iPixel == 0 || (iPixel % 2)) { // odd line pixel - x = ASTextCGFloatPixelHalf(position.x); - } else { - x = ASFloorPixelValue(position.x); - } - } else { - x = position.x; - } - - CGContextSetStrokeColorWithColor(context, color); - ASTextSetLinePatternInContext(style, lineWidth, position.y, context); - CGContextSetLineWidth(context, w); - if (styleBase == ASTextLineStyleSingle) { - CGContextMoveToPoint(context, x, y1); - CGContextAddLineToPoint(context, x, y2); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleThick) { - CGContextMoveToPoint(context, x, y1); - CGContextAddLineToPoint(context, x, y2); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleDouble) { - CGContextMoveToPoint(context, x - w, y1); - CGContextAddLineToPoint(context, x - w, y2); - CGContextStrokePath(context); - CGContextMoveToPoint(context, x + w, y1); - CGContextAddLineToPoint(context, x + w, y2); - CGContextStrokePath(context); - } - } else { - CGFloat x1, x2, y, w; - x1 = ASRoundPixelValue(position.x); - x2 = ASRoundPixelValue(position.x + length); - w = (styleBase == ASTextLineStyleThick ? lineWidth * 2 : lineWidth); - - CGFloat linePixel = ASTextCGFloatToPixel(w); - if (fabs(linePixel - floor(linePixel)) < 0.1) { - int iPixel = linePixel; - if (iPixel == 0 || (iPixel % 2)) { // odd line pixel - y = ASTextCGFloatPixelHalf(position.y); - } else { - y = ASFloorPixelValue(position.y); - } - } else { - y = position.y; - } - - CGContextSetStrokeColorWithColor(context, color); - ASTextSetLinePatternInContext(style, lineWidth, position.x, context); - CGContextSetLineWidth(context, w); - if (styleBase == ASTextLineStyleSingle) { - CGContextMoveToPoint(context, x1, y); - CGContextAddLineToPoint(context, x2, y); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleThick) { - CGContextMoveToPoint(context, x1, y); - CGContextAddLineToPoint(context, x2, y); - CGContextStrokePath(context); - } else if (styleBase == ASTextLineStyleDouble) { - CGContextMoveToPoint(context, x1, y - w); - CGContextAddLineToPoint(context, x2, y - w); - CGContextStrokePath(context); - CGContextMoveToPoint(context, x1, y + w); - CGContextAddLineToPoint(context, x2, y + w); - CGContextStrokePath(context); - } - } - } CGContextRestoreGState(context); -} - -static void ASTextDrawText(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - CGContextSaveGState(context); { - - CGContextTranslateCTM(context, point.x, point.y); - CGContextTranslateCTM(context, 0, size.height); - CGContextScaleCTM(context, 1, -1); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) { - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - NSArray *lineRunRanges = line.verticalRotateRange; - CGFloat posX = line.position.x + verticalOffset; - CGFloat posY = size.height - line.position.y; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextPosition(context, posX, posY); - ASTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset); - } - if (cancel && cancel()) break; - } - - // Use this to draw frame for test/debug. - // CGContextTranslateCTM(context, verticalOffset, size.height); - // CTFrameDraw(layout.frame, context); - - } CGContextRestoreGState(context); -} - -static void ASTextDrawBlockBorder(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - for (NSInteger l = 0, lMax = lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextBorder *border = attrs[ASTextBlockBorderAttributeName]; - if (!border) continue; - - NSUInteger lineStartIndex = line.index; - while (lineStartIndex > 0) { - if (((ASTextLine *)lines[lineStartIndex - 1]).row == line.row) lineStartIndex--; - else break; - } - - CGRect unionRect = CGRectZero; - NSUInteger lineStartRow = ((ASTextLine *)lines[lineStartIndex]).row; - NSUInteger lineContinueIndex = lineStartIndex; - NSUInteger lineContinueRow = lineStartRow; - do { - ASTextLine *one = lines[lineContinueIndex]; - if (lineContinueIndex == lineStartIndex) { - unionRect = one.bounds; - } else { - unionRect = CGRectUnion(unionRect, one.bounds); - } - if (lineContinueIndex + 1 == lMax) break; - ASTextLine *next = lines[lineContinueIndex + 1]; - if (next.row != lineContinueRow) { - ASTextBorder *nextBorder = [layout.text as_attribute:ASTextBlockBorderAttributeName atIndex:next.range.location]; - if ([nextBorder isEqual:border]) { - lineContinueRow++; - } else { - break; - } - } - lineContinueIndex++; - } while (true); - - if (isVertical) { - UIEdgeInsets insets = layout.container.insets; - unionRect.origin.y = insets.top; - unionRect.size.height = layout.container.size.height -insets.top - insets.bottom; - } else { - UIEdgeInsets insets = layout.container.insets; - unionRect.origin.x = insets.left; - unionRect.size.width = layout.container.size.width -insets.left - insets.right; - } - unionRect.origin.x += verticalOffset; - ASTextDrawBorderRects(context, size, border, @[[NSValue valueWithCGRect:unionRect]], isVertical); - - l = lineContinueIndex; - break; - } - } - - - CGContextRestoreGState(context); -} - -static void ASTextDrawBorder(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, ASTextBorderType type, BOOL (^cancel)(void)) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - NSString *borderKey = (type == ASTextBorderTypeNormal ? ASTextBorderAttributeName : ASTextBackgroundBorderAttributeName); - - BOOL needJumpRun = NO; - NSUInteger jumpRunIndex = 0; - - for (NSInteger l = 0, lMax = lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - if (needJumpRun) { - needJumpRun = NO; - r = jumpRunIndex + 1; - if (r >= rMax) break; - } - - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextBorder *border = attrs[borderKey]; - if (!border) continue; - - CFRange runRange = CTRunGetStringRange(run); - if (runRange.location == kCFNotFound || runRange.length == 0) continue; - if (runRange.location + runRange.length > layout.text.length) continue; - - NSMutableArray *runRects = [NSMutableArray new]; - NSInteger endLineIndex = l; - NSInteger endRunIndex = r; - BOOL endFound = NO; - for (NSInteger ll = l; ll < lMax; ll++) { - if (endFound) break; - ASTextLine *iLine = lines[ll]; - CFArrayRef iRuns = CTLineGetGlyphRuns(iLine.CTLine); - - CGRect extLineRect = CGRectNull; - for (NSInteger rr = (ll == l) ? r : 0, rrMax = CFArrayGetCount(iRuns); rr < rrMax; rr++) { - CTRunRef iRun = (CTRunRef)CFArrayGetValueAtIndex(iRuns, rr); - NSDictionary *iAttrs = (id)CTRunGetAttributes(iRun); - ASTextBorder *iBorder = iAttrs[borderKey]; - if (![border isEqual:iBorder]) { - endFound = YES; - break; - } - endLineIndex = ll; - endRunIndex = rr; - - CGPoint iRunPosition = CGPointZero; - CTRunGetPositions(iRun, CFRangeMake(0, 1), &iRunPosition); - CGFloat ascent, descent; - CGFloat iRunWidth = CTRunGetTypographicBounds(iRun, CFRangeMake(0, 0), &ascent, &descent, NULL); - - if (isVertical) { - ASTEXT_SWAP(iRunPosition.x, iRunPosition.y); - iRunPosition.y += iLine.position.y; - CGRect iRect = CGRectMake(verticalOffset + line.position.x - descent, iRunPosition.y, ascent + descent, iRunWidth); - if (CGRectIsNull(extLineRect)) { - extLineRect = iRect; - } else { - extLineRect = CGRectUnion(extLineRect, iRect); - } - } else { - iRunPosition.x += iLine.position.x; - CGRect iRect = CGRectMake(iRunPosition.x, iLine.position.y - ascent, iRunWidth, ascent + descent); - if (CGRectIsNull(extLineRect)) { - extLineRect = iRect; - } else { - extLineRect = CGRectUnion(extLineRect, iRect); - } - } - } - - if (!CGRectIsNull(extLineRect)) { - [runRects addObject:[NSValue valueWithCGRect:extLineRect]]; - } - } - - NSMutableArray *drawRects = [NSMutableArray new]; - CGRect curRect= ((NSValue *)[runRects firstObject]).CGRectValue; - for (NSInteger re = 0, reMax = runRects.count; re < reMax; re++) { - CGRect rect = ((NSValue *)runRects[re]).CGRectValue; - if (isVertical) { - if (fabs(rect.origin.x - curRect.origin.x) < 1) { - curRect = ASTextMergeRectInSameLine(rect, curRect, isVertical); - } else { - [drawRects addObject:[NSValue valueWithCGRect:curRect]]; - curRect = rect; - } - } else { - if (fabs(rect.origin.y - curRect.origin.y) < 1) { - curRect = ASTextMergeRectInSameLine(rect, curRect, isVertical); - } else { - [drawRects addObject:[NSValue valueWithCGRect:curRect]]; - curRect = rect; - } - } - } - if (!CGRectEqualToRect(curRect, CGRectZero)) { - [drawRects addObject:[NSValue valueWithCGRect:curRect]]; - } - - ASTextDrawBorderRects(context, size, border, drawRects, isVertical); - - if (l == endLineIndex) { - r = endRunIndex; - } else { - l = endLineIndex - 1; - needJumpRun = YES; - jumpRunIndex = endRunIndex; - break; - } - - } - } - - CGContextRestoreGState(context); -} - -static void ASTextDrawDecoration(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, ASTextDecorationType type, BOOL (^cancel)(void)) { - NSArray *lines = layout.lines; - - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - CGContextTranslateCTM(context, verticalOffset, 0); - - for (NSUInteger l = 0, lMax = layout.lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextDecoration *underline = attrs[ASTextUnderlineAttributeName]; - ASTextDecoration *strikethrough = attrs[ASTextStrikethroughAttributeName]; - - BOOL needDrawUnderline = NO, needDrawStrikethrough = NO; - if ((type & ASTextDecorationTypeUnderline) && underline.style > 0) { - needDrawUnderline = YES; - } - if ((type & ASTextDecorationTypeStrikethrough) && strikethrough.style > 0) { - needDrawStrikethrough = YES; - } - if (!needDrawUnderline && !needDrawStrikethrough) continue; - - CFRange runRange = CTRunGetStringRange(run); - if (runRange.location == kCFNotFound || runRange.length == 0) continue; - if (runRange.location + runRange.length > layout.text.length) continue; - NSString *runStr = [layout.text attributedSubstringFromRange:NSMakeRange(runRange.location, runRange.length)].string; - if (ASTextIsLinebreakString(runStr)) continue; // may need more checks... - - CGFloat xHeight, underlinePosition, lineThickness; - ASTextGetRunsMaxMetric(runs, &xHeight, &underlinePosition, &lineThickness); - - CGPoint underlineStart, strikethroughStart; - CGFloat length; - - if (isVertical) { - underlineStart.x = line.position.x + underlinePosition; - strikethroughStart.x = line.position.x + xHeight / 2; - - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - underlineStart.y = strikethroughStart.y = runPosition.x + line.position.y; - length = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - - } else { - underlineStart.y = line.position.y - underlinePosition; - strikethroughStart.y = line.position.y - xHeight / 2; - - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - underlineStart.x = strikethroughStart.x = runPosition.x + line.position.x; - length = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), NULL, NULL, NULL); - } - - if (needDrawUnderline) { - CGColorRef color = underline.color.CGColor; - if (!color) { - color = (__bridge CGColorRef)(attrs[(id)kCTForegroundColorAttributeName]); - color = ASTextGetCGColor(color); - } - CGFloat thickness = underline.width ? underline.width.floatValue : lineThickness; - ASTextShadow *shadow = underline.shadow; - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGFloat offsetAlterX = size.width + 0xFFFF; - CGContextSaveGState(context); { - CGSize offset = shadow.offset; - offset.width -= offsetAlterX; - CGContextSaveGState(context); { - CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor); - CGContextSetBlendMode(context, shadow.blendMode); - CGContextTranslateCTM(context, offsetAlterX, 0); - ASTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical); - } CGContextRestoreGState(context); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - ASTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical); - } - - if (needDrawStrikethrough) { - CGColorRef color = strikethrough.color.CGColor; - if (!color) { - color = (__bridge CGColorRef)(attrs[(id)kCTForegroundColorAttributeName]); - color = ASTextGetCGColor(color); - } - CGFloat thickness = strikethrough.width ? strikethrough.width.floatValue : lineThickness; - ASTextShadow *shadow = underline.shadow; - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGFloat offsetAlterX = size.width + 0xFFFF; - CGContextSaveGState(context); { - CGSize offset = shadow.offset; - offset.width -= offsetAlterX; - CGContextSaveGState(context); { - CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor); - CGContextSetBlendMode(context, shadow.blendMode); - CGContextTranslateCTM(context, offsetAlterX, 0); - ASTextDrawLineStyle(context, length, thickness, underline.style, underlineStart, color, isVertical); - } CGContextRestoreGState(context); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - ASTextDrawLineStyle(context, length, thickness, strikethrough.style, strikethroughStart, color, isVertical); - } - } - } - CGContextRestoreGState(context); -} - -static void ASTextDrawAttachment(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, UIView *targetView, CALayer *targetLayer, BOOL (^cancel)(void)) { - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - for (NSUInteger i = 0, max = layout.attachments.count; i < max; i++) { - ASTextAttachment *a = layout.attachments[i]; - if (!a.content) continue; - - UIImage *image = nil; - UIView *view = nil; - CALayer *layer = nil; - if ([a.content isKindOfClass:[UIImage class]]) { - image = a.content; - } else if ([a.content isKindOfClass:[UIView class]]) { - view = a.content; - } else if ([a.content isKindOfClass:[CALayer class]]) { - layer = a.content; - } - if (!image && !view && !layer) continue; - if (image && !context) continue; - if (view && !targetView) continue; - if (layer && !targetLayer) continue; - if (cancel && cancel()) break; - - CGSize asize = image ? image.size : view ? view.frame.size : layer.frame.size; - CGRect rect = ((NSValue *)layout.attachmentRects[i]).CGRectValue; - if (isVertical) { - rect = UIEdgeInsetsInsetRect(rect, UIEdgeInsetRotateVertical(a.contentInsets)); - } else { - rect = UIEdgeInsetsInsetRect(rect, a.contentInsets); - } - rect = ASTextCGRectFitWithContentMode(rect, asize, a.contentMode); - rect = ASTextCGRectPixelRound(rect); - rect = CGRectStandardize(rect); - rect.origin.x += point.x + verticalOffset; - rect.origin.y += point.y; - if (image) { - CGImageRef ref = image.CGImage; - if (ref) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, 0, CGRectGetMaxY(rect) + CGRectGetMinY(rect)); - CGContextScaleCTM(context, 1, -1); - CGContextDrawImage(context, rect, ref); - CGContextRestoreGState(context); - } - } else if (view) { - view.frame = rect; - [targetView addSubview:view]; - } else if (layer) { - layer.frame = rect; - [targetLayer addSublayer:layer]; - } - } -} - -static void ASTextDrawShadow(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - //move out of context. (0xFFFF is just a random large number) - CGFloat offsetAlterX = size.width + 0xFFFF; - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - CGContextSaveGState(context); { - CGContextTranslateCTM(context, point.x, point.y); - CGContextTranslateCTM(context, 0, size.height); - CGContextScaleCTM(context, 1, -1); - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = layout.lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - NSArray *lineRunRanges = line.verticalRotateRange; - CGFloat linePosX = line.position.x; - CGFloat linePosY = size.height - line.position.y; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextPosition(context, linePosX, linePosY); - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextShadow *shadow = attrs[ASTextShadowAttributeName]; - ASTextShadow *nsShadow = [ASTextShadow shadowWithNSShadow:attrs[NSShadowAttributeName]]; // NSShadow compatible - if (nsShadow) { - nsShadow.subShadow = shadow; - shadow = nsShadow; - } - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGSize offset = shadow.offset; - offset.width -= offsetAlterX; - CGContextSaveGState(context); { - CGContextSetShadowWithColor(context, offset, shadow.radius, shadow.color.CGColor); - CGContextSetBlendMode(context, shadow.blendMode); - CGContextTranslateCTM(context, offsetAlterX, 0); - ASTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - } - } - } CGContextRestoreGState(context); -} - -static void ASTextDrawInnerShadow(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, BOOL (^cancel)(void)) { - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - CGContextTranslateCTM(context, 0, size.height); - CGContextScaleCTM(context, 1, -1); - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) { - if (cancel && cancel()) break; - - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - NSArray *lineRunRanges = line.verticalRotateRange; - CGFloat linePosX = line.position.x; - CGFloat linePosY = size.height - line.position.y; - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - if (CTRunGetGlyphCount(run) == 0) continue; - CGContextSetTextMatrix(context, CGAffineTransformIdentity); - CGContextSetTextPosition(context, linePosX, linePosY); - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextShadow *shadow = attrs[ASTextInnerShadowAttributeName]; - while (shadow) { - if (!shadow.color) { - shadow = shadow.subShadow; - continue; - } - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - CGRect runImageBounds = CTRunGetImageBounds(run, context, CFRangeMake(0, 0)); - runImageBounds.origin.x += runPosition.x; - if (runImageBounds.size.width < 0.1 || runImageBounds.size.height < 0.1) continue; - - CFDictionaryRef runAttrs = CTRunGetAttributes(run); - NSValue *glyphTransformValue = (NSValue *)CFDictionaryGetValue(runAttrs, (__bridge const void *)(ASTextGlyphTransformAttributeName)); - if (glyphTransformValue) { - runImageBounds = CGRectMake(0, 0, size.width, size.height); - } - - // text inner shadow - CGContextSaveGState(context); { - CGContextSetBlendMode(context, shadow.blendMode); - CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL); - CGContextSetAlpha(context, CGColorGetAlpha(shadow.color.CGColor)); - CGContextClipToRect(context, runImageBounds); - CGContextBeginTransparencyLayer(context, NULL); { - UIColor *opaqueShadowColor = [shadow.color colorWithAlphaComponent:1]; - CGContextSetShadowWithColor(context, shadow.offset, shadow.radius, opaqueShadowColor.CGColor); - CGContextSetFillColorWithColor(context, opaqueShadowColor.CGColor); - CGContextSetBlendMode(context, kCGBlendModeSourceOut); - CGContextBeginTransparencyLayer(context, NULL); { - CGContextFillRect(context, runImageBounds); - CGContextSetBlendMode(context, kCGBlendModeDestinationIn); - CGContextBeginTransparencyLayer(context, NULL); { - ASTextDrawRun(line, run, context, size, isVertical, lineRunRanges[r], verticalOffset); - } CGContextEndTransparencyLayer(context); - } CGContextEndTransparencyLayer(context); - } CGContextEndTransparencyLayer(context); - } CGContextRestoreGState(context); - shadow = shadow.subShadow; - } - } - } - - CGContextRestoreGState(context); -} - -static void ASTextDrawDebug(ASTextLayout *layout, CGContextRef context, CGSize size, CGPoint point, ASTextDebugOption *op) { - UIGraphicsPushContext(context); - CGContextSaveGState(context); - CGContextTranslateCTM(context, point.x, point.y); - CGContextSetLineWidth(context, 1.0 / ASScreenScale()); - CGContextSetLineDash(context, 0, NULL, 0); - CGContextSetLineJoin(context, kCGLineJoinMiter); - CGContextSetLineCap(context, kCGLineCapButt); - - BOOL isVertical = layout.container.verticalForm; - CGFloat verticalOffset = isVertical ? (size.width - layout.container.size.width) : 0; - CGContextTranslateCTM(context, verticalOffset, 0); - - if (op.CTFrameBorderColor || op.CTFrameFillColor) { - UIBezierPath *path = layout.container.path; - if (!path) { - CGRect rect = (CGRect){CGPointZero, layout.container.size}; - rect = UIEdgeInsetsInsetRect(rect, layout.container.insets); - if (op.CTFrameBorderColor) rect = ASTextCGRectPixelHalf(rect); - else rect = ASTextCGRectPixelRound(rect); - path = [UIBezierPath bezierPathWithRect:rect]; - } - [path closePath]; - - for (UIBezierPath *ex in layout.container.exclusionPaths) { - [path appendPath:ex]; - } - if (op.CTFrameFillColor) { - [op.CTFrameFillColor setFill]; - if (layout.container.pathLineWidth > 0) { - CGContextSaveGState(context); { - CGContextBeginTransparencyLayer(context, NULL); { - CGContextAddPath(context, path.CGPath); - if (layout.container.pathFillEvenOdd) { - CGContextEOFillPath(context); - } else { - CGContextFillPath(context); - } - CGContextSetBlendMode(context, kCGBlendModeDestinationOut); - [[UIColor blackColor] setFill]; - CGPathRef cgPath = CGPathCreateCopyByStrokingPath(path.CGPath, NULL, layout.container.pathLineWidth, kCGLineCapButt, kCGLineJoinMiter, 0); - if (cgPath) { - CGContextAddPath(context, cgPath); - CGContextFillPath(context); - } - CGPathRelease(cgPath); - } CGContextEndTransparencyLayer(context); - } CGContextRestoreGState(context); - } else { - CGContextAddPath(context, path.CGPath); - if (layout.container.pathFillEvenOdd) { - CGContextEOFillPath(context); - } else { - CGContextFillPath(context); - } - } - } - if (op.CTFrameBorderColor) { - CGContextSaveGState(context); { - if (layout.container.pathLineWidth > 0) { - CGContextSetLineWidth(context, layout.container.pathLineWidth); - } - [op.CTFrameBorderColor setStroke]; - CGContextAddPath(context, path.CGPath); - CGContextStrokePath(context); - } CGContextRestoreGState(context); - } - } - - NSArray *lines = layout.lines; - for (NSUInteger l = 0, lMax = lines.count; l < lMax; l++) { - ASTextLine *line = lines[l]; - if (layout.truncatedLine && layout.truncatedLine.index == line.index) line = layout.truncatedLine; - CGRect lineBounds = line.bounds; - if (op.CTLineFillColor) { - [op.CTLineFillColor setFill]; - CGContextAddRect(context, ASTextCGRectPixelRound(lineBounds)); - CGContextFillPath(context); - } - if (op.CTLineBorderColor) { - [op.CTLineBorderColor setStroke]; - CGContextAddRect(context, ASTextCGRectPixelHalf(lineBounds)); - CGContextStrokePath(context); - } - if (op.baselineColor) { - [op.baselineColor setStroke]; - if (isVertical) { - CGFloat x = ASTextCGFloatPixelHalf(line.position.x); - CGFloat y1 = ASTextCGFloatPixelHalf(line.top); - CGFloat y2 = ASTextCGFloatPixelHalf(line.bottom); - CGContextMoveToPoint(context, x, y1); - CGContextAddLineToPoint(context, x, y2); - CGContextStrokePath(context); - } else { - CGFloat x1 = ASTextCGFloatPixelHalf(lineBounds.origin.x); - CGFloat x2 = ASTextCGFloatPixelHalf(lineBounds.origin.x + lineBounds.size.width); - CGFloat y = ASTextCGFloatPixelHalf(line.position.y); - CGContextMoveToPoint(context, x1, y); - CGContextAddLineToPoint(context, x2, y); - CGContextStrokePath(context); - } - } - if (op.CTLineNumberColor) { - [op.CTLineNumberColor set]; - NSMutableAttributedString *num = [[NSMutableAttributedString alloc] initWithString:@(l).description]; - num.as_color = op.CTLineNumberColor; - num.as_font = [UIFont systemFontOfSize:6]; - [num drawAtPoint:CGPointMake(line.position.x, line.position.y - (isVertical ? 1 : 6))]; - } - if (op.CTRunFillColor || op.CTRunBorderColor || op.CTRunNumberColor || op.CGGlyphFillColor || op.CGGlyphBorderColor) { - CFArrayRef runs = CTLineGetGlyphRuns(line.CTLine); - for (NSUInteger r = 0, rMax = CFArrayGetCount(runs); r < rMax; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - - CGPoint glyphPositions[glyphCount]; - CTRunGetPositions(run, CFRangeMake(0, glyphCount), glyphPositions); - - CGSize glyphAdvances[glyphCount]; - CTRunGetAdvances(run, CFRangeMake(0, glyphCount), glyphAdvances); - - CGPoint runPosition = glyphPositions[0]; - if (isVertical) { - ASTEXT_SWAP(runPosition.x, runPosition.y); - runPosition.x = line.position.x; - runPosition.y += line.position.y; - } else { - runPosition.x += line.position.x; - runPosition.y = line.position.y - runPosition.y; - } - - CGFloat ascent, descent, leading; - CGFloat width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading); - CGRect runTypoBounds; - if (isVertical) { - runTypoBounds = CGRectMake(runPosition.x - descent, runPosition.y, ascent + descent, width); - } else { - runTypoBounds = CGRectMake(runPosition.x, line.position.y - ascent, width, ascent + descent); - } - - if (op.CTRunFillColor) { - [op.CTRunFillColor setFill]; - CGContextAddRect(context, ASTextCGRectPixelRound(runTypoBounds)); - CGContextFillPath(context); - } - if (op.CTRunBorderColor) { - [op.CTRunBorderColor setStroke]; - CGContextAddRect(context, ASTextCGRectPixelHalf(runTypoBounds)); - CGContextStrokePath(context); - } - if (op.CTRunNumberColor) { - [op.CTRunNumberColor set]; - NSMutableAttributedString *num = [[NSMutableAttributedString alloc] initWithString:@(r).description]; - num.as_color = op.CTRunNumberColor; - num.as_font = [UIFont systemFontOfSize:6]; - [num drawAtPoint:CGPointMake(runTypoBounds.origin.x, runTypoBounds.origin.y - 1)]; - } - if (op.CGGlyphBorderColor || op.CGGlyphFillColor) { - for (NSUInteger g = 0; g < glyphCount; g++) { - CGPoint pos = glyphPositions[g]; - CGSize adv = glyphAdvances[g]; - CGRect rect; - if (isVertical) { - ASTEXT_SWAP(pos.x, pos.y); - pos.x = runPosition.x; - pos.y += line.position.y; - rect = CGRectMake(pos.x - descent, pos.y, runTypoBounds.size.width, adv.width); - } else { - pos.x += line.position.x; - pos.y = runPosition.y; - rect = CGRectMake(pos.x, pos.y - ascent, adv.width, runTypoBounds.size.height); - } - if (op.CGGlyphFillColor) { - [op.CGGlyphFillColor setFill]; - CGContextAddRect(context, ASTextCGRectPixelRound(rect)); - CGContextFillPath(context); - } - if (op.CGGlyphBorderColor) { - [op.CGGlyphBorderColor setStroke]; - CGContextAddRect(context, ASTextCGRectPixelHalf(rect)); - CGContextStrokePath(context); - } - } - } - } - } - } - CGContextRestoreGState(context); - UIGraphicsPopContext(); -} - - -- (void)drawInContext:(CGContextRef)context - size:(CGSize)size - point:(CGPoint)point - view:(UIView *)view - layer:(CALayer *)layer - debug:(ASTextDebugOption *)debug - cancel:(BOOL (^)(void))cancel{ - @autoreleasepool { - if (self.needDrawBlockBorder && context) { - if (cancel && cancel()) return; - ASTextDrawBlockBorder(self, context, size, point, cancel); - } - if (self.needDrawBackgroundBorder && context) { - if (cancel && cancel()) return; - ASTextDrawBorder(self, context, size, point, ASTextBorderTypeBackgound, cancel); - } - if (self.needDrawShadow && context) { - if (cancel && cancel()) return; - ASTextDrawShadow(self, context, size, point, cancel); - } - if (self.needDrawUnderline && context) { - if (cancel && cancel()) return; - ASTextDrawDecoration(self, context, size, point, ASTextDecorationTypeUnderline, cancel); - } - if (self.needDrawText && context) { - if (cancel && cancel()) return; - ASTextDrawText(self, context, size, point, cancel); - } - if (self.needDrawAttachment && (context || view || layer)) { - if (cancel && cancel()) return; - ASTextDrawAttachment(self, context, size, point, view, layer, cancel); - } - if (self.needDrawInnerShadow && context) { - if (cancel && cancel()) return; - ASTextDrawInnerShadow(self, context, size, point, cancel); - } - if (self.needDrawStrikethrough && context) { - if (cancel && cancel()) return; - ASTextDrawDecoration(self, context, size, point, ASTextDecorationTypeStrikethrough, cancel); - } - if (self.needDrawBorder && context) { - if (cancel && cancel()) return; - ASTextDrawBorder(self, context, size, point, ASTextBorderTypeNormal, cancel); - } - if (debug.needDrawDebug && context) { - if (cancel && cancel()) return; - ASTextDrawDebug(self, context, size, point, debug); - } - } -} - -- (void)drawInContext:(CGContextRef)context - size:(CGSize)size - debug:(ASTextDebugOption *)debug { - [self drawInContext:context size:size point:CGPointZero view:nil layer:nil debug:debug cancel:nil]; -} - -@end diff --git a/Source/TextExperiment/Component/ASTextLine.h b/Source/TextExperiment/Component/ASTextLine.h deleted file mode 100644 index acb02e991b..0000000000 --- a/Source/TextExperiment/Component/ASTextLine.h +++ /dev/null @@ -1,77 +0,0 @@ -// -// ASTextLine.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@class ASTextRunGlyphRange; - -NS_ASSUME_NONNULL_BEGIN - -/** - A text line object wrapped `CTLineRef`, see `ASTextLayout` for more. - */ -@interface ASTextLine : NSObject - -+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical NS_RETURNS_RETAINED; - -@property (nonatomic) NSUInteger index; ///< line index -@property (nonatomic) NSUInteger row; ///< line row -@property (nullable, nonatomic) NSArray *> *verticalRotateRange; ///< Run rotate range - -@property (nonatomic, readonly) CTLineRef CTLine; ///< CoreText line -@property (nonatomic, readonly) NSRange range; ///< string range -@property (nonatomic, readonly) BOOL vertical; ///< vertical form - -@property (nonatomic, readonly) CGRect bounds; ///< bounds (ascent + descent) -@property (nonatomic, readonly) CGSize size; ///< bounds.size -@property (nonatomic, readonly) CGFloat width; ///< bounds.size.width -@property (nonatomic, readonly) CGFloat height; ///< bounds.size.height -@property (nonatomic, readonly) CGFloat top; ///< bounds.origin.y -@property (nonatomic, readonly) CGFloat bottom; ///< bounds.origin.y + bounds.size.height -@property (nonatomic, readonly) CGFloat left; ///< bounds.origin.x -@property (nonatomic, readonly) CGFloat right; ///< bounds.origin.x + bounds.size.width - -@property (nonatomic) CGPoint position; ///< baseline position -@property (nonatomic, readonly) CGFloat ascent; ///< line ascent -@property (nonatomic, readonly) CGFloat descent; ///< line descent -@property (nonatomic, readonly) CGFloat leading; ///< line leading -@property (nonatomic, readonly) CGFloat lineWidth; ///< line width -@property (nonatomic, readonly) CGFloat trailingWhitespaceWidth; - -@property (nullable, nonatomic, readonly) NSArray *attachments; ///< ASTextAttachment -@property (nullable, nonatomic, readonly) NSArray *attachmentRanges; ///< NSRange(NSValue) -@property (nullable, nonatomic, readonly) NSArray *attachmentRects; ///< CGRect(NSValue) - -@end - - -typedef NS_ENUM(NSUInteger, ASTextRunGlyphDrawMode) { - /// No rotate. - ASTextRunGlyphDrawModeHorizontal = 0, - - /// Rotate vertical for single glyph. - ASTextRunGlyphDrawModeVerticalRotate = 1, - - /// Rotate vertical for single glyph, and move the glyph to a better position, - /// such as fullwidth punctuation. - ASTextRunGlyphDrawModeVerticalRotateMove = 2, -}; - -/** - A range in CTRun, used for vertical form. - */ -@interface ASTextRunGlyphRange : NSObject -@property (nonatomic) NSRange glyphRangeInRun; -@property (nonatomic) ASTextRunGlyphDrawMode drawMode; -+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode NS_RETURNS_RETAINED; -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/TextExperiment/Component/ASTextLine.mm b/Source/TextExperiment/Component/ASTextLine.mm deleted file mode 100644 index 37c68e02f5..0000000000 --- a/Source/TextExperiment/Component/ASTextLine.mm +++ /dev/null @@ -1,169 +0,0 @@ -// -// ASTextLine.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@implementation ASTextLine { - CGFloat _firstGlyphPos; // first glyph position for baseline, typically 0. -} - -+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical NS_RETURNS_RETAINED { - if (!CTLine) return nil; - ASTextLine *line = [self new]; - line->_position = position; - line->_vertical = isVertical; - [line setCTLine:CTLine]; - return line; -} - -- (void)dealloc { - if (_CTLine) CFRelease(_CTLine); -} - -- (void)setCTLine:(_Nonnull CTLineRef)CTLine { - if (_CTLine != CTLine) { - if (CTLine) CFRetain(CTLine); - if (_CTLine) CFRelease(_CTLine); - _CTLine = CTLine; - if (_CTLine) { - _lineWidth = CTLineGetTypographicBounds(_CTLine, &_ascent, &_descent, &_leading); - CFRange range = CTLineGetStringRange(_CTLine); - _range = NSMakeRange(range.location, range.length); - if (CTLineGetGlyphCount(_CTLine) > 0) { - CFArrayRef runs = CTLineGetGlyphRuns(_CTLine); - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, 0); - CGPoint pos; - CTRunGetPositions(run, CFRangeMake(0, 1), &pos); - _firstGlyphPos = pos.x; - } else { - _firstGlyphPos = 0; - } - _trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(_CTLine); - } else { - _lineWidth = _ascent = _descent = _leading = _firstGlyphPos = _trailingWhitespaceWidth = 0; - _range = NSMakeRange(0, 0); - } - [self reloadBounds]; - } -} - -- (void)setPosition:(CGPoint)position { - _position = position; - [self reloadBounds]; -} - -- (void)reloadBounds { - if (_vertical) { - _bounds = CGRectMake(_position.x - _descent, _position.y, _ascent + _descent, _lineWidth); - _bounds.origin.y += _firstGlyphPos; - } else { - _bounds = CGRectMake(_position.x, _position.y - _ascent, _lineWidth, _ascent + _descent); - _bounds.origin.x += _firstGlyphPos; - } - - _attachments = nil; - _attachmentRanges = nil; - _attachmentRects = nil; - if (!_CTLine) return; - CFArrayRef runs = CTLineGetGlyphRuns(_CTLine); - NSUInteger runCount = CFArrayGetCount(runs); - if (runCount == 0) return; - - NSMutableArray *attachments = nil; - NSMutableArray *attachmentRanges = nil; - NSMutableArray *attachmentRects = nil; - for (NSUInteger r = 0; r < runCount; r++) { - CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runs, r); - CFIndex glyphCount = CTRunGetGlyphCount(run); - if (glyphCount == 0) continue; - NSDictionary *attrs = (id)CTRunGetAttributes(run); - ASTextAttachment *attachment = attrs[ASTextAttachmentAttributeName]; - if (attachment) { - CGPoint runPosition = CGPointZero; - CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition); - - CGFloat ascent, descent, leading, runWidth; - CGRect runTypoBounds; - runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading); - - if (_vertical) { - ASTEXT_SWAP(runPosition.x, runPosition.y); - runPosition.y = _position.y + runPosition.y; - runTypoBounds = CGRectMake(_position.x + runPosition.x - descent, runPosition.y , ascent + descent, runWidth); - } else { - runPosition.x += _position.x; - runPosition.y = _position.y - runPosition.y; - runTypoBounds = CGRectMake(runPosition.x, runPosition.y - ascent, runWidth, ascent + descent); - } - - NSRange runRange = ASTextNSRangeFromCFRange(CTRunGetStringRange(run)); - if (!attachments) { - attachments = [[NSMutableArray alloc] init]; - attachmentRanges = [[NSMutableArray alloc] init]; - attachmentRects = [[NSMutableArray alloc] init]; - } - [attachments addObject:attachment]; - [attachmentRanges addObject:[NSValue valueWithRange:runRange]]; - [attachmentRects addObject:[NSValue valueWithCGRect:runTypoBounds]]; - } - } - _attachments = attachments; - _attachmentRanges = attachmentRanges; - _attachmentRects = attachmentRects; -} - -- (CGSize)size { - return _bounds.size; -} - -- (CGFloat)width { - return CGRectGetWidth(_bounds); -} - -- (CGFloat)height { - return CGRectGetHeight(_bounds); -} - -- (CGFloat)top { - return CGRectGetMinY(_bounds); -} - -- (CGFloat)bottom { - return CGRectGetMaxY(_bounds); -} - -- (CGFloat)left { - return CGRectGetMinX(_bounds); -} - -- (CGFloat)right { - return CGRectGetMaxX(_bounds); -} - -- (NSString *)description { - NSMutableString *desc = @"".mutableCopy; - NSRange range = self.range; - [desc appendFormat:@" row:%ld range:%tu,%tu", self, (long)self.row, range.location, range.length]; - [desc appendFormat:@" position:%@",NSStringFromCGPoint(self.position)]; - [desc appendFormat:@" bounds:%@",NSStringFromCGRect(self.bounds)]; - return desc; -} - -@end - - -@implementation ASTextRunGlyphRange -+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode NS_RETURNS_RETAINED { - ASTextRunGlyphRange *one = [self new]; - one.glyphRangeInRun = range; - one.drawMode = mode; - return one; -} -@end diff --git a/Source/TextExperiment/String/ASTextAttribute.h b/Source/TextExperiment/String/ASTextAttribute.h deleted file mode 100644 index 571334ed3e..0000000000 --- a/Source/TextExperiment/String/ASTextAttribute.h +++ /dev/null @@ -1,346 +0,0 @@ -// -// ASTextAttribute.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -#pragma mark - Enum Define - -/// The attribute type -typedef NS_OPTIONS(NSInteger, ASTextAttributeType) { - ASTextAttributeTypeNone = 0, - ASTextAttributeTypeUIKit = 1 << 0, ///< UIKit attributes, such as UILabel/UITextField/drawInRect. - ASTextAttributeTypeCoreText = 1 << 1, ///< CoreText attributes, used by CoreText. - ASTextAttributeTypeASText = 1 << 2, ///< ASText attributes, used by ASText. -}; - -/// Get the attribute type from an attribute name. -ASDK_EXTERN ASTextAttributeType ASTextAttributeGetType(NSString *attributeName); - -/** - Line style in ASText (similar to NSUnderlineStyle). - */ -typedef NS_OPTIONS (NSInteger, ASTextLineStyle) { - // basic style (bitmask:0xFF) - ASTextLineStyleNone = 0x00, ///< ( ) Do not draw a line (Default). - ASTextLineStyleSingle = 0x01, ///< (──────) Draw a single line. - ASTextLineStyleThick = 0x02, ///< (━━━━━━━) Draw a thick line. - ASTextLineStyleDouble = 0x09, ///< (══════) Draw a double line. - - // style pattern (bitmask:0xF00) - ASTextLineStylePatternSolid = 0x000, ///< (────────) Draw a solid line (Default). - ASTextLineStylePatternDot = 0x100, ///< (‑ ‑ ‑ ‑ ‑ ‑) Draw a line of dots. - ASTextLineStylePatternDash = 0x200, ///< (— — — —) Draw a line of dashes. - ASTextLineStylePatternDashDot = 0x300, ///< (— ‑ — ‑ — ‑) Draw a line of alternating dashes and dots. - ASTextLineStylePatternDashDotDot = 0x400, ///< (— ‑ ‑ — ‑ ‑) Draw a line of alternating dashes and two dots. - ASTextLineStylePatternCircleDot = 0x900, ///< (••••••••••••) Draw a line of small circle dots. -}; - -/** - Text vertical alignment. - */ -typedef NS_ENUM(NSInteger, ASTextVerticalAlignment) { - ASTextVerticalAlignmentTop = 0, ///< Top alignment. - ASTextVerticalAlignmentCenter = 1, ///< Center alignment. - ASTextVerticalAlignmentBottom = 2, ///< Bottom alignment. -}; - -/** - The direction define in ASText. - */ -typedef NS_OPTIONS(NSUInteger, ASTextDirection) { - ASTextDirectionNone = 0, - ASTextDirectionTop = 1 << 0, - ASTextDirectionRight = 1 << 1, - ASTextDirectionBottom = 1 << 2, - ASTextDirectionLeft = 1 << 3, -}; - -/** - The trunction type, tells the truncation engine which type of truncation is being requested. - */ -typedef NS_ENUM (NSUInteger, ASTextTruncationType) { - /// No truncate. - ASTextTruncationTypeNone = 0, - - /// Truncate at the beginning of the line, leaving the end portion visible. - ASTextTruncationTypeStart = 1, - - /// Truncate at the end of the line, leaving the start portion visible. - ASTextTruncationTypeEnd = 2, - - /// Truncate in the middle of the line, leaving both the start and the end portions visible. - ASTextTruncationTypeMiddle = 3, -}; - - - -#pragma mark - Attribute Name Defined in ASText - -/// The value of this attribute is a `ASTextBackedString` object. -/// Use this attribute to store the original plain text if it is replaced by something else (such as attachment). -UIKIT_EXTERN NSString *const ASTextBackedStringAttributeName; - -/// The value of this attribute is a `ASTextBinding` object. -/// Use this attribute to bind a range of text together, as if it was a single charactor. -UIKIT_EXTERN NSString *const ASTextBindingAttributeName; - -/// The value of this attribute is a `ASTextShadow` object. -/// Use this attribute to add shadow to a range of text. -/// Shadow will be drawn below text glyphs. Use ASTextShadow.subShadow to add multi-shadow. -UIKIT_EXTERN NSString *const ASTextShadowAttributeName; - -/// The value of this attribute is a `ASTextShadow` object. -/// Use this attribute to add inner shadow to a range of text. -/// Inner shadow will be drawn above text glyphs. Use ASTextShadow.subShadow to add multi-shadow. -UIKIT_EXTERN NSString *const ASTextInnerShadowAttributeName; - -/// The value of this attribute is a `ASTextDecoration` object. -/// Use this attribute to add underline to a range of text. -/// The underline will be drawn below text glyphs. -UIKIT_EXTERN NSString *const ASTextUnderlineAttributeName; - -/// The value of this attribute is a `ASTextDecoration` object. -/// Use this attribute to add strikethrough (delete line) to a range of text. -/// The strikethrough will be drawn above text glyphs. -UIKIT_EXTERN NSString *const ASTextStrikethroughAttributeName; - -/// The value of this attribute is a `ASTextBorder` object. -/// Use this attribute to add cover border or cover color to a range of text. -/// The border will be drawn above the text glyphs. -UIKIT_EXTERN NSString *const ASTextBorderAttributeName; - -/// The value of this attribute is a `ASTextBorder` object. -/// Use this attribute to add background border or background color to a range of text. -/// The border will be drawn below the text glyphs. -UIKIT_EXTERN NSString *const ASTextBackgroundBorderAttributeName; - -/// The value of this attribute is a `ASTextBorder` object. -/// Use this attribute to add a code block border to one or more line of text. -/// The border will be drawn below the text glyphs. -UIKIT_EXTERN NSString *const ASTextBlockBorderAttributeName; - -/// The value of this attribute is a `ASTextAttachment` object. -/// Use this attribute to add attachment to text. -/// It should be used in conjunction with a CTRunDelegate. -UIKIT_EXTERN NSString *const ASTextAttachmentAttributeName; - -/// The value of this attribute is a `ASTextHighlight` object. -/// Use this attribute to add a touchable highlight state to a range of text. -UIKIT_EXTERN NSString *const ASTextHighlightAttributeName; - -/// The value of this attribute is a `NSValue` object stores CGAffineTransform. -/// Use this attribute to add transform to each glyph in a range of text. -UIKIT_EXTERN NSString *const ASTextGlyphTransformAttributeName; - - - -#pragma mark - String Token Define - -UIKIT_EXTERN NSString *const ASTextAttachmentToken; ///< Object replacement character (U+FFFC), used for text attachment. -UIKIT_EXTERN NSString *const ASTextTruncationToken; ///< Horizontal ellipsis (U+2026), used for text truncation "…". - - - -#pragma mark - Attribute Value Define - -/** - The tap/long press action callback defined in ASText. - - @param containerView The text container view (such as ASLabel/ASTextView). - @param text The whole text. - @param range The text range in `text` (if no range, the range.location is NSNotFound). - @param rect The text frame in `containerView` (if no data, the rect is CGRectNull). - */ -typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSRange range, CGRect rect); - - -/** - ASTextBackedString objects are used by the NSAttributedString class cluster - as the values for text backed string attributes (stored in the attributed - string under the key named ASTextBackedStringAttributeName). - - It may used for copy/paste plain text from attributed string. - Example: If :) is replace by a custom emoji (such as😊), the backed string can be set to @":)". - */ -@interface ASTextBackedString : NSObject -+ (instancetype)stringWithString:(nullable NSString *)string NS_RETURNS_RETAINED; -@property (nullable, nonatomic, copy) NSString *string; ///< backed string -@end - - -/** - ASTextBinding objects are used by the NSAttributedString class cluster - as the values for shadow attributes (stored in the attributed string under - the key named ASTextBindingAttributeName). - - Add this to a range of text will make the specified characters 'binding together'. - ASTextView will treat the range of text as a single character during text - selection and edit. - */ -@interface ASTextBinding : NSObject -+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm NS_RETURNS_RETAINED; -@property (nonatomic) BOOL deleteConfirm; ///< confirm the range when delete in ASTextView -@end - - -/** - ASTextShadow objects are used by the NSAttributedString class cluster - as the values for shadow attributes (stored in the attributed string under - the key named ASTextShadowAttributeName or ASTextInnerShadowAttributeName). - - It's similar to `NSShadow`, but offers more options. - */ -@interface ASTextShadow : NSObject -+ (instancetype)shadowWithColor:(nullable UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius NS_RETURNS_RETAINED; - -@property (nullable, nonatomic) UIColor *color; ///< shadow color -@property (nonatomic) CGSize offset; ///< shadow offset -@property (nonatomic) CGFloat radius; ///< shadow blur radius -@property (nonatomic) CGBlendMode blendMode; ///< shadow blend mode -@property (nullable, nonatomic) ASTextShadow *subShadow; ///< a sub shadow which will be added above the parent shadow - -+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow NS_RETURNS_RETAINED; ///< convert NSShadow to ASTextShadow -- (NSShadow *)nsShadow; ///< convert ASTextShadow to NSShadow -@end - - -/** - ASTextDecorationLine objects are used by the NSAttributedString class cluster - as the values for decoration line attributes (stored in the attributed string under - the key named ASTextUnderlineAttributeName or ASTextStrikethroughAttributeName). - - When it's used as underline, the line is drawn below text glyphs; - when it's used as strikethrough, the line is drawn above text glyphs. - */ -@interface ASTextDecoration : NSObject -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style NS_RETURNS_RETAINED; -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(nullable NSNumber *)width color:(nullable UIColor *)color NS_RETURNS_RETAINED; -@property (nonatomic) ASTextLineStyle style; ///< line style -@property (nullable, nonatomic) NSNumber *width; ///< line width (nil means automatic width) -@property (nullable, nonatomic) UIColor *color; ///< line color (nil means automatic color) -@property (nullable, nonatomic) ASTextShadow *shadow; ///< line shadow -@end - - -/** - ASTextBorder objects are used by the NSAttributedString class cluster - as the values for border attributes (stored in the attributed string under - the key named ASTextBorderAttributeName or ASTextBackgroundBorderAttributeName). - - It can be used to draw a border around a range of text, or draw a background - to a range of text. - - Example: - ╭──────╮ - │ Text │ - ╰──────╯ - */ -@interface ASTextBorder : NSObject -+ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(nullable UIColor *)color NS_RETURNS_RETAINED; -+ (instancetype)borderWithFillColor:(nullable UIColor *)color cornerRadius:(CGFloat)cornerRadius NS_RETURNS_RETAINED; -@property (nonatomic) ASTextLineStyle lineStyle; ///< border line style -@property (nonatomic) CGFloat strokeWidth; ///< border line width -@property (nullable, nonatomic) UIColor *strokeColor; ///< border line color -@property (nonatomic) CGLineJoin lineJoin; ///< border line join -@property (nonatomic) UIEdgeInsets insets; ///< border insets for text bounds -@property (nonatomic) CGFloat cornerRadius; ///< border corder radius -@property (nullable, nonatomic) ASTextShadow *shadow; ///< border shadow -@property (nullable, nonatomic) UIColor *fillColor; ///< inner fill color -@end - - -/** - ASTextAttachment objects are used by the NSAttributedString class cluster - as the values for attachment attributes (stored in the attributed string under - the key named ASTextAttachmentAttributeName). - - When display an attributed string which contains `ASTextAttachment` object, - the content will be placed in text metric. If the content is `UIImage`, - then it will be drawn to CGContext; if the content is `UIView` or `CALayer`, - then it will be added to the text container's view or layer. - */ -@interface ASTextAttachment : NSObject -+ (instancetype)attachmentWithContent:(nullable id)content NS_RETURNS_RETAINED; -@property (nullable, nonatomic) id content; ///< Supported type: UIImage, UIView, CALayer -@property (nonatomic) UIViewContentMode contentMode; ///< Content display mode. -@property (nonatomic) UIEdgeInsets contentInsets; ///< The insets when drawing content. -@property (nullable, nonatomic) NSDictionary *userInfo; ///< The user information dictionary. -@end - - -/** - ASTextHighlight objects are used by the NSAttributedString class cluster - as the values for touchable highlight attributes (stored in the attributed string - under the key named ASTextHighlightAttributeName). - - When display an attributed string in `ASLabel` or `ASTextView`, the range of - highlight text can be toucheds down by users. If a range of text is turned into - highlighted state, the `attributes` in `ASTextHighlight` will be used to modify - (set or remove) the original attributes in the range for display. - */ -@interface ASTextHighlight : NSObject - -/** - Attributes that you can apply to text in an attributed string when highlight. - Key: Same as CoreText/ASText Attribute Name. - Value: Modify attribute value when highlight (NSNull for remove attribute). - */ -@property (nullable, nonatomic, copy) NSDictionary *attributes; - -/** - Creates a highlight object with specified attributes. - - @param attributes The attributes which will replace original attributes when highlight, - If the value is NSNull, it will removed when highlight. - */ -+ (instancetype)highlightWithAttributes:(nullable NSDictionary *)attributes NS_RETURNS_RETAINED; - -/** - Convenience methods to create a default highlight with the specifeid background color. - - @param color The background border color. - */ -+ (instancetype)highlightWithBackgroundColor:(nullable UIColor *)color NS_RETURNS_RETAINED; - -// Convenience methods below to set the `attributes`. -- (void)setFont:(nullable UIFont *)font; -- (void)setColor:(nullable UIColor *)color; -- (void)setStrokeWidth:(nullable NSNumber *)width; -- (void)setStrokeColor:(nullable UIColor *)color; -- (void)setShadow:(nullable ASTextShadow *)shadow; -- (void)setInnerShadow:(nullable ASTextShadow *)shadow; -- (void)setUnderline:(nullable ASTextDecoration *)underline; -- (void)setStrikethrough:(nullable ASTextDecoration *)strikethrough; -- (void)setBackgroundBorder:(nullable ASTextBorder *)border; -- (void)setBorder:(nullable ASTextBorder *)border; -- (void)setAttachment:(nullable ASTextAttachment *)attachment; - -/** - The user information dictionary, default is nil. - */ -@property (nullable, nonatomic, copy) NSDictionary *userInfo; - -/** - Tap action when user tap the highlight, default is nil. - If the value is nil, ASTextView or ASLabel will ask it's delegate to handle the tap action. - */ -@property (nullable, nonatomic) ASTextAction tapAction; - -/** - Long press action when user long press the highlight, default is nil. - If the value is nil, ASTextView or ASLabel will ask it's delegate to handle the long press action. - */ -@property (nullable, nonatomic) ASTextAction longPressAction; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/TextExperiment/String/ASTextAttribute.mm b/Source/TextExperiment/String/ASTextAttribute.mm deleted file mode 100644 index 8af4322717..0000000000 --- a/Source/TextExperiment/String/ASTextAttribute.mm +++ /dev/null @@ -1,487 +0,0 @@ -// -// ASTextAttribute.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTextAttribute.h" -#import -#import - -NSString *const ASTextBackedStringAttributeName = @"ASTextBackedString"; -NSString *const ASTextBindingAttributeName = @"ASTextBinding"; -NSString *const ASTextShadowAttributeName = @"ASTextShadow"; -NSString *const ASTextInnerShadowAttributeName = @"ASTextInnerShadow"; -NSString *const ASTextUnderlineAttributeName = @"ASTextUnderline"; -NSString *const ASTextStrikethroughAttributeName = @"ASTextStrikethrough"; -NSString *const ASTextBorderAttributeName = @"ASTextBorder"; -NSString *const ASTextBackgroundBorderAttributeName = @"ASTextBackgroundBorder"; -NSString *const ASTextBlockBorderAttributeName = @"ASTextBlockBorder"; -NSString *const ASTextAttachmentAttributeName = @"ASTextAttachment"; -NSString *const ASTextHighlightAttributeName = @"ASTextHighlight"; -NSString *const ASTextGlyphTransformAttributeName = @"ASTextGlyphTransform"; - -NSString *const ASTextAttachmentToken = @"\uFFFC"; -NSString *const ASTextTruncationToken = @"\u2026"; - - -ASTextAttributeType ASTextAttributeGetType(NSString *name){ - if (name.length == 0) return ASTextAttributeTypeNone; - - static NSMutableDictionary *dic; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - dic = [NSMutableDictionary new]; - NSNumber *All = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeCoreText | ASTextAttributeTypeASText); - NSNumber *CoreText_ASText = @(ASTextAttributeTypeCoreText | ASTextAttributeTypeASText); - NSNumber *UIKit_ASText = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeASText); - NSNumber *UIKit_CoreText = @(ASTextAttributeTypeUIKit | ASTextAttributeTypeCoreText); - NSNumber *UIKit = @(ASTextAttributeTypeUIKit); - NSNumber *CoreText = @(ASTextAttributeTypeCoreText); - NSNumber *ASText = @(ASTextAttributeTypeASText); - - dic[NSFontAttributeName] = All; - dic[NSKernAttributeName] = All; - dic[NSForegroundColorAttributeName] = UIKit; - dic[(id)kCTForegroundColorAttributeName] = CoreText; - dic[(id)kCTForegroundColorFromContextAttributeName] = CoreText; - dic[NSBackgroundColorAttributeName] = UIKit; - dic[NSStrokeWidthAttributeName] = All; - dic[NSStrokeColorAttributeName] = UIKit; - dic[(id)kCTStrokeColorAttributeName] = CoreText_ASText; - dic[NSShadowAttributeName] = UIKit_ASText; - dic[NSStrikethroughStyleAttributeName] = UIKit; - dic[NSUnderlineStyleAttributeName] = UIKit_CoreText; - dic[(id)kCTUnderlineColorAttributeName] = CoreText; - dic[NSLigatureAttributeName] = All; - dic[(id)kCTSuperscriptAttributeName] = UIKit; //it's a CoreText attrubite, but only supported by UIKit... - dic[NSVerticalGlyphFormAttributeName] = All; - dic[(id)kCTGlyphInfoAttributeName] = CoreText_ASText; -#if TARGET_OS_IOS -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - dic[(id)kCTCharacterShapeAttributeName] = CoreText_ASText; -#pragma clang diagnostic pop -#endif - dic[(id)kCTRunDelegateAttributeName] = CoreText_ASText; - dic[(id)kCTBaselineClassAttributeName] = CoreText_ASText; - dic[(id)kCTBaselineInfoAttributeName] = CoreText_ASText; - dic[(id)kCTBaselineReferenceInfoAttributeName] = CoreText_ASText; - dic[(id)kCTWritingDirectionAttributeName] = CoreText_ASText; - dic[NSParagraphStyleAttributeName] = All; - - dic[NSStrikethroughColorAttributeName] = UIKit; - dic[NSUnderlineColorAttributeName] = UIKit; - dic[NSTextEffectAttributeName] = UIKit; - dic[NSObliquenessAttributeName] = UIKit; - dic[NSExpansionAttributeName] = UIKit; - dic[(id)kCTLanguageAttributeName] = CoreText_ASText; - dic[NSBaselineOffsetAttributeName] = UIKit; - dic[NSWritingDirectionAttributeName] = All; - dic[NSAttachmentAttributeName] = UIKit; - dic[NSLinkAttributeName] = UIKit; - dic[(id)kCTRubyAnnotationAttributeName] = CoreText; - - dic[ASTextBackedStringAttributeName] = ASText; - dic[ASTextBindingAttributeName] = ASText; - dic[ASTextShadowAttributeName] = ASText; - dic[ASTextInnerShadowAttributeName] = ASText; - dic[ASTextUnderlineAttributeName] = ASText; - dic[ASTextStrikethroughAttributeName] = ASText; - dic[ASTextBorderAttributeName] = ASText; - dic[ASTextBackgroundBorderAttributeName] = ASText; - dic[ASTextBlockBorderAttributeName] = ASText; - dic[ASTextAttachmentAttributeName] = ASText; - dic[ASTextHighlightAttributeName] = ASText; - dic[ASTextGlyphTransformAttributeName] = ASText; - }); - NSNumber *num = dic[name]; - if (num) return num.integerValue; - return ASTextAttributeTypeNone; -} - - -@implementation ASTextBackedString - -+ (instancetype)stringWithString:(NSString *)string NS_RETURNS_RETAINED { - ASTextBackedString *one = [self new]; - one.string = string; - return one; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:self.string forKey:@"string"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _string = [aDecoder decodeObjectForKey:@"string"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.string = self.string; - return one; -} - -@end - - -@implementation ASTextBinding - -+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm NS_RETURNS_RETAINED { - ASTextBinding *one = [self new]; - one.deleteConfirm = deleteConfirm; - return one; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:@(self.deleteConfirm) forKey:@"deleteConfirm"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _deleteConfirm = ((NSNumber *)[aDecoder decodeObjectForKey:@"deleteConfirm"]).boolValue; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.deleteConfirm = self.deleteConfirm; - return one; -} - -@end - - -@implementation ASTextShadow - -+ (instancetype)shadowWithColor:(UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius NS_RETURNS_RETAINED { - ASTextShadow *one = [self new]; - one.color = color; - one.offset = offset; - one.radius = radius; - return one; -} - -+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow NS_RETURNS_RETAINED { - if (!nsShadow) return nil; - ASTextShadow *shadow = [self new]; - shadow.offset = nsShadow.shadowOffset; - shadow.radius = nsShadow.shadowBlurRadius; - id color = nsShadow.shadowColor; - if (color) { - if (CGColorGetTypeID() == CFGetTypeID((__bridge CFTypeRef)(color))) { - color = [UIColor colorWithCGColor:(__bridge CGColorRef)(color)]; - } - if ([color isKindOfClass:[UIColor class]]) { - shadow.color = color; - } - } - return shadow; -} - -- (NSShadow *)nsShadow { - NSShadow *shadow = [NSShadow new]; - shadow.shadowOffset = self.offset; - shadow.shadowBlurRadius = self.radius; - shadow.shadowColor = self.color; - return shadow; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:self.color forKey:@"color"]; - [aCoder encodeObject:@(self.radius) forKey:@"radius"]; - [aCoder encodeObject:[NSValue valueWithCGSize:self.offset] forKey:@"offset"]; - [aCoder encodeObject:self.subShadow forKey:@"subShadow"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _color = [aDecoder decodeObjectForKey:@"color"]; - _radius = ((NSNumber *)[aDecoder decodeObjectForKey:@"radius"]).floatValue; - _offset = ((NSValue *)[aDecoder decodeObjectForKey:@"offset"]).CGSizeValue; - _subShadow = [aDecoder decodeObjectForKey:@"subShadow"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.color = self.color; - one.radius = self.radius; - one.offset = self.offset; - one.subShadow = self.subShadow.copy; - return one; -} - -@end - - -@implementation ASTextDecoration - -- (instancetype)init { - self = [super init]; - _style = ASTextLineStyleSingle; - return self; -} - -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style NS_RETURNS_RETAINED { - ASTextDecoration *one = [self new]; - one.style = style; - return one; -} -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(NSNumber *)width color:(UIColor *)color NS_RETURNS_RETAINED { - ASTextDecoration *one = [self new]; - one.style = style; - one.width = width; - one.color = color; - return one; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:@(self.style) forKey:@"style"]; - [aCoder encodeObject:self.width forKey:@"width"]; - [aCoder encodeObject:self.color forKey:@"color"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - self.style = ((NSNumber *)[aDecoder decodeObjectForKey:@"style"]).unsignedIntegerValue; - self.width = [aDecoder decodeObjectForKey:@"width"]; - self.color = [aDecoder decodeObjectForKey:@"color"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.style = self.style; - one.width = self.width; - one.color = self.color; - return one; -} - -@end - - -@implementation ASTextBorder - -+ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(UIColor *)color NS_RETURNS_RETAINED { - ASTextBorder *one = [self new]; - one.lineStyle = lineStyle; - one.strokeWidth = width; - one.strokeColor = color; - return one; -} - -+ (instancetype)borderWithFillColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius NS_RETURNS_RETAINED { - ASTextBorder *one = [self new]; - one.fillColor = color; - one.cornerRadius = cornerRadius; - one.insets = UIEdgeInsetsMake(-2, 0, 0, -2); - return one; -} - -- (instancetype)init { - self = [super init]; - self.lineStyle = ASTextLineStyleSingle; - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:@(self.lineStyle) forKey:@"lineStyle"]; - [aCoder encodeObject:@(self.strokeWidth) forKey:@"strokeWidth"]; - [aCoder encodeObject:self.strokeColor forKey:@"strokeColor"]; - [aCoder encodeObject:@(self.lineJoin) forKey:@"lineJoin"]; - [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.insets] forKey:@"insets"]; - [aCoder encodeObject:@(self.cornerRadius) forKey:@"cornerRadius"]; - [aCoder encodeObject:self.shadow forKey:@"shadow"]; - [aCoder encodeObject:self.fillColor forKey:@"fillColor"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _lineStyle = ((NSNumber *)[aDecoder decodeObjectForKey:@"lineStyle"]).unsignedIntegerValue; - _strokeWidth = ((NSNumber *)[aDecoder decodeObjectForKey:@"strokeWidth"]).doubleValue; - _strokeColor = [aDecoder decodeObjectForKey:@"strokeColor"]; - _lineJoin = (CGLineJoin)((NSNumber *)[aDecoder decodeObjectForKey:@"join"]).unsignedIntegerValue; - _insets = ((NSValue *)[aDecoder decodeObjectForKey:@"insets"]).UIEdgeInsetsValue; - _cornerRadius = ((NSNumber *)[aDecoder decodeObjectForKey:@"cornerRadius"]).doubleValue; - _shadow = [aDecoder decodeObjectForKey:@"shadow"]; - _fillColor = [aDecoder decodeObjectForKey:@"fillColor"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.lineStyle = self.lineStyle; - one.strokeWidth = self.strokeWidth; - one.strokeColor = self.strokeColor; - one.lineJoin = self.lineJoin; - one.insets = self.insets; - one.cornerRadius = self.cornerRadius; - one.shadow = self.shadow.copy; - one.fillColor = self.fillColor; - return one; -} - -@end - - -@implementation ASTextAttachment - -+ (instancetype)attachmentWithContent:(id)content NS_RETURNS_RETAINED { - ASTextAttachment *one = [self new]; - one.content = content; - return one; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:self.content forKey:@"content"]; - [aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.contentInsets] forKey:@"contentInsets"]; - [aCoder encodeObject:self.userInfo forKey:@"userInfo"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _content = [aDecoder decodeObjectForKey:@"content"]; - _contentInsets = ((NSValue *)[aDecoder decodeObjectForKey:@"contentInsets"]).UIEdgeInsetsValue; - _userInfo = [aDecoder decodeObjectForKey:@"userInfo"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - if ([self.content respondsToSelector:@selector(copy)]) { - one.content = [self.content copy]; - } else { - one.content = self.content; - } - one.contentInsets = self.contentInsets; - one.userInfo = self.userInfo.copy; - return one; -} - -@end - - -@implementation ASTextHighlight - -+ (instancetype)highlightWithAttributes:(NSDictionary *)attributes NS_RETURNS_RETAINED { - ASTextHighlight *one = [self new]; - one.attributes = attributes; - return one; -} - -+ (instancetype)highlightWithBackgroundColor:(UIColor *)color NS_RETURNS_RETAINED { - ASTextBorder *highlightBorder = [ASTextBorder new]; - highlightBorder.insets = UIEdgeInsetsMake(-2, -1, -2, -1); - highlightBorder.cornerRadius = 3; - highlightBorder.fillColor = color; - - ASTextHighlight *one = [self new]; - [one setBackgroundBorder:highlightBorder]; - return one; -} - -- (void)setAttributes:(NSDictionary *)attributes { - _attributes = attributes.mutableCopy; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.attributes = self.attributes.mutableCopy; - return one; -} - -- (void)_makeMutableAttributes { - if (!_attributes) { - _attributes = [NSMutableDictionary new]; - } else if (![_attributes isKindOfClass:[NSMutableDictionary class]]) { - _attributes = _attributes.mutableCopy; - } -} - -- (void)setFont:(UIFont *)font { - [self _makeMutableAttributes]; - if (font == (id)[NSNull null] || font == nil) { - ((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = [NSNull null]; - } else { - CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL); - if (ctFont) { - ((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = (__bridge id)(ctFont); - CFRelease(ctFont); - } - } -} - -- (void)setColor:(UIColor *)color { - [self _makeMutableAttributes]; - if (color == (id)[NSNull null] || color == nil) { - ((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = [NSNull null]; - ((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = [NSNull null]; - } else { - ((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = (__bridge id)(color.CGColor); - ((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = color; - } -} - -- (void)setStrokeWidth:(NSNumber *)width { - [self _makeMutableAttributes]; - if (width == (id)[NSNull null] || width == nil) { - ((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = [NSNull null]; - } else { - ((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = width; - } -} - -- (void)setStrokeColor:(UIColor *)color { - [self _makeMutableAttributes]; - if (color == (id)[NSNull null] || color == nil) { - ((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = [NSNull null]; - ((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = [NSNull null]; - } else { - ((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = (__bridge id)(color.CGColor); - ((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = color; - } -} - -- (void)setTextAttribute:(NSString *)attribute value:(id)value { - [self _makeMutableAttributes]; - if (value == nil) value = [NSNull null]; - ((NSMutableDictionary *)_attributes)[attribute] = value; -} - -- (void)setShadow:(ASTextShadow *)shadow { - [self setTextAttribute:ASTextShadowAttributeName value:shadow]; -} - -- (void)setInnerShadow:(ASTextShadow *)shadow { - [self setTextAttribute:ASTextInnerShadowAttributeName value:shadow]; -} - -- (void)setUnderline:(ASTextDecoration *)underline { - [self setTextAttribute:ASTextUnderlineAttributeName value:underline]; -} - -- (void)setStrikethrough:(ASTextDecoration *)strikethrough { - [self setTextAttribute:ASTextStrikethroughAttributeName value:strikethrough]; -} - -- (void)setBackgroundBorder:(ASTextBorder *)border { - [self setTextAttribute:ASTextBackgroundBorderAttributeName value:border]; -} - -- (void)setBorder:(ASTextBorder *)border { - [self setTextAttribute:ASTextBorderAttributeName value:border]; -} - -- (void)setAttachment:(ASTextAttachment *)attachment { - [self setTextAttribute:ASTextAttachmentAttributeName value:attachment]; -} - -@end - diff --git a/Source/TextExperiment/String/ASTextRunDelegate.h b/Source/TextExperiment/String/ASTextRunDelegate.h deleted file mode 100644 index 973dc5ffe7..0000000000 --- a/Source/TextExperiment/String/ASTextRunDelegate.h +++ /dev/null @@ -1,65 +0,0 @@ -// -// ASTextRunDelegate.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - Wrapper for CTRunDelegateRef. - - Example: - - ASTextRunDelegate *delegate = [ASTextRunDelegate new]; - delegate.ascent = 20; - delegate.descent = 4; - delegate.width = 20; - CTRunDelegateRef ctRunDelegate = delegate.CTRunDelegate; - if (ctRunDelegate) { - /// add to attributed string - CFRelease(ctRunDelegate); - } - - */ -@interface ASTextRunDelegate : NSObject - -/** - Creates and returns the CTRunDelegate. - - @discussion You need call CFRelease() after used. - The CTRunDelegateRef has a strong reference to this ASTextRunDelegate object. - In CoreText, use CTRunDelegateGetRefCon() to get this ASTextRunDelegate object. - - @return The CTRunDelegate object. - */ -- (nullable CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED; - -/** - Additional information about the the run delegate. - */ -@property (nullable, nonatomic, copy) NSDictionary *userInfo; - -/** - The typographic ascent of glyphs in the run. - */ -@property (nonatomic) CGFloat ascent; - -/** - The typographic descent of glyphs in the run. - */ -@property (nonatomic) CGFloat descent; - -/** - The typographic width of glyphs in the run. - */ -@property (nonatomic) CGFloat width; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/TextExperiment/String/ASTextRunDelegate.mm b/Source/TextExperiment/String/ASTextRunDelegate.mm deleted file mode 100644 index 1c179b1fea..0000000000 --- a/Source/TextExperiment/String/ASTextRunDelegate.mm +++ /dev/null @@ -1,68 +0,0 @@ -// -// ASTextRunDelegate.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -static void DeallocCallback(void *ref) { - ASTextRunDelegate *self = (__bridge_transfer ASTextRunDelegate *)(ref); - self = nil; // release -} - -static CGFloat GetAscentCallback(void *ref) { - ASTextRunDelegate *self = (__bridge ASTextRunDelegate *)(ref); - return self.ascent; -} - -static CGFloat GetDecentCallback(void *ref) { - ASTextRunDelegate *self = (__bridge ASTextRunDelegate *)(ref); - return self.descent; -} - -static CGFloat GetWidthCallback(void *ref) { - ASTextRunDelegate *self = (__bridge ASTextRunDelegate *)(ref); - return self.width; -} - -@implementation ASTextRunDelegate - -- (CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED { - CTRunDelegateCallbacks callbacks; - callbacks.version = kCTRunDelegateCurrentVersion; - callbacks.dealloc = DeallocCallback; - callbacks.getAscent = GetAscentCallback; - callbacks.getDescent = GetDecentCallback; - callbacks.getWidth = GetWidthCallback; - return CTRunDelegateCreate(&callbacks, (__bridge_retained void *)(self.copy)); -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { - [aCoder encodeObject:@(_ascent) forKey:@"ascent"]; - [aCoder encodeObject:@(_descent) forKey:@"descent"]; - [aCoder encodeObject:@(_width) forKey:@"width"]; - [aCoder encodeObject:_userInfo forKey:@"userInfo"]; -} - -- (id)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - _ascent = ((NSNumber *)[aDecoder decodeObjectForKey:@"ascent"]).floatValue; - _descent = ((NSNumber *)[aDecoder decodeObjectForKey:@"descent"]).floatValue; - _width = ((NSNumber *)[aDecoder decodeObjectForKey:@"width"]).floatValue; - _userInfo = [aDecoder decodeObjectForKey:@"userInfo"]; - return self; -} - -- (id)copyWithZone:(NSZone *)zone { - __typeof__(self) one = [self.class new]; - one.ascent = self.ascent; - one.descent = self.descent; - one.width = self.width; - one.userInfo = self.userInfo; - return one; -} - -@end diff --git a/Source/TextExperiment/Utility/ASTextUtilities.h b/Source/TextExperiment/Utility/ASTextUtilities.h deleted file mode 100644 index 2ba0f09f53..0000000000 --- a/Source/TextExperiment/Utility/ASTextUtilities.h +++ /dev/null @@ -1,315 +0,0 @@ -// -// ASTextUtilities.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import - - -#ifndef ASTEXT_CLAMP // return the clamped value -#define ASTEXT_CLAMP(_x_, _low_, _high_) (((_x_) > (_high_)) ? (_high_) : (((_x_) < (_low_)) ? (_low_) : (_x_))) -#endif - -#ifndef ASTEXT_SWAP // swap two value -#define ASTEXT_SWAP(_a_, _b_) do { __typeof__(_a_) _tmp_ = (_a_); (_a_) = (_b_); (_b_) = _tmp_; } while (0) -#endif - -NS_ASSUME_NONNULL_BEGIN - -/** - Whether the character is 'line break char': - U+000D (\\r or CR) - U+2028 (Unicode line separator) - U+000A (\\n or LF) - U+2029 (Unicode paragraph separator) - - @param c A character - @return YES or NO. - */ -static inline BOOL ASTextIsLinebreakChar(unichar c) { - switch (c) { - case 0x000D: - case 0x2028: - case 0x000A: - case 0x2029: - return YES; - default: - return NO; - } -} - -/** - Whether the string is a 'line break': - U+000D (\\r or CR) - U+2028 (Unicode line separator) - U+000A (\\n or LF) - U+2029 (Unicode paragraph separator) - \\r\\n, in that order (also known as CRLF) - - @param str A string - @return YES or NO. - */ -static inline BOOL ASTextIsLinebreakString(NSString * _Nullable str) { - if (str.length > 2 || str.length == 0) return NO; - if (str.length == 1) { - unichar c = [str characterAtIndex:0]; - return ASTextIsLinebreakChar(c); - } else { - return ([str characterAtIndex:0] == '\r') && ([str characterAtIndex:1] == '\n'); - } -} - -/** - If the string has a 'line break' suffix, return the 'line break' length. - - @param str A string. - @return The length of the tail line break: 0, 1 or 2. - */ -static inline NSUInteger ASTextLinebreakTailLength(NSString * _Nullable str) { - if (str.length >= 2) { - unichar c2 = [str characterAtIndex:str.length - 1]; - if (ASTextIsLinebreakChar(c2)) { - unichar c1 = [str characterAtIndex:str.length - 2]; - if (c1 == '\r' && c2 == '\n') return 2; - else return 1; - } else { - return 0; - } - } else if (str.length == 1) { - return ASTextIsLinebreakChar([str characterAtIndex:0]) ? 1 : 0; - } else { - return 0; - } -} - -/** - Whether the font contains color bitmap glyphs. - - @discussion Only `AppleColorEmoji` contains color bitmap glyphs in iOS system fonts. - @param font A font. - @return YES: the font contains color bitmap glyphs, NO: the font has no color bitmap glyph. - */ -static inline BOOL ASTextCTFontContainsColorBitmapGlyphs(CTFontRef font) { - return (CTFontGetSymbolicTraits(font) & kCTFontTraitColorGlyphs) != 0; -} - -/** - Get the `AppleColorEmoji` font's ascent with a specified font size. - It may used to create custom emoji. - - @param fontSize The specified font size. - @return The font ascent. - */ -static inline CGFloat ASTextEmojiGetAscentWithFontSize(CGFloat fontSize) { - if (fontSize < 16) { - return 1.25 * fontSize; - } else if (16 <= fontSize && fontSize <= 24) { - return 0.5 * fontSize + 12; - } else { - return fontSize; - } -} - -/** - Get the `AppleColorEmoji` font's descent with a specified font size. - It may used to create custom emoji. - - @param fontSize The specified font size. - @return The font descent. - */ -static inline CGFloat ASTextEmojiGetDescentWithFontSize(CGFloat fontSize) { - if (fontSize < 16) { - return 0.390625 * fontSize; - } else if (16 <= fontSize && fontSize <= 24) { - return 0.15625 * fontSize + 3.75; - } else { - return 0.3125 * fontSize; - } -} - -/** - Get the `AppleColorEmoji` font's glyph bounding rect with a specified font size. - It may used to create custom emoji. - - @param fontSize The specified font size. - @return The font glyph bounding rect. - */ -static inline CGRect ASTextEmojiGetGlyphBoundingRectWithFontSize(CGFloat fontSize) { - CGRect rect; - rect.origin.x = 0.75; - rect.size.width = rect.size.height = ASTextEmojiGetAscentWithFontSize(fontSize); - if (fontSize < 16) { - rect.origin.y = -0.2525 * fontSize; - } else if (16 <= fontSize && fontSize <= 24) { - rect.origin.y = 0.1225 * fontSize -6; - } else { - rect.origin.y = -0.1275 * fontSize; - } - return rect; -} - - -/** - Get the character set which should rotate in vertical form. - @return The shared character set. - */ -NSCharacterSet *ASTextVerticalFormRotateCharacterSet(void); - -/** - Get the character set which should rotate and move in vertical form. - @return The shared character set. - */ -NSCharacterSet *ASTextVerticalFormRotateAndMoveCharacterSet(void); - - -/// Get the transform rotation. -/// @return the rotation in radians [-PI,PI] ([-180°,180°]) -static inline CGFloat ASTextCGAffineTransformGetRotation(CGAffineTransform transform) { - return atan2(transform.b, transform.a); -} - -/// Negates/inverts a UIEdgeInsets. -static inline UIEdgeInsets ASTextUIEdgeInsetsInvert(UIEdgeInsets insets) { - return UIEdgeInsetsMake(-insets.top, -insets.left, -insets.bottom, -insets.right); -} - -/** - Returns a rectangle to fit `rect` with specified content mode. - - @param rect The constraint rect - @param size The content size - @param mode The content mode - @return A rectangle for the given content mode. - @discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill. - */ -CGRect ASTextCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode); - -/// Returns the center for the rectangle. -static inline CGPoint ASTextCGRectGetCenter(CGRect rect) { - return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); -} - -/// Returns the area of the rectangle. -static inline CGFloat ASTextCGRectGetArea(CGRect rect) { - if (CGRectIsNull(rect)) return 0; - rect = CGRectStandardize(rect); - return rect.size.width * rect.size.height; -} - -/// Returns the minmium distance between a point to a rectangle. -static inline CGFloat ASTextCGPointGetDistanceToRect(CGPoint p, CGRect r) { - r = CGRectStandardize(r); - if (CGRectContainsPoint(r, p)) return 0; - CGFloat distV, distH; - if (CGRectGetMinY(r) <= p.y && p.y <= CGRectGetMaxY(r)) { - distV = 0; - } else { - distV = p.y < CGRectGetMinY(r) ? CGRectGetMinY(r) - p.y : p.y - CGRectGetMaxY(r); - } - if (CGRectGetMinX(r) <= p.x && p.x <= CGRectGetMaxX(r)) { - distH = 0; - } else { - distH = p.x < CGRectGetMinX(r) ? CGRectGetMinX(r) - p.x : p.x - CGRectGetMaxX(r); - } - return MAX(distV, distH); -} - -/// Convert point to pixel. -static inline CGFloat ASTextCGFloatToPixel(CGFloat value) { - return value * ASScreenScale(); -} - -/// Convert pixel to point. -static inline CGFloat ASTextCGFloatFromPixel(CGFloat value) { - return value / ASScreenScale(); -} - -/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned) -static inline CGFloat ASTextCGFloatPixelHalf(CGFloat value) { - CGFloat scale = ASScreenScale(); - return (floor(value * scale) + 0.5) / scale; -} - -/// floor point value for pixel-aligned -static inline CGPoint ASTextCGPointPixelFloor(CGPoint point) { - CGFloat scale = ASScreenScale(); - return CGPointMake(floor(point.x * scale) / scale, - floor(point.y * scale) / scale); -} - -/// round point value for pixel-aligned -static inline CGPoint ASTextCGPointPixelRound(CGPoint point) { - CGFloat scale = ASScreenScale(); - return CGPointMake(round(point.x * scale) / scale, - round(point.y * scale) / scale); -} - -/// ceil point value for pixel-aligned -static inline CGPoint ASTextCGPointPixelCeil(CGPoint point) { - CGFloat scale = ASScreenScale(); - return CGPointMake(ceil(point.x * scale) / scale, - ceil(point.y * scale) / scale); -} - -/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned) -static inline CGPoint ASTextCGPointPixelHalf(CGPoint point) { - CGFloat scale = ASScreenScale(); - return CGPointMake((floor(point.x * scale) + 0.5) / scale, - (floor(point.y * scale) + 0.5) / scale); -} - -/// round point value for pixel-aligned -static inline CGRect ASTextCGRectPixelRound(CGRect rect) { - CGPoint origin = ASTextCGPointPixelRound(rect.origin); - CGPoint corner = ASTextCGPointPixelRound(CGPointMake(rect.origin.x + rect.size.width, - rect.origin.y + rect.size.height)); - return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y); -} - -/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned) -static inline CGRect ASTextCGRectPixelHalf(CGRect rect) { - CGPoint origin = ASTextCGPointPixelHalf(rect.origin); - CGPoint corner = ASTextCGPointPixelHalf(CGPointMake(rect.origin.x + rect.size.width, - rect.origin.y + rect.size.height)); - return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y); -} - - -static inline UIFont * _Nullable ASTextFontWithBold(UIFont *font) { - return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold] size:font.pointSize]; -} - -static inline UIFont * _Nullable ASTextFontWithItalic(UIFont *font) { - return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic] size:font.pointSize]; -} - -static inline UIFont * _Nullable ASTextFontWithBoldItalic(UIFont *font) { - return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold | UIFontDescriptorTraitItalic] size:font.pointSize]; -} - - - -/** - Convert CFRange to NSRange - @param range CFRange @return NSRange - */ -static inline NSRange ASTextNSRangeFromCFRange(CFRange range) { - return NSMakeRange(range.location, range.length); -} - -/** - Convert NSRange to CFRange - @param range NSRange @return CFRange - */ -static inline CFRange ASTextCFRangeFromNSRange(NSRange range) { - return CFRangeMake(range.location, range.length); -} - -NS_ASSUME_NONNULL_END diff --git a/Source/TextExperiment/Utility/ASTextUtilities.mm b/Source/TextExperiment/Utility/ASTextUtilities.mm deleted file mode 100644 index 7194dadd44..0000000000 --- a/Source/TextExperiment/Utility/ASTextUtilities.mm +++ /dev/null @@ -1,142 +0,0 @@ -// -// ASTextUtilities.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASTextUtilities.h" - -NSCharacterSet *ASTextVerticalFormRotateCharacterSet() { - static NSMutableCharacterSet *set; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - set = [NSMutableCharacterSet new]; - [set addCharactersInRange:NSMakeRange(0x1100, 256)]; // Hangul Jamo - [set addCharactersInRange:NSMakeRange(0x2460, 160)]; // Enclosed Alphanumerics - [set addCharactersInRange:NSMakeRange(0x2600, 256)]; // Miscellaneous Symbols - [set addCharactersInRange:NSMakeRange(0x2700, 192)]; // Dingbats - [set addCharactersInRange:NSMakeRange(0x2E80, 128)]; // CJK Radicals Supplement - [set addCharactersInRange:NSMakeRange(0x2F00, 224)]; // Kangxi Radicals - [set addCharactersInRange:NSMakeRange(0x2FF0, 16)]; // Ideographic Description Characters - [set addCharactersInRange:NSMakeRange(0x3000, 64)]; // CJK Symbols and Punctuation - [set removeCharactersInRange:NSMakeRange(0x3008, 10)]; - [set removeCharactersInRange:NSMakeRange(0x3014, 12)]; - [set addCharactersInRange:NSMakeRange(0x3040, 96)]; // Hiragana - [set addCharactersInRange:NSMakeRange(0x30A0, 96)]; // Katakana - [set addCharactersInRange:NSMakeRange(0x3100, 48)]; // Bopomofo - [set addCharactersInRange:NSMakeRange(0x3130, 96)]; // Hangul Compatibility Jamo - [set addCharactersInRange:NSMakeRange(0x3190, 16)]; // Kanbun - [set addCharactersInRange:NSMakeRange(0x31A0, 32)]; // Bopomofo Extended - [set addCharactersInRange:NSMakeRange(0x31C0, 48)]; // CJK Strokes - [set addCharactersInRange:NSMakeRange(0x31F0, 16)]; // Katakana Phonetic Extensions - [set addCharactersInRange:NSMakeRange(0x3200, 256)]; // Enclosed CJK Letters and Months - [set addCharactersInRange:NSMakeRange(0x3300, 256)]; // CJK Compatibility - [set addCharactersInRange:NSMakeRange(0x3400, 2582)]; // CJK Unified Ideographs Extension A - [set addCharactersInRange:NSMakeRange(0x4E00, 20941)]; // CJK Unified Ideographs - [set addCharactersInRange:NSMakeRange(0xAC00, 11172)]; // Hangul Syllables - [set addCharactersInRange:NSMakeRange(0xD7B0, 80)]; // Hangul Jamo Extended-B - [set addCharactersInString:@""]; // U+F8FF (Private Use Area) - [set addCharactersInRange:NSMakeRange(0xF900, 512)]; // CJK Compatibility Ideographs - [set addCharactersInRange:NSMakeRange(0xFE10, 16)]; // Vertical Forms - [set addCharactersInRange:NSMakeRange(0xFF00, 240)]; // Halfwidth and Fullwidth Forms - [set addCharactersInRange:NSMakeRange(0x1F200, 256)]; // Enclosed Ideographic Supplement - [set addCharactersInRange:NSMakeRange(0x1F300, 768)]; // Enclosed Ideographic Supplement - [set addCharactersInRange:NSMakeRange(0x1F600, 80)]; // Emoticons (Emoji) - [set addCharactersInRange:NSMakeRange(0x1F680, 128)]; // Transport and Map Symbols - - // See http://unicode-table.com/ for more information. - }); - return set; -} - -NSCharacterSet *ASTextVerticalFormRotateAndMoveCharacterSet() { - static NSMutableCharacterSet *set; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - set = [NSMutableCharacterSet new]; - [set addCharactersInString:@",。、."]; - }); - return set; -} - -CGRect ASTextCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode) { - rect = CGRectStandardize(rect); - size.width = size.width < 0 ? -size.width : size.width; - size.height = size.height < 0 ? -size.height : size.height; - CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); - switch (mode) { - case UIViewContentModeScaleAspectFit: - case UIViewContentModeScaleAspectFill: { - if (rect.size.width < 0.01 || rect.size.height < 0.01 || - size.width < 0.01 || size.height < 0.01) { - rect.origin = center; - rect.size = CGSizeZero; - } else { - CGFloat scale; - if (mode == UIViewContentModeScaleAspectFit) { - if (size.width / size.height < rect.size.width / rect.size.height) { - scale = rect.size.height / size.height; - } else { - scale = rect.size.width / size.width; - } - } else { - if (size.width / size.height < rect.size.width / rect.size.height) { - scale = rect.size.width / size.width; - } else { - scale = rect.size.height / size.height; - } - } - size.width *= scale; - size.height *= scale; - rect.size = size; - rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5); - } - } break; - case UIViewContentModeCenter: { - rect.size = size; - rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5); - } break; - case UIViewContentModeTop: { - rect.origin.x = center.x - size.width * 0.5; - rect.size = size; - } break; - case UIViewContentModeBottom: { - rect.origin.x = center.x - size.width * 0.5; - rect.origin.y += rect.size.height - size.height; - rect.size = size; - } break; - case UIViewContentModeLeft: { - rect.origin.y = center.y - size.height * 0.5; - rect.size = size; - } break; - case UIViewContentModeRight: { - rect.origin.y = center.y - size.height * 0.5; - rect.origin.x += rect.size.width - size.width; - rect.size = size; - } break; - case UIViewContentModeTopLeft: { - rect.size = size; - } break; - case UIViewContentModeTopRight: { - rect.origin.x += rect.size.width - size.width; - rect.size = size; - } break; - case UIViewContentModeBottomLeft: { - rect.origin.y += rect.size.height - size.height; - rect.size = size; - } break; - case UIViewContentModeBottomRight: { - rect.origin.x += rect.size.width - size.width; - rect.origin.y += rect.size.height - size.height; - rect.size = size; - } break; - case UIViewContentModeScaleToFill: - case UIViewContentModeRedraw: - default: { - rect = rect; - } - } - return rect; -} diff --git a/Source/TextExperiment/Utility/NSAttributedString+ASText.h b/Source/TextExperiment/Utility/NSAttributedString+ASText.h deleted file mode 100644 index ef44fb4f35..0000000000 --- a/Source/TextExperiment/Utility/NSAttributedString+ASText.h +++ /dev/null @@ -1,1375 +0,0 @@ -// -// NSAttributedString+ASText.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - Get pre-defined attributes from attributed string. - All properties defined in UIKit, CoreText and ASText are included. - */ -@interface NSAttributedString (ASText) - -#pragma mark - Retrieving character attribute information -///============================================================================= -/// @name Retrieving character attribute information -///============================================================================= - -/** - Returns the attributes at first charactor. - */ -@property (nullable, nonatomic, copy, readonly) NSDictionary *as_attributes; - -/** - Returns the attributes for the character at a given index. - - @discussion Raises an `NSRangeException` if index lies beyond the end of the - receiver's characters. - - @param index The index for which to return attributes. - This value must lie within the bounds of the receiver. - - @return The attributes for the character at index. - */ -- (nullable NSDictionary *)as_attributesAtIndex:(NSUInteger)index; - -/** - Returns the value for an attribute with a given name of the character at a given index. - - @discussion Raises an `NSRangeException` if index lies beyond the end of the - receiver's characters. - - @param attributeName The name of an attribute. - @param index The index for which to return attributes. - This value must not exceed the bounds of the receiver. - - @return The value for the attribute named `attributeName` of the character at - index `index`, or nil if there is no such attribute. - */ -- (nullable id)as_attribute:(NSString *)attributeName atIndex:(NSUInteger)index; - - -#pragma mark - Get character attribute as property -///============================================================================= -/// @name Get character attribute as property -///============================================================================= - -/** - The font of the text. (read-only) - - @discussion Default is Helvetica (Neue) 12. - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) UIFont *as_font; -- (nullable UIFont *)as_fontAtIndex:(NSUInteger)index; - -/** - A kerning adjustment. (read-only) - - @discussion Default is standard kerning. The kerning attribute indicate how many - points the following character should be shifted from its default offset as - defined by the current character's font in points; a positive kern indicates a - shift farther along and a negative kern indicates a shift closer to the current - character. If this attribute is not present, standard kerning will be used. - If this attribute is set to 0.0, no kerning will be done at all. - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_kern; -- (nullable NSNumber *)as_kernAtIndex:(NSUInteger)index; - -/** - The foreground color. (read-only) - - @discussion Default is Black. - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) UIColor *as_color; -- (nullable UIColor *)as_colorAtIndex:(NSUInteger)index; - -/** - The background color. (read-only) - - @discussion Default is nil (or no background). - @discussion Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nullable, nonatomic, readonly) UIColor *as_backgroundColor; -- (nullable UIColor *)as_backgroundColorAtIndex:(NSUInteger)index; - -/** - The stroke width. (read-only) - - @discussion Default value is 0.0 (no stroke). This attribute, interpreted as - a percentage of font point size, controls the text drawing mode: positive - values effect drawing with stroke only; negative values are for stroke and fill. - A typical value for outlined text is 3.0. - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_strokeWidth; -- (nullable NSNumber *)as_strokeWidthAtIndex:(NSUInteger)index; - -/** - The stroke color. (read-only) - - @discussion Default value is nil (same as foreground color). - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 - */ -@property (nullable, nonatomic, readonly) UIColor *as_strokeColor; -- (nullable UIColor *)as_strokeColorAtIndex:(NSUInteger)index; - -/** - The text shadow. (read-only) - - @discussion Default value is nil (no shadow). - @discussion Get this property returns the first character's attribute. - @since UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) NSShadow *as_shadow; -- (nullable NSShadow *)as_shadowAtIndex:(NSUInteger)index; - -/** - The strikethrough style. (read-only) - - @discussion Default value is NSUnderlineStyleNone (no strikethrough). - @discussion Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nonatomic, readonly) NSUnderlineStyle as_strikethroughStyle; -- (NSUnderlineStyle)as_strikethroughStyleAtIndex:(NSUInteger)index; - -/** - The strikethrough color. (read-only) - - @discussion Default value is nil (same as foreground color). - @discussion Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) UIColor *as_strikethroughColor; -- (nullable UIColor *)as_strikethroughColorAtIndex:(NSUInteger)index; - -/** - The underline style. (read-only) - - @discussion Default value is NSUnderlineStyleNone (no underline). - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 - */ -@property (nonatomic, readonly) NSUnderlineStyle as_underlineStyle; -- (NSUnderlineStyle)as_underlineStyleAtIndex:(NSUInteger)index; - -/** - The underline color. (read-only) - - @discussion Default value is nil (same as foreground color). - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) UIColor *as_underlineColor; -- (nullable UIColor *)as_underlineColorAtIndex:(NSUInteger)index; - -/** - Ligature formation control. (read-only) - - @discussion Default is int value 1. The ligature attribute determines what kinds - of ligatures should be used when displaying the string. A value of 0 indicates - that only ligatures essential for proper rendering of text should be used, - 1 indicates that standard ligatures should be used, and 2 indicates that all - available ligatures should be used. Which ligatures are standard depends on the - script and possibly the font. - @discussion Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_ligature; -- (nullable NSNumber *)as_ligatureAtIndex:(NSUInteger)index; - -/** - The text effect. (read-only) - - @discussion Default is nil (no effect). The only currently supported value - is NSTextEffectLetterpressStyle. - @discussion Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) NSString *as_textEffect; -- (nullable NSString *)as_textEffectAtIndex:(NSUInteger)index; - -/** - The skew to be applied to glyphs. (read-only) - - @discussion Default is 0 (no skew). - @discussion Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_obliqueness; -- (nullable NSNumber *)as_obliquenessAtIndex:(NSUInteger)index; - -/** - The log of the expansion factor to be applied to glyphs. (read-only) - - @discussion Default is 0 (no expansion). - @discussion Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_expansion; -- (nullable NSNumber *)as_expansionAtIndex:(NSUInteger)index; - -/** - The character's offset from the baseline, in points. (read-only) - - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic, readonly) NSNumber *as_baselineOffset; -- (nullable NSNumber *)as_baselineOffsetAtIndex:(NSUInteger)index; - -/** - Glyph orientation control. (read-only) - - @discussion Default is NO. A value of NO indicates that horizontal glyph forms - are to be used, YES indicates that vertical glyph forms are to be used. - @discussion Get this property returns the first character's attribute. - @since CoreText:4.3 ASText:6.0 - */ -@property (nonatomic, readonly) BOOL as_verticalGlyphForm; -- (BOOL)as_verticalGlyphFormAtIndex:(NSUInteger)index; - -/** - Specifies text language. (read-only) - - @discussion Value must be a NSString containing a locale identifier. Default is - unset. When this attribute is set to a valid identifier, it will be used to select - localized glyphs (if supported by the font) and locale-specific line breaking rules. - @discussion Get this property returns the first character's attribute. - @since CoreText:7.0 ASText:7.0 - */ -@property (nullable, nonatomic, readonly) NSString *as_language; -- (nullable NSString *)as_languageAtIndex:(NSUInteger)index; - -/** - Specifies a bidirectional override or embedding. (read-only) - - @discussion See alse NSWritingDirection and NSWritingDirectionAttributeName. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:7.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) NSArray *as_writingDirection; -- (nullable NSArray *)as_writingDirectionAtIndex:(NSUInteger)index; - -/** - An NSParagraphStyle object which is used to specify things like - line alignment, tab rulers, writing direction, etc. (read-only) - - @discussion Default is nil ([NSParagraphStyle defaultParagraphStyle]). - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic, readonly) NSParagraphStyle *as_paragraphStyle; -- (nullable NSParagraphStyle *)as_paragraphStyleAtIndex:(NSUInteger)index; - -#pragma mark - Get paragraph attribute as property -///============================================================================= -/// @name Get paragraph attribute as property -///============================================================================= - -/** - The text alignment (A wrapper for NSParagraphStyle). (read-only) - - @discussion Natural text alignment is realized as left or right alignment - depending on the line sweep direction of the first script contained in the paragraph. - @discussion Default is NSTextAlignmentNatural. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) NSTextAlignment as_alignment; -- (NSTextAlignment)as_alignmentAtIndex:(NSUInteger)index; - -/** - The mode that should be used to break lines (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the line break mode to be used laying out the paragraph's text. - @discussion Default is NSLineBreakByWordWrapping. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) NSLineBreakMode as_lineBreakMode; -- (NSLineBreakMode)as_lineBreakModeAtIndex:(NSUInteger)index; - -/** - The distance in points between the bottom of one line fragment and the top of the next. - (A wrapper for NSParagraphStyle) (read-only) - - @discussion This value is always nonnegative. This value is included in the line - fragment heights in the layout manager. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_lineSpacing; -- (CGFloat)as_lineSpacingAtIndex:(NSUInteger)index; - -/** - The space after the end of the paragraph (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the space (measured in points) added at the - end of the paragraph to separate it from the following paragraph. This value must - be nonnegative. The space between paragraphs is determined by adding the previous - paragraph's paragraphSpacing and the current paragraph's paragraphSpacingBefore. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_paragraphSpacing; -- (CGFloat)as_paragraphSpacingAtIndex:(NSUInteger)index; - -/** - The distance between the paragraph's top and the beginning of its text content. - (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the space (measured in points) between the - paragraph's top and the beginning of its text content. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_paragraphSpacingBefore; -- (CGFloat)as_paragraphSpacingBeforeAtIndex:(NSUInteger)index; - -/** - The indentation of the first line (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the distance (in points) from the leading margin - of a text container to the beginning of the paragraph's first line. This value - is always nonnegative. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_firstLineHeadIndent; -- (CGFloat)as_firstLineHeadIndentAtIndex:(NSUInteger)index; - -/** - The indentation of the receiver's lines other than the first. (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the distance (in points) from the leading margin - of a text container to the beginning of lines other than the first. This value is - always nonnegative. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_headIndent; -- (CGFloat)as_headIndentAtIndex:(NSUInteger)index; - -/** - The trailing indentation (A wrapper for NSParagraphStyle). (read-only) - - @discussion If positive, this value is the distance from the leading margin - (for example, the left margin in left-to-right text). If 0 or negative, it's the - distance from the trailing margin. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_tailIndent; -- (CGFloat)as_tailIndentAtIndex:(NSUInteger)index; - -/** - The receiver's minimum height (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the minimum height in points that any line in - the receiver will occupy, regardless of the font size or size of any attached graphic. - This value must be nonnegative. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_minimumLineHeight; -- (CGFloat)as_minimumLineHeightAtIndex:(NSUInteger)index; - -/** - The receiver's maximum line height (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the maximum height in points that any line in - the receiver will occupy, regardless of the font size or size of any attached graphic. - This value is always nonnegative. Glyphs and graphics exceeding this height will - overlap neighboring lines; however, a maximum height of 0 implies no line height limit. - Although this limit applies to the line itself, line spacing adds extra space between adjacent lines. - @discussion Default is 0 (no limit). - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_maximumLineHeight; -- (CGFloat)as_maximumLineHeightAtIndex:(NSUInteger)index; - -/** - The line height multiple (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property contains the line break mode to be used laying out the paragraph's text. - @discussion Default is 0 (no multiple). - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) CGFloat as_lineHeightMultiple; -- (CGFloat)as_lineHeightMultipleAtIndex:(NSUInteger)index; - -/** - The base writing direction (A wrapper for NSParagraphStyle). (read-only) - - @discussion If you specify NSWritingDirectionNaturalDirection, the receiver resolves - the writing direction to either NSWritingDirectionLeftToRight or NSWritingDirectionRightToLeft, - depending on the direction for the user's `language` preference setting. - @discussion Default is NSWritingDirectionNatural. - @discussion Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic, readonly) NSWritingDirection as_baseWritingDirection; -- (NSWritingDirection)as_baseWritingDirectionAtIndex:(NSUInteger)index; - -/** - The paragraph's threshold for hyphenation. (A wrapper for NSParagraphStyle). (read-only) - - @discussion Valid values lie between 0.0 and 1.0 inclusive. Hyphenation is attempted - when the ratio of the text width (as broken without hyphenation) to the width of the - line fragment is less than the hyphenation factor. When the paragraph's hyphenation - factor is 0.0, the layout manager's hyphenation factor is used instead. When both - are 0.0, hyphenation is disabled. - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nonatomic, readonly) float as_hyphenationFactor; -- (float)as_hyphenationFactorAtIndex:(NSUInteger)index; - -/** - The document-wide default tab interval (A wrapper for NSParagraphStyle). (read-only) - - @discussion This property represents the default tab interval in points. Tabs after the - last specified in tabStops are placed at integer multiples of this distance (if positive). - @discussion Default is 0. - @discussion Get this property returns the first character's attribute. - @since CoreText:7.0 UIKit:7.0 ASText:7.0 - */ -@property (nonatomic, readonly) CGFloat as_defaultTabInterval; -- (CGFloat)as_defaultTabIntervalAtIndex:(NSUInteger)index; - -/** - An array of NSTextTab objects representing the receiver's tab stops. - (A wrapper for NSParagraphStyle). (read-only) - - @discussion The NSTextTab objects, sorted by location, define the tab stops for - the paragraph style. - @discussion Default is 12 TabStops with 28.0 tab interval. - @discussion Get this property returns the first character's attribute. - @since CoreText:7.0 UIKit:7.0 ASText:7.0 - */ -@property (nullable, nonatomic, copy, readonly) NSArray *as_tabStops; -- (nullable NSArray *)as_tabStopsAtIndex:(NSUInteger)index; - -#pragma mark - Get ASText attribute as property -///============================================================================= -/// @name Get ASText attribute as property -///============================================================================= - -/** - The text shadow. (read-only) - - @discussion Default value is nil (no shadow). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextShadow *as_textShadow; -- (nullable ASTextShadow *)as_textShadowAtIndex:(NSUInteger)index; - -/** - The text inner shadow. (read-only) - - @discussion Default value is nil (no shadow). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextShadow *as_textInnerShadow; -- (nullable ASTextShadow *)as_textInnerShadowAtIndex:(NSUInteger)index; - -/** - The text underline. (read-only) - - @discussion Default value is nil (no underline). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextDecoration *as_textUnderline; -- (nullable ASTextDecoration *)as_textUnderlineAtIndex:(NSUInteger)index; - -/** - The text strikethrough. (read-only) - - @discussion Default value is nil (no strikethrough). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextDecoration *as_textStrikethrough; -- (nullable ASTextDecoration *)as_textStrikethroughAtIndex:(NSUInteger)index; - -/** - The text border. (read-only) - - @discussion Default value is nil (no border). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextBorder *as_textBorder; -- (nullable ASTextBorder *)as_textBorderAtIndex:(NSUInteger)index; - -/** - The text background border. (read-only) - - @discussion Default value is nil (no background border). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic, readonly) ASTextBorder *as_textBackgroundBorder; -- (nullable ASTextBorder *)as_textBackgroundBorderAtIndex:(NSUInteger)index; - -/** - The glyph transform. (read-only) - - @discussion Default value is CGAffineTransformIdentity (no transform). - @discussion Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nonatomic, readonly) CGAffineTransform as_textGlyphTransform; -- (CGAffineTransform)as_textGlyphTransformAtIndex:(NSUInteger)index; - - -#pragma mark - Query for ASText -///============================================================================= -/// @name Query for ASText -///============================================================================= - -/** - Returns the plain text from a range. - If there's `ASTextBackedStringAttributeName` attribute, the backed string will - replace the attributed string range. - - @param range A range in receiver. - @return The plain text. - */ -- (nullable NSString *)as_plainTextForRange:(NSRange)range; - - -#pragma mark - Create attachment string for ASText -///============================================================================= -/// @name Create attachment string for ASText -///============================================================================= - -/** - Creates and returns an attachment. - - @param content The attachment (UIImage/UIView/CALayer). - @param contentMode The attachment's content mode. - @param width The attachment's container width in layout. - @param ascent The attachment's container ascent in layout. - @param descent The attachment's container descent in layout. - - @return An attributed string, or nil if an error occurs. - @since ASText:6.0 - */ -+ (NSMutableAttributedString *)as_attachmentStringWithContent:(nullable id)content - contentMode:(UIViewContentMode)contentMode - width:(CGFloat)width - ascent:(CGFloat)ascent - descent:(CGFloat)descent; - -/** - Creates and returns an attachment. - - - Example: ContentMode:bottom Alignment:Top. - - The text The attachment holder - ↓ ↓ - ─────────┌──────────────────────┐─────── - / \ │ │ / ___| - / _ \ │ │| | - / ___ \ │ │| |___ ←── The text line - /_/ \_\│ ██████████████ │ \____| - ─────────│ ██████████████ │─────── - │ ██████████████ │ - │ ██████████████ ←───────────────── The attachment content - │ ██████████████ │ - └──────────────────────┘ - - @param content The attachment (UIImage/UIView/CALayer). - @param contentMode The attachment's content mode in attachment holder - @param attachmentSize The attachment holder's size in text layout. - @param font The attachment will align to this font. - @param alignment The attachment holder's alignment to text line. - - @return An attributed string, or nil if an error occurs. - @since ASText:6.0 - */ -+ (NSMutableAttributedString *)as_attachmentStringWithContent:(nullable id)content - contentMode:(UIViewContentMode)contentMode - attachmentSize:(CGSize)attachmentSize - alignToFont:(UIFont *)font - alignment:(ASTextVerticalAlignment)alignment; - -/** - Creates and returns an attahment from a fourquare image as if it was an emoji. - - @param image A fourquare image. - @param fontSize The font size. - - @return An attributed string, or nil if an error occurs. - @since ASText:6.0 - */ -+ (nullable NSMutableAttributedString *)as_attachmentStringWithEmojiImage:(UIImage *)image - fontSize:(CGFloat)fontSize; - -#pragma mark - Utility -///============================================================================= -/// @name Utility -///============================================================================= - -/** - Returns NSMakeRange(0, self.length). - */ -- (NSRange)as_rangeOfAll; - -/** - If YES, it share the same attribute in entire text range. - */ -- (BOOL)as_isSharedAttributesInAllRange; - -/** - If YES, it can be drawn with the [drawWithRect:options:context:] method or displayed with UIKit. - If NO, it should be drawn with CoreText or ASText. - - @discussion If the method returns NO, it means that there's at least one attribute - which is not supported by UIKit (such as CTParagraphStyleRef). If display this string - in UIKit, it may lose some attribute, or even crash the app. - */ -- (BOOL)as_canDrawWithUIKit; - -@end - - - - -/** - Set pre-defined attributes to attributed string. - All properties defined in UIKit, CoreText and ASText are included. - */ -@interface NSMutableAttributedString (ASText) - -#pragma mark - Set character attribute -///============================================================================= -/// @name Set character attribute -///============================================================================= - -/** - Sets the attributes to the entire text string. - - @discussion The old attributes will be removed. - - @param attributes A dictionary containing the attributes to set, or nil to remove all attributes. - */ -- (void)as_setAttributes:(nullable NSDictionary *)attributes; -- (void)setAs_attributes:(nullable NSDictionary *)attributes; - -/** - Sets an attribute with the given name and value to the entire text string. - - @param name A string specifying the attribute name. - @param value The attribute value associated with name. Pass `nil` or `NSNull` to - remove the attribute. - */ -- (void)as_setAttribute:(NSString *)name value:(nullable id)value; - -/** - Sets an attribute with the given name and value to the characters in the specified range. - - @param name A string specifying the attribute name. - @param value The attribute value associated with name. Pass `nil` or `NSNull` to - remove the attribute. - @param range The range of characters to which the specified attribute/value pair applies. - */ -- (void)as_setAttribute:(NSString *)name value:(nullable id)value range:(NSRange)range; - -/** - Removes all attributes in the specified range. - - @param range The range of characters. - */ -- (void)as_removeAttributesInRange:(NSRange)range; - - -#pragma mark - Set character attribute as property -///============================================================================= -/// @name Set character attribute as property -///============================================================================= - -/** - The font of the text. - - @discussion Default is Helvetica (Neue) 12. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) UIFont *as_font; -- (void)as_setFont:(nullable UIFont *)font range:(NSRange)range; - -/** - A kerning adjustment. - - @discussion Default is standard kerning. The kerning attribute indicate how many - points the following character should be shifted from its default offset as - defined by the current character's font in points; a positive kern indicates a - shift farther along and a negative kern indicates a shift closer to the current - character. If this attribute is not present, standard kerning will be used. - If this attribute is set to 0.0, no kerning will be done at all. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSNumber *as_kern; -- (void)as_setKern:(nullable NSNumber *)kern range:(NSRange)range; - -/** - The foreground color. - - @discussion Default is Black. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) UIColor *as_color; -- (void)as_setColor:(nullable UIColor *)color range:(NSRange)range; - -/** - The background color. - - @discussion Default is nil (or no background). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nullable, nonatomic) UIColor *as_backgroundColor; -- (void)as_setBackgroundColor:(nullable UIColor *)backgroundColor range:(NSRange)range; - -/** - The stroke width. - - @discussion Default value is 0.0 (no stroke). This attribute, interpreted as - a percentage of font point size, controls the text drawing mode: positive - values effect drawing with stroke only; negative values are for stroke and fill. - A typical value for outlined text is 3.0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSNumber *as_strokeWidth; -- (void)as_setStrokeWidth:(nullable NSNumber *)strokeWidth range:(NSRange)range; - -/** - The stroke color. - - @discussion Default value is nil (same as foreground color). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) UIColor *as_strokeColor; -- (void)as_setStrokeColor:(nullable UIColor *)strokeColor range:(NSRange)range; - -/** - The text shadow. - - @discussion Default value is nil (no shadow). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSShadow *as_shadow; -- (void)as_setShadow:(nullable NSShadow *)shadow range:(NSRange)range; - -/** - The strikethrough style. - - @discussion Default value is NSUnderlineStyleNone (no strikethrough). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nonatomic) NSUnderlineStyle as_strikethroughStyle; -- (void)as_setStrikethroughStyle:(NSUnderlineStyle)strikethroughStyle range:(NSRange)range; - -/** - The strikethrough color. - - @discussion Default value is nil (same as foreground color). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic) UIColor *as_strikethroughColor; -- (void)as_setStrikethroughColor:(nullable UIColor *)strikethroughColor range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - The underline style. - - @discussion Default value is NSUnderlineStyleNone (no underline). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 - */ -@property (nonatomic) NSUnderlineStyle as_underlineStyle; -- (void)as_setUnderlineStyle:(NSUnderlineStyle)underlineStyle range:(NSRange)range; - -/** - The underline color. - - @discussion Default value is nil (same as foreground color). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:7.0 - */ -@property (nullable, nonatomic) UIColor *as_underlineColor; -- (void)as_setUnderlineColor:(nullable UIColor *)underlineColor range:(NSRange)range; - -/** - Ligature formation control. - - @discussion Default is int value 1. The ligature attribute determines what kinds - of ligatures should be used when displaying the string. A value of 0 indicates - that only ligatures essential for proper rendering of text should be used, - 1 indicates that standard ligatures should be used, and 2 indicates that all - available ligatures should be used. Which ligatures are standard depends on the - script and possibly the font. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:3.2 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSNumber *as_ligature; -- (void)as_setLigature:(nullable NSNumber *)ligature range:(NSRange)range; - -/** - The text effect. - - @discussion Default is nil (no effect). The only currently supported value - is NSTextEffectLetterpressStyle. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic) NSString *as_textEffect; -- (void)as_setTextEffect:(nullable NSString *)textEffect range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - The skew to be applied to glyphs. - - @discussion Default is 0 (no skew). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic) NSNumber *as_obliqueness; -- (void)as_setObliqueness:(nullable NSNumber *)obliqueness range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - The log of the expansion factor to be applied to glyphs. - - @discussion Default is 0 (no expansion). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic) NSNumber *as_expansion; -- (void)as_setExpansion:(nullable NSNumber *)expansion range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - The character's offset from the baseline, in points. - - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:7.0 - */ -@property (nullable, nonatomic) NSNumber *as_baselineOffset; -- (void)as_setBaselineOffset:(nullable NSNumber *)baselineOffset range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - Glyph orientation control. - - @discussion Default is NO. A value of NO indicates that horizontal glyph forms - are to be used, YES indicates that vertical glyph forms are to be used. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:4.3 ASText:6.0 - */ -@property (nonatomic) BOOL as_verticalGlyphForm; -- (void)as_setVerticalGlyphForm:(BOOL)verticalGlyphForm range:(NSRange)range; - -/** - Specifies text language. - - @discussion Value must be a NSString containing a locale identifier. Default is - unset. When this attribute is set to a valid identifier, it will be used to select - localized glyphs (if supported by the font) and locale-specific line breaking rules. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:7.0 ASText:7.0 - */ -@property (nullable, nonatomic) NSString *as_language; -- (void)as_setLanguage:(nullable NSString *)language range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - Specifies a bidirectional override or embedding. - - @discussion See alse NSWritingDirection and NSWritingDirectionAttributeName. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:7.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSArray *as_writingDirection; -- (void)as_setWritingDirection:(nullable NSArray *)writingDirection range:(NSRange)range; - -/** - An NSParagraphStyle object which is used to specify things like - line alignment, tab rulers, writing direction, etc. - - @discussion Default is nil ([NSParagraphStyle defaultParagraphStyle]). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nullable, nonatomic) NSParagraphStyle *as_paragraphStyle; -- (void)as_setParagraphStyle:(nullable NSParagraphStyle *)paragraphStyle range:(NSRange)range; - - -#pragma mark - Set paragraph attribute as property -///============================================================================= -/// @name Set paragraph attribute as property -///============================================================================= - -/** - The text alignment (A wrapper for NSParagraphStyle). - - @discussion Natural text alignment is realized as left or right alignment - depending on the line sweep direction of the first script contained in the paragraph. - @discussion Default is NSTextAlignmentNatural. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) NSTextAlignment as_alignment; -- (void)as_setAlignment:(NSTextAlignment)alignment range:(NSRange)range; - -/** - The mode that should be used to break lines (A wrapper for NSParagraphStyle). - - @discussion This property contains the line break mode to be used laying out the paragraph's text. - @discussion Default is NSLineBreakByWordWrapping. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) NSLineBreakMode as_lineBreakMode; -- (void)as_setLineBreakMode:(NSLineBreakMode)lineBreakMode range:(NSRange)range; - -/** - The distance in points between the bottom of one line fragment and the top of the next. - (A wrapper for NSParagraphStyle) - - @discussion This value is always nonnegative. This value is included in the line - fragment heights in the layout manager. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_lineSpacing; -- (void)as_setLineSpacing:(CGFloat)lineSpacing range:(NSRange)range; - -/** - The space after the end of the paragraph (A wrapper for NSParagraphStyle). - - @discussion This property contains the space (measured in points) added at the - end of the paragraph to separate it from the following paragraph. This value must - be nonnegative. The space between paragraphs is determined by adding the previous - paragraph's paragraphSpacing and the current paragraph's paragraphSpacingBefore. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_paragraphSpacing; -- (void)as_setParagraphSpacing:(CGFloat)paragraphSpacing range:(NSRange)range; - -/** - The distance between the paragraph's top and the beginning of its text content. - (A wrapper for NSParagraphStyle). - - @discussion This property contains the space (measured in points) between the - paragraph's top and the beginning of its text content. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_paragraphSpacingBefore; -- (void)as_setParagraphSpacingBefore:(CGFloat)paragraphSpacingBefore range:(NSRange)range; - -/** - The indentation of the first line (A wrapper for NSParagraphStyle). - - @discussion This property contains the distance (in points) from the leading margin - of a text container to the beginning of the paragraph's first line. This value - is always nonnegative. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_firstLineHeadIndent; -- (void)as_setFirstLineHeadIndent:(CGFloat)firstLineHeadIndent range:(NSRange)range; - -/** - The indentation of the receiver's lines other than the first. (A wrapper for NSParagraphStyle). - - @discussion This property contains the distance (in points) from the leading margin - of a text container to the beginning of lines other than the first. This value is - always nonnegative. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_headIndent; -- (void)as_setHeadIndent:(CGFloat)headIndent range:(NSRange)range; - -/** - The trailing indentation (A wrapper for NSParagraphStyle). - - @discussion If positive, this value is the distance from the leading margin - (for example, the left margin in left-to-right text). If 0 or negative, it's the - distance from the trailing margin. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_tailIndent; -- (void)as_setTailIndent:(CGFloat)tailIndent range:(NSRange)range; - -/** - The receiver's minimum height (A wrapper for NSParagraphStyle). - - @discussion This property contains the minimum height in points that any line in - the receiver will occupy, regardless of the font size or size of any attached graphic. - This value must be nonnegative. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_minimumLineHeight; -- (void)as_setMinimumLineHeight:(CGFloat)minimumLineHeight range:(NSRange)range; - -/** - The receiver's maximum line height (A wrapper for NSParagraphStyle). - - @discussion This property contains the maximum height in points that any line in - the receiver will occupy, regardless of the font size or size of any attached graphic. - This value is always nonnegative. Glyphs and graphics exceeding this height will - overlap neighboring lines; however, a maximum height of 0 implies no line height limit. - Although this limit applies to the line itself, line spacing adds extra space between adjacent lines. - @discussion Default is 0 (no limit). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_maximumLineHeight; -- (void)as_setMaximumLineHeight:(CGFloat)maximumLineHeight range:(NSRange)range; - -/** - The line height multiple (A wrapper for NSParagraphStyle). - - @discussion This property contains the line break mode to be used laying out the paragraph's text. - @discussion Default is 0 (no multiple). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) CGFloat as_lineHeightMultiple; -- (void)as_setLineHeightMultiple:(CGFloat)lineHeightMultiple range:(NSRange)range; - -/** - The base writing direction (A wrapper for NSParagraphStyle). - - @discussion If you specify NSWritingDirectionNaturalDirection, the receiver resolves - the writing direction to either NSWritingDirectionLeftToRight or NSWritingDirectionRightToLeft, - depending on the direction for the user's `language` preference setting. - @discussion Default is NSWritingDirectionNatural. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:6.0 UIKit:6.0 ASText:6.0 - */ -@property (nonatomic) NSWritingDirection as_baseWritingDirection; -- (void)as_setBaseWritingDirection:(NSWritingDirection)baseWritingDirection range:(NSRange)range; - -/** - The paragraph's threshold for hyphenation. (A wrapper for NSParagraphStyle). - - @discussion Valid values lie between 0.0 and 1.0 inclusive. Hyphenation is attempted - when the ratio of the text width (as broken without hyphenation) to the width of the - line fragment is less than the hyphenation factor. When the paragraph's hyphenation - factor is 0.0, the layout manager's hyphenation factor is used instead. When both - are 0.0, hyphenation is disabled. - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since UIKit:6.0 - */ -@property (nonatomic) float as_hyphenationFactor; -- (void)as_setHyphenationFactor:(float)hyphenationFactor range:(NSRange)range; - -/** - The document-wide default tab interval (A wrapper for NSParagraphStyle). - - @discussion This property represents the default tab interval in points. Tabs after the - last specified in tabStops are placed at integer multiples of this distance (if positive). - @discussion Default is 0. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:7.0 UIKit:7.0 ASText:7.0 - */ -@property (nonatomic) CGFloat as_defaultTabInterval; -- (void)as_setDefaultTabInterval:(CGFloat)defaultTabInterval range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -/** - An array of NSTextTab objects representing the receiver's tab stops. - (A wrapper for NSParagraphStyle). - - @discussion The NSTextTab objects, sorted by location, define the tab stops for - the paragraph style. - @discussion Default is 12 TabStops with 28.0 tab interval. - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since CoreText:7.0 UIKit:7.0 ASText:7.0 - */ -@property (nullable, nonatomic, copy) NSArray *as_tabStops; -- (void)as_setTabStops:(nullable NSArray *)tabStops range:(NSRange)range NS_AVAILABLE_IOS(7_0); - -#pragma mark - Set ASText attribute as property -///============================================================================= -/// @name Set ASText attribute as property -///============================================================================= - -/** - The text shadow. - - @discussion Default value is nil (no shadow). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextShadow *as_textShadow; -- (void)as_setTextShadow:(nullable ASTextShadow *)textShadow range:(NSRange)range; - -/** - The text inner shadow. - - @discussion Default value is nil (no shadow). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextShadow *as_textInnerShadow; -- (void)as_setTextInnerShadow:(nullable ASTextShadow *)textInnerShadow range:(NSRange)range; - -/** - The text underline. - - @discussion Default value is nil (no underline). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextDecoration *as_textUnderline; -- (void)as_setTextUnderline:(nullable ASTextDecoration *)textUnderline range:(NSRange)range; - -/** - The text strikethrough. - - @discussion Default value is nil (no strikethrough). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextDecoration *as_textStrikethrough; -- (void)as_setTextStrikethrough:(nullable ASTextDecoration *)textStrikethrough range:(NSRange)range; - -/** - The text border. - - @discussion Default value is nil (no border). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextBorder *as_textBorder; -- (void)as_setTextBorder:(nullable ASTextBorder *)textBorder range:(NSRange)range; - -/** - The text background border. - - @discussion Default value is nil (no background border). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nullable, nonatomic) ASTextBorder *as_textBackgroundBorder; -- (void)as_setTextBackgroundBorder:(nullable ASTextBorder *)textBackgroundBorder range:(NSRange)range; - -/** - The glyph transform. - - @discussion Default value is CGAffineTransformIdentity (no transform). - @discussion Set this property applies to the entire text string. - Get this property returns the first character's attribute. - @since ASText:6.0 - */ -@property (nonatomic) CGAffineTransform as_textGlyphTransform; -- (void)as_setTextGlyphTransform:(CGAffineTransform)textGlyphTransform range:(NSRange)range; - - -#pragma mark - Set discontinuous attribute for range -///============================================================================= -/// @name Set discontinuous attribute for range -///============================================================================= - -- (void)as_setSuperscript:(nullable NSNumber *)superscript range:(NSRange)range; -- (void)as_setGlyphInfo:(nullable CTGlyphInfoRef)glyphInfo range:(NSRange)range; -- (void)as_setCharacterShape:(nullable NSNumber *)characterShape range:(NSRange)range __TVOS_PROHIBITED; -- (void)as_setRunDelegate:(nullable CTRunDelegateRef)runDelegate range:(NSRange)range; -- (void)as_setBaselineClass:(nullable CFStringRef)baselineClass range:(NSRange)range; -- (void)as_setBaselineInfo:(nullable CFDictionaryRef)baselineInfo range:(NSRange)range; -- (void)as_setBaselineReferenceInfo:(nullable CFDictionaryRef)referenceInfo range:(NSRange)range; -- (void)as_setRubyAnnotation:(nullable CTRubyAnnotationRef)ruby range:(NSRange)range NS_AVAILABLE_IOS(8_0); -- (void)as_setAttachment:(nullable NSTextAttachment *)attachment range:(NSRange)range NS_AVAILABLE_IOS(7_0); -- (void)as_setLink:(nullable id)link range:(NSRange)range NS_AVAILABLE_IOS(7_0); -- (void)as_setTextBackedString:(nullable ASTextBackedString *)textBackedString range:(NSRange)range; -- (void)as_setTextBinding:(nullable ASTextBinding *)textBinding range:(NSRange)range; -- (void)as_setTextAttachment:(nullable ASTextAttachment *)textAttachment range:(NSRange)range; -- (void)as_setTextHighlight:(nullable ASTextHighlight *)textHighlight range:(NSRange)range; -- (void)as_setTextBlockBorder:(nullable ASTextBorder *)textBlockBorder range:(NSRange)range; - - -#pragma mark - Convenience methods for text highlight -///============================================================================= -/// @name Convenience methods for text highlight -///============================================================================= - -/** - Convenience method to set text highlight - - @param range text range - @param color text color (pass nil to ignore) - @param backgroundColor text background color when highlight - @param userInfo user information dictionary (pass nil to ignore) - @param tapAction tap action when user tap the highlight (pass nil to ignore) - @param longPressAction long press action when user long press the highlight (pass nil to ignore) - */ -- (void)as_setTextHighlightRange:(NSRange)range - color:(nullable UIColor *)color - backgroundColor:(nullable UIColor *)backgroundColor - userInfo:(nullable NSDictionary *)userInfo - tapAction:(nullable ASTextAction)tapAction - longPressAction:(nullable ASTextAction)longPressAction; - -/** - Convenience method to set text highlight - - @param range text range - @param color text color (pass nil to ignore) - @param backgroundColor text background color when highlight - @param tapAction tap action when user tap the highlight (pass nil to ignore) - */ -- (void)as_setTextHighlightRange:(NSRange)range - color:(nullable UIColor *)color - backgroundColor:(nullable UIColor *)backgroundColor - tapAction:(nullable ASTextAction)tapAction; - -/** - Convenience method to set text highlight - - @param range text range - @param color text color (pass nil to ignore) - @param backgroundColor text background color when highlight - @param userInfo tap action when user tap the highlight (pass nil to ignore) - */ -- (void)as_setTextHighlightRange:(NSRange)range - color:(nullable UIColor *)color - backgroundColor:(nullable UIColor *)backgroundColor - userInfo:(nullable NSDictionary *)userInfo; - -#pragma mark - Utilities -///============================================================================= -/// @name Utilities -///============================================================================= - -/** - Inserts into the receiver the characters of a given string at a given location. - The new string inherit the attributes of the first replaced character from location. - - @param string The string to insert into the receiver, must not be nil. - @param location The location at which string is inserted. The location must not - exceed the bounds of the receiver. - @throw Raises an NSRangeException if the location out of bounds. - */ -- (void)as_insertString:(NSString *)string atIndex:(NSUInteger)location; - -/** - Adds to the end of the receiver the characters of a given string. - The new string inherit the attributes of the receiver's tail. - - @param string The string to append to the receiver, must not be nil. - */ -- (void)as_appendString:(NSString *)string; - -/** - Removes all discontinuous attributes in a specified range. - See `allDiscontinuousAttributeKeys`. - - @param range A text range. - */ -- (void)as_removeDiscontinuousAttributesInRange:(NSRange)range; - -/** - Returns all discontinuous attribute keys, such as RunDelegate/Attachment/Ruby. - - @discussion These attributes can only set to a specified range of text, and - should not extend to other range when editing text. - */ -+ (NSArray *)as_allDiscontinuousAttributeKeys; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/TextExperiment/Utility/NSAttributedString+ASText.mm b/Source/TextExperiment/Utility/NSAttributedString+ASText.mm deleted file mode 100644 index 39f628151c..0000000000 --- a/Source/TextExperiment/Utility/NSAttributedString+ASText.mm +++ /dev/null @@ -1,1208 +0,0 @@ -// -// NSAttributedString+ASText.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import -#import -#import - - -// Dummy class for category -@interface NSAttributedString_ASText : NSObject @end -@implementation NSAttributedString_ASText @end - - -@implementation NSAttributedString (ASText) - -- (NSDictionary *)as_attributesAtIndex:(NSUInteger)index { - if (index > self.length || self.length == 0) return nil; - if (self.length > 0 && index == self.length) index--; - return [self attributesAtIndex:index effectiveRange:NULL]; -} - -- (id)as_attribute:(NSString *)attributeName atIndex:(NSUInteger)index { - if (!attributeName) return nil; - if (index > self.length || self.length == 0) return nil; - if (self.length > 0 && index == self.length) index--; - return [self attribute:attributeName atIndex:index effectiveRange:NULL]; -} - -- (NSDictionary *)as_attributes { - return [self as_attributesAtIndex:0]; -} - -- (UIFont *)as_font { - return [self as_fontAtIndex:0]; -} - -- (UIFont *)as_fontAtIndex:(NSUInteger)index { - return [self as_attribute:NSFontAttributeName atIndex:index]; -} - -- (NSNumber *)as_kern { - return [self as_kernAtIndex:0]; -} - -- (NSNumber *)as_kernAtIndex:(NSUInteger)index { - return [self as_attribute:NSKernAttributeName atIndex:index]; -} - -- (UIColor *)as_color { - return [self as_colorAtIndex:0]; -} - -- (UIColor *)as_colorAtIndex:(NSUInteger)index { - UIColor *color = [self as_attribute:NSForegroundColorAttributeName atIndex:index]; - if (!color) { - CGColorRef ref = (__bridge CGColorRef)([self as_attribute:(NSString *)kCTForegroundColorAttributeName atIndex:index]); - color = [UIColor colorWithCGColor:ref]; - } - if (color && ![color isKindOfClass:[UIColor class]]) { - if (CFGetTypeID((__bridge CFTypeRef)(color)) == CGColorGetTypeID()) { - color = [UIColor colorWithCGColor:(__bridge CGColorRef)(color)]; - } else { - color = nil; - } - } - return color; -} - -- (UIColor *)as_backgroundColor { - return [self as_backgroundColorAtIndex:0]; -} - -- (UIColor *)as_backgroundColorAtIndex:(NSUInteger)index { - return [self as_attribute:NSBackgroundColorAttributeName atIndex:index]; -} - -- (NSNumber *)as_strokeWidth { - return [self as_strokeWidthAtIndex:0]; -} - -- (NSNumber *)as_strokeWidthAtIndex:(NSUInteger)index { - return [self as_attribute:NSStrokeWidthAttributeName atIndex:index]; -} - -- (UIColor *)as_strokeColor { - return [self as_strokeColorAtIndex:0]; -} - -- (UIColor *)as_strokeColorAtIndex:(NSUInteger)index { - UIColor *color = [self as_attribute:NSStrokeColorAttributeName atIndex:index]; - if (!color) { - CGColorRef ref = (__bridge CGColorRef)([self as_attribute:(NSString *)kCTStrokeColorAttributeName atIndex:index]); - color = [UIColor colorWithCGColor:ref]; - } - return color; -} - -- (NSShadow *)as_shadow { - return [self as_shadowAtIndex:0]; -} - -- (NSShadow *)as_shadowAtIndex:(NSUInteger)index { - return [self as_attribute:NSShadowAttributeName atIndex:index]; -} - -- (NSUnderlineStyle)as_strikethroughStyle { - return [self as_strikethroughStyleAtIndex:0]; -} - -- (NSUnderlineStyle)as_strikethroughStyleAtIndex:(NSUInteger)index { - NSNumber *style = [self as_attribute:NSStrikethroughStyleAttributeName atIndex:index]; - return (NSUnderlineStyle)style.integerValue; -} - -- (UIColor *)as_strikethroughColor { - return [self as_strikethroughColorAtIndex:0]; -} - -- (UIColor *)as_strikethroughColorAtIndex:(NSUInteger)index { - return [self as_attribute:NSStrikethroughColorAttributeName atIndex:index]; -} - -- (NSUnderlineStyle)as_underlineStyle { - return [self as_underlineStyleAtIndex:0]; -} - -- (NSUnderlineStyle)as_underlineStyleAtIndex:(NSUInteger)index { - NSNumber *style = [self as_attribute:NSUnderlineStyleAttributeName atIndex:index]; - return (NSUnderlineStyle)style.integerValue; -} - -- (UIColor *)as_underlineColor { - return [self as_underlineColorAtIndex:0]; -} - -- (UIColor *)as_underlineColorAtIndex:(NSUInteger)index { - UIColor *color = [self as_attribute:NSUnderlineColorAttributeName atIndex:index]; - if (!color) { - CGColorRef ref = (__bridge CGColorRef)([self as_attribute:(NSString *)kCTUnderlineColorAttributeName atIndex:index]); - color = [UIColor colorWithCGColor:ref]; - } - return color; -} - -- (NSNumber *)as_ligature { - return [self as_ligatureAtIndex:0]; -} - -- (NSNumber *)as_ligatureAtIndex:(NSUInteger)index { - return [self as_attribute:NSLigatureAttributeName atIndex:index]; -} - -- (NSString *)as_textEffect { - return [self as_textEffectAtIndex:0]; -} - -- (NSString *)as_textEffectAtIndex:(NSUInteger)index { - return [self as_attribute:NSTextEffectAttributeName atIndex:index]; -} - -- (NSNumber *)as_obliqueness { - return [self as_obliquenessAtIndex:0]; -} - -- (NSNumber *)as_obliquenessAtIndex:(NSUInteger)index { - return [self as_attribute:NSObliquenessAttributeName atIndex:index]; -} - -- (NSNumber *)as_expansion { - return [self as_expansionAtIndex:0]; -} - -- (NSNumber *)as_expansionAtIndex:(NSUInteger)index { - return [self as_attribute:NSExpansionAttributeName atIndex:index]; -} - -- (NSNumber *)as_baselineOffset { - return [self as_baselineOffsetAtIndex:0]; -} - -- (NSNumber *)as_baselineOffsetAtIndex:(NSUInteger)index { - return [self as_attribute:NSBaselineOffsetAttributeName atIndex:index]; -} - -- (BOOL)as_verticalGlyphForm { - return [self as_verticalGlyphFormAtIndex:0]; -} - -- (BOOL)as_verticalGlyphFormAtIndex:(NSUInteger)index { - NSNumber *num = [self as_attribute:NSVerticalGlyphFormAttributeName atIndex:index]; - return num.boolValue; -} - -- (NSString *)as_language { - return [self as_languageAtIndex:0]; -} - -- (NSString *)as_languageAtIndex:(NSUInteger)index { - return [self as_attribute:(id)kCTLanguageAttributeName atIndex:index]; -} - -- (NSArray *)as_writingDirection { - return [self as_writingDirectionAtIndex:0]; -} - -- (NSArray *)as_writingDirectionAtIndex:(NSUInteger)index { - return [self as_attribute:(id)kCTWritingDirectionAttributeName atIndex:index]; -} - -- (NSParagraphStyle *)as_paragraphStyle { - return [self as_paragraphStyleAtIndex:0]; -} - -- (NSParagraphStyle *)as_paragraphStyleAtIndex:(NSUInteger)index { - /* - NSParagraphStyle is NOT toll-free bridged to CTParagraphStyleRef. - - CoreText can use both NSParagraphStyle and CTParagraphStyleRef, - but UILabel/UITextView can only use NSParagraphStyle. - - We use NSParagraphStyle in both CoreText and UIKit. - */ - NSParagraphStyle *style = [self as_attribute:NSParagraphStyleAttributeName atIndex:index]; - if (style) { - if (CFGetTypeID((__bridge CFTypeRef)(style)) == CTParagraphStyleGetTypeID()) { \ - style = [NSParagraphStyle as_styleWithCTStyle:(__bridge CTParagraphStyleRef)(style)]; - } - } - return style; -} - -#define ParagraphAttribute(_attr_) \ -NSParagraphStyle *style = self.as_paragraphStyle; \ -if (!style) style = [NSParagraphStyle defaultParagraphStyle]; \ -return style. _attr_; - -#define ParagraphAttributeAtIndex(_attr_) \ -NSParagraphStyle *style = [self as_paragraphStyleAtIndex:index]; \ -if (!style) style = [NSParagraphStyle defaultParagraphStyle]; \ -return style. _attr_; - -- (NSTextAlignment)as_alignment { - ParagraphAttribute(alignment); -} - -- (NSLineBreakMode)as_lineBreakMode { - ParagraphAttribute(lineBreakMode); -} - -- (CGFloat)as_lineSpacing { - ParagraphAttribute(lineSpacing); -} - -- (CGFloat)as_paragraphSpacing { - ParagraphAttribute(paragraphSpacing); -} - -- (CGFloat)as_paragraphSpacingBefore { - ParagraphAttribute(paragraphSpacingBefore); -} - -- (CGFloat)as_firstLineHeadIndent { - ParagraphAttribute(firstLineHeadIndent); -} - -- (CGFloat)as_headIndent { - ParagraphAttribute(headIndent); -} - -- (CGFloat)as_tailIndent { - ParagraphAttribute(tailIndent); -} - -- (CGFloat)as_minimumLineHeight { - ParagraphAttribute(minimumLineHeight); -} - -- (CGFloat)as_maximumLineHeight { - ParagraphAttribute(maximumLineHeight); -} - -- (CGFloat)as_lineHeightMultiple { - ParagraphAttribute(lineHeightMultiple); -} - -- (NSWritingDirection)as_baseWritingDirection { - ParagraphAttribute(baseWritingDirection); -} - -- (float)as_hyphenationFactor { - ParagraphAttribute(hyphenationFactor); -} - -- (CGFloat)as_defaultTabInterval { - ParagraphAttribute(defaultTabInterval); -} - -- (NSArray *)as_tabStops { - ParagraphAttribute(tabStops); -} - -- (NSTextAlignment)as_alignmentAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(alignment); -} - -- (NSLineBreakMode)as_lineBreakModeAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(lineBreakMode); -} - -- (CGFloat)as_lineSpacingAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(lineSpacing); -} - -- (CGFloat)as_paragraphSpacingAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(paragraphSpacing); -} - -- (CGFloat)as_paragraphSpacingBeforeAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(paragraphSpacingBefore); -} - -- (CGFloat)as_firstLineHeadIndentAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(firstLineHeadIndent); -} - -- (CGFloat)as_headIndentAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(headIndent); -} - -- (CGFloat)as_tailIndentAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(tailIndent); -} - -- (CGFloat)as_minimumLineHeightAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(minimumLineHeight); -} - -- (CGFloat)as_maximumLineHeightAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(maximumLineHeight); -} - -- (CGFloat)as_lineHeightMultipleAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(lineHeightMultiple); -} - -- (NSWritingDirection)as_baseWritingDirectionAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(baseWritingDirection); -} - -- (float)as_hyphenationFactorAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(hyphenationFactor); -} - -- (CGFloat)as_defaultTabIntervalAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(defaultTabInterval); -} - -- (NSArray *)as_tabStopsAtIndex:(NSUInteger)index { - ParagraphAttributeAtIndex(tabStops); -} - -#undef ParagraphAttribute -#undef ParagraphAttributeAtIndex - -- (ASTextShadow *)as_textShadow { - return [self as_textShadowAtIndex:0]; -} - -- (ASTextShadow *)as_textShadowAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextShadowAttributeName atIndex:index]; -} - -- (ASTextShadow *)as_textInnerShadow { - return [self as_textInnerShadowAtIndex:0]; -} - -- (ASTextShadow *)as_textInnerShadowAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextInnerShadowAttributeName atIndex:index]; -} - -- (ASTextDecoration *)as_textUnderline { - return [self as_textUnderlineAtIndex:0]; -} - -- (ASTextDecoration *)as_textUnderlineAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextUnderlineAttributeName atIndex:index]; -} - -- (ASTextDecoration *)as_textStrikethrough { - return [self as_textStrikethroughAtIndex:0]; -} - -- (ASTextDecoration *)as_textStrikethroughAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextStrikethroughAttributeName atIndex:index]; -} - -- (ASTextBorder *)as_textBorder { - return [self as_textBorderAtIndex:0]; -} - -- (ASTextBorder *)as_textBorderAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextBorderAttributeName atIndex:index]; -} - -- (ASTextBorder *)as_textBackgroundBorder { - return [self as_textBackgroundBorderAtIndex:0]; -} - -- (ASTextBorder *)as_textBackgroundBorderAtIndex:(NSUInteger)index { - return [self as_attribute:ASTextBackedStringAttributeName atIndex:index]; -} - -- (CGAffineTransform)as_textGlyphTransform { - return [self as_textGlyphTransformAtIndex:0]; -} - -- (CGAffineTransform)as_textGlyphTransformAtIndex:(NSUInteger)index { - NSValue *value = [self as_attribute:ASTextGlyphTransformAttributeName atIndex:index]; - if (!value) return CGAffineTransformIdentity; - return [value CGAffineTransformValue]; -} - -- (NSString *)as_plainTextForRange:(NSRange)range { - if (range.location == NSNotFound ||range.length == NSNotFound) return nil; - NSMutableString *result = [NSMutableString string]; - if (range.length == 0) return result; - NSString *string = self.string; - [self enumerateAttribute:ASTextBackedStringAttributeName inRange:range options:kNilOptions usingBlock:^(id value, NSRange range, BOOL *stop) { - ASTextBackedString *backed = value; - if (backed && backed.string) { - [result appendString:backed.string]; - } else { - [result appendString:[string substringWithRange:range]]; - } - }]; - return result; -} - -+ (NSMutableAttributedString *)as_attachmentStringWithContent:(id)content - contentMode:(UIViewContentMode)contentMode - width:(CGFloat)width - ascent:(CGFloat)ascent - descent:(CGFloat)descent { - NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:ASTextAttachmentToken]; - - ASTextAttachment *attach = [ASTextAttachment new]; - attach.content = content; - attach.contentMode = contentMode; - [atr as_setTextAttachment:attach range:NSMakeRange(0, atr.length)]; - - ASTextRunDelegate *delegate = [ASTextRunDelegate new]; - delegate.width = width; - delegate.ascent = ascent; - delegate.descent = descent; - CTRunDelegateRef delegateRef = delegate.CTRunDelegate; - [atr as_setRunDelegate:delegateRef range:NSMakeRange(0, atr.length)]; - if (delegate) CFRelease(delegateRef); - - return atr; -} - -+ (NSMutableAttributedString *)as_attachmentStringWithContent:(id)content - contentMode:(UIViewContentMode)contentMode - attachmentSize:(CGSize)attachmentSize - alignToFont:(UIFont *)font - alignment:(ASTextVerticalAlignment)alignment { - NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:ASTextAttachmentToken]; - - ASTextAttachment *attach = [ASTextAttachment new]; - attach.content = content; - attach.contentMode = contentMode; - [atr as_setTextAttachment:attach range:NSMakeRange(0, atr.length)]; - - ASTextRunDelegate *delegate = [ASTextRunDelegate new]; - delegate.width = attachmentSize.width; - switch (alignment) { - case ASTextVerticalAlignmentTop: { - delegate.ascent = font.ascender; - delegate.descent = attachmentSize.height - font.ascender; - if (delegate.descent < 0) { - delegate.descent = 0; - delegate.ascent = attachmentSize.height; - } - } break; - case ASTextVerticalAlignmentCenter: { - CGFloat fontHeight = font.ascender - font.descender; - CGFloat yOffset = font.ascender - fontHeight * 0.5; - delegate.ascent = attachmentSize.height * 0.5 + yOffset; - delegate.descent = attachmentSize.height - delegate.ascent; - if (delegate.descent < 0) { - delegate.descent = 0; - delegate.ascent = attachmentSize.height; - } - } break; - case ASTextVerticalAlignmentBottom: { - delegate.ascent = attachmentSize.height + font.descender; - delegate.descent = -font.descender; - if (delegate.ascent < 0) { - delegate.ascent = 0; - delegate.descent = attachmentSize.height; - } - } break; - default: { - delegate.ascent = attachmentSize.height; - delegate.descent = 0; - } break; - } - - CTRunDelegateRef delegateRef = delegate.CTRunDelegate; - [atr as_setRunDelegate:delegateRef range:NSMakeRange(0, atr.length)]; - if (delegate) CFRelease(delegateRef); - - return atr; -} - -+ (NSMutableAttributedString *)as_attachmentStringWithEmojiImage:(UIImage *)image - fontSize:(CGFloat)fontSize { - if (!image || fontSize <= 0) return nil; - - BOOL hasAnim = NO; - if (image.images.count > 1) { - hasAnim = YES; - } else if (NSProtocolFromString(@"ASAnimatedImage") && - [image conformsToProtocol:NSProtocolFromString(@"ASAnimatedImage")]) { - NSNumber *frameCount = [image valueForKey:@"animatedImageFrameCount"]; - if (frameCount.intValue > 1) hasAnim = YES; - } - - CGFloat ascent = ASTextEmojiGetAscentWithFontSize(fontSize); - CGFloat descent = ASTextEmojiGetDescentWithFontSize(fontSize); - CGRect bounding = ASTextEmojiGetGlyphBoundingRectWithFontSize(fontSize); - - ASTextRunDelegate *delegate = [ASTextRunDelegate new]; - delegate.ascent = ascent; - delegate.descent = descent; - delegate.width = bounding.size.width + 2 * bounding.origin.x; - - ASTextAttachment *attachment = [ASTextAttachment new]; - attachment.contentMode = UIViewContentModeScaleAspectFit; - attachment.contentInsets = UIEdgeInsetsMake(ascent - (bounding.size.height + bounding.origin.y), bounding.origin.x, descent + bounding.origin.y, bounding.origin.x); - if (hasAnim) { - Class imageClass = NSClassFromString(@"ASAnimatedImageView"); - if (!imageClass) imageClass = [UIImageView class]; - UIImageView *view = (id)[imageClass new]; - view.frame = bounding; - view.image = image; - view.contentMode = UIViewContentModeScaleAspectFit; - attachment.content = view; - } else { - attachment.content = image; - } - - NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:ASTextAttachmentToken]; - [atr as_setTextAttachment:attachment range:NSMakeRange(0, atr.length)]; - CTRunDelegateRef ctDelegate = delegate.CTRunDelegate; - [atr as_setRunDelegate:ctDelegate range:NSMakeRange(0, atr.length)]; - if (ctDelegate) CFRelease(ctDelegate); - - return atr; -} - -- (NSRange)as_rangeOfAll { - return NSMakeRange(0, self.length); -} - -- (BOOL)as_isSharedAttributesInAllRange { - __block BOOL shared = YES; - __block NSDictionary *firstAttrs = nil; - [self enumerateAttributesInRange:self.as_rangeOfAll options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { - if (range.location == 0) { - firstAttrs = attrs; - } else { - if (firstAttrs.count != attrs.count) { - shared = NO; - *stop = YES; - } else if (firstAttrs) { - if (![firstAttrs isEqualToDictionary:attrs]) { - shared = NO; - *stop = YES; - } - } - } - }]; - return shared; -} - -- (BOOL)as_canDrawWithUIKit { - static NSMutableSet *failSet; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - failSet = [NSMutableSet new]; - [failSet addObject:(id)kCTGlyphInfoAttributeName]; -#if TARGET_OS_IOS -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [failSet addObject:(id)kCTCharacterShapeAttributeName]; -#pragma clang diagnostic pop -#endif - [failSet addObject:(id)kCTLanguageAttributeName]; - [failSet addObject:(id)kCTRunDelegateAttributeName]; - [failSet addObject:(id)kCTBaselineClassAttributeName]; - [failSet addObject:(id)kCTBaselineInfoAttributeName]; - [failSet addObject:(id)kCTBaselineReferenceInfoAttributeName]; - [failSet addObject:(id)kCTRubyAnnotationAttributeName]; - [failSet addObject:ASTextShadowAttributeName]; - [failSet addObject:ASTextInnerShadowAttributeName]; - [failSet addObject:ASTextUnderlineAttributeName]; - [failSet addObject:ASTextStrikethroughAttributeName]; - [failSet addObject:ASTextBorderAttributeName]; - [failSet addObject:ASTextBackgroundBorderAttributeName]; - [failSet addObject:ASTextBlockBorderAttributeName]; - [failSet addObject:ASTextAttachmentAttributeName]; - [failSet addObject:ASTextHighlightAttributeName]; - [failSet addObject:ASTextGlyphTransformAttributeName]; - }); - -#define Fail { result = NO; *stop = YES; return; } - __block BOOL result = YES; - [self enumerateAttributesInRange:self.as_rangeOfAll options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { - if (attrs.count == 0) return; - for (NSString *str in attrs.allKeys) { - if ([failSet containsObject:str]) Fail; - } - if (attrs[(id)kCTForegroundColorAttributeName] && !attrs[NSForegroundColorAttributeName]) Fail; - if (attrs[(id)kCTStrokeColorAttributeName] && !attrs[NSStrokeColorAttributeName]) Fail; - if (attrs[(id)kCTUnderlineColorAttributeName]) { - if (!attrs[NSUnderlineColorAttributeName]) Fail; - } - NSParagraphStyle *style = attrs[NSParagraphStyleAttributeName]; - if (style && CFGetTypeID((__bridge CFTypeRef)(style)) == CTParagraphStyleGetTypeID()) Fail; - }]; - return result; -#undef Fail -} - -@end - -@implementation NSMutableAttributedString (ASText) - -- (void)as_setAttributes:(NSDictionary *)attributes { - [self setAs_attributes:attributes]; -} - -- (void)setAs_attributes:(NSDictionary *)attributes { - if (attributes == (id)[NSNull null]) attributes = nil; - [self setAttributes:@{} range:NSMakeRange(0, self.length)]; - [attributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - [self as_setAttribute:key value:obj]; - }]; -} - -- (void)as_setAttribute:(NSString *)name value:(id)value { - [self as_setAttribute:name value:value range:NSMakeRange(0, self.length)]; -} - -- (void)as_setAttribute:(NSString *)name value:(id)value range:(NSRange)range { - if (!name || [NSNull isEqual:name]) return; - if (value && ![NSNull isEqual:value]) [self addAttribute:name value:value range:range]; - else [self removeAttribute:name range:range]; -} - -- (void)as_removeAttributesInRange:(NSRange)range { - [self setAttributes:nil range:range]; -} - -#pragma mark - Property Setter - -- (void)setAs_font:(UIFont *)font { - /* - In iOS7 and later, UIFont is toll-free bridged to CTFontRef, - although Apple does not mention it in documentation. - - In iOS6, UIFont is a wrapper for CTFontRef, so CoreText can alse use UIfont, - but UILabel/UITextView cannot use CTFontRef. - - We use UIFont for both CoreText and UIKit. - */ - [self as_setFont:font range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_kern:(NSNumber *)kern { - [self as_setKern:kern range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_color:(UIColor *)color { - [self as_setColor:color range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_backgroundColor:(UIColor *)backgroundColor { - [self as_setBackgroundColor:backgroundColor range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_strokeWidth:(NSNumber *)strokeWidth { - [self as_setStrokeWidth:strokeWidth range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_strokeColor:(UIColor *)strokeColor { - [self as_setStrokeColor:strokeColor range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_shadow:(NSShadow *)shadow { - [self as_setShadow:shadow range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_strikethroughStyle:(NSUnderlineStyle)strikethroughStyle { - [self as_setStrikethroughStyle:strikethroughStyle range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_strikethroughColor:(UIColor *)strikethroughColor { - [self as_setStrikethroughColor:strikethroughColor range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_underlineStyle:(NSUnderlineStyle)underlineStyle { - [self as_setUnderlineStyle:underlineStyle range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_underlineColor:(UIColor *)underlineColor { - [self as_setUnderlineColor:underlineColor range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_ligature:(NSNumber *)ligature { - [self as_setLigature:ligature range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textEffect:(NSString *)textEffect { - [self as_setTextEffect:textEffect range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_obliqueness:(NSNumber *)obliqueness { - [self as_setObliqueness:obliqueness range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_expansion:(NSNumber *)expansion { - [self as_setExpansion:expansion range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_baselineOffset:(NSNumber *)baselineOffset { - [self as_setBaselineOffset:baselineOffset range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_verticalGlyphForm:(BOOL)verticalGlyphForm { - [self as_setVerticalGlyphForm:verticalGlyphForm range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_language:(NSString *)language { - [self as_setLanguage:language range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_writingDirection:(NSArray *)writingDirection { - [self as_setWritingDirection:writingDirection range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_paragraphStyle:(NSParagraphStyle *)paragraphStyle { - /* - NSParagraphStyle is NOT toll-free bridged to CTParagraphStyleRef. - - CoreText can use both NSParagraphStyle and CTParagraphStyleRef, - but UILabel/UITextView can only use NSParagraphStyle. - - We use NSParagraphStyle in both CoreText and UIKit. - */ - [self as_setParagraphStyle:paragraphStyle range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_alignment:(NSTextAlignment)alignment { - [self as_setAlignment:alignment range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_baseWritingDirection:(NSWritingDirection)baseWritingDirection { - [self as_setBaseWritingDirection:baseWritingDirection range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_lineSpacing:(CGFloat)lineSpacing { - [self as_setLineSpacing:lineSpacing range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_paragraphSpacing:(CGFloat)paragraphSpacing { - [self as_setParagraphSpacing:paragraphSpacing range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_paragraphSpacingBefore:(CGFloat)paragraphSpacingBefore { - [self as_setParagraphSpacing:paragraphSpacingBefore range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_firstLineHeadIndent:(CGFloat)firstLineHeadIndent { - [self as_setFirstLineHeadIndent:firstLineHeadIndent range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_headIndent:(CGFloat)headIndent { - [self as_setHeadIndent:headIndent range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_tailIndent:(CGFloat)tailIndent { - [self as_setTailIndent:tailIndent range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_lineBreakMode:(NSLineBreakMode)lineBreakMode { - [self as_setLineBreakMode:lineBreakMode range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_minimumLineHeight:(CGFloat)minimumLineHeight { - [self as_setMinimumLineHeight:minimumLineHeight range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_maximumLineHeight:(CGFloat)maximumLineHeight { - [self as_setMaximumLineHeight:maximumLineHeight range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_lineHeightMultiple:(CGFloat)lineHeightMultiple { - [self as_setLineHeightMultiple:lineHeightMultiple range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_hyphenationFactor:(float)hyphenationFactor { - [self as_setHyphenationFactor:hyphenationFactor range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_defaultTabInterval:(CGFloat)defaultTabInterval { - [self as_setDefaultTabInterval:defaultTabInterval range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_tabStops:(NSArray *)tabStops { - [self as_setTabStops:tabStops range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textShadow:(ASTextShadow *)textShadow { - [self as_setTextShadow:textShadow range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textInnerShadow:(ASTextShadow *)textInnerShadow { - [self as_setTextInnerShadow:textInnerShadow range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textUnderline:(ASTextDecoration *)textUnderline { - [self as_setTextUnderline:textUnderline range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textStrikethrough:(ASTextDecoration *)textStrikethrough { - [self as_setTextStrikethrough:textStrikethrough range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textBorder:(ASTextBorder *)textBorder { - [self as_setTextBorder:textBorder range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textBackgroundBorder:(ASTextBorder *)textBackgroundBorder { - [self as_setTextBackgroundBorder:textBackgroundBorder range:NSMakeRange(0, self.length)]; -} - -- (void)setAs_textGlyphTransform:(CGAffineTransform)textGlyphTransform { - [self as_setTextGlyphTransform:textGlyphTransform range:NSMakeRange(0, self.length)]; -} - -#pragma mark - Range Setter - -- (void)as_setFont:(UIFont *)font range:(NSRange)range { - [self as_setAttribute:NSFontAttributeName value:font range:range]; -} - -- (void)as_setKern:(NSNumber *)kern range:(NSRange)range { - [self as_setAttribute:NSKernAttributeName value:kern range:range]; -} - -- (void)as_setColor:(UIColor *)color range:(NSRange)range { - [self as_setAttribute:(id)kCTForegroundColorAttributeName value:(id)color.CGColor range:range]; - [self as_setAttribute:NSForegroundColorAttributeName value:color range:range]; -} - -- (void)as_setBackgroundColor:(UIColor *)backgroundColor range:(NSRange)range { - [self as_setAttribute:NSBackgroundColorAttributeName value:backgroundColor range:range]; -} - -- (void)as_setStrokeWidth:(NSNumber *)strokeWidth range:(NSRange)range { - [self as_setAttribute:NSStrokeWidthAttributeName value:strokeWidth range:range]; -} - -- (void)as_setStrokeColor:(UIColor *)strokeColor range:(NSRange)range { - [self as_setAttribute:(id)kCTStrokeColorAttributeName value:(id)strokeColor.CGColor range:range]; - [self as_setAttribute:NSStrokeColorAttributeName value:strokeColor range:range]; -} - -- (void)as_setShadow:(NSShadow *)shadow range:(NSRange)range { - [self as_setAttribute:NSShadowAttributeName value:shadow range:range]; -} - -- (void)as_setStrikethroughStyle:(NSUnderlineStyle)strikethroughStyle range:(NSRange)range { - NSNumber *style = strikethroughStyle == 0 ? nil : @(strikethroughStyle); - [self as_setAttribute:NSStrikethroughStyleAttributeName value:style range:range]; -} - -- (void)as_setStrikethroughColor:(UIColor *)strikethroughColor range:(NSRange)range { - [self as_setAttribute:NSStrikethroughColorAttributeName value:strikethroughColor range:range]; -} - -- (void)as_setUnderlineStyle:(NSUnderlineStyle)underlineStyle range:(NSRange)range { - NSNumber *style = underlineStyle == 0 ? nil : @(underlineStyle); - [self as_setAttribute:NSUnderlineStyleAttributeName value:style range:range]; -} - -- (void)as_setUnderlineColor:(UIColor *)underlineColor range:(NSRange)range { - [self as_setAttribute:(id)kCTUnderlineColorAttributeName value:(id)underlineColor.CGColor range:range]; - [self as_setAttribute:NSUnderlineColorAttributeName value:underlineColor range:range]; -} - -- (void)as_setLigature:(NSNumber *)ligature range:(NSRange)range { - [self as_setAttribute:NSLigatureAttributeName value:ligature range:range]; -} - -- (void)as_setTextEffect:(NSString *)textEffect range:(NSRange)range { - [self as_setAttribute:NSTextEffectAttributeName value:textEffect range:range]; -} - -- (void)as_setObliqueness:(NSNumber *)obliqueness range:(NSRange)range { - [self as_setAttribute:NSObliquenessAttributeName value:obliqueness range:range]; -} - -- (void)as_setExpansion:(NSNumber *)expansion range:(NSRange)range { - [self as_setAttribute:NSExpansionAttributeName value:expansion range:range]; -} - -- (void)as_setBaselineOffset:(NSNumber *)baselineOffset range:(NSRange)range { - [self as_setAttribute:NSBaselineOffsetAttributeName value:baselineOffset range:range]; -} - -- (void)as_setVerticalGlyphForm:(BOOL)verticalGlyphForm range:(NSRange)range { - NSNumber *v = verticalGlyphForm ? @(YES) : nil; - [self as_setAttribute:NSVerticalGlyphFormAttributeName value:v range:range]; -} - -- (void)as_setLanguage:(NSString *)language range:(NSRange)range { - [self as_setAttribute:(id)kCTLanguageAttributeName value:language range:range]; -} - -- (void)as_setWritingDirection:(NSArray *)writingDirection range:(NSRange)range { - [self as_setAttribute:(id)kCTWritingDirectionAttributeName value:writingDirection range:range]; -} - -- (void)as_setParagraphStyle:(NSParagraphStyle *)paragraphStyle range:(NSRange)range { - /* - NSParagraphStyle is NOT toll-free bridged to CTParagraphStyleRef. - - CoreText can use both NSParagraphStyle and CTParagraphStyleRef, - but UILabel/UITextView can only use NSParagraphStyle. - - We use NSParagraphStyle in both CoreText and UIKit. - */ - [self as_setAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; -} - -#define ParagraphStyleSet(_attr_) \ -[self enumerateAttribute:NSParagraphStyleAttributeName \ -inRange:range \ -options:kNilOptions \ -usingBlock: ^(NSParagraphStyle *value, NSRange subRange, BOOL *stop) { \ -NSMutableParagraphStyle *style = nil; \ -if (value) { \ -if (CFGetTypeID((__bridge CFTypeRef)(value)) == CTParagraphStyleGetTypeID()) { \ -value = [NSParagraphStyle as_styleWithCTStyle:(__bridge CTParagraphStyleRef)(value)]; \ -} \ -if (value. _attr_ == _attr_) return; \ -if ([value isKindOfClass:[NSMutableParagraphStyle class]]) { \ -style = (id)value; \ -} else { \ -style = value.mutableCopy; \ -} \ -} else { \ -if ([NSParagraphStyle defaultParagraphStyle]. _attr_ == _attr_) return; \ -style = [NSParagraphStyle defaultParagraphStyle].mutableCopy; \ -} \ -style. _attr_ = _attr_; \ -[self as_setParagraphStyle:style range:subRange]; \ -}]; - -- (void)as_setAlignment:(NSTextAlignment)alignment range:(NSRange)range { - ParagraphStyleSet(alignment); -} - -- (void)as_setBaseWritingDirection:(NSWritingDirection)baseWritingDirection range:(NSRange)range { - ParagraphStyleSet(baseWritingDirection); -} - -- (void)as_setLineSpacing:(CGFloat)lineSpacing range:(NSRange)range { - ParagraphStyleSet(lineSpacing); -} - -- (void)as_setParagraphSpacing:(CGFloat)paragraphSpacing range:(NSRange)range { - ParagraphStyleSet(paragraphSpacing); -} - -- (void)as_setParagraphSpacingBefore:(CGFloat)paragraphSpacingBefore range:(NSRange)range { - ParagraphStyleSet(paragraphSpacingBefore); -} - -- (void)as_setFirstLineHeadIndent:(CGFloat)firstLineHeadIndent range:(NSRange)range { - ParagraphStyleSet(firstLineHeadIndent); -} - -- (void)as_setHeadIndent:(CGFloat)headIndent range:(NSRange)range { - ParagraphStyleSet(headIndent); -} - -- (void)as_setTailIndent:(CGFloat)tailIndent range:(NSRange)range { - ParagraphStyleSet(tailIndent); -} - -- (void)as_setLineBreakMode:(NSLineBreakMode)lineBreakMode range:(NSRange)range { - ParagraphStyleSet(lineBreakMode); -} - -- (void)as_setMinimumLineHeight:(CGFloat)minimumLineHeight range:(NSRange)range { - ParagraphStyleSet(minimumLineHeight); -} - -- (void)as_setMaximumLineHeight:(CGFloat)maximumLineHeight range:(NSRange)range { - ParagraphStyleSet(maximumLineHeight); -} - -- (void)as_setLineHeightMultiple:(CGFloat)lineHeightMultiple range:(NSRange)range { - ParagraphStyleSet(lineHeightMultiple); -} - -- (void)as_setHyphenationFactor:(float)hyphenationFactor range:(NSRange)range { - ParagraphStyleSet(hyphenationFactor); -} - -- (void)as_setDefaultTabInterval:(CGFloat)defaultTabInterval range:(NSRange)range { - ParagraphStyleSet(defaultTabInterval); -} - -- (void)as_setTabStops:(NSArray *)tabStops range:(NSRange)range { - ParagraphStyleSet(tabStops); -} - -#undef ParagraphStyleSet - -- (void)as_setSuperscript:(NSNumber *)superscript range:(NSRange)range { - if ([superscript isEqualToNumber:@(0)]) { - superscript = nil; - } - [self as_setAttribute:(id)kCTSuperscriptAttributeName value:superscript range:range]; -} - -- (void)as_setGlyphInfo:(CTGlyphInfoRef)glyphInfo range:(NSRange)range { - [self as_setAttribute:(id)kCTGlyphInfoAttributeName value:(__bridge id)glyphInfo range:range]; -} - -- (void)as_setCharacterShape:(NSNumber *)characterShape range:(NSRange)range { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self as_setAttribute:(id)kCTCharacterShapeAttributeName value:characterShape range:range]; -#pragma clang diagnostic pop -} - -- (void)as_setRunDelegate:(CTRunDelegateRef)runDelegate range:(NSRange)range { - [self as_setAttribute:(id)kCTRunDelegateAttributeName value:(__bridge id)runDelegate range:range]; -} - -- (void)as_setBaselineClass:(CFStringRef)baselineClass range:(NSRange)range { - [self as_setAttribute:(id)kCTBaselineClassAttributeName value:(__bridge id)baselineClass range:range]; -} - -- (void)as_setBaselineInfo:(CFDictionaryRef)baselineInfo range:(NSRange)range { - [self as_setAttribute:(id)kCTBaselineInfoAttributeName value:(__bridge id)baselineInfo range:range]; -} - -- (void)as_setBaselineReferenceInfo:(CFDictionaryRef)referenceInfo range:(NSRange)range { - [self as_setAttribute:(id)kCTBaselineReferenceInfoAttributeName value:(__bridge id)referenceInfo range:range]; -} - -- (void)as_setRubyAnnotation:(CTRubyAnnotationRef)ruby range:(NSRange)range { - [self as_setAttribute:(id)kCTRubyAnnotationAttributeName value:(__bridge id)ruby range:range]; -} - -- (void)as_setAttachment:(NSTextAttachment *)attachment range:(NSRange)range { - [self as_setAttribute:NSAttachmentAttributeName value:attachment range:range]; -} - -- (void)as_setLink:(id)link range:(NSRange)range { - [self as_setAttribute:NSLinkAttributeName value:link range:range]; -} - -- (void)as_setTextBackedString:(ASTextBackedString *)textBackedString range:(NSRange)range { - [self as_setAttribute:ASTextBackedStringAttributeName value:textBackedString range:range]; -} - -- (void)as_setTextBinding:(ASTextBinding *)textBinding range:(NSRange)range { - [self as_setAttribute:ASTextBindingAttributeName value:textBinding range:range]; -} - -- (void)as_setTextShadow:(ASTextShadow *)textShadow range:(NSRange)range { - [self as_setAttribute:ASTextShadowAttributeName value:textShadow range:range]; -} - -- (void)as_setTextInnerShadow:(ASTextShadow *)textInnerShadow range:(NSRange)range { - [self as_setAttribute:ASTextInnerShadowAttributeName value:textInnerShadow range:range]; -} - -- (void)as_setTextUnderline:(ASTextDecoration *)textUnderline range:(NSRange)range { - [self as_setAttribute:ASTextUnderlineAttributeName value:textUnderline range:range]; -} - -- (void)as_setTextStrikethrough:(ASTextDecoration *)textStrikethrough range:(NSRange)range { - [self as_setAttribute:ASTextStrikethroughAttributeName value:textStrikethrough range:range]; -} - -- (void)as_setTextBorder:(ASTextBorder *)textBorder range:(NSRange)range { - [self as_setAttribute:ASTextBorderAttributeName value:textBorder range:range]; -} - -- (void)as_setTextBackgroundBorder:(ASTextBorder *)textBackgroundBorder range:(NSRange)range { - [self as_setAttribute:ASTextBackgroundBorderAttributeName value:textBackgroundBorder range:range]; -} - -- (void)as_setTextAttachment:(ASTextAttachment *)textAttachment range:(NSRange)range { - [self as_setAttribute:ASTextAttachmentAttributeName value:textAttachment range:range]; -} - -- (void)as_setTextHighlight:(ASTextHighlight *)textHighlight range:(NSRange)range { - [self as_setAttribute:ASTextHighlightAttributeName value:textHighlight range:range]; -} - -- (void)as_setTextBlockBorder:(ASTextBorder *)textBlockBorder range:(NSRange)range { - [self as_setAttribute:ASTextBlockBorderAttributeName value:textBlockBorder range:range]; -} - -- (void)as_setTextGlyphTransform:(CGAffineTransform)textGlyphTransform range:(NSRange)range { - NSValue *value = CGAffineTransformIsIdentity(textGlyphTransform) ? nil : [NSValue valueWithCGAffineTransform:textGlyphTransform]; - [self as_setAttribute:ASTextGlyphTransformAttributeName value:value range:range]; -} - -- (void)as_setTextHighlightRange:(NSRange)range - color:(UIColor *)color - backgroundColor:(UIColor *)backgroundColor - userInfo:(NSDictionary *)userInfo - tapAction:(ASTextAction)tapAction - longPressAction:(ASTextAction)longPressAction { - ASTextHighlight *highlight = [ASTextHighlight highlightWithBackgroundColor:backgroundColor]; - highlight.userInfo = userInfo; - highlight.tapAction = tapAction; - highlight.longPressAction = longPressAction; - if (color) [self as_setColor:color range:range]; - [self as_setTextHighlight:highlight range:range]; -} - -- (void)as_setTextHighlightRange:(NSRange)range - color:(UIColor *)color - backgroundColor:(UIColor *)backgroundColor - tapAction:(ASTextAction)tapAction { - [self as_setTextHighlightRange:range - color:color - backgroundColor:backgroundColor - userInfo:nil - tapAction:tapAction - longPressAction:nil]; -} - -- (void)as_setTextHighlightRange:(NSRange)range - color:(UIColor *)color - backgroundColor:(UIColor *)backgroundColor - userInfo:(NSDictionary *)userInfo { - [self as_setTextHighlightRange:range - color:color - backgroundColor:backgroundColor - userInfo:userInfo - tapAction:nil - longPressAction:nil]; -} - -- (void)as_insertString:(NSString *)string atIndex:(NSUInteger)location { - [self replaceCharactersInRange:NSMakeRange(location, 0) withString:string]; - [self as_removeDiscontinuousAttributesInRange:NSMakeRange(location, string.length)]; -} - -- (void)as_appendString:(NSString *)string { - NSUInteger length = self.length; - [self replaceCharactersInRange:NSMakeRange(length, 0) withString:string]; - [self as_removeDiscontinuousAttributesInRange:NSMakeRange(length, string.length)]; -} - -- (void)as_removeDiscontinuousAttributesInRange:(NSRange)range { - NSArray *keys = [NSMutableAttributedString as_allDiscontinuousAttributeKeys]; - for (NSString *key in keys) { - [self removeAttribute:key range:range]; - } -} - -+ (NSArray *)as_allDiscontinuousAttributeKeys { - static NSArray *keys; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - keys = @[(id)kCTSuperscriptAttributeName, - (id)kCTRunDelegateAttributeName, - ASTextBackedStringAttributeName, - ASTextBindingAttributeName, - ASTextAttachmentAttributeName, - (id)kCTRubyAnnotationAttributeName, - NSAttachmentAttributeName]; - }); - return keys; -} - -@end diff --git a/Source/TextExperiment/Utility/NSParagraphStyle+ASText.h b/Source/TextExperiment/Utility/NSParagraphStyle+ASText.h deleted file mode 100644 index a7c0aec5ff..0000000000 --- a/Source/TextExperiment/Utility/NSParagraphStyle+ASText.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// NSParagraphStyle+ASText.h -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - Provides extensions for `NSParagraphStyle` to work with CoreText. - */ -@interface NSParagraphStyle (ASText) - -/** - Creates a new NSParagraphStyle object from the CoreText Style. - - @param CTStyle CoreText Paragraph Style. - - @return a new NSParagraphStyle - */ -+ (nullable NSParagraphStyle *)as_styleWithCTStyle:(CTParagraphStyleRef)CTStyle; - -/** - Creates and returns a CoreText Paragraph Style. (need call CFRelease() after used) - */ -- (nullable CTParagraphStyleRef)as_CTStyle CF_RETURNS_RETAINED; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/TextExperiment/Utility/NSParagraphStyle+ASText.mm b/Source/TextExperiment/Utility/NSParagraphStyle+ASText.mm deleted file mode 100644 index bc19fd234c..0000000000 --- a/Source/TextExperiment/Utility/NSParagraphStyle+ASText.mm +++ /dev/null @@ -1,219 +0,0 @@ -// -// NSParagraphStyle+ASText.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -// Dummy class for category -@interface NSParagraphStyle_ASText : NSObject @end -@implementation NSParagraphStyle_ASText @end - - -@implementation NSParagraphStyle (ASText) - -+ (NSParagraphStyle *)as_styleWithCTStyle:(CTParagraphStyleRef)CTStyle { - if (CTStyle == NULL) return nil; - - NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - -#if TARGET_OS_IOS -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGFloat lineSpacing; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(CGFloat), &lineSpacing)) { - style.lineSpacing = lineSpacing; - } -#pragma clang diagnostic pop -#endif - - CGFloat paragraphSpacing; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), ¶graphSpacing)) { - style.paragraphSpacing = paragraphSpacing; - } - - CTTextAlignment alignment; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment)) { - style.alignment = NSTextAlignmentFromCTTextAlignment(alignment); - } - - CGFloat firstLineHeadIndent; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineHeadIndent)) { - style.firstLineHeadIndent = firstLineHeadIndent; - } - - CGFloat headIndent; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &headIndent)) { - style.headIndent = headIndent; - } - - CGFloat tailIndent; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierTailIndent, sizeof(CGFloat), &tailIndent)) { - style.tailIndent = tailIndent; - } - - CTLineBreakMode lineBreakMode; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode)) { - style.lineBreakMode = (NSLineBreakMode)lineBreakMode; - } - - CGFloat minimumLineHeight; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minimumLineHeight)) { - style.minimumLineHeight = minimumLineHeight; - } - - CGFloat maximumLineHeight; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maximumLineHeight)) { - style.maximumLineHeight = maximumLineHeight; - } - - CTWritingDirection baseWritingDirection; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(CTWritingDirection), &baseWritingDirection)) { - style.baseWritingDirection = (NSWritingDirection)baseWritingDirection; - } - - CGFloat lineHeightMultiple; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(CGFloat), &lineHeightMultiple)) { - style.lineHeightMultiple = lineHeightMultiple; - } - - CGFloat paragraphSpacingBefore; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(CGFloat), ¶graphSpacingBefore)) { - style.paragraphSpacingBefore = paragraphSpacingBefore; - } - - CFArrayRef tabStops; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierTabStops, sizeof(CFArrayRef), &tabStops)) { - NSMutableArray *tabs = [NSMutableArray new]; - [((__bridge NSArray *)(tabStops))enumerateObjectsUsingBlock : ^(id obj, NSUInteger idx, BOOL *stop) { - CTTextTabRef ctTab = (__bridge CTTextTabRef)obj; - - NSTextTab *tab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentFromCTTextAlignment(CTTextTabGetAlignment(ctTab)) location:CTTextTabGetLocation(ctTab) options:(__bridge id)CTTextTabGetOptions(ctTab)]; - [tabs addObject:tab]; - }]; - if (tabs.count) { - style.tabStops = tabs; - } - } - - CGFloat defaultTabInterval; - if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierDefaultTabInterval, sizeof(CGFloat), &defaultTabInterval)) { - style.defaultTabInterval = defaultTabInterval; - } - - return style; -} - -- (CTParagraphStyleRef)as_CTStyle CF_RETURNS_RETAINED { - CTParagraphStyleSetting set[kCTParagraphStyleSpecifierCount] = { }; - int count = 0; - -#if TARGET_OS_IOS -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGFloat lineSpacing = self.lineSpacing; - set[count].spec = kCTParagraphStyleSpecifierLineSpacing; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &lineSpacing; - count++; -#pragma clang diagnostic pop -#endif - - CGFloat paragraphSpacing = self.paragraphSpacing; - set[count].spec = kCTParagraphStyleSpecifierParagraphSpacing; - set[count].valueSize = sizeof(CGFloat); - set[count].value = ¶graphSpacing; - count++; - - CTTextAlignment alignment = NSTextAlignmentToCTTextAlignment(self.alignment); - set[count].spec = kCTParagraphStyleSpecifierAlignment; - set[count].valueSize = sizeof(CTTextAlignment); - set[count].value = &alignment; - count++; - - CGFloat firstLineHeadIndent = self.firstLineHeadIndent; - set[count].spec = kCTParagraphStyleSpecifierFirstLineHeadIndent; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &firstLineHeadIndent; - count++; - - CGFloat headIndent = self.headIndent; - set[count].spec = kCTParagraphStyleSpecifierHeadIndent; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &headIndent; - count++; - - CGFloat tailIndent = self.tailIndent; - set[count].spec = kCTParagraphStyleSpecifierTailIndent; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &tailIndent; - count++; - - CTLineBreakMode paraLineBreak = (CTLineBreakMode)self.lineBreakMode; - set[count].spec = kCTParagraphStyleSpecifierLineBreakMode; - set[count].valueSize = sizeof(CTLineBreakMode); - set[count].value = ¶LineBreak; - count++; - - CGFloat minimumLineHeight = self.minimumLineHeight; - set[count].spec = kCTParagraphStyleSpecifierMinimumLineHeight; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &minimumLineHeight; - count++; - - CGFloat maximumLineHeight = self.maximumLineHeight; - set[count].spec = kCTParagraphStyleSpecifierMaximumLineHeight; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &maximumLineHeight; - count++; - - CTWritingDirection paraWritingDirection = (CTWritingDirection)self.baseWritingDirection; - set[count].spec = kCTParagraphStyleSpecifierBaseWritingDirection; - set[count].valueSize = sizeof(CTWritingDirection); - set[count].value = ¶WritingDirection; - count++; - - CGFloat lineHeightMultiple = self.lineHeightMultiple; - set[count].spec = kCTParagraphStyleSpecifierLineHeightMultiple; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &lineHeightMultiple; - count++; - - CGFloat paragraphSpacingBefore = self.paragraphSpacingBefore; - set[count].spec = kCTParagraphStyleSpecifierParagraphSpacingBefore; - set[count].valueSize = sizeof(CGFloat); - set[count].value = ¶graphSpacingBefore; - count++; - - NSMutableArray *tabs = [NSMutableArray array]; - NSInteger numTabs = self.tabStops.count; - if (numTabs) { - [self.tabStops enumerateObjectsUsingBlock: ^(NSTextTab *tab, NSUInteger idx, BOOL *stop) { - CTTextTabRef ctTab = CTTextTabCreate(NSTextAlignmentToCTTextAlignment(tab.alignment), tab.location, (__bridge CFDictionaryRef)tab.options); - [tabs addObject:(__bridge id)ctTab]; - CFRelease(ctTab); - }]; - - CFArrayRef tabStops = (__bridge CFArrayRef)(tabs); - set[count].spec = kCTParagraphStyleSpecifierTabStops; - set[count].valueSize = sizeof(CFArrayRef); - set[count].value = &tabStops; - count++; - } - - CGFloat defaultTabInterval = self.defaultTabInterval; - set[count].spec = kCTParagraphStyleSpecifierDefaultTabInterval; - set[count].valueSize = sizeof(CGFloat); - set[count].value = &defaultTabInterval; - count++; - - CTParagraphStyleRef style = CTParagraphStyleCreate(set, count); - return style; -} - -@end diff --git a/Source/TextKit/ASTextKitAttributes.h b/Source/TextKit/ASTextKitAttributes.h index cf09ad668b..bf7e5e6a8c 100644 --- a/Source/TextKit/ASTextKitAttributes.h +++ b/Source/TextKit/ASTextKitAttributes.h @@ -13,8 +13,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import ASDK_EXTERN NSString *const ASTextKitTruncationAttributeName; @@ -130,5 +128,3 @@ struct ASTextKitAttributes { size_t hash() const; }; - -#endif diff --git a/Source/TextKit/ASTextKitAttributes.mm b/Source/TextKit/ASTextKitAttributes.mm index 400ef437bb..131771ab63 100644 --- a/Source/TextKit/ASTextKitAttributes.mm +++ b/Source/TextKit/ASTextKitAttributes.mm @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import NSString *const ASTextKitTruncationAttributeName = @"ck_truncation"; @@ -46,5 +44,3 @@ }; return ASHashBytes(&data, sizeof(data)); } - -#endif diff --git a/Source/TextKit/ASTextKitContext.h b/Source/TextKit/ASTextKitContext.h index df9d0a0c69..2c70ff5c53 100644 --- a/Source/TextKit/ASTextKitContext.h +++ b/Source/TextKit/ASTextKitContext.h @@ -11,8 +11,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import /** @@ -50,5 +48,3 @@ AS_SUBCLASSING_RESTRICTED NSTextContainer *textContainer))block; @end - -#endif diff --git a/Source/TextKit/ASTextKitContext.mm b/Source/TextKit/ASTextKitContext.mm index d0b6708fd7..e163aa53ab 100644 --- a/Source/TextKit/ASTextKitContext.mm +++ b/Source/TextKit/ASTextKitContext.mm @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import #import @@ -36,15 +34,12 @@ - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString static AS::Mutex *mutex = NULL; static dispatch_once_t onceToken; - 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(); - } + // 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(); @@ -80,7 +75,7 @@ - (instancetype)initWithAttributedString:(NSAttributedString *)attributedString _textContainer.exclusionPaths = exclusionPaths; [_layoutManager addTextContainer:_textContainer]; - if (useGlobalTextKitLock && mutex != NULL) { + if (mutex != NULL) { mutex->unlock(); } } @@ -98,5 +93,3 @@ - (void)performBlockWithLockedTextKitComponents:(NS_NOESCAPE void (^)(NSLayoutMa } @end - -#endif diff --git a/Source/TextKit/ASTextKitCoreTextAdditions.h b/Source/TextKit/ASTextKitCoreTextAdditions.h index 0da484d083..1a3dc90445 100644 --- a/Source/TextKit/ASTextKitCoreTextAdditions.h +++ b/Source/TextKit/ASTextKitCoreTextAdditions.h @@ -11,8 +11,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import NS_ASSUME_NONNULL_BEGIN @@ -85,5 +83,3 @@ ASDK_EXTERN NSAttributedString *ASCleanseAttributedStringOfCoreTextAttributes(NS @end NS_ASSUME_NONNULL_END - -#endif diff --git a/Source/TextKit/ASTextKitCoreTextAdditions.mm b/Source/TextKit/ASTextKitCoreTextAdditions.mm index a6c72d5d97..a3d0af15eb 100644 --- a/Source/TextKit/ASTextKitCoreTextAdditions.mm +++ b/Source/TextKit/ASTextKitCoreTextAdditions.mm @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import #import @@ -326,5 +324,3 @@ + (NSParagraphStyle *)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)co } @end - -#endif diff --git a/Source/TextKit/ASTextKitEntityAttribute.h b/Source/TextKit/ASTextKitEntityAttribute.h index 3655138be7..ab69bb6184 100644 --- a/Source/TextKit/ASTextKitEntityAttribute.h +++ b/Source/TextKit/ASTextKitEntityAttribute.h @@ -11,8 +11,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import /** @@ -32,5 +30,3 @@ AS_SUBCLASSING_RESTRICTED - (instancetype)initWithEntity:(id)entity; @end - -#endif diff --git a/Source/TextKit/ASTextKitEntityAttribute.mm b/Source/TextKit/ASTextKitEntityAttribute.mm index fb87e9bd3a..4f460dfb7b 100644 --- a/Source/TextKit/ASTextKitEntityAttribute.mm +++ b/Source/TextKit/ASTextKitEntityAttribute.mm @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - @implementation ASTextKitEntityAttribute - (instancetype)initWithEntity:(id)entity @@ -39,5 +37,3 @@ - (BOOL)isEqual:(id)object } @end - -#endif diff --git a/Source/TextKit/ASTextKitFontSizeAdjuster.h b/Source/TextKit/ASTextKitFontSizeAdjuster.h index 1b7b10ff5f..a58bcc26c8 100644 --- a/Source/TextKit/ASTextKitFontSizeAdjuster.h +++ b/Source/TextKit/ASTextKitFontSizeAdjuster.h @@ -11,8 +11,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import NS_ASSUME_NONNULL_BEGIN @@ -54,5 +52,3 @@ AS_SUBCLASSING_RESTRICTED @end NS_ASSUME_NONNULL_END - -#endif diff --git a/Source/TextKit/ASTextKitFontSizeAdjuster.mm b/Source/TextKit/ASTextKitFontSizeAdjuster.mm index 30591a87de..4bf054c6ff 100644 --- a/Source/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/Source/TextKit/ASTextKitFontSizeAdjuster.mm @@ -10,8 +10,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import #import @@ -236,5 +234,3 @@ - (CGFloat)scaleFactor } @end - -#endif diff --git a/Source/TextKit/ASTextKitRenderer+Positioning.h b/Source/TextKit/ASTextKitRenderer+Positioning.h index c887282568..ebcf229671 100644 --- a/Source/TextKit/ASTextKitRenderer+Positioning.h +++ b/Source/TextKit/ASTextKitRenderer+Positioning.h @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - typedef void (^as_text_component_index_block_t)(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop); @@ -102,5 +100,3 @@ typedef NS_ENUM(NSUInteger, ASTextKitRendererMeasureOption) { - (CGRect)trailingRect; @end - -#endif diff --git a/Source/TextKit/ASTextKitRenderer+Positioning.mm b/Source/TextKit/ASTextKitRenderer+Positioning.mm index 9dc770e1d9..a97e189b41 100644 --- a/Source/TextKit/ASTextKitRenderer+Positioning.mm +++ b/Source/TextKit/ASTextKitRenderer+Positioning.mm @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import #import @@ -382,5 +380,3 @@ - (CGRect)frameForTextRange:(NSRange)textRange } @end - -#endif diff --git a/Source/TextKit/ASTextKitRenderer+TextChecking.h b/Source/TextKit/ASTextKitRenderer+TextChecking.h index d4ba74fd32..b1d1cab8c3 100644 --- a/Source/TextKit/ASTextKitRenderer+TextChecking.h +++ b/Source/TextKit/ASTextKitRenderer+TextChecking.h @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - /** Application extensions to NSTextCheckingType. We're allowed to do this (see NSTextCheckingAllCustomTypes). */ @@ -28,5 +26,3 @@ static uint64_t const ASTextKitTextCheckingTypeTruncation = 1ULL << 34 - (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point; @end - -#endif diff --git a/Source/TextKit/ASTextKitRenderer+TextChecking.mm b/Source/TextKit/ASTextKitRenderer+TextChecking.mm index e556393572..527f82af28 100644 --- a/Source/TextKit/ASTextKitRenderer+TextChecking.mm +++ b/Source/TextKit/ASTextKitRenderer+TextChecking.mm @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import #import #import @@ -100,5 +98,3 @@ - (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point } @end - -#endif diff --git a/Source/TextKit/ASTextKitRenderer.h b/Source/TextKit/ASTextKitRenderer.h index 0f0cc00e0a..3358b9d359 100644 --- a/Source/TextKit/ASTextKitRenderer.h +++ b/Source/TextKit/ASTextKitRenderer.h @@ -11,8 +11,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import #import @@ -104,5 +102,3 @@ @property (nonatomic, readonly) NSRange firstVisibleRange; @end - -#endif diff --git a/Source/TextKit/ASTextKitRenderer.mm b/Source/TextKit/ASTextKitRenderer.mm index 1a0c9c197f..a6505effeb 100644 --- a/Source/TextKit/ASTextKitRenderer.mm +++ b/Source/TextKit/ASTextKitRenderer.mm @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import #import @@ -290,5 +288,3 @@ - (NSRange)firstVisibleRange } @end - -#endif diff --git a/Source/TextKit/ASTextKitShadower.h b/Source/TextKit/ASTextKitShadower.h index 2f9c604fbc..60f853afa1 100644 --- a/Source/TextKit/ASTextKitShadower.h +++ b/Source/TextKit/ASTextKitShadower.h @@ -11,8 +11,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import /** @@ -74,5 +72,3 @@ AS_SUBCLASSING_RESTRICTED - (void)setShadowInContext:(CGContextRef)context; @end - -#endif diff --git a/Source/TextKit/ASTextKitShadower.mm b/Source/TextKit/ASTextKitShadower.mm index a2f37f7e06..714d93a0e7 100644 --- a/Source/TextKit/ASTextKitShadower.mm +++ b/Source/TextKit/ASTextKitShadower.mm @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import static inline CGSize _insetSize(CGSize size, UIEdgeInsets insets) @@ -173,5 +171,3 @@ - (CGPoint)offsetPointWithExternalPoint:(CGPoint)externalPoint } @end - -#endif diff --git a/Source/TextKit/ASTextKitTailTruncater.h b/Source/TextKit/ASTextKitTailTruncater.h index 70734d9e2c..dc8979582e 100644 --- a/Source/TextKit/ASTextKitTailTruncater.h +++ b/Source/TextKit/ASTextKitTailTruncater.h @@ -11,11 +11,7 @@ #import -#if AS_ENABLE_TEXTNODE - AS_SUBCLASSING_RESTRICTED @interface ASTextKitTailTruncater : NSObject @end - -#endif diff --git a/Source/TextKit/ASTextKitTailTruncater.mm b/Source/TextKit/ASTextKitTailTruncater.mm index 276b008e08..55770f8296 100644 --- a/Source/TextKit/ASTextKitTailTruncater.mm +++ b/Source/TextKit/ASTextKitTailTruncater.mm @@ -9,8 +9,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import @implementation ASTextKitTailTruncater @@ -192,5 +190,3 @@ - (NSRange)firstVisibleRange } @end - -#endif diff --git a/Source/TextKit/ASTextKitTruncating.h b/Source/TextKit/ASTextKitTruncating.h index 35392fd6b3..e54fb189ab 100644 --- a/Source/TextKit/ASTextKitTruncating.h +++ b/Source/TextKit/ASTextKitTruncating.h @@ -11,8 +11,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import #import @@ -57,5 +55,3 @@ NS_ASSUME_NONNULL_BEGIN @end NS_ASSUME_NONNULL_END - -#endif diff --git a/Source/UIImage+ASConvenience.h b/Source/UIImage+ASConvenience.h index e662a6a5a6..0176523c3f 100644 --- a/Source/UIImage+ASConvenience.h +++ b/Source/UIImage+ASConvenience.h @@ -60,17 +60,6 @@ NS_ASSUME_NONNULL_BEGIN @interface UIImage (ASDKResizableRoundedRects) -/** - * This generates a flat-color, rounded-corner resizeable image - * - * @param cornerRadius The radius of the rounded-corner - * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) - * @param fillColor The fill color of the rounded-corner image - */ -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(nullable UIColor *)cornerColor - fillColor:(UIColor *)fillColor NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use as_resizableRoundedImageWithCornerRadius:cornerColor:fillColor:traitCollection: instead"); - /** * This generates a flat-color, rounded-corner resizeable image * @@ -84,21 +73,6 @@ NS_ASSUME_NONNULL_BEGIN fillColor:(UIColor *)fillColor traitCollection:(ASPrimitiveTraitCollection) traitCollection NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; -/** - * This generates a flat-color, rounded-corner resizeable image with a border - * - * @param cornerRadius The radius of the rounded-corner - * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) - * @param fillColor The fill color of the rounded-corner image - * @param borderColor The border color. Set to nil for no border. - * @param borderWidth The border width. Dummy value if borderColor = nil. - */ -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(UIColor *)cornerColor - fillColor:(UIColor *)fillColor - borderColor:(nullable UIColor *)borderColor - borderWidth:(CGFloat)borderWidth NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use as_resizableRoundedImageWithCornerRadius:cornerColor:fillColor:borderColor:borderWidth:traitCollection: instead"); - /** * This generates a flat-color, rounded-corner resizeable image with a border * @@ -115,25 +89,6 @@ NS_ASSUME_NONNULL_BEGIN borderColor:(nullable UIColor *)borderColor borderWidth:(CGFloat)borderWidth traitCollection:(ASPrimitiveTraitCollection) traitCollection NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; -/** - * This generates a flat-color, rounded-corner resizeable image with a border - * - * @param cornerRadius The radius of the rounded-corner - * @param cornerColor The fill color of the corners (For Alpha corners use clearColor) - * @param fillColor The fill color of the rounded-corner image - * @param borderColor The border color. Set to nil for no border. - * @param borderWidth The border width. Dummy value if borderColor = nil. - * @param roundedCorners Select individual or multiple corners to round. Set to UIRectCornerAllCorners to round all 4 corners. - * @param scale The number of pixels per point. Provide 0.0 to use the screen scale. - */ -+ (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius - cornerColor:(nullable UIColor *)cornerColor - fillColor:(UIColor *)fillColor - borderColor:(nullable UIColor *)borderColor - borderWidth:(CGFloat)borderWidth - roundedCorners:(UIRectCorner)roundedCorners - scale:(CGFloat)scale NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Use as_resizableRoundedImageWithCornerRadius:cornerColor:fillColor:borderColor:borderWidth:roundedCorners:traitCollection: instead"); -; /** * This generates a flat-color, rounded-corner resizeable image with a border diff --git a/Source/UIResponder+AsyncDisplayKit.h b/Source/UIResponder+AsyncDisplayKit.h deleted file mode 100644 index 17afab3ae2..0000000000 --- a/Source/UIResponder+AsyncDisplayKit.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// UIResponder+AsyncDisplayKit.h -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface UIResponder (AsyncDisplayKit) - -/** - * The nearest view controller above this responder, if one exists. - * - * This property must be accessed on the main thread. - */ -@property (nonatomic, nullable, readonly) __kindof UIViewController *asdk_associatedViewController; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/UIResponder+AsyncDisplayKit.mm b/Source/UIResponder+AsyncDisplayKit.mm deleted file mode 100644 index 957e64b0ff..0000000000 --- a/Source/UIResponder+AsyncDisplayKit.mm +++ /dev/null @@ -1,31 +0,0 @@ -// -// UIResponder+AsyncDisplayKit.m -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "UIResponder+AsyncDisplayKit.h" - -#import -#import - -@implementation UIResponder (AsyncDisplayKit) - -- (__kindof UIViewController *)asdk_associatedViewController -{ - ASDisplayNodeAssertMainThread(); - - for (UIResponder *responder in [self asdk_responderChainEnumerator]) { - UIViewController *vc = ASDynamicCast(responder, UIViewController); - if (vc) { - return vc; - } - } - return nil; -} - -@end - diff --git a/Source/tvOS/ASControlNode+tvOS.mm b/Source/tvOS/ASControlNode+tvOS.mm deleted file mode 100644 index a823d9625c..0000000000 --- a/Source/tvOS/ASControlNode+tvOS.mm +++ /dev/null @@ -1,92 +0,0 @@ -// -// ASControlNode+tvOS.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#if TARGET_OS_TV -#import -#import - -@implementation ASControlNode (tvOS) - -#pragma mark - tvOS -- (void)_pressDown -{ - [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ - [self setPressedState]; - } completion:^(BOOL finished) { - if (finished) { - [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ - [self setFocusedState]; - } completion:nil]; - } - }]; -} - -- (BOOL)canBecomeFocused -{ - return YES; -} - -- (BOOL)shouldUpdateFocusInContext:(nonnull UIFocusUpdateContext *)context -{ - return YES; -} - -- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator -{ - //FIXME: This is never valid inside an ASCellNode - if (context.nextFocusedView && context.nextFocusedView == self.view) { - //Focused - [coordinator addCoordinatedAnimations:^{ - [self setFocusedState]; - } completion:nil]; - } else{ - //Not focused - [coordinator addCoordinatedAnimations:^{ - [self setDefaultFocusAppearance]; - } completion:nil]; - } -} - -- (void)setFocusedState -{ - CALayer *layer = self.layer; - layer.shadowOffset = CGSizeMake(2, 10); - [self applyDefaultShadowProperties: layer]; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1); -} - -- (void)setPressedState -{ - CALayer *layer = self.layer; - layer.shadowOffset = CGSizeMake(2, 2); - [self applyDefaultShadowProperties: layer]; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); -} - -- (void)applyDefaultShadowProperties:(CALayer *)layer -{ - layer.shadowColor = [UIColor blackColor].CGColor; - layer.shadowRadius = 12.0; - layer.shadowOpacity = 0.45; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; -} - -- (void)setDefaultFocusAppearance -{ - CALayer *layer = self.layer; - layer.shadowOffset = CGSizeZero; - layer.shadowColor = [UIColor blackColor].CGColor; - layer.shadowRadius = 0; - layer.shadowOpacity = 0; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; - self.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1); -} -@end -#endif diff --git a/Source/tvOS/ASImageNode+tvOS.mm b/Source/tvOS/ASImageNode+tvOS.mm deleted file mode 100644 index fa760f1291..0000000000 --- a/Source/tvOS/ASImageNode+tvOS.mm +++ /dev/null @@ -1,191 +0,0 @@ -// -// ASImageNode+tvOS.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#if TARGET_OS_TV -#import -#import - -// TODO: Remove this – we don't need to link GLKit just to convert degrees to radians. -#import -#import - -#import - -@implementation ASImageNode (tvOS) - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesBegan:touches withEvent:event]; - self.isDefaultFocusAppearance = NO; - UIView *view = [self getView]; - CALayer *layer = view.layer; - - CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); - [layer removeAllAnimations]; - [CATransaction begin]; - [CATransaction setCompletionBlock:^{ - layer.shadowOffset = targetShadowOffset; - }]; - - CABasicAnimation *shadowOffsetAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOffset"]; - shadowOffsetAnimation.toValue = [NSValue valueWithCGSize:targetShadowOffset]; - shadowOffsetAnimation.duration = 0.4; - shadowOffsetAnimation.removedOnCompletion = NO; - shadowOffsetAnimation.fillMode = kCAFillModeForwards; - shadowOffsetAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; - [layer addAnimation:shadowOffsetAnimation forKey:@"shadowOffset"]; - [CATransaction commit]; - - CABasicAnimation *shadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; - shadowOpacityAnimation.toValue = [NSNumber numberWithFloat:0.45]; - shadowOpacityAnimation.duration = 0.4; - shadowOpacityAnimation.removedOnCompletion = false; - shadowOpacityAnimation.fillMode = kCAFillModeForwards; - shadowOpacityAnimation.timingFunction = [CAMediaTimingFunction functionWithName:@"easeOut"]; - [layer addAnimation:shadowOpacityAnimation forKey:@"shadowOpacityAnimation"]; - - view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); - - [CATransaction commit]; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesMoved:touches withEvent:event]; - - // TODO: Clean up, and improve visuals. - - if (!self.isDefaultFocusAppearance) { - // This view may correspond to either self.view - // or our superview if we are in a ASCellNode - UIView *view = [self getView]; - - UITouch *touch = [touches anyObject]; - // Get the specific point that was touched - - // This is quite messy in it's current state so is not ready for production. - // The reason it is here is for others to contribute and to make it clear what is occuring. - - // We get the touch location in self.view because - // we are operating in that coordinate system. - // BUT we apply our transforms to *view since we want to apply - // the transforms to the root view (L: 107) - CGPoint point = [touch locationInView:self.view]; - CGFloat pitch = 0; - CGFloat yaw = 0; - BOOL topHalf = NO; - if (point.y > CGRectGetHeight(self.view.frame)) { - pitch = 15; - } else if (point.y < -CGRectGetHeight(self.view.frame)) { - pitch = -15; - } else { - pitch = (point.y/CGRectGetHeight(self.view.frame))*15; - } - if (pitch < 0) { - topHalf = YES; - } - - if (point.x > CGRectGetWidth(self.view.frame)) { - yaw = 10; - } else if (point.x < -CGRectGetWidth(self.view.frame)) { - yaw = -10; - } else { - yaw = (point.x/CGRectGetWidth(self.view.frame))*10; - } - if (!topHalf) { - if (yaw > 0) { - yaw = -yaw; - } else { - yaw = fabs(yaw); - } - } - - CATransform3D pitchTransform = CATransform3DMakeRotation(GLKMathDegreesToRadians(pitch),1.0,0.0,0.0); - CATransform3D yawTransform = CATransform3DMakeRotation(GLKMathDegreesToRadians(yaw),0.0,1.0,0.0); - CATransform3D transform = CATransform3DConcat(pitchTransform, yawTransform); - CATransform3D scaleAndTransform = CATransform3DConcat(transform, CATransform3DMakeAffineTransform(CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25))); - - [UIView animateWithDuration:0.5 animations:^{ - view.layer.transform = scaleAndTransform; - }]; - } else { - [self setDefaultFocusAppearance]; - } -} - - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event -{ - [super touchesEnded:touches withEvent:event]; - [self finishTouches]; -} - -- (void)finishTouches -{ - if (!self.isDefaultFocusAppearance) { - UIView *view = [self getView]; - CALayer *layer = view.layer; - - CGSize targetShadowOffset = CGSizeMake(0.0, self.bounds.size.height/8); - CATransform3D targetScaleTransform = CATransform3DMakeScale(1.2, 1.2, 1.2); - [CATransaction begin]; - [CATransaction setCompletionBlock:^{ - layer.shadowOffset = targetShadowOffset; - }]; - [CATransaction commit]; - - [UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ - view.layer.transform = targetScaleTransform; - } completion:^(BOOL finished) { - if (finished) { - [layer removeAnimationForKey:@"shadowOffset"]; - [layer removeAnimationForKey:@"shadowOpacity"]; - } - }]; - } else { - [self setDefaultFocusAppearance]; - } -} - -- (void)setFocusedState -{ - UIView *view = [self getView]; - CALayer *layer = view.layer; - layer.shadowOffset = CGSizeMake(2, 10); - layer.shadowColor = [UIColor blackColor].CGColor; - layer.shadowRadius = 12.0; - layer.shadowOpacity = 0.45; - layer.shadowPath = [UIBezierPath bezierPathWithRect:self.layer.bounds].CGPath; - view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.25, 1.25); -} - -- (void)setDefaultFocusAppearance -{ - UIView *view = [self getView]; - CALayer *layer = view.layer; - view.transform = CGAffineTransformIdentity; - layer.shadowOpacity = 0; - layer.shadowOffset = CGSizeZero; - layer.shadowRadius = 0; - layer.shadowPath = nil; - [layer removeAnimationForKey:@"shadowOffset"]; - [layer removeAnimationForKey:@"shadowOpacity"]; - self.isDefaultFocusAppearance = YES; -} - -- (UIView *)getView -{ - // TODO: This needs to be re-visited to handle all possibilities. - // If we are inside a ASCellNode, then we need to apply our focus effects to the ASCellNode view/layer rather than the ASImageNode view/layer. - return ASDisplayNodeUltimateParentOfNode(self).view; -} - -@end -#endif diff --git a/Tests/ASButtonNodeTests.mm b/Tests/ASButtonNodeTests.mm index 53e886e3c9..1e5f7ca184 100644 --- a/Tests/ASButtonNodeTests.mm +++ b/Tests/ASButtonNodeTests.mm @@ -61,7 +61,6 @@ - (void)testAccessibilityWithoutATitle ASButtonNode *buttonNode = [[ASButtonNode alloc] init]; buttonNode.accessibilityLabel = @"My Test"; // Make sure the title node is not nil. - buttonNode.titleNode.placeholderColor = [UIColor whiteColor]; buttonNode.selected = YES; XCTAssertTrue([buttonNode.accessibilityLabel isEqualToString:@"My Test"]); } diff --git a/Tests/ASCollectionModernDataSourceTests.mm b/Tests/ASCollectionModernDataSourceTests.mm index e987914f43..0c15907c6e 100644 --- a/Tests/ASCollectionModernDataSourceTests.mm +++ b/Tests/ASCollectionModernDataSourceTests.mm @@ -35,10 +35,6 @@ @implementation ASCollectionModernDataSourceTests { - (void)setUp { [super setUp]; - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalOptimizeDataControllerPipeline; - [ASConfigurationManager test_resetWithConfiguration:config]; - // Default is 2 sections: 2 items in first, 1 item in second. sections = [NSMutableArray array]; [sections addObject:[ASTestSection new]]; diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index ab4779ea9b..e494894f44 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -176,9 +176,6 @@ @implementation ASCollectionViewTests - (void)setUp { [super setUp]; - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalOptimizeDataControllerPipeline; - [ASConfigurationManager test_resetWithConfiguration:config]; } - (void)testDataSourceImplementsNecessaryMethods @@ -1070,23 +1067,14 @@ - (void)_primitiveBatchFetchingFillTestAnimated:(BOOL)animated visible:(BOOL)vis - (void)testInitialRangeBounds { - [self testInitialRangeBoundsWithCellLayoutMode:ASCellLayoutModeNone - shouldWaitUntilAllUpdatesAreProcessed:YES]; + [self testInitialRangeBoundsShouldWaitUntilAllUpdatesAreProcessed:YES]; } -- (void)testInitialRangeBoundsCellLayoutModeAlwaysAsync -{ - [self testInitialRangeBoundsWithCellLayoutMode:ASCellLayoutModeAlwaysAsync - shouldWaitUntilAllUpdatesAreProcessed:YES]; -} - -- (void)testInitialRangeBoundsWithCellLayoutMode:(ASCellLayoutMode)cellLayoutMode - shouldWaitUntilAllUpdatesAreProcessed:(BOOL)shouldWait +- (void)testInitialRangeBoundsShouldWaitUntilAllUpdatesAreProcessed:(BOOL)shouldWait { UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; ASCollectionNode *cn = testController.collectionNode; - cn.cellLayoutMode = cellLayoutMode; [cn setTuningParameters:{ .leadingBufferScreenfuls = 2, .trailingBufferScreenfuls = 0 } forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; window.rootViewController = testController; diff --git a/Tests/ASCollectionViewThrashTests.mm b/Tests/ASCollectionViewThrashTests.mm index b609cb2ba8..f2d184c8e0 100644 --- a/Tests/ASCollectionViewThrashTests.mm +++ b/Tests/ASCollectionViewThrashTests.mm @@ -29,9 +29,6 @@ @implementation ASCollectionViewThrashTests - (void)setUp { [super setUp]; - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalOptimizeDataControllerPipeline; - [ASConfigurationManager test_resetWithConfiguration:config]; } - (void)tearDown @@ -44,13 +41,6 @@ - (void)tearDown _update = nil; } -// NOTE: Despite the documentation, this is not always called if an exception is caught. -- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected -{ - _failed = YES; - [super recordFailureWithDescription:description inFile:filePath atLine:lineNumber expected:expected]; -} - - (void)verifyDataSource:(ASThrashDataSource *)ds { CollectionView *collectionView = ds.collectionView; diff --git a/Tests/ASConfigurationTests.mm b/Tests/ASConfigurationTests.mm deleted file mode 100644 index 4ebd580fb5..0000000000 --- a/Tests/ASConfigurationTests.mm +++ /dev/null @@ -1,138 +0,0 @@ -// -// ASConfigurationTests.m -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import -#import - -#import "ASTestCase.h" - -static ASExperimentalFeatures features[] = { -#if AS_ENABLE_TEXTNODE - ASExperimentalTextNode, -#endif - ASExperimentalInterfaceStateCoalescing, - ASExperimentalLayerDefaults, - ASExperimentalCollectionTeardown, - ASExperimentalFramesetterCache, - ASExperimentalSkipClearData, - ASExperimentalDidEnterPreloadSkipASMLayout, - ASExperimentalDispatchApply, - ASExperimentalDrawingGlobal, - ASExperimentalOptimizeDataControllerPipeline, - ASExperimentalDisableGlobalTextkitLock, - ASExperimentalMainThreadOnlyDataController, -}; - -@interface ASConfigurationTests : ASTestCase - -@end - -@implementation ASConfigurationTests { - void (^onActivate)(ASConfigurationTests *self, ASExperimentalFeatures feature); -} - -+ (NSArray *)names { - return @[ - @"exp_text_node", - @"exp_interface_state_coalesce", - @"exp_infer_layer_defaults", - @"exp_collection_teardown", - @"exp_framesetter_cache", - @"exp_skip_clear_data", - @"exp_did_enter_preload_skip_asm_layout", - @"exp_dispatch_apply", - @"exp_drawing_global", - @"exp_optimize_data_controller_pipeline", - @"exp_disable_global_textkit_lock", - @"exp_main_thread_only_data_controller" - ]; -} - -- (ASExperimentalFeatures)allFeatures -{ - ASExperimentalFeatures allFeatures = 0; - for (int i = 0; i < sizeof(features)/sizeof(ASExperimentalFeatures); i++) { - allFeatures |= features[i]; - } - return allFeatures; -} - -#if AS_ENABLE_TEXTNODE - -- (void)testExperimentalFeatureConfig -{ - // Set the config - ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; - config.experimentalFeatures = ASExperimentalLayerDefaults; - config.delegate = self; - [ASConfigurationManager test_resetWithConfiguration:config]; - - // Set an expectation for a callback, and assert we only get one. - XCTestExpectation *e = [self expectationWithDescription:@"Callbacks done."]; - e.expectedFulfillmentCount = 2; - e.assertForOverFulfill = YES; - onActivate = ^(ASConfigurationTests *self, ASExperimentalFeatures feature) { - [e fulfill]; - }; - - // Now activate the graphics experiment and expect it works. - XCTAssertTrue(ASActivateExperimentalFeature(ASExperimentalLayerDefaults)); - // We should get a callback here - // Now activate text node and expect it fails. - XCTAssertFalse(ASActivateExperimentalFeature(ASExperimentalTextNode)); - // But we should get another callback. - [self waitForExpectationsWithTimeout:3 handler:nil]; -} - -#endif - -- (void)textureDidActivateExperimentalFeatures:(ASExperimentalFeatures)feature -{ - if (onActivate) { - onActivate(self, feature); - } -} - -- (void)testMappingNamesToFlags -{ - // Throw in a bad bit. - ASExperimentalFeatures allFeatures = [self allFeatures]; - ASExperimentalFeatures featuresWithBadBit = allFeatures | (1 << 22); - NSArray *expectedNames = [ASConfigurationTests names]; - XCTAssertEqualObjects(expectedNames, ASExperimentalFeaturesGetNames(featuresWithBadBit)); -} - -- (void)testMappingFlagsFromNames -{ - // Throw in a bad name. - NSMutableArray *allNames = [[NSMutableArray alloc] initWithArray:[ASConfigurationTests names]]; - [allNames addObject:@"__invalid_name"]; - ASExperimentalFeatures expected = [self allFeatures]; - XCTAssertEqual(expected, ASExperimentalFeaturesFromArray(allNames)); -} - -- (void)testFlagMatchName -{ - NSArray *names = [ASConfigurationTests names]; - for (NSInteger i = 0; i < names.count; i++) { - XCTAssertEqual(features[i], ASExperimentalFeaturesFromArray(@[names[i]])); - } -} - -- (void)testNameMatchFlag { - NSArray *names = [ASConfigurationTests names]; - for (NSInteger i = 0; i < names.count; i++) { - XCTAssertEqualObjects(@[names[i]], ASExperimentalFeaturesGetNames(features[i])); - } -} - -@end diff --git a/Tests/ASDKViewControllerTests.mm b/Tests/ASDKViewControllerTests.mm deleted file mode 100644 index 1f7af48c1c..0000000000 --- a/Tests/ASDKViewControllerTests.mm +++ /dev/null @@ -1,94 +0,0 @@ -// -// ASDKViewControllerTests.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import - -#import "NSInvocation+ASTestHelpers.h" - -@interface ASDKViewControllerTests : XCTestCase - -@end - -@implementation ASDKViewControllerTests - -- (void)testThatAutomaticSubnodeManagementScrollViewInsetsAreApplied -{ - UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - [window makeKeyAndVisible]; - - ASDisplayNode *node = [[ASDisplayNode alloc] init]; - node.automaticallyManagesSubnodes = YES; - ASScrollNode *scrollNode = [[ASScrollNode alloc] init]; - node.layoutSpecBlock = ^(ASDisplayNode *node, ASSizeRange constrainedSize){ - return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:scrollNode]; - }; - ASDKViewController *vc = [[ASDKViewController alloc] initWithNode:node]; - window.rootViewController = [[UINavigationController alloc] initWithRootViewController:vc]; - [window makeKeyAndVisible]; - [window layoutIfNeeded]; - - XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame)); - XCTAssertEqual(scrollNode.view.contentInset.top, 0); -} - -- (void)testThatViewControllerFrameIsRightAfterCustomTransitionWithNonextendedEdges -{ - UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - [window makeKeyAndVisible]; - - ASDisplayNode *node = [[ASDisplayNode alloc] init]; - - ASDKViewController *vc = [[ASDKViewController alloc] initWithNode:node]; - vc.node.backgroundColor = [UIColor greenColor]; - vc.edgesForExtendedLayout = UIRectEdgeNone; - - UIViewController * oldVC = [[UIViewController alloc] init]; - UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:oldVC]; - id navDelegate = [OCMockObject niceMockForProtocol:@protocol(UINavigationControllerDelegate)]; - id animator = [OCMockObject niceMockForProtocol:@protocol(UIViewControllerAnimatedTransitioning)]; - [[[[navDelegate expect] ignoringNonObjectArgs] andReturn:animator] navigationController:[OCMArg any] animationControllerForOperation:UINavigationControllerOperationPush fromViewController:[OCMArg any] toViewController:[OCMArg any]]; - [[[animator expect] andReturnValue:@0.3] transitionDuration:[OCMArg any]]; - XCTestExpectation *e = [self expectationWithDescription:@"Transition completed"]; - [[[animator expect] andDo:^(NSInvocation *invocation) { - id ctx = [invocation as_argumentAtIndexAsObject:2]; - UIView *container = [ctx containerView]; - [container addSubview:vc.view]; - vc.view.alpha = 0; - vc.view.frame = [ctx finalFrameForViewController:vc]; - [UIView animateWithDuration:0.3 animations:^{ - vc.view.alpha = 1; - oldVC.view.alpha = 0; - } completion:^(BOOL finished) { - [oldVC.view removeFromSuperview]; - [ctx completeTransition:finished]; - [e fulfill]; - }]; - }] animateTransition:[OCMArg any]]; - nav.delegate = navDelegate; - window.rootViewController = nav; - [window makeKeyAndVisible]; - [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; - [nav pushViewController:vc animated:YES]; - [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; - - [self waitForExpectationsWithTimeout:2 handler:nil]; - - CGFloat navHeight = CGRectGetMaxY([nav.navigationBar convertRect:nav.navigationBar.bounds toView:window]); - CGRect expectedRect, slice; - CGRectDivide(window.bounds, &slice, &expectedRect, navHeight, CGRectMinYEdge); - XCTAssertEqualObjects(NSStringFromCGRect(expectedRect), NSStringFromCGRect(node.frame)); - [navDelegate verify]; - [animator verify]; -} - -@end diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index edf622c71c..fda39a8c61 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -29,7 +29,6 @@ #import #import #import -#import #import "ASXCTExtensions.h" #import "ASDisplayNodeTestsHelper.h" @@ -274,12 +273,6 @@ - (BOOL)canBecomeFirstResponder { } @end -@interface ASTestViewController: ASDKViewController -@end -@implementation ASTestViewController -- (BOOL)prefersStatusBarHidden { return YES; } -@end - @interface UIResponderNodeTestDisplayViewCallingSuper : _ASDisplayView @end @implementation UIResponderNodeTestDisplayViewCallingSuper @@ -513,8 +506,6 @@ - (void)checkValuesMatchDefaults:(ASDisplayNode *)node isLayerBacked:(BOOL)isLay XCTAssertEqual(UIViewAutoresizingNone, node.autoresizingMask, @"default autoresizingMask broken %@", hasLoadedView); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(8, 8, 8, 8), node.layoutMargins), @"default layoutMargins broken %@", hasLoadedView); XCTAssertEqual(NO, node.preservesSuperviewLayoutMargins, @"default preservesSuperviewLayoutMargins broken %@", hasLoadedView); - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, node.safeAreaInsets), @"default safeAreaInsets broken %@", hasLoadedView); - XCTAssertEqual(YES, node.insetsLayoutMarginsFromSafeArea, @"default insetsLayoutMarginsFromSafeArea broken %@", hasLoadedView); if (node.nodeLoaded) { XCTAssertNotNil(node.tintColor, @"default tintColor broken %@", hasLoadedView); // It has been populated by the UIView. } else { @@ -642,7 +633,6 @@ - (void)checkValuesMatchSetValues:(ASDisplayNode *)node isLayerBacked:(BOOL)isLa XCTAssertEqual(NO, node.autoresizesSubviews, @"autoresizesSubviews broken %@", hasLoadedView); XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(3, 5, 8, 11), node.layoutMargins), @"layoutMargins broken %@", hasLoadedView); XCTAssertEqual(YES, node.preservesSuperviewLayoutMargins, @"preservesSuperviewLayoutMargins broken %@", hasLoadedView); - XCTAssertEqual(NO, node.insetsLayoutMarginsFromSafeArea, @"insetsLayoutMarginsFromSafeArea broken %@", hasLoadedView); } } @@ -715,7 +705,6 @@ - (void)checkSimpleBridgePropertiesSetPropagate:(BOOL)isLayerBacked node.exclusiveTouch = YES; node.autoresizesSubviews = NO; node.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; - node.insetsLayoutMarginsFromSafeArea = NO; node.layoutMargins = UIEdgeInsetsMake(3, 5, 8, 11); node.preservesSuperviewLayoutMargins = YES; } @@ -2673,52 +2662,6 @@ - (void)DISABLED_testThatItIsAllowedToRetrieveDebugDescriptionIncludingVCOffMain XCTAssert(hasVC); } -- (void)testThatSubnodeSafeAreaInsetsAreCalculatedCorrectly -{ - ASDisplayNode *rootNode = [[ASDisplayNode alloc] init]; - ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; - - rootNode.automaticallyManagesSubnodes = YES; - rootNode.layoutSpecBlock = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { - return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(1, 2, 3, 4) child:subnode]; - }; - - ASTestViewController *viewController = [[ASTestViewController alloc] initWithNode:rootNode]; - viewController.additionalSafeAreaInsets = UIEdgeInsetsMake(10, 10, 10, 10); - - // It looks like iOS 11 suppresses safeAreaInsets calculation for the views that are not on screen. - UIWindow *window = [[UIWindow alloc] init]; - window.rootViewController = viewController; - [window setHidden:NO]; - - [window.rootViewController.view setNeedsLayout]; - [window.rootViewController.view layoutIfNeeded]; - - UIEdgeInsets expectedRootNodeSafeArea = UIEdgeInsetsMake(10, 10, 10, 10); - UIEdgeInsets expectedSubnodeSafeArea = UIEdgeInsetsMake(9, 8, 7, 6); - - UIEdgeInsets windowSafeArea = UIEdgeInsetsZero; - if (AS_AVAILABLE_IOS(11.0)) { - windowSafeArea = window.safeAreaInsets; - } - - expectedRootNodeSafeArea = ASConcatInsets(expectedRootNodeSafeArea, windowSafeArea); - expectedSubnodeSafeArea = ASConcatInsets(expectedSubnodeSafeArea, windowSafeArea); - - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(expectedRootNodeSafeArea, rootNode.safeAreaInsets), - @"expected rootNode.safeAreaInsets to be %@ but got %@ (window.safeAreaInsets %@)", - NSStringFromUIEdgeInsets(expectedRootNodeSafeArea), - NSStringFromUIEdgeInsets(rootNode.safeAreaInsets), - NSStringFromUIEdgeInsets(windowSafeArea)); - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(expectedSubnodeSafeArea, subnode.safeAreaInsets), - @"expected subnode.safeAreaInsets to be %@ but got %@ (window.safeAreaInsets %@)", - NSStringFromUIEdgeInsets(expectedSubnodeSafeArea), - NSStringFromUIEdgeInsets(subnode.safeAreaInsets), - NSStringFromUIEdgeInsets(windowSafeArea)); - - [window setHidden:YES]; -} - - (void)testScreenScale { XCTAssertEqual(ASScreenScale(), UIScreen.mainScreen.scale); diff --git a/Tests/ASDisplayViewAccessibilityTests.mm b/Tests/ASDisplayViewAccessibilityTests.mm index 2b8a848b13..da2684a6ce 100644 --- a/Tests/ASDisplayViewAccessibilityTests.mm +++ b/Tests/ASDisplayViewAccessibilityTests.mm @@ -18,10 +18,7 @@ #import #import #import -#import -#import #import -#import #import #import "ASDisplayNodeTestsHelper.h" @@ -339,7 +336,8 @@ - (void)testAccessibilityElementsNotInAppWindow { ASDisplayNode *node = [[ASDisplayNode alloc] init]; node.automaticallyManagesSubnodes = YES; - ASDKViewController *vc = [[ASDKViewController alloc] initWithNode:node]; + UIViewController *vc = [[UIViewController alloc] init]; + [vc.view addSubnode:node]; window.rootViewController = vc; [window makeKeyAndVisible]; [window layoutIfNeeded]; @@ -389,7 +387,8 @@ - (void)testAccessibilityElementsNotInAppWindowButInScrollView { ASScrollNode *node = [[ASScrollNode alloc] init]; node.automaticallyManagesSubnodes = YES; - ASDKViewController *vc = [[ASDKViewController alloc] initWithNode:node]; + UIViewController *vc = [[UIViewController alloc] init]; + [vc.view addSubnode:node]; window.rootViewController = vc; [window makeKeyAndVisible]; [window layoutIfNeeded]; @@ -494,7 +493,8 @@ - (void)testSubnodeIsModal { ASDisplayNode *node = [[ASDisplayNode alloc] init]; node.automaticallyManagesSubnodes = YES; - ASDKViewController *vc = [[ASDKViewController alloc] initWithNode:node]; + UIViewController *vc = [[UIViewController alloc] init]; + [vc.view addSubnode:node]; window.rootViewController = vc; [window makeKeyAndVisible]; [window layoutIfNeeded]; @@ -535,7 +535,8 @@ - (void)testMultipleSubnodesAreModal { ASDisplayNode *node = [[ASDisplayNode alloc] init]; node.automaticallyManagesSubnodes = YES; - ASDKViewController *vc = [[ASDKViewController alloc] initWithNode:node]; + UIViewController *vc = [[UIViewController alloc] init]; + [vc.view addSubnode:node]; window.rootViewController = vc; [window makeKeyAndVisible]; [window layoutIfNeeded]; @@ -597,7 +598,8 @@ - (void)testAccessibilityElementsHidden { ASDisplayNode *node = [[ASDisplayNode alloc] init]; node.automaticallyManagesSubnodes = YES; - ASDKViewController *vc = [[ASDKViewController alloc] initWithNode:node]; + UIViewController *vc = [[UIViewController alloc] init]; + [vc.view addSubnode:node]; window.rootViewController = vc; [window makeKeyAndVisible]; [window layoutIfNeeded]; diff --git a/Tests/ASGraphicsContextTests.mm b/Tests/ASGraphicsContextTests.mm index 1a93734183..58786dd1d2 100644 --- a/Tests/ASGraphicsContextTests.mm +++ b/Tests/ASGraphicsContextTests.mm @@ -11,7 +11,6 @@ #import #import #import -#import @interface ASGraphicsContextTests : XCTestCase @end @@ -21,9 +20,6 @@ @implementation ASGraphicsContextTests - (void)setUp { [super setUp]; - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalDrawingGlobal; - [ASConfigurationManager test_resetWithConfiguration:config]; } diff --git a/Tests/ASImageNodeBackingSizeTests.mm b/Tests/ASImageNodeBackingSizeTests.mm index 96d8384140..14a9b25171 100644 --- a/Tests/ASImageNodeBackingSizeTests.mm +++ b/Tests/ASImageNodeBackingSizeTests.mm @@ -55,7 +55,7 @@ - (void)testScaleAspectFitModeBackingSizeCalculation { CGSize backingSize = CGSizeZero; CGRect imageDrawRect = CGRectZero; - ASCroppedImageBackingSizeAndDrawRectInBounds(imageSize, boundsSize, UIViewContentModeScaleAspectFit, CGRectZero, false, CGSizeZero, &backingSize, &imageDrawRect); + ASCroppedImageBackingSizeAndDrawRectInBounds(imageSize, boundsSize, UIViewContentModeScaleAspectFit, CGRectZero, &backingSize, &imageDrawRect); CGSize backingSizeShouldBe = _FillSizeWithAspectRatio(boundsSize.width / boundsSize.height, imageSize); CGSize drawRectSizeShouldBe = _FitSizeWithAspectRatio(imageSize.width / imageSize.height, backingSizeShouldBe); XCTAssertTrue(CGSizeEqualToSize(backingSizeShouldBe, backingSize)); @@ -70,7 +70,7 @@ - (void)testScaleAspectFillModeBackingSizeCalculation { CGSize backingSize = CGSizeZero; CGRect imageDrawRect = CGRectZero; - ASCroppedImageBackingSizeAndDrawRectInBounds(imageSize, boundsSize, UIViewContentModeScaleAspectFill, CGRectZero, false, CGSizeZero, &backingSize, &imageDrawRect); + ASCroppedImageBackingSizeAndDrawRectInBounds(imageSize, boundsSize, UIViewContentModeScaleAspectFill, CGRectZero, &backingSize, &imageDrawRect); CGSize backingSizeShouldBe = _FitSizeWithAspectRatio(boundsSize.width / boundsSize.height, imageSize); CGSize drawRectSizeShouldBe = _FillSizeWithAspectRatio(imageSize.width / imageSize.height, backingSizeShouldBe); XCTAssertTrue(CGSizeEqualToSize(backingSizeShouldBe, backingSize)); diff --git a/Tests/ASImageNodeSnapshotTests.mm b/Tests/ASImageNodeSnapshotTests.mm index 13364930bd..7ea2a56136 100644 --- a/Tests/ASImageNodeSnapshotTests.mm +++ b/Tests/ASImageNodeSnapshotTests.mm @@ -60,30 +60,6 @@ - (void)testRenderLogoSquare ASSnapshotVerifyNode(imageNode, nil); } -- (void)testForcedScaling -{ - CGSize forcedImageSize = CGSizeMake(100, 100); - - ASImageNode *imageNode = [[ASImageNode alloc] init]; - imageNode.forcedSize = forcedImageSize; - imageNode.image = [self testImage]; - - // Snapshot testing requires that node is formally laid out. - imageNode.style.width = ASDimensionMake(forcedImageSize.width); - imageNode.style.height = ASDimensionMake(forcedImageSize.height); - ASDisplayNodeSizeToFitSize(imageNode, forcedImageSize); - ASSnapshotVerifyNode(imageNode, @"first"); - - imageNode.style.width = ASDimensionMake(200); - imageNode.style.height = ASDimensionMake(200); - ASDisplayNodeSizeToFitSize(imageNode, CGSizeMake(200, 200)); - ASSnapshotVerifyNode(imageNode, @"second"); - - XCTAssert(CGImageGetWidth((CGImageRef)imageNode.contents) == forcedImageSize.width * imageNode.contentsScale && - CGImageGetHeight((CGImageRef)imageNode.contents) == forcedImageSize.height * imageNode.contentsScale, - @"Contents should be 100 x 100 by contents scale."); -} - - (void)testTintColorOnNodePropertyAlwaysTemplate { UIImage *test = [self testImage]; @@ -101,10 +77,6 @@ - (void)testTintColorOnNodePropertyAlwaysTemplate - (void)testTintColorOnGrayscaleNodePropertyAlwaysTemplate { - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalDrawingGlobal; - [ASConfigurationManager test_resetWithConfiguration:config]; - UIImage *test = [self testGrayscaleImage]; ASImageNode *node = [[ASImageNode alloc] init]; node.image = [test imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; @@ -187,51 +159,8 @@ - (void)testTintColorInheritsFromSupernodeViewBacked ASSnapshotVerifyNode(node, nil); } -- (void)testRoundedCornerBlock -{ - UIGraphicsBeginImageContext(CGSizeMake(100, 100)); - [[UIColor blueColor] setFill]; - UIRectFill(CGRectMake(0, 0, 100, 100)); - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - ASPrimitiveTraitCollection traitCollection; - - if (@available(iOS 13.0, *)) { - traitCollection = ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection.currentTraitCollection); - } else { - traitCollection = ASPrimitiveTraitCollectionMakeDefault(); - } - - UIImage *rounded = ASImageNodeRoundBorderModificationBlock(2, [UIColor redColor])(result, traitCollection); - ASImageNode *node = [[ASImageNode alloc] init]; - node.image = rounded; - ASDisplayNodeSizeToFitSize(node, rounded.size); - ASSnapshotVerifyNode(node, nil); -} - -- (void)testTintColorBlock -{ - UIGraphicsBeginImageContext(CGSizeMake(100, 100)); - [[UIColor blueColor] setFill]; - UIRectFill(CGRectMake(0, 0, 100, 100)); - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - UIImage *rounded = ASImageNodeTintColorModificationBlock([UIColor redColor])(result, ASPrimitiveTraitCollectionMakeDefault()); - ASImageNode *node = [[ASImageNode alloc] init]; - node.image = rounded; - ASDisplayNodeSizeToFitSize(node, rounded.size); - ASSnapshotVerifyNode(node, nil); -} - - (void)testUIGraphicsRendererDrawingExperiment { - // Test to ensure that rendering with UIGraphicsRenderer don't regress - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalDrawingGlobal; - [ASConfigurationManager test_resetWithConfiguration:config]; - ASImageNode *imageNode = [[ASImageNode alloc] init]; imageNode.image = [self testImage]; ASDisplayNodeSizeToFitSize(imageNode, CGSizeMake(100, 100)); diff --git a/Tests/ASMultiplexImageNodeTests.mm b/Tests/ASMultiplexImageNodeTests.mm deleted file mode 100644 index 8ebd77c458..0000000000 --- a/Tests/ASMultiplexImageNodeTests.mm +++ /dev/null @@ -1,265 +0,0 @@ -// -// ASMultiplexImageNodeTests.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import "NSInvocation+ASTestHelpers.h" - -#import -#import -#import - -#import - -@interface ASMultiplexImageNodeTests : XCTestCase -{ -@private - id mockCache; - id mockDownloader; - id mockDataSource; - id mockDelegate; - ASMultiplexImageNode *imageNode; -} - -@end - -@implementation ASMultiplexImageNodeTests - -#pragma mark - Helpers. - -- (NSURL *)_testImageURL -{ - return [[NSBundle bundleForClass:[self class]] URLForResource:@"logo-square" - withExtension:@"png" - subdirectory:@"TestResources"]; -} - -- (UIImage *)_testImage -{ - return [UIImage imageWithContentsOfFile:[self _testImageURL].path]; -} - -#pragma mark - Unit tests. - -// TODO: add tests for delegate display notifications - -- (void)setUp -{ - [super setUp]; - - mockCache = OCMStrictProtocolMock(@protocol(ASImageCacheProtocol)); - [mockCache setExpectationOrderMatters:YES]; - mockDownloader = OCMStrictProtocolMock(@protocol(ASImageDownloaderProtocol)); - [mockDownloader setExpectationOrderMatters:YES]; - imageNode = [[ASMultiplexImageNode alloc] initWithCache:mockCache downloader:mockDownloader]; - - mockDataSource = OCMStrictProtocolMock(@protocol(ASMultiplexImageNodeDataSource)); - [mockDataSource setExpectationOrderMatters:YES]; - imageNode.dataSource = mockDataSource; - - mockDelegate = OCMProtocolMock(@protocol(ASMultiplexImageNodeDelegate)); - [mockDelegate setExpectationOrderMatters:YES]; - imageNode.delegate = mockDelegate; -} - -- (void)tearDown -{ - OCMVerifyAll(mockDelegate); - OCMVerifyAll(mockDataSource); - OCMVerifyAll(mockDownloader); - OCMVerifyAll(mockCache); - [super tearDown]; -} - -- (void)testDataSourceImageMethod -{ - NSNumber *imageIdentifier = @1; - - OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier]) - .andReturn([self _testImage]); - - imageNode.imageIdentifiers = @[imageIdentifier]; - [imageNode reloadImageIdentifierSources]; - - // Also expect it to be loaded immediately. - XCTAssertEqualObjects(imageNode.loadedImageIdentifier, imageIdentifier, @"imageIdentifier was not loaded"); - // And for the image to be equivalent to the image we provided. - XCTAssertEqualObjects(UIImagePNGRepresentation(imageNode.image), - UIImagePNGRepresentation([self _testImage]), - @"Loaded image isn't the one we provided"); -} - -- (void)testDataSourceURLMethod -{ - NSNumber *imageIdentifier = @1; - - // First expect to be hit for the image directly, and fail to return it. - OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier]) - .andReturn((id)nil); - // BUG: -imageForImageIdentifier is called twice in this case (where we return nil). - OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier]) - .andReturn((id)nil); - // Then expect to be hit for the URL, which we'll return. - OCMExpect([mockDataSource multiplexImageNode:imageNode URLForImageIdentifier:imageIdentifier]) - .andReturn([self _testImageURL]); - - // Mock the cache to do a cache-hit for the test image URL. - OCMExpect([mockCache cachedImageWithURL:[self _testImageURL] callbackQueue:OCMOCK_ANY completion:[OCMArg isNotNil]]) - .andDo(^(NSInvocation *inv) { - ASImageCacherCompletion completionBlock = [inv as_argumentAtIndexAsObject:4]; - completionBlock([self _testImage], ASImageCacheTypeAsynchronous); - }); - - imageNode.imageIdentifiers = @[imageIdentifier]; - // Kick off loading. - [imageNode reloadImageIdentifierSources]; - - // Also expect it to be loaded immediately. - XCTAssertEqualObjects(imageNode.loadedImageIdentifier, imageIdentifier, @"imageIdentifier was not loaded"); - // And for the image to be equivalent to the image we provided. - XCTAssertEqualObjects(UIImagePNGRepresentation(imageNode.image), - UIImagePNGRepresentation([self _testImage]), - @"Loaded image isn't the one we provided"); -} - -- (void)testAddLowerQualityImageIdentifier -{ - // Adding a lower quality image identifier should not cause any loading. - NSNumber *highResIdentifier = @2, *lowResIdentifier = @1; - - OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:highResIdentifier]) - .andReturn([self _testImage]); - imageNode.imageIdentifiers = @[highResIdentifier]; - [imageNode reloadImageIdentifierSources]; - - // At this point, we should have the high-res identifier loaded and the DS should have been hit once. - XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded."); - - // BUG: We should not get another -imageForImageIdentifier:highResIdentifier. - OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:highResIdentifier]) - .andReturn([self _testImage]); - - imageNode.imageIdentifiers = @[highResIdentifier, lowResIdentifier]; - [imageNode reloadImageIdentifierSources]; - - // At this point the high-res should still be loaded, and the data source should not have been hit again (see BUG above). - XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded."); -} - -- (void)testAddHigherQualityImageIdentifier -{ - NSNumber *lowResIdentifier = @1, *highResIdentifier = @2; - - OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:lowResIdentifier]) - .andReturn([self _testImage]); - - imageNode.imageIdentifiers = @[lowResIdentifier]; - [imageNode reloadImageIdentifierSources]; - - // At this point, we should have the low-res identifier loaded and the DS should have been hit once. - XCTAssertEqualObjects(imageNode.loadedImageIdentifier, lowResIdentifier, @"Low res identifier should be loaded."); - - OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:highResIdentifier]) - .andReturn([self _testImage]); - - imageNode.imageIdentifiers = @[highResIdentifier, lowResIdentifier]; - [imageNode reloadImageIdentifierSources]; - - // At this point the high-res should be loaded, and the data source should been hit twice. - XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded."); -} - -- (void)testIntermediateImageDownloading -{ - imageNode.downloadsIntermediateImages = YES; - - // Let them call URLForImageIdentifier all they want. - OCMStub([mockDataSource multiplexImageNode:imageNode URLForImageIdentifier:[OCMArg isNotNil]]); - - // Set up a few identifiers to load. - NSInteger identifierCount = 5; - NSMutableArray *imageIdentifiers = [NSMutableArray array]; - for (NSInteger identifier = identifierCount; identifier > 0; identifier--) { - [imageIdentifiers addObject:@(identifier)]; - } - - // Create the array of IDs in the order we expect them to get -imageForImageIdentifier: - // BUG: The second to last ID (the last one that returns nil) will get -imageForImageIdentifier: called - // again after the last ID (the one that returns non-nil). - id secondToLastID = imageIdentifiers[identifierCount - 2]; - NSArray *imageIdentifiersThatWillBeCalled = [imageIdentifiers arrayByAddingObject:secondToLastID]; - - for (id imageID in imageIdentifiersThatWillBeCalled) { - // Return nil for everything except the worst ID. - OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageID]) - .andDo(^(NSInvocation *inv){ - id imageID = [inv as_argumentAtIndexAsObject:3]; - if ([imageID isEqual:imageIdentifiers.lastObject]) { - [inv as_setReturnValueWithObject:[self _testImage]]; - } else { - [inv as_setReturnValueWithObject:nil]; - } - }); - } - - imageNode.imageIdentifiers = imageIdentifiers; - [imageNode reloadImageIdentifierSources]; -} - -- (void)testUncachedDownload -{ - // Mock a cache miss. - OCMExpect([mockCache cachedImageWithURL:[self _testImageURL] callbackQueue:OCMOCK_ANY completion:[OCMArg isNotNil]]) - .andDo(^(NSInvocation *inv){ - ASImageCacherCompletion completion = [inv as_argumentAtIndexAsObject:4]; - completion(nil, ASImageCacheTypeAsynchronous); - }); - - // 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]]) - .andDo(^(NSInvocation *inv){ - // Simulate progress. - ASImageDownloaderProgress progressBlock = [inv as_argumentAtIndexAsObject:5]; - progressBlock(mockedProgress); - - // Simulate completion. - ASImageDownloaderCompletion completionBlock = [inv as_argumentAtIndexAsObject:6]; - completionBlock([self _testImage], nil, nil, nil); - }); - - NSNumber *imageIdentifier = @1; - - // Mock the data source to return nil image, and our test URL. - OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier]); - // BUG: Multiplex image node will call imageForImageIdentifier twice if we return nil. - OCMExpect([mockDataSource multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier]); - OCMExpect([mockDataSource multiplexImageNode:imageNode URLForImageIdentifier:imageIdentifier]) - .andReturn([self _testImageURL]); - - // Mock the delegate to expect start, 50% progress, and completion invocations. - OCMExpect([mockDelegate multiplexImageNode:imageNode didStartDownloadOfImageWithIdentifier:imageIdentifier]); - OCMExpect([mockDelegate multiplexImageNode:imageNode didUpdateDownloadProgress:mockedProgress forImageWithIdentifier:imageIdentifier]); - OCMExpect([mockDelegate multiplexImageNode:imageNode didUpdateImage:[OCMArg isNotNil] withIdentifier:imageIdentifier fromImage:[OCMArg isNil] withIdentifier:[OCMArg isNil]]); - OCMExpect([mockDelegate multiplexImageNode:imageNode didFinishDownloadingImageWithIdentifier:imageIdentifier error:[OCMArg isNil]]); - - imageNode.imageIdentifiers = @[imageIdentifier]; - // Kick off loading. - [imageNode reloadImageIdentifierSources]; - - // Wait until the image is loaded. - [self expectationForPredicate:[NSPredicate predicateWithFormat:@"loadedImageIdentifier = %@", imageIdentifier] evaluatedWithObject:imageNode handler:nil]; - [self waitForExpectationsWithTimeout:30 handler:nil]; -} - -- (void)testThatSettingAnImageExternallyWillThrow -{ - XCTAssertThrows(imageNode.image = [UIImage imageNamed:@""]); -} - -@end diff --git a/Tests/ASMutableAttributedStringBuilderTests.mm b/Tests/ASMutableAttributedStringBuilderTests.mm deleted file mode 100644 index 2415dfc626..0000000000 --- a/Tests/ASMutableAttributedStringBuilderTests.mm +++ /dev/null @@ -1,78 +0,0 @@ -// -// ASMutableAttributedStringBuilderTests.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -@interface ASMutableAttributedStringBuilderTests : XCTestCase - -@end - -@implementation ASMutableAttributedStringBuilderTests - -- (NSString *)_string -{ - return @"Normcore PBR hella, viral slow-carb mustache chillwave church-key cornhole messenger bag swag vinyl biodiesel ethnic. Fashion axe messenger bag raw denim street art. Flannel Wes Anderson normcore church-key 8-bit. Master cleanse four loko try-hard Carles stumptown ennui, twee literally wayfarers kitsch tofu PBR. Cliche organic post-ironic Wes Anderson kale chips fashion axe. Narwhal Blue Bottle sustainable, Odd Future Godard sriracha banjo disrupt Marfa irony pug Wes Anderson YOLO yr church-key. Mlkshk Intelligentsia semiotics quinoa, butcher meggings wolf Bushwick keffiyeh ethnic pour-over Pinterest letterpress."; -} - -- (ASMutableAttributedStringBuilder *)_builder -{ - return [[ASMutableAttributedStringBuilder alloc] initWithString:[self _string]]; -} - -- (NSRange)_randomizedRangeForStringBuilder:(ASMutableAttributedStringBuilder *)builder -{ - NSUInteger loc = arc4random() % (builder.length - 1); - NSUInteger len = arc4random() % (builder.length - loc); - len = ((len > 0) ? len : 1); - return NSMakeRange(loc, len); -} - -- (void)testSimpleAttributions -{ - // Add a attributes, and verify that they get set on the correct locations. - for (int i = 0; i < 100; i++) { - ASMutableAttributedStringBuilder *builder = [self _builder]; - NSRange range = [self _randomizedRangeForStringBuilder:builder]; - NSString *keyValue = [NSString stringWithFormat:@"%d", i]; - [builder addAttribute:keyValue value:keyValue range:range]; - NSAttributedString *attrStr = [builder composedAttributedString]; - XCTAssertEqual(builder.length, attrStr.length, @"out string should have same length as builder"); - __block BOOL found = NO; - [attrStr enumerateAttributesInRange:NSMakeRange(0, attrStr.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange r, BOOL *stop) { - if ([attrs[keyValue] isEqualToString:keyValue]) { - XCTAssertTrue(NSEqualRanges(range, r), @"enumerated range %@ should be equal to the set range %@", NSStringFromRange(r), NSStringFromRange(range)); - found = YES; - } - }]; - XCTAssertTrue(found, @"enumeration should have found the attribute we set"); - } -} - -- (void)testSetOverAdd -{ - ASMutableAttributedStringBuilder *builder = [self _builder]; - NSRange addRange = NSMakeRange(0, builder.length); - NSRange setRange = NSMakeRange(0, 1); - [builder addAttribute:@"attr" value:@"val1" range:addRange]; - [builder setAttributes:@{@"attr" : @"val2"} range:setRange]; - NSAttributedString *attrStr = [builder composedAttributedString]; - NSRange setRangeOut; - NSString *setAttr = [attrStr attribute:@"attr" atIndex:0 effectiveRange:&setRangeOut]; - XCTAssertTrue(NSEqualRanges(setRange, setRangeOut), @"The out set range should equal the range we used originally"); - XCTAssertEqualObjects(setAttr, @"val2", @"the set value should be val2"); - - NSRange addRangeOut; - NSString *addAttr = [attrStr attribute:@"attr" atIndex:2 effectiveRange:&addRangeOut]; - XCTAssertTrue(NSEqualRanges(NSMakeRange(1, builder.length - 1), addRangeOut), @"the add range should only cover beyond the set range"); - XCTAssertEqualObjects(addAttr, @"val1", @"the added attribute should be present at index 2"); -} - -@end diff --git a/Tests/ASNavigationControllerTests.mm b/Tests/ASNavigationControllerTests.mm deleted file mode 100644 index 379905a4b3..0000000000 --- a/Tests/ASNavigationControllerTests.mm +++ /dev/null @@ -1,52 +0,0 @@ -// -// ASNavigationControllerTests.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -@interface ASNavigationControllerTests : XCTestCase -@end - -@implementation ASNavigationControllerTests - -- (void)testSetViewControllers { - ASDKViewController *firstController = [ASDKViewController new]; - ASDKViewController *secondController = [ASDKViewController new]; - NSArray *expectedViewControllerStack = @[firstController, secondController]; - ASNavigationController *navigationController = [ASNavigationController new]; - [navigationController setViewControllers:@[firstController, secondController]]; - XCTAssertEqual(navigationController.topViewController, secondController); - XCTAssertEqual(navigationController.visibleViewController, secondController); - XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]); -} - -- (void)testPopViewController { - ASDKViewController *firstController = [ASDKViewController new]; - ASDKViewController *secondController = [ASDKViewController new]; - NSArray *expectedViewControllerStack = @[firstController]; - ASNavigationController *navigationController = [ASNavigationController new]; - [navigationController setViewControllers:@[firstController, secondController]]; - [navigationController popViewControllerAnimated:false]; - XCTAssertEqual(navigationController.topViewController, firstController); - XCTAssertEqual(navigationController.visibleViewController, firstController); - XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]); -} - -- (void)testPushViewController { - ASDKViewController *firstController = [ASDKViewController new]; - ASDKViewController *secondController = [ASDKViewController new]; - NSArray *expectedViewControllerStack = @[firstController, secondController]; - ASNavigationController *navigationController = [[ASNavigationController new] initWithRootViewController:firstController]; - [navigationController pushViewController:secondController animated:false]; - XCTAssertEqual(navigationController.topViewController, secondController); - XCTAssertEqual(navigationController.visibleViewController, secondController); - XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]); -} - -@end diff --git a/Tests/ASNetworkImageNodeTests.mm b/Tests/ASNetworkImageNodeTests.mm index a707cd163a..d0853c1c1a 100644 --- a/Tests/ASNetworkImageNodeTests.mm +++ b/Tests/ASNetworkImageNodeTests.mm @@ -20,9 +20,6 @@ @interface ASTestImageDownloader : NSObject @interface ASTestImageCache : NSObject @end -@interface ASTestAnimatedImage : NSObject -@end - @implementation ASNetworkImageNodeTests { ASNetworkImageNode *node; id downloader; @@ -76,18 +73,6 @@ - (void)testThatProgressBlockIsSetAndClearedCorrectlyOnChangeURL [downloader verifyWithDelay:5]; } -- (void)testThatAnimatedImageClearedCorrectlyOnChangeURL -{ - [node layer]; - [node enterInterfaceState:ASInterfaceStateInHierarchy]; - - // Set URL while visible, should set progress block - node.animatedImage = [ASTestAnimatedImage new]; - [node setURL:[NSURL URLWithString:@"http://imageA"] resetToDefault:YES]; - - XCTAssertEqualObjects(nil, node.animatedImage); -} - - (void)testThatSettingAnImageWillStayForEnteringAndExitingPreloadState { UIImage *image = [[UIImage alloc] init]; @@ -148,61 +133,3 @@ - (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock call // nop } @end - -@implementation ASTestAnimatedImage -@synthesize playbackReadyCallback; - -- (UIImage *)coverImage -{ - return [UIImage new]; -} - -- (BOOL)coverImageReady -{ - return YES; -} - -- (CFTimeInterval)totalDuration -{ - return 1; -} - -- (NSUInteger)frameInterval -{ - return 0.2; -} - -- (size_t)loopCount -{ - return 0; -} - -- (size_t)frameCount -{ - return 5; -} - -- (BOOL)playbackReady -{ - return YES; -} - -- (NSError *)error -{ - return nil; -} - -- (CGImageRef)imageAtIndex:(NSUInteger)index -{ - return [[UIImage new] CGImage]; -} - -- (CFTimeInterval)durationAtIndex:(NSUInteger)index -{ - return 0.2; -} - -- (void)clearAnimatedImageCache -{} - -@end diff --git a/Tests/ASPagerNodeTests.mm b/Tests/ASPagerNodeTests.mm deleted file mode 100644 index 835de4d4da..0000000000 --- a/Tests/ASPagerNodeTests.mm +++ /dev/null @@ -1,179 +0,0 @@ -// -// ASPagerNodeTests.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import - -@interface ASPagerNodeTestDataSource : NSObject -@end - -@implementation ASPagerNodeTestDataSource - -- (instancetype)init -{ - if (!(self = [super init])) { - return nil; - } - return self; -} - -- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode -{ - return 2; -} - -- (ASCellNode *)pagerNode:(ASPagerNode *)pagerNode nodeAtIndex:(NSInteger)index -{ - return [[ASCellNode alloc] init]; -} - -@end - -@interface ASPagerNodeTestController: UIViewController -@property (nonatomic) ASPagerNodeTestDataSource *testDataSource; -@property (nonatomic) ASPagerNode *pagerNode; -@end - -@implementation ASPagerNodeTestController - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - // Populate these immediately so that they're not unexpectedly nil during tests. - self.testDataSource = [[ASPagerNodeTestDataSource alloc] init]; - - self.pagerNode = [[ASPagerNode alloc] init]; - self.pagerNode.dataSource = self.testDataSource; - - [self.view addSubnode:self.pagerNode]; - } - return self; -} - -@end - -@interface ASPagerNodeTests : XCTestCase -@property (nonatomic) ASPagerNode *pagerNode; - -@property (nonatomic) ASPagerNodeTestDataSource *testDataSource; -@end - -@implementation ASPagerNodeTests - -- (void)testPagerReturnsIndexOfPages -{ - ASPagerNodeTestController *testController = [self testController]; - - ASCellNode *cellNode = [testController.pagerNode nodeForPageAtIndex:0]; - - XCTAssertEqual([testController.pagerNode indexOfPageWithNode:cellNode], 0); -} - -- (void)testPagerReturnsNotFoundForCellThatDontExistInPager -{ - ASPagerNodeTestController *testController = [self testController]; - - ASCellNode *badNode = [[ASCellNode alloc] init]; - - XCTAssertEqual([testController.pagerNode indexOfPageWithNode:badNode], NSNotFound); -} - -- (void)testScrollPageToIndex -{ - ASPagerNodeTestController *testController = [self testController]; - testController.pagerNode.frame = CGRectMake(0, 0, 500, 500); - [testController.pagerNode scrollToPageAtIndex:1 animated:false]; - - XCTAssertEqual(testController.pagerNode.currentPageIndex, 1); -} - -- (ASPagerNodeTestController *)testController -{ - ASPagerNodeTestController *testController = [[ASPagerNodeTestController alloc] initWithNibName:nil bundle:nil]; - UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - [window makeKeyAndVisible]; - window.rootViewController = testController; - - [testController.pagerNode reloadData]; - [testController.pagerNode setNeedsLayout]; - - return testController; -} - -// Disabled due to flakiness https://github.com/facebook/AsyncDisplayKit/issues/2818 -- (void)DISABLED_testThatRootPagerNodeDoesGetTheRightInsetWhilePoppingBack -{ - UICollectionViewCell *cell = nil; - - UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - ASDisplayNode *node = [[ASDisplayNode alloc] init]; - node.automaticallyManagesSubnodes = YES; - - ASPagerNodeTestDataSource *dataSource = [[ASPagerNodeTestDataSource alloc] init]; - ASPagerNode *pagerNode = [[ASPagerNode alloc] init]; - pagerNode.dataSource = dataSource; - node.layoutSpecBlock = ^(ASDisplayNode *node, ASSizeRange constrainedSize){ - return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsZero child:pagerNode]; - }; - ASDKViewController *vc = [[ASDKViewController alloc] initWithNode:node]; - UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; - window.rootViewController = nav; - [window makeKeyAndVisible]; - [window layoutIfNeeded]; - - // Wait until view controller is visible - XCTestExpectation *e = [self expectationWithDescription:@"Transition completed"]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [e fulfill]; - }); - [self waitForExpectationsWithTimeout:2 handler:nil]; - - // Test initial values -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - cell = [pagerNode.view cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; -#pragma clang diagnostic pop - XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame)); - XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame)); - XCTAssertEqual(pagerNode.contentOffset.y, 0); - XCTAssertEqual(pagerNode.contentInset.top, 0); - - e = [self expectationWithDescription:@"Transition completed"]; - // Push another view controller - UIViewController *vc2 = [[UIViewController alloc] init]; - vc2.view.frame = nav.view.bounds; - vc2.view.backgroundColor = [UIColor blueColor]; - [nav pushViewController:vc2 animated:YES]; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.505 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [e fulfill]; - }); - [self waitForExpectationsWithTimeout:2 handler:nil]; - - // Pop view controller - e = [self expectationWithDescription:@"Transition completed"]; - [vc2.navigationController popViewControllerAnimated:YES]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.505 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - [e fulfill]; - }); - [self waitForExpectationsWithTimeout:2 handler:nil]; - - // Test values again after popping the view controller -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - cell = [pagerNode.view cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; -#pragma clang diagnostic pop - XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame)); - XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame)); - XCTAssertEqual(pagerNode.contentOffset.y, 0); - XCTAssertEqual(pagerNode.contentInset.top, 0); -} - -@end diff --git a/Tests/ASPhotosFrameworkImageRequestTests.mm b/Tests/ASPhotosFrameworkImageRequestTests.mm deleted file mode 100644 index fe1cb408b4..0000000000 --- a/Tests/ASPhotosFrameworkImageRequestTests.mm +++ /dev/null @@ -1,65 +0,0 @@ -// -// ASPhotosFrameworkImageRequestTests.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#if AS_USE_PHOTOS - -#import -#import - -static NSString *const kTestAssetID = @"testAssetID"; - -@interface ASPhotosFrameworkImageRequestTests : XCTestCase - -@end - -@implementation ASPhotosFrameworkImageRequestTests - -#pragma mark Example Data - -+ (ASPhotosFrameworkImageRequest *)exampleImageRequest -{ - ASPhotosFrameworkImageRequest *req = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:kTestAssetID]; - req.options.networkAccessAllowed = YES; - req.options.normalizedCropRect = CGRectMake(0.2, 0.1, 0.6, 0.8); - req.targetSize = CGSizeMake(1024, 1536); - req.contentMode = PHImageContentModeAspectFill; - req.options.version = PHImageRequestOptionsVersionOriginal; - req.options.resizeMode = PHImageRequestOptionsResizeModeFast; - return req; -} - -+ (NSURL *)urlForExampleImageRequest -{ - NSString *str = [NSString stringWithFormat:@"ph://%@?width=1024&height=1536&version=2&contentmode=1&network=1&resizemode=1&deliverymode=0&crop_x=0.2&crop_y=0.1&crop_w=0.6&crop_h=0.8", kTestAssetID]; - return [NSURL URLWithString:str]; -} - -#pragma mark Test cases - -- (void)testThatConvertingToURLWorks -{ - XCTAssertEqualObjects([self.class exampleImageRequest].url, [self.class urlForExampleImageRequest]); -} - -- (void)testThatParsingFromURLWorks -{ - NSURL *url = [self.class urlForExampleImageRequest]; - XCTAssertEqualObjects([ASPhotosFrameworkImageRequest requestWithURL:url], [self.class exampleImageRequest]); -} - -- (void)testThatCopyingWorks -{ - ASPhotosFrameworkImageRequest *example = [self.class exampleImageRequest]; - ASPhotosFrameworkImageRequest *copy = [[self.class exampleImageRequest] copy]; - XCTAssertEqualObjects(example, copy); -} - -@end - -#endif diff --git a/Tests/ASRunLoopQueueTests.mm b/Tests/ASRunLoopQueueTests.mm index 1396f90438..5a7ef086c4 100644 --- a/Tests/ASRunLoopQueueTests.mm +++ b/Tests/ASRunLoopQueueTests.mm @@ -169,11 +169,6 @@ - (void)testWeakQueueWithAllDeallocatedObjectsIsDrained - (void)testASCATransactionQueueDisable { - // Disable coalescing. - ASConfiguration *config = [[ASConfiguration alloc] init]; - config.experimentalFeatures = kNilOptions; - [ASConfigurationManager test_resetWithConfiguration:config]; - ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init]; QueueObject *object = [[QueueObject alloc] init]; XCTAssertFalse(object.queueObjectProcessed); @@ -183,19 +178,4 @@ - (void)testASCATransactionQueueDisable XCTAssertFalse(queue.enabled); } -- (void)testASCATransactionQueueProcess -{ - ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; - config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing; - [ASConfigurationManager test_resetWithConfiguration:config]; - - ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init]; - QueueObject *object = [[QueueObject alloc] init]; - [queue enqueue:object]; - XCTAssertFalse(object.queueObjectProcessed); - ASCATransactionQueueWait(queue); - XCTAssertTrue(object.queueObjectProcessed); - XCTAssertTrue(queue.enabled); -} - @end diff --git a/Tests/ASScrollNodeTests.mm b/Tests/ASScrollNodeTests.mm index 1f7e8c1760..88fe2540a4 100644 --- a/Tests/ASScrollNodeTests.mm +++ b/Tests/ASScrollNodeTests.mm @@ -179,11 +179,11 @@ - (void)testASScrollNodeAccessibility { node.accessibilityLabel = @"node"; [scrollNode addSubnode:node]; node.frame = CGRectMake(0,0,100,100); - ASTextNode2 *text = [[ASTextNode2 alloc] init]; + ASTextNode *text = [[ASTextNode alloc] init]; text.attributedText = [[NSAttributedString alloc] initWithString:@"text"]; [node addSubnode:text]; - ASTextNode2 *text2 = [[ASTextNode2 alloc] init]; + ASTextNode *text2 = [[ASTextNode alloc] init]; text2.attributedText = [[NSAttributedString alloc] initWithString:@"text2"]; [node addSubnode:text2]; __unused UIView *view = scrollNode.view; diff --git a/Tests/ASTabBarControllerTests.mm b/Tests/ASTabBarControllerTests.mm deleted file mode 100644 index b20c7a984d..0000000000 --- a/Tests/ASTabBarControllerTests.mm +++ /dev/null @@ -1,41 +0,0 @@ -// -// ASTabBarControllerTests.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -@interface ASTabBarControllerTests: XCTestCase - -@end - -@implementation ASTabBarControllerTests - -- (void)testTabBarControllerSelectIndex { - ASDKViewController *firstViewController = [ASDKViewController new]; - ASDKViewController *secondViewController = [ASDKViewController new]; - NSArray *viewControllers = @[firstViewController, secondViewController]; - ASTabBarController *tabBarController = [ASTabBarController new]; - [tabBarController setViewControllers:viewControllers]; - [tabBarController setSelectedIndex:1]; - XCTAssertTrue([tabBarController.viewControllers isEqualToArray:viewControllers]); - XCTAssertEqual(tabBarController.selectedViewController, secondViewController); -} - -- (void)testTabBarControllerSelectViewController { - ASDKViewController *firstViewController = [ASDKViewController new]; - ASDKViewController *secondViewController = [ASDKViewController new]; - NSArray *viewControllers = @[firstViewController, secondViewController]; - ASTabBarController *tabBarController = [ASTabBarController new]; - [tabBarController setViewControllers:viewControllers]; - [tabBarController setSelectedViewController:secondViewController]; - XCTAssertTrue([tabBarController.viewControllers isEqualToArray:viewControllers]); - XCTAssertEqual(tabBarController.selectedViewController, secondViewController); -} - -@end diff --git a/Tests/ASTableViewTests.mm b/Tests/ASTableViewTests.mm index 0d07d129be..9b841604e6 100644 --- a/Tests/ASTableViewTests.mm +++ b/Tests/ASTableViewTests.mm @@ -38,21 +38,29 @@ - (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)())invalidationB @end -@interface ASTestTableView : ASTableView -@property (nonatomic) void (^willDeallocBlock)(ASTableView *tableView); +@interface ASTestTableNode : ASTableNode +@property (nonatomic) void (^willDeallocBlock)(ASTableNode *tableNode); @end -@implementation ASTestTableView +@implementation ASTestTableNode - (instancetype)__initWithFrame:(CGRect)frame style:(UITableViewStyle)style { - return [super _initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] owningNode:nil]; + if (self = [super init]) { + __weak __typeof__(self) weakSelf = self; + [self setViewBlock:^{ + // Variable will be unused if event logging is off. + __unused __typeof__(self) strongSelf = weakSelf; + return [[ASTableView alloc] _initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] owningNode:strongSelf]; + }]; + } + return self; } - (ASTestDataController *)testDataController { - return (ASTestDataController *)self.dataController; + return (ASTestDataController *)self.view.dataController; } - (void)dealloc @@ -72,29 +80,20 @@ @interface ASTableViewTestDelegate : NSObject *)sectionIndexTitlesForTableView:(UITableView *)tableView { return @[ @"A", @"B", @"C" ]; } -- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index +- (NSInteger)tableNode:(ASTableNode *)tableNode sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { return 0; } @@ -222,13 +209,10 @@ @interface ASTableViewFilledDelegate : NSObject @implementation ASTableViewFilledDelegate -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (ASSizeRange)tableView:(ASTableView *)tableView constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath +- (ASSizeRange)tableNode:(ASTableNode *)tableNode constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath { return ASSizeRangeMake(CGSizeMake(10, 42)); } -#pragma clang diagnostic pop @end @@ -241,23 +225,20 @@ @implementation ASTableViewTests - (void)setUp { [super setUp]; - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalOptimizeDataControllerPipeline; - [ASConfigurationManager test_resetWithConfiguration:config]; } - (void)testDataSourceImplementsNecessaryMethods { - ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 400) + ASTestTableNode *tableNode = [[ASTestTableNode alloc] __initWithFrame:CGRectMake(0, 0, 100, 400) style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = (ASTableViewFilledDataSource *)[NSObject new]; - XCTAssertThrows((tableView.asyncDataSource = dataSource)); + XCTAssertThrows((tableNode.view.asyncDataSource = dataSource)); dataSource = [ASTableViewFilledDataSource new]; - XCTAssertNoThrow((tableView.asyncDataSource = dataSource)); + XCTAssertNoThrow((tableNode.view.asyncDataSource = dataSource)); } - (void)testConstrainedSizeForRowAtIndexPath @@ -265,27 +246,28 @@ - (void)testConstrainedSizeForRowAtIndexPath // Initial width of the table view is non-zero and all nodes are measured with this size. // Any subsequent size change must trigger a relayout. // Width and height are swapped so that a later size change will simulate a rotation - ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 400) + ASTestTableNode *tableNode = [[ASTestTableNode alloc] __initWithFrame:CGRectMake(0, 0, 100, 400) style:UITableViewStylePlain]; + tableNode.view.frame = CGRectMake(0, 0, 100, 400); ASTableViewFilledDelegate *delegate = [ASTableViewFilledDelegate new]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; - tableView.asyncDelegate = delegate; - tableView.asyncDataSource = dataSource; + tableNode.view.asyncDelegate = delegate; + tableNode.view.asyncDataSource = dataSource; - [tableView reloadData]; - [tableView waitUntilAllUpdatesAreCommitted]; - [tableView setNeedsLayout]; - [tableView layoutIfNeeded]; + [tableNode.view reloadData]; + [tableNode.view waitUntilAllUpdatesAreCommitted]; + [tableNode.view setNeedsLayout]; + [tableNode.view layoutIfNeeded]; CGFloat separatorHeight = 1.0 / ASScreenScale(); for (int section = 0; section < NumberOfSections; section++) { - for (int row = 0; row < [tableView numberOfRowsInSection:section]; row++) { + for (int row = 0; row < [tableNode.view numberOfRowsInSection:section]; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; - CGRect rect = [tableView rectForRowAtIndexPath:indexPath]; + CGRect rect = [tableNode.view rectForRowAtIndexPath:indexPath]; XCTAssertEqual(rect.size.width, 100); // specified width should be ignored for table - XCTAssertEqual(rect.size.height, 42 + separatorHeight); + XCTAssertEqualWithAccuracy(rect.size.height, 42 + separatorHeight, 0.1); } } } @@ -293,11 +275,11 @@ - (void)testConstrainedSizeForRowAtIndexPath // TODO: Convert this to ARC. - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate { - ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectZero style:UITableViewStylePlain]; + ASTestTableNode *tableNode = [[ASTestTableNode alloc] __initWithFrame:CGRectZero style:UITableViewStylePlain]; - __block BOOL tableViewDidDealloc = NO; - tableView.willDeallocBlock = ^(ASTableView *v){ - tableViewDidDealloc = YES; + __block BOOL tableNodeDidDealloc = NO; + tableNode.willDeallocBlock = ^(ASTableNode *v){ + tableNodeDidDealloc = YES; }; ASTableViewTestDelegate *delegate = [[ASTableViewTestDelegate alloc] init]; @@ -307,14 +289,14 @@ - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate delegateDidDealloc = YES; }; - tableView.asyncDataSource = delegate; - tableView.asyncDelegate = delegate; + tableNode.dataSource = delegate; + tableNode.delegate = delegate; // [delegate release]; XCTAssertTrue(delegateDidDealloc, @"unexpected delegate lifetime:%@", delegate); // XCTAssertNoThrow([tableView release], @"unexpected exception when deallocating table view:%@", tableView); - XCTAssertTrue(tableViewDidDealloc, @"unexpected table view lifetime:%@", tableView); + XCTAssertTrue(tableNodeDidDealloc, @"unexpected table view lifetime:%@", tableNode); } - (NSIndexSet *)randomIndexSet @@ -346,17 +328,17 @@ - (NSArray *)randomIndexPathsExisting:(BOOL)existing rowCount:(NSInteger)rowCoun - (void)DISABLED_testReloadData { // Keep the viewport moderately sized so that new cells are loaded on scrolling - ASTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 500) + ASTableNode *tableNode = [[ASTestTableNode alloc] __initWithFrame:CGRectMake(0, 0, 100, 500) style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; - tableView.asyncDelegate = dataSource; - tableView.asyncDataSource = dataSource; + tableNode.delegate = dataSource; + tableNode.dataSource = dataSource; XCTestExpectation *reloadDataExpectation = [self expectationWithDescription:@"reloadData"]; - [tableView reloadDataWithCompletion:^{ + [tableNode reloadDataWithCompletion:^{ NSLog(@"*** Reload Complete ***"); [reloadDataExpectation fulfill]; }]; @@ -378,20 +360,20 @@ - (void)DISABLED_testReloadData //NSLog(@"Iteration %03d: %@|%@|%@|%@|%g", i, (rowAnimation == UITableViewRowAnimationNone) ? @"NONE " : @"MIDDLE", animatedScroll ? @"ASCR" : @" ", reloadRowsInsteadOfSections ? @"ROWS" : @"SECS", useBeginEndUpdates ? @"BEGEND" : @" ", runLoopDelay); if (useBeginEndUpdates) { - [tableView beginUpdates]; + [tableNode.view beginUpdates]; } if (reloadRowsInsteadOfSections) { NSArray *indexPaths = [self randomIndexPathsExisting:YES rowCount:dataSource.rowsPerSection]; //NSLog(@"reloading rows: %@", indexPaths); - [tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:rowAnimation]; + [tableNode reloadRowsAtIndexPaths:indexPaths withRowAnimation:rowAnimation]; } else { NSIndexSet *sections = [self randomIndexSet]; //NSLog(@"reloading sections: %@", sections); - [tableView reloadSections:sections withRowAnimation:rowAnimation]; + [tableNode reloadSections:sections withRowAnimation:rowAnimation]; } - [tableView setContentOffset:CGPointMake(0, arc4random_uniform(tableView.contentSize.height - tableView.bounds.size.height)) animated:animatedScroll]; + [tableNode setContentOffset:CGPointMake(0, arc4random_uniform(tableNode.view.contentSize.height - tableNode.bounds.size.height)) animated:animatedScroll]; if (runLoopDelay > 0) { // Run other stuff on the main queue for between 2ms and 1000ms. @@ -399,7 +381,7 @@ - (void)DISABLED_testReloadData } if (useBeginEndUpdates) { - [tableView endUpdates]; + [tableNode.view endUpdates]; } } } @@ -410,13 +392,13 @@ - (void)testRelayoutAllNodesWithNonZeroSizeInitially // Any subsequence size change must trigger a relayout. CGSize tableViewFinalSize = CGSizeMake(100, 500); // Width and height are swapped so that a later size change will simulate a rotation - ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width) + ASTestTableNode *tableView = [[ASTestTableNode alloc] __initWithFrame:CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width) style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; - tableView.asyncDelegate = dataSource; - tableView.asyncDataSource = dataSource; + tableView.delegate = dataSource; + tableView.dataSource = dataSource; [tableView layoutIfNeeded]; @@ -427,7 +409,7 @@ - (void)testRelayoutAllNodesWithNonZeroSizeInitially - (void)testRelayoutVisibleRowsWhenEditingModeIsChanged { CGSize tableViewSize = CGSizeMake(100, 500); - ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) + ASTestTableNode *tableNode = [[ASTestTableNode alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; // Currently this test requires that the text in the cell node fills the @@ -439,25 +421,25 @@ - (void)testRelayoutVisibleRowsWhenEditingModeIsChanged return textCellNode; }; }; - tableView.asyncDelegate = dataSource; - tableView.asyncDataSource = dataSource; + tableNode.view.asyncDelegate = dataSource; + tableNode.view.asyncDataSource = dataSource; - [self triggerFirstLayoutMeasurementForTableView:tableView]; + [self triggerFirstLayoutMeasurementForTableView:tableNode.view]; - NSArray *visibleNodes = [tableView visibleNodes]; + NSArray *visibleNodes = [tableNode.view visibleNodes]; XCTAssertGreaterThan(visibleNodes.count, 0); // Cause table view to enter editing mode. // Visibile nodes should be re-measured on main thread with the new (smaller) content view width. // Other nodes are untouched. XCTestExpectation *relayoutAfterEnablingEditingExpectation = [self expectationWithDescription:@"relayoutAfterEnablingEditing"]; - [tableView beginUpdates]; - [tableView setEditing:YES]; - [tableView endUpdatesAnimated:YES completion:^(BOOL completed) { + [tableNode.view beginUpdates]; + [tableNode.view setEditing:YES]; + [tableNode.view endUpdatesAnimated:YES completion:^(BOOL completed) { for (int section = 0; section < NumberOfSections; section++) { for (int row = 0; row < dataSource.rowsPerSection; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; - ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath]; + ASTestTextCellNode *node = (ASTestTextCellNode *)[tableNode.view nodeForRowAtIndexPath:indexPath]; if ([visibleNodes containsObject:node]) { XCTAssertEqual(node.numberOfLayoutsOnMainThread, 1); XCTAssertLessThan(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width); @@ -479,13 +461,13 @@ - (void)testRelayoutVisibleRowsWhenEditingModeIsChanged // Visibile nodes should be re-measured again. // All nodes should have max constrained width equals to the table view width. XCTestExpectation *relayoutAfterDisablingEditingExpectation = [self expectationWithDescription:@"relayoutAfterDisablingEditing"]; - [tableView beginUpdates]; - [tableView setEditing:NO]; - [tableView endUpdatesAnimated:YES completion:^(BOOL completed) { + [tableNode.view beginUpdates]; + [tableNode.view setEditing:NO]; + [tableNode.view endUpdatesAnimated:YES completion:^(BOOL completed) { for (int section = 0; section < NumberOfSections; section++) { for (int row = 0; row < dataSource.rowsPerSection; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; - ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath]; + ASTestTextCellNode *node = (ASTestTextCellNode *)[tableNode.view nodeForRowAtIndexPath:indexPath]; BOOL visible = [visibleNodes containsObject:node]; XCTAssertEqual(node.numberOfLayoutsOnMainThread, visible ? 2: 0); XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width); @@ -503,24 +485,24 @@ - (void)testRelayoutVisibleRowsWhenEditingModeIsChanged - (void)DISABLED_testRelayoutRowsAfterEditingModeIsChangedAndTheyBecomeVisible { CGSize tableViewSize = CGSizeMake(100, 500); - ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) + ASTestTableNode *tableNode = [[ASTestTableNode alloc] __initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) style:UITableViewStylePlain]; ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; - tableView.asyncDelegate = dataSource; - tableView.asyncDataSource = dataSource; + tableNode.delegate = dataSource; + tableNode.dataSource = dataSource; - [self triggerFirstLayoutMeasurementForTableView:tableView]; + [self triggerFirstLayoutMeasurementForTableView:tableNode.view]; // Cause table view to enter editing mode and then scroll to the bottom. // The last node should be re-measured on main thread with the new (smaller) content view width. NSIndexPath *lastRowIndexPath = [NSIndexPath indexPathForRow:(dataSource.rowsPerSection - 1) inSection:(NumberOfSections - 1)]; XCTestExpectation *relayoutExpectation = [self expectationWithDescription:@"relayout"]; - [tableView beginUpdates]; - [tableView setEditing:YES]; - [tableView setContentOffset:CGPointMake(0, CGFLOAT_MAX) animated:YES]; - [tableView endUpdatesAnimated:YES completion:^(BOOL completed) { - ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:lastRowIndexPath]; + [tableNode.view beginUpdates]; + [tableNode.view setEditing:YES]; + [tableNode setContentOffset:CGPointMake(0, CGFLOAT_MAX) animated:YES]; + [tableNode.view endUpdatesAnimated:YES completion:^(BOOL completed) { + ASTestTextCellNode *node = (ASTestTextCellNode *)[tableNode nodeForRowAtIndexPath:lastRowIndexPath]; XCTAssertEqual(node.numberOfLayoutsOnMainThread, 1); XCTAssertLessThan(node.constrainedSizeForCalculatedLayout.max.width, tableViewSize.width); [relayoutExpectation fulfill]; @@ -535,19 +517,19 @@ - (void)DISABLED_testRelayoutRowsAfterEditingModeIsChangedAndTheyBecomeVisible - (void)testIndexPathForNode { CGSize tableViewSize = CGSizeMake(100, 500); - ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) - style:UITableViewStylePlain]; + ASTestTableNode *tableNode = [[ASTestTableNode alloc] initWithStyle:UITableViewStylePlain]; + tableNode.frame = CGRectMake(0, 0, tableViewSize.width, tableViewSize.height); ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; - tableView.asyncDelegate = dataSource; - tableView.asyncDataSource = dataSource; + tableNode.delegate = dataSource; + tableNode.dataSource = dataSource; - [tableView reloadDataWithCompletion:^{ + [tableNode reloadDataWithCompletion:^{ for (NSUInteger i = 0; i < NumberOfSections; i++) { for (NSUInteger j = 0; j < dataSource.rowsPerSection; j++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:j inSection:i]; - ASCellNode *cellNode = [tableView nodeForRowAtIndexPath:indexPath]; - NSIndexPath *reportedIndexPath = [tableView indexPathForNode:cellNode]; + ASCellNode *cellNode = [tableNode nodeForRowAtIndexPath:indexPath]; + NSIndexPath *reportedIndexPath = [tableNode indexPathForNode:cellNode]; XCTAssertEqual(indexPath.row, reportedIndexPath.row); } } @@ -578,24 +560,24 @@ - (void)triggerFirstLayoutMeasurementForTableView:(ASTableView *)tableView{ [tableView waitUntilAllUpdatesAreCommitted]; } -- (void)triggerSizeChangeAndAssertRelayoutAllNodesForTableView:(ASTestTableView *)tableView newSize:(CGSize)newSize +- (void)triggerSizeChangeAndAssertRelayoutAllNodesForTableView:(ASTestTableNode *)tableNode newSize:(CGSize)newSize { XCTestExpectation *nodesMeasuredUsingNewConstrainedSizeExpectation = [self expectationWithDescription:@"nodesMeasuredUsingNewConstrainedSize"]; - [tableView beginUpdates]; + [tableNode.view beginUpdates]; - CGRect frame = tableView.frame; + CGRect frame = tableNode.frame; frame.size = newSize; - tableView.frame = frame; - [tableView layoutIfNeeded]; + tableNode.frame = frame; + [tableNode layoutIfNeeded]; - [tableView endUpdatesAnimated:NO completion:^(BOOL completed) { - XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 1); + [tableNode.view endUpdatesAnimated:NO completion:^(BOOL completed) { + XCTAssertEqual(tableNode.testDataController.numberOfAllNodesRelayouts, 1); for (int section = 0; section < NumberOfSections; section++) { - for (int row = 0; row < [tableView numberOfRowsInSection:section]; row++) { + for (int row = 0; row < [tableNode numberOfRowsInSection:section]; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; - ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath:indexPath]; + ASTestTextCellNode *node = (ASTestTextCellNode *)[tableNode nodeForRowAtIndexPath:indexPath]; XCTAssertLessThanOrEqual(node.numberOfLayoutsOnMainThread, 1); XCTAssertEqual(node.constrainedSizeForCalculatedLayout.max.width, newSize.width); } @@ -737,7 +719,7 @@ - (void)testSectionIndexHandling CGFloat cellWidth = cell.contentView.frame.size.width; XCTAssert(cellWidth > 0 && cellWidth < 320, @"Expected cell width to be about 305. Width: %@", @(cellWidth)); - ASSizeRange expectedSizeRange = ASSizeRangeMake(CGSizeMake(cellWidth, 0)); + ASSizeRange expectedSizeRange = ASSizeRangeMake(CGSizeMake(320, 0)); expectedSizeRange.max.height = CGFLOAT_MAX; for (NSInteger i = 0; i < node.numberOfSections; i++) { @@ -776,7 +758,8 @@ - (void)testIssue2252 ASTableViewFilledDataSource *ds = [[ASTableViewFilledDataSource alloc] init]; ds.rowsPerSection = 1; node.dataSource = ds; - ASDKViewController *vc = [[ASDKViewController alloc] initWithNode:node]; + UIViewController *vc = [[UIViewController alloc] init]; + [vc.view addSubnode:node]; UITabBarController *tabCtrl = [[UITabBarController alloc] init]; tabCtrl.viewControllers = @[ vc ]; tabCtrl.tabBar.translucent = NO; @@ -909,17 +892,17 @@ - (void)testTintColorIsPropagatedToTableViewCell { // If a tint color is explicitly defined on an ASCellNode, we should CGSize tableViewSize = CGSizeMake(100, 500); - ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) - style:UITableViewStylePlain]; + ASTestTableNode *tableNode = [[ASTestTableNode alloc] initWithStyle:UITableViewStylePlain]; + tableNode.view.frame = CGRectMake(0, 0, tableViewSize.width, tableViewSize.height); ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; - tableView.asyncDelegate = dataSource; - tableView.asyncDataSource = dataSource; + tableNode.view.asyncDelegate = dataSource; + tableNode.view.asyncDataSource = dataSource; - [tableView reloadData]; - [tableView waitUntilAllUpdatesAreCommitted]; + [tableNode.view reloadData]; + [tableNode.view waitUntilAllUpdatesAreCommitted]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - UITableViewCell *uikitCell = [tableView cellForRowAtIndexPath:indexPath]; + UITableViewCell *uikitCell = [tableNode.view cellForRowAtIndexPath:indexPath]; BOOL areColorsEqual = CGColorEqualToColor(uikitCell.tintColor.CGColor, UIColor.yellowColor.CGColor); XCTAssertTrue(areColorsEqual); } diff --git a/Tests/ASTableViewThrashTests.mm b/Tests/ASTableViewThrashTests.mm index 695bf67ff9..e05aef48fa 100644 --- a/Tests/ASTableViewThrashTests.mm +++ b/Tests/ASTableViewThrashTests.mm @@ -31,9 +31,6 @@ @implementation ASTableViewThrashTests - (void)setUp { [super setUp]; - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalOptimizeDataControllerPipeline; - [ASConfigurationManager test_resetWithConfiguration:config]; } - (void)tearDown @@ -46,13 +43,6 @@ - (void)tearDown _update = nil; } -// NOTE: Despite the documentation, this is not always called if an exception is caught. -- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected -{ - _failed = YES; - [super recordFailureWithDescription:description inFile:filePath atLine:lineNumber expected:expected]; -} - #pragma mark Test Methods // Disabled temporarily due to issue where cell nodes are not marked invisible before deallocation. diff --git a/Tests/ASTextKitCoreTextAdditionsTests.mm b/Tests/ASTextKitCoreTextAdditionsTests.mm index 8b0c47be8d..09114a4adb 100644 --- a/Tests/ASTextKitCoreTextAdditionsTests.mm +++ b/Tests/ASTextKitCoreTextAdditionsTests.mm @@ -13,8 +13,6 @@ #import -#if AS_ENABLE_TEXTNODE - BOOL floatsCloseEnough(CGFloat float1, CGFloat float2) { CGFloat epsilon = 0.00001; return (fabs(float1 - float2) < epsilon); @@ -70,5 +68,3 @@ - (void)testNSParagraphStyleNoCleansing } @end - -#endif diff --git a/Tests/ASTextKitFontSizeAdjusterTests.mm b/Tests/ASTextKitFontSizeAdjusterTests.mm index c91ff99a49..2f9cc0157f 100644 --- a/Tests/ASTextKitFontSizeAdjusterTests.mm +++ b/Tests/ASTextKitFontSizeAdjusterTests.mm @@ -9,8 +9,6 @@ #import #import -#if AS_ENABLE_TEXTNODE - @interface ASFontSizeAdjusterTests : XCTestCase @end @@ -47,5 +45,3 @@ - (void)testFontSizeAdjusterAttributes } @end - -#endif diff --git a/Tests/ASTextKitTests.mm b/Tests/ASTextKitTests.mm index 0f1a3ec723..b417dce944 100644 --- a/Tests/ASTextKitTests.mm +++ b/Tests/ASTextKitTests.mm @@ -17,8 +17,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import #import #import @@ -227,5 +225,3 @@ - (void)testTextKitComponentsCanCalculateSizeInBackground } @end - -#endif diff --git a/Tests/ASTextKitTruncationTests.mm b/Tests/ASTextKitTruncationTests.mm index 1e19c8d0f2..103bdcc8dc 100644 --- a/Tests/ASTextKitTruncationTests.mm +++ b/Tests/ASTextKitTruncationTests.mm @@ -12,8 +12,6 @@ #import -#if AS_ENABLE_TEXTNODE - #import @interface ASTextKitTruncationTests : XCTestCase @@ -167,5 +165,3 @@ - (void)testHandleZeroHeightConstrainedSize } @end - -#endif diff --git a/Tests/ASTextNode2SnapshotTests.mm b/Tests/ASTextNode2SnapshotTests.mm deleted file mode 100644 index 0cda2ce451..0000000000 --- a/Tests/ASTextNode2SnapshotTests.mm +++ /dev/null @@ -1,357 +0,0 @@ -// -// ASTextNode2SnapshotTests.mm -// Texture -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - - -#import "ASTestCase.h" -#import "ASSnapshotTestCase.h" -#import - -@interface ASTextNode2SnapshotTests : ASSnapshotTestCase - -@end - -@interface LineBreakConfig : NSObject - -@property (nonatomic, assign) NSUInteger numberOfLines; -@property (nonatomic, assign) NSLineBreakMode lineBreakMode; - -+ (NSArray *)configs; - -- (instancetype)initWithNumberOfLines:(NSUInteger)numberOfLines lineBreakMode:(NSLineBreakMode)lineBreakMode; -- (NSString *)breakModeDescription; - -@end - -@implementation LineBreakConfig - -+ (NSArray *)configs -{ - static dispatch_once_t init_predicate; - static NSArray *allConfigs = nil; - - dispatch_once(&init_predicate, ^{ - NSMutableArray *setup = [NSMutableArray new]; - for (int i = 0; i <= 3; i++) { - for (int j = NSLineBreakByWordWrapping; j <= NSLineBreakByTruncatingMiddle; j++) { - if (j == NSLineBreakByClipping) continue; - [setup addObject:[[LineBreakConfig alloc] initWithNumberOfLines:i lineBreakMode:(NSLineBreakMode) j]]; - } - - allConfigs = [NSArray arrayWithArray:setup]; - } - }); - return allConfigs; -} - -- (instancetype)initWithNumberOfLines:(NSUInteger)numberOfLines lineBreakMode:(NSLineBreakMode)lineBreakMode -{ - self = [super init]; - if (self != nil) { - _numberOfLines = numberOfLines; - _lineBreakMode = lineBreakMode; - - return self; - } - return nil; -} - -- (NSString *)breakModeDescription { - NSString *lineBreak = nil; - switch (self.lineBreakMode) { - case NSLineBreakByTruncatingHead: - lineBreak = @"NSLineBreakByTruncatingHead"; - break; - case NSLineBreakByCharWrapping: - lineBreak = @"NSLineBreakByCharWrapping"; - break; - case NSLineBreakByClipping: - lineBreak = @"NSLineBreakByClipping"; - break; - case NSLineBreakByWordWrapping: - lineBreak = @"NSLineBreakByWordWrapping"; - break; - case NSLineBreakByTruncatingTail: - lineBreak = @"NSLineBreakByTruncatingTail"; - break; - case NSLineBreakByTruncatingMiddle: - lineBreak = @"NSLineBreakByTruncatingMiddle"; - break; - default: - lineBreak = @"Unknown?"; - break; - } - return lineBreak; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"numberOfLines: %lu\nlineBreakMode: %@", (unsigned long) self.numberOfLines, [self breakModeDescription]]; -} - -@end - -@implementation ASTextNode2SnapshotTests - -- (void)setUp -{ - [super setUp]; - - // This will use ASTextNode2 for snapshot tests. - // All tests are duplicated from ASTextNodeSnapshotTests. - ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; -#if AS_ENABLE_TEXTNODE - config.experimentalFeatures = ASExperimentalTextNode; -#endif - [ASConfigurationManager test_resetWithConfiguration:config]; - - self.recordMode = NO; -} - -- (void)tearDown -{ - [super tearDown]; - ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; - config.experimentalFeatures = kNilOptions; - [ASConfigurationManager test_resetWithConfiguration:config]; -} - -- (void)testTextContainerInset_ASTextNode2 -{ - // trivial test case to ensure ASSnapshotTestCase works - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar" - attributes:@{NSFontAttributeName: [UIFont italicSystemFontOfSize:24]}]; - textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2); - ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); - - ASSnapshotVerifyNode(textNode, nil); -} - -- (void)testTextTruncationModes_ASTextNode2 -{ - UIView *container = [[UIView alloc] initWithFrame:(CGRect) {CGPointZero, (CGSize) {375.0f, 667.0f}}]; - - UILabel *textNodeLabel = [[UILabel alloc] init]; - UILabel *uiLabelLabel = [[UILabel alloc] init]; - UILabel *description = [[UILabel alloc] init]; - textNodeLabel.text = @"ASTextNode2:"; - textNodeLabel.font = [UIFont boldSystemFontOfSize:16.0]; - textNodeLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0]; - uiLabelLabel.text = @"UILabel:"; - uiLabelLabel.font = [UIFont boldSystemFontOfSize:16.0]; - uiLabelLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0]; - - description.text = @""; - description.font = [UIFont italicSystemFontOfSize:16.0]; - description.numberOfLines = 0; - - uiLabelLabel.textColor = [UIColor colorWithRed:0.0 green:0.7 blue:0.0 alpha:1.0]; - - UILabel *reference = [[UILabel alloc] init]; - ASTextNode *textNode = [[ASTextNode alloc] init]; // ASTextNode2 - - NSMutableAttributedString *refString = [[NSMutableAttributedString alloc] initWithString:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." - attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:18.0f] }]; - NSMutableAttributedString *asString = [refString mutableCopy]; - - reference.attributedText = refString; - textNode.attributedText = asString; - - CGSize size = (CGSize) {container.bounds.size.width, 120.0}; - CGPoint origin = (CGPoint) {CGRectGetWidth(container.bounds) / 2 - size.width / 2, CGRectGetHeight(container.bounds) / 2 - size.height / 2}; // center - - textNode.frame = (CGRect) {origin, size}; - reference.frame = CGRectOffset(textNode.frame, 0, -160.0f); - - textNodeLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, textNodeLabel.font.lineHeight}}; - origin = (CGPoint) {textNode.frame.origin.x, textNode.frame.origin.y - textNodeLabel.bounds.size.height}; - textNodeLabel.frame = (CGRect) {origin, textNodeLabel.bounds.size}; - - uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}}; - origin = (CGPoint) {reference.frame.origin.x, reference.frame.origin.y - uiLabelLabel.bounds.size.height}; - uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size}; - - uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}}; - origin = (CGPoint) {textNode.frame.origin.x, textNode.frame.origin.y - uiLabelLabel.bounds.size.height}; - uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size}; - - uiLabelLabel.bounds = (CGRect) {CGPointZero, (CGSize) {container.bounds.size.width, uiLabelLabel.font.lineHeight}}; - origin = (CGPoint) {reference.frame.origin.x, reference.frame.origin.y - uiLabelLabel.bounds.size.height}; - uiLabelLabel.frame = (CGRect) {origin, uiLabelLabel.bounds.size}; - - description.bounds = textNode.bounds; - description.frame = (CGRect) {(CGPoint) {0, container.bounds.size.height * 0.8}, description.bounds.size}; - - [container addSubview:reference]; - [container addSubview:textNode.view]; - [container addSubview:textNodeLabel]; - [container addSubview:uiLabelLabel]; - [container addSubview:description]; - - NSArray *c = [LineBreakConfig configs]; - for (LineBreakConfig *config in c) { - reference.lineBreakMode = textNode.truncationMode = config.lineBreakMode; - reference.numberOfLines = textNode.maximumNumberOfLines = config.numberOfLines; - description.text = config.description; - [container setNeedsLayout]; - NSString *identifier = [NSString stringWithFormat:@"%@_%luLines", [config breakModeDescription], (unsigned long)config.numberOfLines]; - [ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode]; - ASSnapshotVerifyViewWithTolerance(container, identifier, 0.01); - } -} - -- (void)testTextContainerInsetIsIncludedWithSmallerConstrainedSize_ASTextNode2 -{ - UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; - backgroundView.layer.as_allowsHighlightDrawing = YES; - - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar judar judar judar judar judar" - attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }]; - - textNode.textContainerInset = UIEdgeInsetsMake(10, 10, 10, 10); - - ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 80))]; - textNode.frame = CGRectMake(50, 50, layout.size.width, layout.size.height); - - [backgroundView addSubview:textNode.view]; - backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50)); - - textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length); - - [ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode]; - ASSnapshotVerifyLayer(backgroundView.layer, nil); -} - -- (void)testTextContainerInsetHighlight_ASTextNode2 -{ - UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; - backgroundView.layer.as_allowsHighlightDrawing = YES; - - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"yolo" - attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }]; - - textNode.textContainerInset = UIEdgeInsetsMake(5, 10, 10, 5); - ASLayout *layout = [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))]; - textNode.frame = CGRectMake(50, 50, layout.size.width, layout.size.height); - - [backgroundView addSubview:textNode.view]; - backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50)); - - textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length); - - [ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode]; - ASSnapshotVerifyView(backgroundView, nil); -} - -// This test is disabled because the fast-path is disabled. -- (void)DISABLED_testThatFastPathTruncationWorks_ASTextNode2 -{ - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important" attributes:@{ NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont italicSystemFontOfSize:24] }]; - [textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 50))]; - ASSnapshotVerifyNode(textNode, nil); -} - -- (void)testThatSlowPathTruncationWorks_ASTextNode2 -{ - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important" attributes:@{ NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont italicSystemFontOfSize:24] }]; - // Set exclusion paths to trigger slow path - textNode.exclusionPaths = @[ [UIBezierPath bezierPath] ]; - ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 50))); - ASSnapshotVerifyNode(textNode, nil); -} - -- (void)testShadowing_ASTextNode2 -{ - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important"]; - textNode.shadowColor = [UIColor blackColor].CGColor; - textNode.shadowOpacity = 0.3; - textNode.shadowRadius = 3; - textNode.shadowOffset = CGSizeMake(0, 1); - ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); - ASSnapshotVerifyNode(textNode, nil); -} - -/** - * https://github.com/TextureGroup/Texture/issues/822 - */ -- (void)DISABLED_testThatTruncationTokenAttributesPrecedeThoseInheritedFromTextWhenTruncateTailMode_ASTextNode2 -{ - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.style.maxSize = CGSizeMake(20, 80); - NSMutableAttributedString *mas = [[NSMutableAttributedString alloc] initWithString:@"Quality is an important "]; - [mas appendAttributedString:[[NSAttributedString alloc] initWithString:@"thing" attributes:@{ NSBackgroundColorAttributeName : UIColor.yellowColor}]]; - textNode.attributedText = mas; - textNode.truncationMode = NSLineBreakByTruncatingTail; - - textNode.truncationAttributedText = [[NSAttributedString alloc] initWithString:@"\u2026" attributes:@{ NSBackgroundColorAttributeName: UIColor.greenColor }]; - ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); - ASSnapshotVerifyNode(textNode, nil); -} - -- (void)testThatDefaultTruncationTokenAttributesAreInheritedFromTextWhenTruncated_ASTextNode2 -{ - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.style.maxSize = CGSizeMake(20, 80); - - // Set initial attributed text - textNode.attributedText = [[NSMutableAttributedString alloc] initWithString:@"Quality is an important thing" attributes:@{NSForegroundColorAttributeName : [UIColor greenColor]}]; - - // Trigger sizing for internal composed truncation text creation - ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); - - // Change attributed text with different foreground color - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is an important thing" attributes:@{NSForegroundColorAttributeName : [UIColor grayColor]}]; - - // Size again and verify snapshot - ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); - ASSnapshotVerifyNode(textNode, nil); -} - -- (void)testTextTintColor_ASTextNode2 -{ - // trivial test case to ensure ASSnapshotTestCase works - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar" - attributes:@{NSFontAttributeName: [UIFont italicSystemFontOfSize:24]}]; - textNode.textColorFollowsTintColor = YES; - textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2); - textNode.tintColor = UIColor.redColor; - ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); - - ASSnapshotVerifyNode(textNode, nil); -} - - -- (void)testTintColorHierarchyChange -{ - ASDisplayNode *containerNode = [[ASDisplayNode alloc] init]; - containerNode.tintColor = [UIColor greenColor]; - - ASTextNode *node = [[ASTextNode alloc] init]; - [containerNode addSubnode:node]; - [node setLayerBacked:YES]; - node.textColorFollowsTintColor = YES; - node.attributedText = [[NSAttributedString alloc] initWithString:@"Hello" attributes:@{}]; - ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); - containerNode.style.preferredSize = node.bounds.size; - ASSnapshotVerifyNode(node, @"green_tint_from_parent"); - - ASDisplayNode *containerNode2 = [[ASDisplayNode alloc] init]; - containerNode2.tintColor = [UIColor redColor]; - [containerNode2 addSubnode:node]; - containerNode2.style.preferredSize = node.bounds.size; - ASSnapshotVerifyNode(node, @"red_tint_from_parent"); -} - - -@end diff --git a/Tests/ASTextNode2Tests.mm b/Tests/ASTextNode2Tests.mm deleted file mode 100644 index 70d25cb26c..0000000000 --- a/Tests/ASTextNode2Tests.mm +++ /dev/null @@ -1,133 +0,0 @@ -// -// ASTextNode2Tests.mm -// TextureTests -// -// Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import - -#import -#import -#import - -#import "ASTestCase.h" - -@interface ASTextNode2Tests : XCTestCase - -@property(nonatomic) ASTextNode2 *textNode; -@property(nonatomic, copy) NSAttributedString *attributedText; - -@end - -@implementation ASTextNode2Tests - -- (void)setUp -{ - [super setUp]; - _textNode = [[ASTextNode2 alloc] init]; - - UIFontDescriptor *desc = [UIFontDescriptor fontDescriptorWithName:@"Didot" size:18]; - NSArray *arr = @[ @{ - UIFontFeatureTypeIdentifierKey : @(kLetterCaseType), - UIFontFeatureSelectorIdentifierKey : @(kSmallCapsSelector) - } ]; - desc = [desc fontDescriptorByAddingAttributes:@{UIFontDescriptorFeatureSettingsAttribute : arr}]; - UIFont *f = [UIFont fontWithDescriptor:desc size:0]; - NSDictionary *d = @{NSFontAttributeName : f}; - NSMutableAttributedString *mas = [[NSMutableAttributedString alloc] - initWithString: - @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor " - @"incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " - @"exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " - @"dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " - @"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " - @"mollit anim id est laborum." - attributes:d]; - NSMutableParagraphStyle *para = [NSMutableParagraphStyle new]; - para.alignment = NSTextAlignmentCenter; - para.lineSpacing = 1.0; - [mas addAttribute:NSParagraphStyleAttributeName value:para range:NSMakeRange(0, mas.length - 1)]; - - // Vary the linespacing on the last line - NSMutableParagraphStyle *lastLinePara = [NSMutableParagraphStyle new]; - lastLinePara.alignment = para.alignment; - lastLinePara.lineSpacing = 5.0; - [mas addAttribute:NSParagraphStyleAttributeName - value:lastLinePara - range:NSMakeRange(mas.length - 1, 1)]; - - _attributedText = mas; - _textNode.attributedText = _attributedText; -} - -- (void)testTruncation -{ - XCTAssertTrue([(ASTextNode *)_textNode shouldTruncateForConstrainedSize:ASSizeRangeMake(CGSizeMake(100, 100))], @"Text Node should truncate"); - - _textNode.frame = CGRectMake(0, 0, 100, 100); - XCTAssertTrue(_textNode.isTruncated, @"Text Node should be truncated"); -} - -- (void)testAccessibility -{ - XCTAssertTrue(_textNode.isAccessibilityElement, @"Should be an accessibility element"); - XCTAssertTrue(_textNode.accessibilityTraits == UIAccessibilityTraitStaticText, - @"Should have static text accessibility trait, instead has %llu", - _textNode.accessibilityTraits); - XCTAssertTrue(_textNode.defaultAccessibilityTraits == UIAccessibilityTraitStaticText, - @"Default accessibility traits should return static text accessibility trait, " - @"instead returns %llu", - _textNode.defaultAccessibilityTraits); - - XCTAssertTrue([_textNode.accessibilityLabel isEqualToString:_attributedText.string], - @"Accessibility label is incorrectly set to \n%@\n when it should be \n%@\n", - _textNode.accessibilityLabel, _attributedText.string); - XCTAssertTrue([_textNode.defaultAccessibilityLabel isEqualToString:_attributedText.string], - @"Default accessibility label incorrectly returns \n%@\n when it should be \n%@\n", - _textNode.defaultAccessibilityLabel, _attributedText.string); -} - -- (void)testRespectingAccessibilitySetting -{ - ASTextNode2 *textNode = [[ASTextNode2 alloc] init]; - textNode.attributedText = _attributedText; - textNode.isAccessibilityElement = NO; - - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"new string"]; - XCTAssertFalse(textNode.isAccessibilityElement); - - // Ensure removing string on an accessible text node updates the setting. - ASTextNode2 *accessibleTextNode = [ASTextNode2 new]; - accessibleTextNode.attributedText = _attributedText; - accessibleTextNode.attributedText = nil; - XCTAssertFalse(accessibleTextNode.isAccessibilityElement); -} - -- (void)testSupportsLayerBacking -{ - ASTextNode2 *textNode = [[ASTextNode2 alloc] init]; - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"new string"]; - XCTAssertTrue(textNode.supportsLayerBacking); - - NSString *link = @"https://texturegroup.com"; - NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"Texture Website: %@", link]]; - NSRange linkRange = [attributedText.string rangeOfString:link]; - [attributedText addAttribute:NSLinkAttributeName value:link range:linkRange]; - textNode.attributedText = attributedText; - XCTAssertFalse(textNode.supportsLayerBacking); -} - -- (void)testEmptyStringSize -{ - CGSize constrainedSize = CGSizeMake(100, CGFLOAT_MAX); - _textNode.attributedText = [[NSAttributedString alloc] initWithString:@""]; - CGSize sizeWithEmptyString = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size; - XCTAssertTrue(ASIsCGSizeValidForSize(sizeWithEmptyString)); - XCTAssertTrue(sizeWithEmptyString.width == 0); -} - -@end diff --git a/Tests/ASTextNodeSnapshotTests.mm b/Tests/ASTextNodeSnapshotTests.mm index 74118a6e84..f5166a2d26 100644 --- a/Tests/ASTextNodeSnapshotTests.mm +++ b/Tests/ASTextNodeSnapshotTests.mm @@ -89,16 +89,6 @@ - (void)DISABLED_testThatFastPathTruncationWorks ASSnapshotVerifyNode(textNode, nil); } -- (void)testThatSlowPathTruncationWorks -{ - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"Quality is Important" attributes:@{ NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont italicSystemFontOfSize:24] }]; - // Set exclusion paths to trigger slow path - textNode.exclusionPaths = @[ [UIBezierPath bezierPath] ]; - ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(100, 50))); - ASSnapshotVerifyNode(textNode, nil); -} - - (void)testShadowing { ASTextNode *textNode = [[ASTextNode alloc] init]; @@ -144,24 +134,6 @@ - (void)testFontPointSizeScaling ASSnapshotVerifyNode(textNode, nil); } - -- (void)testUIGraphicsRendererDrawingExperiment -{ - // Test to ensure that rendering with UIGraphicsRenderer don't regress - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalDrawingGlobal; - [ASConfigurationManager test_resetWithConfiguration:config]; - - // trivial test case to ensure ASSnapshotTestCase works - ASTextNode *textNode = [[ASTextNode alloc] init]; - textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar" - attributes:@{NSFontAttributeName : [UIFont italicSystemFontOfSize:24]}]; - textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2); - ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); - - ASSnapshotVerifyNode(textNode, nil); -} - - (void)testTintColorHierarchyChange { ASDisplayNode *containerNode = [[ASDisplayNode alloc] init]; diff --git a/Tests/ASTextNodeTests.mm b/Tests/ASTextNodeTests.mm index e01f2516fb..ae861b89bd 100644 --- a/Tests/ASTextNodeTests.mm +++ b/Tests/ASTextNodeTests.mm @@ -118,13 +118,6 @@ - (void)testSettingTruncationMessage XCTAssertTrue([_textNode.truncationAttributedText isEqualToAttributedString:truncation], @"Failed to set truncation message"); } -- (void)testSettingAdditionalTruncationMessage -{ - NSAttributedString *additionalTruncationMessage = [[NSAttributedString alloc] initWithString:@"read more" attributes:nil]; - _textNode.additionalTruncationMessage = additionalTruncationMessage; - XCTAssertTrue([_textNode.additionalTruncationMessage isEqualToAttributedString:additionalTruncationMessage], @"Failed to set additionalTruncationMessage message"); -} - - (void)testCalculatedSizeIsGreaterThanOrEqualToConstrainedSize { for (NSInteger i = 10; i < 500; i += 50) { @@ -243,25 +236,6 @@ - (void)testTapNotOnALinkAttribute XCTAssertFalse(delegate.tappedLinkValue, @"Expected the delegate to be told that the value %@ was tapped, instead it thinks the tapped attribute value is %@", linkAttributeValue, delegate.tappedLinkValue); } -#pragma mark exclusion Paths - -- (void)testSettingExclusionPaths -{ - NSArray *exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(10, 20, 30, 40)]]; - _textNode.exclusionPaths = exclusionPaths; - XCTAssertTrue([_textNode.exclusionPaths isEqualToArray:exclusionPaths], @"Failed to set exclusion paths"); -} - -- (void)testAddingExclusionPathsShouldInvalidateAndIncreaseTheSize -{ - CGSize constrainedSize = CGSizeMake(100, CGFLOAT_MAX); - CGSize sizeWithoutExclusionPaths = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size; - _textNode.exclusionPaths = @[[UIBezierPath bezierPathWithRect:CGRectMake(50, 20, 30, 40)]]; - CGSize sizeWithExclusionPaths = [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size; - - XCTAssertGreaterThan(sizeWithExclusionPaths.height, sizeWithoutExclusionPaths.height, @"Setting exclusions paths should invalidate the calculated size and return a greater size"); -} - - (void)testEmptyStringSize { CGSize constrainedSize = CGSizeMake(100, CGFLOAT_MAX); @@ -271,77 +245,6 @@ - (void)testEmptyStringSize XCTAssertTrue(sizeWithEmptyString.width == 0); } -#if AS_ENABLE_TEXTNODE -- (void)testThatTheExperimentWorksCorrectly -{ - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalTextNode; - [ASConfigurationManager test_resetWithConfiguration:config]; - - ASTextNode *plainTextNode = [[ASTextNode alloc] init]; - XCTAssertEqualObjects(plainTextNode.class, [ASTextNode2 class]); - - ASTextNodeSecondSubclass *sc2 = [[ASTextNodeSecondSubclass alloc] init]; - XCTAssertEqualObjects([ASTextNodeSubclass superclass], [ASTextNode2 class]); - XCTAssertEqualObjects(sc2.superclass, [ASTextNodeSubclass class]); -} - -- (void)testTextNodeSwitchWorksInMultiThreadEnvironment -{ - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalTextNode; - [ASConfigurationManager test_resetWithConfiguration:config]; - XCTestExpectation *exp = [self expectationWithDescription:@"wait for full bucket"]; - - dispatch_queue_t queue = dispatch_queue_create("com.texture.AsyncDisplayKit.ASTextNodeTestsQueue", DISPATCH_QUEUE_CONCURRENT); - dispatch_group_t g = dispatch_group_create(); - for (int i = 0; i < 20; i++) { - dispatch_group_async(g, queue, ^{ - ASTextNode *textNode = [[ASTextNodeSecondSubclass alloc] init]; - XCTAssert([textNode isKindOfClass:[ASTextNode2 class]]); - @synchronized(self.textNodeBucket) { - [self.textNodeBucket addObject:textNode]; - if (self.textNodeBucket.count == 20) { - [exp fulfill]; - } - } - }); - } - [self waitForExpectations:@[exp] timeout:3]; - exp = nil; - [self.textNodeBucket removeAllObjects]; -} - -- (void)testTextNodeSwitchWorksInMultiThreadEnvironment2 -{ - ASConfiguration *config = [ASConfiguration new]; - config.experimentalFeatures = ASExperimentalTextNode; - [ASConfigurationManager test_resetWithConfiguration:config]; - XCTestExpectation *exp = [self expectationWithDescription:@"wait for full bucket"]; - - NSLock *lock = [[NSLock alloc] init]; - NSMutableArray *textNodeBucket = [[NSMutableArray alloc] init]; - - dispatch_queue_t queue = dispatch_queue_create("com.texture.AsyncDisplayKit.ASTextNodeTestsQueue", DISPATCH_QUEUE_CONCURRENT); - dispatch_group_t g = dispatch_group_create(); - for (int i = 0; i < 20; i++) { - dispatch_group_async(g, queue, ^{ - ASTextNode *textNode = [[ASTextNodeSecondSubclass alloc] init]; - XCTAssert([textNode isKindOfClass:[ASTextNode2 class]]); - [lock lock]; - [textNodeBucket addObject:textNode]; - if (textNodeBucket.count == 20) { - [exp fulfill]; - } - [lock unlock]; - }); - } - [self waitForExpectations:@[exp] timeout:3]; - exp = nil; - [textNodeBucket removeAllObjects]; -} -#endif - @end @implementation ASTextNodeSubclass diff --git a/Tests/ASThrashUtility.m b/Tests/ASThrashUtility.m index ac2da5315b..ff33195588 100644 --- a/Tests/ASThrashUtility.m +++ b/Tests/ASThrashUtility.m @@ -236,22 +236,6 @@ - (void)setData:(NSArray *)data _data = data; } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - return self.data[section].items.count; -} -#pragma clang diagnostic pop - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return self.data.count; -} -#pragma clang diagnostic pop - - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { return self.data[section].headerHeight; @@ -302,17 +286,6 @@ - (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtI return node; } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath -{ - ASThrashTestNode *node = [[ASThrashTestNode alloc] init]; - node.item = self.data[indexPath.section].items[indexPath.item]; - [self.allNodes addObject:node]; - return node; -} -#pragma clang diagnostic pop - #endif @end diff --git a/Tests/ASVideoNodeTests.mm b/Tests/ASVideoNodeTests.mm deleted file mode 100644 index 60c7f89c42..0000000000 --- a/Tests/ASVideoNodeTests.mm +++ /dev/null @@ -1,428 +0,0 @@ -// -// ASVideoNodeTests.mm -// Texture -// -// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. -// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved. -// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -#import -#import -#import - -#import - -#import "ASDisplayNodeTestsHelper.h" - -#if AS_USE_VIDEO -@interface ASVideoNodeTests : XCTestCase -{ - ASVideoNode *_videoNode; - AVURLAsset *_firstAsset; - AVAsset *_secondAsset; - NSURL *_url; - NSArray *_requestedKeys; -} -@end - -@interface ASVideoNode () { - ASDisplayNode *_playerNode; - AVPlayer *_player; -} - - -@property ASInterfaceState interfaceState; -@property (readonly) ASDisplayNode *spinner; -@property ASDisplayNode *playerNode; -@property AVPlayer *player; -@property BOOL shouldBePlaying; - -- (void)setVideoPlaceholderImage:(UIImage *)image; -- (void)prepareToPlayAsset:(AVAsset *)asset withKeys:(NSArray *)requestedKeys; - -@end - -@implementation ASVideoNodeTests - -- (void)setUp -{ - _videoNode = [[ASVideoNode alloc] init]; - _firstAsset = [AVURLAsset assetWithURL:[NSURL URLWithString:@"firstURL"]]; - _secondAsset = [AVAsset assetWithURL:[NSURL URLWithString:@"secondURL"]]; - _url = [NSURL URLWithString:@"testURL"]; - _requestedKeys = @[ @"playable" ]; -} - -- (void)testOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnode -{ - _videoNode.asset = _firstAsset; - [self doOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl]; -} - -- (void)testOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl -{ - _videoNode.asset = [AVAsset assetWithURL:_url]; - [self doOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl]; -} - -- (void)doOnPlayIfVideoIsNotReadyInitializeSpinnerAndAddAsSubnodeWithUrl -{ - _videoNode.interfaceState = ASInterfaceStatePreload; - [_videoNode play]; -} - - -- (void)testOnPauseSpinnerIsPausedIfPresent -{ - _videoNode.asset = _firstAsset; - [self doOnPauseSpinnerIsPausedIfPresentWithURL]; -} - -- (void)testOnPauseSpinnerIsPausedIfPresentWithURL -{ - _videoNode.asset = [AVAsset assetWithURL:_url]; - [self doOnPauseSpinnerIsPausedIfPresentWithURL]; -} - -- (void)doOnPauseSpinnerIsPausedIfPresentWithURL -{ - _videoNode.interfaceState = ASInterfaceStatePreload; - - [_videoNode play]; - [_videoNode pause]; - -} - - -- (void)testOnVideoReadySpinnerIsStoppedAndRemoved -{ - _videoNode.asset = _firstAsset; - [self doOnVideoReadySpinnerIsStoppedAndRemovedWithURL]; -} - -- (void)testOnVideoReadySpinnerIsStoppedAndRemovedWithURL -{ - _videoNode.asset = [AVAsset assetWithURL:_url]; - [self doOnVideoReadySpinnerIsStoppedAndRemovedWithURL]; -} - -- (void)doOnVideoReadySpinnerIsStoppedAndRemovedWithURL -{ - _videoNode.interfaceState = ASInterfaceStatePreload; - - [_videoNode play]; - [_videoNode observeValueForKeyPath:@"status" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @(AVPlayerItemStatusReadyToPlay)} context:NULL]; -} - - -- (void)testPlayerDefaultsToNil -{ - _videoNode.asset = _firstAsset; - XCTAssertNil(_videoNode.player); -} - -- (void)testPlayerDefaultsToNilWithURL -{ - _videoNode.asset = [AVAsset assetWithURL:_url]; - XCTAssertNil(_videoNode.player); -} - -- (void)testPlayerIsCreatedAsynchronouslyInPreload -{ - AVAsset *asset = _firstAsset; - - id assetMock = [OCMockObject partialMockForObject:asset]; - id videoNodeMock = [OCMockObject partialMockForObject:_videoNode]; - - [[[assetMock stub] andReturnValue:@YES] isPlayable]; - [[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys]; - - _videoNode.asset = assetMock; - _videoNode.interfaceState = ASInterfaceStatePreload; - - [videoNodeMock verifyWithDelay:1.0f]; - - XCTAssertNotNil(_videoNode.player); -} - -- (void)testPlayerIsCreatedAsynchronouslyInPreloadWithURL -{ - AVAsset *asset = [AVAsset assetWithURL:_url]; - - id assetMock = [OCMockObject partialMockForObject:asset]; - id videoNodeMock = [OCMockObject partialMockForObject:_videoNode]; - - [[[assetMock stub] andReturnValue:@YES] isPlayable]; - [[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys]; - - _videoNode.asset = assetMock; - _videoNode.interfaceState = ASInterfaceStatePreload; - - [videoNodeMock verifyWithDelay:1.0f]; - - XCTAssertNotNil(_videoNode.player); -} - -- (void)testPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlaying -{ - _videoNode.asset = _firstAsset; - [self doPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL]; -} - -- (void)testPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL -{ - _videoNode.asset = [AVAsset assetWithURL:_url]; - [self doPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL]; -} - -- (void)doPlayerLayerNodeIsAddedOnDidLoadIfVisibleAndAutoPlayingWithURL -{ - [_videoNode setInterfaceState:ASInterfaceStateNone]; - [_videoNode didLoad]; - - XCTAssert(![_videoNode.subnodes containsObject:_videoNode.playerNode]); -} - - -- (void)testPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying -{ - _videoNode.asset = _firstAsset; - [self doPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying]; -} - -- (void)testPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlayingWithUrl -{ - _videoNode.asset = [AVAsset assetWithURL:_url]; - [self doPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying]; -} - -- (void)doPlayerLayerNodeIsNotAddedIfVisibleButShouldNotBePlaying -{ - [_videoNode pause]; - [_videoNode layer]; - [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay]; - - XCTAssert(![_videoNode.subnodes containsObject:_videoNode.playerNode]); -} - - -- (void)testVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay -{ - _videoNode.asset = _firstAsset; - [self doVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay]; -} - -- (void)testVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplayWithURL -{ - _videoNode.asset = [AVAsset assetWithURL:_url]; - [self doVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay]; -} - -- (void)doVideoStartsPlayingOnDidDidBecomeVisibleWhenShouldAutoplay -{ - _videoNode.shouldAutoplay = YES; - _videoNode.playerNode = [[ASDisplayNode alloc] initWithLayerBlock:^CALayer *{ - AVPlayerLayer *playerLayer = [[AVPlayerLayer alloc] init]; - return playerLayer; - }]; - _videoNode.playerNode.layer.frame = CGRectZero; - - [_videoNode layer]; - [_videoNode didEnterVisibleState]; - - XCTAssertTrue(_videoNode.shouldBePlaying); -} - -- (void)testVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater -{ - _videoNode.asset = _firstAsset; - [self doVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater]; -} - -- (void)testVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLaterWithURL -{ - _videoNode.asset = [AVAsset assetWithURL:_url]; - [self doVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater]; -} - -- (void)doVideoShouldPauseWhenItLeavesVisibleButShouldKnowPlayingShouldRestartLater -{ - [_videoNode play]; - - [_videoNode interfaceStateDidChange:ASInterfaceStateNone fromState:ASInterfaceStateVisible]; - - XCTAssertFalse(_videoNode.isPlaying); - XCTAssertTrue(_videoNode.shouldBePlaying); -} - - -- (void)testVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack -{ - _videoNode.asset = _firstAsset; - [self doVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack]; -} - -- (void)testVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBackWithURL -{ - _videoNode.asset = [AVAsset assetWithURL:_url]; - [self doVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack]; -} - -- (void)doVideoThatIsPlayingWhenItLeavesVisibleRangeStartsAgainWhenItComesBack -{ - [_videoNode play]; - - [_videoNode interfaceStateDidChange:ASInterfaceStateVisible fromState:ASInterfaceStateNone]; - [_videoNode interfaceStateDidChange:ASInterfaceStateNone fromState:ASInterfaceStateVisible]; - - XCTAssertTrue(_videoNode.shouldBePlaying); -} - -- (void)testMutingShouldMutePlayer -{ - [_videoNode setPlayer:[[AVPlayer alloc] init]]; - - _videoNode.muted = YES; - - XCTAssertTrue(_videoNode.player.muted); -} - -- (void)testUnMutingShouldUnMutePlayer -{ - [_videoNode setPlayer:[[AVPlayer alloc] init]]; - - _videoNode.muted = YES; - _videoNode.muted = NO; - - XCTAssertFalse(_videoNode.player.muted); -} - -- (void)testVideoThatDoesNotAutorepeatsShouldPauseOnPlaybackEnd -{ - id assetMock = [OCMockObject partialMockForObject:_firstAsset]; - [[[assetMock stub] andReturnValue:@YES] isPlayable]; - - _videoNode.asset = assetMock; - _videoNode.shouldAutorepeat = NO; - - [_videoNode layer]; - [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload]; - [_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys]; - [_videoNode play]; - - XCTAssertTrue(_videoNode.isPlaying); - - [[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:_videoNode.currentItem]; - - XCTAssertFalse(_videoNode.isPlaying); - XCTAssertEqual(0, CMTimeGetSeconds(_videoNode.player.currentTime)); -} - -- (void)testVideoThatAutorepeatsShouldRepeatOnPlaybackEnd -{ - id assetMock = [OCMockObject partialMockForObject:_firstAsset]; - [[[assetMock stub] andReturnValue:@YES] isPlayable]; - - _videoNode.asset = assetMock; - _videoNode.shouldAutorepeat = YES; - - [_videoNode layer]; - [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload]; - [_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys]; - [_videoNode play]; - - [[NSNotificationCenter defaultCenter] postNotificationName:AVPlayerItemDidPlayToEndTimeNotification object:_videoNode.currentItem]; - - XCTAssertTrue(_videoNode.isPlaying); -} - -- (void)testVideoResumedWhenBufferIsLikelyToKeepUp -{ - id assetMock = [OCMockObject partialMockForObject:_firstAsset]; - [[[assetMock stub] andReturnValue:@YES] isPlayable]; - - _videoNode.asset = assetMock; - - [_videoNode layer]; - [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload]; - [_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys]; - ASCATransactionQueueWait(nil); - [_videoNode pause]; - _videoNode.shouldBePlaying = YES; - XCTAssertFalse(_videoNode.isPlaying); - - [_videoNode observeValueForKeyPath:@"playbackLikelyToKeepUp" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @YES} context:NULL]; - - XCTAssertTrue(_videoNode.isPlaying); -} - -- (void)testSettingVideoGravityChangesPlaceholderContentMode -{ - [_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]]; - XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.contentMode); - - _videoNode.gravity = AVLayerVideoGravityResize; - XCTAssertEqual(UIViewContentModeScaleToFill, _videoNode.contentMode); - - _videoNode.gravity = AVLayerVideoGravityResizeAspect; - XCTAssertEqual(UIViewContentModeScaleAspectFit, _videoNode.contentMode); - - _videoNode.gravity = AVLayerVideoGravityResizeAspectFill; - XCTAssertEqual(UIViewContentModeScaleAspectFill, _videoNode.contentMode); -} - -- (void)testChangingAssetsChangesPlaceholderImage -{ - UIImage *firstImage = [[UIImage alloc] init]; - - _videoNode.asset = _firstAsset; - [_videoNode setVideoPlaceholderImage:firstImage]; - XCTAssertEqual(firstImage, _videoNode.image); - - _videoNode.asset = _secondAsset; - XCTAssertNotEqual(firstImage, _videoNode.image); -} - -- (void)testClearingPreloadedContentShouldClearAssetData -{ - AVAsset *asset = _firstAsset; - - id assetMock = [OCMockObject partialMockForObject:asset]; - id videoNodeMock = [OCMockObject partialMockForObject:_videoNode]; - - [[[assetMock stub] andReturnValue:@YES] isPlayable]; - [[[videoNodeMock expect] andForwardToRealObject] prepareToPlayAsset:assetMock withKeys:_requestedKeys]; - - _videoNode.asset = assetMock; - [_videoNode didEnterPreloadState]; - [_videoNode setVideoPlaceholderImage:[[UIImage alloc] init]]; - - [videoNodeMock verifyWithDelay:1.0f]; - - XCTAssertNotNil(_videoNode.player); - XCTAssertNotNil(_videoNode.currentItem); - XCTAssertNotNil(_videoNode.image); - - [_videoNode didExitPreloadState]; - XCTAssertNil(_videoNode.player); - XCTAssertNil(_videoNode.currentItem); -} - -- (void)testDelegateProperlySetForClassHierarchy -{ - _videoNode.delegate = self; - - XCTAssertTrue([_videoNode.delegate conformsToProtocol:@protocol(ASVideoNodeDelegate)]); - XCTAssertTrue([_videoNode.delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]); - XCTAssertTrue([((ASNetworkImageNode*)_videoNode).delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]); - - XCTAssertEqual(_videoNode.delegate, self); - XCTAssertEqual(((ASNetworkImageNode*)_videoNode).delegate, self); -} - -@end - -#endif \ No newline at end of file diff --git a/Tests/Common/ASTestCase.mm b/Tests/Common/ASTestCase.mm index 81236d271f..19d99481b5 100644 --- a/Tests/Common/ASTestCase.mm +++ b/Tests/Common/ASTestCase.mm @@ -28,8 +28,6 @@ - (void)setUp - (void)tearDown { - [ASConfigurationManager test_resetWithConfiguration:nil]; - // Clear out all application windows. Note: the system will retain these sometimes on its // own but we'll do our best. for (UIWindow *window in [UIApplication sharedApplication].windows) { diff --git a/Texture.podspec b/Texture.podspec index ff5a19a816..b379693e9b 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -39,58 +39,10 @@ Pod::Spec.new do |spec| 'Source/TextKit/*.h', ] end - - spec.subspec 'PINRemoteImage' do |pin| - pin.dependency 'PINRemoteImage/iOS', '~> 3.0.0' - pin.dependency 'PINRemoteImage/PINCache' - pin.dependency 'Texture/Core' - end - - spec.subspec 'IGListKit' do |igl| - igl.dependency 'IGListKit', '~> 4.0' - igl.dependency 'IGListDiffKit', '~> 4.0' - igl.dependency 'Texture/Core' - end - - spec.subspec 'Yoga' do |yoga| - yoga.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) YOGA=1' } - yoga.dependency 'Yoga', '1.6.0' - yoga.dependency 'Texture/Core' - end - - # If flag is enabled the old TextNode with all dependencies will be compiled out - spec.subspec 'TextNode2' do |text_node| - text_node.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) AS_ENABLE_TEXTNODE=0' } - text_node.dependency 'Texture/Core' - end - - spec.subspec 'Video' do |video| - video.frameworks = ['AVFoundation', 'CoreMedia'] - video.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) AS_USE_VIDEO=1' } - video.dependency 'Texture/Core' - end - - spec.subspec 'MapKit' do |map| - map.frameworks = ['CoreLocation', 'MapKit'] - map.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) AS_USE_MAPKIT=1' } - map.dependency 'Texture/Core' - end - - spec.subspec 'Photos' do |photos| - photos.frameworks = 'Photos' - photos.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) AS_USE_PHOTOS=1' } - photos.dependency 'Texture/Core' - end - - spec.subspec 'AssetsLibrary' do |assetslib| - assetslib.frameworks = 'AssetsLibrary' - assetslib.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) AS_USE_ASSETS_LIBRARY=1' } - assetslib.dependency 'Texture/Core' - end # Include these by default for backwards compatibility. # This will change in 3.0. - spec.default_subspecs = 'Core', 'PINRemoteImage', 'Video', 'MapKit', 'AssetsLibrary', 'Photos' + spec.default_subspecs = 'Core' spec.social_media_url = 'https://twitter.com/TextureiOS' spec.library = 'c++'