Merge branch 'master' into update-objc

Conflicts:
	AsyncDisplayKit/ASDisplayNodeExtras.h
	AsyncDisplayKit/Details/ASTextNodeRenderer.h
	AsyncDisplayKit/Details/ASTextNodeShadower.h
This commit is contained in:
Adlai Holler
2015-12-01 16:45:25 -08:00
139 changed files with 4747 additions and 1755 deletions

View File

@@ -1,11 +1,11 @@
Pod::Spec.new do |spec|
spec.name = 'AsyncDisplayKit'
spec.version = '1.9.1'
spec.version = '1.9.2'
spec.license = { :type => 'BSD' }
spec.homepage = 'http://asyncdisplaykit.org'
spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' }
spec.summary = 'Smooth asynchronous user interfaces for iOS apps.'
spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.1' }
spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.2' }
spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/'

View File

@@ -58,11 +58,6 @@
058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E5195D050800B7D73C /* _ASDisplayView.mm */; };
058D0A1A195D050800B7D73C /* ASHighlightOverlayLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */; };
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */; };
058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */; };
058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09ED195D050800B7D73C /* ASTextNodeRenderer.mm */; };
058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09EF195D050800B7D73C /* ASTextNodeShadower.m */; };
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F1195D050800B7D73C /* ASTextNodeTextKitHelpers.mm */; };
058D0A20195D050800B7D73C /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */; };
058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */; };
058D0A22195D050800B7D73C /* _ASAsyncTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F9195D050800B7D73C /* _ASAsyncTransaction.m */; };
058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */; };
@@ -79,9 +74,7 @@
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.m */; };
058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */; };
058D0A3D195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A33195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m */; };
058D0A3E195D057000B7D73C /* ASTextNodeRendererTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A34195D057000B7D73C /* ASTextNodeRendererTests.m */; };
058D0A3F195D057000B7D73C /* ASTextNodeShadowerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A35195D057000B7D73C /* ASTextNodeShadowerTests.m */; };
058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */; };
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A36195D057000B7D73C /* ASTextNodeTests.m */; };
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */; };
058D0A47195D05CB00B7D73C /* ASControlNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09D5195D050800B7D73C /* ASControlNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -95,12 +88,6 @@
058D0A55195D05DC00B7D73C /* _ASDisplayView.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E4195D050800B7D73C /* _ASDisplayView.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A57195D05DC00B7D73C /* ASHighlightOverlayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A59195D05DC00B7D73C /* ASMutableAttributedStringBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A5B195D05DC00B7D73C /* ASTextNodeCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A5D195D05DC00B7D73C /* ASTextNodeRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A5F195D05DC00B7D73C /* ASTextNodeShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EE195D050800B7D73C /* ASTextNodeShadower.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A61195D05DC00B7D73C /* ASTextNodeTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F0195D050800B7D73C /* ASTextNodeTextKitHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A63195D05DC00B7D73C /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F2195D050800B7D73C /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A64195D05DC00B7D73C /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; };
058D0A6A195D05EC00B7D73C /* _ASAsyncTransactionContainer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -145,6 +132,64 @@
251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; };
251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; };
2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; };
254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */; };
254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */; };
254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */; };
254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; };
254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */; };
254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; };
254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */; };
254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754961BEE44CD00737CA5 /* ASTextKitContext.h */; };
254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */; };
254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */; };
254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */; };
254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */; };
254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; };
254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; };
254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; };
254C6B801BF94DF4003EC431 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */; };
254C6B821BF94F8A003EC431 /* ASTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */; };
254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; };
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; };
254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */; };
254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754971BEE44CD00737CA5 /* ASTextKitContext.mm */; };
254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */; };
254C6B881BF94F8A003EC431 /* ASTextKitRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */; };
254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549C1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm */; };
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 */; };
254C6B8D1BF94F8A003EC431 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */; };
257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */; };
257754A51BEE44CD00737CA5 /* ASTextKitRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */; };
257754A71BEE44CD00737CA5 /* ASTextKitAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754961BEE44CD00737CA5 /* ASTextKitContext.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754971BEE44CD00737CA5 /* ASTextKitContext.mm */; };
257754AA1BEE44CD00737CA5 /* ASTextKitEntityAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */; };
257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */; };
257754AD1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754AE1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549C1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm */; };
257754AF1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */; };
257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */; };
257754B31BEE44CD00737CA5 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; };
257754B51BEE44CD00737CA5 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754BE1BEE458E00737CA5 /* ASTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */; };
257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; };
257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754C11BEE458E00737CA5 /* ASTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; };
258FF4271C0D152600A83844 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */; };
258FF4281C0D152600A83844 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */; };
25A977EF1C0D2A5500406B62 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = 258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */; };
25BAA16F1C0D18D2002747C7 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = 258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */; };
2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */; };
2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; };
@@ -351,17 +396,6 @@
B350622B1B010EFD0018CF92 /* ASRangeHandlerRender.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */; settings = {ATTRIBUTES = (Public, ); }; };
B350622C1B010EFD0018CF92 /* ASRangeHandlerRender.mm in Sources */ = {isa = PBXBuildFile; fileRef = 292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */; };
B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; };
B350622E1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
B350622F1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */; };
B35062301B010EFD0018CF92 /* ASTextNodeRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
B35062311B010EFD0018CF92 /* ASTextNodeRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09ED195D050800B7D73C /* ASTextNodeRenderer.mm */; };
B35062321B010EFD0018CF92 /* ASTextNodeShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09EE195D050800B7D73C /* ASTextNodeShadower.h */; settings = {ATTRIBUTES = (Public, ); }; };
B35062331B010EFD0018CF92 /* ASTextNodeShadower.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09EF195D050800B7D73C /* ASTextNodeShadower.m */; };
B35062341B010EFD0018CF92 /* ASTextNodeTextKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F0195D050800B7D73C /* ASTextNodeTextKitHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; };
B35062351B010EFD0018CF92 /* ASTextNodeTextKitHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F1195D050800B7D73C /* ASTextNodeTextKitHelpers.mm */; };
B35062361B010EFD0018CF92 /* ASTextNodeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F2195D050800B7D73C /* ASTextNodeTypes.h */; settings = {ATTRIBUTES = (Public, ); }; };
B35062371B010EFD0018CF92 /* ASTextNodeWordKerner.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */; settings = {ATTRIBUTES = (Public, ); }; };
B35062381B010EFD0018CF92 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */; };
B35062391B010EFD0018CF92 /* ASThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; settings = {ATTRIBUTES = (Public, ); }; };
B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */; };
@@ -503,17 +537,6 @@
058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASHighlightOverlayLayer.mm; sourceTree = "<group>"; };
058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMutableAttributedStringBuilder.h; sourceTree = "<group>"; };
058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMutableAttributedStringBuilder.m; sourceTree = "<group>"; };
058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeCoreTextAdditions.h; sourceTree = "<group>"; };
058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeCoreTextAdditions.m; sourceTree = "<group>"; };
058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeRenderer.h; sourceTree = "<group>"; };
058D09ED195D050800B7D73C /* ASTextNodeRenderer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeRenderer.mm; sourceTree = "<group>"; };
058D09EE195D050800B7D73C /* ASTextNodeShadower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeShadower.h; sourceTree = "<group>"; };
058D09EF195D050800B7D73C /* ASTextNodeShadower.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeShadower.m; sourceTree = "<group>"; };
058D09F0195D050800B7D73C /* ASTextNodeTextKitHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeTextKitHelpers.h; sourceTree = "<group>"; };
058D09F1195D050800B7D73C /* ASTextNodeTextKitHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeTextKitHelpers.mm; sourceTree = "<group>"; };
058D09F2195D050800B7D73C /* ASTextNodeTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeTypes.h; sourceTree = "<group>"; };
058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextNodeWordKerner.h; sourceTree = "<group>"; };
058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeWordKerner.m; sourceTree = "<group>"; };
058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableAttributedString+TextKitAdditions.h"; sourceTree = "<group>"; };
058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableAttributedString+TextKitAdditions.m"; sourceTree = "<group>"; };
058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransaction.h; sourceTree = "<group>"; };
@@ -524,7 +547,6 @@
058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransactionGroup.h; sourceTree = "<group>"; };
058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASAsyncTransactionGroup.m; sourceTree = "<group>"; };
058D09FF195D050800B7D73C /* UIView+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+ASConvenience.h"; sourceTree = "<group>"; };
058D0A00195D050800B7D73C /* UIView+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+ASConvenience.m"; sourceTree = "<group>"; };
058D0A02195D050800B7D73C /* _AS-objc-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "_AS-objc-internal.h"; sourceTree = "<group>"; };
058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCoreAnimationExtras.h; sourceTree = "<group>"; };
058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASCoreAnimationExtras.mm; sourceTree = "<group>"; };
@@ -547,9 +569,7 @@
058D0A30195D057000B7D73C /* ASDisplayNodeTestsHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeTestsHelper.h; sourceTree = "<group>"; };
058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeTestsHelper.m; sourceTree = "<group>"; };
058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMutableAttributedStringBuilderTests.m; sourceTree = "<group>"; };
058D0A33195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeCoreTextAdditionsTests.m; sourceTree = "<group>"; };
058D0A34195D057000B7D73C /* ASTextNodeRendererTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeRendererTests.m; sourceTree = "<group>"; };
058D0A35195D057000B7D73C /* ASTextNodeShadowerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeShadowerTests.m; sourceTree = "<group>"; };
058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextKitCoreTextAdditionsTests.m; sourceTree = "<group>"; };
058D0A36195D057000B7D73C /* ASTextNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeTests.m; sourceTree = "<group>"; };
058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeWordKernerTests.mm; sourceTree = "<group>"; };
058D0A43195D058D00B7D73C /* ASAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAssert.h; sourceTree = "<group>"; };
@@ -579,6 +599,36 @@
251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspector.m; sourceTree = "<group>"; };
251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDataController+Subclasses.h"; sourceTree = "<group>"; };
2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewFlowLayoutInspectorTests.m; sourceTree = "<group>"; };
254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTruncationTests.mm; sourceTree = "<group>"; };
254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextKitTests.mm; sourceTree = "<group>"; };
2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEqualityHashHelpers.mm; sourceTree = "<group>"; };
257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitRenderer.h; path = TextKit/ASTextKitRenderer.h; sourceTree = "<group>"; };
257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitAttributes.mm; path = TextKit/ASTextKitAttributes.mm; sourceTree = "<group>"; };
257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitAttributes.h; path = TextKit/ASTextKitAttributes.h; sourceTree = "<group>"; };
257754961BEE44CD00737CA5 /* ASTextKitContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitContext.h; path = TextKit/ASTextKitContext.h; sourceTree = "<group>"; };
257754971BEE44CD00737CA5 /* ASTextKitContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitContext.mm; path = TextKit/ASTextKitContext.mm; sourceTree = "<group>"; };
257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitEntityAttribute.h; path = TextKit/ASTextKitEntityAttribute.h; sourceTree = "<group>"; };
257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitEntityAttribute.m; path = TextKit/ASTextKitEntityAttribute.m; sourceTree = "<group>"; };
2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitRenderer.mm; path = TextKit/ASTextKitRenderer.mm; sourceTree = "<group>"; };
2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ASTextKitRenderer+Positioning.h"; path = "TextKit/ASTextKitRenderer+Positioning.h"; sourceTree = "<group>"; };
2577549C1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "ASTextKitRenderer+Positioning.mm"; path = "TextKit/ASTextKitRenderer+Positioning.mm"; sourceTree = "<group>"; };
2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ASTextKitRenderer+TextChecking.h"; path = "TextKit/ASTextKitRenderer+TextChecking.h"; sourceTree = "<group>"; };
2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "ASTextKitRenderer+TextChecking.mm"; path = "TextKit/ASTextKitRenderer+TextChecking.mm"; sourceTree = "<group>"; };
2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitShadower.h; path = TextKit/ASTextKitShadower.h; sourceTree = "<group>"; };
257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitShadower.mm; path = TextKit/ASTextKitShadower.mm; sourceTree = "<group>"; };
257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTailTruncater.h; path = TextKit/ASTextKitTailTruncater.h; sourceTree = "<group>"; };
257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitTailTruncater.mm; path = TextKit/ASTextKitTailTruncater.mm; sourceTree = "<group>"; };
257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTruncating.h; path = TextKit/ASTextKitTruncating.h; sourceTree = "<group>"; };
257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASEqualityHashHelpers.h; path = TextKit/ASEqualityHashHelpers.h; sourceTree = "<group>"; };
257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitHelpers.mm; path = TextKit/ASTextKitHelpers.mm; sourceTree = "<group>"; };
257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitCoreTextAdditions.m; path = TextKit/ASTextKitCoreTextAdditions.m; sourceTree = "<group>"; };
257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeWordKerner.h; path = TextKit/ASTextNodeWordKerner.h; sourceTree = "<group>"; };
257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitHelpers.h; path = TextKit/ASTextKitHelpers.h; sourceTree = "<group>"; };
257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitCoreTextAdditions.h; path = TextKit/ASTextKitCoreTextAdditions.h; sourceTree = "<group>"; };
257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeTypes.h; path = TextKit/ASTextNodeTypes.h; sourceTree = "<group>"; };
257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextNodeWordKerner.m; path = TextKit/ASTextNodeWordKerner.m; sourceTree = "<group>"; };
258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerVisible.h; sourceTree = "<group>"; };
258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeHandlerVisible.mm; sourceTree = "<group>"; };
2911485B1A77147A005D0878 /* ASControlNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASControlNodeTests.m; sourceTree = "<group>"; };
292C59991A956527007E5DD6 /* ASLayoutRangeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutRangeType.h; sourceTree = "<group>"; };
292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerPreload.h; sourceTree = "<group>"; };
@@ -600,7 +650,7 @@
4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = "<group>"; };
4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = "<group>"; };
4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = "<group>"; };
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = "<group>"; };
9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASAsciiArtBoxCreator.h; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h; sourceTree = "<group>"; };
9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASAsciiArtBoxCreator.m; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m; sourceTree = "<group>"; };
@@ -830,6 +880,7 @@
058D09E1195D050800B7D73C /* Details */,
058D0A01195D050800B7D73C /* Private */,
AC6456051B0A333200CF11B8 /* Layout */,
257754661BED245B00737CA5 /* TextKit */,
058D09B2195D04C000B7D73C /* Supporting Files */,
);
path = AsyncDisplayKit;
@@ -875,9 +926,9 @@
052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */,
058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */,
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */,
058D0A33195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m */,
058D0A34195D057000B7D73C /* ASTextNodeRendererTests.m */,
058D0A35195D057000B7D73C /* ASTextNodeShadowerTests.m */,
058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */,
254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */,
254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */,
058D0A36195D057000B7D73C /* ASTextNodeTests.m */,
058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */,
058D09C6195D04C000B7D73C /* Supporting Files */,
@@ -938,23 +989,14 @@
055F1A3619ABD413004DAFF1 /* ASRangeController.h */,
055F1A3719ABD413004DAFF1 /* ASRangeController.mm */,
292C599C1A956527007E5DD6 /* ASRangeHandler.h */,
258FF4251C0D152600A83844 /* ASRangeHandlerVisible.h */,
258FF4261C0D152600A83844 /* ASRangeHandlerVisible.mm */,
292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */,
292C599B1A956527007E5DD6 /* ASRangeHandlerPreload.mm */,
292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */,
292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */,
296A0A311A951715005ACEAA /* ASScrollDirection.h */,
205F0E111B371BD7007741D0 /* ASScrollDirection.m */,
058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */,
058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */,
058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */,
058D09ED195D050800B7D73C /* ASTextNodeRenderer.mm */,
058D09EE195D050800B7D73C /* ASTextNodeShadower.h */,
058D09EF195D050800B7D73C /* ASTextNodeShadower.m */,
058D09F0195D050800B7D73C /* ASTextNodeTextKitHelpers.h */,
058D09F1195D050800B7D73C /* ASTextNodeTextKitHelpers.mm */,
058D09F2195D050800B7D73C /* ASTextNodeTypes.h */,
058D09F3195D050800B7D73C /* ASTextNodeWordKerner.h */,
058D09F4195D050800B7D73C /* ASTextNodeWordKerner.m */,
058D0A12195D050800B7D73C /* ASThread.h */,
205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */,
205F0E201B376416007741D0 /* CGRect+ASConvenience.m */,
@@ -964,7 +1006,6 @@
205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */,
205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */,
058D09FF195D050800B7D73C /* UIView+ASConvenience.h */,
058D0A00195D050800B7D73C /* UIView+ASConvenience.m */,
);
path = Details;
sourceTree = "<group>";
@@ -1036,6 +1077,39 @@
path = Base;
sourceTree = SOURCE_ROOT;
};
257754661BED245B00737CA5 /* TextKit */ = {
isa = PBXGroup;
children = (
257754B71BEE458D00737CA5 /* ASTextKitHelpers.mm */,
257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */,
257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */,
257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */,
257754BA1BEE458E00737CA5 /* ASTextKitHelpers.h */,
257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */,
257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */,
257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */,
257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */,
257754961BEE44CD00737CA5 /* ASTextKitContext.h */,
257754971BEE44CD00737CA5 /* ASTextKitContext.mm */,
257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */,
257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */,
257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */,
2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */,
2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */,
2577549C1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm */,
2577549D1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h */,
2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */,
2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */,
257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */,
257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */,
257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */,
257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */,
257754A41BEE44CD00737CA5 /* ASEqualityHashHelpers.h */,
2577548F1BED289A00737CA5 /* ASEqualityHashHelpers.mm */,
);
name = TextKit;
sourceTree = "<group>";
};
AC6456051B0A333200CF11B8 /* Layout */ = {
isa = PBXGroup;
children = (
@@ -1109,6 +1183,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */,
AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */,
058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */,
058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */,
@@ -1121,6 +1196,7 @@
058D0A74195D05F800B7D73C /* _ASPendingState.h in Headers */,
9C5586691BD549CB00B50E3A /* ASAsciiArtBoxCreator.h in Headers */,
058D0A76195D05F900B7D73C /* _ASScopeTimer.h in Headers */,
257754B31BEE44CD00737CA5 /* ASTextKitTailTruncater.h in Headers */,
205F0E191B37339C007741D0 /* ASAbstractLayoutController.h in Headers */,
058D0A82195D060300B7D73C /* ASAssert.h in Headers */,
0516FA3C1A15563400B4EBED /* ASAvailability.h in Headers */,
@@ -1134,25 +1210,31 @@
055F1A3C19ABD43F004DAFF1 /* ASCellNode.h in Headers */,
ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */,
18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */,
AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */,
205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */,
AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */,
058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */,
058D0A47195D05CB00B7D73C /* ASControlNode.h in Headers */,
257754A51BEE44CD00737CA5 /* ASTextKitRenderer.h in Headers */,
464052201A3F83C40061C0BA /* ASDataController.h in Headers */,
05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */,
ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */,
058D0A78195D05F900B7D73C /* ASDisplayNode+DebugTiming.h in Headers */,
DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */,
058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */,
258FF4271C0D152600A83844 /* ASRangeHandlerVisible.h in Headers */,
058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */,
058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */,
AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */,
058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */,
257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */,
058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */,
0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */,
1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */,
257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */,
464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */,
257754AF1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.h in Headers */,
058D0A57195D05DC00B7D73C /* ASHighlightOverlayLayer.h in Headers */,
058D0A7C195D05F900B7D73C /* ASImageNode+CGExtras.h in Headers */,
058D0A4F195D05CB00B7D73C /* ASImageNode.h in Headers */,
@@ -1169,10 +1251,12 @@
9C5FA3511B8F6ADF00A62714 /* ASLayoutOptions.h in Headers */,
9C65A72A1BA8EA4D0084DA91 /* ASLayoutOptionsPrivate.h in Headers */,
292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */,
257754B61BEE44CD00737CA5 /* ASEqualityHashHelpers.h in Headers */,
ACF6ED261B17843500DA7C62 /* ASLayoutSpec.h in Headers */,
ACF6ED4D1B17847A00DA7C62 /* ASLayoutSpecUtilities.h in Headers */,
AC026B6F1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h in Headers */,
0516FA3D1A15563400B4EBED /* ASLog.h in Headers */,
257754AA1BEE44CD00737CA5 /* ASTextKitEntityAttribute.h in Headers */,
0442850D1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */,
0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */,
058D0A59195D05DC00B7D73C /* ASMutableAttributedStringBuilder.h in Headers */,
@@ -1188,28 +1272,27 @@
D785F6621A74327E00291744 /* ASScrollNode.h in Headers */,
058D0A7F195D05F900B7D73C /* ASSentinel.h in Headers */,
9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */,
257754C31BEE458E00737CA5 /* ASTextNodeTypes.h in Headers */,
9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */,
AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */,
CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */,
ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */,
257754B51BEE44CD00737CA5 /* ASTextKitTruncating.h in Headers */,
ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */,
257754A71BEE44CD00737CA5 /* ASTextKitAttributes.h in Headers */,
ACF6ED511B17847A00DA7C62 /* ASStackUnpositionedLayout.h in Headers */,
9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */,
ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */,
055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */,
251B8EF71BBB3D690087C538 /* ASCollectionDataController.h in Headers */,
257754C11BEE458E00737CA5 /* ASTextKitHelpers.h in Headers */,
0574D5E219C110940097DC25 /* ASTableViewProtocols.h in Headers */,
058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */,
058D0A5B195D05DC00B7D73C /* ASTextNodeCoreTextAdditions.h in Headers */,
058D0A5D195D05DC00B7D73C /* ASTextNodeRenderer.h in Headers */,
058D0A5F195D05DC00B7D73C /* ASTextNodeShadower.h in Headers */,
058D0A61195D05DC00B7D73C /* ASTextNodeTextKitHelpers.h in Headers */,
058D0A63195D05DC00B7D73C /* ASTextNodeTypes.h in Headers */,
058D0A64195D05DC00B7D73C /* ASTextNodeWordKerner.h in Headers */,
058D0A81195D05F900B7D73C /* ASThread.h in Headers */,
ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */,
6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */,
257754AD1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h in Headers */,
205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */,
058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */,
205F0E0F1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h in Headers */,
@@ -1222,10 +1305,12 @@
buildActionMask = 2147483647;
files = (
AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */,
25BAA16F1C0D18D2002747C7 /* ASRangeHandlerVisible.h in Headers */,
B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */,
B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */,
B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */,
B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */,
254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */,
B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */,
B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */,
B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */,
@@ -1233,9 +1318,14 @@
B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */,
9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */,
B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */,
254C6B771BF94DF4003EC431 /* ASTextKitAttributes.h in Headers */,
509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */,
B35062571B010F070018CF92 /* ASAssert.h in Headers */,
254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */,
B35062581B010F070018CF92 /* ASAvailability.h in Headers */,
254C6B731BF94DF4003EC431 /* ASTextKitCoreTextAdditions.h in Headers */,
254C6B7A1BF94DF4003EC431 /* ASTextKitRenderer.h in Headers */,
254C6B7C1BF94DF4003EC431 /* ASTextKitRenderer+TextChecking.h in Headers */,
34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */,
B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */,
B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */,
@@ -1247,6 +1337,7 @@
34EFC7631B701CBF00AD841F /* ASCenterLayoutSpec.h in Headers */,
18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
B35061F51B010EFD0018CF92 /* ASCollectionView.h in Headers */,
254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */,
509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */,
B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */,
B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */,
@@ -1254,6 +1345,7 @@
B35062171B010EFD0018CF92 /* ASDataController.h in Headers */,
B35062191B010EFD0018CF92 /* ASDealloc2MainObject.h in Headers */,
34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */,
254C6B801BF94DF4003EC431 /* ASEqualityHashHelpers.h in Headers */,
B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */,
B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */,
B35061FB1B010EFD0018CF92 /* ASDisplayNode.h in Headers */,
@@ -1267,6 +1359,7 @@
C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */,
AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */,
B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */,
254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */,
B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */,
B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */,
430E7C901B4C23F100697A4C /* ASIndexPath.h in Headers */,
@@ -1294,13 +1387,17 @@
B350622B1B010EFD0018CF92 /* ASRangeHandlerRender.h in Headers */,
34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */,
34EFC7651B701CCC00AD841F /* ASRelativeSize.h in Headers */,
254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */,
B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */,
254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */,
B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */,
B35062551B010EFD0018CF92 /* ASSentinel.h in Headers */,
9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */,
9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */,
34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */,
254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */,
CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */,
34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */,
2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */,
044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */,
@@ -1308,15 +1405,10 @@
34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */,
9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */,
34EFC7731B701D0700AD841F /* ASStaticLayoutSpec.h in Headers */,
254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */,
B350620A1B010EFD0018CF92 /* ASTableView.h in Headers */,
B350620C1B010EFD0018CF92 /* ASTableViewProtocols.h in Headers */,
B350620D1B010EFD0018CF92 /* ASTextNode.h in Headers */,
B350622E1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.h in Headers */,
B35062301B010EFD0018CF92 /* ASTextNodeRenderer.h in Headers */,
B35062321B010EFD0018CF92 /* ASTextNodeShadower.h in Headers */,
B35062341B010EFD0018CF92 /* ASTextNodeTextKitHelpers.h in Headers */,
B35062361B010EFD0018CF92 /* ASTextNodeTypes.h in Headers */,
B35062371B010EFD0018CF92 /* ASTextNodeWordKerner.h in Headers */,
B35062391B010EFD0018CF92 /* ASThread.h in Headers */,
2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */,
509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */,
@@ -1373,7 +1465,6 @@
058D09B9195D04C000B7D73C /* Frameworks */,
058D09BA195D04C000B7D73C /* Resources */,
3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */,
6CC5F540055A48FCA8C12BF5 /* Embed Pods Frameworks */,
);
buildRules = (
);
@@ -1503,21 +1594,6 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
6CC5F540055A48FCA8C12BF5 /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -1538,7 +1614,9 @@
058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */,
058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */,
058D0A26195D050800B7D73C /* _ASCoreAnimationExtras.mm in Sources */,
257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */,
AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */,
257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */,
058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */,
058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */,
9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */,
@@ -1563,6 +1641,7 @@
058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */,
0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */,
464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */,
257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */,
058D0A1A195D050800B7D73C /* ASHighlightOverlayLayer.mm in Sources */,
058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */,
058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */,
@@ -1574,38 +1653,43 @@
9C5FA35F1B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */,
251B8EFA1BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m in Sources */,
ACF6ED271B17843500DA7C62 /* ASLayoutSpec.mm in Sources */,
257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */,
0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */,
DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */,
ACF6ED2C1B17843500DA7C62 /* ASOverlayLayoutSpec.mm in Sources */,
0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */,
257754921BED28F300737CA5 /* ASEqualityHashHelpers.mm in Sources */,
257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */,
055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */,
044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */,
292C59A11A956527007E5DD6 /* ASRangeHandlerPreload.mm in Sources */,
292C59A41A956527007E5DD6 /* ASRangeHandlerRender.mm in Sources */,
257754AE1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm in Sources */,
ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */,
AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */,
205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */,
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */,
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
258FF4281C0D152600A83844 /* ASRangeHandlerVisible.mm in Sources */,
251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */,
ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */,
257754BE1BEE458E00737CA5 /* ASTextKitHelpers.mm in Sources */,
257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */,
ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */,
ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */,
257754A61BEE44CD00737CA5 /* ASTextKitAttributes.mm in Sources */,
ACF6ED321B17843500DA7C62 /* ASStaticLayoutSpec.mm in Sources */,
AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.m in Sources */,
055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */,
058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */,
058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */,
058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */,
058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */,
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */,
058D0A20195D050800B7D73C /* ASTextNodeWordKerner.m in Sources */,
257754AC1BEE44CD00737CA5 /* ASTextKitRenderer.mm in Sources */,
ACC945AB1BA9E7C1005E1FB8 /* ASViewController.m in Sources */,
B0F8805B1BEAEC7500D17647 /* ASTableNode.m in Sources */,
205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */,
257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */,
058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */,
CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */,
@@ -1637,12 +1721,12 @@
058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */,
ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */,
ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */,
254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */,
05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */,
ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */,
3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */,
058D0A3D195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m in Sources */,
058D0A3E195D057000B7D73C /* ASTextNodeRendererTests.m in Sources */,
058D0A3F195D057000B7D73C /* ASTextNodeShadowerTests.m in Sources */,
254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */,
058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */,
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */,
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */,
);
@@ -1665,6 +1749,7 @@
B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */,
B350624C1B010EFD0018CF92 /* _ASPendingState.m in Sources */,
509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */,
254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */,
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */,
B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */,
@@ -1679,14 +1764,17 @@
34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */,
B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */,
B35062501B010EFD0018CF92 /* ASDisplayNode+DebugTiming.mm in Sources */,
254C6B891BF94F8A003EC431 /* ASTextKitRenderer+Positioning.mm in Sources */,
B35062511B010EFD0018CF92 /* ASDisplayNode+UIViewBridge.mm in Sources */,
B35061FC1B010EFD0018CF92 /* ASDisplayNode.mm in Sources */,
B35061FF1B010EFD0018CF92 /* ASDisplayNodeExtras.mm in Sources */,
B35062011B010EFD0018CF92 /* ASEditableTextNode.mm in Sources */,
254C6B881BF94F8A003EC431 /* ASTextKitRenderer.mm in Sources */,
B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */,
B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */,
B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */,
B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */,
254C6B821BF94F8A003EC431 /* ASTextKitHelpers.mm in Sources */,
430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */,
34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */,
34EFC75E1B701BF000AD841F /* ASInternalHelpers.mm in Sources */,
@@ -1694,7 +1782,9 @@
9C5FA3541B8F6ADF00A62714 /* ASLayoutOptions.mm in Sources */,
9C5FA3601B90C9A500A62714 /* ASLayoutOptionsPrivate.mm in Sources */,
DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */,
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */,
34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */,
254C6B8C1BF94F8A003EC431 /* ASTextKitTailTruncater.mm in Sources */,
B35062051B010EFD0018CF92 /* ASMultiplexImageNode.mm in Sources */,
B35062251B010EFD0018CF92 /* ASMutableAttributedStringBuilder.m in Sources */,
B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */,
@@ -1705,8 +1795,11 @@
B350622A1B010EFD0018CF92 /* ASRangeHandlerPreload.mm in Sources */,
B350622C1B010EFD0018CF92 /* ASRangeHandlerRender.mm in Sources */,
34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */,
254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */,
34EFC7661B701CD200AD841F /* ASRelativeSize.mm in Sources */,
254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */,
509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */,
25A977EF1C0D2A5500406B62 /* ASRangeHandlerVisible.mm in Sources */,
B35062091B010EFD0018CF92 /* ASScrollNode.m in Sources */,
B35062561B010EFD0018CF92 /* ASSentinel.m in Sources */,
9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
@@ -1717,16 +1810,15 @@
34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */,
B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */,
B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */,
B350622F1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.m in Sources */,
B35062311B010EFD0018CF92 /* ASTextNodeRenderer.mm in Sources */,
B35062331B010EFD0018CF92 /* ASTextNodeShadower.m in Sources */,
B35062351B010EFD0018CF92 /* ASTextNodeTextKitHelpers.mm in Sources */,
B35062381B010EFD0018CF92 /* ASTextNodeWordKerner.m in Sources */,
C78F7E2A1BF7808300CDEAFC /* ASTableNode.m in Sources */,
509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */,
254C6B8D1BF94F8A003EC431 /* ASEqualityHashHelpers.mm in Sources */,
254C6B871BF94F8A003EC431 /* ASTextKitEntityAttribute.m in Sources */,
34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */,
254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */,
B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */,
254C6B8A1BF94F8A003EC431 /* ASTextKitRenderer+TextChecking.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1870,6 +1962,7 @@
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
DSTROOT = /tmp/AsyncDisplayKit.dst;
GCC_INPUT_FILETYPE = automatic;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
@@ -1887,6 +1980,7 @@
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
DSTROOT = /tmp/AsyncDisplayKit.dst;
GCC_INPUT_FILETYPE = automatic;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch";
GCC_TREAT_WARNINGS_AS_ERRORS = YES;

View File

@@ -20,8 +20,8 @@
// FIXME: Temporary nonsense import until method names are finalized and exposed
#import "ASDisplayNode+Subclasses.h"
const static NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone;
static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone;
static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero};
#pragma mark -
#pragma mark Proxying.
@@ -115,13 +115,22 @@ static BOOL _isInterceptedSelector(SEL sel)
@implementation _ASCollectionViewCell
- (void)setNode:(ASCellNode *)node
{
_node = node;
node.selected = self.selected;
node.highlighted = self.highlighted;
}
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
_node.selected = selected;
}
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
_node.highlighted = highlighted;
}
@@ -479,11 +488,8 @@ static BOOL _isInterceptedSelector(SEL sel)
_ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
ASCellNode *node = [_dataController nodeAtIndexPath:indexPath];
[_rangeController configureContentView:cell.contentView forCellNode:node];
cell.node = node;
[_rangeController configureContentView:cell.contentView forCellNode:node];
return cell;
}
@@ -664,7 +670,17 @@ static BOOL _isInterceptedSelector(SEL sel)
- (ASSizeRange)dataController:(ASDataController *)dataController constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
ASSizeRange constrainedSize;
ASSizeRange constrainedSize = kInvalidSizeRange;
if (_layoutInspector) {
constrainedSize = [_layoutInspector collectionView:self constrainedSizeForNodeAtIndexPath:indexPath];
}
if (!ASSizeRangeEqualToSizeRange(constrainedSize, kInvalidSizeRange)) {
return constrainedSize;
}
// TODO: Move this logic into the flow layout inspector. Create a simple inspector for non-flow layouts that don't
// implement a custom inspector.
if (_asyncDataSourceImplementsConstrainedSizeForNode) {
constrainedSize = [_asyncDataSource collectionView:self constrainedSizeForNodeAtIndexPath:indexPath];
} else {

View File

@@ -34,6 +34,27 @@ typedef CALayer * _Nonnull(^ASDisplayNodeLayerBlock)();
*/
typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
/**
Interface state is available on ASDisplayNode and ASViewController, and
allows checking whether a node is in an interface situation where it is prudent to trigger certain
actions: measurement, data fetching, display, and visibility (the latter for animations or other onscreen-only effects).
*/
typedef NS_OPTIONS(NSUInteger, ASInterfaceState)
{
/** The element is not predicted to be onscreen soon and preloading should not be performed */
ASInterfaceStateNone = 1 << 0,
/** The element may be added to a view soon that could become visible. Measure the layout, including size calculation. */
ASInterfaceStateMeasureLayout = 1 << 1,
/** The element is likely enough to come onscreen that disk and/or network data required for display should be fetched. */
ASInterfaceStateFetchData = 1 << 2,
/** The element is very likely to become visible, and concurrent rendering should be executed for any -setNeedsDisplay. */
ASInterfaceStateDisplay = 1 << 3,
/** The element is physically onscreen by at least 1 pixel.
In practice, all other bit fields should also be set when this flag is set. */
ASInterfaceStateVisible = 1 << 4,
};
/**
* An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view
* hierarchy off the main thread, and could do rendering off the main thread as well.
@@ -125,7 +146,6 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
/** @name Getting view and layer */
/**
* @abstract Returns a view.
*
@@ -162,10 +182,17 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
*/
@property (nonatomic, readonly, retain) CALayer *layer;
/**
* @abstract Returns the Interface State 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;
/** @name Managing dimensions */
/**
* @abstract Asks the node to measure and return the size that best fits its subnodes.
*

View File

@@ -379,11 +379,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return YES;
}
- (void)__exitedHierarchy
{
}
- (UIView *)_viewToLoad
{
UIView *view;
@@ -787,55 +782,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return transform;
}
static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possibleAncestor, ASDisplayNode *possibleDescendent)
{
ASDisplayNode *supernode = possibleDescendent;
while (supernode) {
if (supernode == possibleAncestor) {
return YES;
}
supernode = supernode.supernode;
}
return NO;
}
/**
* NOTE: It is an error to try to convert between nodes which do not share a common ancestor. This behavior is
* disallowed in UIKit documentation and the behavior is left undefined. The output does not have a rigorously defined
* failure mode (i.e. returning CGPointZero or returning the point exactly as passed in). Rather than track the internal
* undefined and undocumented behavior of UIKit in ASDisplayNode, this operation is defined to be incorrect in all
* circumstances and must be fixed wherever encountered.
*/
static inline ASDisplayNode *_ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2)
{
ASDisplayNode *possibleAncestor = node1;
while (possibleAncestor) {
if (_ASDisplayNodeIsAncestorOfDisplayNode(possibleAncestor, node2)) {
break;
}
possibleAncestor = possibleAncestor.supernode;
}
ASDisplayNodeCAssertNotNil(possibleAncestor, @"Could not find a common ancestor between node1: %@ and node2: %@", node1, node2);
return possibleAncestor;
}
static inline ASDisplayNode *_getRootNode(ASDisplayNode *node)
{
// node <- supernode on each loop
// previous <- node on each loop where node is not nil
// previous is the final non-nil value of supernode, i.e. the root node
ASDisplayNode *previousNode = node;
while ((node = [node supernode])) {
previousNode = node;
}
return previousNode;
}
static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNode *referenceNode, ASDisplayNode *targetNode)
{
ASDisplayNode *ancestor = _ASDisplayNodeFindClosestCommonAncestor(referenceNode, targetNode);
ASDisplayNode *ancestor = ASDisplayNodeFindClosestCommonAncestor(referenceNode, targetNode);
// Transform into global (away from reference coordinate space)
CATransform3D transformToGlobal = [referenceNode _transformToAncestor:ancestor];
@@ -850,7 +799,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo
{
ASDisplayNodeAssertThreadAffinity(self);
// Get root node of the accessible node hierarchy, if node not specified
node = node ? node : _getRootNode(self);
node = node ? node : ASDisplayNodeUltimateParentOfNode(self);
// Calculate transform to map points between coordinate spaces
CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self);
@@ -865,7 +814,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo
{
ASDisplayNodeAssertThreadAffinity(self);
// Get root node of the accessible node hierarchy, if node not specified
node = node ? node : _getRootNode(self);
node = node ? node : ASDisplayNodeUltimateParentOfNode(self);
// Calculate transform to map points between coordinate spaces
CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node);
@@ -880,7 +829,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo
{
ASDisplayNodeAssertThreadAffinity(self);
// Get root node of the accessible node hierarchy, if node not specified
node = node ? node : _getRootNode(self);
node = node ? node : ASDisplayNodeUltimateParentOfNode(self);
// Calculate transform to map points between coordinate spaces
CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self);
@@ -895,7 +844,7 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo
{
ASDisplayNodeAssertThreadAffinity(self);
// Get root node of the accessible node hierarchy, if node not specified
node = node ? node : _getRootNode(self);
node = node ? node : ASDisplayNodeUltimateParentOfNode(self);
// Calculate transform to map points between coordinate spaces
CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node);
@@ -1614,6 +1563,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
- (void)__didLoad
{
ASDN::MutexLocker l(_propertyLock);
if (_nodeLoadedBlock) {
_nodeLoadedBlock(self);
_nodeLoadedBlock = nil;
@@ -1638,8 +1588,6 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode");
ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive");
[self __exitedHierarchy];
}
- (void)clearContents
@@ -1650,6 +1598,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
_placeholderImage = nil;
}
// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or exitInterfaceState:
- (void)recursivelyClearContents
{
for (ASDisplayNode *subnode in self.subnodes) {
@@ -1663,6 +1612,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
// subclass override
}
// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or enterInterfaceState:
- (void)recursivelyFetchData
{
for (ASDisplayNode *subnode in self.subnodes) {
@@ -1676,6 +1626,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
// subclass override
}
// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or exitInterfaceState:
- (void)recursivelyClearFetchedData
{
for (ASDisplayNode *subnode in self.subnodes) {
@@ -1684,6 +1635,67 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
[self clearFetchedData];
}
- (ASInterfaceState)interfaceState
{
ASDN::MutexLocker l(_propertyLock);
return _interfaceState;
}
- (void)setInterfaceState:(ASInterfaceState)interfaceState
{
ASDN::MutexLocker l(_propertyLock);
if (interfaceState != _interfaceState) {
if ((interfaceState & ASInterfaceStateMeasureLayout) != (_interfaceState & ASInterfaceStateMeasureLayout)) {
// Trigger asynchronous measurement if it is not already cached or being calculated.
}
// Entered or exited data loading state.
if ((interfaceState & ASInterfaceStateFetchData) != (_interfaceState & ASInterfaceStateFetchData)) {
if (interfaceState & ASInterfaceStateFetchData) {
[self fetchData];
} else {
[self clearFetchedData];
}
}
// Entered or exited contents rendering state.
if ((interfaceState & ASInterfaceStateDisplay) != (_interfaceState & ASInterfaceStateDisplay)) {
if (interfaceState & ASInterfaceStateDisplay) {
// Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here.
[self setDisplaySuspended:NO];
} else {
[self setDisplaySuspended:YES];
[self clearContents];
}
}
// Entered or exited data loading state.
if ((interfaceState & ASInterfaceStateVisible) != (_interfaceState & ASInterfaceStateVisible)) {
if (interfaceState & ASInterfaceStateVisible) {
// Consider providing a -didBecomeVisible.
} else {
// Consider providing a -didBecomeInvisible.
}
}
_interfaceState = interfaceState;
}
}
- (void)enterInterfaceState:(ASInterfaceState)interfaceState
{
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
node.interfaceState |= interfaceState;
});
}
- (void)exitInterfaceState:(ASInterfaceState)interfaceState
{
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
node.interfaceState &= (~interfaceState);
});
}
- (void)layout
{
ASDisplayNodeAssertMainThread();
@@ -1740,6 +1752,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
- (void)setNeedsDisplayAtScale:(CGFloat)contentsScale
{
ASDN::MutexLocker l(_propertyLock);
if (contentsScale != self.contentsScaleForDisplay) {
self.contentsScaleForDisplay = contentsScale;
[self setNeedsDisplay];
@@ -1748,12 +1761,9 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
- (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale
{
[self setNeedsDisplayAtScale:contentsScale];
ASDN::MutexLocker l(_propertyLock);
for (ASDisplayNode *child in _subnodes) {
[child recursivelySetNeedsDisplayAtScale:contentsScale];
}
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
[node setNeedsDisplayAtScale:contentsScale];
});
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
@@ -1889,6 +1899,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer)
_recursivelySetDisplaySuspended(self, nil, flag);
}
// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or a variant with a condition / test block.
static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, BOOL flag)
{
// If there is no layer, but node whose its view is loaded, then we can traverse down its layer hierarchy. Otherwise we must stick to the node hierarchy to avoid loading views prematurely. Note that for nodes that haven't loaded their views, they can't possibly have subviews/sublayers, so we don't need to traverse the layer hierarchy for them.

View File

@@ -26,6 +26,19 @@ extern ASDisplayNode * _Nullable ASLayerToDisplayNode(CALayer * _Nullable layer)
*/
extern ASDisplayNode * _Nullable ASViewToDisplayNode(UIView * _Nullable view);
/**
Given a node, returns the root of the node heirarchy (where supernode == nil)
*/
extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node);
/**
This function will walk the layer heirarchy, spanning discontinuous sections of the node heirarchy (e.g. the layers
of UIKit intermediate views in UIViewControllers, UITableView, UICollectionView).
In the event that a node's backing layer is not created yet, the function will only walk the direct subnodes instead
of forcing the layer heirarchy to be created.
*/
extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^block)(ASDisplayNode *node));
/**
Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block.
*/
@@ -37,7 +50,17 @@ extern id ASDisplayNodeFind(ASDisplayNode * _Nullable node, BOOL (^block)(ASDisp
extern id ASDisplayNodeFindClass(ASDisplayNode *start, Class c);
/**
Given a display node, collects all descendents. This is a specialization of ASCollectContainer() that walks the Core Animation layer tree as opposed to the display node tree, thus supporting non-continuous display node hierarchies.
* Given two nodes, finds their most immediate common parent. Used for geometry conversion methods.
* NOTE: It is an error to try to convert between nodes which do not share a common ancestor. This behavior is
* disallowed in UIKit documentation and the behavior is left undefined. The output does not have a rigorously defined
* failure mode (i.e. returning CGPointZero or returning the point exactly as passed in). Rather than track the internal
* undefined and undocumented behavior of UIKit in ASDisplayNode, this operation is defined to be incorrect in all
* circumstances and must be fixed wherever encountered.
*/
extern ASDisplayNode * _Nullable ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2);
/**
Given a display node, collects all descendents. This is a specialization of ASCollectContainer() that walks the Core Animation layer tree as opposed to the display node tree, thus supporting non-continues display node hierarchies.
*/
extern NSArray<ASDisplayNode *> *ASCollectDisplayNodes(ASDisplayNode *node);

View File

@@ -10,16 +10,42 @@
#import "ASDisplayNodeInternal.h"
ASDisplayNode *ASLayerToDisplayNode(CALayer *layer)
extern ASDisplayNode *ASLayerToDisplayNode(CALayer *layer)
{
return layer.asyncdisplaykit_node;
}
ASDisplayNode *ASViewToDisplayNode(UIView *view)
extern ASDisplayNode *ASViewToDisplayNode(UIView *view)
{
return view.asyncdisplaykit_node;
}
extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^block)(ASDisplayNode *node))
{
if (!node) {
ASDisplayNodeCAssertNotNil(layer, @"Cannot recursively perform with nil node and nil layer");
ASDisplayNodeCAssertMainThread();
node = ASLayerToDisplayNode(layer);
}
if (node) {
block(node);
}
if (!layer && [node isNodeLoaded]) {
layer = node.layer;
}
if (layer) {
for (CALayer *sublayer in [layer sublayers]) {
ASDisplayNodePerformBlockOnEveryNode(sublayer, nil, block);
}
} else if (node) {
for (ASDisplayNode *subnode in [node subnodes]) {
ASDisplayNodePerformBlockOnEveryNode(nil, subnode, block);
}
}
}
id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node))
{
CALayer *layer = node.layer;
@@ -122,6 +148,45 @@ extern __kindof ASDisplayNode * ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNo
});
}
static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possibleAncestor, ASDisplayNode *possibleDescendent)
{
ASDisplayNode *supernode = possibleDescendent;
while (supernode) {
if (supernode == possibleAncestor) {
return YES;
}
supernode = supernode.supernode;
}
return NO;
}
extern ASDisplayNode *ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2)
{
ASDisplayNode *possibleAncestor = node1;
while (possibleAncestor) {
if (_ASDisplayNodeIsAncestorOfDisplayNode(possibleAncestor, node2)) {
break;
}
possibleAncestor = possibleAncestor.supernode;
}
ASDisplayNodeCAssertNotNil(possibleAncestor, @"Could not find a common ancestor between node1: %@ and node2: %@", node1, node2);
return possibleAncestor;
}
extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node)
{
// node <- supernode on each loop
// previous <- node on each loop where node is not nil
// previous is the final non-nil value of supernode, i.e. the root node
ASDisplayNode *previousNode = node;
while ((node = [node supernode])) {
previousNode = node;
}
return previousNode;
}
#pragma mark - Placeholders
UIColor *ASDisplayNodeDefaultPlaceholderColor()

View File

@@ -12,7 +12,7 @@
#import "ASDisplayNode+Subclasses.h"
#import "ASEqualityHelpers.h"
#import "ASTextNodeTextKitHelpers.h"
#import "ASTextKitHelpers.h"
#import "ASTextNodeWordKerner.h"
#import "ASThread.h"

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASEqualityHashHelpers.h"
#import <functional>
#import <objc/runtime.h>
#import <stdio.h>
#import <string>
NSUInteger ASIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count)
{
uint64_t result = subhashes[0];
for (int ii = 1; ii < count; ++ii) {
result = ASHashCombine(result, subhashes[ii]);
}
return ASHash64ToNative(result);
}

View File

@@ -63,7 +63,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
// Image flags.
BOOL _downloadsIntermediateImages; // Defaults to NO.
OSSpinLock _imageIdentifiersLock;
ASDN::Mutex _imageIdentifiersLock;
NSArray *_imageIdentifiers;
id _loadedImageIdentifier;
id _loadingImageIdentifier;
@@ -270,23 +270,24 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
- (NSArray *)imageIdentifiers
{
OSSpinLockLock(&_imageIdentifiersLock);
NSArray *imageIdentifiers = [_imageIdentifiers copy];
OSSpinLockUnlock(&_imageIdentifiersLock);
return imageIdentifiers;
ASDN::MutexLocker l(_imageIdentifiersLock);
return _imageIdentifiers;
}
- (void)setImageIdentifiers:(NSArray *)imageIdentifiers
{
OSSpinLockLock(&_imageIdentifiersLock);
{
ASDN::MutexLocker l(_imageIdentifiersLock);
if (ASObjectIsEqual(_imageIdentifiers, imageIdentifiers)) {
return;
}
if (ASObjectIsEqual(_imageIdentifiers, imageIdentifiers)) {
OSSpinLockUnlock(&_imageIdentifiersLock);
return;
_imageIdentifiers = [[NSArray alloc] initWithArray:imageIdentifiers copyItems:YES];
}
_imageIdentifiers = [imageIdentifiers copy];
OSSpinLockUnlock(&_imageIdentifiersLock);
if (self.interfaceState & ASInterfaceStateFetchData) {
[self fetchData];
}
}
- (void)reloadImageIdentifierSources
@@ -305,7 +306,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
if (ASObjectIsEqual(displayedImageIdentifier, _displayedImageIdentifier))
return;
_displayedImageIdentifier = [displayedImageIdentifier copy];
_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.
@@ -352,11 +353,10 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
- (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierOut
{
OSSpinLockLock(&_imageIdentifiersLock);
ASDN::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) {
OSSpinLockUnlock(&_imageIdentifiersLock);
return nil;
}
@@ -365,15 +365,13 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
UIImage *image = [_dataSource multiplexImageNode:self imageForImageIdentifier:imageIdentifier];
if (image) {
if (imageIdentifierOut) {
*imageIdentifierOut = [imageIdentifier copy];
*imageIdentifierOut = imageIdentifier;
}
OSSpinLockUnlock(&_imageIdentifiersLock);
return image;
}
}
OSSpinLockUnlock(&_imageIdentifiersLock);
return nil;
}
@@ -381,12 +379,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
#pragma mark -
- (id)_nextImageIdentifierToDownload
{
OSSpinLockLock(&_imageIdentifiersLock);
ASDN::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)) {
OSSpinLockUnlock(&_imageIdentifiersLock);
return nil;
}
@@ -410,8 +407,6 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
}
}
OSSpinLockUnlock(&_imageIdentifiersLock);
return nextImageIdentifierToDownload;
}
@@ -680,9 +675,10 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
if (error && !([error.domain isEqual:ASMultiplexImageNodeErrorDomain] && error.code == ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged))
return;
OSSpinLockLock(&_imageIdentifiersLock);
_imageIdentifiersLock.lock();
NSUInteger imageIdentifierCount = [_imageIdentifiers count];
OSSpinLockUnlock(&_imageIdentifiersLock);
_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.

View File

@@ -82,8 +82,9 @@
if (reset || _URL == nil)
self.image = _defaultImage;
if (self.nodeLoaded && self.layer.superlayer)
[self _lazilyLoadImageIfNecessary];
if (self.interfaceState & ASInterfaceStateFetchData) {
[self fetchData];
}
}
- (NSURL *)URL

View File

@@ -135,13 +135,22 @@ static BOOL _isInterceptedSelector(SEL sel)
[super didTransitionToState:state];
}
- (void)setNode:(ASCellNode *)node
{
_node = node;
node.selected = self.selected;
node.highlighted = self.highlighted;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
_node.selected = selected;
}
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
[super setHighlighted:highlighted animated:animated];
_node.highlighted = highlighted;
}

View File

@@ -70,7 +70,7 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
@abstract The maximum number of lines to render of the text before truncation.
@default 0 (No limit)
*/
@property (nonatomic, assign) NSUInteger maximumLineCount;
@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
/**
@abstract The number of lines in the text. Text must have been sized first.

View File

@@ -13,13 +13,15 @@
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASTextNodeCoreTextAdditions.h>
#import <AsyncDisplayKit/ASTextNodeTextKitHelpers.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import "ASTextKitCoreTextAdditions.h"
#import "ASTextKitHelpers.h"
#import "ASTextKitRenderer.h"
#import "ASTextKitRenderer+Positioning.h"
#import "ASTextKitShadower.h"
#import "ASInternalHelpers.h"
#import "ASTextNodeRenderer.h"
#import "ASTextNodeShadower.h"
#import "ASEqualityHelpers.h"
static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15;
@@ -30,14 +32,11 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
@interface ASTextNodeDrawParameters : NSObject
- (instancetype)initWithRenderer:(ASTextNodeRenderer *)renderer
shadower:(ASTextNodeShadower *)shadower
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
textOrigin:(CGPoint)textOrigin
backgroundColor:(CGColorRef)backgroundColor;
@property (nonatomic, strong, readonly) ASTextNodeRenderer *renderer;
@property (nonatomic, strong, readonly) ASTextNodeShadower *shadower;
@property (nonatomic, strong, readonly) ASTextKitRenderer *renderer;
@property (nonatomic, assign, readonly) CGPoint textOrigin;
@@ -47,14 +46,12 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
@implementation ASTextNodeDrawParameters
- (instancetype)initWithRenderer:(ASTextNodeRenderer *)renderer
shadower:(ASTextNodeShadower *)shadower
- (instancetype)initWithRenderer:(ASTextKitRenderer *)renderer
textOrigin:(CGPoint)textOrigin
backgroundColor:(CGColorRef)backgroundColor
{
if (self = [super init]) {
_renderer = renderer;
_shadower = shadower;
_textOrigin = textOrigin;
_backgroundColor = CGColorRetain(backgroundColor);
}
@@ -91,8 +88,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
CGSize _constrainedSize;
ASTextNodeRenderer *_renderer;
ASTextNodeShadower *_shadower;
ASTextKitRenderer *_renderer;
UILongPressGestureRecognizer *_longPressGestureRecognizer;
}
@@ -117,7 +113,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
self.needsDisplayOnBoundsChange = YES;
_truncationMode = NSLineBreakByWordWrapping;
_truncationAttributedString = DefaultTruncationAttributedString();
_composedTruncationString = DefaultTruncationAttributedString();
// The common case is for a text node to be non-opaque and blended over some background.
self.opaque = NO;
@@ -181,32 +177,13 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
{
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);
// The supplied constrainedSize should include room for shadowPadding.
// Inset the constrainedSize by the shadow padding to get the size available for text.
UIEdgeInsets shadowPadding = [[self _shadower] shadowPadding];
// Invert the negative values of shadow padding to get a positive inset
UIEdgeInsets shadowPaddingOutset = ASDNEdgeInsetsInvert(shadowPadding);
// Inset the padded constrainedSize to get the remaining size available for text
CGRect constrainedRect = CGRect{CGPointZero, constrainedSize};
CGSize constrainedSizeForText = UIEdgeInsetsInsetRect(constrainedRect, shadowPaddingOutset).size;
ASDisplayNodeAssert(constrainedSizeForText.width >= 0, @"Constrained width for text (%f) after subtracting shadow padding (%@) is too narrow", constrainedSizeForText.width, NSStringFromUIEdgeInsets(shadowPadding));
ASDisplayNodeAssert(constrainedSizeForText.height >= 0, @"Constrained height for text (%f) after subtracting shadow padding (%@) is too short", constrainedSizeForText.height, NSStringFromUIEdgeInsets(shadowPadding));
_constrainedSize = constrainedSizeForText;
_constrainedSize = constrainedSize;
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
CGSize rendererSize = [[self _renderer] size];
// Add shadow padding back
CGSize renderSizePlusShadowPadding = UIEdgeInsetsInsetRect(CGRect{CGPointZero, rendererSize}, shadowPadding).size;
ASDisplayNodeAssert(renderSizePlusShadowPadding.width >= 0, @"Calculated width for text with shadow padding (%f) is too narrow", constrainedSizeForText.width);
ASDisplayNodeAssert(renderSizePlusShadowPadding.height >= 0, @"Calculated height for text with shadow padding (%f) is too short", constrainedSizeForText.height);
renderSizePlusShadowPadding = ceilSizeValue(renderSizePlusShadowPadding);
return CGSizeMake(MIN(renderSizePlusShadowPadding.width, constrainedSize.width),
MIN(renderSizePlusShadowPadding.height, constrainedSize.height));
return [[self _renderer] size];
}
- (void)displayDidFinish
@@ -269,21 +246,28 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
#pragma mark - Renderer Management
- (ASTextNodeRenderer *)_renderer
- (ASTextKitRenderer *)_renderer
{
ASDN::MutexLocker l(_rendererLock);
if (_renderer == nil) {
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : self.bounds.size;
_renderer = [[ASTextNodeRenderer alloc] initWithAttributedString:_attributedString
truncationString:_composedTruncationString
truncationMode:_truncationMode
maximumLineCount:_maximumLineCount
exclusionPaths:_exclusionPaths
constrainedSize:constrainedSize];
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
constrainedSize:constrainedSize];
}
return _renderer;
}
- (ASTextKitAttributes)_rendererAttributes
{
return {
.attributedString = _attributedString,
.truncationAttributedString = _composedTruncationString,
.lineBreakMode = _truncationMode,
.maximumNumberOfLines = _maximumNumberOfLines,
.exclusionPaths = _exclusionPaths,
};
}
- (void)_invalidateRenderer
{
ASDN::MutexLocker l(_rendererLock);
@@ -291,7 +275,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
// Destruction of the layout managers/containers/text storage is quite
// expensive, and can take some time, so we dispatch onto a bg queue to
// actually dealloc.
__block ASTextNodeRenderer *renderer = _renderer;
__block ASTextKitRenderer *renderer = _renderer;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
renderer = nil;
});
@@ -299,23 +283,6 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
_renderer = nil;
}
#pragma mark - Shadow Drawer Management
- (ASTextNodeShadower *)_shadower
{
if (_shadower == nil) {
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:_shadowOffset
shadowColor:_shadowColor
shadowOpacity:_shadowOpacity
shadowRadius:_shadowRadius];
}
return _shadower;
}
- (void)_invalidateShadower
{
_shadower = nil;
}
#pragma mark - Modifying User Text
- (void)setAttributedString:(NSAttributedString *)attributedString {
@@ -399,11 +366,11 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
}
// Draw shadow
[parameters.shadower setShadowInContext:context];
[[parameters.renderer shadower] setShadowInContext:context];
// Draw text
bounds.origin = parameters.textOrigin;
[parameters.renderer drawInRect:bounds inContext:context];
[parameters.renderer drawInContext:context bounds:bounds];
CGContextRestoreGState(context);
}
@@ -414,9 +381,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
UIEdgeInsets shadowPadding = [self shadowPadding];
CGPoint textOrigin = CGPointMake(self.bounds.origin.x - shadowPadding.left, self.bounds.origin.y - shadowPadding.top);
return [[ASTextNodeDrawParameters alloc] initWithRenderer:[self _renderer]
shadower:[self _shadower]
textOrigin:textOrigin
backgroundColor:self.backgroundColor.CGColor];
textOrigin:textOrigin
backgroundColor:self.backgroundColor.CGColor];
}
#pragma mark - Attributes
@@ -436,8 +402,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
range:(out NSRange *)rangeOut
inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut
{
ASTextNodeRenderer *renderer = [self _renderer];
NSRange visibleRange = [renderer visibleRange];
ASTextKitRenderer *renderer = [self _renderer];
NSRange visibleRange = renderer.visibleRanges[0];
NSAttributedString *attributedString = _attributedString;
// Check in a 9-point region around the actual touch point so we make sure
@@ -635,10 +601,11 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
}
if (highlightTargetLayer != nil) {
NSArray *highlightRects = [[self _renderer] rectsForTextRange:highlightRange measureOption:ASTextNodeRendererMeasureOptionBlock];
NSArray *highlightRects = [[self _renderer] rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock];
NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count];
for (NSValue *rectValue in highlightRects) {
CGRect rendererRect = [[self class] _adjustRendererRect:rectValue.CGRectValue forShadowPadding:_shadower.shadowPadding];
UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding;
CGRect rendererRect = [[self class] _adjustRendererRect:rectValue.CGRectValue forShadowPadding:shadowPadding];
CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer];
// We set our overlay layer's frame to the bounds of the highlight target layer.
@@ -699,7 +666,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
return rendererRect;
}
- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextNodeRendererMeasureOption)measureOption
- (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption
{
NSArray *rects = [[self _renderer] rectsForTextRange:textRange measureOption:measureOption];
NSMutableArray *adjustedRects = [NSMutableArray array];
@@ -717,12 +684,12 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
- (NSArray *)rectsForTextRange:(NSRange)textRange
{
return [self _rectsForTextRange:textRange measureOption:ASTextNodeRendererMeasureOptionCapHeight];
return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionCapHeight];
}
- (NSArray *)highlightRectsForTextRange:(NSRange)textRange
{
return [self _rectsForTextRange:textRange measureOption:ASTextNodeRendererMeasureOptionBlock];
return [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionBlock];
}
- (CGRect)trailingRect
@@ -755,11 +722,11 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
UIGraphicsBeginImageContext(size);
[self.placeholderColor setFill];
ASTextNodeRenderer *renderer = [self _renderer];
NSRange textRange = [renderer visibleRange];
ASTextKitRenderer *renderer = [self _renderer];
NSRange textRange = renderer.visibleRanges[0];
// cap height is both faster and creates less subpixel blending
NSArray *lineRects = [self _rectsForTextRange:textRange measureOption:ASTextNodeRendererMeasureOptionLineHeight];
NSArray *lineRects = [self _rectsForTextRange:textRange measureOption:ASTextKitRendererMeasureOptionLineHeight];
// fill each line with the placeholder color
for (NSValue *rectValue in lineRects) {
@@ -830,7 +797,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
BOOL linkCrossesVisibleRange = (lastCharIndex > range.location) && (lastCharIndex < NSMaxRange(range) - 1);
if (inAdditionalTruncationMessage) {
NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:[[self _renderer] visibleRange]];
NSRange visibleRange = [self _renderer].visibleRanges[0];
NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange];
[self _setHighlightRange:truncationMessageRange forAttributeName:ASTextNodeTruncationTokenAttributeName value:nil animated:YES];
} else if (range.length && !linkCrossesVisibleRange && linkAttributeValue != nil && linkAttributeName != nil) {
[self _setHighlightRange:range forAttributeName:linkAttributeName value:linkAttributeValue animated:YES];
@@ -905,7 +873,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
CGColorRetain(shadowColor);
}
_shadowColor = shadowColor;
[self _invalidateShadower];
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
@@ -921,7 +889,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
{
if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) {
_shadowOffset = shadowOffset;
[self _invalidateShadower];
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
@@ -937,7 +905,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
{
if (_shadowOpacity != shadowOpacity) {
_shadowOpacity = shadowOpacity;
[self _invalidateShadower];
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
@@ -953,7 +921,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
{
if (_shadowRadius != shadowRadius) {
_shadowRadius = shadowRadius;
[self _invalidateShadower];
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];
});
@@ -962,7 +930,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
- (UIEdgeInsets)shadowPadding
{
return [[self _shadower] shadowPadding];
return [self _renderer].shadower.shadowPadding;
}
#pragma mark - Truncation Message
@@ -1010,13 +978,14 @@ static NSAttributedString *DefaultTruncationAttributedString()
- (BOOL)isTruncated
{
return [[self _renderer] truncationStringCharacterRange].location != NSNotFound;
NSRange visibleRange = [self _renderer].visibleRanges[0];
return visibleRange.length < _attributedString.length;
}
- (void)setMaximumLineCount:(NSUInteger)maximumLineCount
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
{
if (_maximumLineCount != maximumLineCount) {
_maximumLineCount = maximumLineCount;
if (_maximumNumberOfLines != maximumNumberOfLines) {
_maximumNumberOfLines = maximumNumberOfLines;
[self _invalidateRenderer];
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
[self setNeedsDisplay];

View File

@@ -65,12 +65,6 @@
#import <AsyncDisplayKit/ASRangeHandler.h>
#import <AsyncDisplayKit/ASRangeHandlerPreload.h>
#import <AsyncDisplayKit/ASRangeHandlerRender.h>
#import <AsyncDisplayKit/ASTextNodeCoreTextAdditions.h>
#import <AsyncDisplayKit/ASTextNodeRenderer.h>
#import <AsyncDisplayKit/ASTextNodeShadower.h>
#import <AsyncDisplayKit/ASTextNodeTextKitHelpers.h>
#import <AsyncDisplayKit/ASTextNodeTypes.h>
#import <AsyncDisplayKit/ASTextNodeWordKerner.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/CGRect+ASConvenience.h>
#import <AsyncDisplayKit/NSMutableAttributedString+TextKitAdditions.h>

View File

@@ -26,14 +26,19 @@
}
_tuningParameters = std::vector<ASRangeTuningParameters>(ASLayoutRangeTypeCount);
_tuningParameters[ASLayoutRangeTypeVisible] = {
.leadingBufferScreenfuls = 0,
.trailingBufferScreenfuls = 0
};
_tuningParameters[ASLayoutRangeTypeRender] = {
.leadingBufferScreenfuls = 1.5,
.trailingBufferScreenfuls = 0.75
};
_tuningParameters[ASLayoutRangeTypePreload] = {
.leadingBufferScreenfuls = 3,
.trailingBufferScreenfuls = 2
};
_tuningParameters[ASLayoutRangeTypeRender] = {
.leadingBufferScreenfuls = 2,
.trailingBufferScreenfuls = 1
};
return self;
}
@@ -49,6 +54,7 @@
- (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
{
ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters");
ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, @"Must not set Visible range tuning parameters (always 0, 0)");
_tuningParameters[rangeType] = tuningParameters;
}

View File

@@ -15,6 +15,11 @@
@protocol ASCollectionViewLayoutInspecting <NSObject>
/**
* Provides the size range needed to measure the collection view's item.
*/
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath;
/**
* Asks the inspector to provide a constrained size range for the given supplementary node.
*/
@@ -30,6 +35,8 @@
*/
- (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section;
@optional
/**
* Allow the inspector to respond to delegate changes.
*

View File

@@ -49,6 +49,12 @@
#pragma mark - ASCollectionViewLayoutInspecting
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath
{
// TODO: Provide constrained size for flow layout item nodes
return ASSizeRangeMake(CGSizeZero, CGSizeZero);
}
- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
CGSize constrainedSize;

View File

@@ -9,6 +9,7 @@
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, ASLayoutRangeType) {
ASLayoutRangeTypeVisible,
ASLayoutRangeTypeRender,
ASLayoutRangeTypePreload,
ASLayoutRangeTypeCount

View File

@@ -11,6 +11,7 @@
#import "ASAssert.h"
#import "ASDisplayNodeExtras.h"
#import "ASMultiDimensionalArrayUtils.h"
#import "ASRangeHandlerVisible.h"
#import "ASRangeHandlerRender.h"
#import "ASRangeHandlerPreload.h"
#import "ASInternalHelpers.h"
@@ -36,6 +37,7 @@
_rangeIsValid = YES;
_rangeTypeIndexPaths = [NSMutableDictionary dictionary];
_rangeTypeHandlers = @{
@(ASLayoutRangeTypeVisible): [[ASRangeHandlerVisible alloc] init],
@(ASLayoutRangeTypeRender): [[ASRangeHandlerRender alloc] init],
@(ASLayoutRangeTypePreload): [[ASRangeHandlerPreload alloc] init],
};

View File

@@ -7,22 +7,21 @@
*/
#import "ASRangeHandlerPreload.h"
#import "ASDisplayNode.h"
#import "ASDisplayNode+Subclasses.h"
#import "ASDisplayNodeInternal.h"
@implementation ASRangeHandlerPreload
- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType
{
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypePreload, @"Preload delegate should not handle other ranges");
[node recursivelyFetchData];
[node enterInterfaceState:ASInterfaceStateFetchData];
}
- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType
{
ASDisplayNodeAssert(rangeType == ASLayoutRangeTypePreload, @"Preload delegate should not handle other ranges");
[node recursivelyClearFetchedData];
[node exitInterfaceState:ASInterfaceStateFetchData];
}
@end

View File

@@ -57,7 +57,8 @@
[node.view removeFromSuperview];
}
[node recursivelySetDisplaySuspended:NO];
// The node un-suspends display.
[node enterInterfaceState:ASInterfaceStateDisplay];
// Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile.
// Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations.
@@ -89,7 +90,8 @@
// preservation of this content could result in the app being killed, which is not likely preferable over briefly seeing placeholders in the event the user scrolls backwards.
// Nonetheless, future changes to the implementation will likely eliminate this behavior to simplify debugging and extensibility of working range functionality.
[node recursivelySetDisplaySuspended:YES];
// The node calls clearCurrentContents and suspends display
[node exitInterfaceState:ASInterfaceStateDisplay];
if (node.layer.superlayer != [[self workingWindow] layer]) {
// In this case, the node has previously passed through the working range (or it is zero), and it has now fallen outside the working range.
@@ -102,7 +104,6 @@
// At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell.
[node.layer removeFromSuperlayer];
[node recursivelyClearContents];
}
@end

View File

@@ -0,0 +1,15 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import <AsyncDisplayKit/ASRangeHandler.h>
@interface ASRangeHandlerVisible : NSObject <ASRangeHandler>
@end

View File

@@ -0,0 +1,25 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASRangeHandlerVisible.h"
#import "ASDisplayNode.h"
#import "ASDisplayNodeInternal.h"
@implementation ASRangeHandlerVisible
- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType
{
[node enterInterfaceState:ASInterfaceStateVisible];
}
- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType
{
[node exitInterfaceState:ASInterfaceStateVisible];
}
@end

View File

@@ -1,191 +0,0 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^as_renderer_index_block_t)(NSUInteger characterIndex,
CGRect glyphBoundingRect,
BOOL *stop);
/*
* Measure options are used to specify which type of line height measurement to
* use.
*
* ASTextNodeRendererMeasureOptionLineHeight is faster and will give the
* height from the baseline to the next line.
*
* ASTextNodeRendererMeasureOptionCapHeight is a more nuanced measure of the
* glyphs in the given range that attempts to produce a visually balanced
* rectangle above and below the glyphs to produce nice looking text highlights.
*
* ASTextNodeRendererMeasureOptionBlock uses the cap height option to
* generate each glyph index, but combines all but the first and last line rect
* into a single block. Looks nice for multiline selection.
*
*/
typedef NS_ENUM(NSUInteger, ASTextNodeRendererMeasureOption) {
ASTextNodeRendererMeasureOptionLineHeight,
ASTextNodeRendererMeasureOptionCapHeight,
ASTextNodeRendererMeasureOptionBlock
};
/*
* This is an immutable textkit renderer that is responsible for sizing and
* rendering text.
*
* @discussion This class implements internal locking to allow it to be used
* safely from background threads. It is recommended that you create and cache a
* renderer for each combination of parameters.
*/
@interface ASTextNodeRenderer : NSObject
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
truncationString:(nullable NSAttributedString *)truncationString
truncationMode:(NSLineBreakMode)truncationMode
maximumLineCount:(NSUInteger)maximumLineCount
exclusionPaths:(nullable NSArray<UIBezierPath *> *)exclusionPaths
constrainedSize:(CGSize)constrainedSize;
/*
* Designated Initializer
*
* @discussion No sizing occurs as a result of initializing a renderer.
* Instead, sizing and truncation operations occur lazily as they are needed,
* so feel free
*/
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
truncationString:(nullable NSAttributedString *)truncationString
truncationMode:(NSLineBreakMode)truncationMode
maximumLineCount:(NSUInteger)maximumLineCount
constrainedSize:(CGSize)constrainedSize;
#pragma mark - Drawing
/*
* Draw the renderer's text content into the bounds provided.
*
* @param bounds The rect in which to draw the contents of the renderer.
* @param context The CGContext in which to draw the contents of the renderer.
*
* @discussion Note that if a shadow is to be drawn, then the text will actually
* draw inside a region that is inset from the bounds provided. Use
* shadowPadding to properly transform the bounds such that this is correct for
* your use-case. See shadowPadding docs for more.
*
* Initializes the textkit components lazily if they have not yet been created.
* You may want to consider triggering this cost before hitting the draw method
* if you are sensitive to this cost in drawInRect...
*/
- (void)drawInRect:(CGRect)bounds inContext:(CGContextRef)context;
#pragma mark - Layout
/*
* Returns the computed size of the renderer given the constrained size and
* other parameters in the initializer.
*
* @discussion No actual computation is done in this method. It simply returns
* the cached calculated size from initialization so this is very cheap to call.
*
* Triggers initialization of textkit components, truncation, and sizing.
*/
- (CGSize)size;
/*
* Returns the trailing rect unused by the renderer in the last rendered line.
*
* @discussion In the coordinate space of the renderer.
*
* Triggers initialization of textkit components, truncation, and sizing.
*/
- (CGRect)trailingRect;
/*
* Returns the bounding rect for the given character range.
*
* @param textRange The character range for which the bounding rect will be
* computed. Should be within the range of the attributedString of this
* renderer.
*
* @discussion In the coordinate space of the renderer.
*
* Triggers initialization of textkit components, truncation, and sizing.
*/
- (CGRect)frameForTextRange:(NSRange)textRange;
/*
* Returns an array of rects representing the lines in the given character range
*
* @param textRange The character range for which the rects will be computed.
* should be within the range of the attributedString of this renderer.
* @param measureOption The measure option to use for construction of the rects.
* see ASTextNodeRendererMeasureOption docs for usage.
*
* @discussion This method is useful for providing highlighting text. Returned
* rects are in the coordinate space of the renderer.
*
* Triggers initialization of textkit components, truncation, and sizing.
*/
- (NSArray<NSValue *> *)rectsForTextRange:(NSRange)textRange
measureOption:(ASTextNodeRendererMeasureOption)measureOption;
/*
* Enumerate the text character indexes at a position within the coordinate
* space of the renderer.
*
* @param position The point inside the coordinate space of the renderer at
* which text indexes will be enumerated.
* @param block The block that will be executed for each index identified that
* may correspond to the given position. The block is given the character index
* that corresponds to the glyph at each index in question, as well as the
* bounding rect for that glyph.
*
* @discussion Glyph location based on a touch point is not an exact science
* because user touches are not well-represented by a simple point, especially
* in the context of link-heavy text. So we have this method to make it a bit
* easier. This method checks a grid of candidate positions around the touch
* point you give it, and computes the bounding rect of the glyph corresponding
* to the character index given.
*
* The bounding rect of the glyph can be used to identify the best glyph index
* that corresponds to your touch. For instance, comparing centroidal distance
* from the glyph bounding rect to the touch center is useful for identifying
* which link a user actually intended to select.
*
* Triggers initialization of textkit components, truncation, and sizing.
*/
- (void)enumerateTextIndexesAtPosition:(CGPoint)position
usingBlock:(as_renderer_index_block_t)block;
#pragma mark - Text Ranges
/*
* The character range that represents the truncationString provided in the
* initializer. location will be NSNotFound if no truncation occurred.
*
* Triggers initialization of textkit components, truncation, and sizing.
*/
- (NSRange)truncationStringCharacterRange;
/*
* The character range from the original attributedString that is displayed by
* the renderer given the parameters in the initializer.
*
* Triggers initialization of textkit components, truncation, and sizing.
*/
- (NSRange)visibleRange;
/*
* The number of lines shown in the string.
*
* Triggers initialization of textkit components, truncation, and sizing.
*/
- (NSUInteger)lineCount;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,654 +0,0 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASTextNodeRenderer.h"
#import <CoreText/CoreText.h>
#import "ASAssert.h"
#import "ASTextNodeTextKitHelpers.h"
#import "ASTextNodeWordKerner.h"
#import "ASThread.h"
static const CGFloat ASTextNodeRendererGlyphTouchHitSlop = 5.0;
static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3;
@interface ASTextNodeRenderer ()
@end
@implementation ASTextNodeRenderer {
CGSize _constrainedSize;
CGSize _calculatedSize;
NSAttributedString *_attributedString;
NSAttributedString *_truncationString;
NSLineBreakMode _truncationMode;
NSUInteger _maximumLineCount;
NSRange _truncationCharacterRange;
NSRange _visibleRange;
ASTextNodeWordKerner *_wordKerner;
ASDN::RecursiveMutex _textKitLock;
NSLayoutManager *_layoutManager;
NSTextStorage *_textStorage;
NSTextContainer *_textContainer;
NSArray *_exclusionPaths;
}
#pragma mark - Initialization
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
truncationString:(NSAttributedString *)truncationString
truncationMode:(NSLineBreakMode)truncationMode
maximumLineCount:(NSUInteger)maximumLineCount
exclusionPaths:(NSArray *)exclusionPaths
constrainedSize:(CGSize)constrainedSize
{
if (self = [super init]) {
_attributedString = attributedString;
_truncationString = truncationString;
_truncationMode = truncationMode;
_truncationCharacterRange = NSMakeRange(NSNotFound, truncationString.length);
_maximumLineCount = maximumLineCount;
_exclusionPaths = exclusionPaths;
_constrainedSize = constrainedSize;
}
return self;
}
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
truncationString:(NSAttributedString *)truncationString
truncationMode:(NSLineBreakMode)truncationMode
maximumLineCount:(NSUInteger)maximumLineCount
constrainedSize:(CGSize)constrainedSize
{
return [self initWithAttributedString:attributedString truncationString:truncationString truncationMode:truncationMode maximumLineCount:maximumLineCount exclusionPaths:nil constrainedSize:constrainedSize];
}
/*
* Use this method to lazily construct the TextKit components.
*/
- (void)_initializeTextKitComponentsIfNeeded
{
ASDN::MutexLocker l(_textKitLock);
if (_layoutManager == nil) {
[self _initializeTextKitComponentsWithAttributedString:_attributedString];
}
}
- (void)_initializeTextKitComponentsWithAttributedString:(NSAttributedString *)attributedString
{
ASDN::MutexLocker l(_textKitLock);
// Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. :(
static ASDN::StaticMutex mutex = ASDISPLAYNODE_MUTEX_INITIALIZER;
ASDN::StaticMutexLocker gl(mutex);
// Create the TextKit component stack with our default configuration.
_textStorage = (attributedString ? [[NSTextStorage alloc] initWithAttributedString:attributedString] : [[NSTextStorage alloc] init]);
_layoutManager = [[NSLayoutManager alloc] init];
_layoutManager.usesFontLeading = NO;
_wordKerner = [[ASTextNodeWordKerner alloc] init];
_layoutManager.delegate = _wordKerner;
[_textStorage addLayoutManager:_layoutManager];
_textContainer = [[NSTextContainer alloc] initWithSize:_constrainedSize];
// We want the text laid out up to the very edges of the container.
_textContainer.lineFragmentPadding = 0;
// Translate our truncation mode into a line break mode on the container
_textContainer.lineBreakMode = _truncationMode;
// Set maximum number of lines
_textContainer.maximumNumberOfLines = _maximumLineCount;
_textContainer.exclusionPaths = _exclusionPaths;
[_layoutManager addTextContainer:_textContainer];
ASDN::StaticMutexUnlocker gu(mutex);
[self _invalidateLayout];
}
#pragma mark - Layout Initialization
- (void)_invalidateLayout
{
ASDN::MutexLocker l(_textKitLock);
// Force a layout, which means we have to recompute our truncation parameters
NSInteger originalStringLength = _textStorage.string.length;
[self _calculateSize];
NSRange visibleGlyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
_visibleRange = [_layoutManager characterRangeForGlyphRange:visibleGlyphRange actualGlyphRange:NULL];
// Check if text is truncated, and if so apply our truncation string
if (_visibleRange.length < originalStringLength && _truncationString.length > 0) {
NSInteger firstCharacterIndexToReplace = [self _calculateCharacterIndexBeforeTruncationMessage];
if (firstCharacterIndexToReplace == 0 || firstCharacterIndexToReplace == NSNotFound) {
// Something went horribly wrong, short-circuit
[self _calculateSize];
return;
}
// Update/truncate the visible range of text
_visibleRange = NSMakeRange(0, firstCharacterIndexToReplace);
NSRange truncationReplacementRange = NSMakeRange(firstCharacterIndexToReplace, _textStorage.length - firstCharacterIndexToReplace);
// Replace the end of the visible message with the truncation string
[_textStorage replaceCharactersInRange:truncationReplacementRange
withAttributedString:_truncationString];
_truncationCharacterRange = NSMakeRange(firstCharacterIndexToReplace, _truncationString.length);
// We must recompute the calculated size because we may have changed it in
// changing the string
[self _calculateSize];
}
}
#pragma mark - Sizing
/*
* Calculates the size of the text in the renderer based on the parameters
* stored in the ivars of this class.
*
* This method can be expensive, so it is important that it not be called
* frequently. It not only sizes the text, but it also configures the TextKit
* components for drawing, and responding to all other queries made to this
* class.
*/
- (void)_calculateSize
{
ASDN::MutexLocker l(_textKitLock);
if (_attributedString.length == 0) {
_calculatedSize = CGSizeZero;
return;
}
[self _initializeTextKitComponentsIfNeeded];
// Force glyph generation and layout, which may not have happened yet (and
// isn't triggered by -usedRectForTextContainer:).
[_layoutManager ensureLayoutForTextContainer:_textContainer];
CGRect constrainedRect = CGRect{CGPointZero, _constrainedSize};
CGRect boundingRect = [_layoutManager usedRectForTextContainer:_textContainer];
// TextKit often returns incorrect glyph bounding rects in the horizontal
// direction, so we clip to our bounding rect to make sure our width
// calculations aren't being offset by glyphs going beyond the constrained
// rect.
boundingRect = CGRectIntersection(boundingRect, (CGRect){.size = constrainedRect.size});
_calculatedSize = boundingRect.size;
}
- (CGSize)size
{
[self _initializeTextKitComponentsIfNeeded];
return _calculatedSize;
}
#pragma mark - Layout
- (CGRect)trailingRect
{
ASDN::MutexLocker l(_textKitLock);
[self _initializeTextKitComponentsIfNeeded];
// If have an empty string, then our whole bounds constitute trailing space.
if ([_textStorage length] == 0) {
return CGRectMake(0, 0, _calculatedSize.width, _calculatedSize.height);
}
// Take everything after our final character as trailing space.
NSArray *finalRects = [self rectsForTextRange:NSMakeRange([_textStorage length] - 1, 1) measureOption:ASTextNodeRendererMeasureOptionLineHeight];
CGRect finalGlyphRect = [[finalRects lastObject] CGRectValue];
CGPoint origin = CGPointMake(CGRectGetMaxX(finalGlyphRect), CGRectGetMinY(finalGlyphRect));
CGSize size = CGSizeMake(_calculatedSize.width - origin.x, _calculatedSize.height - origin.y);
return (CGRect){origin, size};
}
- (CGRect)frameForTextRange:(NSRange)textRange
{
ASDN::MutexLocker l(_textKitLock);
[self _initializeTextKitComponentsIfNeeded];
// Bail on invalid range.
if (NSMaxRange(textRange) > [_textStorage length]) {
ASDisplayNodeAssertNotNil(nil, @"Invalid range");
return CGRectZero;
}
// Force glyph generation and layout.
[_layoutManager ensureLayoutForTextContainer:_textContainer];
NSRange glyphRange = [_layoutManager glyphRangeForCharacterRange:textRange actualCharacterRange:NULL];
CGRect textRect = [_layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:_textContainer];
return textRect;
}
- (NSArray *)rectsForTextRange:(NSRange)textRange
measureOption:(ASTextNodeRendererMeasureOption)measureOption
{
ASDN::MutexLocker l(_textKitLock);
[self _initializeTextKitComponentsIfNeeded];
BOOL textRangeIsValid = (NSMaxRange(textRange) <= [_textStorage length]);
ASDisplayNodeAssertTrue(textRangeIsValid);
if (!textRangeIsValid) {
return @[];
}
// Used for block measure option
__block CGRect firstRect = CGRectNull;
__block CGRect lastRect = CGRectNull;
__block CGRect blockRect = CGRectNull;
NSMutableArray *textRects = [NSMutableArray array];
NSString *string = _textStorage.string;
NSRange totalGlyphRange = [_layoutManager glyphRangeForCharacterRange:textRange actualCharacterRange:NULL];
[_layoutManager enumerateLineFragmentsForGlyphRange:totalGlyphRange usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) {
CGRect lineRect = CGRectNull;
// If we're empty, don't bother looping through glyphs, use the default.
if (CGRectIsEmpty(usedRect)) {
lineRect = usedRect;
} else {
// TextKit's bounding rect computations are just a touch off, so we actually
// compose the rects by hand from the center of the given TextKit bounds and
// imposing the font attributes returned by the glyph's font.
NSRange lineGlyphRange = NSIntersectionRange(totalGlyphRange, glyphRange);
for (NSUInteger i = lineGlyphRange.location; i < NSMaxRange(lineGlyphRange) && i < string.length; i++) {
// We grab the properly sized rect for the glyph
CGRect properGlyphRect = [self _rectForGlyphAtIndex:i measureOption:measureOption];
// Don't count empty glyphs towards our line rect.
if (!CGRectIsEmpty(properGlyphRect)) {
lineRect = CGRectIsNull(lineRect) ? properGlyphRect
: CGRectUnion(lineRect, properGlyphRect);
}
}
}
if (!CGRectIsNull(lineRect)) {
if (measureOption == ASTextNodeRendererMeasureOptionBlock) {
// For the block measurement option we store the first & last rect as
// special cases, then merge everything else into a single block rect
if (CGRectIsNull(firstRect)) {
// We don't have a firstRect, so we must be on the first line.
firstRect = lineRect;
} else if(CGRectIsNull(lastRect)) {
// We don't have a lastRect, but we do have a firstRect, so we must
// be on the second line. No need to merge in the blockRect just yet
lastRect = lineRect;
} else if(CGRectIsNull(blockRect)) {
// We have both a first and last rect, so we must be on the third line
// we don't have any blockRect to merge it into, so we just set it
// directly.
blockRect = lastRect;
lastRect = lineRect;
} else {
// Everything is already set, so we just merge this line into the
// block.
blockRect = CGRectUnion(blockRect, lastRect);
lastRect = lineRect;
}
} else {
// If the block option isn't being used then each line is being treated
// individually.
[textRects addObject:[NSValue valueWithCGRect:lineRect]];
}
}
}];
if (measureOption == ASTextNodeRendererMeasureOptionBlock) {
// Block measure option is handled differently with just 3 vars for the entire range.
if (!CGRectIsNull(firstRect)) {
if (!CGRectIsNull(blockRect)) {
CGFloat rightEdge = MAX(CGRectGetMaxX(blockRect), CGRectGetMaxX(lastRect));
if (rightEdge > CGRectGetMaxX(firstRect)) {
// Force the right side of the first rect to properly align with the
// right side of the rightmost of the block and last rect
firstRect.size.width += rightEdge - CGRectGetMaxX(firstRect);
}
// Force the left side of the block rect to properly align with the
// left side of the leftmost of the first and last rect
blockRect.origin.x = MIN(CGRectGetMinX(firstRect), CGRectGetMinX(lastRect));
// Force the right side of the block rect to properly align with the
// right side of the rightmost of the first and last rect
blockRect.size.width += MAX(CGRectGetMaxX(firstRect), CGRectGetMaxX(lastRect)) - CGRectGetMaxX(blockRect);
}
if (!CGRectIsNull(lastRect)) {
// Force the left edge of the last rect to properly align with the
// left side of the leftmost of the first and block rect, if necessary.
CGFloat leftEdge = MIN(CGRectGetMinX(blockRect), CGRectGetMinX(firstRect));
CGFloat lastRectNudgeAmount = MAX(CGRectGetMinX(lastRect) - leftEdge, 0);
lastRect.origin.x = MIN(leftEdge, CGRectGetMinX(lastRect));
lastRect.size.width += lastRectNudgeAmount;
}
[textRects addObject:[NSValue valueWithCGRect:firstRect]];
}
if (!CGRectIsNull(blockRect)) {
[textRects addObject:[NSValue valueWithCGRect:blockRect]];
}
if (!CGRectIsNull(lastRect)) {
[textRects addObject:[NSValue valueWithCGRect:lastRect]];
}
}
return textRects;
}
- (CGRect)_rectForGlyphAtIndex:(NSUInteger)glyphIndex
measureOption:(ASTextNodeRendererMeasureOption)measureOption
{
ASDN::MutexLocker l(_textKitLock);
NSUInteger charIndex = [_layoutManager characterIndexForGlyphAtIndex:glyphIndex];
CGGlyph glyph = [_layoutManager glyphAtIndex:glyphIndex];
CTFontRef font = (__bridge_retained CTFontRef)[_textStorage attribute:NSFontAttributeName
atIndex:charIndex
effectiveRange:NULL];
if (font == nil) {
font = (__bridge_retained CTFontRef)[UIFont systemFontOfSize:12.0];
}
// Glyph Advance
// +-------------------------+
// | |
// | |
// +------------------------+--|-------------------------|--+-----------+-----+ What TextKit returns sometimes
// | | | XXXXXXXXXXX + | | | (approx. correct height, but
// | ---------|--+---------+ XXX XXXX +|-----------|-----| sometimes inaccurate bounding
// | | | XXX XXXXX| | | widths)
// | | | XX XX | | |
// | | | XX | | |
// | | | XXX | | |
// | | | XX | | |
// | | | XXXXXXXXXXX | | |
// | Cap Height->| | XX | | |
// | | | XX | Ascent-->| |
// | | | XX | | |
// | | | XX | | |
// | | | X | | |
// | | | X | | |
// | | | X | | |
// | | | XX | | |
// | | | X | | |
// | ---------|-------+ X +-------------------------------------|
// | | XX | |
// | | X | |
// | | XX Descent------>| |
// | | XXXXXX | |
// | | XXX | |
// +------------------------+-------------------------------------------------+
// |
// +--+Actual bounding box
CGRect glyphRect = [_layoutManager boundingRectForGlyphRange:NSMakeRange(glyphIndex, 1)
inTextContainer:_textContainer];
// If it is a NSTextAttachment, we don't have the matched glyph and use width of glyphRect instead of advance.
CGFloat advance = (glyph == kCGFontIndexInvalid) ? glyphRect.size.width : CTFontGetAdvancesForGlyphs(font, kCTFontOrientationHorizontal, &glyph, NULL, 1);
// We treat the center of the glyph's bounding box as the center of our new rect
CGPoint glyphCenter = CGPointMake(CGRectGetMidX(glyphRect), CGRectGetMidY(glyphRect));
CGRect properGlyphRect;
if (measureOption == ASTextNodeRendererMeasureOptionCapHeight
|| measureOption == ASTextNodeRendererMeasureOptionBlock) {
CGFloat ascent = CTFontGetAscent(font);
CGFloat descent = CTFontGetDescent(font);
CGFloat capHeight = CTFontGetCapHeight(font);
CGFloat leading = CTFontGetLeading(font);
CGFloat glyphHeight = ascent + descent;
// For visual balance, we add the cap height padding above the cap, and
// below the baseline, we scale by the descent so it grows with the size of
// the text.
CGFloat topPadding = ASTextNodeRendererTextCapHeightPadding * descent;
CGFloat bottomPadding = topPadding;
properGlyphRect = CGRectMake(glyphCenter.x - advance * 0.5,
glyphCenter.y - glyphHeight * 0.5 + (ascent - capHeight) - topPadding + leading,
advance,
capHeight + topPadding + bottomPadding);
} else {
// We are just measuring the line heights here, so we can use the
// heights used by TextKit, which tend to be pretty good.
properGlyphRect = CGRectMake(glyphCenter.x - advance * 0.5,
glyphRect.origin.y,
advance,
glyphRect.size.height);
}
CFRelease(font);
return properGlyphRect;
}
- (void)enumerateTextIndexesAtPosition:(CGPoint)position usingBlock:(as_renderer_index_block_t)block
{
if (position.x > _constrainedSize.width
|| position.y > _constrainedSize.height
|| block == NULL) {
// Short circuit if the position is outside the size of this renderer, or
// if the block is null.
return;
}
ASDN::MutexLocker l(_textKitLock);
[self _initializeTextKitComponentsIfNeeded];
// We break it up into a 44pt box for the touch, and find the closest link
// attribute-containing glyph to the center of the touch.
CGFloat squareSide = 44.f;
// Should be odd if you want to test the center of the touch.
NSInteger pointsOnASide = 3;
// The distance between any 2 of the adjacent points
CGFloat pointSeparation = squareSide / pointsOnASide;
// These are for tracking which point we're on. We start with -pointsOnASide/2
// and go to pointsOnASide/2. So if pointsOnASide=3, we go from -1 to 1.
NSInteger endIndex = pointsOnASide / 2;
NSInteger startIndex = -endIndex;
BOOL stop = NO;
for (NSInteger i = startIndex; i <= endIndex && !stop; i++) {
for (NSInteger j = startIndex; j <= endIndex && !stop; j++) {
CGPoint currentPoint = CGPointMake(position.x + i * pointSeparation,
position.y + j * pointSeparation);
// We ask the layout manager for the proper glyph at the touch point
NSUInteger glyphIndex = [_layoutManager glyphIndexForPoint:currentPoint
inTextContainer:_textContainer];
// If it's an invalid glyph, quit.
BOOL isValidGlyph = NO;
[_layoutManager glyphAtIndex:glyphIndex isValidIndex:&isValidGlyph];
if (!isValidGlyph) {
continue;
}
NSUInteger characterIndex = [_layoutManager characterIndexForGlyphAtIndex:glyphIndex];
CGRect glyphRect = [self _rectForGlyphAtIndex:glyphIndex
measureOption:ASTextNodeRendererMeasureOptionLineHeight];
// Sometimes TextKit plays jokes on us and returns glyphs that really
// aren't close to the point in question. Silly TextKit...
if (!CGRectContainsPoint(CGRectInset(glyphRect, -ASTextNodeRendererGlyphTouchHitSlop, -ASTextNodeRendererGlyphTouchHitSlop), currentPoint)) {
continue;
}
block(characterIndex, glyphRect, &stop);
}
}
}
#pragma mark - Truncation
/*
* Calculates the intersection of the truncation message within the end of the
* last line.
*
* This is accomplished by temporarily adding an exclusion rect for the size of
* the truncation string at the end of the last line of text, and forcing the
* layout manager to re-layout and clip the text such that we get a natural
* clipping based on the settings of the layout manager.
*/
- (NSUInteger)_calculateCharacterIndexBeforeTruncationMessage
{
ASDN::MutexLocker l(_textKitLock);
CGRect constrainedRect = (CGRect){.size = _calculatedSize};
NSRange visibleGlyphRange = [_layoutManager glyphRangeForBoundingRect:constrainedRect inTextContainer:_textContainer];
NSInteger lastVisibleGlyphIndex = (NSMaxRange(visibleGlyphRange) - 1);
CGRect lastLineRect = [_layoutManager lineFragmentRectForGlyphAtIndex:lastVisibleGlyphIndex effectiveRange:NULL];
// Calculate the bounding rectangle for the truncation message
ASTextKitComponents *truncationComponents = [ASTextKitComponents componentsWithAttributedSeedString:_truncationString
textContainerSize:constrainedRect.size];
// Size the truncation message
[truncationComponents.layoutManager ensureLayoutForTextContainer:truncationComponents.textContainer];
NSRange truncationGlyphRange = [truncationComponents.layoutManager glyphRangeForTextContainer:truncationComponents.textContainer];
CGRect truncationUsedRect = [truncationComponents.layoutManager boundingRectForGlyphRange:truncationGlyphRange inTextContainer:truncationComponents.textContainer];
CGRect translatedTruncationRect = CGRectMake(CGRectGetMaxX(constrainedRect) - truncationUsedRect.size.width,
CGRectGetMinY(lastLineRect),
truncationUsedRect.size.width,
truncationUsedRect.size.height);
// Determine which glyph is the first to be clipped / overlaps the truncation message.
CGPoint beginningOfTruncationMessage = CGPointMake(translatedTruncationRect.origin.x, CGRectGetMidY(translatedTruncationRect));
NSUInteger firstClippedGlyphIndex = [_layoutManager glyphIndexForPoint:beginningOfTruncationMessage inTextContainer:_textContainer fractionOfDistanceThroughGlyph:NULL];
NSUInteger firstCharacterIndexToReplace = [_layoutManager characterIndexForGlyphAtIndex:firstClippedGlyphIndex];
ASDisplayNodeAssert(firstCharacterIndexToReplace != NSNotFound, @"The beginning of the truncation message exclusion rect (%@) didn't intersect any glyphs", NSStringFromCGPoint(beginningOfTruncationMessage));
// Break on word boundaries
return [self _findTruncationInsertionPointAtOrBeforeCharacterIndex:firstCharacterIndexToReplace];
}
+ (NSCharacterSet *)_truncationCharacterSet
{
static NSCharacterSet *truncationCharacterSet;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableCharacterSet *mutableCharacterSet = [[NSMutableCharacterSet alloc] init];
[mutableCharacterSet formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[mutableCharacterSet addCharactersInString:@".,!?:;"];
truncationCharacterSet = mutableCharacterSet;
});
return truncationCharacterSet;
}
/**
* @abstract Finds the first whitespace at or before the character index do we don't truncate in the middle of words
* @discussion If there are multiple whitespaces together (say a space and a newline), this will backtrack to the first one
*/
- (NSUInteger)_findTruncationInsertionPointAtOrBeforeCharacterIndex:(NSUInteger)firstCharacterIndexToReplace
{
ASDN::MutexLocker l(_textKitLock);
// Don't attempt to truncate beyond the beginning of the string
if (firstCharacterIndexToReplace >= _textStorage.length) {
return 0;
}
// Find the glyph range of the line fragment containing the first character to replace.
NSRange lineGlyphRange;
[_layoutManager lineFragmentRectForGlyphAtIndex:[_layoutManager glyphIndexForCharacterAtIndex:firstCharacterIndexToReplace]
effectiveRange:&lineGlyphRange];
// Look for the first whitespace from the end of the line, starting from the truncation point
NSUInteger startingSearchIndex = [_layoutManager characterIndexForGlyphAtIndex:lineGlyphRange.location];
NSUInteger endingSearchIndex = firstCharacterIndexToReplace;
NSRange rangeToSearch = NSMakeRange(startingSearchIndex, (endingSearchIndex - startingSearchIndex));
NSCharacterSet *truncationCharacterSet = [[self class] _truncationCharacterSet];
NSRange rangeOfLastVisibleWhitespace = [_textStorage.string rangeOfCharacterFromSet:truncationCharacterSet
options:NSBackwardsSearch
range:rangeToSearch];
// Couldn't find a good place to truncate. Might be because there is no whitespace in the text, or we're dealing
// with a foreign language encoding. Settle for truncating at the original place, which may be mid-word.
if (rangeOfLastVisibleWhitespace.location == NSNotFound) {
return firstCharacterIndexToReplace;
} else {
return rangeOfLastVisibleWhitespace.location;
}
}
#pragma mark - Drawing
- (void)drawInRect:(CGRect)bounds inContext:(CGContextRef)context
{
ASDisplayNodeAssert(context, @"This is no good without a context.");
UIGraphicsPushContext(context);
CGContextSaveGState(context);
[self _initializeTextKitComponentsIfNeeded];
NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
{
ASDN::MutexLocker l(_textKitLock);
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:bounds.origin];
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:bounds.origin];
}
CGContextRestoreGState(context);
UIGraphicsPopContext();
}
#pragma mark - String Ranges
- (NSUInteger)lineCount
{
ASDN::MutexLocker l(_textKitLock);
[self _initializeTextKitComponentsIfNeeded];
NSUInteger lineCount = 0;
for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [_layoutManager numberOfGlyphs]; lineCount++) {
[_layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange];
}
return lineCount;
}
- (NSRange)visibleRange
{
ASDN::MutexLocker l(_textKitLock);
[self _initializeTextKitComponentsIfNeeded];
return _visibleRange;
}
- (NSRange)truncationStringCharacterRange
{
ASDN::MutexLocker l(_textKitLock);
[self _initializeTextKitComponentsIfNeeded];
return _truncationCharacterRange;
}
@end

View File

@@ -1,72 +0,0 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
* @abstract Negates/inverts a UIEdgeInsets.
* @discussion Useful for undoing the application of shadow padding to a frame/bounds CGRect.
* For example,
* CGRect insetRect = UIEdgeInsetsRect(originalRect, insets);
* CGRect equalToOriginalRect = UIEdgeInsetsRect(originalRect, ASDNEdgeInsetsInvert(insets));
*/
static inline UIEdgeInsets ASDNEdgeInsetsInvert(UIEdgeInsets insets)
{
UIEdgeInsets invertedInsets = UIEdgeInsetsMake(-insets.top, -insets.left, -insets.bottom, -insets.right);
return invertedInsets;
}
/**
* @abstract an immutable class for calculating shadow padding drawing a shadowed background for text
*/
@interface ASTextNodeShadower : NSObject
- (instancetype)initWithShadowOffset:(CGSize)shadowOffset
shadowColor:(CGColorRef)shadowColor
shadowOpacity:(CGFloat)shadowOpacity
shadowRadius:(CGFloat)shadowRadius;
/**
* @abstract The offset from the top-left corner at which the shadow starts.
* @discussion A positive width will move the shadow to the right.
* A positive height will move the shadow downwards.
*/
@property (nonatomic, readonly, assign) CGSize shadowOffset;
//! CGColor in which the shadow is drawn
@property (nonatomic, readonly, assign) CGColorRef shadowColor;
//! Alpha of the shadow
@property (nonatomic, readonly, assign) CGFloat shadowOpacity;
//! Radius, in pixels
@property (nonatomic, readonly, assign) CGFloat shadowRadius;
/**
* @abstract The edge insets which represent shadow padding
* @discussion Each edge inset is less than or equal to zero.
*
* Example:
* CGRect boundsWithoutShadowPadding; // Large enough to fit text, not large enough to fit the shadow as well
* UIEdgeInsets shadowPadding = [shadower shadowPadding];
* CGRect boundsWithShadowPadding = UIEdgeInsetsRect(boundsWithoutShadowPadding, shadowPadding);
*/
- (UIEdgeInsets)shadowPadding;
/**
* @abstract draws the shadow for text in the provided CGContext
* @discussion Call within the text node's +drawRect method
*/
- (void)setShadowInContext:(CGContextRef)context;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,90 +0,0 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASTextNodeShadower.h"
@implementation ASTextNodeShadower {
UIEdgeInsets _calculatedShadowPadding;
}
- (instancetype)initWithShadowOffset:(CGSize)shadowOffset
shadowColor:(CGColorRef)shadowColor
shadowOpacity:(CGFloat)shadowOpacity
shadowRadius:(CGFloat)shadowRadius
{
if (self = [super init]) {
_shadowOffset = shadowOffset;
_shadowColor = CGColorRetain(shadowColor);
_shadowOpacity = shadowOpacity;
_shadowRadius = shadowRadius;
_calculatedShadowPadding = UIEdgeInsetsMake(-INFINITY, -INFINITY, INFINITY, INFINITY);
}
return self;
}
- (void)dealloc
{
CGColorRelease(_shadowColor);
}
/*
* This method is duplicated here because it gets called frequently, and we were
* wasting valuable time constructing a state object to ask it.
*/
- (BOOL)_shouldDrawShadow
{
return _shadowOpacity != 0.0 && _shadowColor != NULL && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero));
}
- (void)setShadowInContext:(CGContextRef)context
{
if ([self _shouldDrawShadow]) {
CGColorRef textShadowColor = CGColorRetain(_shadowColor);
CGSize textShadowOffset = _shadowOffset;
CGFloat textShadowOpacity = _shadowOpacity;
CGFloat textShadowRadius = _shadowRadius;
if (textShadowOpacity != 1.0) {
CGFloat inherentAlpha = CGColorGetAlpha(textShadowColor);
CGColorRef oldTextShadowColor = textShadowColor;
textShadowColor = CGColorCreateCopyWithAlpha(textShadowColor, inherentAlpha * textShadowOpacity);
CGColorRelease(oldTextShadowColor);
}
CGContextSetShadowWithColor(context, textShadowOffset, textShadowRadius, textShadowColor);
CGColorRelease(textShadowColor);
}
}
- (UIEdgeInsets)shadowPadding
{
if (_calculatedShadowPadding.top == -INFINITY) {
if (![self _shouldDrawShadow]) {
return UIEdgeInsetsZero;
}
UIEdgeInsets shadowPadding = UIEdgeInsetsZero;
// min values are expected to be negative for most typical shadowOffset and
// blurRadius settings:
shadowPadding.top = fminf(0.0f, _shadowOffset.height - _shadowRadius);
shadowPadding.left = fminf(0.0f, _shadowOffset.width - _shadowRadius);
shadowPadding.bottom = fminf(0.0f, -_shadowOffset.height - _shadowRadius);
shadowPadding.right = fminf(0.0f, -_shadowOffset.width - _shadowRadius);
_calculatedShadowPadding = shadowPadding;
}
return _calculatedShadowPadding;
}
@end

View File

@@ -8,7 +8,7 @@
*
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@protocol ASLayoutableAsciiArtProtocol <NSObject>
/**

View File

@@ -8,7 +8,6 @@
*
*/
@import UIKit;
#import "ASAsciiArtBoxCreator.h"
static const NSUInteger kDebugBoxPadding = 2;

View File

@@ -33,6 +33,21 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutJustifyContent) {
On underflow, children are right/bottom-aligned within this spec's bounds.
*/
ASStackLayoutJustifyContentEnd,
/**
On overflow or if the stack has only 1 child, this value is identical to ASStackLayoutJustifyContentStart.
Otherwise, the starting edge of the first child is at the starting edge of the stack,
the ending edge of the last child is at the ending edge of the stack, and the remaining children
are distributed so that the spacing between any two adjacent ones is the same.
If there is a remaining space after spacing division, it is combined with the last spacing (i.e the one between the last 2 children).
*/
ASStackLayoutJustifyContentSpaceBetween,
/**
On overflow or if the stack has only 1 child, this value is identical to ASStackLayoutJustifyContentCenter.
Otherwise, children are distributed such that the spacing between any two adjacent ones is the same,
and the spacing between the first/last child and the stack edges is half the size of the spacing between children.
If there is a remaining space after spacing division, it is combined with the last spacing (i.e the one between the last child and the stack ending edge).
*/
ASStackLayoutJustifyContentSpaceAround
};
/** Orientation of children along cross axis */
@@ -67,3 +82,27 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignSelf) {
/** Expand to fill cross axis */
ASStackLayoutAlignSelfStretch,
};
/** Orientation of children along horizontal axis */
typedef NS_ENUM(NSUInteger, ASHorizontalAlignment) {
/** No alignment specified. Default value */
ASHorizontalAlignmentNone,
/** Left aligned */
ASAlignmentLeft,
/** Center aligned */
ASAlignmentMiddle,
/** Right aligned */
ASAlignmentRight,
};
/** Orientation of children along vertical axis */
typedef NS_ENUM(NSUInteger, ASVerticalAlignment) {
/** No alignment specified. Default value */
ASVerticalAlignmentNone,
/** Top aligned */
ASAlignmentTop,
/** Center aligned */
ASAlignmentCenter,
/** Bottom aligned */
ASAlignmentBottom,
};

View File

@@ -36,10 +36,25 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface ASStackLayoutSpec : ASLayoutSpec
/** Specifies the direction children are stacked in. */
/**
Specifies the direction children are stacked in. If horizontalAlignment and verticalAlignment were set,
they will be resolved again, causing justifyContent and alignItems to be updated accordingly
*/
@property (nonatomic, assign) ASStackLayoutDirection direction;
/** The amount of space between each child. */
@property (nonatomic, assign) CGFloat spacing;
/**
Specifies how children are aligned horizontally. Depends on the stack direction, setting the alignment causes either
justifyContent or alignItems to be updated. The alignment will remain valid after future direction changes.
Thus, it is preferred to those properties
*/
@property (nonatomic, assign) ASHorizontalAlignment horizontalAlignment;
/**
Specifies how children are aligned vertically. Depends on the stack direction, setting the alignment causes either
justifyContent or alignItems to be updated. The alignment will remain valid after future direction changes.
Thus, it is preferred to those properties
*/
@property (nonatomic, assign) ASVerticalAlignment verticalAlignment;
/** The amount of space between each child. */
@property (nonatomic, assign) ASStackLayoutJustifyContent justifyContent;
/** Orientation of children along cross axis */

View File

@@ -25,7 +25,7 @@
@implementation ASStackLayoutSpec
{
ASDN::RecursiveMutex _propertyLock;
ASDN::RecursiveMutex _propertyLock;
}
- (instancetype)init
@@ -58,8 +58,10 @@
return nil;
}
_direction = direction;
_alignItems = alignItems;
_spacing = spacing;
_horizontalAlignment = ASHorizontalAlignmentNone;
_verticalAlignment = ASVerticalAlignmentNone;
_alignItems = alignItems;
_justifyContent = justifyContent;
[self setChildren:children];
@@ -69,18 +71,44 @@
- (void)setDirection:(ASStackLayoutDirection)direction
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
_direction = direction;
if (_direction != direction) {
_direction = direction;
[self resolveHorizontalAlignment];
[self resolveVerticalAlignment];
}
}
- (void)setHorizontalAlignment:(ASHorizontalAlignment)horizontalAlignment
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
if (_horizontalAlignment != horizontalAlignment) {
_horizontalAlignment = horizontalAlignment;
[self resolveHorizontalAlignment];
}
}
- (void)setVerticalAlignment:(ASVerticalAlignment)verticalAlignment
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
if (_verticalAlignment != verticalAlignment) {
_verticalAlignment = verticalAlignment;
[self resolveVerticalAlignment];
}
}
- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
ASDisplayNodeAssert(_horizontalAlignment == ASHorizontalAlignmentNone, @"Cannot set this property directly because horizontalAlignment is being used");
ASDisplayNodeAssert(_verticalAlignment == ASVerticalAlignmentNone, @"Cannot set this property directly because verticalAlignment is being used");
_alignItems = alignItems;
}
- (void)setJustifyContent:(ASStackLayoutJustifyContent)justifyContent
{
ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable");
ASDisplayNodeAssert(_horizontalAlignment == ASHorizontalAlignmentNone, @"Cannot set this property directly because horizontalAlignment is being used");
ASDisplayNodeAssert(_verticalAlignment == ASVerticalAlignmentNone, @"Cannot set this property directly because verticalAlignment is being used");
_justifyContent = justifyContent;
}
@@ -109,6 +137,10 @@
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize
{
if (self.children.count == 0) {
return [ASLayout layoutWithLayoutableObject:self size:constrainedSize.min];
}
ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems, .baselineRelativeArrangement = _baselineRelativeArrangement};
BOOL needsBaselinePass = _baselineRelativeArrangement || _alignItems == ASStackLayoutAlignItemsBaselineFirst || _alignItems == ASStackLayoutAlignItemsBaselineLast;
@@ -149,6 +181,24 @@
sublayouts:sublayouts];
}
- (void)resolveHorizontalAlignment
{
if (_direction == ASStackLayoutDirectionHorizontal) {
_justifyContent = justifyContent(_horizontalAlignment, _justifyContent);
} else {
_alignItems = alignment(_horizontalAlignment, _alignItems);
}
}
- (void)resolveVerticalAlignment
{
if (_direction == ASStackLayoutDirectionHorizontal) {
_alignItems = alignment(_verticalAlignment, _alignItems);
} else {
_justifyContent = justifyContent(_verticalAlignment, _justifyContent);
}
}
@end
@implementation ASStackLayoutSpec (Debugging)

View File

@@ -73,6 +73,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
_ASPendingState *_pendingViewState;
ASInterfaceState _interfaceState;
struct ASDisplayNodeFlags {
// public properties
unsigned synchronous:1;
@@ -116,10 +118,13 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) {
// Bitmask to check which methods an object overrides.
@property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides;
// These methods are recursive, and either union or remove the provided interfaceState to all sub-elements.
- (void)enterInterfaceState:(ASInterfaceState)interfaceState;
- (void)exitInterfaceState:(ASInterfaceState)interfaceState;
// Swizzle to extend the builtin functionality with custom logic
- (BOOL)__shouldLoadViewOrLayer;
- (BOOL)__shouldSize;
- (void)__exitedHierarchy;
// Core implementation of -measureWithSizeRange:. Must be called with _propertyLock held.
- (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize;

View File

@@ -68,3 +68,63 @@ inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment,
return stackAlignment;
}
}
inline ASStackLayoutAlignItems alignment(ASHorizontalAlignment alignment, ASStackLayoutAlignItems defaultAlignment)
{
switch (alignment) {
case ASAlignmentLeft:
return ASStackLayoutAlignItemsStart;
case ASAlignmentMiddle:
return ASStackLayoutAlignItemsCenter;
case ASAlignmentRight:
return ASStackLayoutAlignItemsEnd;
case ASHorizontalAlignmentNone:
default:
return defaultAlignment;
}
}
inline ASStackLayoutAlignItems alignment(ASVerticalAlignment alignment, ASStackLayoutAlignItems defaultAlignment)
{
switch (alignment) {
case ASAlignmentTop:
return ASStackLayoutAlignItemsStart;
case ASAlignmentCenter:
return ASStackLayoutAlignItemsCenter;
case ASAlignmentBottom:
return ASStackLayoutAlignItemsEnd;
case ASVerticalAlignmentNone:
default:
return defaultAlignment;
}
}
inline ASStackLayoutJustifyContent justifyContent(ASHorizontalAlignment alignment, ASStackLayoutJustifyContent defaultJustifyContent)
{
switch (alignment) {
case ASAlignmentLeft:
return ASStackLayoutJustifyContentStart;
case ASAlignmentMiddle:
return ASStackLayoutJustifyContentCenter;
case ASAlignmentRight:
return ASStackLayoutJustifyContentEnd;
case ASHorizontalAlignmentNone:
default:
return defaultJustifyContent;
}
}
inline ASStackLayoutJustifyContent justifyContent(ASVerticalAlignment alignment, ASStackLayoutJustifyContent defaultJustifyContent)
{
switch (alignment) {
case ASAlignmentTop:
return ASStackLayoutJustifyContentStart;
case ASAlignmentCenter:
return ASStackLayoutJustifyContentCenter;
case ASAlignmentBottom:
return ASStackLayoutJustifyContentEnd;
case ASVerticalAlignmentNone:
default:
return defaultJustifyContent;
}
}

View File

@@ -15,6 +15,7 @@
#import "ASStackLayoutSpecUtilities.h"
#import "ASLayoutable.h"
#import "ASLayoutOptions.h"
#import "ASAssert.h"
static CGFloat crossOffset(const ASStackLayoutSpecStyle &style,
const ASStackUnpositionedItem &l,
@@ -33,8 +34,20 @@ static CGFloat crossOffset(const ASStackLayoutSpecStyle &style,
}
}
/**
* Positions children according to the stack style and positioning properties.
*
* @param style The layout style of the overall stack layout
* @param firstChildOffset Offset of the first child
* @param extraSpacing Spacing between children, in addition to spacing set to the stack's layout style
* @param lastChildOffset Offset of the last child
* @param unpositionedLayout Unpositioned children of the stack
* @param constrainedSize Constrained size of the stack
*/
static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style,
const CGFloat offset,
const CGFloat firstChildOffset,
const CGFloat extraSpacing,
const CGFloat lastChildOffset,
const ASStackUnpositionedLayout &unpositionedLayout,
const ASSizeRange &constrainedSize)
{
@@ -48,12 +61,16 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style
const auto maxCrossSize = crossDimension(style.direction, constrainedSize.max);
const CGFloat crossSize = MIN(MAX(minCrossSize, largestChildCrossSize), maxCrossSize);
CGPoint p = directionPoint(style.direction, offset, 0);
CGPoint p = directionPoint(style.direction, firstChildOffset, 0);
BOOL first = YES;
const auto lastChild = unpositionedLayout.items.back().child;
CGFloat offset = 0;
auto stackedChildren = AS::map(unpositionedLayout.items, [&](const ASStackUnpositionedItem &l) -> ASLayout *{
p = p + directionPoint(style.direction, l.child.spacingBefore, 0);
offset = (l.child == lastChild) ? lastChildOffset : 0;
p = p + directionPoint(style.direction, l.child.spacingBefore + offset, 0);
if (!first) {
p = p + directionPoint(style.direction, style.spacing, 0);
p = p + directionPoint(style.direction, style.spacing + extraSpacing, 0);
}
first = NO;
l.layout.position = p + directionPoint(style.direction, 0, crossOffset(style, l, crossSize));
@@ -64,16 +81,45 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style
return {stackedChildren, crossSize};
}
static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style,
const CGFloat firstChildOffset,
const ASStackUnpositionedLayout &unpositionedLayout,
const ASSizeRange &constrainedSize)
{
return stackedLayout(style, firstChildOffset, 0, 0, unpositionedLayout, constrainedSize);
}
ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout,
const ASStackLayoutSpecStyle &style,
const ASSizeRange &constrainedSize)
{
switch (style.justifyContent) {
const auto numOfItems = unpositionedLayout.items.size();
ASDisplayNodeCAssertTrue(numOfItems > 0);
const CGFloat violation = unpositionedLayout.violation;
ASStackLayoutJustifyContent justifyContent = style.justifyContent;
// Handle edge cases of "space between" and "space around"
if (justifyContent == ASStackLayoutJustifyContentSpaceBetween && (violation < 0 || numOfItems == 1)) {
justifyContent = ASStackLayoutJustifyContentStart;
} else if (justifyContent == ASStackLayoutJustifyContentSpaceAround && (violation < 0 || numOfItems == 1)) {
justifyContent = ASStackLayoutJustifyContentCenter;
}
switch (justifyContent) {
case ASStackLayoutJustifyContentStart:
return stackedLayout(style, 0, unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentCenter:
return stackedLayout(style, floorf(unpositionedLayout.violation / 2), unpositionedLayout, constrainedSize);
return stackedLayout(style, floorf(violation / 2), unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentEnd:
return stackedLayout(style, unpositionedLayout.violation, unpositionedLayout, constrainedSize);
return stackedLayout(style, violation, unpositionedLayout, constrainedSize);
case ASStackLayoutJustifyContentSpaceBetween: {
const auto numOfSpacings = numOfItems - 1;
return stackedLayout(style, 0, floorf(violation / numOfSpacings), fmodf(violation, numOfSpacings), unpositionedLayout, constrainedSize);
}
case ASStackLayoutJustifyContentSpaceAround: {
// Spacing between items are twice the spacing on the edges
CGFloat spacingUnit = floorf(violation / (numOfItems * 2));
return stackedLayout(style, spacingUnit, spacingUnit * 2, 0, unpositionedLayout, constrainedSize);
}
}
}

View File

@@ -34,12 +34,20 @@ typedef NS_ENUM(NSInteger, _ASHierarchyChangeType) {
@interface _ASHierarchyChangeSet : NSObject
/// @precondition The change set must be completed.
@property (nonatomic, strong, readonly) NSIndexSet *deletedSections;
/// @precondition The change set must be completed.
@property (nonatomic, strong, readonly) NSIndexSet *insertedSections;
/// @precondition The change set must be completed.
@property (nonatomic, strong, readonly) NSIndexSet *reloadedSections;
@property (nonatomic, strong, readonly) NSArray *insertedItems;
@property (nonatomic, strong, readonly) NSArray *deletedItems;
@property (nonatomic, strong, readonly) NSArray *reloadedItems;
/**
Get the section index after the update for the given section before the update.
@precondition The change set must be completed.
@returns The new section index, or NSNotFound if the given section was deleted.
*/
- (NSInteger)newSectionForOldSection:(NSInteger)oldSection;
@property (nonatomic, readonly) BOOL completed;

View File

@@ -17,6 +17,9 @@
Assumes: `changes` is [_ASHierarchySectionChange] all with the same changeType
*/
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes;
/// Returns all the indexes from all the `indexSet`s of the given `_ASHierarchySectionChange` objects.
+ (NSMutableIndexSet *)allIndexesInChanges:(NSArray *)changes;
@end
@interface _ASHierarchyItemChange ()
@@ -40,26 +43,12 @@
@end
@implementation _ASHierarchyChangeSet {
NSMutableIndexSet *_deletedSections;
NSMutableIndexSet *_insertedSections;
NSMutableIndexSet *_reloadedSections;
NSMutableArray *_insertedItems;
NSMutableArray *_deletedItems;
NSMutableArray *_reloadedItems;
}
@implementation _ASHierarchyChangeSet
- (instancetype)init
{
self = [super init];
if (self) {
_deletedSections = [NSMutableIndexSet new];
_insertedSections = [NSMutableIndexSet new];
_reloadedSections = [NSMutableIndexSet new];
_deletedItems = [NSMutableArray new];
_insertedItems = [NSMutableArray new];
_reloadedItems = [NSMutableArray new];
_insertItemChanges = [NSMutableArray new];
_deleteItemChanges = [NSMutableArray new];
@@ -110,6 +99,17 @@
}
}
- (NSInteger)newSectionForOldSection:(NSInteger)oldSection
{
[self _ensureCompleted];
if ([_deletedSections containsIndex:oldSection]) {
return NSNotFound;
}
NSInteger indexAfterDeletes = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)];
return indexAfterDeletes + [_insertedSections countOfIndexesInRange:NSMakeRange(0, indexAfterDeletes)];
}
- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
{
[self _ensureNotCompleted];
@@ -172,9 +172,32 @@
[_ASHierarchySectionChange sortAndCoalesceChanges:_deleteSectionChanges];
[_ASHierarchySectionChange sortAndCoalesceChanges:_insertSectionChanges];
[_ASHierarchySectionChange sortAndCoalesceChanges:_reloadSectionChanges];
[_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:_deletedSections];
[_ASHierarchyItemChange sortAndCoalesceChanges:_reloadItemChanges ignoringChangesInSections:_reloadedSections];
[_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:_insertedSections];
_deletedSections = [[_ASHierarchySectionChange allIndexesInChanges:_deleteSectionChanges] copy];
_insertedSections = [[_ASHierarchySectionChange allIndexesInChanges:_insertSectionChanges] copy];
_reloadedSections = [[_ASHierarchySectionChange allIndexesInChanges:_reloadSectionChanges] copy];
// These are invalid old section indexes.
NSMutableIndexSet *deletedOrReloaded = [_deletedSections mutableCopy];
[deletedOrReloaded addIndexes:_reloadedSections];
// These are invalid new section indexes.
NSMutableIndexSet *insertedOrReloaded = [_insertedSections mutableCopy];
// Get the new section that each reloaded section index corresponds to.
[_reloadedSections enumerateIndexesUsingBlock:^(NSUInteger oldIndex, __unused BOOL * stop) {
NSUInteger newIndex = [self newSectionForOldSection:oldIndex];
if (newIndex != NSNotFound) {
[insertedOrReloaded addIndex:newIndex];
}
}];
// Ignore item reloads/deletes in reloaded/deleted sections.
[_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:deletedOrReloaded];
[_ASHierarchyItemChange sortAndCoalesceChanges:_reloadItemChanges ignoringChangesInSections:deletedOrReloaded];
// Ignore item inserts in reloaded(new)/inserted sections.
[_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:insertedOrReloaded];
}
}
@@ -218,38 +241,46 @@
NSMutableArray *result = [NSMutableArray new];
__block ASDataControllerAnimationOptions currentOptions = 0;
__block NSMutableIndexSet *currentIndexes = nil;
NSUInteger lastIndex = allIndexes.lastIndex;
NSMutableIndexSet *currentIndexes = [NSMutableIndexSet indexSet];
NSEnumerationOptions options = type == _ASHierarchyChangeTypeDelete ? NSEnumerationReverse : kNilOptions;
[allIndexes enumerateIndexesWithOptions:options usingBlock:^(NSUInteger idx, __unused BOOL * stop) {
ASDataControllerAnimationOptions options = [animationOptions[@(idx)] integerValue];
BOOL endingCurrentGroup = NO;
if (currentIndexes == nil) {
// Starting a new group
currentIndexes = [NSMutableIndexSet indexSetWithIndex:idx];
currentOptions = options;
} else if (options == currentOptions) {
// Continuing the current group
[currentIndexes addIndex:idx];
} else {
endingCurrentGroup = YES;
}
BOOL endingLastGroup = (currentIndexes != nil && lastIndex == idx);
if (endingCurrentGroup || endingLastGroup) {
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:currentIndexes animationOptions:currentOptions];
// End the previous group if needed.
if (options != currentOptions && currentIndexes.count > 0) {
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions];
[result addObject:change];
currentOptions = 0;
currentIndexes = nil;
[currentIndexes removeAllIndexes];
}
// Start a new group if needed.
if (currentIndexes.count == 0) {
currentOptions = options;
}
[currentIndexes addIndex:idx];
}];
// Finish up the last group.
if (currentIndexes.count > 0) {
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions];
[result addObject:change];
}
[changes setArray:result];
}
+ (NSMutableIndexSet *)allIndexesInChanges:(NSArray *)changes
{
NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
for (_ASHierarchySectionChange *change in changes) {
[indexes addIndexes:change.indexSet];
}
return indexes;
}
@end
@implementation _ASHierarchyItemChange
@@ -303,32 +334,30 @@
NSMutableArray *result = [NSMutableArray new];
ASDataControllerAnimationOptions currentOptions = 0;
NSMutableArray *currentIndexPaths = nil;
NSIndexPath *lastIndexPath = allIndexPaths.lastObject;
NSMutableArray *currentIndexPaths = [NSMutableArray array];
for (NSIndexPath *indexPath in allIndexPaths) {
ASDataControllerAnimationOptions options = [animationOptions[indexPath] integerValue];
BOOL endingCurrentGroup = NO;
if (currentIndexPaths == nil) {
// Starting a new group
currentIndexPaths = [NSMutableArray arrayWithObject:indexPath];
currentOptions = options;
} else if (options == currentOptions) {
// Continuing the current group
[currentIndexPaths addObject:indexPath];
} else {
endingCurrentGroup = YES;
}
BOOL endingLastGroup = (currentIndexPaths != nil && (NSOrderedSame == [lastIndexPath compare:indexPath]));
if (endingCurrentGroup || endingLastGroup) {
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:currentIndexPaths animationOptions:currentOptions presorted:YES];
// End the previous group if needed.
if (options != currentOptions && currentIndexPaths.count > 0) {
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES];
[result addObject:change];
currentOptions = 0;
currentIndexPaths = nil;
[currentIndexPaths removeAllObjects];
}
// Start a new group if needed.
if (currentIndexPaths.count == 0) {
currentOptions = options;
}
[currentIndexPaths addObject:indexPath];
}
// Finish up the last group.
if (currentIndexPaths.count > 0) {
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES];
[result addObject:change];
}
[changes setArray:result];

View File

@@ -0,0 +1,171 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <string>
// From folly:
// This is the Hash128to64 function from Google's cityhash (available
// under the MIT License). We use it to reduce multiple 64 bit hashes
// into a single hash.
inline uint64_t ASHashCombine(const uint64_t upper, const uint64_t lower) {
// Murmur-inspired hashing.
const uint64_t kMul = 0x9ddfea08eb382d69ULL;
uint64_t a = (lower ^ upper) * kMul;
a ^= (a >> 47);
uint64_t b = (upper ^ a) * kMul;
b ^= (b >> 47);
b *= kMul;
return b;
}
#if __LP64__
inline size_t ASHash64ToNative(uint64_t key) {
return key;
}
#else
// Thomas Wang downscaling hash function
inline size_t ASHash64ToNative(uint64_t key) {
key = (~key) + (key << 18);
key = key ^ (key >> 31);
key = key * 21;
key = key ^ (key >> 11);
key = key + (key << 6);
key = key ^ (key >> 22);
return (uint32_t) key;
}
#endif
NSUInteger ASIntegerArrayHash(const NSUInteger *subhashes, NSUInteger count);
namespace AS {
// Default is not an ObjC class
template<typename T, typename V = bool>
struct is_objc_class : std::false_type { };
// Conditionally enable this template specialization on whether T is convertible to id, makes the is_objc_class a true_type
template<typename T>
struct is_objc_class<T, typename std::enable_if<std::is_convertible<T, id>::value, bool>::type> : std::true_type { };
// ASUtils::hash<T>()(value) -> either std::hash<T> if c++ or [o hash] if ObjC object.
template <typename T, typename Enable = void> struct hash;
// For non-objc types, defer to std::hash
template <typename T> struct hash<T, typename std::enable_if<!is_objc_class<T>::value>::type> {
size_t operator ()(const T& a) {
return std::hash<T>()(a);
}
};
// For objc types, call [o hash]
template <typename T> struct hash<T, typename std::enable_if<is_objc_class<T>::value>::type> {
size_t operator ()(id o) {
return [o hash];
}
};
template <typename T, typename Enable = void> struct is_equal;
// For non-objc types use == operator
template <typename T> struct is_equal<T, typename std::enable_if<!is_objc_class<T>::value>::type> {
bool operator ()(const T& a, const T& b) {
return a == b;
}
};
// For objc types, check pointer equality, then use -isEqual:
template <typename T> struct is_equal<T, typename std::enable_if<is_objc_class<T>::value>::type> {
bool operator ()(id a, id b) {
return a == b || [a isEqual:b];
}
};
};
namespace ASTupleOperations
{
// Recursive case (hash up to Index)
template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
struct _hash_helper
{
static size_t hash(Tuple const& tuple)
{
size_t prev = _hash_helper<Tuple, Index-1>::hash(tuple);
using TypeForIndex = typename std::tuple_element<Index,Tuple>::type;
size_t thisHash = AS::hash<TypeForIndex>()(std::get<Index>(tuple));
return ASHashCombine(prev, thisHash);
}
};
// Base case (hash 0th element)
template <class Tuple>
struct _hash_helper<Tuple, 0>
{
static size_t hash(Tuple const& tuple)
{
using TypeForIndex = typename std::tuple_element<0,Tuple>::type;
return AS::hash<TypeForIndex>()(std::get<0>(tuple));
}
};
// Recursive case (elements equal up to Index)
template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
struct _eq_helper
{
static bool equal(Tuple const& a, Tuple const& b)
{
bool prev = _eq_helper<Tuple, Index-1>::equal(a, b);
using TypeForIndex = typename std::tuple_element<Index,Tuple>::type;
auto aValue = std::get<Index>(a);
auto bValue = std::get<Index>(b);
return prev && AS::is_equal<TypeForIndex>()(aValue, bValue);
}
};
// Base case (0th elements equal)
template <class Tuple>
struct _eq_helper<Tuple, 0>
{
static bool equal(Tuple const& a, Tuple const& b)
{
using TypeForIndex = typename std::tuple_element<0,Tuple>::type;
auto& aValue = std::get<0>(a);
auto& bValue = std::get<0>(b);
return AS::is_equal<TypeForIndex>()(aValue, bValue);
}
};
template <typename ... TT> struct hash;
template <typename ... TT>
struct hash<std::tuple<TT...>>
{
size_t operator()(std::tuple<TT...> const& tt) const
{
return _hash_helper<std::tuple<TT...>>::hash(tt);
}
};
template <typename ... TT> struct equal_to;
template <typename ... TT>
struct equal_to<std::tuple<TT...>>
{
bool operator()(std::tuple<TT...> const& a, std::tuple<TT...> const& b) const
{
return _eq_helper<std::tuple<TT...>>::equal(a, b);
}
};
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
#ifndef ComponentKit_ASTextKitAttributes_h
#define ComponentKit_ASTextKitAttributes_h
@protocol ASTextKitTruncating;
extern NSString *const ASTextKitTruncationAttributeName;
/**
Use ASTextKitEntityAttribute as the value of this attribute to embed a link or other interactable content inside the
text.
*/
extern NSString *const ASTextKitEntityAttributeName;
static inline BOOL _objectsEqual(id<NSObject> obj1, id<NSObject> obj2)
{
return obj1 == obj2 ? YES : [obj1 isEqual:obj2];
}
/**
All NSObject values in this struct should be copied when passed into the TextComponent.
*/
struct ASTextKitAttributes {
/**
The string to be drawn. ASTextKit will not augment this string with default colors, etc. so this must be complete.
*/
NSAttributedString *attributedString;
/**
The string to use as the truncation string, usually just "...". If you have a range of text you would like to
restrict highlighting to (for instance if you have "... Continue Reading", use the ASTextKitTruncationAttributeName
to mark the specific range of the string that should be highlightable.
*/
NSAttributedString *truncationAttributedString;
/**
This is the character set that ASTextKit should attempt to avoid leaving as a trailing character before your
truncation token. By default this set includes "\s\t\n\r.,!?:;" so you don't end up with ugly looking truncation
text like "Hey, this is some fancy Truncation!\n\n...". Instead it would be truncated as "Hey, this is some fancy
truncation...". This is not always possible.
Set this to the empty charset if you want to just use the "dumb" truncation behavior. A nil value will be
substituted with the default described above.
*/
NSCharacterSet *avoidTailTruncationSet;
/**
The line-break mode to apply to the text. Since this also impacts how TextKit will attempt to truncate the text
in your string, we only support NSLineBreakByWordWrapping and NSLineBreakByCharWrapping.
*/
NSLineBreakMode lineBreakMode;
/**
The maximum number of lines to draw in the drawable region. Leave blank or set to 0 to define no maximum.
*/
NSUInteger maximumNumberOfLines;
/**
An array of UIBezierPath objects representing the exclusion paths inside the receiver's bounding rectangle. Default value: nil.
*/
NSArray *exclusionPaths;
/**
The shadow offset for any shadows applied to the text. The coordinate space for this is the same as UIKit, so a
positive width means towards the right, and a positive height means towards the bottom.
*/
CGSize shadowOffset;
/**
The color to use in drawing the text's shadow.
*/
UIColor *shadowColor;
/**
The opacity of the shadow from 0 to 1.
*/
CGFloat shadowOpacity;
/**
The radius that should be applied to the shadow blur. Larger values mean a larger, more blurred shadow.
*/
CGFloat shadowRadius;
/**
A pointer to a function that that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager.
*/
NSLayoutManager *(*layoutManagerFactory)(void);
/**
We provide an explicit copy function so we can use aggregate initializer syntax while providing copy semantics for
the NSObjects inside.
*/
const ASTextKitAttributes copy() const
{
return {
[attributedString copy],
[truncationAttributedString copy],
[avoidTailTruncationSet copy],
lineBreakMode,
maximumNumberOfLines,
[exclusionPaths copy],
shadowOffset,
[shadowColor copy],
shadowOpacity,
shadowRadius,
layoutManagerFactory
};
};
bool operator==(const ASTextKitAttributes &other) const
{
// These comparisons are in a specific order to reduce the overall cost of this function.
return lineBreakMode == other.lineBreakMode
&& maximumNumberOfLines == other.maximumNumberOfLines
&& shadowOpacity == other.shadowOpacity
&& shadowRadius == other.shadowRadius
&& layoutManagerFactory == other.layoutManagerFactory
&& CGSizeEqualToSize(shadowOffset, other.shadowOffset)
&& _objectsEqual(exclusionPaths, other.exclusionPaths)
&& _objectsEqual(avoidTailTruncationSet, other.avoidTailTruncationSet)
&& _objectsEqual(shadowColor, other.shadowColor)
&& _objectsEqual(attributedString, other.attributedString)
&& _objectsEqual(truncationAttributedString, other.truncationAttributedString);
}
size_t hash() const;
};
#endif

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASTextKitAttributes.h"
#import "ASEqualityHashHelpers.h"
#include <functional>
NSString *const ASTextKitTruncationAttributeName = @"ck_truncation";
NSString *const ASTextKitEntityAttributeName = @"ck_entity";
size_t ASTextKitAttributes::hash() const
{
NSUInteger subhashes[] = {
[attributedString hash],
[truncationAttributedString hash],
[avoidTailTruncationSet hash],
std::hash<NSUInteger>()((NSUInteger) layoutManagerFactory),
std::hash<NSInteger>()(lineBreakMode),
std::hash<NSInteger>()(maximumNumberOfLines),
[exclusionPaths hash],
std::hash<CGFloat>()(shadowOffset.width),
std::hash<CGFloat>()(shadowOffset.height),
[shadowColor hash],
std::hash<CGFloat>()(shadowOpacity),
std::hash<CGFloat>()(shadowRadius),
};
return ASIntegerArrayHash(subhashes, sizeof(subhashes) / sizeof(subhashes[0]));
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
/**
A threadsafe container for the TextKit components that ASTextKit uses to lay out and truncate its text.
This container is the sole owner and manager of the TextKit classes. This is an important model because of major
thread safety issues inside vanilla TextKit. It provides a central locking location for accessing TextKit methods.
*/
@interface ASTextKitContext : NSObject
/**
Initializes a context and its associated TextKit components.
Initialization of TextKit components is a globally locking operation so be careful of bottlenecks with this class.
*/
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
lineBreakMode:(NSLineBreakMode)lineBreakMode
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
exclusionPaths:(NSArray *)exclusionPaths
constrainedSize:(CGSize)constrainedSize
layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory;
/**
All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to
TextKit components may cause crashes.
The block provided MUST not call out to client code from within its scope or it is possible for this to cause deadlocks
in your application. Use with EXTREME care.
Callers MUST NOT keep a ref to these internal objects and use them later. This WILL cause crashes in your application.
*/
- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *layoutManager,
NSTextStorage *textStorage,
NSTextContainer *textContainer))block;
@end

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <mutex>
#import "ASTextKitContext.h"
@implementation ASTextKitContext
{
// All TextKit operations (even non-mutative ones) must be executed serially.
std::mutex _textKitMutex;
NSLayoutManager *_layoutManager;
NSTextStorage *_textStorage;
NSTextContainer *_textContainer;
}
- (instancetype)initWithAttributedString:(NSAttributedString *)attributedString
lineBreakMode:(NSLineBreakMode)lineBreakMode
maximumNumberOfLines:(NSUInteger)maximumNumberOfLines
exclusionPaths:(NSArray *)exclusionPaths
constrainedSize:(CGSize)constrainedSize
layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory
{
if (self = [super init]) {
// Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock.
static std::mutex __static_mutex;
std::lock_guard<std::mutex> l(__static_mutex);
// Create the TextKit component stack with our default configuration.
_textStorage = (attributedString ? [[NSTextStorage alloc] initWithAttributedString:attributedString] : [[NSTextStorage alloc] init]);
_layoutManager = layoutManagerFactory ? layoutManagerFactory() : [[NSLayoutManager alloc] init];
_layoutManager.usesFontLeading = NO;
[_textStorage addLayoutManager:_layoutManager];
_textContainer = [[NSTextContainer alloc] initWithSize:constrainedSize];
// We want the text laid out up to the very edges of the container.
_textContainer.lineFragmentPadding = 0;
_textContainer.lineBreakMode = lineBreakMode;
_textContainer.maximumNumberOfLines = maximumNumberOfLines;
_textContainer.exclusionPaths = exclusionPaths;
[_layoutManager addTextContainer:_textContainer];
}
return self;
}
- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *,
NSTextStorage *,
NSTextContainer *))block
{
std::lock_guard<std::mutex> l(_textKitMutex);
block(_layoutManager, _textStorage, _textContainer);
}
@end

View File

@@ -64,7 +64,7 @@ ASDISPLAYNODE_EXTERN_C_END
#pragma mark -
#pragma mark -
@interface NSParagraphStyle (ASTextNodeCoreTextAdditions)
@interface NSParagraphStyle (ASTextKitCoreTextAdditions)
/**
@abstract Returns an NSParagraphStyle initialized with the paragraph specifiers from the given CTParagraphStyleRef.

View File

@@ -6,7 +6,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASTextNodeCoreTextAdditions.h"
#import "ASTextKitCoreTextAdditions.h"
#import <CoreText/CTFont.h>
#import <CoreText/CTStringAttributes.h>
@@ -155,7 +155,7 @@ NSAttributedString *ASCleanseAttributedStringOfCoreTextAttributes(NSAttributedSt
#pragma mark -
#pragma mark -
@implementation NSParagraphStyle (ASTextNodeCoreTextAdditions)
@implementation NSParagraphStyle (ASTextKitCoreTextAdditions)
+ (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle;
{

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
/**
The object that should be embedded with ASTextKitEntityAttributeName. Please note that the entity you provide MUST
implement a proper hash and isEqual function or your application performance will grind to a halt due to
NSMutableAttributedString's usage of a global hash table of all attributes. This means the entity should NOT be a
Foundation Collection (NSArray, NSDictionary, NSSet, etc.) since their hash function is a simple count of the values
in the collection, which causes pathological performance problems deep inside NSAttributedString's implementation.
rdar://19352367
*/
@interface ASTextKitEntityAttribute : NSObject
@property (nonatomic, strong, readonly) id<NSObject> entity;
- (instancetype)initWithEntity:(id<NSObject>)entity;
@end

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASTextKitEntityAttribute.h"
@implementation ASTextKitEntityAttribute
- (instancetype)initWithEntity:(id<NSObject>)entity
{
if (self = [super init]) {
_entity = entity;
}
return self;
}
- (NSUInteger)hash
{
return [_entity hash];
}
- (BOOL)isEqual:(id)object
{
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
ASTextKitEntityAttribute *other = (ASTextKitEntityAttribute *)object;
return _entity == other.entity || [_entity isEqual:other.entity];
}
@end

View File

@@ -6,7 +6,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASTextNodeTextKitHelpers.h"
#import "ASTextKitHelpers.h"
@interface ASTextKitComponents ()

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASTextKitRenderer.h"
typedef void (^as_text_component_index_block_t)(NSUInteger characterIndex,
CGRect glyphBoundingRect,
BOOL *stop);
/**
Measure options are used to specify which type of line height measurement to use.
ASTextNodeRendererMeasureOptionLineHeight is faster and will give the height from the baseline to the next line.
ASTextNodeRendererMeasureOptionCapHeight is a more nuanced measure of the glyphs in the given range that attempts to
produce a visually balanced rectangle above and below the glyphs to produce nice looking text highlights.
ASTextNodeRendererMeasureOptionBlock uses the cap height option to generate each glyph index, but combines all but the
first and last line rect into a single block. Looks nice for multiline selection.
*/
typedef NS_ENUM(NSUInteger, ASTextKitRendererMeasureOption) {
ASTextKitRendererMeasureOptionLineHeight,
ASTextKitRendererMeasureOptionCapHeight,
ASTextKitRendererMeasureOptionBlock
};
@interface ASTextKitRenderer (Positioning)
/**
Returns the bounding rect for the given character range.
@param textRange The character range for which the bounding rect will be computed. Should be within the range of the
attributedString of this renderer.
@discussion In the external, shadowed coordinate space.
*/
- (CGRect)frameForTextRange:(NSRange)textRange;
/**
Returns an array of rects representing the lines in the given character range
@param textRange The character range for which the rects will be computed. Should be within the range of the
attributedString of this renderer.
@param measureOption The measure option to use for construction of the rects. See ASTextKitRendererMeasureOption
docs for usage.
@discussion This method is useful for providing highlighting text. Returned rects are in the coordinate space of the
renderer.
Triggers initialization of textkit components, truncation, and sizing.
*/
- (NSArray *)rectsForTextRange:(NSRange)textRange
measureOption:(ASTextKitRendererMeasureOption)measureOption;
/**
Enumerate the text character indexes at a position within the coordinate space of the renderer.
@param position The point in the shadowed coordinate space at which text indexes will be enumerated.
@param block The block that will be executed for each index identified that may correspond to the given position. The
block is given the character index that corresponds to the glyph at each index in question, as well as the bounding
rect for that glyph.
@discussion Glyph location based on a touch point is not an exact science because user touches are not well-represented
by a simple point, especially in the context of link-heavy text. So we have this method to make it a bit easier. This
method checks a grid of candidate positions around the touch point you give it, and computes the bounding rect of the
glyph corresponding to the character index given.
The bounding rect of the glyph can be used to identify the best glyph index that corresponds to your touch. For
instance, comparing centroidal distance from the glyph bounding rect to the touch center is useful for identifying
which link a user actually intended to select.
Triggers initialization of textkit components, truncation, and sizing.
*/
- (void)enumerateTextIndexesAtPosition:(CGPoint)position
usingBlock:(as_text_component_index_block_t)block;
/**
Returns the single text index whose glyph's centroid is closest to the given position.
@param position The point in the shadowed coordinate space that should be checked.
@discussion This will use the grid enumeration function above, `enumerateTextIndexesAtPosition...`, in order to find
the closest glyph, so it is possible that a glyph could be missed, but ultimately unlikely.
*/
- (NSUInteger)nearestTextIndexAtPosition:(CGPoint)position;
/**
Returns the trailing rect unused by the renderer in the last rendered line.
@discussion In the external shadowed coordinate space.
Triggers initialization of textkit components, truncation, and sizing.
*/
- (CGRect)trailingRect;
@end

View File

@@ -0,0 +1,374 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASTextKitRenderer+Positioning.h"
#import <CoreText/CoreText.h>
#import "ASAssert.h"
#import "ASTextKitContext.h"
#import "ASTextKitShadower.h"
static const CGFloat ASTextKitRendererGlyphTouchHitSlop = 5.0;
static const CGFloat ASTextKitRendererTextCapHeightPadding = 1.3;
@implementation ASTextKitRenderer (Tracking)
- (NSArray *)rectsForTextRange:(NSRange)textRange
measureOption:(ASTextKitRendererMeasureOption)measureOption
{
__block NSArray *textRects = @[];
[self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
BOOL textRangeIsValid = (NSMaxRange(textRange) <= [textStorage length]);
ASDisplayNodeCAssertTrue(textRangeIsValid);
if (!textRangeIsValid) {
return;
}
// Used for block measure option
__block CGRect firstRect = CGRectNull;
__block CGRect lastRect = CGRectNull;
__block CGRect blockRect = CGRectNull;
NSMutableArray *mutableTextRects = [NSMutableArray array];
NSString *string = textStorage.string;
NSRange totalGlyphRange = [layoutManager glyphRangeForCharacterRange:textRange actualCharacterRange:NULL];
[layoutManager enumerateLineFragmentsForGlyphRange:totalGlyphRange usingBlock:^(CGRect rect,
CGRect usedRect,
NSTextContainer *innerTextContainer,
NSRange glyphRange,
BOOL *stop) {
CGRect lineRect = CGRectNull;
// If we're empty, don't bother looping through glyphs, use the default.
if (CGRectIsEmpty(usedRect)) {
lineRect = usedRect;
} else {
// TextKit's bounding rect computations are just a touch off, so we actually
// compose the rects by hand from the center of the given TextKit bounds and
// imposing the font attributes returned by the glyph's font.
NSRange lineGlyphRange = NSIntersectionRange(totalGlyphRange, glyphRange);
for (NSUInteger i = lineGlyphRange.location; i < NSMaxRange(lineGlyphRange) && i < string.length; i++) {
// We grab the properly sized rect for the glyph
CGRect properGlyphRect = [self _internalRectForGlyphAtIndex:i
measureOption:measureOption
layoutManager:layoutManager
textContainer:textContainer
textStorage:textStorage];
// Don't count empty glyphs towards our line rect.
if (!CGRectIsEmpty(properGlyphRect)) {
lineRect = CGRectIsNull(lineRect) ? properGlyphRect
: CGRectUnion(lineRect, properGlyphRect);
}
}
}
if (!CGRectIsNull(lineRect)) {
if (measureOption == ASTextKitRendererMeasureOptionBlock) {
// For the block measurement option we store the first & last rect as
// special cases, then merge everything else into a single block rect
if (CGRectIsNull(firstRect)) {
// We don't have a firstRect, so we must be on the first line.
firstRect = lineRect;
} else if(CGRectIsNull(lastRect)) {
// We don't have a lastRect, but we do have a firstRect, so we must
// be on the second line. No need to merge in the blockRect just yet
lastRect = lineRect;
} else if(CGRectIsNull(blockRect)) {
// We have both a first and last rect, so we must be on the third line
// we don't have any blockRect to merge it into, so we just set it
// directly.
blockRect = lastRect;
lastRect = lineRect;
} else {
// Everything is already set, so we just merge this line into the
// block.
blockRect = CGRectUnion(blockRect, lastRect);
lastRect = lineRect;
}
} else {
// If the block option isn't being used then each line is being treated
// individually.
[mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:lineRect]]];
}
}
}];
if (measureOption == ASTextKitRendererMeasureOptionBlock) {
// Block measure option is handled differently with just 3 vars for the entire range.
if (!CGRectIsNull(firstRect)) {
if (!CGRectIsNull(blockRect)) {
CGFloat rightEdge = MAX(CGRectGetMaxX(blockRect), CGRectGetMaxX(lastRect));
if (rightEdge > CGRectGetMaxX(firstRect)) {
// Force the right side of the first rect to properly align with the
// right side of the rightmost of the block and last rect
firstRect.size.width += rightEdge - CGRectGetMaxX(firstRect);
}
// Force the left side of the block rect to properly align with the
// left side of the leftmost of the first and last rect
blockRect.origin.x = MIN(CGRectGetMinX(firstRect), CGRectGetMinX(lastRect));
// Force the right side of the block rect to properly align with the
// right side of the rightmost of the first and last rect
blockRect.size.width += MAX(CGRectGetMaxX(firstRect), CGRectGetMaxX(lastRect)) - CGRectGetMaxX(blockRect);
}
if (!CGRectIsNull(lastRect)) {
// Force the left edge of the last rect to properly align with the
// left side of the leftmost of the first and block rect, if necessary.
CGFloat leftEdge = MIN(CGRectGetMinX(blockRect), CGRectGetMinX(firstRect));
CGFloat lastRectNudgeAmount = MAX(CGRectGetMinX(lastRect) - leftEdge, 0);
lastRect.origin.x = MIN(leftEdge, CGRectGetMinX(lastRect));
lastRect.size.width += lastRectNudgeAmount;
}
[mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:firstRect]]];
}
if (!CGRectIsNull(blockRect)) {
[mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:blockRect]]];
}
if (!CGRectIsNull(lastRect)) {
[mutableTextRects addObject:[NSValue valueWithCGRect:[self.shadower offsetRectWithInternalRect:lastRect]]];
}
}
textRects = mutableTextRects;
}];
return textRects;
}
- (NSUInteger)nearestTextIndexAtPosition:(CGPoint)position
{
// Check in a 9-point region around the actual touch point so we make sure
// we get the best attribute for the touch.
__block CGFloat minimumGlyphDistance = CGFLOAT_MAX;
__block NSUInteger minimumGlyphCharacterIndex = NSNotFound;
[self enumerateTextIndexesAtPosition:position usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) {
CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect));
CGFloat currentDistance = sqrtf(powf(position.x - glyphLocation.x, 2.f) + powf(position.y - glyphLocation.y, 2.f));
if (currentDistance < minimumGlyphDistance) {
minimumGlyphDistance = currentDistance;
minimumGlyphCharacterIndex = characterIndex;
}
}];
return minimumGlyphCharacterIndex;
}
/**
Measured from the internal coordinate space of the context, not accounting for shadow offsets. Actually uses CoreText
as an approximation to work around problems in TextKit's glyph sizing.
*/
- (CGRect)_internalRectForGlyphAtIndex:(NSUInteger)glyphIndex
measureOption:(ASTextKitRendererMeasureOption)measureOption
layoutManager:(NSLayoutManager *)layoutManager
textContainer:(NSTextContainer *)textContainer
textStorage:(NSTextStorage *)textStorage
{
NSUInteger charIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
CGGlyph glyph = [layoutManager glyphAtIndex:glyphIndex];
CTFontRef font = (__bridge_retained CTFontRef)[textStorage attribute:NSFontAttributeName
atIndex:charIndex
effectiveRange:NULL];
if (font == nil) {
font = (__bridge_retained CTFontRef)[UIFont systemFontOfSize:12.0];
}
// Glyph Advance
// +-------------------------+
// | |
// | |
// +------------------------+--|-------------------------|--+-----------+-----+ What TextKit returns sometimes
// | | | XXXXXXXXXXX + | | | (approx. correct height, but
// | ---------|--+---------+ XXX XXXX +|-----------|-----| sometimes inaccurate bounding
// | | | XXX XXXXX| | | widths)
// | | | XX XX | | |
// | | | XX | | |
// | | | XXX | | |
// | | | XX | | |
// | | | XXXXXXXXXXX | | |
// | Cap Height->| | XX | | |
// | | | XX | Ascent-->| |
// | | | XX | | |
// | | | XX | | |
// | | | X | | |
// | | | X | | |
// | | | X | | |
// | | | XX | | |
// | | | X | | |
// | ---------|-------+ X +-------------------------------------|
// | | XX | |
// | | X | |
// | | XX Descent------>| |
// | | XXXXXX | |
// | | XXX | |
// +------------------------+-------------------------------------------------+
// |
// +--+Actual bounding box
CGRect glyphRect = [layoutManager boundingRectForGlyphRange:NSMakeRange(glyphIndex, 1)
inTextContainer:textContainer];
// If it is a NSTextAttachment, we don't have the matched glyph and use width of glyphRect instead of advance.
CGFloat advance = (glyph == kCGFontIndexInvalid) ? glyphRect.size.width : CTFontGetAdvancesForGlyphs(font, kCTFontOrientationHorizontal, &glyph, NULL, 1);
// We treat the center of the glyph's bounding box as the center of our new rect
CGPoint glyphCenter = CGPointMake(CGRectGetMidX(glyphRect), CGRectGetMidY(glyphRect));
CGRect properGlyphRect;
if (measureOption == ASTextKitRendererMeasureOptionCapHeight
|| measureOption == ASTextKitRendererMeasureOptionBlock) {
CGFloat ascent = CTFontGetAscent(font);
CGFloat descent = CTFontGetDescent(font);
CGFloat capHeight = CTFontGetCapHeight(font);
CGFloat leading = CTFontGetLeading(font);
CGFloat glyphHeight = ascent + descent;
// For visual balance, we add the cap height padding above the cap, and
// below the baseline, we scale by the descent so it grows with the size of
// the text.
CGFloat topPadding = ASTextKitRendererTextCapHeightPadding * descent;
CGFloat bottomPadding = topPadding;
properGlyphRect = CGRectMake(glyphCenter.x - advance * 0.5,
glyphCenter.y - glyphHeight * 0.5 + (ascent - capHeight) - topPadding + leading,
advance,
capHeight + topPadding + bottomPadding);
} else {
// We are just measuring the line heights here, so we can use the
// heights used by TextKit, which tend to be pretty good.
properGlyphRect = CGRectMake(glyphCenter.x - advance * 0.5,
glyphRect.origin.y,
advance,
glyphRect.size.height);
}
CFRelease(font);
return properGlyphRect;
}
- (void)enumerateTextIndexesAtPosition:(CGPoint)externalPosition usingBlock:(as_text_component_index_block_t)block
{
// This method is a little complex because it has to call out to client code from inside an enumeration that needs
// to achieve a lock on the textkit components. It cannot call out to client code from within that lock so we just
// perform the textkit-locked ops inside the locked context.
ASTextKitContext *lockingContext = self.context;
CGPoint internalPosition = [self.shadower offsetPointWithExternalPoint:externalPosition];
__block BOOL invalidPosition = NO;
[lockingContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
invalidPosition = internalPosition.x > textContainer.size.width
|| internalPosition.y > textContainer.size.height
|| block == NULL;
}];
if (invalidPosition) {
// Short circuit if the position is outside the size of this renderer, or if the block is null.
return;
}
// We break it up into a 44pt box for the touch, and find the closest link attribute-containing glyph to the center of
// the touch.
CGFloat squareSide = 44.f;
// Should be odd if you want to test the center of the touch.
NSInteger pointsOnASide = 3;
// The distance between any 2 of the adjacent points
CGFloat pointSeparation = squareSide / pointsOnASide;
// These are for tracking which point we're on. We start with -pointsOnASide/2 and go to pointsOnASide/2. So if
// pointsOnASide=3, we go from -1 to 1.
NSInteger endIndex = pointsOnASide / 2;
NSInteger startIndex = -endIndex;
BOOL stop = NO;
for (NSInteger i = startIndex; i <= endIndex && !stop; i++) {
for (NSInteger j = startIndex; j <= endIndex && !stop; j++) {
CGPoint currentPoint = CGPointMake(internalPosition.x + i * pointSeparation,
internalPosition.y + j * pointSeparation);
__block NSUInteger characterIndex = NSNotFound;
__block BOOL isValidGlyph = NO;
__block CGRect glyphRect = CGRectNull;
[lockingContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
// We ask the layout manager for the proper glyph at the touch point
NSUInteger glyphIndex = [layoutManager glyphIndexForPoint:currentPoint
inTextContainer:textContainer];
// If it's an invalid glyph, quit.
[layoutManager glyphAtIndex:glyphIndex isValidIndex:&isValidGlyph];
if (!isValidGlyph) {
return;
}
characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
glyphRect = [self _internalRectForGlyphAtIndex:glyphIndex
measureOption:ASTextKitRendererMeasureOptionLineHeight
layoutManager:layoutManager
textContainer:textContainer
textStorage:textStorage];
}];
// Sometimes TextKit plays jokes on us and returns glyphs that really aren't close to the point in question.
// Silly TextKit...
if (!isValidGlyph || !CGRectContainsPoint(CGRectInset(glyphRect, -ASTextKitRendererGlyphTouchHitSlop, -ASTextKitRendererGlyphTouchHitSlop), currentPoint)) {
continue;
}
block(characterIndex, [self.shadower offsetRectWithInternalRect:glyphRect], &stop);
}
}
}
- (CGRect)trailingRect
{
__block CGRect trailingRect = CGRectNull;
[self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
CGSize calculatedSize = textContainer.size;
// If have an empty string, then our whole bounds constitute trailing space.
if ([textStorage length] == 0) {
trailingRect = CGRectMake(0, 0, calculatedSize.width, calculatedSize.height);
return;
}
// Take everything after our final character as trailing space.
NSArray *finalRects = [self rectsForTextRange:NSMakeRange([textStorage length] - 1, 1) measureOption:ASTextKitRendererMeasureOptionLineHeight];
CGRect finalGlyphRect = [[finalRects lastObject] CGRectValue];
CGPoint origin = CGPointMake(CGRectGetMaxX(finalGlyphRect), CGRectGetMinY(finalGlyphRect));
CGSize size = CGSizeMake(calculatedSize.width - origin.x, calculatedSize.height - origin.y);
trailingRect = (CGRect){origin, size};
}];
return trailingRect;
}
- (CGRect)frameForTextRange:(NSRange)textRange
{
__block CGRect textRect = CGRectNull;
[self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
// Bail on invalid range.
if (NSMaxRange(textRange) > [textStorage length]) {
ASDisplayNodeCFailAssert(@"Invalid range");
return;
}
// Force glyph generation and layout.
[layoutManager ensureLayoutForTextContainer:textContainer];
NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:textRange actualCharacterRange:NULL];
textRect = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
}];
return textRect;
}
@end

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASTextKitRenderer.h"
/**
Application extensions to NSTextCheckingType. We're allowed to do this (see NSTextCheckingAllCustomTypes).
*/
static uint64_t const ASTextKitTextCheckingTypeEntity = 1ULL << 33;
static uint64_t const ASTextKitTextCheckingTypeTruncation = 1ULL << 34;
@class ASTextKitEntityAttribute;
@interface ASTextKitTextCheckingResult : NSTextCheckingResult
@property (nonatomic, strong, readonly) ASTextKitEntityAttribute *entityAttribute;
@end
@interface ASTextKitRenderer (TextChecking)
- (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point;
@end

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASTextKitRenderer+TextChecking.h"
#import "ASTextKitAttributes.h"
#import "ASTextKitEntityAttribute.h"
#import "ASTextKitRenderer+Positioning.h"
#import "ASTextKitTailTruncater.h"
@implementation ASTextKitTextCheckingResult
{
// Be explicit about the fact that we are overriding the super class' implementation of -range and -resultType
// and substituting our own custom values. (We could use @synthesize to make these ivars, but our linter correctly
// complains; it's weird to use @synthesize for properties that are redeclared on top of an original declaration in
// the superclass. We only do it here because NSTextCheckingResult doesn't expose an initializer, which is silly.)
NSRange _rangeOverride;
NSTextCheckingType _resultTypeOverride;
}
- (instancetype)initWithType:(NSTextCheckingType)type
entityAttribute:(ASTextKitEntityAttribute *)entityAttribute
range:(NSRange)range
{
if ((self = [super init])) {
_resultTypeOverride = type;
_rangeOverride = range;
_entityAttribute = entityAttribute;
}
return self;
}
- (NSTextCheckingType)resultType
{
return _resultTypeOverride;
}
- (NSRange)range
{
return _rangeOverride;
}
@end
@implementation ASTextKitRenderer (TextChecking)
- (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point
{
__block NSTextCheckingResult *result = nil;
NSAttributedString *attributedString = self.attributes.attributedString;
NSAttributedString *truncationAttributedString = self.attributes.truncationAttributedString;
// get the index of the last character, so we can handle text in the truncation token
NSRange visibleRange = self.truncater.visibleRanges[0];
__block NSRange truncationTokenRange = { NSNotFound, 0 };
[truncationAttributedString enumerateAttribute:ASTextKitTruncationAttributeName inRange:NSMakeRange(0, truncationAttributedString.length)
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
if (value != nil && range.length > 0) {
truncationTokenRange = range;
}
}];
if (truncationTokenRange.location == NSNotFound) {
// The truncation string didn't specify a substring which should be highlighted, so we just highlight it all
truncationTokenRange = { 0, self.attributes.truncationAttributedString.length };
}
truncationTokenRange.location += NSMaxRange(visibleRange);
[self enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger index, CGRect glyphBoundingRect, BOOL *stop){
if (index >= truncationTokenRange.location) {
result = [[ASTextKitTextCheckingResult alloc] initWithType:ASTextKitTextCheckingTypeTruncation
entityAttribute:nil
range:truncationTokenRange];
} else {
NSRange range;
NSDictionary *attributes = [attributedString attributesAtIndex:index effectiveRange:&range];
ASTextKitEntityAttribute *entityAttribute = attributes[ASTextKitEntityAttributeName];
if (entityAttribute) {
result = [[ASTextKitTextCheckingResult alloc] initWithType:ASTextKitTextCheckingTypeEntity
entityAttribute:entityAttribute
range:range];
}
}
if (result != nil) {
*stop = YES;
}
}];
return result;
}
@end

View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <vector>
#import <UIKit/UIKit.h>
#import "ASTextKitAttributes.h"
@class ASTextKitContext;
@class ASTextKitShadower;
@protocol ASTextKitTruncating;
/**
ASTextKitRenderer is a modular object that is responsible for laying out and drawing text.
A renderer will hold onto the TextKit layouts for the given attributes after initialization. This may constitute a
large amount of memory for large enough applications, so care must be taken when keeping many of these around in-memory
at once.
This object is designed to be modular and simple. All complex maintenance of state should occur in sub-objects or be
derived via pure functions or categories. No touch-related handling belongs in this class.
ALL sizing and layout information from this class is in the external coordinate space of the TextKit components. This
is an important distinction because all internal sizing and layout operations are carried out within the shadowed
coordinate space. Padding will be added for you in order to ensure clipping does not occur, and additional information
on this transform is available via the shadower should you need it.
*/
@interface ASTextKitRenderer : NSObject
/**
Designated Initializer
dvlkferufedgjnhjjfhldjedlunvtdtv
@discussion Sizing will occur as a result of initialization, so be careful when/where you use this.
*/
- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)textComponentAttributes
constrainedSize:(const CGSize)constrainedSize;
@property (nonatomic, strong, readonly) ASTextKitContext *context;
@property (nonatomic, strong, readonly) id<ASTextKitTruncating> truncater;
@property (nonatomic, strong, readonly) ASTextKitShadower *shadower;
@property (nonatomic, assign, readonly) ASTextKitAttributes attributes;
@property (nonatomic, assign, readonly) CGSize constrainedSize;
#pragma mark - Drawing
/*
Draw the renderer's text content into the bounds provided.
@param bounds The rect in which to draw the contents of the renderer.
*/
- (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds;
#pragma mark - Layout
/*
Returns the computed size of the renderer given the constrained size and other parameters in the initializer.
*/
- (CGSize)size;
#pragma mark - Text Ranges
/*
The character range from the original attributedString that is displayed by the renderer given the parameters in the
initializer.
*/
- (std::vector<NSRange>)visibleRanges;
/*
The number of lines shown in the string.
*/
- (NSUInteger)lineCount;
@end

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASTextKitRenderer.h"
#import "ASAssert.h"
#import "ASTextKitContext.h"
#import "ASTextKitShadower.h"
#import "ASTextKitTailTruncater.h"
#import "ASTextKitTruncating.h"
static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
{
static NSCharacterSet *truncationCharacterSet;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableCharacterSet *mutableCharacterSet = [[NSMutableCharacterSet alloc] init];
[mutableCharacterSet formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[mutableCharacterSet addCharactersInString:@".,!?:;"];
truncationCharacterSet = mutableCharacterSet;
});
return truncationCharacterSet;
}
@implementation ASTextKitRenderer {
CGSize _calculatedSize;
}
#pragma mark - Initialization
- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)attributes
constrainedSize:(const CGSize)constrainedSize
{
if (self = [super init]) {
_constrainedSize = constrainedSize;
_attributes = attributes;
_shadower = [[ASTextKitShadower alloc] initWithShadowOffset:attributes.shadowOffset
shadowColor:attributes.shadowColor
shadowOpacity:attributes.shadowOpacity
shadowRadius:attributes.shadowRadius];
// We must inset the constrained size by the size of the shadower.
CGSize shadowConstrainedSize = [_shadower insetSizeWithConstrainedSize:_constrainedSize];
_context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString
lineBreakMode:attributes.lineBreakMode
maximumNumberOfLines:attributes.maximumNumberOfLines
exclusionPaths:attributes.exclusionPaths
constrainedSize:shadowConstrainedSize
layoutManagerFactory:attributes.layoutManagerFactory];
_truncater = [[ASTextKitTailTruncater alloc] initWithContext:_context
truncationAttributedString:attributes.truncationAttributedString
avoidTailTruncationSet:attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet()
constrainedSize:shadowConstrainedSize];
[self _calculateSize];
}
return self;
}
#pragma mark - Sizing
- (void)_calculateSize
{
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by
// -usedRectForTextContainer:).
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
[layoutManager ensureLayoutForTextContainer:textContainer];
}];
CGRect constrainedRect = {CGPointZero, _constrainedSize};
__block CGRect boundingRect;
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
boundingRect = [layoutManager usedRectForTextContainer:textContainer];
}];
// TextKit often returns incorrect glyph bounding rects in the horizontal direction, so we clip to our bounding rect
// to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect.
boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size});
_calculatedSize = [_shadower outsetSizeWithInsetSize:boundingRect.size];
}
- (CGSize)size
{
return _calculatedSize;
}
#pragma mark - Drawing
- (void)drawInContext:(CGContextRef)context bounds:(CGRect)bounds;
{
// We add an assertion so we can track the rare conditions where a graphics context is not present
ASDisplayNodeAssertNotNil(context, @"This is no good without a context.");
CGRect shadowInsetBounds = [_shadower insetRectWithConstrainedRect:bounds];
CGContextSaveGState(context);
[_shadower setShadowInContext:context];
UIGraphicsPushContext(context);
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
}];
UIGraphicsPopContext();
CGContextRestoreGState(context);
}
#pragma mark - String Ranges
- (NSUInteger)lineCount
{
__block NSUInteger lineCount = 0;
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
for (NSRange lineRange = { 0, 0 }; NSMaxRange(lineRange) < [layoutManager numberOfGlyphs]; lineCount++) {
[layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange];
}
}];
return lineCount;
}
- (std::vector<NSRange>)visibleRanges
{
return _truncater.visibleRanges;
}
@end

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
/**
* @abstract an immutable class for calculating shadow padding drawing a shadowed background for text
*/
@interface ASTextKitShadower : NSObject
- (instancetype)initWithShadowOffset:(CGSize)shadowOffset
shadowColor:(UIColor *)shadowColor
shadowOpacity:(CGFloat)shadowOpacity
shadowRadius:(CGFloat)shadowRadius;
/**
* @abstract The offset from the top-left corner at which the shadow starts.
* @discussion A positive width will move the shadow to the right.
* A positive height will move the shadow downwards.
*/
@property (nonatomic, readonly, assign) CGSize shadowOffset;
//! CGColor in which the shadow is drawn
@property (nonatomic, readonly, strong) UIColor *shadowColor;
//! Alpha of the shadow
@property (nonatomic, readonly, assign) CGFloat shadowOpacity;
//! Radius, in pixels
@property (nonatomic, readonly, assign) CGFloat shadowRadius;
/**
* @abstract The edge insets which represent shadow padding
* @discussion Each edge inset is less than or equal to zero.
*
* Example:
* CGRect boundsWithoutShadowPadding; // Large enough to fit text, not large enough to fit the shadow as well
* UIEdgeInsets shadowPadding = [shadower shadowPadding];
* CGRect boundsWithShadowPadding = UIEdgeInsetsRect(boundsWithoutShadowPadding, shadowPadding);
*/
- (UIEdgeInsets)shadowPadding;
- (CGSize)insetSizeWithConstrainedSize:(CGSize)constrainedSize;
- (CGRect)insetRectWithConstrainedRect:(CGRect)constrainedRect;
- (CGSize)outsetSizeWithInsetSize:(CGSize)insetSize;
- (CGRect)outsetRectWithInsetRect:(CGRect)insetRect;
- (CGRect)offsetRectWithInternalRect:(CGRect)internalRect;
- (CGPoint)offsetPointWithInternalPoint:(CGPoint)internalPoint;
- (CGPoint)offsetPointWithExternalPoint:(CGPoint)externalPoint;
/**
* @abstract draws the shadow for text in the provided CGContext
* @discussion Call within the text node's +drawRect method
*/
- (void)setShadowInContext:(CGContextRef)context;
@end

View File

@@ -0,0 +1,148 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASTextKitShadower.h"
static inline CGSize _insetSize(CGSize size, UIEdgeInsets insets)
{
return UIEdgeInsetsInsetRect({.size = size}, insets).size;
}
static inline UIEdgeInsets _invertInsets(UIEdgeInsets insets)
{
return {
.top = -insets.top,
.left = -insets.left,
.bottom = -insets.bottom,
.right = -insets.right
};
}
@implementation ASTextKitShadower {
UIEdgeInsets _calculatedShadowPadding;
}
- (instancetype)initWithShadowOffset:(CGSize)shadowOffset
shadowColor:(UIColor *)shadowColor
shadowOpacity:(CGFloat)shadowOpacity
shadowRadius:(CGFloat)shadowRadius
{
if (self = [super init]) {
_shadowOffset = shadowOffset;
_shadowColor = shadowColor;
_shadowOpacity = shadowOpacity;
_shadowRadius = shadowRadius;
_calculatedShadowPadding = UIEdgeInsetsMake(-INFINITY, -INFINITY, INFINITY, INFINITY);
}
return self;
}
/*
* This method is duplicated here because it gets called frequently, and we were
* wasting valuable time constructing a state object to ask it.
*/
- (BOOL)_shouldDrawShadow
{
return _shadowOpacity != 0.0 && _shadowColor != nil && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero));
}
- (void)setShadowInContext:(CGContextRef)context
{
if ([self _shouldDrawShadow]) {
CGColorRef textShadowColor = CGColorRetain(_shadowColor.CGColor);
CGSize textShadowOffset = _shadowOffset;
CGFloat textShadowOpacity = _shadowOpacity;
CGFloat textShadowRadius = _shadowRadius;
if (textShadowOpacity != 1.0) {
CGFloat inherentAlpha = CGColorGetAlpha(textShadowColor);
CGColorRef oldTextShadowColor = textShadowColor;
textShadowColor = CGColorCreateCopyWithAlpha(textShadowColor, inherentAlpha * textShadowOpacity);
CGColorRelease(oldTextShadowColor);
}
CGContextSetShadowWithColor(context, textShadowOffset, textShadowRadius, textShadowColor);
CGColorRelease(textShadowColor);
}
}
- (UIEdgeInsets)shadowPadding
{
if (_calculatedShadowPadding.top == -INFINITY) {
if (![self _shouldDrawShadow]) {
return UIEdgeInsetsZero;
}
UIEdgeInsets shadowPadding = UIEdgeInsetsZero;
// min values are expected to be negative for most typical shadowOffset and
// blurRadius settings:
shadowPadding.top = fminf(0.0f, _shadowOffset.height - _shadowRadius);
shadowPadding.left = fminf(0.0f, _shadowOffset.width - _shadowRadius);
shadowPadding.bottom = fminf(0.0f, -_shadowOffset.height - _shadowRadius);
shadowPadding.right = fminf(0.0f, -_shadowOffset.width - _shadowRadius);
_calculatedShadowPadding = shadowPadding;
}
return _calculatedShadowPadding;
}
- (CGSize)insetSizeWithConstrainedSize:(CGSize)constrainedSize
{
return _insetSize(constrainedSize, _invertInsets([self shadowPadding]));
}
- (CGRect)insetRectWithConstrainedRect:(CGRect)constrainedRect
{
return UIEdgeInsetsInsetRect(constrainedRect, _invertInsets([self shadowPadding]));
}
- (CGSize)outsetSizeWithInsetSize:(CGSize)insetSize
{
return _insetSize(insetSize, [self shadowPadding]);
}
- (CGRect)outsetRectWithInsetRect:(CGRect)insetRect
{
return UIEdgeInsetsInsetRect(insetRect, [self shadowPadding]);
}
- (CGRect)offsetRectWithInternalRect:(CGRect)internalRect
{
return (CGRect){
.origin = [self offsetPointWithInternalPoint:internalRect.origin],
.size = internalRect.size
};
}
- (CGPoint)offsetPointWithInternalPoint:(CGPoint)internalPoint
{
UIEdgeInsets shadowPadding = [self shadowPadding];
return (CGPoint){
internalPoint.x + shadowPadding.left,
internalPoint.y + shadowPadding.top
};
}
- (CGPoint)offsetPointWithExternalPoint:(CGPoint)externalPoint
{
UIEdgeInsets shadowPadding = [self shadowPadding];
return (CGPoint){
externalPoint.x - shadowPadding.left,
externalPoint.y - shadowPadding.top
};
}
@end

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
#import "ASTextKitTruncating.h"
@interface ASTextKitTailTruncater : NSObject <ASTextKitTruncating>
@end

View File

@@ -0,0 +1,191 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ASAssert.h"
#import "ASTextKitContext.h"
#import "ASTextKitTailTruncater.h"
@implementation ASTextKitTailTruncater
{
__weak ASTextKitContext *_context;
NSAttributedString *_truncationAttributedString;
NSCharacterSet *_avoidTailTruncationSet;
CGSize _constrainedSize;
}
@synthesize visibleRanges = _visibleRanges;
@synthesize truncationStringRect = _truncationStringRect;
- (instancetype)initWithContext:(ASTextKitContext *)context
truncationAttributedString:(NSAttributedString *)truncationAttributedString
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
constrainedSize:(CGSize)constrainedSize
{
if (self = [super init]) {
_context = context;
_truncationAttributedString = truncationAttributedString;
_avoidTailTruncationSet = avoidTailTruncationSet;
_constrainedSize = constrainedSize;
[self _truncate];
}
return self;
}
/**
Calculates the intersection of the truncation message within the end of the last line.
*/
- (NSUInteger)_calculateCharacterIndexBeforeTruncationMessage:(NSLayoutManager *)layoutManager
textStorage:(NSTextStorage *)textStorage
textContainer:(NSTextContainer *)textContainer
{
CGRect constrainedRect = (CGRect){ .size = textContainer.size };
NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:constrainedRect
inTextContainer:textContainer];
NSInteger lastVisibleGlyphIndex = (NSMaxRange(visibleGlyphRange) - 1);
if (lastVisibleGlyphIndex < 0) {
return NSNotFound;
}
CGRect lastLineRect = [layoutManager lineFragmentRectForGlyphAtIndex:lastVisibleGlyphIndex
effectiveRange:NULL];
CGRect lastLineUsedRect = [layoutManager lineFragmentUsedRectForGlyphAtIndex:lastVisibleGlyphIndex
effectiveRange:NULL];
NSParagraphStyle *paragraphStyle = [textStorage attributesAtIndex:[layoutManager characterIndexForGlyphAtIndex:lastVisibleGlyphIndex]
effectiveRange:NULL][NSParagraphStyleAttributeName];
// We assume LTR so long as the writing direction is not
BOOL rtlWritingDirection = paragraphStyle ? paragraphStyle.baseWritingDirection == NSWritingDirectionRightToLeft : NO;
// We only want to treat the trunction rect as left-aligned in the case that we are right-aligned and our writing
// direction is RTL.
BOOL leftAligned = CGRectGetMinX(lastLineRect) == CGRectGetMinX(lastLineUsedRect) || !rtlWritingDirection;
// Calculate the bounding rectangle for the truncation message
ASTextKitContext *truncationContext = [[ASTextKitContext alloc] initWithAttributedString:_truncationAttributedString
lineBreakMode:NSLineBreakByWordWrapping
maximumNumberOfLines:1
exclusionPaths:nil
constrainedSize:constrainedRect.size
layoutManagerFactory:nil];
__block CGRect truncationUsedRect;
[truncationContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *truncationLayoutManager, NSTextStorage *truncationTextStorage, NSTextContainer *truncationTextContainer) {
// Size the truncation message
[truncationLayoutManager ensureLayoutForTextContainer:truncationTextContainer];
NSRange truncationGlyphRange = [truncationLayoutManager glyphRangeForTextContainer:truncationTextContainer];
truncationUsedRect = [truncationLayoutManager boundingRectForGlyphRange:truncationGlyphRange
inTextContainer:truncationTextContainer];
}];
CGFloat truncationOriginX = (leftAligned ?
CGRectGetMaxX(constrainedRect) - truncationUsedRect.size.width :
CGRectGetMinX(constrainedRect));
CGRect translatedTruncationRect = CGRectMake(truncationOriginX,
CGRectGetMinY(lastLineRect),
truncationUsedRect.size.width,
truncationUsedRect.size.height);
// Determine which glyph is the first to be clipped / overlaps the truncation message.
CGFloat truncationMessageX = (leftAligned ?
CGRectGetMinX(translatedTruncationRect) :
CGRectGetMaxX(translatedTruncationRect));
CGPoint beginningOfTruncationMessage = CGPointMake(truncationMessageX,
CGRectGetMidY(translatedTruncationRect));
NSUInteger firstClippedGlyphIndex = [layoutManager glyphIndexForPoint:beginningOfTruncationMessage
inTextContainer:textContainer
fractionOfDistanceThroughGlyph:NULL];
// If it didn't intersect with any text then it should just return the last visible character index, since the
// truncation rect can fully fit on the line without clipping any other text.
if (firstClippedGlyphIndex == NSNotFound) {
return [layoutManager characterIndexForGlyphAtIndex:lastVisibleGlyphIndex];
}
NSUInteger firstCharacterIndexToReplace = [layoutManager characterIndexForGlyphAtIndex:firstClippedGlyphIndex];
// Break on word boundaries
return [self _findTruncationInsertionPointAtOrBeforeCharacterIndex:firstCharacterIndexToReplace
layoutManager:layoutManager
textStorage:textStorage];
}
/**
Finds the first whitespace at or before the character index do we don't truncate in the middle of words
If there are multiple whitespaces together (say a space and a newline), this will backtrack to the first one
*/
- (NSUInteger)_findTruncationInsertionPointAtOrBeforeCharacterIndex:(NSUInteger)firstCharacterIndexToReplace
layoutManager:(NSLayoutManager *)layoutManager
textStorage:(NSTextStorage *)textStorage
{
// Don't attempt to truncate beyond the end of the string
if (firstCharacterIndexToReplace >= textStorage.length) {
return 0;
}
// Find the glyph range of the line fragment containing the first character to replace.
NSRange lineGlyphRange;
[layoutManager lineFragmentRectForGlyphAtIndex:[layoutManager glyphIndexForCharacterAtIndex:firstCharacterIndexToReplace]
effectiveRange:&lineGlyphRange];
// Look for the first whitespace from the end of the line, starting from the truncation point
NSUInteger startingSearchIndex = [layoutManager characterIndexForGlyphAtIndex:lineGlyphRange.location];
NSUInteger endingSearchIndex = firstCharacterIndexToReplace;
NSRange rangeToSearch = NSMakeRange(startingSearchIndex, (endingSearchIndex - startingSearchIndex));
NSRange rangeOfLastVisibleAvoidedChars = { .location = NSNotFound };
if (_avoidTailTruncationSet) {
rangeOfLastVisibleAvoidedChars = [textStorage.string rangeOfCharacterFromSet:_avoidTailTruncationSet
options:NSBackwardsSearch
range:rangeToSearch];
}
// Couldn't find a good place to truncate. Might be because there is no whitespace in the text, or we're dealing
// with a foreign language encoding. Settle for truncating at the original place, which may be mid-word.
if (rangeOfLastVisibleAvoidedChars.location == NSNotFound) {
return firstCharacterIndexToReplace;
} else {
return rangeOfLastVisibleAvoidedChars.location;
}
}
- (void)_truncate
{
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
NSUInteger originalStringLength = textStorage.length;
[layoutManager ensureLayoutForTextContainer:textContainer];
NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:{ .size = textContainer.size }
inTextContainer:textContainer];
NSRange visibleCharacterRange = [layoutManager characterRangeForGlyphRange:visibleGlyphRange
actualGlyphRange:NULL];
// Check if text is truncated, and if so apply our truncation string
if (visibleCharacterRange.length < originalStringLength && _truncationAttributedString.length > 0) {
NSInteger firstCharacterIndexToReplace = [self _calculateCharacterIndexBeforeTruncationMessage:layoutManager
textStorage:textStorage
textContainer:textContainer];
if (firstCharacterIndexToReplace == 0 || firstCharacterIndexToReplace == NSNotFound) {
return;
}
// Update/truncate the visible range of text
visibleCharacterRange = NSMakeRange(0, firstCharacterIndexToReplace);
NSRange truncationReplacementRange = NSMakeRange(firstCharacterIndexToReplace,
textStorage.length - firstCharacterIndexToReplace);
// Replace the end of the visible message with the truncation string
[textStorage replaceCharactersInRange:truncationReplacementRange
withAttributedString:_truncationAttributedString];
}
_visibleRanges = { visibleCharacterRange };
}];
}
@end

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <vector>
#import <UIKit/UIKit.h>
#import "ASTextKitRenderer.h"
@protocol ASTextKitTruncating <NSObject>
@property (nonatomic, assign, readonly) std::vector<NSRange> visibleRanges;
@property (nonatomic, assign, readonly) CGRect truncationStringRect;
/**
A truncater object is initialized with the full state of the text. It is a Single Responsibility Object that is
mutative. It configures the state of the TextKit components (layout manager, text container, text storage) to achieve
the intended truncation, then it stores the resulting state for later fetching.
The truncater may mutate the state of the text storage such that only the drawn string is actually present in the
text storage itself.
The truncater should not store a strong reference to the context to prevent retain cycles.
*/
- (instancetype)initWithContext:(ASTextKitContext *)context
truncationAttributedString:(NSAttributedString *)truncationAttributedString
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
constrainedSize:(CGSize)constrainedSize;
@end

View File

@@ -28,6 +28,8 @@
self.recordMode = NO;
}
#pragma mark - Utility methods
static NSArray *defaultSubnodes()
{
return defaultSubnodesWithSameSize(CGSizeZero, NO);
@@ -63,6 +65,24 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[self testStackLayoutSpecWithStyle:style sizeRange:sizeRange subnodes:subnodes identifier:identifier];
}
- (void)testStackLayoutSpecWithDirection:(ASStackLayoutDirection)direction
itemsHorizontalAlignment:(ASHorizontalAlignment)horizontalAlignment
itemsVerticalAlignment:(ASVerticalAlignment)verticalAlignment
identifier:(NSString *)identifier
{
NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, NO);
ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init];
stackLayoutSpec.direction = direction;
stackLayoutSpec.children = subnodes;
[stackLayoutSpec setHorizontalAlignment:horizontalAlignment];
[stackLayoutSpec setVerticalAlignment:verticalAlignment];
CGSize exactSize = CGSizeMake(200, 200);
static ASSizeRange kSize = ASSizeRangeMake(exactSize, exactSize);
[self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:subnodes identifier:identifier];
}
- (void)testStackLayoutSpecWithStyle:(ASStackLayoutSpecStyle)style
sizeRange:(ASSizeRange)sizeRange
subnodes:(NSArray *)subnodes
@@ -76,13 +96,23 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
sizeRange:(ASSizeRange)sizeRange
subnodes:(NSArray *)subnodes
identifier:(NSString *)identifier
{
ASStackLayoutSpec *stackLayoutSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:style.direction
spacing:style.spacing
justifyContent:style.justifyContent
alignItems:style.alignItems
children:children];
[self testStackLayoutSpec:stackLayoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier];
}
- (void)testStackLayoutSpec:(ASStackLayoutSpec *)stackLayoutSpec
sizeRange:(ASSizeRange)sizeRange
subnodes:(NSArray *)subnodes
identifier:(NSString *)identifier
{
ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor whiteColor]);
ASLayoutSpec *layoutSpec =
[ASBackgroundLayoutSpec
backgroundLayoutSpecWithChild:[ASStackLayoutSpec stackLayoutSpecWithDirection:style.direction spacing:style.spacing justifyContent:style.justifyContent alignItems:style.alignItems children:children]
background:backgroundNode];
ASLayoutSpec *layoutSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:stackLayoutSpec background:backgroundNode];
NSMutableArray *newSubnodes = [NSMutableArray arrayWithObject:backgroundNode];
[newSubnodes addObjectsFromArray:subnodes];
@@ -90,6 +120,8 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[self testLayoutSpec:layoutSpec sizeRange:sizeRange subnodes:newSubnodes identifier:identifier];
}
#pragma mark -
- (void)testUnderflowBehaviors
{
// width 300px; height 0-300px
@@ -98,6 +130,8 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:YES sizeRange:kSize identifier:@"flex"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flex:NO sizeRange:kSize identifier:@"justifySpaceBetween"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flex:NO sizeRange:kSize identifier:@"justifySpaceAround"];
}
- (void)testOverflowBehaviors
@@ -108,6 +142,10 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentCenter flex:NO sizeRange:kSize identifier:@"justifyCenter"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentEnd flex:NO sizeRange:kSize identifier:@"justifyEnd"];
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentStart flex:YES sizeRange:kSize identifier:@"flex"];
// On overflow, "space between" is identical to "content start"
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flex:NO sizeRange:kSize identifier:@"justifyStart"];
// On overflow, "space around" is identical to "content center"
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flex:NO sizeRange:kSize identifier:@"justifyCenter"];
}
- (void)testOverflowBehaviorsWhenAllFlexShrinkChildrenHaveBeenClampedToZeroButViolationStillExists
@@ -246,6 +284,50 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[self testStackLayoutSpecWithStyle:style sizeRange:kVariableHeight subnodes:subnodes identifier:@"variableHeight"];
}
- (void)testJustifiedSpaceBetweenWithOneChild
{
ASStackLayoutSpecStyle style = {
.direction = ASStackLayoutDirectionHorizontal,
.justifyContent = ASStackLayoutJustifyContentSpaceBetween
};
ASStaticSizeDisplayNode *child = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
child.staticSize = {50, 50};
// width 300px; height 0-INF
static ASSizeRange kVariableHeight = {{300, 0}, {300, INFINITY}};
[self testStackLayoutSpecWithStyle:style sizeRange:kVariableHeight subnodes:@[child] identifier:nil];
}
- (void)testJustifiedSpaceAroundWithOneChild
{
ASStackLayoutSpecStyle style = {
.direction = ASStackLayoutDirectionHorizontal,
.justifyContent = ASStackLayoutJustifyContentSpaceAround
};
ASStaticSizeDisplayNode *child = ASDisplayNodeWithBackgroundColor([UIColor redColor]);
child.staticSize = {50, 50};
// width 300px; height 0-INF
static ASSizeRange kVariableHeight = {{300, 0}, {300, INFINITY}};
[self testStackLayoutSpecWithStyle:style sizeRange:kVariableHeight subnodes:@[child] identifier:nil];
}
- (void)testJustifiedSpaceBetweenWithRemainingSpace
{
// width 301px; height 0-300px; 1px remaining
static ASSizeRange kSize = {{301, 0}, {301, 300}};
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceBetween flex:NO sizeRange:kSize identifier:nil];
}
- (void)testJustifiedSpaceAroundWithRemainingSpace
{
// width 305px; height 0-300px; 5px remaining
static ASSizeRange kSize = {{305, 0}, {305, 300}};
[self testStackLayoutSpecWithJustify:ASStackLayoutJustifyContentSpaceAround flex:NO sizeRange:kSize identifier:nil];
}
- (void)testChildThatChangesCrossSizeWhenMainSizeIsFlexed
{
ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal};
@@ -522,4 +604,54 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex)
[self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil];
}
- (void)testHorizontalAndVerticalAlignments
{
[self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASAlignmentLeft itemsVerticalAlignment:ASAlignmentTop identifier:@"horizontalTopLeft"];
[self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASAlignmentMiddle itemsVerticalAlignment:ASAlignmentCenter identifier:@"horizontalCenter"];
[self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASAlignmentRight itemsVerticalAlignment:ASAlignmentBottom identifier:@"horizontalBottomRight"];
[self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASAlignmentLeft itemsVerticalAlignment:ASAlignmentTop identifier:@"verticalTopLeft"];
[self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASAlignmentMiddle itemsVerticalAlignment:ASAlignmentCenter identifier:@"verticalCenter"];
[self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASAlignmentRight itemsVerticalAlignment:ASAlignmentBottom identifier:@"verticalBottomRight"];
}
- (void)testDirectionChangeAfterSettingHorizontalAndVerticalAlignments
{
ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; // Default direction is horizontal
stackLayoutSpec.horizontalAlignment = ASAlignmentRight;
stackLayoutSpec.verticalAlignment = ASAlignmentCenter;
XCTAssertEqual(stackLayoutSpec.alignItems, ASStackLayoutAlignItemsCenter);
XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentEnd);
stackLayoutSpec.direction = ASStackLayoutDirectionVertical;
XCTAssertEqual(stackLayoutSpec.alignItems, ASStackLayoutAlignItemsEnd);
XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentCenter);
}
- (void)testAlignItemsAndJustifyContentRestrictionsIfHorizontalAndVerticalAlignmentsAreUsed
{
ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init];
// No assertions should be thrown here because alignments are not used
stackLayoutSpec.alignItems = ASStackLayoutAlignItemsEnd;
stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentEnd;
// Set alignments and assert that assertions are thrown
stackLayoutSpec.horizontalAlignment = ASAlignmentMiddle;
stackLayoutSpec.verticalAlignment = ASAlignmentCenter;
XCTAssertThrows(stackLayoutSpec.alignItems = ASStackLayoutAlignItemsEnd);
XCTAssertThrows(stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentEnd);
// Unset alignments. alignItems and justifyContent should not be changed
stackLayoutSpec.horizontalAlignment = ASHorizontalAlignmentNone;
stackLayoutSpec.verticalAlignment = ASVerticalAlignmentNone;
XCTAssertEqual(stackLayoutSpec.alignItems, ASStackLayoutAlignItemsCenter);
XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentCenter);
// Now that alignments are none, setting alignItems and justifyContent should be allowed again
stackLayoutSpec.alignItems = ASStackLayoutAlignItemsEnd;
stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentEnd;
XCTAssertEqual(stackLayoutSpec.alignItems, ASStackLayoutAlignItemsEnd);
XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentEnd);
}
@end

View File

@@ -10,13 +10,13 @@
#import <XCTest/XCTest.h>
#import "ASTextNodeCoreTextAdditions.h"
#import "ASTextKitCoreTextAdditions.h"
@interface ASTextNodeCoreTextAdditionsTests : XCTestCase
@interface ASTextKitCoreTextAdditionsTests : XCTestCase
@end
@implementation ASTextNodeCoreTextAdditionsTests
@implementation ASTextKitCoreTextAdditionsTests
- (void)testAttributeCleansing
{

View File

@@ -0,0 +1,136 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
#import "ASTextKitAttributes.h"
#import "ASTextKitRenderer.h"
@interface ASTextKitTests : XCTestCase
@end
static UITextView *UITextViewWithAttributes(const ASTextKitAttributes &attributes, const CGSize constrainedSize)
{
UITextView *textView = [[UITextView alloc] initWithFrame:{ .size = constrainedSize }];
textView.backgroundColor = [UIColor clearColor];
textView.textContainer.lineBreakMode = attributes.lineBreakMode;
textView.textContainer.lineFragmentPadding = 0.f;
textView.textContainer.maximumNumberOfLines = attributes.maximumNumberOfLines;
textView.textContainerInset = UIEdgeInsetsZero;
textView.layoutManager.usesFontLeading = NO;
textView.attributedText = attributes.attributedString;
return textView;
}
static UIImage *UITextViewImageWithAttributes(const ASTextKitAttributes &attributes, const CGSize constrainedSize)
{
UITextView *textView = UITextViewWithAttributes(attributes, constrainedSize);
UIGraphicsBeginImageContextWithOptions(constrainedSize, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
{
[textView.layer renderInContext:context];
}
CGContextRestoreGState(context);
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshot;
}
static UIImage *ASTextKitImageWithAttributes(const ASTextKitAttributes &attributes, const CGSize constrainedSize)
{
ASTextKitRenderer *renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:attributes
constrainedSize:constrainedSize];
UIGraphicsBeginImageContextWithOptions(constrainedSize, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
{
[renderer drawInContext:context bounds:{.size = constrainedSize}];
}
CGContextRestoreGState(context);
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshot;
}
static BOOL checkAttributes(const ASTextKitAttributes &attributes, const CGSize constrainedSize)
{
FBSnapshotTestController *controller = [[FBSnapshotTestController alloc] init];
UIImage *labelImage = UITextViewImageWithAttributes(attributes, constrainedSize);
UIImage *textKitImage = ASTextKitImageWithAttributes(attributes, constrainedSize);
return [controller compareReferenceImage:labelImage toImage:textKitImage error:nil];
}
@implementation ASTextKitTests
- (void)testSimpleStrings
{
ASTextKitAttributes attributes {
.attributedString = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}]
};
XCTAssert(checkAttributes(attributes, { 100, 100 }));
}
- (void)testChangingAPropertyChangesHash
{
NSAttributedString *as = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}];
ASTextKitAttributes attrib1 {
.attributedString = as,
.lineBreakMode = NSLineBreakByClipping,
};
ASTextKitAttributes attrib2 {
.attributedString = as,
};
XCTAssertNotEqual(attrib1.hash(), attrib2.hash(), @"Hashes should differ when NSLineBreakByClipping changes.");
}
- (void)testSameStringHashesSame
{
ASTextKitAttributes attrib1 {
.attributedString = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}],
};
ASTextKitAttributes attrib2 {
.attributedString = [[NSAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}],
};
XCTAssertEqual(attrib1.hash(), attrib2.hash(), @"Hashes should be the same!");
}
- (void)testStringsWithVariableAttributes
{
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:@"hello" attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:12]}];
for (int i = 0; i < attrStr.length; i++) {
// Color each character something different
CGFloat factor = ((CGFloat)i) / ((CGFloat)attrStr.length);
[attrStr addAttribute:NSForegroundColorAttributeName
value:[UIColor colorWithRed:factor
green:1.0 - factor
blue:0.0
alpha:1.0]
range:NSMakeRange(i, 1)];
}
ASTextKitAttributes attributes {
.attributedString = attrStr
};
XCTAssert(checkAttributes(attributes, { 100, 100 }));
}
@end

View File

@@ -0,0 +1,146 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "ASTextKitContext.h"
#import "ASTextKitTailTruncater.h"
@interface ASTextKitTruncationTests : XCTestCase
@end
@implementation ASTextKitTruncationTests
- (NSString *)_sentenceString
{
return @"90's cray photo booth tote bag bespoke Carles. Plaid wayfarers Odd Future master cleanse tattooed four dollar toast small batch kale chips leggings meh photo booth occupy irony.";
}
- (NSAttributedString *)_sentenceAttributedString
{
return [[NSAttributedString alloc] initWithString:[self _sentenceString] attributes:@{}];
}
- (NSAttributedString *)_simpleTruncationAttributedString
{
return [[NSAttributedString alloc] initWithString:@"..." attributes:@{}];
}
- (void)testEmptyTruncationStringSameAsStraightTextKitTailTruncation
{
CGSize constrainedSize = CGSizeMake(100, 50);
NSAttributedString *attributedString = [self _sentenceAttributedString];
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
lineBreakMode:NSLineBreakByWordWrapping
maximumNumberOfLines:0
exclusionPaths:nil
constrainedSize:constrainedSize
layoutManagerFactory:nil];
__block NSRange textKitVisibleRange;
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
textKitVisibleRange = [layoutManager characterRangeForGlyphRange:[layoutManager glyphRangeForTextContainer:textContainer]
actualGlyphRange:NULL];
}];
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:nil
avoidTailTruncationSet:nil
constrainedSize:constrainedSize];
XCTAssert(NSEqualRanges(textKitVisibleRange, tailTruncater.visibleRanges[0]));
}
- (void)testSimpleTailTruncation
{
CGSize constrainedSize = CGSizeMake(100, 60);
NSAttributedString *attributedString = [self _sentenceAttributedString];
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
lineBreakMode:NSLineBreakByWordWrapping
maximumNumberOfLines:0
exclusionPaths:nil
constrainedSize:constrainedSize
layoutManagerFactory:nil];
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@""]
constrainedSize:constrainedSize];
__block NSString *drawnString;
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
drawnString = textStorage.string;
}];
NSString *expectedString = @"90's cray photo booth tote bag bespoke Carles. Plaid wayfarers...";
XCTAssertEqualObjects(expectedString, drawnString);
XCTAssert(NSEqualRanges(NSMakeRange(0, 62), tailTruncater.visibleRanges[0]));
}
- (void)testAvoidedCharTailWordBoundaryTruncation
{
CGSize constrainedSize = CGSizeMake(100, 50);
NSAttributedString *attributedString = [self _sentenceAttributedString];
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
lineBreakMode:NSLineBreakByWordWrapping
maximumNumberOfLines:0
exclusionPaths:nil
constrainedSize:constrainedSize
layoutManagerFactory:nil];
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
constrainedSize:constrainedSize];
(void)tailTruncater;
__block NSString *drawnString;
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
drawnString = textStorage.string;
}];
// This should have removed the additional "." in the string right after Carles.
NSString *expectedString = @"90's cray photo booth tote bag bespoke Carles...";
XCTAssertEqualObjects(expectedString, drawnString);
}
- (void)testAvoidedCharTailCharBoundaryTruncation
{
CGSize constrainedSize = CGSizeMake(50, 50);
NSAttributedString *attributedString = [self _sentenceAttributedString];
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
lineBreakMode:NSLineBreakByCharWrapping
maximumNumberOfLines:0
exclusionPaths:nil
constrainedSize:constrainedSize
layoutManagerFactory:nil];
ASTextKitTailTruncater *tailTruncater = [[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
constrainedSize:constrainedSize];
// So Xcode doesn't yell at me for an unused var...
(void)tailTruncater;
__block NSString *drawnString;
[context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
drawnString = textStorage.string;
}];
// This should have removed the additional "." in the string right after Carles.
NSString *expectedString = @"90's cray photo booth t...";
XCTAssertEqualObjects(expectedString, drawnString);
}
- (void)testHandleZeroHeightConstrainedSize
{
CGSize constrainedSize = CGSizeMake(50, 0);
NSAttributedString *attributedString = [self _sentenceAttributedString];
ASTextKitContext *context = [[ASTextKitContext alloc] initWithAttributedString:attributedString
lineBreakMode:NSLineBreakByCharWrapping
maximumNumberOfLines:0
exclusionPaths:nil
constrainedSize:constrainedSize
layoutManagerFactory:nil];
XCTAssertNoThrow([[ASTextKitTailTruncater alloc] initWithContext:context
truncationAttributedString:[self _simpleTruncationAttributedString]
avoidTailTruncationSet:[NSCharacterSet characterSetWithCharactersInString:@"."]
constrainedSize:constrainedSize]);
}
@end

View File

@@ -1,178 +0,0 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "ASTextNodeRenderer.h"
@interface ASTextNodeRendererTests : XCTestCase
@property (nonatomic, readwrite, strong) ASTextNodeRenderer *renderer;
@property (nonatomic, copy, readwrite) NSAttributedString *attributedString;
@property (nonatomic, copy, readwrite) NSAttributedString *truncationString;
@property (nonatomic, readwrite, assign) NSLineBreakMode truncationMode;
@property (nonatomic, readwrite, assign) NSUInteger maximumLineCount;
@property (nonatomic, readwrite, assign) CGFloat lineSpacing;
@property (nonatomic, readwrite, assign) CGSize constrainedSize;
@property (nonatomic, readwrite) NSArray *exclusionPaths;
@end
@implementation ASTextNodeRendererTests
- (void)setUp
{
[super setUp];
_truncationMode = NSLineBreakByWordWrapping;
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
_lineSpacing = 14.0;
paragraphStyle.lineSpacing = _lineSpacing;
paragraphStyle.maximumLineHeight = _lineSpacing;
paragraphStyle.minimumLineHeight = _lineSpacing;
NSDictionary *attributes = @{ NSFontAttributeName : [UIFont systemFontOfSize:12.0],
NSParagraphStyleAttributeName : paragraphStyle };
_attributedString = [[NSAttributedString alloc] initWithString:@"Lorem ipsum" attributes:attributes];
_truncationString = [[NSAttributedString alloc] initWithString:@"More"];
_exclusionPaths = nil;
_constrainedSize = CGSizeMake(FLT_MAX, FLT_MAX);
}
- (void)setUpRenderer
{
_renderer = [[ASTextNodeRenderer alloc] initWithAttributedString:_attributedString
truncationString:_truncationString
truncationMode:_truncationMode
maximumLineCount:_maximumLineCount
exclusionPaths:_exclusionPaths
constrainedSize:_constrainedSize];
}
- (void)testCalculateSize
{
[self setUpRenderer];
CGSize size = [_renderer size];
XCTAssertTrue(size.width > 0, @"Should have a nonzero width");
XCTAssertTrue(size.height > 0, @"Should have a nonzero height");
}
- (void)testCalculateSizeWithEmptyString
{
_attributedString = [[NSAttributedString alloc] initWithString:@""];
[self setUpRenderer];
CGSize size = [_renderer size];
XCTAssertTrue(CGSizeEqualToSize(CGSizeZero, size), @"Empty NSAttributedString should result in CGSizeZero");
}
- (void)testCalculateSizeWithNilString
{
_attributedString = nil;
[self setUpRenderer];
CGSize size = [_renderer size];
XCTAssertTrue(CGSizeEqualToSize(CGSizeZero, size), @"Nil NSAttributedString should result in CGSizeZero");
}
- (void)testNumberOfLines
{
[self setUpRenderer];
CGSize size = [_renderer size];
NSInteger numberOfLines = size.height / _lineSpacing;
XCTAssertTrue(numberOfLines == 1 , @"If constrained height (%f) is float max, then there should only be one line of text. Size %@", _constrainedSize.width, NSStringFromCGSize(size));
}
- (void)testMaximumLineCount
{
NSArray *lines = [NSArray arrayWithObjects:@"Hello!", @"world!", @"foo", @"bar", @"baz", nil];
_maximumLineCount = 2;
for (int i = 0; i <= [lines count]; i++) {
NSString *line = [[lines subarrayWithRange:NSMakeRange(0, i)] componentsJoinedByString:@"\n"];
_attributedString = [[NSAttributedString alloc] initWithString:line];
[self setUpRenderer];
[_renderer size];
XCTAssertTrue(_renderer.lineCount <= _maximumLineCount, @"The line count %tu after rendering should be no larger than the maximum line count %tu", _renderer.lineCount, _maximumLineCount);
}
}
- (void)testNoTruncationIfEnoughSpace
{
[self setUpRenderer];
[_renderer size];
NSRange stringRange = NSMakeRange(0, _attributedString.length);
NSRange visibleRange = [_renderer visibleRange];
XCTAssertTrue(NSEqualRanges(stringRange, visibleRange), @"There should be no truncation if the text has plenty of space to lay out");
XCTAssertTrue(NSEqualRanges([_renderer truncationStringCharacterRange], NSMakeRange(NSNotFound, _truncationString.length)), @"There should be no range for the truncation string if no truncation is occurring");
}
- (void)testTruncation
{
[self setUpRenderer];
CGSize calculatedSize = [_renderer size];
// Make the constrained size just a *little* too small
_constrainedSize = CGSizeMake(calculatedSize.width - 2, calculatedSize.height);
_renderer = nil;
[self setUpRenderer];
[_renderer size];
NSRange stringRange = NSMakeRange(0, _attributedString.length);
NSRange visibleRange = [_renderer visibleRange];
XCTAssertTrue(visibleRange.length < stringRange.length, @"Some truncation should occur if the constrained size is smaller than the previously calculated bounding size. String length %tu, visible range %@", _attributedString.length, NSStringFromRange(visibleRange));
NSRange truncationRange = [_renderer truncationStringCharacterRange];
XCTAssertTrue(truncationRange.location == NSMaxRange(visibleRange), @"Truncation location (%zd) should be after the end of the visible range (%zd)", truncationRange.location, NSMaxRange(visibleRange));
XCTAssertTrue(truncationRange.length == _truncationString.length, @"Truncation string length (%zd) should be the full length of the supplied truncation string (%@)", truncationRange.length, _truncationString.string);
}
/**
* We don't want to decrease the total number of lines, i.e. truncate too aggressively,
* But we also don't want to add extra lines just to display our truncation message
*/
- (void)testTruncationConservesOriginalHeight
{
[self setUpRenderer];
CGSize calculatedSize = [_renderer size];
// Make the constrained size just a *little* too small
_constrainedSize = CGSizeMake(calculatedSize.width - 1, calculatedSize.height);
[self setUpRenderer];
CGSize calculatedSizeWithTruncation = [_renderer size];
// Floating point equality
XCTAssertTrue(fabs(calculatedSizeWithTruncation.height - calculatedSize.height) < .001, @"The height after truncation (%f) doesn't match the normal calculated height (%f)", calculatedSizeWithTruncation.height, calculatedSize.height);
}
- (void)testNoCrashOnTappingEmptyTextNode
{
_attributedString = [[NSAttributedString alloc] initWithString:@""];
[self setUpRenderer];
[_renderer size];
[_renderer enumerateTextIndexesAtPosition:CGPointZero usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) {
XCTFail(@"Shouldn't be any text indexes to enumerate");
}];
}
- (void)testExclusionPaths
{
_constrainedSize = CGSizeMake(200, CGFLOAT_MAX);
[self setUpRenderer];
CGSize sizeWithoutExclusionPath = [_renderer size];
CGRect exclusionRect = CGRectMake(20, 0, 180, _lineSpacing * 2.0);
_exclusionPaths = @[[UIBezierPath bezierPathWithRect:exclusionRect]];
[self setUpRenderer];
CGSize sizeWithExclusionPath = [_renderer size];
XCTAssertEqualWithAccuracy(sizeWithoutExclusionPath.height + exclusionRect.size.height, sizeWithExclusionPath.height, 0.5, @"Using an exclusion path so the the text can not fit into the first two lines should increment the size of the text by the heigth of the exclusion path");
}
@end

View File

@@ -1,156 +0,0 @@
/* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <XCTest/XCTest.h>
#import "ASTextNodeShadower.h"
@interface ASTextNodeShadowerTests : XCTestCase
@property (nonatomic, readwrite, strong) ASTextNodeShadower *shadower;
@end
@implementation ASTextNodeShadowerTests
- (void)testInstantiation
{
CGSize shadowOffset = CGSizeMake(3, 5);
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
CGFloat shadowOpacity = 0.3;
CGFloat shadowRadius = 4.2;
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
shadowColor:shadowColor
shadowOpacity:shadowOpacity
shadowRadius:shadowRadius];
XCTAssertNotNil(_shadower, @"Couldn't instantiate shadow drawer");
XCTAssertTrue(CGSizeEqualToSize(_shadower.shadowOffset, shadowOffset), @"Failed to set shadowOffset (%@) to %@", NSStringFromCGSize(_shadower.shadowOffset), NSStringFromCGSize(shadowOffset));
XCTAssertTrue(_shadower.shadowColor == shadowColor, @"Failed to set shadowColor (%@) to %@", _shadower.shadowColor, shadowColor);
XCTAssertTrue(_shadower.shadowOpacity == shadowOpacity, @"Failed to set shadowOpacity (%f) to %f", _shadower.shadowOpacity, shadowOpacity);
XCTAssertTrue(_shadower.shadowRadius == shadowRadius, @"Failed to set shadowRadius (%f) to %f", _shadower.shadowRadius, shadowRadius);
CGColorRelease(shadowColor);
}
- (void)testNoShadowIfNoRadiusAndNoOffset
{
CGSize shadowOffset = CGSizeZero;
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
CGFloat shadowOpacity = 0.3;
CGFloat shadowRadius = 0;
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
shadowColor:shadowColor
shadowOpacity:shadowOpacity
shadowRadius:shadowRadius];
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, UIEdgeInsetsZero), @"There should be no shadow padding if shadow radius is zero");
CGColorRelease(shadowColor);
}
- (void)testShadowIfOffsetButNoRadius
{
CGSize shadowOffset = CGSizeMake(3, 5);
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
CGFloat shadowOpacity = 0.3;
CGFloat shadowRadius = 0;
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
shadowColor:shadowColor
shadowOpacity:shadowOpacity
shadowRadius:shadowRadius];
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
UIEdgeInsets expectedInsets = UIEdgeInsetsMake(0, 0, -5, -3);
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, expectedInsets), @"Expected insets %@, encountered insets %@", NSStringFromUIEdgeInsets(expectedInsets), NSStringFromUIEdgeInsets(shadowPadding));
CGColorRelease(shadowColor);
}
- (void)testNoShadowIfNoOpacity
{
CGSize shadowOffset = CGSizeMake(3, 5);
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
CGFloat shadowOpacity = 0;
CGFloat shadowRadius = 4;
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
shadowColor:shadowColor
shadowOpacity:shadowOpacity
shadowRadius:shadowRadius];
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, UIEdgeInsetsZero), @"There should be no shadow padding if shadow opacity is zero");
CGColorRelease(shadowColor);
}
- (void)testShadowPaddingForRadiusOf4
{
CGSize shadowOffset = CGSizeZero;
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
CGFloat shadowOpacity = 1;
CGFloat shadowRadius = 4;
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
shadowColor:shadowColor
shadowOpacity:shadowOpacity
shadowRadius:shadowRadius];
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
UIEdgeInsets expectedInsets = UIEdgeInsetsMake(-shadowRadius, -shadowRadius, -shadowRadius, -shadowRadius);
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, expectedInsets), @"Unexpected edge insets %@ for radius of %f ", NSStringFromUIEdgeInsets(shadowPadding), shadowRadius);
CGColorRelease(shadowColor);
}
- (void)testShadowPaddingForRadiusOf4OffsetOf11
{
CGSize shadowOffset = CGSizeMake(1, 1);
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
CGFloat shadowOpacity = 1;
CGFloat shadowRadius = 4;
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
shadowColor:shadowColor
shadowOpacity:shadowOpacity
shadowRadius:shadowRadius];
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
UIEdgeInsets expectedInsets = UIEdgeInsetsMake(-shadowRadius + shadowOffset.height, // Top: -3
-shadowRadius + shadowOffset.width, // Left: -3
-shadowRadius - shadowOffset.height, // Bottom: -5
-shadowRadius - shadowOffset.width); // Right: -5
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, expectedInsets), @"Unexpected edge insets %@ for radius of %f ", NSStringFromUIEdgeInsets(shadowPadding), shadowRadius);
CGColorRelease(shadowColor);
}
- (void)testShadowPaddingForRadiusOf4OffsetOfNegative11
{
CGSize shadowOffset = CGSizeMake(-1, -1);
CGColorRef shadowColor = CGColorRetain([UIColor blackColor].CGColor);
CGFloat shadowOpacity = 1;
CGFloat shadowRadius = 4;
_shadower = [[ASTextNodeShadower alloc] initWithShadowOffset:shadowOffset
shadowColor:shadowColor
shadowOpacity:shadowOpacity
shadowRadius:shadowRadius];
UIEdgeInsets shadowPadding = [_shadower shadowPadding];
UIEdgeInsets expectedInsets = UIEdgeInsetsMake(-shadowRadius + shadowOffset.height, // Top: -3
-shadowRadius + shadowOffset.width, // Left: -5
-shadowRadius - shadowOffset.height, // Bottom: -5
-shadowRadius - shadowOffset.width); // Right: -3
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(shadowPadding, expectedInsets), @"Unexpected edge insets %@ for radius of %f ", NSStringFromUIEdgeInsets(shadowPadding), shadowRadius);
CGColorRelease(shadowColor);
}
- (void)testASDNEdgeInsetsInvert
{
UIEdgeInsets insets = UIEdgeInsetsMake(-5, -7, -3, -2);
UIEdgeInsets invertedInsets = ASDNEdgeInsetsInvert(insets);
UIEdgeInsets expectedInsets = UIEdgeInsetsMake(5, 7, 3, 2);
XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(invertedInsets, expectedInsets), @"Expected %@, actual result %@", NSStringFromUIEdgeInsets(expectedInsets), NSStringFromUIEdgeInsets(invertedInsets));
}
- (void)testASDNEdgeInsetsInvertDoubleNegation
{
CGRect originalRect = CGRectMake(31, 32, 33, 34);
UIEdgeInsets insets = UIEdgeInsetsMake(-5, -7, -3, -2);
CGRect insettedRect = UIEdgeInsetsInsetRect(originalRect, insets);
CGRect outsettedInsettedRect = UIEdgeInsetsInsetRect(insettedRect, ASDNEdgeInsetsInvert(insets));
XCTAssertTrue(CGRectEqualToRect(originalRect, outsettedInsettedRect), @"Insetting a CGRect, and then outsetting it (insetting with the negated edge insets) should return the original CGRect");
}
@end

View File

@@ -14,12 +14,16 @@
#import <XCTest/XCTest.h>
static BOOL CGSizeEqualToSizeWithIn(CGSize size1, CGSize size2, CGFloat delta)
{
return fabs(size1.width - size2.width) < delta && fabs(size1.height - size2.height) < delta;
}
@interface ASTextNodeTestDelegate : NSObject <ASTextNodeDelegate>
@property (nonatomic, copy, readonly) NSString *tappedLinkAttribute;
@property (nonatomic, assign, readonly) id tappedLinkValue;
@end
@implementation ASTextNodeTestDelegate
@@ -104,7 +108,7 @@
CGSize calculatedSize = [_textNode measure:constrainedSize];
CGSize recalculatedSize = [_textNode measure:calculatedSize];
XCTAssertTrue(CGSizeEqualToSize(calculatedSize, recalculatedSize), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 4.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
}
}
@@ -115,7 +119,7 @@
CGSize calculatedSize = [_textNode measure:constrainedSize];
CGSize recalculatedSize = [_textNode measure:calculatedSize];
XCTAssertTrue(CGSizeEqualToSize(calculatedSize, recalculatedSize), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
XCTAssertTrue(CGSizeEqualToSizeWithIn(calculatedSize, recalculatedSize, 11.0), @"Recalculated size %@ should be same as original size %@", NSStringFromCGSize(recalculatedSize), NSStringFromCGSize(calculatedSize));
}
}

View File

@@ -8,7 +8,7 @@
#import <XCTest/XCTest.h>
#import "ASTextNodeTextKitHelpers.h"
#import "ASTextKitHelpers.h"
#import "ASTextNodeTypes.h"
#import "ASTextNodeWordKerner.h"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -83,4 +83,4 @@ to implement node hierarchies or custom drawing.
* Read the [Getting Started guide]({{ site.baseurl }}/guide)
* Get the [sample projects](https://github.com/facebook/AsyncDisplayKit/tree/master/examples)
* Browse the [API reference]({{ site.baseurl }}/appledoc)
* Watch the [NSLondon talk](http://vimeo.com/103589245)
* Watch the [NSLondon talk](http://vimeo.com/103589245) or the [NSSpain talk](https://www.youtube.com/watch?v=RY_X7l1g79Q)

View File

@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25FDEC911BF31EE700CEB123 /* ItemNode.m */; };
9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */; };
9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; };
AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; };
@@ -17,6 +18,8 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
25FDEC901BF31EE700CEB123 /* ItemNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemNode.h; sourceTree = "<group>"; };
25FDEC911BF31EE700CEB123 /* ItemNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemNode.m; sourceTree = "<group>"; };
2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; };
9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SupplementaryNode.h; sourceTree = "<group>"; };
9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SupplementaryNode.m; sourceTree = "<group>"; };
@@ -83,6 +86,8 @@
AC3C4A611A11F47200143C57 /* Supporting Files */,
9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */,
9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */,
25FDEC901BF31EE700CEB123 /* ItemNode.h */,
25FDEC911BF31EE700CEB123 /* ItemNode.m */,
);
indentWidth = 2;
path = Sample;
@@ -228,6 +233,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */,
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */,
9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */,
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */,

View File

@@ -0,0 +1,18 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface ItemNode : ASTextCellNode
- (instancetype)initWithString:(NSString *)string;
@end

View File

@@ -0,0 +1,49 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import "ItemNode.h"
@implementation ItemNode
- (instancetype)initWithString:(NSString *)string
{
self = [super init];
if (self != nil) {
self.text = string;
[self updateBackgroundColor];
}
return self;
}
- (void)updateBackgroundColor
{
if (self.highlighted) {
self.backgroundColor = [UIColor grayColor];
} else if (self.selected) {
self.backgroundColor = [UIColor darkGrayColor];
} else {
self.backgroundColor = [UIColor lightGrayColor];
}
}
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
[self updateBackgroundColor];
}
- (void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
[self updateBackgroundColor];
}
@end

View File

@@ -13,6 +13,7 @@
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "SupplementaryNode.h"
#import "ItemNode.h"
@interface ViewController () <ASCollectionViewDataSource, ASCollectionViewDelegateFlowLayout>
{
@@ -78,11 +79,7 @@
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text = [NSString stringWithFormat:@"[%zd.%zd] says hi", indexPath.section, indexPath.item];
ASTextCellNode *node = [[ASTextCellNode alloc] init];
node.text = text;
node.backgroundColor = [UIColor lightGrayColor];
return node;
return [[ItemNode alloc] initWithString:text];
}
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath

View File

@@ -0,0 +1,3 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
pod 'AsyncDisplayKit', :path => '../..'

View File

@@ -0,0 +1,381 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */; };
25A1FA881C02FCB000193875 /* ImageCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25A1FA871C02FCB000193875 /* ImageCellNode.m */; };
9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */; };
9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; };
AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; };
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; };
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; };
AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; };
FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F02BAF78E68BC56FD8C161B7 /* libPods.a */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
25A1FA831C02F7AC00193875 /* MosaicCollectionViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MosaicCollectionViewLayout.h; sourceTree = "<group>"; };
25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MosaicCollectionViewLayout.m; sourceTree = "<group>"; };
25A1FA861C02FCB000193875 /* ImageCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageCellNode.h; sourceTree = "<group>"; };
25A1FA871C02FCB000193875 /* ImageCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageCellNode.m; sourceTree = "<group>"; };
2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; };
9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SupplementaryNode.h; sourceTree = "<group>"; };
9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SupplementaryNode.m; sourceTree = "<group>"; };
9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Launchboard.storyboard; sourceTree = "<group>"; };
AC3C4A5E1A11F47200143C57 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
AC3C4A621A11F47200143C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AC3C4A631A11F47200143C57 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
AC3C4A651A11F47200143C57 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
AC3C4A661A11F47200143C57 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
AC3C4A681A11F47200143C57 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
AC3C4A691A11F47200143C57 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; };
F02BAF78E68BC56FD8C161B7 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
AC3C4A5B1A11F47200143C57 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
90A2B9C5397C46134C8A793B /* Pods */ = {
isa = PBXGroup;
children = (
2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */,
CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
AC3C4A551A11F47200143C57 = {
isa = PBXGroup;
children = (
AC3C4A601A11F47200143C57 /* Sample */,
AC3C4A5F1A11F47200143C57 /* Products */,
90A2B9C5397C46134C8A793B /* Pods */,
D6E38FF0CB18E3F55CF06437 /* Frameworks */,
);
sourceTree = "<group>";
};
AC3C4A5F1A11F47200143C57 /* Products */ = {
isa = PBXGroup;
children = (
AC3C4A5E1A11F47200143C57 /* Sample.app */,
);
name = Products;
sourceTree = "<group>";
};
AC3C4A601A11F47200143C57 /* Sample */ = {
isa = PBXGroup;
children = (
25A1FA831C02F7AC00193875 /* MosaicCollectionViewLayout.h */,
25A1FA841C02F7AC00193875 /* MosaicCollectionViewLayout.m */,
AC3C4A651A11F47200143C57 /* AppDelegate.h */,
AC3C4A661A11F47200143C57 /* AppDelegate.m */,
AC3C4A681A11F47200143C57 /* ViewController.h */,
AC3C4A691A11F47200143C57 /* ViewController.m */,
25A1FA861C02FCB000193875 /* ImageCellNode.h */,
25A1FA871C02FCB000193875 /* ImageCellNode.m */,
AC3C4A8D1A11F80C00143C57 /* Images.xcassets */,
AC3C4A611A11F47200143C57 /* Supporting Files */,
9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */,
9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */,
);
indentWidth = 2;
path = Sample;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
AC3C4A611A11F47200143C57 /* Supporting Files */ = {
isa = PBXGroup;
children = (
AC3C4A621A11F47200143C57 /* Info.plist */,
AC3C4A631A11F47200143C57 /* main.m */,
9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
D6E38FF0CB18E3F55CF06437 /* Frameworks */ = {
isa = PBXGroup;
children = (
F02BAF78E68BC56FD8C161B7 /* libPods.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
AC3C4A5D1A11F47200143C57 /* Sample */ = {
isa = PBXNativeTarget;
buildConfigurationList = AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */;
buildPhases = (
F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */,
AC3C4A5A1A11F47200143C57 /* Sources */,
AC3C4A5B1A11F47200143C57 /* Frameworks */,
AC3C4A5C1A11F47200143C57 /* Resources */,
A6902C454C7661D0D277AC62 /* Copy Pods Resources */,
EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Sample;
productName = Sample;
productReference = AC3C4A5E1A11F47200143C57 /* Sample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
AC3C4A561A11F47200143C57 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
AC3C4A5D1A11F47200143C57 = {
CreatedOnToolsVersion = 6.1;
};
};
};
buildConfigurationList = AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = AC3C4A551A11F47200143C57;
productRefGroup = AC3C4A5F1A11F47200143C57 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
AC3C4A5D1A11F47200143C57 /* Sample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
AC3C4A5C1A11F47200143C57 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */,
AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
A6902C454C7661D0D277AC62 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
AC3C4A5A1A11F47200143C57 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
25A1FA851C02F7AC00193875 /* MosaicCollectionViewLayout.m in Sources */,
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */,
9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */,
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */,
AC3C4A641A11F47200143C57 /* main.m in Sources */,
25A1FA881C02FCB000193875 /* ImageCellNode.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
AC3C4A7F1A11F47200143C57 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
AC3C4A801A11F47200143C57 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
AC3C4A821A11F47200143C57 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
AC3C4A831A11F47200143C57 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AC3C4A7F1A11F47200143C57 /* Debug */,
AC3C4A801A11F47200143C57 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
AC3C4A821A11F47200143C57 /* Debug */,
AC3C4A831A11F47200143C57 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = AC3C4A561A11F47200143C57 /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Sample.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AC3C4A5D1A11F47200143C57"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AC3C4A5D1A11F47200143C57"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</MacroExpansion>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AC3C4A5D1A11F47200143C57"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "AC3C4A5D1A11F47200143C57"
BuildableName = "Sample.app"
BlueprintName = "Sample"
ReferencedContainer = "container:Sample.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Sample.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,18 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

View File

@@ -0,0 +1,29 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import "AppDelegate.h"
#import "ViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = [[ViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
@end

View File

@@ -0,0 +1,15 @@
//
// ImageCellNode.h
// Sample
//
// Created by McCallum, Levi on 11/22/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface ImageCellNode : ASCellNode
- (instancetype)initWithImage:(UIImage *)image;
@end

View File

@@ -0,0 +1,37 @@
//
// ImageCellNode.m
// Sample
//
// Created by McCallum, Levi on 11/22/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import "ImageCellNode.h"
@implementation ImageCellNode {
ASImageNode *_imageNode;
}
- (id)initWithImage:(UIImage *)image
{
self = [super init];
if (self != nil) {
_imageNode = [[ASImageNode alloc] init];
_imageNode.image = image;
[self addSubnode:_imageNode];
}
return self;
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
[_imageNode measure:constrainedSize];
return constrainedSize;
}
- (void)layout
{
_imageNode.frame = CGRectMake(0, 0, _imageNode.calculatedSize.width, _imageNode.calculatedSize.height);
}
@end

View File

@@ -0,0 +1,39 @@
{
"images" : [
{
"orientation" : "portrait",
"idiom" : "iphone",
"filename" : "Default-568h@2x.png",
"minimum-system-version" : "7.0",
"subtype" : "retina4",
"scale" : "2x"
},
{
"idiom" : "iphone",
"scale" : "1x",
"orientation" : "portrait"
},
{
"idiom" : "iphone",
"scale" : "2x",
"orientation" : "portrait"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"filename" : "Default-568h@2x.png",
"subtype" : "retina4",
"scale" : "2x"
},
{
"orientation" : "portrait",
"idiom" : "iphone",
"minimum-system-version" : "7.0",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "image_0.jpg"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Some files were not shown because too many files have changed in this diff Show More