diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79090ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# OS X +.DS_Store + +# Xcode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ +*.xccheckout +profile +*.moved-aside +DerivedData +*.hmap +*.ipa + +# Bundler +.bundle + +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control +# +# Note: if you ignore the Pods directory, make sure to uncomment +# `pod install` in .travis.yml +# +# Pods/ + +# Ignore the folder with failed screenshots +Example/Tests/FailureDiffs* diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..57cf282 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.6.5 diff --git a/Example/Podfile b/Example/Podfile new file mode 100644 index 0000000..97b95a2 --- /dev/null +++ b/Example/Podfile @@ -0,0 +1,15 @@ +use_frameworks! +platform :ios, '10.0' + +target 'StylableUIKit_Example' do + pod 'StylableUIKit/Core', :path => '../' + pod 'StylableUIKit/Lottie', :path => '../' + + pod 'SwiftLint' + + target 'StylableUIKit_Tests' do + inherit! :search_paths + + pod 'iOSSnapshotTestCase', '~> 6.0' + end +end diff --git a/Example/Podfile.lock b/Example/Podfile.lock new file mode 100644 index 0000000..6902561 --- /dev/null +++ b/Example/Podfile.lock @@ -0,0 +1,38 @@ +PODS: + - iOSSnapshotTestCase (6.2.0): + - iOSSnapshotTestCase/SwiftSupport (= 6.2.0) + - iOSSnapshotTestCase/Core (6.2.0) + - iOSSnapshotTestCase/SwiftSupport (6.2.0): + - iOSSnapshotTestCase/Core + - lottie-ios (3.1.6) + - StylableUIKit/Core (3.1.0) + - StylableUIKit/Lottie (3.1.0): + - lottie-ios (~> 3.1) + - StylableUIKit/Core + - SwiftLint (0.37.0) + +DEPENDENCIES: + - iOSSnapshotTestCase (~> 6.0) + - StylableUIKit/Core (from `../`) + - StylableUIKit/Lottie (from `../`) + - SwiftLint + +SPEC REPOS: + trunk: + - iOSSnapshotTestCase + - lottie-ios + - SwiftLint + +EXTERNAL SOURCES: + StylableUIKit: + :path: "../" + +SPEC CHECKSUMS: + iOSSnapshotTestCase: 9ab44cb5aa62b84d31847f40680112e15ec579a6 + lottie-ios: 85ce835dd8c53e02509f20729fc7d6a4e6645a0a + StylableUIKit: b753c35ecd2366fe8b1d981a50c3ff84cece4637 + SwiftLint: c078a14d7d7ade75e5507795d185e3da41d844d2 + +PODFILE CHECKSUM: 9fe40686bdaa77e966122dbae4ca1cde783185d6 + +COCOAPODS: 1.8.4 diff --git a/Example/Pods/Local Podspecs/StylableUIKit.podspec.json b/Example/Pods/Local Podspecs/StylableUIKit.podspec.json new file mode 100644 index 0000000..8e137a9 --- /dev/null +++ b/Example/Pods/Local Podspecs/StylableUIKit.podspec.json @@ -0,0 +1,58 @@ +{ + "name": "StylableUIKit", + "version": "3.1.0", + "summary": "A protocol for applying design to iOS apps following Atomic Design Theory", + "description": "A library which allows iOS apps to be skinned / themed following Atomic Design Theory", + "homepage": "https://github.com/design-ops/stylable-uikit", + "license": { + "type": "Apache License, Version 2.0", + "file": "LICENSE" + }, + "authors": [ + "atomoil", + "deanWomborne", + "kerrmarin", + "jdbarbosa", + "cristianoalves92" + ], + "platforms": { + "ios": "10.0" + }, + "source": { + "git": "git@github.com:design-ops/stylable-uikit.git", + "tag": "v3.1.0" + }, + "frameworks": [ + "Foundation", + "UIKit" + ], + "swift_versions": "5.0", + "default_subspecs": "Core", + "subspecs": [ + { + "name": "Core", + "source_files": [ + "StylableUIKit/", + "StylableUIKit/Classes/**/*.swift" + ], + "exclude_files": [ + "StylableUIKit/Classes/Exclude", + "StylableUIKit/Classes/Subspec" + ] + }, + { + "name": "Lottie", + "dependencies": { + "lottie-ios": [ + "~> 3.1" + ], + "StylableUIKit/Core": [ + + ] + }, + "source_files": "StylableUIKit/Classes/Subspec/Lottie", + "compiler_flags": "-DFABRIC_STYLIST_SUPPORTS_LOTTIE" + } + ], + "swift_version": "5.0" +} diff --git a/Example/Pods/Manifest.lock b/Example/Pods/Manifest.lock new file mode 100644 index 0000000..6902561 --- /dev/null +++ b/Example/Pods/Manifest.lock @@ -0,0 +1,38 @@ +PODS: + - iOSSnapshotTestCase (6.2.0): + - iOSSnapshotTestCase/SwiftSupport (= 6.2.0) + - iOSSnapshotTestCase/Core (6.2.0) + - iOSSnapshotTestCase/SwiftSupport (6.2.0): + - iOSSnapshotTestCase/Core + - lottie-ios (3.1.6) + - StylableUIKit/Core (3.1.0) + - StylableUIKit/Lottie (3.1.0): + - lottie-ios (~> 3.1) + - StylableUIKit/Core + - SwiftLint (0.37.0) + +DEPENDENCIES: + - iOSSnapshotTestCase (~> 6.0) + - StylableUIKit/Core (from `../`) + - StylableUIKit/Lottie (from `../`) + - SwiftLint + +SPEC REPOS: + trunk: + - iOSSnapshotTestCase + - lottie-ios + - SwiftLint + +EXTERNAL SOURCES: + StylableUIKit: + :path: "../" + +SPEC CHECKSUMS: + iOSSnapshotTestCase: 9ab44cb5aa62b84d31847f40680112e15ec579a6 + lottie-ios: 85ce835dd8c53e02509f20729fc7d6a4e6645a0a + StylableUIKit: b753c35ecd2366fe8b1d981a50c3ff84cece4637 + SwiftLint: c078a14d7d7ade75e5507795d185e3da41d844d2 + +PODFILE CHECKSUM: 9fe40686bdaa77e966122dbae4ca1cde783185d6 + +COCOAPODS: 1.8.4 diff --git a/Example/Pods/Pods.xcodeproj/project.pbxproj b/Example/Pods/Pods.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e25e5a0 --- /dev/null +++ b/Example/Pods/Pods.xcodeproj/project.pbxproj @@ -0,0 +1,1997 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXAggregateTarget section */ + 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */ = { + isa = PBXAggregateTarget; + buildConfigurationList = AE7B4FB01588B9E6DF09CB79FC7CE7BD /* Build configuration list for PBXAggregateTarget "SwiftLint" */; + buildPhases = ( + ); + dependencies = ( + ); + name = SwiftLint; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 021AB92728510E298CC54FF5E7AC371C /* RestrictedLayerStylable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2AF4F28E5D8D7122AD290D066D2185 /* RestrictedLayerStylable.swift */; }; + 02F02960A3A126D25ACE1E111A6DD615 /* Mask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A3424A129DCE4AD48D0A80B90B9E7A8 /* Mask.swift */; }; + 05195E7FF4C60B1F0459D1320AE7F87C /* BundleImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59DB72A6392CF91A3946729B5ECA7FEC /* BundleImageProvider.swift */; }; + 0527DC882E09A33967F98A0EEFE25DD4 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA086F7539AC4E5F02932BFEFE999439 /* ColorExtension.swift */; }; + 052927C8BDC535147D14CEB79D9DAE1D /* Pods-StylableUIKit_Tests-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 666C1E81F957B007A1BDF46F2EC925B6 /* Pods-StylableUIKit_Tests-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 06DD3917D477FB37B67F0B214FED18C4 /* CurveVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5DEB7DB872F1359172AD078498B9848 /* CurveVertex.swift */; }; + 0715914E00A466DC249E0ADD1EF4A7B3 /* PreCompLayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9710FDAC9E77F5E9D1696B02279A3515 /* PreCompLayerModel.swift */; }; + 073E2C39FBBF31EFA269308E238958BC /* Pods-StylableUIKit_Tests-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = D5AB7868209B2165B93BA05999D6ECFE /* Pods-StylableUIKit_Tests-dummy.m */; }; + 07A557663FA65416FE893CC362EAAA79 /* TextTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 488609954ABCCD4E70F84384147D452A /* TextTransform.swift */; }; + 07C5109C39B3C2EF140324D52F6EAD0E /* StrokeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23ECA3097E04D6FEE2EA9DF2DE50073D /* StrokeNode.swift */; }; + 08449447FF2F8862406237FF9F0E2EA0 /* Repeater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E4059B25E5E09B1D555F126D162516 /* Repeater.swift */; }; + 085EBC88244CFA42A128190C67AEE52A /* AnimationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7859A70AD80F5EE84F8A1E09F775170 /* AnimationContext.swift */; }; + 0904AA51109A23AF7E16612B35FF1001 /* AnyNodeProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5923C4470A5F464DAE4DCF4C46D233 /* AnyNodeProperty.swift */; }; + 0909768F839A1CB30B6766AEF3B25847 /* PointValueProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47BF1972756382AA4E72A9013CE4875 /* PointValueProvider.swift */; }; + 09F3021D3323D0B64812EE10886827AB /* UIImage+Diff.h in Headers */ = {isa = PBXBuildFile; fileRef = 32EA0C94258313A5F8DF98FA1FB33387 /* UIImage+Diff.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 0C625A3F10C94671FB94C8971211A393 /* FloatValueProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92293CB65933FEA609B06A47BFBFF47D /* FloatValueProvider.swift */; }; + 0D25866550309DA89CF18F232746CB15 /* LayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE5FAEF44E1F75BA4EC6BBD7AA7A8CE9 /* LayerModel.swift */; }; + 0D4A923376019329CE9FD1ED19C98E76 /* NodePropertyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA53E107FD0D2CD5C8EBBCDB59078FA3 /* NodePropertyMap.swift */; }; + 10042BC1EFD6FB13EAC2499381F0FD66 /* AnimationSubview.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD07EAB31953CEBB71C40E2BA64EF2C6 /* AnimationSubview.swift */; }; + 115B9F2A343D1880D138F41B9491C125 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3357625801F09413816ACD60A5F1A64 /* Foundation.framework */; }; + 122F61784A56796A2CD7B2CD201C5525 /* FBSnapshotTestController.m in Sources */ = {isa = PBXBuildFile; fileRef = FB260B2CFB4D44136DAFDEE8896FD4D7 /* FBSnapshotTestController.m */; }; + 124AC9CF9318BA43602339B0E6BBA1DF /* GradientStrokeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B153F71EC21C615DCEEC792DA193874 /* GradientStrokeRenderer.swift */; }; + 135B519B888DBB4F193D4EFF97675942 /* Trim.swift in Sources */ = {isa = PBXBuildFile; fileRef = D761830421837FFE0112C277FEE013E0 /* Trim.swift */; }; + 167D7AADECFABC8D83FA56D1AAD8DFCD /* MathKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B112EE14E786D76E7E681855F45BA3E2 /* MathKit.swift */; }; + 1A1E93B6CF3867EE7DC553FE20A7B917 /* DottedLayerOutline.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8BD21673FFC8B94B75666E89828AE49 /* DottedLayerOutline.swift */; }; + 1A943B62A9FD1C4040E404C0433CC0C7 /* AnimatorNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FF05A76BC973E3DECDC6FDC2FDFEA3 /* AnimatorNode.swift */; }; + 1B4D9FF51D9423A86A233BD39023D57D /* ShapeTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B25A197D8A52F7AC9644A0B438F3E20 /* ShapeTransform.swift */; }; + 1BDB78C584D5A90ADB1E271DE0AAE611 /* StarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F2C129964B166867AC57FA824080634 /* StarNode.swift */; }; + 1C5FCE5F245C5C64E3AC16703F625C7E /* Stylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 946099FE4096FE33731A06630DAC5658 /* Stylist.swift */; }; + 1F052FA064BB601F0C2B7BFC580CAB58 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3357625801F09413816ACD60A5F1A64 /* Foundation.framework */; }; + 1F16C28936A130072E2A7068BF776B52 /* StylableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 198CA2BF65D1C3825C97C094CBE60C3E /* StylableButton.swift */; }; + 200C96F5C1D72F89B06FEEC27273018B /* Merge.swift in Sources */ = {isa = PBXBuildFile; fileRef = C702734BB61421EED37FA5E403B6FB4E /* Merge.swift */; }; + 207F4D1CA99D5F6AB826F5C70AB5B625 /* FillRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E46E60021235C1C35E4C0E7F590BEE6 /* FillRenderer.swift */; }; + 211ABA7844864DA1E16599727D17B8E8 /* TextCompositionLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15B79F3C90574234CB1D9D45F9B86DF6 /* TextCompositionLayer.swift */; }; + 23C7638975B7288F5F514C0FD1830C7E /* UIImage+Snapshot.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA78CD813CD59F35615BDA1404B4D0E /* UIImage+Snapshot.m */; }; + 2427FA080A184DC70FC29692B2C2F1F8 /* Rectangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DF3A4CD2B86E861188639DCE7D905D /* Rectangle.swift */; }; + 253DC4C2214CF59DD2394B67FE5602F5 /* PrecompAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ED089043178D008E6428E59FB8B64FF /* PrecompAsset.swift */; }; + 2643963F8600F16366AF89ABF6D91F82 /* UIImage+Compare.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3247B49FFE60141DCC6E3F09E8F587 /* UIImage+Compare.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 272A60CDD69F3DEA65B406D54E7D4E8C /* GradientValueProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C379C8F9DB403AB40D5BE08B0A235BA /* GradientValueProvider.swift */; }; + 29C99C292ED4895594699F4615CB03F1 /* LayerStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FDCAFFD83DED18E6813044B733CB438 /* LayerStyle.swift */; }; + 29DEAC4B549ED276A9CDC7BE18E26440 /* StrokeRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A0272C6413D935F85D9A0605B9AFF72 /* StrokeRenderer.swift */; }; + 2B4F8C3A106618349345FFDBBFD4732E /* ShapeRenderLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505127C28A6BBF7640A38C2BCB5459AC /* ShapeRenderLayer.swift */; }; + 2C4F68C959D0529E34AFA6BFAFE42FE6 /* AnimationTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C2215C4369FA071D3AFC03B6A3571A /* AnimationTime.swift */; }; + 2F55B79797DE4F1C093EB3685ABF6FCC /* Asset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E53F0A109097AC14591182A3B6A781F /* Asset.swift */; }; + 30181F2F621BBD0A4D81B529B34EE504 /* AnimatorNodeDebugging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0134D9EC77D58FC6DAE183BC69D420B4 /* AnimatorNodeDebugging.swift */; }; + 368B06D8C82325E5547E88A18840EBCF /* AnimatedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = E73751FF2E1F6450CC96A993FE154428 /* AnimatedControl.swift */; }; + 38130C849E36F6F516FB33D693DD1CD7 /* TextAnimatorNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F166374728BA61385CE0F99FB290FED /* TextAnimatorNode.swift */; }; + 3B540469AF7772C156A3AD0CC6184734 /* FBSnapshotTestCase.h in Headers */ = {isa = PBXBuildFile; fileRef = 5066FE2E3AE6FAF3BE69D583B7A6C6F9 /* FBSnapshotTestCase.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3C5BA865C37EE7C7E74B13FE0FBDB8D9 /* PathElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3314434FB57226915CB1C2F5C36B4C79 /* PathElement.swift */; }; + 3CE95FD1D722818A524677118DF557BA /* UIImage+Snapshot.h in Headers */ = {isa = PBXBuildFile; fileRef = DF18BEBE83A4D7A09612AB19BA9A97E2 /* UIImage+Snapshot.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 3E57C81B7BAA0362BC1BCE4042415C14 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE449AA3FD1ED2D00D17C8EACE41F19B /* Font.swift */; }; + 42D78ECA8D9956F51BF7A7882A40C2B2 /* GroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE360F166EC21B3DC9AACC5FB6F400F4 /* GroupNode.swift */; }; + 4674EF4CBB4CB5C9823734C5198994FB /* Stroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7C38DB5E77415614E3DD213C71C4A3D /* Stroke.swift */; }; + 476C4EBD027F5DD6165DA8DE9C437459 /* String+Lottie.swift in Sources */ = {isa = PBXBuildFile; fileRef = F10CC13A9FC8FF8B54E28CC46A517B24 /* String+Lottie.swift */; settings = {COMPILER_FLAGS = "-DFABRIC_STYLIST_SUPPORTS_LOTTIE"; }; }; + 495B38BD12AB86618A5D1D733908920B /* DashPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29896A7720E01A21E52E4DC9AA85DBB /* DashPattern.swift */; }; + 4A9765EE896EB9C4886D44DE8F929A7E /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3594C8B9A6847185E0695C678BFA3CBA /* UIKit.framework */; }; + 4C6EEDAD2980F21361E123D4EA97FE52 /* SwiftSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DDB6F9E60DAB454A31E1AF3BA57AF96 /* SwiftSupport.swift */; }; + 4D88D1D4B6DA13E10CB05B3146C04464 /* UIImage+Diff.m in Sources */ = {isa = PBXBuildFile; fileRef = F1E9B5B40C9D55CE2EB9E3E3B3489B43 /* UIImage+Diff.m */; }; + 4DA04D007F39F55CEDE2D8851592B1D0 /* TextAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7B1A46187451C718EC7C10F13185358 /* TextAnimator.swift */; }; + 4DEFDD1659B6A1C9996799FFAAAA6391 /* FillI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFEC4B9505553AEF6E4A28B88B75A935 /* FillI.swift */; }; + 5458E427A06FB786456932A3D176BCBC /* LayerImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C557D0586B1DF168D5316404C6002A0 /* LayerImageProvider.swift */; }; + 548B4638EB0C24C603B616586BD50503 /* KeyedDecodingContainerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8829B18BC10A81B9137665BB6E8D8DF2 /* KeyedDecodingContainerExtensions.swift */; }; + 54C6B939FC97701D061C97E7A9BC7290 /* ShapeLayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76420F2B0C49DA9024D9D46579527EC2 /* ShapeLayerModel.swift */; }; + 56961600EB511A0C4AB090E89D4CB31D /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3357625801F09413816ACD60A5F1A64 /* Foundation.framework */; }; + 56E99698157F624BC79E63C876B479A6 /* LinearLayerOutline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AEE7F581515D14F487EBE22701CC423 /* LinearLayerOutline.swift */; }; + 570D2238436AE876843CF195B7CA1FE9 /* KeypathSearchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 240B1C8668288F04700A77482E4B130C /* KeypathSearchable.swift */; }; + 5A1503DCEEC67FB6DAABE2F399B0B473 /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AA8EA53D3E8A160742FE010D2006CF2 /* UIColorExtension.swift */; }; + 5C28BB9553F94CED0FA4065BBF5090CA /* NodeProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AAC5FD988A62CC995CFFDFC1C7D408 /* NodeProperty.swift */; }; + 5CFD587F8709F0968C89C8F574F542C9 /* NSAttributedString+textTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB07439868C653FACEF5A4213A0DA351 /* NSAttributedString+textTransform.swift */; }; + 5E305C261973A96B7421411F12F9F7BB /* FillNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA35FA6F1384B93259E2AE248E7670C2 /* FillNode.swift */; }; + 60B11295B839619FDBA5A02479AA53A0 /* Vectors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCC9DA88DFBEBD76B336EF3FA4D4E81 /* Vectors.swift */; }; + 612D819D2BEC1DFC7DEC6710CF0F7E7B /* ColorValueProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D471259EB7CA6E99D9214A5FC8455F /* ColorValueProvider.swift */; }; + 61ECDE04C5946A030B22457C613FDD25 /* GroupOutputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 903CAA7B22CF6C2317DA99ACB4F8B407 /* GroupOutputNode.swift */; }; + 635D998A41DC23D8DE3CBC222531A100 /* StylableUIKit-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 706D165B0ABC2FC1C23CFD348A8AD336 /* StylableUIKit-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 650BD98AA163733C6F4C8A7472225845 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3357625801F09413816ACD60A5F1A64 /* Foundation.framework */; }; + 6543CE32DD97E4F24919522FE4E79A94 /* FBSnapshotTestCasePlatform.h in Headers */ = {isa = PBXBuildFile; fileRef = C073ABA222866A71C0639369CAE1AD6B /* FBSnapshotTestCasePlatform.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 65AF7EFA2C84C6EC1FBE3A5200F37DE7 /* ItemsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AFADE56F7E421B19C6DAFDDC7243E70 /* ItemsExtension.swift */; }; + 66B18DFA74A7385B92CF408B3A6E2421 /* BezierPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 449BECFA6EE731CEB064E0E8049C0CB0 /* BezierPath.swift */; }; + 67DFAD4C9A2F7BE4CD1A5C475B3BB59F /* RenderNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49BA712C28E8BC1BBA9F078506A63D75 /* RenderNode.swift */; }; + 6A19DD39E6347AC91865C1E8202E6049 /* AnyValueContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 056E18E13B0889482A65AD1B916A32F4 /* AnyValueContainer.swift */; }; + 6B20D02E5D429A424923EC8E314576C3 /* SingleValueProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78DA25FCEC460D8CC843AFE8142C7AA /* SingleValueProvider.swift */; }; + 6B4027FDB9B152959D31DA5CF5C421B3 /* Lottie+StylistAnimatedAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F0404567BD464504B5FF43E353596F /* Lottie+StylistAnimatedAsset.swift */; settings = {COMPILER_FLAGS = "-DFABRIC_STYLIST_SUPPORTS_LOTTIE"; }; }; + 6B646E0927489EC259F20E457EA0C8CB /* KeyframeGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75511F820C463B56E94AA5BE6D304585 /* KeyframeGroup.swift */; }; + 6D6EAFAB7121B03432865FF5505519C2 /* MaskContainerLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE89BF228FAD5CEAE18DA5AD43A9D79 /* MaskContainerLayer.swift */; }; + 6ED0F83B3E5FA3B18EB93D937E17CCBF /* lottie-ios-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 1432E944BF221B70237E5690F66D1C12 /* lottie-ios-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6FE1CB5ABFB5718B314AAADD41EC7908 /* AnimationKeypath.swift in Sources */ = {isa = PBXBuildFile; fileRef = E08ACDD9085A2184459735D9F3D5B8F8 /* AnimationKeypath.swift */; }; + 71ED4E1B587688E31035017D6E1BA4B8 /* VectorsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF07B406B562208ED74701CE1E184DC /* VectorsExtensions.swift */; }; + 72930E70A3CC4CCA5FE871A9A6C3C5FF /* TextDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92BFBF4D00FD8CC942963341EF06370 /* TextDocument.swift */; }; + 7463B2B6601F367B1D308EC7A191CB3E /* Marker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D711A0C67AB45CD28C6B422DED74FD7 /* Marker.swift */; }; + 759FEB111C6706D8804C475FEC353C91 /* ValueContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E8ECD6CD13CDFE40BD582BB58D0949F /* ValueContainer.swift */; }; + 763A6C2CD77715BEC9413619706D78F3 /* KeyframeInterpolator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40593815441D8E7CFD7CC5DBB0FF5016 /* KeyframeInterpolator.swift */; }; + 796D4CB4F4AB8ECBBD931B099A74E833 /* FBSnapshotTestController.h in Headers */ = {isa = PBXBuildFile; fileRef = 249C74BA0B81A1F25BBCB05327F51673 /* FBSnapshotTestController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7AC242AE72CA6A2DF9EC2CC7082686AA /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5BFA8DAE2D70ED4DC3F1D033511089 /* StringExtensions.swift */; }; + 7E65C983477106B3212FF7140415883A /* Transform.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD6EF487F05397C41F9EE69421DB0E7A /* Transform.swift */; }; + 7EE2F5E99566868EA0FC8BD0A0FCB611 /* CompositionLayersInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3413469AB1BACA8E56CFF0C8BC7D21C3 /* CompositionLayersInitializer.swift */; }; + 803A7BFA6853A4DEB38484080633622E /* TextStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1160673E50C791C5B8B1B3825CAD7E46 /* TextStyle.swift */; }; + 82CE8E0DC98452F08E1A03D3CA91B763 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3594C8B9A6847185E0695C678BFA3CBA /* UIKit.framework */; }; + 84846136ADA17AD44F0FD81DB41B5974 /* SolidCompositionLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528B7CCA43B98371BC840A1188D67C1B /* SolidCompositionLayer.swift */; }; + 85729A10E2757EF02FEA2F89EFD0C6D7 /* TextLayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372DD8BAE931ECCF24302E80BE1995BA /* TextLayerModel.swift */; }; + 8635C02F65D3BA9468647773820AF926 /* ShapeContainerLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7281E5092AE391FE3AE09B65AEDEDFE4 /* ShapeContainerLayer.swift */; }; + 86BC25FD2CC085B17A374500C747062D /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A460D115DDD71343129CE0146A49D47F /* QuartzCore.framework */; }; + 87842D6380CD5BC81AC53DC4BCED9F3E /* LRUAnimationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0977C96A168161F86A9F4F3A36D6854 /* LRUAnimationCache.swift */; }; + 8962113B149FE8ACE1344354AD6DD805 /* ImageCompositionLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F216188BB94767F4F82D769F143299 /* ImageCompositionLayer.swift */; }; + 89CB900EF33224231DA340B651E38612 /* Ellipse.swift in Sources */ = {isa = PBXBuildFile; fileRef = E904E6082E395DBF5AA17147A004A1FF /* Ellipse.swift */; }; + 8B528BFD60CE6C973F5AEB7AC57AFAC5 /* GradientStrokeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81EA689EE3E3DEEAB8594A412D156E4D /* GradientStrokeNode.swift */; }; + 8B9F8F56D66F35325369A9CFE9E59C54 /* KeyframeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B5C1A28ABE2C9387BCDF70503107FD0 /* KeyframeExtensions.swift */; }; + 8BB0093003D163A10821B533A0E03018 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4615A1BBC5756B54B4FC9B8FF7DA9A2 /* CoreGraphics.framework */; }; + 8BCBAF0C43F1BA9273A06CE4FD8C9BBB /* AnyValueProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26035F0E7223633025143D3AFE4CE3D0 /* AnyValueProvider.swift */; }; + 8C3A4D21C84C1702C3A501D268D3C75E /* StylableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A17BE7AB51D35E9119DDD4E562F3555 /* StylableTextField.swift */; }; + 8CA0E9EE027CB96F81C15F9C08622A11 /* UIViewController+stylable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 325D97519E67A795F3551B6230A763A7 /* UIViewController+stylable.swift */; }; + 8D181C938FF7B5B5ED1570D3C5C88E16 /* CompatibleAnimationKeypath.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1737E80888FBCF794B968F5366D3DD4 /* CompatibleAnimationKeypath.swift */; }; + 8E4CBBA93AFAB0953F7149862D37FA9E /* AnimatedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9C9BF5441DF87BE9CEEB2F11F664AA /* AnimatedButton.swift */; }; + 8E61ABC79616CB1CC563C854657B8089 /* CGFloatExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8546FB146D088111CDE959FAD883BC9 /* CGFloatExtensions.swift */; }; + 8E8B5D05EE35209638D332BA81F31786 /* StylableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8032A778CA1ABD99A33C054A84FCB1E6 /* StylableLabel.swift */; }; + 92526E403B78182E1226E1B08DE8E667 /* CompositionLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AFFF54AB654A31EE46626876FB788C7 /* CompositionLayer.swift */; }; + 953E599E6D5C07548F97ED3873CD00BB /* TrimPathNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19E73EE14706ABCEDD110E2C1E9152E2 /* TrimPathNode.swift */; }; + 95A0803B1B2F4A67FFA5476F7533EBB6 /* Interpolatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CBC699E993178A2C85F23E9A0ED5C5 /* Interpolatable.swift */; }; + 95D5EFDCF6B26A9FB579F3EFF9EB9089 /* AnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42B0665B63FAAA3E374E31FB3AF363AB /* AnimationView.swift */; }; + 95EAF4840FA44407D7B9D93198FC5059 /* SolidLayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D594A6A8FA244CC5DA997B1EF87235F7 /* SolidLayerModel.swift */; }; + 968B5318A745263F0D3A72023217CD12 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5062DC1B95F4E6572B0A799BC07ADB66 /* XCTest.framework */; }; + 96EA93D5A758302CF9B6CEC107542917 /* FilepathImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBAB2A2F2B3660E1F6B6E7CC303EDCB2 /* FilepathImageProvider.swift */; }; + 989CEF16EDC5D18216EE1400413F48C5 /* Pods-StylableUIKit_Example-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 628CD69AC056DC3C40D11D33902C0BF1 /* Pods-StylableUIKit_Example-dummy.m */; }; + 9BCBBD1AB5C9742C9F2D07EDA72BD0EF /* GradientFillNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F523577122BC9296BD8EE0CE12F60083 /* GradientFillNode.swift */; }; + 9D8842EBA687733DA232A097038639F2 /* VariantStyleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F758CF72BA6373498264D7BD8AB2D9 /* VariantStyleProvider.swift */; }; + 9F79284FB1127D33E602EE78C41F48D7 /* AnimationPublic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59999D236BEFA798575B8350387733B0 /* AnimationPublic.swift */; }; + 9FC2F90D16562C099A4BB2F4541583E6 /* CompatibleAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3317FD44B5BB148D57C7C96F529D805 /* CompatibleAnimationView.swift */; }; + 9FC6E9E8AB3284D5F7C7E7A869E083C3 /* LayerFill.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8E5FA8071F53D149BC3628C85DA96D8 /* LayerFill.swift */; }; + A2BDC46F2D787618D6021DB4A5899AA0 /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE085E0E4A1732F66A6FEC23EDD35389 /* Animation.swift */; }; + A31282B9F906F135C8C26AAD07547E76 /* PreCompositionLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1790839B7841B5EB80A0630747E0F088 /* PreCompositionLayer.swift */; }; + A62E62FAA565DE00C72FAA6471E85424 /* GradientStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EEEE5E0D9AFFD5FB171192255267A93 /* GradientStroke.swift */; }; + A7B8B90C6AC44CFB1C1085E36DDB90D4 /* LayerTextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70EDD87729BCAF997AD6CFE03A747310 /* LayerTextProvider.swift */; }; + AABF54BC7ADD4805106D2B8D9D3164B6 /* PassThroughOutputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF74593614BCDB8539CCE3778D308DBB /* PassThroughOutputNode.swift */; }; + AEA220D845FEB4CEA18D3125D0BE349E /* ShapeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AF4880DEB53CB74804B05BAA3C4E1B2 /* ShapeNode.swift */; }; + AF29C5AB251934C18E7F83E631D5D9C0 /* AnimationImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C189678585D4BF32CE1A187D7AA71A1A /* AnimationImageProvider.swift */; }; + B088422F77B6924629DF438D0956CBD8 /* AnimatedSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1DC73709D22F07D0974DAD1BA6DA64 /* AnimatedSwitch.swift */; }; + B279388B409EC8A801F40303D1A62C17 /* AnimationTextProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 296D3A00ACD93D4BD58E895DDA8423A9 /* AnimationTextProvider.swift */; }; + B2D2699AADE6EFDA74745F3203E4CDA0 /* InvertedMatteLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13BDBB8978A1BAF9CA8A2E55449F06E9 /* InvertedMatteLayer.swift */; }; + B3564C0CC587E19E8CD186F51C93E78A /* NSAttributedString+attributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF63CC66214F103A3BC99947249D063F /* NSAttributedString+attributes.swift */; }; + B35B6452EECACAB7769C4303372E6004 /* ShapeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7042929470234AF28FFD978E47EA73A1 /* ShapeItem.swift */; }; + B4E93DFF079361B23C2964ECF09A8F01 /* EllipseNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29AD7E4CED07F0DAEA9F5BC27AC3D3C /* EllipseNode.swift */; }; + B57C560E9D79B963A5F9885E88667E8D /* Pods-StylableUIKit_Example-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = E61C19688B479280970B6FD2A205DC18 /* Pods-StylableUIKit_Example-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B6A9C62A5262C0767EF3B8C78D6FAA7D /* FBSnapshotTestCasePlatform.m in Sources */ = {isa = PBXBuildFile; fileRef = BD39857568FA4B18786510BBF6FEC920 /* FBSnapshotTestCasePlatform.m */; }; + B762BE0B0FD3DB84BD55B3EB21559426 /* AnimationKeypathExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A5C6CFB78EED6E185D796C256D5400D /* AnimationKeypathExtension.swift */; }; + B9AB73B70AB1022258D56948EC3BB2E4 /* StylableTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DB15E791FF182E127BA1D93B8FE1FF3 /* StylableTextView.swift */; }; + BA5778813E5E439FEDFAD5747E37AA69 /* PathNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506B8B95965B0B2F67DF5960FCC4009D /* PathNode.swift */; }; + BA911EC932D20CFAB29511E799E44956 /* LayerTransformNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74BB0B4F9099E413374D0C27C5AA58B2 /* LayerTransformNode.swift */; }; + C040B2F9389B59FB472EE18DF6DD2858 /* Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7CDB55A457AD7047859E76316FFAF90 /* Group.swift */; }; + C2E9F88322FC6CD78D82A0442FF76945 /* AnimationCacheProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0173023FF29C54B5CFEC160D41095602 /* AnimationCacheProvider.swift */; }; + C930BCB1B395E19ABBE9D4D491867353 /* iOSSnapshotTestCase-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = BB3A3B4251002074BEE7975C48E66D95 /* iOSSnapshotTestCase-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC2044231C448FD44A4E9AB8654FC52E /* Glyph.swift in Sources */ = {isa = PBXBuildFile; fileRef = A87B74CEBBBE2A7CCADAC0C2E5381CA3 /* Glyph.swift */; }; + CD1ACB8AD0A8E96A607F3F4790413B0C /* StylistIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8A6A022DB3ABDAB4ADB2482318EFF81 /* StylistIdentifier.swift */; }; + CE83D63C6E2F9C5955A2188A17148939 /* LayerDebugging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F05CE1250B392526FB5C71C7588F4E5 /* LayerDebugging.swift */; }; + CF1B291CC2629267E5900A3B25AB7081 /* ShapeCompositionLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6F841D18AE0AF8217EE79AB41270CB7 /* ShapeCompositionLayer.swift */; }; + CF1D2DFC1BAEBCE04E9C8B49805B3F24 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16610E5283F2F90D2F851FE38873A1B3 /* Color.swift */; }; + CF4EDBBD1FAF1C2A989E9BC0C9516E2C /* AnimatedAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFCB1066BB242F334731326228EA88F5 /* AnimatedAsset.swift */; }; + CF5AF72858427D88437435E8DBAD61DA /* GradientFill.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16958CD6B85B544E75A18878DF385B53 /* GradientFill.swift */; }; + D124116FFCE71466B01BC3E9C7EF9804 /* InterpolatableExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53470AF295ADA350FCE99BD1EE705E48 /* InterpolatableExtensions.swift */; }; + D1288CB0DF1D6EC64AFFECA261FF9D91 /* StylableUIKit-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 04DC669BD6778AD397B42156C910F8B4 /* StylableUIKit-dummy.m */; }; + D376CA1F6421CF60E660C40DB4927865 /* NullCompositionLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A945B41761BB320F124276303928C76 /* NullCompositionLayer.swift */; }; + D4E17FF709E26EAE084EE9F3DBD347F2 /* PolygonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79136739E4AD8EC78782F1A41E0A90C /* PolygonNode.swift */; }; + D687201AA6DD0F6F7CE235D803F0DDD2 /* LottieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE854B933C605525C76AA9ED69ED2CA /* LottieView.swift */; }; + D78FFCC5022ECA523E5C9BCCB7542798 /* GroupInterpolator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FD0C667A020A7BD5CF271E6634028A7 /* GroupInterpolator.swift */; }; + D7C952F8235974A8CB26EB1C3EEBA93A /* ImageAsset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34B5078CDB82C7B6F7CBD88EB6767DE6 /* ImageAsset.swift */; }; + D9D098013565C5E799F602084D540BD0 /* Star.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B492A9194AFA6526391DCDEB4DA9A98 /* Star.swift */; }; + DA428DD4DD36CDDCB0C29B07DF0678F4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3594C8B9A6847185E0695C678BFA3CBA /* UIKit.framework */; }; + DE4F3D06F5ADD538AB97AF3D35982271 /* CompoundBezierPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F377BC79A240E2BA4D14A9369B5E5C /* CompoundBezierPath.swift */; }; + DF067743DBB9CBEF42193CC349FCC65E /* iOSSnapshotTestCase-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 22192CA128113BBDAFB32945BD55003D /* iOSSnapshotTestCase-dummy.m */; }; + E011A607387241276369F9BEEE33DA62 /* FBSnapshotTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = B7E25BBBA432699014E7E3D7B5009B6F /* FBSnapshotTestCase.m */; }; + E03A337E4F131D3C0E3F13AE8CF2FD90 /* GradientFillRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ECE15E56018B727E04624F9B65EB8E /* GradientFillRenderer.swift */; }; + E0AEDC3FACFC674C8704AE88681164AD /* UIImage+Compare.m in Sources */ = {isa = PBXBuildFile; fileRef = DE17BCF149D5CC4FCA24577AE281FB6C /* UIImage+Compare.m */; }; + E17503C408B826887EC3C3FF3A04768A /* lottie-ios-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E24B111C47498430ACE138534022C8A /* lottie-ios-dummy.m */; }; + E21932BEA2B0B11EF6AAF4AC67E8587D /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A460D115DDD71343129CE0146A49D47F /* QuartzCore.framework */; }; + E242AA455327F1C80C36A75613F600C2 /* SizeValueProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14F77D7E3E3B47851B85AFB73F04FCF8 /* SizeValueProvider.swift */; }; + E7752C34B6B6A38FBD7B902DFBD77C8C /* Shape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41411EDED41281360AD68D6AC9B44C45 /* Shape.swift */; }; + EA0B889C017C171FB00B983AEF5D02B0 /* Keyframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D90377977E1DC735DF1A7FFAFC9ECD3 /* Keyframe.swift */; }; + ED7D0C5A3982EF5FA1024A247FE717D4 /* ImageLayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A65D98766F19B77C36E9C7A2920CDE8C /* ImageLayerModel.swift */; }; + F1FD2D2C38EFF42C72FFB64341DC7ABA /* PathOutputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 015404149AC61C24E561E38534B0C26D /* PathOutputNode.swift */; }; + F665DC648FD6987FC6227ABB928BB84F /* Lottie.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA8C51F4FCA0BA9FBEACD1D49CDE3DB2 /* Lottie.framework */; }; + F936DB9595933867F99ADCF839C6480E /* AnimationContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 059212762E12B4E2D6ED718877EAEFBA /* AnimationContainer.swift */; }; + F9D2B4E0047159BF0F67002264A41A70 /* Asset.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9ECF02113DDF0ECE97C0C223254D941 /* Asset.swift */; }; + FE107397E2A2ACFE036BC031711F665B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3357625801F09413816ACD60A5F1A64 /* Foundation.framework */; }; + FE37D243E4D61BC10FC513CFBF0C1F40 /* RectNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40202ADE4FA230A38CAEDA9C567235D6 /* RectNode.swift */; }; + FE6DD102461FCE23B54F090EB3097202 /* AnimationViewInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05B176F8F8B70EB0A2FF285EFB2BDED1 /* AnimationViewInitializers.swift */; }; + FEA26BC97107438875E472742974F29F /* AssetLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50095AFC0B1D31EBF7D31E3C6384CE1B /* AssetLibrary.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 076096937D5CC3F847A24FB9501A4C1D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0B967D7F8561D42493EE289EC8D450D1; + remoteInfo = "lottie-ios"; + }; + 5D84A4EF7E9FDFBC3D03741DFB5EB3FA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0B967D7F8561D42493EE289EC8D450D1; + remoteInfo = "lottie-ios"; + }; + 61563F2AF49F9E2F77C6018A63B95A01 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C393038B0BEF088C1B93E6528005862D; + remoteInfo = iOSSnapshotTestCase; + }; + 6A582F7813412FB23852778D02759AE0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D15B2DA9CE4FEB60C2768C7CE05B9032; + remoteInfo = StylableUIKit; + }; + D02B20FFF73D55879BCB01418D8891D5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3FCE3490DD43EB855E2C738A2D8BCCA4; + remoteInfo = "Pods-StylableUIKit_Example"; + }; + FC6BC92580CDD1BD34C6902926356E53 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BFDFE7DC352907FC980B868725387E98 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 52B60EC2A583F24ACBB69C113F5488B9; + remoteInfo = SwiftLint; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0134D9EC77D58FC6DAE183BC69D420B4 /* AnimatorNodeDebugging.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatorNodeDebugging.swift; path = "lottie-swift/src/Private/Utility/Debugging/AnimatorNodeDebugging.swift"; sourceTree = ""; }; + 015404149AC61C24E561E38534B0C26D /* PathOutputNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PathOutputNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift"; sourceTree = ""; }; + 0173023FF29C54B5CFEC160D41095602 /* AnimationCacheProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationCacheProvider.swift; path = "lottie-swift/src/Public/AnimationCache/AnimationCacheProvider.swift"; sourceTree = ""; }; + 01EB995205ADE922F0632B1BB5A88A4D /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; path = LICENSE; sourceTree = ""; }; + 04DC669BD6778AD397B42156C910F8B4 /* StylableUIKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "StylableUIKit-dummy.m"; sourceTree = ""; }; + 056E18E13B0889482A65AD1B916A32F4 /* AnyValueContainer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnyValueContainer.swift; path = "lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift"; sourceTree = ""; }; + 059212762E12B4E2D6ED718877EAEFBA /* AnimationContainer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationContainer.swift; path = "lottie-swift/src/Private/LayerContainers/AnimationContainer.swift"; sourceTree = ""; }; + 05B176F8F8B70EB0A2FF285EFB2BDED1 /* AnimationViewInitializers.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationViewInitializers.swift; path = "lottie-swift/src/Public/Animation/AnimationViewInitializers.swift"; sourceTree = ""; }; + 08AAC5FD988A62CC995CFFDFC1C7D408 /* NodeProperty.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NodeProperty.swift; path = "lottie-swift/src/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift"; sourceTree = ""; }; + 0B153F71EC21C615DCEEC792DA193874 /* GradientStrokeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GradientStrokeRenderer.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift"; sourceTree = ""; }; + 0D711A0C67AB45CD28C6B422DED74FD7 /* Marker.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Marker.swift; path = "lottie-swift/src/Private/Model/Objects/Marker.swift"; sourceTree = ""; }; + 0DA78CD813CD59F35615BDA1404B4D0E /* UIImage+Snapshot.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+Snapshot.m"; path = "FBSnapshotTestCase/Categories/UIImage+Snapshot.m"; sourceTree = ""; }; + 0DFAC9CF845B4FDD43602723506DC35C /* Pods-StylableUIKit_Tests-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-StylableUIKit_Tests-acknowledgements.markdown"; sourceTree = ""; }; + 0F2C129964B166867AC57FA824080634 /* StarNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StarNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift"; sourceTree = ""; }; + 1160673E50C791C5B8B1B3825CAD7E46 /* TextStyle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TextStyle.swift; sourceTree = ""; }; + 13BDBB8978A1BAF9CA8A2E55449F06E9 /* InvertedMatteLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = InvertedMatteLayer.swift; path = "lottie-swift/src/Private/LayerContainers/Utility/InvertedMatteLayer.swift"; sourceTree = ""; }; + 1432E944BF221B70237E5690F66D1C12 /* lottie-ios-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "lottie-ios-umbrella.h"; sourceTree = ""; }; + 14F77D7E3E3B47851B85AFB73F04FCF8 /* SizeValueProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SizeValueProvider.swift; path = "lottie-swift/src/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift"; sourceTree = ""; }; + 15B79F3C90574234CB1D9D45F9B86DF6 /* TextCompositionLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TextCompositionLayer.swift; path = "lottie-swift/src/Private/LayerContainers/CompLayers/TextCompositionLayer.swift"; sourceTree = ""; }; + 16610E5283F2F90D2F851FE38873A1B3 /* Color.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Color.swift; path = "lottie-swift/src/Public/Primitives/Color.swift"; sourceTree = ""; }; + 16958CD6B85B544E75A18878DF385B53 /* GradientFill.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GradientFill.swift; path = "lottie-swift/src/Private/Model/ShapeItems/GradientFill.swift"; sourceTree = ""; }; + 1790839B7841B5EB80A0630747E0F088 /* PreCompositionLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PreCompositionLayer.swift; path = "lottie-swift/src/Private/LayerContainers/CompLayers/PreCompositionLayer.swift"; sourceTree = ""; }; + 17D54C2D735D50C71C57CAF3C87F2BC6 /* lottie-ios-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "lottie-ios-Info.plist"; sourceTree = ""; }; + 18F758CF72BA6373498264D7BD8AB2D9 /* VariantStyleProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = VariantStyleProvider.swift; sourceTree = ""; }; + 198CA2BF65D1C3825C97C094CBE60C3E /* StylableButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StylableButton.swift; sourceTree = ""; }; + 19E73EE14706ABCEDD110E2C1E9152E2 /* TrimPathNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TrimPathNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift"; sourceTree = ""; }; + 1A3424A129DCE4AD48D0A80B90B9E7A8 /* Mask.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Mask.swift; path = "lottie-swift/src/Private/Model/Objects/Mask.swift"; sourceTree = ""; }; + 1B492A9194AFA6526391DCDEB4DA9A98 /* Star.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Star.swift; path = "lottie-swift/src/Private/Model/ShapeItems/Star.swift"; sourceTree = ""; }; + 1F05CE1250B392526FB5C71C7588F4E5 /* LayerDebugging.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayerDebugging.swift; path = "lottie-swift/src/Private/Utility/Debugging/LayerDebugging.swift"; sourceTree = ""; }; + 22192CA128113BBDAFB32945BD55003D /* iOSSnapshotTestCase-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "iOSSnapshotTestCase-dummy.m"; sourceTree = ""; }; + 22C2215C4369FA071D3AFC03B6A3571A /* AnimationTime.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationTime.swift; path = "lottie-swift/src/Public/Primitives/AnimationTime.swift"; sourceTree = ""; }; + 23ECA3097E04D6FEE2EA9DF2DE50073D /* StrokeNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StrokeNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift"; sourceTree = ""; }; + 240B1C8668288F04700A77482E4B130C /* KeypathSearchable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KeypathSearchable.swift; path = "lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift"; sourceTree = ""; }; + 249C74BA0B81A1F25BBCB05327F51673 /* FBSnapshotTestController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBSnapshotTestController.h; path = FBSnapshotTestCase/FBSnapshotTestController.h; sourceTree = ""; }; + 26035F0E7223633025143D3AFE4CE3D0 /* AnyValueProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnyValueProvider.swift; path = "lottie-swift/src/Public/DynamicProperties/AnyValueProvider.swift"; sourceTree = ""; }; + 296D3A00ACD93D4BD58E895DDA8423A9 /* AnimationTextProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationTextProvider.swift; path = "lottie-swift/src/Public/TextProvider/AnimationTextProvider.swift"; sourceTree = ""; }; + 2AA8EA53D3E8A160742FE010D2006CF2 /* UIColorExtension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = UIColorExtension.swift; path = "lottie-swift/src/Public/iOS/UIColorExtension.swift"; sourceTree = ""; }; + 2DB15E791FF182E127BA1D93B8FE1FF3 /* StylableTextView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StylableTextView.swift; sourceTree = ""; }; + 30FF05A76BC973E3DECDC6FDC2FDFEA3 /* AnimatorNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatorNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Protocols/AnimatorNode.swift"; sourceTree = ""; }; + 325D97519E67A795F3551B6230A763A7 /* UIViewController+stylable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "UIViewController+stylable.swift"; sourceTree = ""; }; + 32EA0C94258313A5F8DF98FA1FB33387 /* UIImage+Diff.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+Diff.h"; path = "FBSnapshotTestCase/Categories/UIImage+Diff.h"; sourceTree = ""; }; + 3314434FB57226915CB1C2F5C36B4C79 /* PathElement.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PathElement.swift; path = "lottie-swift/src/Private/Utility/Primitives/PathElement.swift"; sourceTree = ""; }; + 3413469AB1BACA8E56CFF0C8BC7D21C3 /* CompositionLayersInitializer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CompositionLayersInitializer.swift; path = "lottie-swift/src/Private/LayerContainers/Utility/CompositionLayersInitializer.swift"; sourceTree = ""; }; + 34B5078CDB82C7B6F7CBD88EB6767DE6 /* ImageAsset.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageAsset.swift; path = "lottie-swift/src/Private/Model/Assets/ImageAsset.swift"; sourceTree = ""; }; + 3594C8B9A6847185E0695C678BFA3CBA /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; }; + 372DD8BAE931ECCF24302E80BE1995BA /* TextLayerModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TextLayerModel.swift; path = "lottie-swift/src/Private/Model/Layers/TextLayerModel.swift"; sourceTree = ""; }; + 373C1D7B4F18E6C8F823250D36BAAE7D /* iOSSnapshotTestCase-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "iOSSnapshotTestCase-Info.plist"; sourceTree = ""; }; + 3A17BE7AB51D35E9119DDD4E562F3555 /* StylableTextField.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StylableTextField.swift; sourceTree = ""; }; + 3A945B41761BB320F124276303928C76 /* NullCompositionLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NullCompositionLayer.swift; path = "lottie-swift/src/Private/LayerContainers/CompLayers/NullCompositionLayer.swift"; sourceTree = ""; }; + 3AF4880DEB53CB74804B05BAA3C4E1B2 /* ShapeNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShapeNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift"; sourceTree = ""; }; + 3C379C8F9DB403AB40D5BE08B0A235BA /* GradientValueProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GradientValueProvider.swift; path = "lottie-swift/src/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift"; sourceTree = ""; }; + 3CE854B933C605525C76AA9ED69ED2CA /* LottieView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LottieView.swift; path = "lottie-swift/src/Public/iOS/LottieView.swift"; sourceTree = ""; }; + 3E53F0A109097AC14591182A3B6A781F /* Asset.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Asset.swift; path = "lottie-swift/src/Private/Model/Assets/Asset.swift"; sourceTree = ""; }; + 3FDCAFFD83DED18E6813044B733CB438 /* LayerStyle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LayerStyle.swift; sourceTree = ""; }; + 40202ADE4FA230A38CAEDA9C567235D6 /* RectNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RectNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/RectNode.swift"; sourceTree = ""; }; + 40593815441D8E7CFD7CC5DBB0FF5016 /* KeyframeInterpolator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KeyframeInterpolator.swift; path = "lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift"; sourceTree = ""; }; + 41411EDED41281360AD68D6AC9B44C45 /* Shape.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Shape.swift; path = "lottie-swift/src/Private/Model/ShapeItems/Shape.swift"; sourceTree = ""; }; + 42B0665B63FAAA3E374E31FB3AF363AB /* AnimationView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationView.swift; path = "lottie-swift/src/Public/Animation/AnimationView.swift"; sourceTree = ""; }; + 449BECFA6EE731CEB064E0E8049C0CB0 /* BezierPath.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BezierPath.swift; path = "lottie-swift/src/Private/Utility/Primitives/BezierPath.swift"; sourceTree = ""; }; + 46803D5C7BFC0268CE09EF74C7B3D147 /* Pods-StylableUIKit_Example.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-StylableUIKit_Example.modulemap"; sourceTree = ""; }; + 488609954ABCCD4E70F84384147D452A /* TextTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TextTransform.swift; sourceTree = ""; }; + 49BA712C28E8BC1BBA9F078506A63D75 /* RenderNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = RenderNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Protocols/RenderNode.swift"; sourceTree = ""; }; + 4AFADE56F7E421B19C6DAFDDC7243E70 /* ItemsExtension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ItemsExtension.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Extensions/ItemsExtension.swift"; sourceTree = ""; }; + 4B0657703A0F7504A1F25C58C1D9BF97 /* Pods-StylableUIKit_Tests-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-StylableUIKit_Tests-frameworks.sh"; sourceTree = ""; }; + 4B25A197D8A52F7AC9644A0B438F3E20 /* ShapeTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShapeTransform.swift; path = "lottie-swift/src/Private/Model/ShapeItems/ShapeTransform.swift"; sourceTree = ""; }; + 4C1DC73709D22F07D0974DAD1BA6DA64 /* AnimatedSwitch.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedSwitch.swift; path = "lottie-swift/src/Public/iOS/AnimatedSwitch.swift"; sourceTree = ""; }; + 4CCC9DA88DFBEBD76B336EF3FA4D4E81 /* Vectors.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Vectors.swift; path = "lottie-swift/src/Public/Primitives/Vectors.swift"; sourceTree = ""; }; + 4CF07B406B562208ED74701CE1E184DC /* VectorsExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = VectorsExtensions.swift; path = "lottie-swift/src/Private/Utility/Primitives/VectorsExtensions.swift"; sourceTree = ""; }; + 4E5BFA8DAE2D70ED4DC3F1D033511089 /* StringExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StringExtensions.swift; path = "lottie-swift/src/Private/Utility/Extensions/StringExtensions.swift"; sourceTree = ""; }; + 50095AFC0B1D31EBF7D31E3C6384CE1B /* AssetLibrary.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AssetLibrary.swift; path = "lottie-swift/src/Private/Model/Assets/AssetLibrary.swift"; sourceTree = ""; }; + 500BA72426940EA7A34228112E0157A3 /* StylableUIKit.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = StylableUIKit.podspec; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 505127C28A6BBF7640A38C2BCB5459AC /* ShapeRenderLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShapeRenderLayer.swift; path = "lottie-swift/src/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift"; sourceTree = ""; }; + 5062DC1B95F4E6572B0A799BC07ADB66 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 5066FE2E3AE6FAF3BE69D583B7A6C6F9 /* FBSnapshotTestCase.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBSnapshotTestCase.h; path = FBSnapshotTestCase/FBSnapshotTestCase.h; sourceTree = ""; }; + 506B8B95965B0B2F67DF5960FCC4009D /* PathNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PathNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Protocols/PathNode.swift"; sourceTree = ""; }; + 51BA97E8B5085EFFB47BC9C0B785CEA7 /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Lottie.framework; path = "lottie-ios.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 528B7CCA43B98371BC840A1188D67C1B /* SolidCompositionLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SolidCompositionLayer.swift; path = "lottie-swift/src/Private/LayerContainers/CompLayers/SolidCompositionLayer.swift"; sourceTree = ""; }; + 52E4059B25E5E09B1D555F126D162516 /* Repeater.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Repeater.swift; path = "lottie-swift/src/Private/Model/ShapeItems/Repeater.swift"; sourceTree = ""; }; + 53470AF295ADA350FCE99BD1EE705E48 /* InterpolatableExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = InterpolatableExtensions.swift; path = "lottie-swift/src/Private/Utility/Interpolatable/InterpolatableExtensions.swift"; sourceTree = ""; }; + 55F216188BB94767F4F82D769F143299 /* ImageCompositionLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageCompositionLayer.swift; path = "lottie-swift/src/Private/LayerContainers/CompLayers/ImageCompositionLayer.swift"; sourceTree = ""; }; + 59999D236BEFA798575B8350387733B0 /* AnimationPublic.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationPublic.swift; path = "lottie-swift/src/Public/Animation/AnimationPublic.swift"; sourceTree = ""; }; + 59DB72A6392CF91A3946729B5ECA7FEC /* BundleImageProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BundleImageProvider.swift; path = "lottie-swift/src/Public/iOS/BundleImageProvider.swift"; sourceTree = ""; }; + 5A0272C6413D935F85D9A0605B9AFF72 /* StrokeRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = StrokeRenderer.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift"; sourceTree = ""; }; + 5AB06DE1BB4AA0944378CDB8531D24E8 /* StylableUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = StylableUIKit.framework; path = StylableUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5B2EA56E964E82236F693F021B8B8B56 /* Pods-StylableUIKit_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-StylableUIKit_Example.release.xcconfig"; sourceTree = ""; }; + 5C3A42D58E538C45A0E3710A8258ABE3 /* Pods-StylableUIKit_Tests.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-StylableUIKit_Tests.modulemap"; sourceTree = ""; }; + 5DDB6F9E60DAB454A31E1AF3BA57AF96 /* SwiftSupport.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SwiftSupport.swift; path = FBSnapshotTestCase/SwiftSupport.swift; sourceTree = ""; }; + 5E24B111C47498430ACE138534022C8A /* lottie-ios-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "lottie-ios-dummy.m"; sourceTree = ""; }; + 5E81A7D72B31C5BA1E93EADADB1B616B /* Pods-StylableUIKit_Tests-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-StylableUIKit_Tests-Info.plist"; sourceTree = ""; }; + 6237D2F145F83895362AE76B6871E050 /* Pods-StylableUIKit_Example-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-StylableUIKit_Example-acknowledgements.plist"; sourceTree = ""; }; + 628CD69AC056DC3C40D11D33902C0BF1 /* Pods-StylableUIKit_Example-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-StylableUIKit_Example-dummy.m"; sourceTree = ""; }; + 660020C1AFA0F23325D2F7774D74E2E8 /* StylableUIKit.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = StylableUIKit.modulemap; sourceTree = ""; }; + 666C1E81F957B007A1BDF46F2EC925B6 /* Pods-StylableUIKit_Tests-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-StylableUIKit_Tests-umbrella.h"; sourceTree = ""; }; + 6AEE7F581515D14F487EBE22701CC423 /* LinearLayerOutline.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LinearLayerOutline.swift; sourceTree = ""; }; + 6AFFF54AB654A31EE46626876FB788C7 /* CompositionLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CompositionLayer.swift; path = "lottie-swift/src/Private/LayerContainers/CompLayers/CompositionLayer.swift"; sourceTree = ""; }; + 6C7909A8CAECD96FA3C42C3BF2163AEA /* Pods_StylableUIKit_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_StylableUIKit_Tests.framework; path = "Pods-StylableUIKit_Tests.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7042929470234AF28FFD978E47EA73A1 /* ShapeItem.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShapeItem.swift; path = "lottie-swift/src/Private/Model/ShapeItems/ShapeItem.swift"; sourceTree = ""; }; + 706D165B0ABC2FC1C23CFD348A8AD336 /* StylableUIKit-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "StylableUIKit-umbrella.h"; sourceTree = ""; }; + 70EDD87729BCAF997AD6CFE03A747310 /* LayerTextProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayerTextProvider.swift; path = "lottie-swift/src/Private/LayerContainers/Utility/LayerTextProvider.swift"; sourceTree = ""; }; + 7281E5092AE391FE3AE09B65AEDEDFE4 /* ShapeContainerLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShapeContainerLayer.swift; path = "lottie-swift/src/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift"; sourceTree = ""; }; + 731B7EA6D2C410A3EE3DEC51F41645D9 /* iOSSnapshotTestCase.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = iOSSnapshotTestCase.modulemap; sourceTree = ""; }; + 74BB0B4F9099E413374D0C27C5AA58B2 /* LayerTransformNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayerTransformNode.swift; path = "lottie-swift/src/Private/LayerContainers/Utility/LayerTransformNode.swift"; sourceTree = ""; }; + 75511F820C463B56E94AA5BE6D304585 /* KeyframeGroup.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KeyframeGroup.swift; path = "lottie-swift/src/Private/Model/Keyframes/KeyframeGroup.swift"; sourceTree = ""; }; + 76420F2B0C49DA9024D9D46579527EC2 /* ShapeLayerModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShapeLayerModel.swift; path = "lottie-swift/src/Private/Model/Layers/ShapeLayerModel.swift"; sourceTree = ""; }; + 7A5923C4470A5F464DAE4DCF4C46D233 /* AnyNodeProperty.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnyNodeProperty.swift; path = "lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift"; sourceTree = ""; }; + 7CDF7AC53AC4A8C4C842E372C70A7EA6 /* SwiftLint.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = SwiftLint.xcconfig; sourceTree = ""; }; + 7E9C9BF5441DF87BE9CEEB2F11F664AA /* AnimatedButton.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedButton.swift; path = "lottie-swift/src/Public/iOS/AnimatedButton.swift"; sourceTree = ""; }; + 7FD0C667A020A7BD5CF271E6634028A7 /* GroupInterpolator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GroupInterpolator.swift; path = "lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift"; sourceTree = ""; }; + 8032A778CA1ABD99A33C054A84FCB1E6 /* StylableLabel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StylableLabel.swift; sourceTree = ""; }; + 81EA689EE3E3DEEAB8594A412D156E4D /* GradientStrokeNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GradientStrokeNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift"; sourceTree = ""; }; + 84ECE15E56018B727E04624F9B65EB8E /* GradientFillRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GradientFillRenderer.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift"; sourceTree = ""; }; + 860214E7F7E4AF5B029DA88AE2FA0B92 /* lottie-ios.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "lottie-ios.xcconfig"; sourceTree = ""; }; + 87D471259EB7CA6E99D9214A5FC8455F /* ColorValueProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorValueProvider.swift; path = "lottie-swift/src/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift"; sourceTree = ""; }; + 881A3F2A8EACD17C1CC0FE61388C5451 /* lottie-ios-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "lottie-ios-prefix.pch"; sourceTree = ""; }; + 8829B18BC10A81B9137665BB6E8D8DF2 /* KeyedDecodingContainerExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KeyedDecodingContainerExtensions.swift; path = "lottie-swift/src/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift"; sourceTree = ""; }; + 89B162F41650676AB69326E9D142FEF9 /* Pods_StylableUIKit_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_StylableUIKit_Example.framework; path = "Pods-StylableUIKit_Example.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8A5C6CFB78EED6E185D796C256D5400D /* AnimationKeypathExtension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationKeypathExtension.swift; path = "lottie-swift/src/Private/Utility/Extensions/AnimationKeypathExtension.swift"; sourceTree = ""; }; + 8C557D0586B1DF168D5316404C6002A0 /* LayerImageProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayerImageProvider.swift; path = "lottie-swift/src/Private/LayerContainers/Utility/LayerImageProvider.swift"; sourceTree = ""; }; + 8D90377977E1DC735DF1A7FFAFC9ECD3 /* Keyframe.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Keyframe.swift; path = "lottie-swift/src/Private/Model/Keyframes/Keyframe.swift"; sourceTree = ""; }; + 8E8ECD6CD13CDFE40BD582BB58D0949F /* ValueContainer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ValueContainer.swift; path = "lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift"; sourceTree = ""; }; + 8ED089043178D008E6428E59FB8B64FF /* PrecompAsset.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PrecompAsset.swift; path = "lottie-swift/src/Private/Model/Assets/PrecompAsset.swift"; sourceTree = ""; }; + 8EE89BF228FAD5CEAE18DA5AD43A9D79 /* MaskContainerLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MaskContainerLayer.swift; path = "lottie-swift/src/Private/LayerContainers/CompLayers/MaskContainerLayer.swift"; sourceTree = ""; }; + 903CAA7B22CF6C2317DA99ACB4F8B407 /* GroupOutputNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GroupOutputNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift"; sourceTree = ""; }; + 92293CB65933FEA609B06A47BFBFF47D /* FloatValueProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FloatValueProvider.swift; path = "lottie-swift/src/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift"; sourceTree = ""; }; + 946099FE4096FE33731A06630DAC5658 /* Stylist.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Stylist.swift; sourceTree = ""; }; + 9710FDAC9E77F5E9D1696B02279A3515 /* PreCompLayerModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PreCompLayerModel.swift; path = "lottie-swift/src/Private/Model/Layers/PreCompLayerModel.swift"; sourceTree = ""; }; + 9B5C1A28ABE2C9387BCDF70503107FD0 /* KeyframeExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = KeyframeExtensions.swift; path = "lottie-swift/src/Private/Utility/Interpolatable/KeyframeExtensions.swift"; sourceTree = ""; }; + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9E46E60021235C1C35E4C0E7F590BEE6 /* FillRenderer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FillRenderer.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift"; sourceTree = ""; }; + 9EEEE5E0D9AFFD5FB171192255267A93 /* GradientStroke.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GradientStroke.swift; path = "lottie-swift/src/Private/Model/ShapeItems/GradientStroke.swift"; sourceTree = ""; }; + 9F166374728BA61385CE0F99FB290FED /* TextAnimatorNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TextAnimatorNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift"; sourceTree = ""; }; + A460D115DDD71343129CE0146A49D47F /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/QuartzCore.framework; sourceTree = DEVELOPER_DIR; }; + A65D98766F19B77C36E9C7A2920CDE8C /* ImageLayerModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ImageLayerModel.swift; path = "lottie-swift/src/Private/Model/Layers/ImageLayerModel.swift"; sourceTree = ""; }; + A87B74CEBBBE2A7CCADAC0C2E5381CA3 /* Glyph.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Glyph.swift; path = "lottie-swift/src/Private/Model/Text/Glyph.swift"; sourceTree = ""; }; + A8A6A022DB3ABDAB4ADB2482318EFF81 /* StylistIdentifier.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StylistIdentifier.swift; sourceTree = ""; }; + A92BFBF4D00FD8CC942963341EF06370 /* TextDocument.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TextDocument.swift; path = "lottie-swift/src/Private/Model/Text/TextDocument.swift"; sourceTree = ""; }; + A9ECF02113DDF0ECE97C0C223254D941 /* Asset.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = Asset.swift; sourceTree = ""; }; + AB2EF7F49636832D0F4DD5E002BBDB50 /* Pods-StylableUIKit_Example-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-StylableUIKit_Example-acknowledgements.markdown"; sourceTree = ""; }; + AB9D6DF74D13984EAACE4D124AC0E61E /* Pods-StylableUIKit_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-StylableUIKit_Tests.release.xcconfig"; sourceTree = ""; }; + ABF041F01910A296BDC6A7BC8E47580A /* Pods-StylableUIKit_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-StylableUIKit_Tests.debug.xcconfig"; sourceTree = ""; }; + AD07EAB31953CEBB71C40E2BA64EF2C6 /* AnimationSubview.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationSubview.swift; path = "lottie-swift/src/Public/iOS/AnimationSubview.swift"; sourceTree = ""; }; + AE085E0E4A1732F66A6FEC23EDD35389 /* Animation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Animation.swift; path = "lottie-swift/src/Private/Model/Animation.swift"; sourceTree = ""; }; + AE449AA3FD1ED2D00D17C8EACE41F19B /* Font.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Font.swift; path = "lottie-swift/src/Private/Model/Text/Font.swift"; sourceTree = ""; }; + B10AFF46F94F7D4D3314BCDDE3E2315E /* Pods-StylableUIKit_Example-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-StylableUIKit_Example-Info.plist"; sourceTree = ""; }; + B112EE14E786D76E7E681855F45BA3E2 /* MathKit.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MathKit.swift; path = "lottie-swift/src/Private/Utility/Extensions/MathKit.swift"; sourceTree = ""; }; + B29AD7E4CED07F0DAEA9F5BC27AC3D3C /* EllipseNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = EllipseNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift"; sourceTree = ""; }; + B504C6C300126613F44985F63DB93EDE /* iOSSnapshotTestCase.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = iOSSnapshotTestCase.xcconfig; sourceTree = ""; }; + B775FC1798B4296803F6783FD3376AC7 /* StylableUIKit.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = StylableUIKit.xcconfig; sourceTree = ""; }; + B7CDB55A457AD7047859E76316FFAF90 /* Group.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Group.swift; path = "lottie-swift/src/Private/Model/ShapeItems/Group.swift"; sourceTree = ""; }; + B7E25BBBA432699014E7E3D7B5009B6F /* FBSnapshotTestCase.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FBSnapshotTestCase.m; path = FBSnapshotTestCase/FBSnapshotTestCase.m; sourceTree = ""; }; + B8AC51CBABCD6078ED0CE35155F84C99 /* StylableUIKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "StylableUIKit-prefix.pch"; sourceTree = ""; }; + B8E5FA8071F53D149BC3628C85DA96D8 /* LayerFill.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LayerFill.swift; sourceTree = ""; }; + B91B00F8BE943329D633234BD67AE0AC /* FBSnapshotTestCase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FBSnapshotTestCase.framework; path = iOSSnapshotTestCase.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BB07439868C653FACEF5A4213A0DA351 /* NSAttributedString+textTransform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+textTransform.swift"; sourceTree = ""; }; + BB3A3B4251002074BEE7975C48E66D95 /* iOSSnapshotTestCase-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "iOSSnapshotTestCase-umbrella.h"; sourceTree = ""; }; + BD39857568FA4B18786510BBF6FEC920 /* FBSnapshotTestCasePlatform.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FBSnapshotTestCasePlatform.m; path = FBSnapshotTestCase/FBSnapshotTestCasePlatform.m; sourceTree = ""; }; + BD66DD9A3F53F41AB7357C6F93E15F91 /* StylableUIKit-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "StylableUIKit-Info.plist"; sourceTree = ""; }; + C073ABA222866A71C0639369CAE1AD6B /* FBSnapshotTestCasePlatform.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FBSnapshotTestCasePlatform.h; path = FBSnapshotTestCase/FBSnapshotTestCasePlatform.h; sourceTree = ""; }; + C189678585D4BF32CE1A187D7AA71A1A /* AnimationImageProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationImageProvider.swift; path = "lottie-swift/src/Public/ImageProvider/AnimationImageProvider.swift"; sourceTree = ""; }; + C29896A7720E01A21E52E4DC9AA85DBB /* DashPattern.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DashPattern.swift; path = "lottie-swift/src/Private/Model/Objects/DashPattern.swift"; sourceTree = ""; }; + C4CBC699E993178A2C85F23E9A0ED5C5 /* Interpolatable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Interpolatable.swift; path = "lottie-swift/src/Private/Utility/Interpolatable/Interpolatable.swift"; sourceTree = ""; }; + C4F0404567BD464504B5FF43E353596F /* Lottie+StylistAnimatedAsset.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "Lottie+StylistAnimatedAsset.swift"; path = "StylableUIKit/Classes/Subspec/Lottie/Lottie+StylistAnimatedAsset.swift"; sourceTree = ""; }; + C5DEB7DB872F1359172AD078498B9848 /* CurveVertex.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CurveVertex.swift; path = "lottie-swift/src/Private/Utility/Primitives/CurveVertex.swift"; sourceTree = ""; }; + C702734BB61421EED37FA5E403B6FB4E /* Merge.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Merge.swift; path = "lottie-swift/src/Private/Model/ShapeItems/Merge.swift"; sourceTree = ""; }; + C79136739E4AD8EC78782F1A41E0A90C /* PolygonNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PolygonNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift"; sourceTree = ""; }; + C7C38DB5E77415614E3DD213C71C4A3D /* Stroke.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Stroke.swift; path = "lottie-swift/src/Private/Model/ShapeItems/Stroke.swift"; sourceTree = ""; }; + C8546FB146D088111CDE959FAD883BC9 /* CGFloatExtensions.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CGFloatExtensions.swift; path = "lottie-swift/src/Private/Utility/Extensions/CGFloatExtensions.swift"; sourceTree = ""; }; + C9C0457172E1E7873D05C587813F3A7E /* iOSSnapshotTestCase-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "iOSSnapshotTestCase-prefix.pch"; sourceTree = ""; }; + CA35FA6F1384B93259E2AE248E7670C2 /* FillNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FillNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift"; sourceTree = ""; }; + CA3603248D245DD452606F3A00976216 /* Pods-StylableUIKit_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-StylableUIKit_Example.debug.xcconfig"; sourceTree = ""; }; + CA53E107FD0D2CD5C8EBBCDB59078FA3 /* NodePropertyMap.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = NodePropertyMap.swift; path = "lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift"; sourceTree = ""; }; + CD6EF487F05397C41F9EE69421DB0E7A /* Transform.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Transform.swift; path = "lottie-swift/src/Private/Model/Objects/Transform.swift"; sourceTree = ""; }; + D0977C96A168161F86A9F4F3A36D6854 /* LRUAnimationCache.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LRUAnimationCache.swift; path = "lottie-swift/src/Public/AnimationCache/LRUAnimationCache.swift"; sourceTree = ""; }; + D1737E80888FBCF794B968F5366D3DD4 /* CompatibleAnimationKeypath.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CompatibleAnimationKeypath.swift; path = "lottie-swift/src/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift"; sourceTree = ""; }; + D17E2B4C6A5C0CC67B45763B96C0B34A /* Pods-StylableUIKit_Example-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-StylableUIKit_Example-frameworks.sh"; sourceTree = ""; }; + D3357625801F09413816ACD60A5F1A64 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + D4615A1BBC5756B54B4FC9B8FF7DA9A2 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/CoreGraphics.framework; sourceTree = DEVELOPER_DIR; }; + D594A6A8FA244CC5DA997B1EF87235F7 /* SolidLayerModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SolidLayerModel.swift; path = "lottie-swift/src/Private/Model/Layers/SolidLayerModel.swift"; sourceTree = ""; }; + D5AB7868209B2165B93BA05999D6ECFE /* Pods-StylableUIKit_Tests-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-StylableUIKit_Tests-dummy.m"; sourceTree = ""; }; + D6BBC12BE51607207D2094DD5954C226 /* Pods-StylableUIKit_Tests-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-StylableUIKit_Tests-acknowledgements.plist"; sourceTree = ""; }; + D761830421837FFE0112C277FEE013E0 /* Trim.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Trim.swift; path = "lottie-swift/src/Private/Model/ShapeItems/Trim.swift"; sourceTree = ""; }; + DB310932F37EE63CEDCA51BB46650930 /* lottie-ios.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "lottie-ios.modulemap"; sourceTree = ""; }; + DC3247B49FFE60141DCC6E3F09E8F587 /* UIImage+Compare.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+Compare.h"; path = "FBSnapshotTestCase/Categories/UIImage+Compare.h"; sourceTree = ""; }; + DE17BCF149D5CC4FCA24577AE281FB6C /* UIImage+Compare.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+Compare.m"; path = "FBSnapshotTestCase/Categories/UIImage+Compare.m"; sourceTree = ""; }; + DE360F166EC21B3DC9AACC5FB6F400F4 /* GroupNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GroupNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift"; sourceTree = ""; }; + DE5FAEF44E1F75BA4EC6BBD7AA7A8CE9 /* LayerModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LayerModel.swift; path = "lottie-swift/src/Private/Model/Layers/LayerModel.swift"; sourceTree = ""; }; + DF18BEBE83A4D7A09612AB19BA9A97E2 /* UIImage+Snapshot.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+Snapshot.h"; path = "FBSnapshotTestCase/Categories/UIImage+Snapshot.h"; sourceTree = ""; }; + DFEC4B9505553AEF6E4A28B88B75A935 /* FillI.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FillI.swift; path = "lottie-swift/src/Private/Model/ShapeItems/FillI.swift"; sourceTree = ""; }; + E08ACDD9085A2184459735D9F3D5B8F8 /* AnimationKeypath.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationKeypath.swift; path = "lottie-swift/src/Public/DynamicProperties/AnimationKeypath.swift"; sourceTree = ""; }; + E1DF3A4CD2B86E861188639DCE7D905D /* Rectangle.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Rectangle.swift; path = "lottie-swift/src/Private/Model/ShapeItems/Rectangle.swift"; sourceTree = ""; }; + E3317FD44B5BB148D57C7C96F529D805 /* CompatibleAnimationView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CompatibleAnimationView.swift; path = "lottie-swift/src/Public/iOS/Compatibility/CompatibleAnimationView.swift"; sourceTree = ""; }; + E61C19688B479280970B6FD2A205DC18 /* Pods-StylableUIKit_Example-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-StylableUIKit_Example-umbrella.h"; sourceTree = ""; }; + E73751FF2E1F6450CC96A993FE154428 /* AnimatedControl.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimatedControl.swift; path = "lottie-swift/src/Public/iOS/AnimatedControl.swift"; sourceTree = ""; }; + E7B1A46187451C718EC7C10F13185358 /* TextAnimator.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = TextAnimator.swift; path = "lottie-swift/src/Private/Model/Text/TextAnimator.swift"; sourceTree = ""; }; + E904E6082E395DBF5AA17147A004A1FF /* Ellipse.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = Ellipse.swift; path = "lottie-swift/src/Private/Model/ShapeItems/Ellipse.swift"; sourceTree = ""; }; + EA2AF4F28E5D8D7122AD290D066D2185 /* RestrictedLayerStylable.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = RestrictedLayerStylable.swift; sourceTree = ""; }; + EBAB2A2F2B3660E1F6B6E7CC303EDCB2 /* FilepathImageProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = FilepathImageProvider.swift; path = "lottie-swift/src/Public/iOS/FilepathImageProvider.swift"; sourceTree = ""; }; + EFCB1066BB242F334731326228EA88F5 /* AnimatedAsset.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AnimatedAsset.swift; sourceTree = ""; }; + F10CC13A9FC8FF8B54E28CC46A517B24 /* String+Lottie.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = "String+Lottie.swift"; path = "StylableUIKit/Classes/Subspec/Lottie/String+Lottie.swift"; sourceTree = ""; }; + F1E9B5B40C9D55CE2EB9E3E3B3489B43 /* UIImage+Diff.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+Diff.m"; path = "FBSnapshotTestCase/Categories/UIImage+Diff.m"; sourceTree = ""; }; + F1F377BC79A240E2BA4D14A9369B5E5C /* CompoundBezierPath.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = CompoundBezierPath.swift; path = "lottie-swift/src/Private/Utility/Primitives/CompoundBezierPath.swift"; sourceTree = ""; }; + F229D8F4B48C7BCCE729612EC98C2A84 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; path = README.md; sourceTree = ""; }; + F47BF1972756382AA4E72A9013CE4875 /* PointValueProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PointValueProvider.swift; path = "lottie-swift/src/Public/DynamicProperties/ValueProviders/PointValueProvider.swift"; sourceTree = ""; }; + F523577122BC9296BD8EE0CE12F60083 /* GradientFillNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = GradientFillNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift"; sourceTree = ""; }; + F6F841D18AE0AF8217EE79AB41270CB7 /* ShapeCompositionLayer.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShapeCompositionLayer.swift; path = "lottie-swift/src/Private/LayerContainers/CompLayers/ShapeCompositionLayer.swift"; sourceTree = ""; }; + F7859A70AD80F5EE84F8A1E09F775170 /* AnimationContext.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = AnimationContext.swift; path = "lottie-swift/src/Private/Utility/Helpers/AnimationContext.swift"; sourceTree = ""; }; + F78DA25FCEC460D8CC843AFE8142C7AA /* SingleValueProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = SingleValueProvider.swift; path = "lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift"; sourceTree = ""; }; + F8BD21673FFC8B94B75666E89828AE49 /* DottedLayerOutline.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DottedLayerOutline.swift; sourceTree = ""; }; + FA086F7539AC4E5F02932BFEFE999439 /* ColorExtension.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ColorExtension.swift; path = "lottie-swift/src/Private/Utility/Primitives/ColorExtension.swift"; sourceTree = ""; }; + FA8C51F4FCA0BA9FBEACD1D49CDE3DB2 /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Lottie.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FB260B2CFB4D44136DAFDEE8896FD4D7 /* FBSnapshotTestController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FBSnapshotTestController.m; path = FBSnapshotTestCase/FBSnapshotTestController.m; sourceTree = ""; }; + FF63CC66214F103A3BC99947249D063F /* NSAttributedString+attributes.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+attributes.swift"; sourceTree = ""; }; + FF74593614BCDB8539CCE3778D308DBB /* PassThroughOutputNode.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = PassThroughOutputNode.swift; path = "lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 50858D1F7655E526CC07ADD6928173BA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1F052FA064BB601F0C2B7BFC580CAB58 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 567BA33EE1AD9462F9BF5A84521C03F4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 650BD98AA163733C6F4C8A7472225845 /* Foundation.framework in Frameworks */, + F665DC648FD6987FC6227ABB928BB84F /* Lottie.framework in Frameworks */, + 4A9765EE896EB9C4886D44DE8F929A7E /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 75F27FC5AD7655163FDA472F01618C69 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 56961600EB511A0C4AB090E89D4CB31D /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + BE889A3E434498F6780A98614E0D69C7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8BB0093003D163A10821B533A0E03018 /* CoreGraphics.framework in Frameworks */, + FE107397E2A2ACFE036BC031711F665B /* Foundation.framework in Frameworks */, + E21932BEA2B0B11EF6AAF4AC67E8587D /* QuartzCore.framework in Frameworks */, + DA428DD4DD36CDDCB0C29B07DF0678F4 /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FDD1A2F6F0C7CD64F79B3E79FC562312 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 115B9F2A343D1880D138F41B9491C125 /* Foundation.framework in Frameworks */, + 86BC25FD2CC085B17A374500C747062D /* QuartzCore.framework in Frameworks */, + 82CE8E0DC98452F08E1A03D3CA91B763 /* UIKit.framework in Frameworks */, + 968B5318A745263F0D3A72023217CD12 /* XCTest.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 02C3E475EB5000FD139DFE475D8D2DE2 /* iOSSnapshotTestCase */ = { + isa = PBXGroup; + children = ( + D4DDDDB1956AB89C99EC9A4D469E2AAE /* Core */, + DF83EDFC75C8ECB456AB69024762D953 /* Support Files */, + 52D2F1BF961C4F85083DBFBA721B5C31 /* SwiftSupport */, + ); + name = iOSSnapshotTestCase; + path = iOSSnapshotTestCase; + sourceTree = ""; + }; + 110193C0E69EB8413E7891F4B34C53D3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + FA8C51F4FCA0BA9FBEACD1D49CDE3DB2 /* Lottie.framework */, + 61B89408C0658F9051C3B0F8069DB015 /* iOS */, + ); + name = Frameworks; + sourceTree = ""; + }; + 131342B7F20921E43E7FDFC761E9FC87 /* Support Files */ = { + isa = PBXGroup; + children = ( + 660020C1AFA0F23325D2F7774D74E2E8 /* StylableUIKit.modulemap */, + B775FC1798B4296803F6783FD3376AC7 /* StylableUIKit.xcconfig */, + 04DC669BD6778AD397B42156C910F8B4 /* StylableUIKit-dummy.m */, + BD66DD9A3F53F41AB7357C6F93E15F91 /* StylableUIKit-Info.plist */, + B8AC51CBABCD6078ED0CE35155F84C99 /* StylableUIKit-prefix.pch */, + 706D165B0ABC2FC1C23CFD348A8AD336 /* StylableUIKit-umbrella.h */, + ); + name = "Support Files"; + path = "Example/Pods/Target Support Files/StylableUIKit"; + sourceTree = ""; + }; + 143356B1DEB4B4D3C91863099A21029F /* Pods-StylableUIKit_Tests */ = { + isa = PBXGroup; + children = ( + 5C3A42D58E538C45A0E3710A8258ABE3 /* Pods-StylableUIKit_Tests.modulemap */, + 0DFAC9CF845B4FDD43602723506DC35C /* Pods-StylableUIKit_Tests-acknowledgements.markdown */, + D6BBC12BE51607207D2094DD5954C226 /* Pods-StylableUIKit_Tests-acknowledgements.plist */, + D5AB7868209B2165B93BA05999D6ECFE /* Pods-StylableUIKit_Tests-dummy.m */, + 4B0657703A0F7504A1F25C58C1D9BF97 /* Pods-StylableUIKit_Tests-frameworks.sh */, + 5E81A7D72B31C5BA1E93EADADB1B616B /* Pods-StylableUIKit_Tests-Info.plist */, + 666C1E81F957B007A1BDF46F2EC925B6 /* Pods-StylableUIKit_Tests-umbrella.h */, + ABF041F01910A296BDC6A7BC8E47580A /* Pods-StylableUIKit_Tests.debug.xcconfig */, + AB9D6DF74D13984EAACE4D124AC0E61E /* Pods-StylableUIKit_Tests.release.xcconfig */, + ); + name = "Pods-StylableUIKit_Tests"; + path = "Target Support Files/Pods-StylableUIKit_Tests"; + sourceTree = ""; + }; + 34289A59FD6C8323950A5AE9C24E07B2 /* Extensions */ = { + isa = PBXGroup; + children = ( + 325D97519E67A795F3551B6230A763A7 /* UIViewController+stylable.swift */, + ); + name = Extensions; + path = StylableUIKit/Classes/Extensions; + sourceTree = ""; + }; + 34BBE33026F270D49220D6C89FE1CD85 /* Pods-StylableUIKit_Example */ = { + isa = PBXGroup; + children = ( + 46803D5C7BFC0268CE09EF74C7B3D147 /* Pods-StylableUIKit_Example.modulemap */, + AB2EF7F49636832D0F4DD5E002BBDB50 /* Pods-StylableUIKit_Example-acknowledgements.markdown */, + 6237D2F145F83895362AE76B6871E050 /* Pods-StylableUIKit_Example-acknowledgements.plist */, + 628CD69AC056DC3C40D11D33902C0BF1 /* Pods-StylableUIKit_Example-dummy.m */, + D17E2B4C6A5C0CC67B45763B96C0B34A /* Pods-StylableUIKit_Example-frameworks.sh */, + B10AFF46F94F7D4D3314BCDDE3E2315E /* Pods-StylableUIKit_Example-Info.plist */, + E61C19688B479280970B6FD2A205DC18 /* Pods-StylableUIKit_Example-umbrella.h */, + CA3603248D245DD452606F3A00976216 /* Pods-StylableUIKit_Example.debug.xcconfig */, + 5B2EA56E964E82236F693F021B8B8B56 /* Pods-StylableUIKit_Example.release.xcconfig */, + ); + name = "Pods-StylableUIKit_Example"; + path = "Target Support Files/Pods-StylableUIKit_Example"; + sourceTree = ""; + }; + 35F5D7897AC2350A8C1D1D230BEDAD7F /* Support Files */ = { + isa = PBXGroup; + children = ( + DB310932F37EE63CEDCA51BB46650930 /* lottie-ios.modulemap */, + 860214E7F7E4AF5B029DA88AE2FA0B92 /* lottie-ios.xcconfig */, + 5E24B111C47498430ACE138534022C8A /* lottie-ios-dummy.m */, + 17D54C2D735D50C71C57CAF3C87F2BC6 /* lottie-ios-Info.plist */, + 881A3F2A8EACD17C1CC0FE61388C5451 /* lottie-ios-prefix.pch */, + 1432E944BF221B70237E5690F66D1C12 /* lottie-ios-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/lottie-ios"; + sourceTree = ""; + }; + 3C39E0C737E280D0D222EB2384E5BCB6 /* Core */ = { + isa = PBXGroup; + children = ( + FBD21AF32C92E7EF35DBD88D0644531A /* Assets */, + D6A12ADD71ADEA8757A60003F8496B38 /* Components */, + FF4BC63DE1A21F76C97D233B7C85051A /* Core */, + 34289A59FD6C8323950A5AE9C24E07B2 /* Extensions */, + FC032F9F0132D7709B137194D40433F9 /* LayerStyle */, + 8FBDE0027BC03E4C94D99E2D9BF4E641 /* TextStyle */, + 696ED014536ECBD18794A2B9DFE2163D /* Utils */, + ); + name = Core; + sourceTree = ""; + }; + 44C37201CBE97C13C07D7F1BA0BBEB77 /* Support Files */ = { + isa = PBXGroup; + children = ( + 7CDF7AC53AC4A8C4C842E372C70A7EA6 /* SwiftLint.xcconfig */, + ); + name = "Support Files"; + path = "../Target Support Files/SwiftLint"; + sourceTree = ""; + }; + 4F99BDC41C9D67DB1D97A87E4F7F653C /* Pod */ = { + isa = PBXGroup; + children = ( + 01EB995205ADE922F0632B1BB5A88A4D /* LICENSE */, + F229D8F4B48C7BCCE729612EC98C2A84 /* README.md */, + 500BA72426940EA7A34228112E0157A3 /* StylableUIKit.podspec */, + ); + name = Pod; + sourceTree = ""; + }; + 52D2F1BF961C4F85083DBFBA721B5C31 /* SwiftSupport */ = { + isa = PBXGroup; + children = ( + 5DDB6F9E60DAB454A31E1AF3BA57AF96 /* SwiftSupport.swift */, + ); + name = SwiftSupport; + sourceTree = ""; + }; + 5660F3E49EF0E31FE2D91319B45A3429 /* lottie-ios */ = { + isa = PBXGroup; + children = ( + 7E9C9BF5441DF87BE9CEEB2F11F664AA /* AnimatedButton.swift */, + E73751FF2E1F6450CC96A993FE154428 /* AnimatedControl.swift */, + 4C1DC73709D22F07D0974DAD1BA6DA64 /* AnimatedSwitch.swift */, + AE085E0E4A1732F66A6FEC23EDD35389 /* Animation.swift */, + 0173023FF29C54B5CFEC160D41095602 /* AnimationCacheProvider.swift */, + 059212762E12B4E2D6ED718877EAEFBA /* AnimationContainer.swift */, + F7859A70AD80F5EE84F8A1E09F775170 /* AnimationContext.swift */, + C189678585D4BF32CE1A187D7AA71A1A /* AnimationImageProvider.swift */, + E08ACDD9085A2184459735D9F3D5B8F8 /* AnimationKeypath.swift */, + 8A5C6CFB78EED6E185D796C256D5400D /* AnimationKeypathExtension.swift */, + 59999D236BEFA798575B8350387733B0 /* AnimationPublic.swift */, + AD07EAB31953CEBB71C40E2BA64EF2C6 /* AnimationSubview.swift */, + 296D3A00ACD93D4BD58E895DDA8423A9 /* AnimationTextProvider.swift */, + 22C2215C4369FA071D3AFC03B6A3571A /* AnimationTime.swift */, + 42B0665B63FAAA3E374E31FB3AF363AB /* AnimationView.swift */, + 05B176F8F8B70EB0A2FF285EFB2BDED1 /* AnimationViewInitializers.swift */, + 30FF05A76BC973E3DECDC6FDC2FDFEA3 /* AnimatorNode.swift */, + 0134D9EC77D58FC6DAE183BC69D420B4 /* AnimatorNodeDebugging.swift */, + 7A5923C4470A5F464DAE4DCF4C46D233 /* AnyNodeProperty.swift */, + 056E18E13B0889482A65AD1B916A32F4 /* AnyValueContainer.swift */, + 26035F0E7223633025143D3AFE4CE3D0 /* AnyValueProvider.swift */, + 3E53F0A109097AC14591182A3B6A781F /* Asset.swift */, + 50095AFC0B1D31EBF7D31E3C6384CE1B /* AssetLibrary.swift */, + 449BECFA6EE731CEB064E0E8049C0CB0 /* BezierPath.swift */, + 59DB72A6392CF91A3946729B5ECA7FEC /* BundleImageProvider.swift */, + C8546FB146D088111CDE959FAD883BC9 /* CGFloatExtensions.swift */, + 16610E5283F2F90D2F851FE38873A1B3 /* Color.swift */, + FA086F7539AC4E5F02932BFEFE999439 /* ColorExtension.swift */, + 87D471259EB7CA6E99D9214A5FC8455F /* ColorValueProvider.swift */, + D1737E80888FBCF794B968F5366D3DD4 /* CompatibleAnimationKeypath.swift */, + E3317FD44B5BB148D57C7C96F529D805 /* CompatibleAnimationView.swift */, + 6AFFF54AB654A31EE46626876FB788C7 /* CompositionLayer.swift */, + 3413469AB1BACA8E56CFF0C8BC7D21C3 /* CompositionLayersInitializer.swift */, + F1F377BC79A240E2BA4D14A9369B5E5C /* CompoundBezierPath.swift */, + C5DEB7DB872F1359172AD078498B9848 /* CurveVertex.swift */, + C29896A7720E01A21E52E4DC9AA85DBB /* DashPattern.swift */, + E904E6082E395DBF5AA17147A004A1FF /* Ellipse.swift */, + B29AD7E4CED07F0DAEA9F5BC27AC3D3C /* EllipseNode.swift */, + EBAB2A2F2B3660E1F6B6E7CC303EDCB2 /* FilepathImageProvider.swift */, + DFEC4B9505553AEF6E4A28B88B75A935 /* FillI.swift */, + CA35FA6F1384B93259E2AE248E7670C2 /* FillNode.swift */, + 9E46E60021235C1C35E4C0E7F590BEE6 /* FillRenderer.swift */, + 92293CB65933FEA609B06A47BFBFF47D /* FloatValueProvider.swift */, + AE449AA3FD1ED2D00D17C8EACE41F19B /* Font.swift */, + A87B74CEBBBE2A7CCADAC0C2E5381CA3 /* Glyph.swift */, + 16958CD6B85B544E75A18878DF385B53 /* GradientFill.swift */, + F523577122BC9296BD8EE0CE12F60083 /* GradientFillNode.swift */, + 84ECE15E56018B727E04624F9B65EB8E /* GradientFillRenderer.swift */, + 9EEEE5E0D9AFFD5FB171192255267A93 /* GradientStroke.swift */, + 81EA689EE3E3DEEAB8594A412D156E4D /* GradientStrokeNode.swift */, + 0B153F71EC21C615DCEEC792DA193874 /* GradientStrokeRenderer.swift */, + 3C379C8F9DB403AB40D5BE08B0A235BA /* GradientValueProvider.swift */, + B7CDB55A457AD7047859E76316FFAF90 /* Group.swift */, + 7FD0C667A020A7BD5CF271E6634028A7 /* GroupInterpolator.swift */, + DE360F166EC21B3DC9AACC5FB6F400F4 /* GroupNode.swift */, + 903CAA7B22CF6C2317DA99ACB4F8B407 /* GroupOutputNode.swift */, + 34B5078CDB82C7B6F7CBD88EB6767DE6 /* ImageAsset.swift */, + 55F216188BB94767F4F82D769F143299 /* ImageCompositionLayer.swift */, + A65D98766F19B77C36E9C7A2920CDE8C /* ImageLayerModel.swift */, + C4CBC699E993178A2C85F23E9A0ED5C5 /* Interpolatable.swift */, + 53470AF295ADA350FCE99BD1EE705E48 /* InterpolatableExtensions.swift */, + 13BDBB8978A1BAF9CA8A2E55449F06E9 /* InvertedMatteLayer.swift */, + 4AFADE56F7E421B19C6DAFDDC7243E70 /* ItemsExtension.swift */, + 8829B18BC10A81B9137665BB6E8D8DF2 /* KeyedDecodingContainerExtensions.swift */, + 8D90377977E1DC735DF1A7FFAFC9ECD3 /* Keyframe.swift */, + 9B5C1A28ABE2C9387BCDF70503107FD0 /* KeyframeExtensions.swift */, + 75511F820C463B56E94AA5BE6D304585 /* KeyframeGroup.swift */, + 40593815441D8E7CFD7CC5DBB0FF5016 /* KeyframeInterpolator.swift */, + 240B1C8668288F04700A77482E4B130C /* KeypathSearchable.swift */, + 1F05CE1250B392526FB5C71C7588F4E5 /* LayerDebugging.swift */, + 8C557D0586B1DF168D5316404C6002A0 /* LayerImageProvider.swift */, + DE5FAEF44E1F75BA4EC6BBD7AA7A8CE9 /* LayerModel.swift */, + 70EDD87729BCAF997AD6CFE03A747310 /* LayerTextProvider.swift */, + 74BB0B4F9099E413374D0C27C5AA58B2 /* LayerTransformNode.swift */, + 3CE854B933C605525C76AA9ED69ED2CA /* LottieView.swift */, + D0977C96A168161F86A9F4F3A36D6854 /* LRUAnimationCache.swift */, + 0D711A0C67AB45CD28C6B422DED74FD7 /* Marker.swift */, + 1A3424A129DCE4AD48D0A80B90B9E7A8 /* Mask.swift */, + 8EE89BF228FAD5CEAE18DA5AD43A9D79 /* MaskContainerLayer.swift */, + B112EE14E786D76E7E681855F45BA3E2 /* MathKit.swift */, + C702734BB61421EED37FA5E403B6FB4E /* Merge.swift */, + 08AAC5FD988A62CC995CFFDFC1C7D408 /* NodeProperty.swift */, + CA53E107FD0D2CD5C8EBBCDB59078FA3 /* NodePropertyMap.swift */, + 3A945B41761BB320F124276303928C76 /* NullCompositionLayer.swift */, + FF74593614BCDB8539CCE3778D308DBB /* PassThroughOutputNode.swift */, + 3314434FB57226915CB1C2F5C36B4C79 /* PathElement.swift */, + 506B8B95965B0B2F67DF5960FCC4009D /* PathNode.swift */, + 015404149AC61C24E561E38534B0C26D /* PathOutputNode.swift */, + F47BF1972756382AA4E72A9013CE4875 /* PointValueProvider.swift */, + C79136739E4AD8EC78782F1A41E0A90C /* PolygonNode.swift */, + 8ED089043178D008E6428E59FB8B64FF /* PrecompAsset.swift */, + 9710FDAC9E77F5E9D1696B02279A3515 /* PreCompLayerModel.swift */, + 1790839B7841B5EB80A0630747E0F088 /* PreCompositionLayer.swift */, + E1DF3A4CD2B86E861188639DCE7D905D /* Rectangle.swift */, + 40202ADE4FA230A38CAEDA9C567235D6 /* RectNode.swift */, + 49BA712C28E8BC1BBA9F078506A63D75 /* RenderNode.swift */, + 52E4059B25E5E09B1D555F126D162516 /* Repeater.swift */, + 41411EDED41281360AD68D6AC9B44C45 /* Shape.swift */, + F6F841D18AE0AF8217EE79AB41270CB7 /* ShapeCompositionLayer.swift */, + 7281E5092AE391FE3AE09B65AEDEDFE4 /* ShapeContainerLayer.swift */, + 7042929470234AF28FFD978E47EA73A1 /* ShapeItem.swift */, + 76420F2B0C49DA9024D9D46579527EC2 /* ShapeLayerModel.swift */, + 3AF4880DEB53CB74804B05BAA3C4E1B2 /* ShapeNode.swift */, + 505127C28A6BBF7640A38C2BCB5459AC /* ShapeRenderLayer.swift */, + 4B25A197D8A52F7AC9644A0B438F3E20 /* ShapeTransform.swift */, + F78DA25FCEC460D8CC843AFE8142C7AA /* SingleValueProvider.swift */, + 14F77D7E3E3B47851B85AFB73F04FCF8 /* SizeValueProvider.swift */, + 528B7CCA43B98371BC840A1188D67C1B /* SolidCompositionLayer.swift */, + D594A6A8FA244CC5DA997B1EF87235F7 /* SolidLayerModel.swift */, + 1B492A9194AFA6526391DCDEB4DA9A98 /* Star.swift */, + 0F2C129964B166867AC57FA824080634 /* StarNode.swift */, + 4E5BFA8DAE2D70ED4DC3F1D033511089 /* StringExtensions.swift */, + C7C38DB5E77415614E3DD213C71C4A3D /* Stroke.swift */, + 23ECA3097E04D6FEE2EA9DF2DE50073D /* StrokeNode.swift */, + 5A0272C6413D935F85D9A0605B9AFF72 /* StrokeRenderer.swift */, + E7B1A46187451C718EC7C10F13185358 /* TextAnimator.swift */, + 9F166374728BA61385CE0F99FB290FED /* TextAnimatorNode.swift */, + 15B79F3C90574234CB1D9D45F9B86DF6 /* TextCompositionLayer.swift */, + A92BFBF4D00FD8CC942963341EF06370 /* TextDocument.swift */, + 372DD8BAE931ECCF24302E80BE1995BA /* TextLayerModel.swift */, + CD6EF487F05397C41F9EE69421DB0E7A /* Transform.swift */, + D761830421837FFE0112C277FEE013E0 /* Trim.swift */, + 19E73EE14706ABCEDD110E2C1E9152E2 /* TrimPathNode.swift */, + 2AA8EA53D3E8A160742FE010D2006CF2 /* UIColorExtension.swift */, + 8E8ECD6CD13CDFE40BD582BB58D0949F /* ValueContainer.swift */, + 4CCC9DA88DFBEBD76B336EF3FA4D4E81 /* Vectors.swift */, + 4CF07B406B562208ED74701CE1E184DC /* VectorsExtensions.swift */, + 35F5D7897AC2350A8C1D1D230BEDAD7F /* Support Files */, + ); + name = "lottie-ios"; + path = "lottie-ios"; + sourceTree = ""; + }; + 61B89408C0658F9051C3B0F8069DB015 /* iOS */ = { + isa = PBXGroup; + children = ( + D4615A1BBC5756B54B4FC9B8FF7DA9A2 /* CoreGraphics.framework */, + D3357625801F09413816ACD60A5F1A64 /* Foundation.framework */, + A460D115DDD71343129CE0146A49D47F /* QuartzCore.framework */, + 3594C8B9A6847185E0695C678BFA3CBA /* UIKit.framework */, + 5062DC1B95F4E6572B0A799BC07ADB66 /* XCTest.framework */, + ); + name = iOS; + sourceTree = ""; + }; + 696ED014536ECBD18794A2B9DFE2163D /* Utils */ = { + isa = PBXGroup; + children = ( + FF63CC66214F103A3BC99947249D063F /* NSAttributedString+attributes.swift */, + ); + name = Utils; + path = StylableUIKit/Classes/Utils; + sourceTree = ""; + }; + 716257438614323B5C7B45E635E46772 /* Lottie */ = { + isa = PBXGroup; + children = ( + C4F0404567BD464504B5FF43E353596F /* Lottie+StylistAnimatedAsset.swift */, + F10CC13A9FC8FF8B54E28CC46A517B24 /* String+Lottie.swift */, + ); + name = Lottie; + sourceTree = ""; + }; + 762C35794D429297CB3C88A522FC6939 /* Targets Support Files */ = { + isa = PBXGroup; + children = ( + 34BBE33026F270D49220D6C89FE1CD85 /* Pods-StylableUIKit_Example */, + 143356B1DEB4B4D3C91863099A21029F /* Pods-StylableUIKit_Tests */, + ); + name = "Targets Support Files"; + sourceTree = ""; + }; + 79BC83E09A31B35A06593F10D1EDC5C9 /* LayerOutline */ = { + isa = PBXGroup; + children = ( + F8BD21673FFC8B94B75666E89828AE49 /* DottedLayerOutline.swift */, + 6AEE7F581515D14F487EBE22701CC423 /* LinearLayerOutline.swift */, + ); + name = LayerOutline; + path = LayerOutline; + sourceTree = ""; + }; + 8FBDE0027BC03E4C94D99E2D9BF4E641 /* TextStyle */ = { + isa = PBXGroup; + children = ( + BB07439868C653FACEF5A4213A0DA351 /* NSAttributedString+textTransform.swift */, + 1160673E50C791C5B8B1B3825CAD7E46 /* TextStyle.swift */, + 488609954ABCCD4E70F84384147D452A /* TextTransform.swift */, + ); + name = TextStyle; + path = StylableUIKit/Classes/TextStyle; + sourceTree = ""; + }; + B117F278F2F154D1D873FB27A8778557 /* StylableUIKit */ = { + isa = PBXGroup; + children = ( + 3C39E0C737E280D0D222EB2384E5BCB6 /* Core */, + 716257438614323B5C7B45E635E46772 /* Lottie */, + 4F99BDC41C9D67DB1D97A87E4F7F653C /* Pod */, + 131342B7F20921E43E7FDFC761E9FC87 /* Support Files */, + ); + name = StylableUIKit; + path = ../..; + sourceTree = ""; + }; + B7F19629862EC73EE9A5F5C1C3C3AF8C /* Pods */ = { + isa = PBXGroup; + children = ( + 02C3E475EB5000FD139DFE475D8D2DE2 /* iOSSnapshotTestCase */, + 5660F3E49EF0E31FE2D91319B45A3429 /* lottie-ios */, + DB5B2B0E201AA3BE1426E974EADA02E3 /* SwiftLint */, + ); + name = Pods; + sourceTree = ""; + }; + CF1408CF629C7361332E53B88F7BD30C = { + isa = PBXGroup; + children = ( + 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, + D1272AFFBD66F2782C864CDA4F3CBF8E /* Development Pods */, + 110193C0E69EB8413E7891F4B34C53D3 /* Frameworks */, + B7F19629862EC73EE9A5F5C1C3C3AF8C /* Pods */, + D1E82A3D60449FBA3B4BB65D3057E3BA /* Products */, + 762C35794D429297CB3C88A522FC6939 /* Targets Support Files */, + ); + sourceTree = ""; + }; + D1272AFFBD66F2782C864CDA4F3CBF8E /* Development Pods */ = { + isa = PBXGroup; + children = ( + B117F278F2F154D1D873FB27A8778557 /* StylableUIKit */, + ); + name = "Development Pods"; + sourceTree = ""; + }; + D1E82A3D60449FBA3B4BB65D3057E3BA /* Products */ = { + isa = PBXGroup; + children = ( + B91B00F8BE943329D633234BD67AE0AC /* FBSnapshotTestCase.framework */, + 51BA97E8B5085EFFB47BC9C0B785CEA7 /* Lottie.framework */, + 89B162F41650676AB69326E9D142FEF9 /* Pods_StylableUIKit_Example.framework */, + 6C7909A8CAECD96FA3C42C3BF2163AEA /* Pods_StylableUIKit_Tests.framework */, + 5AB06DE1BB4AA0944378CDB8531D24E8 /* StylableUIKit.framework */, + ); + name = Products; + sourceTree = ""; + }; + D4DDDDB1956AB89C99EC9A4D469E2AAE /* Core */ = { + isa = PBXGroup; + children = ( + 5066FE2E3AE6FAF3BE69D583B7A6C6F9 /* FBSnapshotTestCase.h */, + B7E25BBBA432699014E7E3D7B5009B6F /* FBSnapshotTestCase.m */, + C073ABA222866A71C0639369CAE1AD6B /* FBSnapshotTestCasePlatform.h */, + BD39857568FA4B18786510BBF6FEC920 /* FBSnapshotTestCasePlatform.m */, + 249C74BA0B81A1F25BBCB05327F51673 /* FBSnapshotTestController.h */, + FB260B2CFB4D44136DAFDEE8896FD4D7 /* FBSnapshotTestController.m */, + DC3247B49FFE60141DCC6E3F09E8F587 /* UIImage+Compare.h */, + DE17BCF149D5CC4FCA24577AE281FB6C /* UIImage+Compare.m */, + 32EA0C94258313A5F8DF98FA1FB33387 /* UIImage+Diff.h */, + F1E9B5B40C9D55CE2EB9E3E3B3489B43 /* UIImage+Diff.m */, + DF18BEBE83A4D7A09612AB19BA9A97E2 /* UIImage+Snapshot.h */, + 0DA78CD813CD59F35615BDA1404B4D0E /* UIImage+Snapshot.m */, + ); + name = Core; + sourceTree = ""; + }; + D6A12ADD71ADEA8757A60003F8496B38 /* Components */ = { + isa = PBXGroup; + children = ( + 198CA2BF65D1C3825C97C094CBE60C3E /* StylableButton.swift */, + 8032A778CA1ABD99A33C054A84FCB1E6 /* StylableLabel.swift */, + 3A17BE7AB51D35E9119DDD4E562F3555 /* StylableTextField.swift */, + 2DB15E791FF182E127BA1D93B8FE1FF3 /* StylableTextView.swift */, + ); + name = Components; + path = StylableUIKit/Classes/Components; + sourceTree = ""; + }; + DB5B2B0E201AA3BE1426E974EADA02E3 /* SwiftLint */ = { + isa = PBXGroup; + children = ( + 44C37201CBE97C13C07D7F1BA0BBEB77 /* Support Files */, + ); + name = SwiftLint; + path = SwiftLint; + sourceTree = ""; + }; + DF83EDFC75C8ECB456AB69024762D953 /* Support Files */ = { + isa = PBXGroup; + children = ( + 731B7EA6D2C410A3EE3DEC51F41645D9 /* iOSSnapshotTestCase.modulemap */, + B504C6C300126613F44985F63DB93EDE /* iOSSnapshotTestCase.xcconfig */, + 22192CA128113BBDAFB32945BD55003D /* iOSSnapshotTestCase-dummy.m */, + 373C1D7B4F18E6C8F823250D36BAAE7D /* iOSSnapshotTestCase-Info.plist */, + C9C0457172E1E7873D05C587813F3A7E /* iOSSnapshotTestCase-prefix.pch */, + BB3A3B4251002074BEE7975C48E66D95 /* iOSSnapshotTestCase-umbrella.h */, + ); + name = "Support Files"; + path = "../Target Support Files/iOSSnapshotTestCase"; + sourceTree = ""; + }; + FBD21AF32C92E7EF35DBD88D0644531A /* Assets */ = { + isa = PBXGroup; + children = ( + EFCB1066BB242F334731326228EA88F5 /* AnimatedAsset.swift */, + A9ECF02113DDF0ECE97C0C223254D941 /* Asset.swift */, + ); + name = Assets; + path = StylableUIKit/Classes/Assets; + sourceTree = ""; + }; + FC032F9F0132D7709B137194D40433F9 /* LayerStyle */ = { + isa = PBXGroup; + children = ( + B8E5FA8071F53D149BC3628C85DA96D8 /* LayerFill.swift */, + 3FDCAFFD83DED18E6813044B733CB438 /* LayerStyle.swift */, + EA2AF4F28E5D8D7122AD290D066D2185 /* RestrictedLayerStylable.swift */, + 79BC83E09A31B35A06593F10D1EDC5C9 /* LayerOutline */, + ); + name = LayerStyle; + path = StylableUIKit/Classes/LayerStyle; + sourceTree = ""; + }; + FF4BC63DE1A21F76C97D233B7C85051A /* Core */ = { + isa = PBXGroup; + children = ( + 946099FE4096FE33731A06630DAC5658 /* Stylist.swift */, + A8A6A022DB3ABDAB4ADB2482318EFF81 /* StylistIdentifier.swift */, + 18F758CF72BA6373498264D7BD8AB2D9 /* VariantStyleProvider.swift */, + ); + name = Core; + path = StylableUIKit/Classes/Core; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 9494C8D834CB7D26C8A1904909D79EF0 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 6ED0F83B3E5FA3B18EB93D937E17CCBF /* lottie-ios-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A1F0BF3267CFE49DA92D818F3B3A46DA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 3B540469AF7772C156A3AD0CC6184734 /* FBSnapshotTestCase.h in Headers */, + 6543CE32DD97E4F24919522FE4E79A94 /* FBSnapshotTestCasePlatform.h in Headers */, + 796D4CB4F4AB8ECBBD931B099A74E833 /* FBSnapshotTestController.h in Headers */, + C930BCB1B395E19ABBE9D4D491867353 /* iOSSnapshotTestCase-umbrella.h in Headers */, + 2643963F8600F16366AF89ABF6D91F82 /* UIImage+Compare.h in Headers */, + 09F3021D3323D0B64812EE10886827AB /* UIImage+Diff.h in Headers */, + 3CE95FD1D722818A524677118DF557BA /* UIImage+Snapshot.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A7729F6407783BBCDF3B3444B1C717C5 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 635D998A41DC23D8DE3CBC222531A100 /* StylableUIKit-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AB724EB099A60408F59F592D99CD4ED8 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 052927C8BDC535147D14CEB79D9DAE1D /* Pods-StylableUIKit_Tests-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + ABFBA6FEA61934E0941DD42417A89BD8 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + B57C560E9D79B963A5F9885E88667E8D /* Pods-StylableUIKit_Example-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 0B967D7F8561D42493EE289EC8D450D1 /* lottie-ios */ = { + isa = PBXNativeTarget; + buildConfigurationList = 70218D2F5AD28E03059A8D767F4C83B8 /* Build configuration list for PBXNativeTarget "lottie-ios" */; + buildPhases = ( + 9494C8D834CB7D26C8A1904909D79EF0 /* Headers */, + 75BB11ED607FE0B814991A92562187DE /* Sources */, + BE889A3E434498F6780A98614E0D69C7 /* Frameworks */, + 8C3311483D862458890E0F57A6E8B1FC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "lottie-ios"; + productName = "lottie-ios"; + productReference = 51BA97E8B5085EFFB47BC9C0B785CEA7 /* Lottie.framework */; + productType = "com.apple.product-type.framework"; + }; + 3FCE3490DD43EB855E2C738A2D8BCCA4 /* Pods-StylableUIKit_Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 86694221EA19B62158E4E994DB4E051B /* Build configuration list for PBXNativeTarget "Pods-StylableUIKit_Example" */; + buildPhases = ( + ABFBA6FEA61934E0941DD42417A89BD8 /* Headers */, + F0F16DFC2B9982FA5FB5F1D1EF124547 /* Sources */, + 50858D1F7655E526CC07ADD6928173BA /* Frameworks */, + DE175F4523884EFB8A45A900BB7768E9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 6E33E54B3012498865093501EB95588E /* PBXTargetDependency */, + 2EB9F6D8B52DB3538D97956AAD6869A7 /* PBXTargetDependency */, + A5BBD623DD98CEC95B57D47638905DD7 /* PBXTargetDependency */, + ); + name = "Pods-StylableUIKit_Example"; + productName = "Pods-StylableUIKit_Example"; + productReference = 89B162F41650676AB69326E9D142FEF9 /* Pods_StylableUIKit_Example.framework */; + productType = "com.apple.product-type.framework"; + }; + 9CB1EC2E82C1BCB5616EC51DF07B9023 /* Pods-StylableUIKit_Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0ECF5A7BD8E2157C431BD6C20DA850C7 /* Build configuration list for PBXNativeTarget "Pods-StylableUIKit_Tests" */; + buildPhases = ( + AB724EB099A60408F59F592D99CD4ED8 /* Headers */, + AB6C40025318CD8E0CABA790C4FF28B8 /* Sources */, + 75F27FC5AD7655163FDA472F01618C69 /* Frameworks */, + DD79F2E5E2AD335CFA38766492B8FEF4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + BB3B17D53E0F28A87F357D7E78BF1C4D /* PBXTargetDependency */, + 950B4EA92FB88F31C9F8094F332BEB83 /* PBXTargetDependency */, + ); + name = "Pods-StylableUIKit_Tests"; + productName = "Pods-StylableUIKit_Tests"; + productReference = 6C7909A8CAECD96FA3C42C3BF2163AEA /* Pods_StylableUIKit_Tests.framework */; + productType = "com.apple.product-type.framework"; + }; + C393038B0BEF088C1B93E6528005862D /* iOSSnapshotTestCase */ = { + isa = PBXNativeTarget; + buildConfigurationList = A2580343E01EF46F4A1E600895C7753A /* Build configuration list for PBXNativeTarget "iOSSnapshotTestCase" */; + buildPhases = ( + A1F0BF3267CFE49DA92D818F3B3A46DA /* Headers */, + FA38C61E9FAB25560B08593BFCCD050F /* Sources */, + FDD1A2F6F0C7CD64F79B3E79FC562312 /* Frameworks */, + B8848E61F4166ABF3DCCE39D45C21A1B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iOSSnapshotTestCase; + productName = iOSSnapshotTestCase; + productReference = B91B00F8BE943329D633234BD67AE0AC /* FBSnapshotTestCase.framework */; + productType = "com.apple.product-type.framework"; + }; + D15B2DA9CE4FEB60C2768C7CE05B9032 /* StylableUIKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1354D69273ADFA00854B713F528A1211 /* Build configuration list for PBXNativeTarget "StylableUIKit" */; + buildPhases = ( + A7729F6407783BBCDF3B3444B1C717C5 /* Headers */, + 5C7B9C3A736109C21FF9E108EC736E8F /* Sources */, + 567BA33EE1AD9462F9BF5A84521C03F4 /* Frameworks */, + 97404EADFB25440BAE6FAF005CE185A6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 85AAB8A1728B54ACFF4FC5F0C1A3FE9D /* PBXTargetDependency */, + ); + name = StylableUIKit; + productName = StylableUIKit; + productReference = 5AB06DE1BB4AA0944378CDB8531D24E8 /* StylableUIKit.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BFDFE7DC352907FC980B868725387E98 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1100; + LastUpgradeCheck = 1100; + }; + buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = CF1408CF629C7361332E53B88F7BD30C; + productRefGroup = D1E82A3D60449FBA3B4BB65D3057E3BA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C393038B0BEF088C1B93E6528005862D /* iOSSnapshotTestCase */, + 0B967D7F8561D42493EE289EC8D450D1 /* lottie-ios */, + 3FCE3490DD43EB855E2C738A2D8BCCA4 /* Pods-StylableUIKit_Example */, + 9CB1EC2E82C1BCB5616EC51DF07B9023 /* Pods-StylableUIKit_Tests */, + D15B2DA9CE4FEB60C2768C7CE05B9032 /* StylableUIKit */, + 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8C3311483D862458890E0F57A6E8B1FC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97404EADFB25440BAE6FAF005CE185A6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B8848E61F4166ABF3DCCE39D45C21A1B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DD79F2E5E2AD335CFA38766492B8FEF4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DE175F4523884EFB8A45A900BB7768E9 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 5C7B9C3A736109C21FF9E108EC736E8F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CF4EDBBD1FAF1C2A989E9BC0C9516E2C /* AnimatedAsset.swift in Sources */, + F9D2B4E0047159BF0F67002264A41A70 /* Asset.swift in Sources */, + 1A1E93B6CF3867EE7DC553FE20A7B917 /* DottedLayerOutline.swift in Sources */, + 9FC6E9E8AB3284D5F7C7E7A869E083C3 /* LayerFill.swift in Sources */, + 29C99C292ED4895594699F4615CB03F1 /* LayerStyle.swift in Sources */, + 56E99698157F624BC79E63C876B479A6 /* LinearLayerOutline.swift in Sources */, + 6B4027FDB9B152959D31DA5CF5C421B3 /* Lottie+StylistAnimatedAsset.swift in Sources */, + B3564C0CC587E19E8CD186F51C93E78A /* NSAttributedString+attributes.swift in Sources */, + 5CFD587F8709F0968C89C8F574F542C9 /* NSAttributedString+textTransform.swift in Sources */, + 021AB92728510E298CC54FF5E7AC371C /* RestrictedLayerStylable.swift in Sources */, + 476C4EBD027F5DD6165DA8DE9C437459 /* String+Lottie.swift in Sources */, + 1F16C28936A130072E2A7068BF776B52 /* StylableButton.swift in Sources */, + 8E8B5D05EE35209638D332BA81F31786 /* StylableLabel.swift in Sources */, + 8C3A4D21C84C1702C3A501D268D3C75E /* StylableTextField.swift in Sources */, + B9AB73B70AB1022258D56948EC3BB2E4 /* StylableTextView.swift in Sources */, + D1288CB0DF1D6EC64AFFECA261FF9D91 /* StylableUIKit-dummy.m in Sources */, + 1C5FCE5F245C5C64E3AC16703F625C7E /* Stylist.swift in Sources */, + CD1ACB8AD0A8E96A607F3F4790413B0C /* StylistIdentifier.swift in Sources */, + 803A7BFA6853A4DEB38484080633622E /* TextStyle.swift in Sources */, + 07A557663FA65416FE893CC362EAAA79 /* TextTransform.swift in Sources */, + 8CA0E9EE027CB96F81C15F9C08622A11 /* UIViewController+stylable.swift in Sources */, + 9D8842EBA687733DA232A097038639F2 /* VariantStyleProvider.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 75BB11ED607FE0B814991A92562187DE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8E4CBBA93AFAB0953F7149862D37FA9E /* AnimatedButton.swift in Sources */, + 368B06D8C82325E5547E88A18840EBCF /* AnimatedControl.swift in Sources */, + B088422F77B6924629DF438D0956CBD8 /* AnimatedSwitch.swift in Sources */, + A2BDC46F2D787618D6021DB4A5899AA0 /* Animation.swift in Sources */, + C2E9F88322FC6CD78D82A0442FF76945 /* AnimationCacheProvider.swift in Sources */, + F936DB9595933867F99ADCF839C6480E /* AnimationContainer.swift in Sources */, + 085EBC88244CFA42A128190C67AEE52A /* AnimationContext.swift in Sources */, + AF29C5AB251934C18E7F83E631D5D9C0 /* AnimationImageProvider.swift in Sources */, + 6FE1CB5ABFB5718B314AAADD41EC7908 /* AnimationKeypath.swift in Sources */, + B762BE0B0FD3DB84BD55B3EB21559426 /* AnimationKeypathExtension.swift in Sources */, + 9F79284FB1127D33E602EE78C41F48D7 /* AnimationPublic.swift in Sources */, + 10042BC1EFD6FB13EAC2499381F0FD66 /* AnimationSubview.swift in Sources */, + B279388B409EC8A801F40303D1A62C17 /* AnimationTextProvider.swift in Sources */, + 2C4F68C959D0529E34AFA6BFAFE42FE6 /* AnimationTime.swift in Sources */, + 95D5EFDCF6B26A9FB579F3EFF9EB9089 /* AnimationView.swift in Sources */, + FE6DD102461FCE23B54F090EB3097202 /* AnimationViewInitializers.swift in Sources */, + 1A943B62A9FD1C4040E404C0433CC0C7 /* AnimatorNode.swift in Sources */, + 30181F2F621BBD0A4D81B529B34EE504 /* AnimatorNodeDebugging.swift in Sources */, + 0904AA51109A23AF7E16612B35FF1001 /* AnyNodeProperty.swift in Sources */, + 6A19DD39E6347AC91865C1E8202E6049 /* AnyValueContainer.swift in Sources */, + 8BCBAF0C43F1BA9273A06CE4FD8C9BBB /* AnyValueProvider.swift in Sources */, + 2F55B79797DE4F1C093EB3685ABF6FCC /* Asset.swift in Sources */, + FEA26BC97107438875E472742974F29F /* AssetLibrary.swift in Sources */, + 66B18DFA74A7385B92CF408B3A6E2421 /* BezierPath.swift in Sources */, + 05195E7FF4C60B1F0459D1320AE7F87C /* BundleImageProvider.swift in Sources */, + 8E61ABC79616CB1CC563C854657B8089 /* CGFloatExtensions.swift in Sources */, + CF1D2DFC1BAEBCE04E9C8B49805B3F24 /* Color.swift in Sources */, + 0527DC882E09A33967F98A0EEFE25DD4 /* ColorExtension.swift in Sources */, + 612D819D2BEC1DFC7DEC6710CF0F7E7B /* ColorValueProvider.swift in Sources */, + 8D181C938FF7B5B5ED1570D3C5C88E16 /* CompatibleAnimationKeypath.swift in Sources */, + 9FC2F90D16562C099A4BB2F4541583E6 /* CompatibleAnimationView.swift in Sources */, + 92526E403B78182E1226E1B08DE8E667 /* CompositionLayer.swift in Sources */, + 7EE2F5E99566868EA0FC8BD0A0FCB611 /* CompositionLayersInitializer.swift in Sources */, + DE4F3D06F5ADD538AB97AF3D35982271 /* CompoundBezierPath.swift in Sources */, + 06DD3917D477FB37B67F0B214FED18C4 /* CurveVertex.swift in Sources */, + 495B38BD12AB86618A5D1D733908920B /* DashPattern.swift in Sources */, + 89CB900EF33224231DA340B651E38612 /* Ellipse.swift in Sources */, + B4E93DFF079361B23C2964ECF09A8F01 /* EllipseNode.swift in Sources */, + 96EA93D5A758302CF9B6CEC107542917 /* FilepathImageProvider.swift in Sources */, + 4DEFDD1659B6A1C9996799FFAAAA6391 /* FillI.swift in Sources */, + 5E305C261973A96B7421411F12F9F7BB /* FillNode.swift in Sources */, + 207F4D1CA99D5F6AB826F5C70AB5B625 /* FillRenderer.swift in Sources */, + 0C625A3F10C94671FB94C8971211A393 /* FloatValueProvider.swift in Sources */, + 3E57C81B7BAA0362BC1BCE4042415C14 /* Font.swift in Sources */, + CC2044231C448FD44A4E9AB8654FC52E /* Glyph.swift in Sources */, + CF5AF72858427D88437435E8DBAD61DA /* GradientFill.swift in Sources */, + 9BCBBD1AB5C9742C9F2D07EDA72BD0EF /* GradientFillNode.swift in Sources */, + E03A337E4F131D3C0E3F13AE8CF2FD90 /* GradientFillRenderer.swift in Sources */, + A62E62FAA565DE00C72FAA6471E85424 /* GradientStroke.swift in Sources */, + 8B528BFD60CE6C973F5AEB7AC57AFAC5 /* GradientStrokeNode.swift in Sources */, + 124AC9CF9318BA43602339B0E6BBA1DF /* GradientStrokeRenderer.swift in Sources */, + 272A60CDD69F3DEA65B406D54E7D4E8C /* GradientValueProvider.swift in Sources */, + C040B2F9389B59FB472EE18DF6DD2858 /* Group.swift in Sources */, + D78FFCC5022ECA523E5C9BCCB7542798 /* GroupInterpolator.swift in Sources */, + 42D78ECA8D9956F51BF7A7882A40C2B2 /* GroupNode.swift in Sources */, + 61ECDE04C5946A030B22457C613FDD25 /* GroupOutputNode.swift in Sources */, + D7C952F8235974A8CB26EB1C3EEBA93A /* ImageAsset.swift in Sources */, + 8962113B149FE8ACE1344354AD6DD805 /* ImageCompositionLayer.swift in Sources */, + ED7D0C5A3982EF5FA1024A247FE717D4 /* ImageLayerModel.swift in Sources */, + 95A0803B1B2F4A67FFA5476F7533EBB6 /* Interpolatable.swift in Sources */, + D124116FFCE71466B01BC3E9C7EF9804 /* InterpolatableExtensions.swift in Sources */, + B2D2699AADE6EFDA74745F3203E4CDA0 /* InvertedMatteLayer.swift in Sources */, + 65AF7EFA2C84C6EC1FBE3A5200F37DE7 /* ItemsExtension.swift in Sources */, + 548B4638EB0C24C603B616586BD50503 /* KeyedDecodingContainerExtensions.swift in Sources */, + EA0B889C017C171FB00B983AEF5D02B0 /* Keyframe.swift in Sources */, + 8B9F8F56D66F35325369A9CFE9E59C54 /* KeyframeExtensions.swift in Sources */, + 6B646E0927489EC259F20E457EA0C8CB /* KeyframeGroup.swift in Sources */, + 763A6C2CD77715BEC9413619706D78F3 /* KeyframeInterpolator.swift in Sources */, + 570D2238436AE876843CF195B7CA1FE9 /* KeypathSearchable.swift in Sources */, + CE83D63C6E2F9C5955A2188A17148939 /* LayerDebugging.swift in Sources */, + 5458E427A06FB786456932A3D176BCBC /* LayerImageProvider.swift in Sources */, + 0D25866550309DA89CF18F232746CB15 /* LayerModel.swift in Sources */, + A7B8B90C6AC44CFB1C1085E36DDB90D4 /* LayerTextProvider.swift in Sources */, + BA911EC932D20CFAB29511E799E44956 /* LayerTransformNode.swift in Sources */, + E17503C408B826887EC3C3FF3A04768A /* lottie-ios-dummy.m in Sources */, + D687201AA6DD0F6F7CE235D803F0DDD2 /* LottieView.swift in Sources */, + 87842D6380CD5BC81AC53DC4BCED9F3E /* LRUAnimationCache.swift in Sources */, + 7463B2B6601F367B1D308EC7A191CB3E /* Marker.swift in Sources */, + 02F02960A3A126D25ACE1E111A6DD615 /* Mask.swift in Sources */, + 6D6EAFAB7121B03432865FF5505519C2 /* MaskContainerLayer.swift in Sources */, + 167D7AADECFABC8D83FA56D1AAD8DFCD /* MathKit.swift in Sources */, + 200C96F5C1D72F89B06FEEC27273018B /* Merge.swift in Sources */, + 5C28BB9553F94CED0FA4065BBF5090CA /* NodeProperty.swift in Sources */, + 0D4A923376019329CE9FD1ED19C98E76 /* NodePropertyMap.swift in Sources */, + D376CA1F6421CF60E660C40DB4927865 /* NullCompositionLayer.swift in Sources */, + AABF54BC7ADD4805106D2B8D9D3164B6 /* PassThroughOutputNode.swift in Sources */, + 3C5BA865C37EE7C7E74B13FE0FBDB8D9 /* PathElement.swift in Sources */, + BA5778813E5E439FEDFAD5747E37AA69 /* PathNode.swift in Sources */, + F1FD2D2C38EFF42C72FFB64341DC7ABA /* PathOutputNode.swift in Sources */, + 0909768F839A1CB30B6766AEF3B25847 /* PointValueProvider.swift in Sources */, + D4E17FF709E26EAE084EE9F3DBD347F2 /* PolygonNode.swift in Sources */, + 253DC4C2214CF59DD2394B67FE5602F5 /* PrecompAsset.swift in Sources */, + 0715914E00A466DC249E0ADD1EF4A7B3 /* PreCompLayerModel.swift in Sources */, + A31282B9F906F135C8C26AAD07547E76 /* PreCompositionLayer.swift in Sources */, + 2427FA080A184DC70FC29692B2C2F1F8 /* Rectangle.swift in Sources */, + FE37D243E4D61BC10FC513CFBF0C1F40 /* RectNode.swift in Sources */, + 67DFAD4C9A2F7BE4CD1A5C475B3BB59F /* RenderNode.swift in Sources */, + 08449447FF2F8862406237FF9F0E2EA0 /* Repeater.swift in Sources */, + E7752C34B6B6A38FBD7B902DFBD77C8C /* Shape.swift in Sources */, + CF1B291CC2629267E5900A3B25AB7081 /* ShapeCompositionLayer.swift in Sources */, + 8635C02F65D3BA9468647773820AF926 /* ShapeContainerLayer.swift in Sources */, + B35B6452EECACAB7769C4303372E6004 /* ShapeItem.swift in Sources */, + 54C6B939FC97701D061C97E7A9BC7290 /* ShapeLayerModel.swift in Sources */, + AEA220D845FEB4CEA18D3125D0BE349E /* ShapeNode.swift in Sources */, + 2B4F8C3A106618349345FFDBBFD4732E /* ShapeRenderLayer.swift in Sources */, + 1B4D9FF51D9423A86A233BD39023D57D /* ShapeTransform.swift in Sources */, + 6B20D02E5D429A424923EC8E314576C3 /* SingleValueProvider.swift in Sources */, + E242AA455327F1C80C36A75613F600C2 /* SizeValueProvider.swift in Sources */, + 84846136ADA17AD44F0FD81DB41B5974 /* SolidCompositionLayer.swift in Sources */, + 95EAF4840FA44407D7B9D93198FC5059 /* SolidLayerModel.swift in Sources */, + D9D098013565C5E799F602084D540BD0 /* Star.swift in Sources */, + 1BDB78C584D5A90ADB1E271DE0AAE611 /* StarNode.swift in Sources */, + 7AC242AE72CA6A2DF9EC2CC7082686AA /* StringExtensions.swift in Sources */, + 4674EF4CBB4CB5C9823734C5198994FB /* Stroke.swift in Sources */, + 07C5109C39B3C2EF140324D52F6EAD0E /* StrokeNode.swift in Sources */, + 29DEAC4B549ED276A9CDC7BE18E26440 /* StrokeRenderer.swift in Sources */, + 4DA04D007F39F55CEDE2D8851592B1D0 /* TextAnimator.swift in Sources */, + 38130C849E36F6F516FB33D693DD1CD7 /* TextAnimatorNode.swift in Sources */, + 211ABA7844864DA1E16599727D17B8E8 /* TextCompositionLayer.swift in Sources */, + 72930E70A3CC4CCA5FE871A9A6C3C5FF /* TextDocument.swift in Sources */, + 85729A10E2757EF02FEA2F89EFD0C6D7 /* TextLayerModel.swift in Sources */, + 7E65C983477106B3212FF7140415883A /* Transform.swift in Sources */, + 135B519B888DBB4F193D4EFF97675942 /* Trim.swift in Sources */, + 953E599E6D5C07548F97ED3873CD00BB /* TrimPathNode.swift in Sources */, + 5A1503DCEEC67FB6DAABE2F399B0B473 /* UIColorExtension.swift in Sources */, + 759FEB111C6706D8804C475FEC353C91 /* ValueContainer.swift in Sources */, + 60B11295B839619FDBA5A02479AA53A0 /* Vectors.swift in Sources */, + 71ED4E1B587688E31035017D6E1BA4B8 /* VectorsExtensions.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AB6C40025318CD8E0CABA790C4FF28B8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 073E2C39FBBF31EFA269308E238958BC /* Pods-StylableUIKit_Tests-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F0F16DFC2B9982FA5FB5F1D1EF124547 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 989CEF16EDC5D18216EE1400413F48C5 /* Pods-StylableUIKit_Example-dummy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FA38C61E9FAB25560B08593BFCCD050F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E011A607387241276369F9BEEE33DA62 /* FBSnapshotTestCase.m in Sources */, + B6A9C62A5262C0767EF3B8C78D6FAA7D /* FBSnapshotTestCasePlatform.m in Sources */, + 122F61784A56796A2CD7B2CD201C5525 /* FBSnapshotTestController.m in Sources */, + DF067743DBB9CBEF42193CC349FCC65E /* iOSSnapshotTestCase-dummy.m in Sources */, + 4C6EEDAD2980F21361E123D4EA97FE52 /* SwiftSupport.swift in Sources */, + E0AEDC3FACFC674C8704AE88681164AD /* UIImage+Compare.m in Sources */, + 4D88D1D4B6DA13E10CB05B3146C04464 /* UIImage+Diff.m in Sources */, + 23C7638975B7288F5F514C0FD1830C7E /* UIImage+Snapshot.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 2EB9F6D8B52DB3538D97956AAD6869A7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = SwiftLint; + target = 52B60EC2A583F24ACBB69C113F5488B9 /* SwiftLint */; + targetProxy = FC6BC92580CDD1BD34C6902926356E53 /* PBXContainerItemProxy */; + }; + 6E33E54B3012498865093501EB95588E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = StylableUIKit; + target = D15B2DA9CE4FEB60C2768C7CE05B9032 /* StylableUIKit */; + targetProxy = 6A582F7813412FB23852778D02759AE0 /* PBXContainerItemProxy */; + }; + 85AAB8A1728B54ACFF4FC5F0C1A3FE9D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "lottie-ios"; + target = 0B967D7F8561D42493EE289EC8D450D1 /* lottie-ios */; + targetProxy = 5D84A4EF7E9FDFBC3D03741DFB5EB3FA /* PBXContainerItemProxy */; + }; + 950B4EA92FB88F31C9F8094F332BEB83 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = iOSSnapshotTestCase; + target = C393038B0BEF088C1B93E6528005862D /* iOSSnapshotTestCase */; + targetProxy = 61563F2AF49F9E2F77C6018A63B95A01 /* PBXContainerItemProxy */; + }; + A5BBD623DD98CEC95B57D47638905DD7 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "lottie-ios"; + target = 0B967D7F8561D42493EE289EC8D450D1 /* lottie-ios */; + targetProxy = 076096937D5CC3F847A24FB9501A4C1D /* PBXContainerItemProxy */; + }; + BB3B17D53E0F28A87F357D7E78BF1C4D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "Pods-StylableUIKit_Example"; + target = 3FCE3490DD43EB855E2C738A2D8BCCA4 /* Pods-StylableUIKit_Example */; + targetProxy = D02B20FFF73D55879BCB01418D8891D5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 196DFA3E4A09A28224918543529A1885 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_DEBUG=1", + "DEBUG=1", + "$(inherited)", + ); + 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 = 10.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Debug; + }; + 446F6B9FEFD338EFB076360E8EDEB9EA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 860214E7F7E4AF5B029DA88AE2FA0B92 /* lottie-ios.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/lottie-ios/lottie-ios-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/lottie-ios/lottie-ios-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/lottie-ios/lottie-ios.modulemap"; + PRODUCT_MODULE_NAME = Lottie; + PRODUCT_NAME = Lottie; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 4C4C41A3D7E0D934EEC08166EA5EC3C3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B775FC1798B4296803F6783FD3376AC7 /* StylableUIKit.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/StylableUIKit/StylableUIKit-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/StylableUIKit/StylableUIKit-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/StylableUIKit/StylableUIKit.modulemap"; + PRODUCT_MODULE_NAME = StylableUIKit; + PRODUCT_NAME = StylableUIKit; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 4CAE2823BF5A4696BEDD99DBD065D644 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ABF041F01910A296BDC6A7BC8E47580A /* Pods-StylableUIKit_Tests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 6A1DAD282FA960B41E503845A9153C35 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5B2EA56E964E82236F693F021B8B8B56 /* Pods-StylableUIKit_Example.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 735E48BAFEC0083F42E8DC8FF60D7717 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB9D6DF74D13984EAACE4D124AC0E61E /* Pods-StylableUIKit_Tests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 84D7C4574E8F0F3095623F0E06F5B402 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7CDF7AC53AC4A8C4C842E372C70A7EA6 /* SwiftLint.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 902EF181ADD1C34613367819C5AF9EDB /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B775FC1798B4296803F6783FD3376AC7 /* StylableUIKit.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/StylableUIKit/StylableUIKit-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/StylableUIKit/StylableUIKit-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/StylableUIKit/StylableUIKit.modulemap"; + PRODUCT_MODULE_NAME = StylableUIKit; + PRODUCT_NAME = StylableUIKit; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + B01D14FDC83DCF9D4BE53066BEA96D05 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "POD_CONFIGURATION_RELEASE=1", + "$(inherited)", + ); + 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 = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRIP_INSTALLED_PRODUCT = NO; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + SYMROOT = "${SRCROOT}/../build"; + }; + name = Release; + }; + D1CC9192B4A5470D099DF362005A6DBC /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CA3603248D245DD452606F3A00976216 /* Pods-StylableUIKit_Example.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + D918F4150EECABA3E5AFAF296B4255D9 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 860214E7F7E4AF5B029DA88AE2FA0B92 /* lottie-ios.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/lottie-ios/lottie-ios-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/lottie-ios/lottie-ios-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/lottie-ios/lottie-ios.modulemap"; + PRODUCT_MODULE_NAME = Lottie; + PRODUCT_NAME = Lottie; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + DEED47E09AF743F48544C1C4FEADEF47 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7CDF7AC53AC4A8C4C842E372C70A7EA6 /* SwiftLint.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + E30677E30722EED4EFAAF96B70FB53E0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B504C6C300126613F44985F63DB93EDE /* iOSSnapshotTestCase.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase.modulemap"; + PRODUCT_MODULE_NAME = FBSnapshotTestCase; + PRODUCT_NAME = FBSnapshotTestCase; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.1; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + EC06F4BCD0F4CC10B27E32823A6F0935 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B504C6C300126613F44985F63DB93EDE /* iOSSnapshotTestCase.xcconfig */; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = NO; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MODULEMAP_FILE = "Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase.modulemap"; + PRODUCT_MODULE_NAME = FBSnapshotTestCase; + PRODUCT_NAME = FBSnapshotTestCase; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_VERSION = 5.1; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0ECF5A7BD8E2157C431BD6C20DA850C7 /* Build configuration list for PBXNativeTarget "Pods-StylableUIKit_Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4CAE2823BF5A4696BEDD99DBD065D644 /* Debug */, + 735E48BAFEC0083F42E8DC8FF60D7717 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1354D69273ADFA00854B713F528A1211 /* Build configuration list for PBXNativeTarget "StylableUIKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 902EF181ADD1C34613367819C5AF9EDB /* Debug */, + 4C4C41A3D7E0D934EEC08166EA5EC3C3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 196DFA3E4A09A28224918543529A1885 /* Debug */, + B01D14FDC83DCF9D4BE53066BEA96D05 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 70218D2F5AD28E03059A8D767F4C83B8 /* Build configuration list for PBXNativeTarget "lottie-ios" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D918F4150EECABA3E5AFAF296B4255D9 /* Debug */, + 446F6B9FEFD338EFB076360E8EDEB9EA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 86694221EA19B62158E4E994DB4E051B /* Build configuration list for PBXNativeTarget "Pods-StylableUIKit_Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D1CC9192B4A5470D099DF362005A6DBC /* Debug */, + 6A1DAD282FA960B41E503845A9153C35 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A2580343E01EF46F4A1E600895C7753A /* Build configuration list for PBXNativeTarget "iOSSnapshotTestCase" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E30677E30722EED4EFAAF96B70FB53E0 /* Debug */, + EC06F4BCD0F4CC10B27E32823A6F0935 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AE7B4FB01588B9E6DF09CB79FC7CE7BD /* Build configuration list for PBXAggregateTarget "SwiftLint" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DEED47E09AF743F48544C1C4FEADEF47 /* Debug */, + 84D7C4574E8F0F3095623F0E06F5B402 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; +} diff --git a/Example/Pods/SwiftLint/LICENSE b/Example/Pods/SwiftLint/LICENSE new file mode 100644 index 0000000..a029e45 --- /dev/null +++ b/Example/Pods/SwiftLint/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Realm Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 THE +AUTHORS OR COPYRIGHT HOLDERS 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. diff --git a/Example/Pods/SwiftLint/swiftlint b/Example/Pods/SwiftLint/swiftlint new file mode 100755 index 0000000..81abb96 Binary files /dev/null and b/Example/Pods/SwiftLint/swiftlint differ diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-Info.plist b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-Info.plist new file mode 100644 index 0000000..2243fe6 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-acknowledgements.markdown b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-acknowledgements.markdown new file mode 100644 index 0000000..9a48388 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-acknowledgements.markdown @@ -0,0 +1,255 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## StylableUIKit + +Copyright (c) 2020 Design Operations Collective + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 THE +AUTHORS OR COPYRIGHT HOLDERS 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. + +## SwiftLint + +The MIT License (MIT) + +Copyright (c) 2015 Realm Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 THE +AUTHORS OR COPYRIGHT HOLDERS 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. + + +## lottie-ios + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Airbnb, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +Generated by CocoaPods - https://cocoapods.org diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-acknowledgements.plist b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-acknowledgements.plist new file mode 100644 index 0000000..2bf17ed --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-acknowledgements.plist @@ -0,0 +1,299 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + Copyright (c) 2020 Design Operations Collective + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 THE +AUTHORS OR COPYRIGHT HOLDERS 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. + License + Apache License, Version 2.0 + Title + StylableUIKit + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + +Copyright (c) 2015 Realm Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 THE +AUTHORS OR COPYRIGHT HOLDERS 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. + + License + MIT + Title + SwiftLint + Type + PSGroupSpecifier + + + FooterText + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Airbnb, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + License + Apache + Title + lottie-ios + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-dummy.m b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-dummy.m new file mode 100644 index 0000000..290642e --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_StylableUIKit_Example : NSObject +@end +@implementation PodsDummy_Pods_StylableUIKit_Example +@end diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-frameworks.sh b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-frameworks.sh new file mode 100755 index 0000000..37ff0fb --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-frameworks.sh @@ -0,0 +1,173 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then + # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy + # frameworks to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + +# Used as a return value for each invocation of `strip_invalid_archs` function. +STRIP_BINARY_RETVAL=0 + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +# Copies and strips a vendored framework +install_framework() +{ + if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then + local source="${BUILT_PRODUCTS_DIR}/$1" + elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then + local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" + elif [ -r "$1" ]; then + local source="$1" + fi + + local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + + if [ -L "${source}" ]; then + echo "Symlinked..." + source="$(readlink "${source}")" + fi + + # Use filter instead of exclude so missing patterns don't throw errors. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" + + local basename + basename="$(basename -s .framework "$1")" + binary="${destination}/${basename}.framework/${basename}" + + if ! [ -r "$binary" ]; then + binary="${destination}/${basename}" + elif [ -L "${binary}" ]; then + echo "Destination binary is symlinked..." + dirname="$(dirname "${binary}")" + binary="${dirname}/$(readlink "${binary}")" + fi + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then + strip_invalid_archs "$binary" + fi + + # Resign the code if required by the build settings to avoid unstable apps + code_sign_if_enabled "${destination}/$(basename "$1")" + + # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. + if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then + local swift_runtime_libs + swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) + for lib in $swift_runtime_libs; do + echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" + rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" + code_sign_if_enabled "${destination}/${lib}" + done + fi +} + +# Copies and strips a vendored dSYM +install_dsym() { + local source="$1" + if [ -r "$source" ]; then + # Copy the dSYM into a the targets temp dir. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" + + local basename + basename="$(basename -s .framework.dSYM "$source")" + binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then + strip_invalid_archs "$binary" + fi + + if [[ $STRIP_BINARY_RETVAL == 1 ]]; then + # Move the stripped file into its final destination. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" + else + # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. + touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" + fi + fi +} + +# Copies the bcsymbolmap files of a vendored framework +install_bcsymbolmap() { + local bcsymbolmap_path="$1" + local destination="${BUILT_PRODUCTS_DIR}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" +} + +# Signs a framework with the provided identity +code_sign_if_enabled() { + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then + # Use the current code_sign_identity + echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" + + if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + code_sign_cmd="$code_sign_cmd &" + fi + echo "$code_sign_cmd" + eval "$code_sign_cmd" + fi +} + +# Strip invalid architectures +strip_invalid_archs() { + binary="$1" + # Get architectures for current target binary + binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" + # Intersect them with the architectures we are building for + intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" + # If there are no archs supported by this binary then warn the user + if [[ -z "$intersected_archs" ]]; then + echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." + STRIP_BINARY_RETVAL=0 + return + fi + stripped="" + for arch in $binary_archs; do + if ! [[ "${ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + fi + STRIP_BINARY_RETVAL=1 +} + + +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/StylableUIKit/StylableUIKit.framework" + install_framework "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/StylableUIKit/StylableUIKit.framework" + install_framework "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework" +fi +if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + wait +fi diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-umbrella.h b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-umbrella.h new file mode 100644 index 0000000..79b074e --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double Pods_StylableUIKit_ExampleVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_StylableUIKit_ExampleVersionString[]; + diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.debug.xcconfig b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.debug.xcconfig new file mode 100644 index 0000000..15ba53f --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.debug.xcconfig @@ -0,0 +1,12 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/StylableUIKit" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/StylableUIKit/StylableUIKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Foundation" -framework "Lottie" -framework "QuartzCore" -framework "StylableUIKit" -framework "UIKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.modulemap b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.modulemap new file mode 100644 index 0000000..d1658c6 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.modulemap @@ -0,0 +1,6 @@ +framework module Pods_StylableUIKit_Example { + umbrella header "Pods-StylableUIKit_Example-umbrella.h" + + export * + module * { export * } +} diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.release.xcconfig b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.release.xcconfig new file mode 100644 index 0000000..15ba53f --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.release.xcconfig @@ -0,0 +1,12 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/StylableUIKit" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/StylableUIKit/StylableUIKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "Foundation" -framework "Lottie" -framework "QuartzCore" -framework "StylableUIKit" -framework "UIKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-Info.plist b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-Info.plist new file mode 100644 index 0000000..2243fe6 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-acknowledgements.markdown b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-acknowledgements.markdown new file mode 100644 index 0000000..86eb1f4 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-acknowledgements.markdown @@ -0,0 +1,29 @@ +# Acknowledgements +This application makes use of the following third party libraries: + +## iOSSnapshotTestCase + +MIT License + +Copyright (c) 2017-2018, Uber Technologies, Inc. +Copyright (c) 2013-2018, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 THE +AUTHORS OR COPYRIGHT HOLDERS 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. + +Generated by CocoaPods - https://cocoapods.org diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-acknowledgements.plist b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-acknowledgements.plist new file mode 100644 index 0000000..f24c9fd --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-acknowledgements.plist @@ -0,0 +1,61 @@ + + + + + PreferenceSpecifiers + + + FooterText + This application makes use of the following third party libraries: + Title + Acknowledgements + Type + PSGroupSpecifier + + + FooterText + MIT License + +Copyright (c) 2017-2018, Uber Technologies, Inc. +Copyright (c) 2013-2018, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 THE +AUTHORS OR COPYRIGHT HOLDERS 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. + + License + MIT + Title + iOSSnapshotTestCase + Type + PSGroupSpecifier + + + FooterText + Generated by CocoaPods - https://cocoapods.org + Title + + Type + PSGroupSpecifier + + + StringsTable + Acknowledgements + Title + Acknowledgements + + diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-dummy.m b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-dummy.m new file mode 100644 index 0000000..63525b7 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_Pods_StylableUIKit_Tests : NSObject +@end +@implementation PodsDummy_Pods_StylableUIKit_Tests +@end diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-frameworks.sh b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-frameworks.sh new file mode 100755 index 0000000..7610fea --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-frameworks.sh @@ -0,0 +1,171 @@ +#!/bin/sh +set -e +set -u +set -o pipefail + +function on_error { + echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" +} +trap 'on_error $LINENO' ERR + +if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then + # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy + # frameworks to, so exit 0 (signalling the script phase was successful). + exit 0 +fi + +echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" +mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + +COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" +SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + +# Used as a return value for each invocation of `strip_invalid_archs` function. +STRIP_BINARY_RETVAL=0 + +# This protects against multiple targets copying the same framework dependency at the same time. The solution +# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html +RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") + +# Copies and strips a vendored framework +install_framework() +{ + if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then + local source="${BUILT_PRODUCTS_DIR}/$1" + elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then + local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" + elif [ -r "$1" ]; then + local source="$1" + fi + + local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" + + if [ -L "${source}" ]; then + echo "Symlinked..." + source="$(readlink "${source}")" + fi + + # Use filter instead of exclude so missing patterns don't throw errors. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" + + local basename + basename="$(basename -s .framework "$1")" + binary="${destination}/${basename}.framework/${basename}" + + if ! [ -r "$binary" ]; then + binary="${destination}/${basename}" + elif [ -L "${binary}" ]; then + echo "Destination binary is symlinked..." + dirname="$(dirname "${binary}")" + binary="${dirname}/$(readlink "${binary}")" + fi + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then + strip_invalid_archs "$binary" + fi + + # Resign the code if required by the build settings to avoid unstable apps + code_sign_if_enabled "${destination}/$(basename "$1")" + + # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. + if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then + local swift_runtime_libs + swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) + for lib in $swift_runtime_libs; do + echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" + rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" + code_sign_if_enabled "${destination}/${lib}" + done + fi +} + +# Copies and strips a vendored dSYM +install_dsym() { + local source="$1" + if [ -r "$source" ]; then + # Copy the dSYM into a the targets temp dir. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" + + local basename + basename="$(basename -s .framework.dSYM "$source")" + binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" + + # Strip invalid architectures so "fat" simulator / device frameworks work on device + if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then + strip_invalid_archs "$binary" + fi + + if [[ $STRIP_BINARY_RETVAL == 1 ]]; then + # Move the stripped file into its final destination. + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" + else + # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. + touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" + fi + fi +} + +# Copies the bcsymbolmap files of a vendored framework +install_bcsymbolmap() { + local bcsymbolmap_path="$1" + local destination="${BUILT_PRODUCTS_DIR}" + echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" + rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" +} + +# Signs a framework with the provided identity +code_sign_if_enabled() { + if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then + # Use the current code_sign_identity + echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" + local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" + + if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + code_sign_cmd="$code_sign_cmd &" + fi + echo "$code_sign_cmd" + eval "$code_sign_cmd" + fi +} + +# Strip invalid architectures +strip_invalid_archs() { + binary="$1" + # Get architectures for current target binary + binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" + # Intersect them with the architectures we are building for + intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" + # If there are no archs supported by this binary then warn the user + if [[ -z "$intersected_archs" ]]; then + echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." + STRIP_BINARY_RETVAL=0 + return + fi + stripped="" + for arch in $binary_archs; do + if ! [[ "${ARCHS}" == *"$arch"* ]]; then + # Strip non-valid architectures in-place + lipo -remove "$arch" -output "$binary" "$binary" + stripped="$stripped $arch" + fi + done + if [[ "$stripped" ]]; then + echo "Stripped $binary of architectures:$stripped" + fi + STRIP_BINARY_RETVAL=1 +} + + +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/iOSSnapshotTestCase/FBSnapshotTestCase.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/iOSSnapshotTestCase/FBSnapshotTestCase.framework" +fi +if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then + wait +fi diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-umbrella.h b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-umbrella.h new file mode 100644 index 0000000..564139f --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double Pods_StylableUIKit_TestsVersionNumber; +FOUNDATION_EXPORT const unsigned char Pods_StylableUIKit_TestsVersionString[]; + diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.debug.xcconfig b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.debug.xcconfig new file mode 100644 index 0000000..9c67120 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.debug.xcconfig @@ -0,0 +1,12 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/StylableUIKit" "${PODS_CONFIGURATION_BUILD_DIR}/iOSSnapshotTestCase" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/StylableUIKit/StylableUIKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/iOSSnapshotTestCase/FBSnapshotTestCase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "FBSnapshotTestCase" -framework "Foundation" -framework "Lottie" -framework "QuartzCore" -framework "StylableUIKit" -framework "UIKit" -framework "XCTest" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.modulemap b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.modulemap new file mode 100644 index 0000000..3057661 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.modulemap @@ -0,0 +1,6 @@ +framework module Pods_StylableUIKit_Tests { + umbrella header "Pods-StylableUIKit_Tests-umbrella.h" + + export * + module * { export * } +} diff --git a/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.release.xcconfig b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.release.xcconfig new file mode 100644 index 0000000..9c67120 --- /dev/null +++ b/Example/Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.release.xcconfig @@ -0,0 +1,12 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" "${PODS_CONFIGURATION_BUILD_DIR}/StylableUIKit" "${PODS_CONFIGURATION_BUILD_DIR}/iOSSnapshotTestCase" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/StylableUIKit/StylableUIKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/iOSSnapshotTestCase/FBSnapshotTestCase.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/Lottie.framework/Headers" +LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "FBSnapshotTestCase" -framework "Foundation" -framework "Lottie" -framework "QuartzCore" -framework "StylableUIKit" -framework "UIKit" -framework "XCTest" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_PODFILE_DIR_PATH = ${SRCROOT}/. +PODS_ROOT = ${SRCROOT}/Pods +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-Info.plist b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-Info.plist new file mode 100644 index 0000000..90db36a --- /dev/null +++ b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 3.1.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-dummy.m b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-dummy.m new file mode 100644 index 0000000..2c0e5bf --- /dev/null +++ b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_StylableUIKit : NSObject +@end +@implementation PodsDummy_StylableUIKit +@end diff --git a/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-prefix.pch b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-umbrella.h b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-umbrella.h new file mode 100644 index 0000000..cbc5929 --- /dev/null +++ b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double StylableUIKitVersionNumber; +FOUNDATION_EXPORT const unsigned char StylableUIKitVersionString[]; + diff --git a/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit.modulemap b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit.modulemap new file mode 100644 index 0000000..b72ff7c --- /dev/null +++ b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit.modulemap @@ -0,0 +1,6 @@ +framework module StylableUIKit { + umbrella header "StylableUIKit-umbrella.h" + + export * + module * { export * } +} diff --git a/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit.xcconfig b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit.xcconfig new file mode 100644 index 0000000..ef48ad1 --- /dev/null +++ b/Example/Pods/Target Support Files/StylableUIKit/StylableUIKit.xcconfig @@ -0,0 +1,12 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/StylableUIKit +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "UIKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/../.. +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/SwiftLint/SwiftLint.xcconfig b/Example/Pods/Target Support Files/SwiftLint/SwiftLint.xcconfig new file mode 100644 index 0000000..59a6456 --- /dev/null +++ b/Example/Pods/Target Support Files/SwiftLint/SwiftLint.xcconfig @@ -0,0 +1,9 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftLint +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftLint +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/iOSSnapshotTestCase/Info.plist b/Example/Pods/Target Support Files/iOSSnapshotTestCase/Info.plist new file mode 100644 index 0000000..e92eb78 --- /dev/null +++ b/Example/Pods/Target Support Files/iOSSnapshotTestCase/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 6.0.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-Info.plist b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-Info.plist new file mode 100644 index 0000000..45024a7 --- /dev/null +++ b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 6.2.0 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-dummy.m b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-dummy.m new file mode 100644 index 0000000..9967d3e --- /dev/null +++ b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_iOSSnapshotTestCase : NSObject +@end +@implementation PodsDummy_iOSSnapshotTestCase +@end diff --git a/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-prefix.pch b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-umbrella.h b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-umbrella.h new file mode 100644 index 0000000..1734e02 --- /dev/null +++ b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase-umbrella.h @@ -0,0 +1,19 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + +#import "FBSnapshotTestCase.h" +#import "FBSnapshotTestCasePlatform.h" +#import "FBSnapshotTestController.h" + +FOUNDATION_EXPORT double FBSnapshotTestCaseVersionNumber; +FOUNDATION_EXPORT const unsigned char FBSnapshotTestCaseVersionString[]; + diff --git a/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase.modulemap b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase.modulemap new file mode 100644 index 0000000..d2c2777 --- /dev/null +++ b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase.modulemap @@ -0,0 +1,6 @@ +framework module FBSnapshotTestCase { + umbrella header "iOSSnapshotTestCase-umbrella.h" + + export * + module * { export * } +} diff --git a/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase.xcconfig b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase.xcconfig new file mode 100644 index 0000000..ea180b9 --- /dev/null +++ b/Example/Pods/Target Support Files/iOSSnapshotTestCase/iOSSnapshotTestCase.xcconfig @@ -0,0 +1,13 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/iOSSnapshotTestCase +ENABLE_BITCODE = NO +FRAMEWORK_SEARCH_PATHS = $(inherited) "$(PLATFORM_DIR)/Developer/Library/Frameworks" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "Foundation" -framework "QuartzCore" -framework "UIKit" -framework "XCTest" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/iOSSnapshotTestCase +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/Target Support Files/lottie-ios/lottie-ios-Info.plist b/Example/Pods/Target Support Files/lottie-ios/lottie-ios-Info.plist new file mode 100644 index 0000000..c0f42cd --- /dev/null +++ b/Example/Pods/Target Support Files/lottie-ios/lottie-ios-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 3.1.6 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Example/Pods/Target Support Files/lottie-ios/lottie-ios-dummy.m b/Example/Pods/Target Support Files/lottie-ios/lottie-ios-dummy.m new file mode 100644 index 0000000..67e66c9 --- /dev/null +++ b/Example/Pods/Target Support Files/lottie-ios/lottie-ios-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_lottie_ios : NSObject +@end +@implementation PodsDummy_lottie_ios +@end diff --git a/Example/Pods/Target Support Files/lottie-ios/lottie-ios-prefix.pch b/Example/Pods/Target Support Files/lottie-ios/lottie-ios-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Example/Pods/Target Support Files/lottie-ios/lottie-ios-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Example/Pods/Target Support Files/lottie-ios/lottie-ios-umbrella.h b/Example/Pods/Target Support Files/lottie-ios/lottie-ios-umbrella.h new file mode 100644 index 0000000..287f9db --- /dev/null +++ b/Example/Pods/Target Support Files/lottie-ios/lottie-ios-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double LottieVersionNumber; +FOUNDATION_EXPORT const unsigned char LottieVersionString[]; + diff --git a/Example/Pods/Target Support Files/lottie-ios/lottie-ios.modulemap b/Example/Pods/Target Support Files/lottie-ios/lottie-ios.modulemap new file mode 100644 index 0000000..494806f --- /dev/null +++ b/Example/Pods/Target Support Files/lottie-ios/lottie-ios.modulemap @@ -0,0 +1,6 @@ +framework module Lottie { + umbrella header "lottie-ios-umbrella.h" + + export * + module * { export * } +} diff --git a/Example/Pods/Target Support Files/lottie-ios/lottie-ios.xcconfig b/Example/Pods/Target Support Files/lottie-ios/lottie-ios.xcconfig new file mode 100644 index 0000000..d01b732 --- /dev/null +++ b/Example/Pods/Target Support Files/lottie-ios/lottie-ios.xcconfig @@ -0,0 +1,11 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_LDFLAGS = $(inherited) -framework "CoreGraphics" -framework "QuartzCore" -framework "UIKit" +OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/lottie-ios +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Compare.h b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Compare.h new file mode 100644 index 0000000..fb7d7b0 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Compare.h @@ -0,0 +1,49 @@ +// +// Created by Gabriel Handford on 3/1/09. +// Copyright 2009-2013. All rights reserved. +// Created by John Boiles on 10/20/11. +// Copyright (c) 2011. All rights reserved +// Modified by Felix Schulze on 2/11/13. +// Copyright 2013. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 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 THE AUTHORS OR COPYRIGHT +// HOLDERS 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface UIImage (Compare) + +/** + Compares the image against another given image. + + @param image The other image to compare against. + @param perPixelTolerance How much (in percentage) any given pixel's colors are allowed to change from the pixel in the reference image. + @param overallTolerance The overall percentage of pixels that are allowed to change from the pixels in the reference image. + @return A BOOL which represents if the image is the same or not. + */ +- (BOOL)fb_compareWithImage:(UIImage *)image perPixelTolerance:(CGFloat)perPixelTolerance overallTolerance:(CGFloat)overallTolerance; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Compare.m b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Compare.m new file mode 100644 index 0000000..a52c965 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Compare.m @@ -0,0 +1,179 @@ +// +// Created by Gabriel Handford on 3/1/09. +// Copyright 2009-2013. All rights reserved. +// Created by John Boiles on 10/20/11. +// Copyright (c) 2011. All rights reserved +// Modified by Felix Schulze on 2/11/13. +// Copyright 2013. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 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 THE AUTHORS OR COPYRIGHT +// HOLDERS 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 + +// This makes debugging much more fun +typedef union { + uint32_t raw; + unsigned char bytes[4]; + struct { + char red; + char green; + char blue; + char alpha; + } __attribute__((packed)) pixels; +} FBComparePixel; + +@implementation UIImage (Compare) + +- (BOOL)fb_compareWithImage:(UIImage *)image perPixelTolerance:(CGFloat)perPixelTolerance overallTolerance:(CGFloat)overallTolerance +{ + CGSize referenceImageSize = CGSizeMake(CGImageGetWidth(self.CGImage), CGImageGetHeight(self.CGImage)); + CGSize imageSize = CGSizeMake(CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage)); + NSAssert(CGSizeEqualToSize(referenceImageSize, imageSize), @"Images must be same size."); + + // The images have the equal size, so we could use the smallest amount of bytes because of byte padding + size_t minBytesPerRow = MIN(CGImageGetBytesPerRow(self.CGImage), CGImageGetBytesPerRow(image.CGImage)); + size_t referenceImageSizeBytes = referenceImageSize.height * minBytesPerRow; + void *referenceImagePixels = calloc(1, referenceImageSizeBytes); + void *imagePixels = calloc(1, referenceImageSizeBytes); + + if (!referenceImagePixels || !imagePixels) { + free(referenceImagePixels); + free(imagePixels); + return NO; + } + + CGContextRef referenceImageContext = CGBitmapContextCreate(referenceImagePixels, + referenceImageSize.width, + referenceImageSize.height, + CGImageGetBitsPerComponent(self.CGImage), + minBytesPerRow, + CGImageGetColorSpace(self.CGImage), + (CGBitmapInfo)kCGImageAlphaPremultipliedLast); + CGContextRef imageContext = CGBitmapContextCreate(imagePixels, + imageSize.width, + imageSize.height, + CGImageGetBitsPerComponent(image.CGImage), + minBytesPerRow, + CGImageGetColorSpace(image.CGImage), + (CGBitmapInfo)kCGImageAlphaPremultipliedLast); + + if (!referenceImageContext || !imageContext) { + CGContextRelease(referenceImageContext); + CGContextRelease(imageContext); + free(referenceImagePixels); + free(imagePixels); + return NO; + } + + CGContextDrawImage(referenceImageContext, CGRectMake(0, 0, referenceImageSize.width, referenceImageSize.height), self.CGImage); + CGContextDrawImage(imageContext, CGRectMake(0, 0, imageSize.width, imageSize.height), image.CGImage); + + CGContextRelease(referenceImageContext); + CGContextRelease(imageContext); + + BOOL imageEqual = YES; + FBComparePixel *p1 = referenceImagePixels; + FBComparePixel *p2 = imagePixels; + + // Do a fast compare if we can + if (overallTolerance == 0 && perPixelTolerance == 0) { + imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0); + } else { + const NSUInteger pixelCount = referenceImageSize.width * referenceImageSize.height; + // Go through each pixel in turn and see if it is different + imageEqual = [self _compareAllPixelsWithPerPixelTolerance:perPixelTolerance + overallTolerance:overallTolerance + pixelCount:pixelCount + referencePixels:p1 + imagePixels:p2]; + } + + free(referenceImagePixels); + free(imagePixels); + + return imageEqual; +} + +- (BOOL)_comparePixelWithPerPixelTolerance:(CGFloat)perPixelTolerance + referencePixel:(FBComparePixel *)referencePixel + imagePixel:(FBComparePixel *)imagePixel +{ + if (referencePixel->raw == imagePixel->raw) { + return YES; + } else if (perPixelTolerance == 0) { + return NO; + } + + CGFloat redPercentDiff = [self _calculatePercentDifferenceForReferencePixelComponent:referencePixel->pixels.red + imagePixelComponent:imagePixel->pixels.red]; + CGFloat greenPercentDiff = [self _calculatePercentDifferenceForReferencePixelComponent:referencePixel->pixels.green + imagePixelComponent:imagePixel->pixels.green]; + CGFloat bluePercentDiff = [self _calculatePercentDifferenceForReferencePixelComponent:referencePixel->pixels.blue + imagePixelComponent:imagePixel->pixels.blue]; + CGFloat alphaPercentDiff = [self _calculatePercentDifferenceForReferencePixelComponent:referencePixel->pixels.alpha + imagePixelComponent:imagePixel->pixels.alpha]; + + BOOL anyDifferencesFound = (redPercentDiff > perPixelTolerance || + greenPercentDiff > perPixelTolerance || + bluePercentDiff > perPixelTolerance || + alphaPercentDiff > perPixelTolerance); + + return !anyDifferencesFound; +} + +- (CGFloat)_calculatePercentDifferenceForReferencePixelComponent:(char)p1 + imagePixelComponent:(char)p2 +{ + NSInteger referencePixelComponent = (unsigned char)p1; + NSInteger imagePixelComponent = (unsigned char)p2; + NSUInteger componentDifference = ABS(referencePixelComponent - imagePixelComponent); + return (CGFloat)componentDifference / 256; +} + +- (BOOL)_compareAllPixelsWithPerPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + pixelCount:(NSUInteger)pixelCount + referencePixels:(FBComparePixel *)referencePixel + imagePixels:(FBComparePixel *)imagePixel +{ + NSUInteger numDiffPixels = 0; + for (NSUInteger n = 0; n < pixelCount; ++n) { + // If this pixel is different, increment the pixel diff count and see + // if we have hit our limit. + BOOL isIdenticalPixel = [self _comparePixelWithPerPixelTolerance:perPixelTolerance referencePixel:referencePixel imagePixel:imagePixel]; + if (!isIdenticalPixel) { + numDiffPixels++; + + CGFloat percent = (CGFloat)numDiffPixels / (CGFloat)pixelCount; + if (percent > overallTolerance) { + return NO; + } + } + + referencePixel++; + imagePixel++; + } + return YES; +} + +@end diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Diff.h b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Diff.h new file mode 100644 index 0000000..2a97e11 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Diff.h @@ -0,0 +1,41 @@ +// +// Created by Gabriel Handford on 3/1/09. +// Copyright 2009-2013. All rights reserved. +// Created by John Boiles on 10/20/11. +// Copyright (c) 2011. All rights reserved +// Modified by Felix Schulze on 2/11/13. +// Copyright 2013. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 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 THE AUTHORS OR COPYRIGHT +// HOLDERS 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 + +NS_ASSUME_NONNULL_BEGIN + +@interface UIImage (Diff) + +- (UIImage *)fb_diffWithImage:(UIImage *)image; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Diff.m b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Diff.m new file mode 100644 index 0000000..7cb1d40 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Diff.m @@ -0,0 +1,56 @@ +// +// Created by Gabriel Handford on 3/1/09. +// Copyright 2009-2013. All rights reserved. +// Created by John Boiles on 10/20/11. +// Copyright (c) 2011. All rights reserved +// Modified by Felix Schulze on 2/11/13. +// Copyright 2013. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// 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 THE AUTHORS OR COPYRIGHT +// HOLDERS 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 + +@implementation UIImage (Diff) + +- (UIImage *)fb_diffWithImage:(UIImage *)image +{ + if (!image) { + return nil; + } + CGSize imageSize = CGSizeMake(MAX(self.size.width, image.size.width), MAX(self.size.height, image.size.height)); + UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0); + CGContextRef context = UIGraphicsGetCurrentContext(); + [self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; + CGContextSetAlpha(context, 0.5); + CGContextBeginTransparencyLayer(context, NULL); + [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)]; + CGContextSetBlendMode(context, kCGBlendModeDifference); + CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor); + CGContextFillRect(context, CGRectMake(0, 0, self.size.width, self.size.height)); + CGContextEndTransparencyLayer(context); + UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return returnImage; +} + +@end diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Snapshot.h b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Snapshot.h new file mode 100644 index 0000000..10c304f --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Snapshot.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017-2018, Uber Technologies, Inc. + * Copyright (c) 2015-2018, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface UIImage (Snapshot) + +/// Uses renderInContext: to get a snapshot of the layer. ++ (nullable UIImage *)fb_imageForLayer:(CALayer *)layer; + +/// Uses renderInContext: to get a snapshot of the view layer. ++ (nullable UIImage *)fb_imageForViewLayer:(UIView *)view; + +/// Uses drawViewHierarchyInRect: to get a snapshot of the view and adds the view into a window if needed. ++ (nullable UIImage *)fb_imageForView:(UIView *)view; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Snapshot.m b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Snapshot.m new file mode 100644 index 0000000..cfe2743 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/Categories/UIImage+Snapshot.m @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2017-2018, Uber Technologies, Inc. + * Copyright (c) 2015-2018, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +@implementation UIImage (Snapshot) + ++ (UIImage *)fb_imageForLayer:(CALayer *)layer +{ + CGRect bounds = layer.bounds; + NSAssert1(CGRectGetWidth(bounds), @"Zero width for layer %@", layer); + NSAssert1(CGRectGetHeight(bounds), @"Zero height for layer %@", layer); + + UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0); + CGContextRef context = UIGraphicsGetCurrentContext(); + NSAssert1(context, @"Could not generate context for layer %@", layer); + CGContextSaveGState(context); + [layer layoutIfNeeded]; + [layer renderInContext:context]; + CGContextRestoreGState(context); + + UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return snapshot; +} + ++ (UIImage *)fb_imageForViewLayer:(UIView *)view +{ + [view layoutIfNeeded]; + return [self fb_imageForLayer:view.layer]; +} + ++ (UIImage *)fb_imageForView:(UIView *)view +{ + // If the input view is already a UIWindow, then just use that. Otherwise wrap in a window. + UIWindow *window = [view isKindOfClass:[UIWindow class]] ? (UIWindow *)view : view.window; + BOOL removeFromSuperview = NO; + if (!window) { + window = [[UIApplication sharedApplication] keyWindow]; + } + + if (!view.window && view != window) { + [window addSubview:view]; + removeFromSuperview = YES; + } + + [view layoutIfNeeded]; + + CGRect bounds = view.bounds; + NSAssert1(CGRectGetWidth(bounds), @"Zero width for view %@", view); + NSAssert1(CGRectGetHeight(bounds), @"Zero height for view %@", view); + + UIGraphicsImageRenderer *graphicsImageRenderer = [[UIGraphicsImageRenderer alloc] initWithSize:bounds.size]; + + UIImage *snapshot = [graphicsImageRenderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) { + [view drawViewHierarchyInRect:bounds afterScreenUpdates:YES]; + }]; + + if (removeFromSuperview) { + [view removeFromSuperview]; + } + + return snapshot; +} + +@end diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCase.h b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCase.h new file mode 100644 index 0000000..365b741 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCase.h @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2017-2018, Uber Technologies, Inc. + * Copyright (c) 2015-2018, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +#import + +#import + +#import + +/* + There are three ways of setting reference image directories. + + 1. Set the preprocessor macro FB_REFERENCE_IMAGE_DIR to a double quoted + c-string with the path. This only works for Objective-C tests. + 2. Set an environment variable named FB_REFERENCE_IMAGE_DIR with the path. This + takes precedence over the preprocessor macro to allow for run-time override. + 3. Keep everything unset, which will cause the reference images to be looked up + inside the bundle holding the current test, in the + Resources/ReferenceImages_* directories. + */ +#ifndef FB_REFERENCE_IMAGE_DIR +#define FB_REFERENCE_IMAGE_DIR "" +#endif + +/* + There are three ways of setting failed image diff directories. + + 1. Set the preprocessor macro IMAGE_DIFF_DIR to a double quoted + c-string with the path. + 2. Set an environment variable named IMAGE_DIFF_DIR with the path. This + takes precedence over the preprocessor macro to allow for run-time override. + 3. Keep everything unset, which will cause the failed image diff images to be saved + inside a temporary directory. + */ +#ifndef IMAGE_DIFF_DIR +#define IMAGE_DIFF_DIR "" +#endif + +/** + Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though. + @param view The view to snapshot. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param suffixes An NSOrderedSet of strings for the different suffixes. + @param tolerance The overall percentage of pixels that can differ and still count as an 'identical' view. + */ +#define FBSnapshotVerifyViewWithOptions(view__, identifier__, suffixes__, tolerance__) \ + FBSnapshotVerifyViewOrLayerWithOptions(View, view__, identifier__, suffixes__, tolerance__) + +/** + Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though. + @param view The view to snapshot. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param suffixes An NSOrderedSet of strings for the different suffixes. + @param pixelTolerance The percentage a given pixel's R,G,B and A components can differ and still be considered 'identical'. + @param tolerance The overall percentage of pixels that can differ and still count as an 'identical' layer. + */ +#define FBSnapshotVerifyViewWithPixelOptions(view__, identifier__, suffixes__, pixelTolerance__, tolerance__) \ + FBSnapshotVerifyViewOrLayerWithPixelOptions(View, view__, identifier__, suffixes__, pixelTolerance__, tolerance__) + +#define FBSnapshotVerifyView(view__, identifier__) \ + FBSnapshotVerifyViewWithOptions(view__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0) + + +/** + Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though. + @param layer The layer to snapshot. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param suffixes An NSOrderedSet of strings for the different suffixes. + @param pixelTolerance The percentage a given pixel's R,G,B and A components can differ and still be considered 'identical'. + @param tolerance The overall percentage of pixels that can differ and still count as an 'identical' layer. + */ +#define FBSnapshotVerifyLayerWithPixelOptions(layer__, identifier__, suffixes__, pixelTolerance__, tolerance__) \ + FBSnapshotVerifyViewOrLayerWithPixelOptions(Layer, layer__, identifier__, suffixes__, pixelTolerance__, tolerance__) + +/** + Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though. + @param layer The layer to snapshot. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param suffixes An NSOrderedSet of strings for the different suffixes. + @param tolerance The overall percentage of pixels that can differ and still count as an 'identical' layer. + */ +#define FBSnapshotVerifyLayerWithOptions(layer__, identifier__, suffixes__, tolerance__) \ + FBSnapshotVerifyViewOrLayerWithOptions(Layer, layer__, identifier__, suffixes__, tolerance__) + +#define FBSnapshotVerifyLayer(layer__, identifier__) \ + FBSnapshotVerifyLayerWithOptions(layer__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0) + +#define FBSnapshotVerifyViewOrLayerWithOptions(what__, viewOrLayer__, identifier__, suffixes__, tolerance__) \ + { \ + NSString *errorDescription = [self snapshotVerifyViewOrLayer:viewOrLayer__ identifier:identifier__ suffixes:suffixes__ overallTolerance:tolerance__ defaultReferenceDirectory:(@FB_REFERENCE_IMAGE_DIR) defaultImageDiffDirectory:(@IMAGE_DIFF_DIR)]; \ + BOOL noErrors = (errorDescription == nil); \ + XCTAssertTrue(noErrors, @"%@", errorDescription); \ + } + +#define FBSnapshotVerifyViewOrLayerWithPixelOptions(what__, viewOrLayer__, identifier__, suffixes__, pixelTolerance__, tolerance__) \ + { \ + NSString *errorDescription = [self snapshotVerifyViewOrLayer:viewOrLayer__ identifier:identifier__ suffixes:suffixes__ perPixelTolerance:pixelTolerance__ overallTolerance:tolerance__ defaultReferenceDirectory:(@FB_REFERENCE_IMAGE_DIR) defaultImageDiffDirectory:(@IMAGE_DIFF_DIR)]; \ + BOOL noErrors = (errorDescription == nil); \ + XCTAssertTrue(noErrors, @"%@", errorDescription); \ + } + +NS_ASSUME_NONNULL_BEGIN + +/** + The base class of view snapshotting tests. If you have small UI component, it's often easier to configure it in a test + and compare an image of the view to a reference image that write lots of complex layout-code tests. + + In order to flip the tests in your subclass to record the reference images set @c recordMode to @c YES. + + @attention When recording, the reference image directory should be explicitly + set, otherwise the images may be written to somewhere inside the + simulator directory. + + For example: + @code + - (void)setUp + { + [super setUp]; + self.recordMode = YES; + } + @endcode + */ +@interface FBSnapshotTestCase : XCTestCase + +/** + When YES, the test macros will save reference images, rather than performing an actual test. + */ +@property (readwrite, nonatomic, assign) BOOL recordMode; + +/** + When set, allows fine-grained control over what you want the file names to include. + + Allows you to combine which device or simulator specific details you want in your snapshot file names. + + The default value is FBSnapshotTestCaseFileNameIncludeOptionScreenScale. + + @discussion If you are migrating from the now deleted FBSnapshotTestCaseAgnosticOption to FBSnapshotTestCaseFileNameIncludeOption, we default to using FBSnapshotTestCaseFileNameIncludeOptionScreenScale for fileNameOptions to make the transition easy. If you don't want to have the screen scale included in your file name, you need to set fileNameOptions to a mask that doesn't include FBSnapshotTestCaseFileNameIncludeOptionScreenScale: + + self.fileNameOptions = (FBSnapshotTestCaseFileNameIncludeOptionDevice | FBSnapshotTestCaseFileNameIncludeOptionOS); + */ + +@property (readwrite, nonatomic, assign) FBSnapshotTestCaseFileNameIncludeOption fileNameOptions; + +/** + Overrides the folder name in which the snapshot is going to be saved. + + @attention This property *must* be called *AFTER* [super setUp]. + */ +@property (readwrite, nonatomic, copy, nullable) NSString *folderName; + +/** + When YES, renders a snapshot of the complete view hierarchy as visible onscreen. + There are several things that do not work if renderInContext: is used. + - UIVisualEffect #70 + - UIAppearance #91 + - Size Classes #92 + + @attention If the view does't belong to a UIWindow, it will create one and add the view as a subview. + */ +@property (readwrite, nonatomic, assign) BOOL usesDrawViewHierarchyInRect; + +- (void)setUp NS_REQUIRES_SUPER; +- (void)tearDown NS_REQUIRES_SUPER; + +/** + Performs the comparison or records a snapshot of the layer if recordMode is YES. + @param viewOrLayer The UIView or CALayer to snapshot. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param suffixes An NSOrderedSet of strings for the different suffixes. + @param overallTolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care. + @param defaultReferenceDirectory The directory to default to for reference images. + @param defaultImageDiffDirectory The directory to default to for failed image diffs. + @returns nil if the comparison (or saving of the reference image) succeeded. Otherwise it contains an error description. + */ +- (NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer + identifier:(nullable NSString *)identifier + suffixes:(NSOrderedSet *)suffixes + overallTolerance:(CGFloat)overallTolerance + defaultReferenceDirectory:(nullable NSString *)defaultReferenceDirectory + defaultImageDiffDirectory:(nullable NSString *)defaultImageDiffDirectory; + +/** + Performs the comparison or records a snapshot of the layer if recordMode is YES. + @param viewOrLayer The UIView or CALayer to snapshot. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param suffixes An NSOrderedSet of strings for the different suffixes. + @param perPixelTolerance The percentage a given pixel's R,G,B and A components can differ and still be considered 'identical'. Each color shade difference represents a 0.390625% change. + @param overallTolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care. + @param defaultReferenceDirectory The directory to default to for reference images. + @param defaultImageDiffDirectory The directory to default to for failed image diffs. + @returns nil if the comparison (or saving of the reference image) succeeded. Otherwise it contains an error description. + */ +- (nullable NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer + identifier:(nullable NSString *)identifier + suffixes:(NSOrderedSet *)suffixes + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + defaultReferenceDirectory:(nullable NSString *)defaultReferenceDirectory + defaultImageDiffDirectory:(nullable NSString *)defaultImageDiffDirectory; + +/** + Performs the comparison or records a snapshot of the layer if recordMode is YES. + @param layer The Layer to snapshot. + @param referenceImagesDirectory The directory in which reference images are stored. + @param imageDiffDirectory The directory in which failed image diffs are stored. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param overallTolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care. + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfLayer:(CALayer *)layer + referenceImagesDirectory:(NSString *)referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:(nullable NSString *)identifier + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr; + +/** + Performs the comparison or records a snapshot of the layer if recordMode is YES. + @param layer The Layer to snapshot. + @param referenceImagesDirectory The directory in which reference images are stored. + @param imageDiffDirectory The directory in which failed image diffs are stored. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param perPixelTolerance The percentage a given pixel's R,G,B and A components can differ and still be considered 'identical'. Each color shade difference represents a 0.390625% change. + @param overallTolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care. + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfLayer:(CALayer *)layer + referenceImagesDirectory:(NSString *)referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:(nullable NSString *)identifier + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr; + +/** + Performs the comparison or records a snapshot of the view if recordMode is YES. + @param view The view to snapshot. + @param referenceImagesDirectory The directory in which reference images are stored. + @param imageDiffDirectory The directory in which failed image diffs are stored. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param overallTolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care. + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfView:(UIView *)view + referenceImagesDirectory:(NSString *)referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:(nullable NSString *)identifier + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr; + +/** + Performs the comparison or records a snapshot of the view if recordMode is YES. + @param view The view to snapshot. + @param referenceImagesDirectory The directory in which reference images are stored. + @param imageDiffDirectory The directory in which failed image diffs are stored. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param perPixelTolerance The percentage a given pixel's R,G,B and A components can differ and still be considered 'identical'. Each color shade difference represents a 0.390625% change. + @param overallTolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care. + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfView:(UIView *)view + referenceImagesDirectory:(NSString *)referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:(nullable NSString *)identifier + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr; + +/** + Checks if reference image with identifier based name exists in the reference images directory. + @param referenceImagesDirectory The directory in which reference images are stored. + @param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method. + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if reference image exists. + */ +- (BOOL)referenceImageRecordedInDirectory:(NSString *)referenceImagesDirectory + identifier:(nullable NSString *)identifier + error:(NSError **)errorPtr; + +/** + Returns the reference image directory. + + Helper function used to implement the assert macros. + + @param dir Directory to use if environment variable not specified. Ignored if null or empty. + */ +- (NSString *)getReferenceImageDirectoryWithDefault:(nullable NSString *)dir; + +/** + Returns the failed image diff directory. + + Helper function used to implement the assert macros. + + @param dir Directory to use if environment variable not specified. Ignored if null or empty. + */ +- (NSString *)getImageDiffDirectoryWithDefault:(nullable NSString *)dir; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCase.m b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCase.m new file mode 100644 index 0000000..dc10fee --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCase.m @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2017-2018, Uber Technologies, Inc. + * Copyright (c) 2015-2018, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +@implementation FBSnapshotTestCase { + FBSnapshotTestController *_snapshotController; +} + +#pragma mark - Overrides + +- (void)setUp +{ + [super setUp]; + _snapshotController = [[FBSnapshotTestController alloc] initWithTestClass:[self class]]; +} + +- (void)tearDown +{ + _snapshotController = nil; + [super tearDown]; +} + +- (BOOL)recordMode +{ + return _snapshotController.recordMode; +} + +- (void)setRecordMode:(BOOL)recordMode +{ + NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__); + _snapshotController.recordMode = recordMode; +} + +- (FBSnapshotTestCaseFileNameIncludeOption)fileNameOptions +{ + return _snapshotController.fileNameOptions; +} + +- (void)setFileNameOptions:(FBSnapshotTestCaseFileNameIncludeOption)fileNameOptions +{ + NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__); + _snapshotController.fileNameOptions = fileNameOptions; +} + +- (BOOL)usesDrawViewHierarchyInRect +{ + return _snapshotController.usesDrawViewHierarchyInRect; +} + +- (void)setUsesDrawViewHierarchyInRect:(BOOL)usesDrawViewHierarchyInRect +{ + NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__); + _snapshotController.usesDrawViewHierarchyInRect = usesDrawViewHierarchyInRect; +} + +- (NSString *)folderName +{ + return _snapshotController.folderName; +} + +- (void)setFolderName:(NSString *)folderName +{ + _snapshotController.folderName = folderName; +} + +#pragma mark - Public API + +- (NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer + identifier:(NSString *)identifier + suffixes:(NSOrderedSet *)suffixes + overallTolerance:(CGFloat)overallTolerance + defaultReferenceDirectory:(NSString *)defaultReferenceDirectory + defaultImageDiffDirectory:(NSString *)defaultImageDiffDirectory +{ + return [self snapshotVerifyViewOrLayer:viewOrLayer + identifier:identifier + suffixes:suffixes + perPixelTolerance:0 + overallTolerance:overallTolerance + defaultReferenceDirectory:defaultReferenceDirectory + defaultImageDiffDirectory:defaultImageDiffDirectory]; +} + +- (NSString *)snapshotVerifyViewOrLayer:(id)viewOrLayer + identifier:(NSString *)identifier + suffixes:(NSOrderedSet *)suffixes + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + defaultReferenceDirectory:(NSString *)defaultReferenceDirectory + defaultImageDiffDirectory:(NSString *)defaultImageDiffDirectory +{ + if (viewOrLayer == nil) { + return @"Object to be snapshotted must not be nil"; + } + + NSString *referenceImageDirectory = [self getReferenceImageDirectoryWithDefault:defaultReferenceDirectory]; + if (referenceImageDirectory == nil) { + return @"Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as an Environment variable in your scheme."; + } + + NSString *imageDiffDirectory = [self getImageDiffDirectoryWithDefault:defaultImageDiffDirectory]; + if (imageDiffDirectory == nil) { + return @"Missing value for imageDiffDirectory - Set IMAGE_DIFF_DIR as an Environment variable in your scheme."; + } + + if (suffixes.count == 0) { + return [NSString stringWithFormat:@"Suffixes set cannot be empty %@", suffixes]; + } + + NSError *error = nil; + NSMutableArray *errors = [NSMutableArray array]; + + if (self.recordMode) { + NSString *referenceImagesDirectory = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffixes.firstObject]; + BOOL referenceImageSaved = [self _compareSnapshotOfViewOrLayer:viewOrLayer referenceImagesDirectory:referenceImagesDirectory imageDiffDirectory:imageDiffDirectory identifier:(identifier) perPixelTolerance:perPixelTolerance overallTolerance:overallTolerance error:&error]; + if (!referenceImageSaved) { + [errors addObject:error]; + } + + return @"Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!"; + } else { + BOOL testSuccess = NO; + for (NSString *suffix in suffixes) { + NSString *referenceImagesDirectory = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffix]; + BOOL referenceImageAvailable = [self referenceImageRecordedInDirectory:referenceImagesDirectory identifier:(identifier) error:&error]; + + if (referenceImageAvailable) { + BOOL comparisonSuccess = [self _compareSnapshotOfViewOrLayer:viewOrLayer referenceImagesDirectory:referenceImagesDirectory imageDiffDirectory:imageDiffDirectory identifier:identifier perPixelTolerance:perPixelTolerance overallTolerance:overallTolerance error:&error]; + [errors removeAllObjects]; + if (comparisonSuccess) { + testSuccess = YES; + break; + } else { + [errors addObject:error]; + } + } else { + [errors addObject:error]; + } + } + + if (!testSuccess) { + return [NSString stringWithFormat:@"Snapshot comparison failed: %@", errors.firstObject]; + } else { + return nil; + } + } +} + +- (BOOL)compareSnapshotOfLayer:(CALayer *)layer + referenceImagesDirectory:(NSString *)referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:(NSString *)identifier + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr +{ + return [self _compareSnapshotOfViewOrLayer:layer + referenceImagesDirectory:referenceImagesDirectory + imageDiffDirectory:imageDiffDirectory + identifier:identifier + perPixelTolerance:0 + overallTolerance:overallTolerance + error:errorPtr]; +} + +- (BOOL)compareSnapshotOfLayer:(CALayer *)layer + referenceImagesDirectory:(NSString *)referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:(NSString *)identifier + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr +{ + return [self _compareSnapshotOfViewOrLayer:layer + referenceImagesDirectory:referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:identifier + perPixelTolerance:perPixelTolerance + overallTolerance:overallTolerance + error:errorPtr]; +} + +- (BOOL)compareSnapshotOfView:(UIView *)view + referenceImagesDirectory:(NSString *)referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:(NSString *)identifier + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr +{ + return [self _compareSnapshotOfViewOrLayer:view + referenceImagesDirectory:referenceImagesDirectory + imageDiffDirectory:imageDiffDirectory + identifier:identifier + perPixelTolerance:0 + overallTolerance:overallTolerance + error:errorPtr]; +} + +- (BOOL)compareSnapshotOfView:(UIView *)view + referenceImagesDirectory:(NSString *)referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:(NSString *)identifier + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr +{ + return [self _compareSnapshotOfViewOrLayer:view + referenceImagesDirectory:referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:identifier + perPixelTolerance:perPixelTolerance + overallTolerance:overallTolerance + error:errorPtr]; +} + +- (BOOL)referenceImageRecordedInDirectory:(NSString *)referenceImagesDirectory + identifier:(NSString *)identifier + error:(NSError **)errorPtr +{ + NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__); + _snapshotController.referenceImagesDirectory = referenceImagesDirectory; + UIImage *referenceImage = [_snapshotController referenceImageForSelector:self.invocation.selector + identifier:identifier + error:errorPtr]; + + return (referenceImage != nil); +} + +- (NSString *)getReferenceImageDirectoryWithDefault:(NSString *)dir +{ + NSString *envReferenceImageDirectory = [NSProcessInfo processInfo].environment[@"FB_REFERENCE_IMAGE_DIR"]; + if (envReferenceImageDirectory) { + return envReferenceImageDirectory; + } + if (dir && dir.length > 0) { + return dir; + } + return [[NSBundle bundleForClass:self.class].resourcePath stringByAppendingPathComponent:@"ReferenceImages"]; +} + +- (NSString *)getImageDiffDirectoryWithDefault:(NSString *)dir +{ + NSString *envImageDiffDirectory = [NSProcessInfo processInfo].environment[@"IMAGE_DIFF_DIR"]; + if (envImageDiffDirectory) { + return envImageDiffDirectory; + } + if (dir && dir.length > 0) { + return dir; + } + return NSTemporaryDirectory(); +} + +#pragma mark - Private API + +- (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer + referenceImagesDirectory:(NSString *)referenceImagesDirectory + imageDiffDirectory:(NSString *)imageDiffDirectory + identifier:(NSString *)identifier + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr +{ + _snapshotController.referenceImagesDirectory = referenceImagesDirectory; + _snapshotController.imageDiffDirectory = imageDiffDirectory; + return [_snapshotController compareSnapshotOfViewOrLayer:viewOrLayer + selector:self.invocation.selector + identifier:identifier + perPixelTolerance:perPixelTolerance + overallTolerance:overallTolerance + error:errorPtr]; +} + +@end diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCasePlatform.h b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCasePlatform.h new file mode 100644 index 0000000..03f6d28 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCasePlatform.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017-2018, Uber Technologies, Inc. + * Copyright (c) 2015-2018, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import + +#ifdef __cplusplus +extern "C" { +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + An option mask that allows you to cherry pick which parts you want to include in the snapshot file name. + + - FBSnapshotTestCaseFileNameIncludeOptionNone: Don't include any of these options at all. + - FBSnapshotTestCaseFileNameIncludeOptionDevice: The file name should include the device name, as returned by UIDevice.currentDevice.model. + - FBSnapshotTestCaseFileNameIncludeOptionOS: The file name should include the OS version, as returned by UIDevice.currentDevice.systemVersion. + - FBSnapshotTestCaseFileNameIncludeOptionScreenSize: The file name should include the screen size of the current device, as returned by UIScreen.mainScreen.bounds.size. + - FBSnapshotTestCaseFileNameIncludeOptionScreenScale: The file name should include the scale of the current device, as returned by UIScreen.mainScreen.scale. + */ +typedef NS_OPTIONS(NSUInteger, FBSnapshotTestCaseFileNameIncludeOption) { + FBSnapshotTestCaseFileNameIncludeOptionNone = 1 << 0, + FBSnapshotTestCaseFileNameIncludeOptionDevice = 1 << 1, + FBSnapshotTestCaseFileNameIncludeOptionOS = 1 << 2, + FBSnapshotTestCaseFileNameIncludeOptionScreenSize = 1 << 3, + FBSnapshotTestCaseFileNameIncludeOptionScreenScale = 1 << 4 +}; + +/** + Returns a Boolean value that indicates whether the snapshot test is running in 64Bit. + This method is a convenience for creating the suffixes set based on the architecture + that the test is running. + + @returns @c YES if the test is running in 64bit, otherwise @c NO. + */ +BOOL FBSnapshotTestCaseIs64Bit(void); + +/** + Returns a default set of strings that is used to append a suffix based on the architectures. + @warning Do not modify this function, you can create your own and use it with @c FBSnapshotVerifyViewWithOptions() + + @returns An @c NSOrderedSet object containing strings that are appended to the reference images directory. + */ +NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void); + +/** + Returns a fully normalized file name as per the provided option mask. Strips punctuation and spaces and replaces them with @c _. + + @param fileName The file name to normalize. + @param option File Name Include options to use before normalization. + @return An @c NSString object containing the passed @c fileName and optionally, with the device model and/or OS and/or screen size and/or screen scale appended at the end. + */ +NSString *FBFileNameIncludeNormalizedFileNameFromOption(NSString *fileName, FBSnapshotTestCaseFileNameIncludeOption option); + +NS_ASSUME_NONNULL_END + +#ifdef __cplusplus +} +#endif diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCasePlatform.m b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCasePlatform.m new file mode 100644 index 0000000..3f7d9db --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestCasePlatform.m @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017-2018, Uber Technologies, Inc. + * Copyright (c) 2015-2018, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +BOOL FBSnapshotTestCaseIs64Bit(void) +{ +#if __LP64__ + return YES; +#else + return NO; +#endif +} + +NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void) +{ + if (FBSnapshotTestCaseIs64Bit()) { + return [NSOrderedSet orderedSetWithObject:@"_64"]; + } else { + return [NSOrderedSet orderedSetWithObject:@"_32"]; + } +} + +NSString *FBFileNameIncludeNormalizedFileNameFromOption(NSString *fileName, FBSnapshotTestCaseFileNameIncludeOption option) +{ + if ((option & FBSnapshotTestCaseFileNameIncludeOptionDevice) == FBSnapshotTestCaseFileNameIncludeOptionDevice) { + UIDevice *device = [UIDevice currentDevice]; + fileName = [fileName stringByAppendingFormat:@"_%@", device.model]; + } + + if ((option & FBSnapshotTestCaseFileNameIncludeOptionOS) == FBSnapshotTestCaseFileNameIncludeOptionOS) { + UIDevice *device = [UIDevice currentDevice]; + NSString *os = device.systemVersion; + fileName = [fileName stringByAppendingFormat:@"_%@", os]; + } + + if ((option & FBSnapshotTestCaseFileNameIncludeOptionScreenSize) == FBSnapshotTestCaseFileNameIncludeOptionScreenSize) { + CGSize screenSize = [UIScreen mainScreen].bounds.size; + fileName = [fileName stringByAppendingFormat:@"_%.0fx%.0f", screenSize.width, screenSize.height]; + } + + NSMutableCharacterSet *invalidCharacters = [NSMutableCharacterSet new]; + [invalidCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]]; + [invalidCharacters formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; + NSArray *validComponents = [fileName componentsSeparatedByCharactersInSet:invalidCharacters]; + fileName = [validComponents componentsJoinedByString:@"_"]; + + if ((option & FBSnapshotTestCaseFileNameIncludeOptionScreenScale) == FBSnapshotTestCaseFileNameIncludeOptionScreenScale) { + CGFloat screenScale = [[UIScreen mainScreen] scale]; + fileName = [fileName stringByAppendingFormat:@"@%.fx", screenScale]; + } + + return fileName; +} diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestController.h b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestController.h new file mode 100644 index 0000000..556361f --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestController.h @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2017-2018, Uber Technologies, Inc. + * Copyright (c) 2015-2018, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, FBSnapshotTestControllerErrorCode) { + FBSnapshotTestControllerErrorCodeUnknown, + FBSnapshotTestControllerErrorCodeNeedsRecord, + FBSnapshotTestControllerErrorCodePNGCreationFailed, + FBSnapshotTestControllerErrorCodeImagesDifferentSizes, + FBSnapshotTestControllerErrorCodeImagesDifferent, +}; + +/** + Errors returned by the methods of FBSnapshotTestController use this domain. + */ +extern NSString *const FBSnapshotTestControllerErrorDomain; + +/** + Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary. + */ +extern NSString *const FBReferenceImageFilePathKey; + +/** + Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary. + */ +extern NSString *const FBReferenceImageKey; + +/** + Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary. + */ +extern NSString *const FBCapturedImageKey; + +/** + Errors returned by the methods of FBSnapshotTestController sometimes contain this key in the `userInfo` dictionary. + */ +extern NSString *const FBDiffedImageKey; + +/** + Provides the heavy-lifting for FBSnapshotTestCase. It loads and saves images, along with performing the actual pixel- + by-pixel comparison of images. + Instances are initialized with the test class, and directories to read and write to. + */ +@interface FBSnapshotTestController : NSObject + +/** + Record snapshots. + */ +@property (readwrite, nonatomic, assign) BOOL recordMode; + +/** + When set, allows fine-grained control over what you want the file names to include. + + Allows you to combine which device or simulator specific details you want in your snapshot file names. + + The default value is FBSnapshotTestCaseFileNameIncludeOptionScreenScale. + + @discussion If you are migrating from the now deleted FBSnapshotTestCaseAgnosticOption to FBSnapshotTestCaseFileNameIncludeOption, we default to using FBSnapshotTestCaseFileNameIncludeOptionScreenScale for fileNameOptions to make the transition easier. If you don't want to have the screen scale included in your file name, you need to set fileNameOptions to a mask that doesn't include FBSnapshotTestCaseFileNameIncludeOptionScreenScale: + + self.fileNameOptions = (FBSnapshotTestCaseFileNameIncludeOptionDevice | FBSnapshotTestCaseFileNameIncludeOptionOS); + */ +@property (readwrite, nonatomic, assign) FBSnapshotTestCaseFileNameIncludeOption fileNameOptions; + +/** + Uses drawViewHierarchyInRect:afterScreenUpdates: to draw the image instead of renderInContext: + */ +@property (readwrite, nonatomic, assign) BOOL usesDrawViewHierarchyInRect; + +/** + The directory in which reference images are stored. + */ +@property (readwrite, nonatomic, copy, nullable) NSString *referenceImagesDirectory; + +/** + The directory in which failed snapshot images are stored. + */ +@property (readwrite, nonatomic, copy) NSString *imageDiffDirectory; + +/** + The name folder in which the snapshots will be saved for a given test case. +*/ +@property (readwrite, nonatomic, copy) NSString *folderName; + +/** + @param testClass The subclass of FBSnapshotTestCase that is using this controller. + @returns An instance of FBSnapshotTestController. + */ +- (instancetype)initWithTestClass:(Class)testClass; + +/** + Performs the comparison of the layer. + @param layer The Layer to snapshot. + @param selector The test method being run. + @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfLayer:(CALayer *)layer + selector:(SEL)selector + identifier:(nullable NSString *)identifier + error:(NSError **)errorPtr; + +/** + Performs the comparison of the view. + @param view The view to snapshot. + @param selector The test method being run. + @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfView:(UIView *)view + selector:(SEL)selector + identifier:(nullable NSString *)identifier + error:(NSError **)errorPtr; + +/** + Performs the comparison of a view or layer. + @param viewOrLayer The view or layer to snapshot. + @param selector The test method being run. + @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. + @param overallTolerance The percentage of pixels that can differ and still be considered 'identical'. + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer + selector:(SEL)selector + identifier:(nullable NSString *)identifier + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr; + +/** + Performs the comparison of a view or layer. + @param viewOrLayer The view or layer to snapshot. + @param selector The test method being run. + @param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method. + @param perPixelTolerance The percentage a given pixel's R,G,B and A components can differ and still be considered 'identical'. + @param overallTolerance The percentage of pixels that can differ and still be considered 'identical'. + @param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc). + @returns YES if the comparison (or saving of the reference image) succeeded. + */ +- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer + selector:(SEL)selector + identifier:(nullable NSString *)identifier + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr; + +/** + Loads a reference image. + @param selector The test method being run. + @param identifier The optional identifier, used when multiple images are tested in a single -test method. + @param errorPtr An error, if this methods returns nil, the error will be something useful. + @returns An image. + */ +- (nullable UIImage *)referenceImageForSelector:(SEL)selector + identifier:(nullable NSString *)identifier + error:(NSError **)errorPtr; + +/** + Performs a pixel-by-pixel comparison of the two images with an allowable margin of error. + @param referenceImage The reference (correct) image. + @param image The image to test against the reference. + @param overallTolerance The percentage of pixels that can differ and still be considered 'identical'. + @param errorPtr An error that indicates why the comparison failed if it does. + @returns YES if the comparison succeeded and the images are the same(ish). + */ +- (BOOL)compareReferenceImage:(UIImage *)referenceImage + toImage:(UIImage *)image + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr; + +/** + Performs a pixel-by-pixel comparison of the two images with an allowable margin of error. + @param referenceImage The reference (correct) image. + @param image The image to test against the reference. + @param perPixelTolerance The percentage a given pixel's R,G,B and A components can differ and still be considered 'identical'. + @param overallTolerance The percentage of pixels that can differ and still be considered 'identical'. + @param errorPtr An error that indicates why the comparison failed if it does. + @returns YES if the comparison succeeded and the images are the same(ish). + */ +- (BOOL)compareReferenceImage:(UIImage *)referenceImage + toImage:(UIImage *)image + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr; + +/** + Saves the reference image and the test image to `failedOutputDirectory`. + @param referenceImage The reference (correct) image. + @param testImage The image to test against the reference. + @param selector The test method being run. + @param identifier The optional identifier, used when multiple images are tested in a single -test method. + @param errorPtr An error that indicates why the comparison failed if it does. + @returns YES if the save succeeded. + */ +- (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage + testImage:(UIImage *)testImage + selector:(SEL)selector + identifier:(nullable NSString *)identifier + error:(NSError **)errorPtr; +@end + +NS_ASSUME_NONNULL_END diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestController.m b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestController.m new file mode 100644 index 0000000..f4db6e3 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/FBSnapshotTestController.m @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2017-2018, Uber Technologies, Inc. + * Copyright (c) 2015-2018, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +#import +#import +#import +#import +#import + +#import +#import + +NSString *const FBSnapshotTestControllerErrorDomain = @"FBSnapshotTestControllerErrorDomain"; +NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey"; +NSString *const FBReferenceImageKey = @"FBReferenceImageKey"; +NSString *const FBCapturedImageKey = @"FBCapturedImageKey"; +NSString *const FBDiffedImageKey = @"FBDiffedImageKey"; + +typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) { + FBTestSnapshotFileNameTypeReference, + FBTestSnapshotFileNameTypeFailedReference, + FBTestSnapshotFileNameTypeFailedTest, + FBTestSnapshotFileNameTypeFailedTestDiff, +}; + +@implementation FBSnapshotTestController { + NSFileManager *_fileManager; +} + +#pragma mark - Initializers + +- (instancetype)initWithTestClass:(Class)testClass; +{ + if (self = [super init]) { + _folderName = NSStringFromClass(testClass); + _fileNameOptions = FBSnapshotTestCaseFileNameIncludeOptionScreenScale; + + _fileManager = [[NSFileManager alloc] init]; + } + return self; +} + +#pragma mark - Overrides + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ %@", [super description], _referenceImagesDirectory]; +} + +#pragma mark - Public API + +- (BOOL)compareSnapshotOfLayer:(CALayer *)layer + selector:(SEL)selector + identifier:(NSString *)identifier + error:(NSError **)errorPtr +{ + return [self compareSnapshotOfViewOrLayer:layer + selector:selector + identifier:identifier + perPixelTolerance:0 + overallTolerance:0 + error:errorPtr]; +} + +- (BOOL)compareSnapshotOfView:(UIView *)view + selector:(SEL)selector + identifier:(NSString *)identifier + error:(NSError **)errorPtr +{ + return [self compareSnapshotOfViewOrLayer:view + selector:selector + identifier:identifier + perPixelTolerance:0 + overallTolerance:0 + error:errorPtr]; +} + +- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer + selector:(SEL)selector + identifier:(NSString *)identifier + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr +{ + return [self compareSnapshotOfViewOrLayer:viewOrLayer + selector:selector + identifier:identifier + perPixelTolerance:0 + overallTolerance:overallTolerance + error:errorPtr]; +} + + +- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer + selector:(SEL)selector + identifier:(NSString *)identifier + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr +{ + if (self.recordMode) { + return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr]; + } else { + return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier perPixelTolerance:perPixelTolerance overallTolerance:overallTolerance error:errorPtr]; + } +} + +- (UIImage *)referenceImageForSelector:(SEL)selector + identifier:(NSString *)identifier + error:(NSError **)errorPtr +{ + NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier]; + UIImage *image = [UIImage imageWithContentsOfFile:filePath]; + if (image == nil && errorPtr != NULL) { + BOOL exists = [_fileManager fileExistsAtPath:filePath]; + if (!exists) { + *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain + code:FBSnapshotTestControllerErrorCodeNeedsRecord + userInfo:@{ + FBReferenceImageFilePathKey : filePath, + NSLocalizedDescriptionKey : @"Unable to load reference image.", + NSLocalizedFailureReasonErrorKey : @"Reference image not found. You need to run the test in record mode", + }]; + } else { + *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain + code:FBSnapshotTestControllerErrorCodeUnknown + userInfo:nil]; + } + } + return image; +} + +- (BOOL)compareReferenceImage:(UIImage *)referenceImage + toImage:(UIImage *)image + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr +{ + return [self compareReferenceImage:referenceImage + toImage:image + perPixelTolerance:0 + overallTolerance:overallTolerance + error:errorPtr]; +} + +- (BOOL)compareReferenceImage:(UIImage *)referenceImage + toImage:(UIImage *)image + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr +{ + CGSize referenceImageSize = CGSizeMake(CGImageGetWidth(referenceImage.CGImage), CGImageGetHeight(referenceImage.CGImage)); + CGSize imageSize = CGSizeMake(CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage)); + + BOOL sameImageDimensions = CGSizeEqualToSize(referenceImageSize, imageSize); + if (sameImageDimensions && [referenceImage fb_compareWithImage:image perPixelTolerance:perPixelTolerance overallTolerance:overallTolerance]) { + return YES; + } + + if (errorPtr != NULL) { + NSString *errorDescription = sameImageDimensions ? @"Images different" : @"Images different sizes"; + NSString *errorReason = sameImageDimensions ? [NSString stringWithFormat:@"image pixels differed by more than %.2f%% from the reference image", overallTolerance * 100] : [NSString stringWithFormat:@"referenceImage:%@, image:%@", NSStringFromCGSize(referenceImageSize), NSStringFromCGSize(imageSize)]; + FBSnapshotTestControllerErrorCode errorCode = sameImageDimensions ? FBSnapshotTestControllerErrorCodeImagesDifferent : FBSnapshotTestControllerErrorCodeImagesDifferentSizes; + + *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain + code:errorCode + userInfo:@{ + NSLocalizedDescriptionKey : errorDescription, + NSLocalizedFailureReasonErrorKey : errorReason, + FBReferenceImageKey : referenceImage, + FBCapturedImageKey : image, + FBDiffedImageKey : [referenceImage fb_diffWithImage:image], + }]; + } + return NO; +} + +- (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage + testImage:(UIImage *)testImage + selector:(SEL)selector + identifier:(NSString *)identifier + error:(NSError **)errorPtr +{ + UIImage *diffImage = [referenceImage fb_diffWithImage:testImage]; + + [XCTContext runActivityNamed:identifier ?: NSStringFromSelector(selector) block:^(id _Nonnull activity) { + XCTAttachment *referenceAttachment = [XCTAttachment attachmentWithImage:referenceImage]; + referenceAttachment.name = @"Reference Image"; + + XCTAttachment *failedAttachment = [XCTAttachment attachmentWithImage:testImage]; + failedAttachment.name = @"Failed Image"; + + XCTAttachment *diffAttachment = [XCTAttachment attachmentWithImage:diffImage]; + diffAttachment.name = @"Diffed Image"; + + [activity addAttachment:referenceAttachment]; + [activity addAttachment:failedAttachment]; + [activity addAttachment:diffAttachment]; + }]; + + NSData *referencePNGData = UIImagePNGRepresentation(referenceImage); + NSData *testPNGData = UIImagePNGRepresentation(testImage); + + NSString *referencePath = [self _failedFilePathForSelector:selector + identifier:identifier + fileNameType:FBTestSnapshotFileNameTypeFailedReference]; + + NSError *creationError = nil; + BOOL didCreateDir = [_fileManager createDirectoryAtPath:[referencePath stringByDeletingLastPathComponent] + withIntermediateDirectories:YES + attributes:nil + error:&creationError]; + if (!didCreateDir) { + if (errorPtr != NULL) { + *errorPtr = creationError; + } + return NO; + } + + if (![referencePNGData writeToFile:referencePath options:NSDataWritingAtomic error:errorPtr]) { + return NO; + } + + NSString *testPath = [self _failedFilePathForSelector:selector + identifier:identifier + fileNameType:FBTestSnapshotFileNameTypeFailedTest]; + + if (![testPNGData writeToFile:testPath options:NSDataWritingAtomic error:errorPtr]) { + return NO; + } + + NSString *diffPath = [self _failedFilePathForSelector:selector + identifier:identifier + fileNameType:FBTestSnapshotFileNameTypeFailedTestDiff]; + + NSData *diffImageData = UIImagePNGRepresentation(diffImage); + + if (![diffImageData writeToFile:diffPath options:NSDataWritingAtomic error:errorPtr]) { + return NO; + } + + NSLog(@"If you have Kaleidoscope installed you can run this command to see an image diff:\n" + @"ksdiff \"%@\" \"%@\"", + referencePath, testPath); + + return YES; +} + +#pragma mark - Private API + +- (NSString *)_fileNameForSelector:(SEL)selector + identifier:(NSString *)identifier + fileNameType:(FBTestSnapshotFileNameType)fileNameType +{ + NSString *fileName = nil; + switch (fileNameType) { + case FBTestSnapshotFileNameTypeFailedReference: + fileName = @"reference_"; + break; + case FBTestSnapshotFileNameTypeFailedTest: + fileName = @"failed_"; + break; + case FBTestSnapshotFileNameTypeFailedTestDiff: + fileName = @"diff_"; + break; + default: + fileName = @""; + break; + } + fileName = [fileName stringByAppendingString:NSStringFromSelector(selector)]; + if (0 < identifier.length) { + fileName = [fileName stringByAppendingFormat:@"_%@", identifier]; + } + + BOOL noFileNameOption = (self.fileNameOptions & FBSnapshotTestCaseFileNameIncludeOptionNone) == FBSnapshotTestCaseFileNameIncludeOptionNone; + if (!noFileNameOption) { + fileName = FBFileNameIncludeNormalizedFileNameFromOption(fileName, self.fileNameOptions); + } + + fileName = [fileName stringByAppendingPathExtension:@"png"]; + return fileName; +} + +- (NSString *)_referenceFilePathForSelector:(SEL)selector + identifier:(NSString *)identifier +{ + NSString *fileName = [self _fileNameForSelector:selector + identifier:identifier + fileNameType:FBTestSnapshotFileNameTypeReference]; + NSString *filePath = [_referenceImagesDirectory stringByAppendingPathComponent:self.folderName]; + filePath = [filePath stringByAppendingPathComponent:fileName]; + return filePath; +} + +- (NSString *)_failedFilePathForSelector:(SEL)selector + identifier:(NSString *)identifier + fileNameType:(FBTestSnapshotFileNameType)fileNameType +{ + NSString *fileName = [self _fileNameForSelector:selector + identifier:identifier + fileNameType:fileNameType]; + + NSString *filePath = [_imageDiffDirectory stringByAppendingPathComponent:self.folderName]; + filePath = [filePath stringByAppendingPathComponent:fileName]; + return filePath; +} + +- (BOOL)_performPixelComparisonWithViewOrLayer:(id)viewOrLayer + selector:(SEL)selector + identifier:(NSString *)identifier + perPixelTolerance:(CGFloat)perPixelTolerance + overallTolerance:(CGFloat)overallTolerance + error:(NSError **)errorPtr +{ + UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr]; + if (referenceImage != nil) { + UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer]; + BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot perPixelTolerance:perPixelTolerance overallTolerance:overallTolerance error:errorPtr]; + if (!imagesSame) { + NSError *saveError = nil; + if ([self saveFailedReferenceImage:referenceImage testImage:snapshot selector:selector identifier:identifier error:&saveError] == NO) { + NSLog(@"Error saving test images: %@", saveError); + } + } + return imagesSame; + } + return NO; +} + +- (BOOL)_recordSnapshotOfViewOrLayer:(id)viewOrLayer + selector:(SEL)selector + identifier:(NSString *)identifier + error:(NSError **)errorPtr +{ + UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer]; + + [XCTContext runActivityNamed:identifier ?: NSStringFromSelector(selector) block:^(id _Nonnull activity) { + XCTAttachment *recordedAttachment = [XCTAttachment attachmentWithImage:snapshot]; + recordedAttachment.name = @"Recorded Image"; + [activity addAttachment:recordedAttachment]; + }]; + + return [self _saveReferenceImage:snapshot selector:selector identifier:identifier error:errorPtr]; +} + +- (BOOL)_saveReferenceImage:(UIImage *)image + selector:(SEL)selector + identifier:(NSString *)identifier + error:(NSError **)errorPtr +{ + BOOL didWrite = NO; + if (image != nil) { + NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier]; + NSData *pngData = UIImagePNGRepresentation(image); + if (pngData != nil) { + NSError *creationError = nil; + BOOL didCreateDir = [_fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent] + withIntermediateDirectories:YES + attributes:nil + error:&creationError]; + if (!didCreateDir) { + if (errorPtr != NULL) { + *errorPtr = creationError; + } + return NO; + } + didWrite = [pngData writeToFile:filePath options:NSDataWritingAtomic error:errorPtr]; + if (didWrite) { + NSLog(@"Reference image save at: %@", filePath); + } + } else { + if (errorPtr != nil) { + *errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain + code:FBSnapshotTestControllerErrorCodePNGCreationFailed + userInfo:@{ + FBReferenceImageFilePathKey : filePath, + }]; + } + } + } + return didWrite; +} + +- (UIImage *)_imageForViewOrLayer:(id)viewOrLayer +{ + if ([viewOrLayer isKindOfClass:[UIView class]]) { + if (_usesDrawViewHierarchyInRect) { + return [UIImage fb_imageForView:viewOrLayer]; + } else { + return [UIImage fb_imageForViewLayer:viewOrLayer]; + } + } else if ([viewOrLayer isKindOfClass:[CALayer class]]) { + return [UIImage fb_imageForLayer:viewOrLayer]; + } else { + [NSException raise:@"Only UIView and CALayer classes can be snapshotted" format:@"%@", viewOrLayer]; + } + return nil; +} + +@end diff --git a/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/SwiftSupport.swift b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/SwiftSupport.swift new file mode 100644 index 0000000..61f0104 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/FBSnapshotTestCase/SwiftSupport.swift @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017-2018, Uber Technologies, Inc. + * Copyright (c) 2015-2018, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +public extension FBSnapshotTestCase { + func FBSnapshotVerifyView(_ view: UIView, identifier: String? = nil, suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), perPixelTolerance: CGFloat = 0, overallTolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, perPixelTolerance: perPixelTolerance, overallTolerance: overallTolerance, file: file, line: line) + } + + func FBSnapshotVerifyViewController(_ viewController: UIViewController, identifier: String? = nil, suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), perPixelTolerance: CGFloat = 0, overallTolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + viewController.view.bounds = UIScreen.main.bounds + viewController.viewWillAppear(false) + viewController.viewDidAppear(false) + + FBSnapshotVerifyView(viewController.view, identifier: identifier, suffixes: suffixes, perPixelTolerance: perPixelTolerance, overallTolerance: overallTolerance, file: file, line: line) + } + + func FBSnapshotVerifyLayer(_ layer: CALayer, identifier: String? = nil, suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), perPixelTolerance: CGFloat = 0, overallTolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, perPixelTolerance: perPixelTolerance, overallTolerance: overallTolerance, file: file, line: line) + } + + private func FBSnapshotVerifyViewOrLayer(_ viewOrLayer: AnyObject, identifier: String? = nil, suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), perPixelTolerance: CGFloat = 0, overallTolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { + let envReferenceImageDirectory = self.getReferenceImageDirectory(withDefault: nil) + let envImageDiffDirectory = self.getImageDiffDirectory(withDefault: nil) + var error: NSError? + var comparisonSuccess = false + + for suffix in suffixes { + let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)" + let imageDiffDirectory = envImageDiffDirectory + if viewOrLayer.isKind(of: UIView.self) { + do { + try compareSnapshot(of: viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, imageDiffDirectory: imageDiffDirectory, identifier: identifier, perPixelTolerance: perPixelTolerance, overallTolerance: overallTolerance) + comparisonSuccess = true + } catch let error1 as NSError { + error = error1 + comparisonSuccess = false + } + } else if viewOrLayer.isKind(of: CALayer.self) { + do { + try compareSnapshot(of: viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, imageDiffDirectory: imageDiffDirectory, identifier: identifier, perPixelTolerance: perPixelTolerance, overallTolerance: overallTolerance) + comparisonSuccess = true + } catch let error1 as NSError { + error = error1 + comparisonSuccess = false + } + } else { + assertionFailure("Only UIView and CALayer classes can be snapshotted") + } + + assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line) + + if comparisonSuccess || recordMode { + break + } + + assert(comparisonSuccess, message: "Snapshot comparison failed: \(String(describing: error))", file: file, line: line) + } + } + + func assert(_ assertion: Bool, message: String, file: StaticString, line: UInt) { + if !assertion { + XCTFail(message, file: file, line: line) + } + } +} diff --git a/Example/Pods/iOSSnapshotTestCase/LICENSE b/Example/Pods/iOSSnapshotTestCase/LICENSE new file mode 100644 index 0000000..1b3cb06 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2017-2018, Uber Technologies, Inc. +Copyright (c) 2013-2018, Facebook, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 THE +AUTHORS OR COPYRIGHT HOLDERS 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. diff --git a/Example/Pods/iOSSnapshotTestCase/README.md b/Example/Pods/iOSSnapshotTestCase/README.md new file mode 100644 index 0000000..157b5c1 --- /dev/null +++ b/Example/Pods/iOSSnapshotTestCase/README.md @@ -0,0 +1,108 @@ +# iOSSnapshotTestCase (previously [FBSnapshotTestCase](https://github.com/facebookarchive/ios-snapshot-test-case)) + +[![Build Status](https://travis-ci.org/uber/ios-snapshot-test-case.svg)](https://travis-ci.org/uber/ios-snapshot-test-case) +[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/iOSSnapshotTestCase.svg)](https://img.shields.io/cocoapods/v/iOSSnapshotTestCase.svg) +[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) + +## What it does + +A "snapshot test case" takes a configured `UIView` or `CALayer` and uses the necessary UIKit or Core Animation methods to generate an image snapshot of its contents. It +compares this snapshot to a "reference image" stored in your source code +repository and fails the test if the two images don't match. + +## Why? + +We write a lot of UI code. There are a lot of edge +cases that we want to handle correctly when you are creating `UIView` instances: + +- What if there is more text than can fit in the space available? +- What if an image doesn't match the size of an image view? +- What should the highlighted state look like? + +It's straightforward to test logic code, but less obvious how you should test +views. You can do a lot of rectangle asserts, but these are hard to understand +or visualize. Looking at an image diff shows you exactly what changed and how +it will look to users. + +`iOSSnapshotTestCase` was developed to make snapshot tests easy. + +## Installation + +### Step 1: Add iOSSnapshotTestCase to your project + +#### CocoaPods + +Add the following lines to your Podfile: + +```ruby +target "Tests" do + use_frameworks! + pod 'iOSSnapshotTestCase' +end +``` + +If your test target is Objective-C only use `iOSSnapshotTestCase/Core` instead, which doesn't contain Swift support. + +#### Carthage + +Add the following line to your Cartfile: + +```carthage +github "uber/ios-snapshot-test-case" ~> 6.1.0 +``` + +### Step 2: Setup Test Scheme +Replace "Tests" with the name of your test project. + +1. There are [three ways](https://github.com/uber/ios-snapshot-test-case/blob/master/FBSnapshotTestCase/FBSnapshotTestCase.h#L19-L29) of setting reference image directories, the recommended one is to define `FB_REFERENCE_IMAGE_DIR` in your scheme. This should point to the directory where you want reference images to be stored. We normally use this: + +|Name|Value| +|:---|:----| +|`FB_REFERENCE_IMAGE_DIR`|`$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages`| +|`IMAGE_DIFF_DIR`|`$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/FailureDiffs`| + +Define the `IMAGE_DIFF_DIR` to the directory where you want to store diffs of failed snapshots. There are also [three ways](https://github.com/uber/ios-snapshot-test-case/blob/master/FBSnapshotTestCase/FBSnapshotTestCase.h#L34-L43) to set failed image diff directories. + +![](FBSnapshotTestCaseDemo/Scheme_FB_REFERENCE_IMAGE_DIR.png) + +## Creating a snapshot test + +1. Subclass `FBSnapshotTestCase` instead of `XCTestCase`. +2. From within your test, use `FBSnapshotVerifyView`. +3. Run the test once with `self.recordMode = YES;` in the test's `-setUp` + method. (This creates the reference images on disk.) +4. Remove the line enabling record mode and run the test. + +## Features + +- Automatically names reference images on disk according to test class and + selector. +- Prints a descriptive error message to the console on failure. (Bonus: + failure message includes a one-line command to see an image diff if + you have [Kaleidoscope](http://www.kaleidoscopeapp.com) installed.) +- Supply an optional "identifier" if you want to perform multiple snapshots + in a single test method. +- Support for `CALayer` via `FBSnapshotVerifyLayer`. +- `usesDrawViewHierarchyInRect` to handle cases like `UIVisualEffect`, `UIAppearance` and Size Classes. +- `fileNameOptions` to control appending the device model (`iPhone`, `iPad`, `iPod Touch`, etc), OS version, screen size and screen scale to the images (allowing to have multiple tests for the same «snapshot» for different `OS`s and devices). + +## Notes + +Your unit tests _should_ be inside an "application" bundle, not a "logic/library" test bundle. (That is, it +should be run within the Simulator so that it has access to UIKit.) + +*However*, if you are writing snapshot tests inside a library/framework, you might want to keep your test bundle as a library test bundle without a Test Host. + +Read more on this [here](docs/LibraryVsApplicationTestBundles.md). + +## Authors + +`iOSSnapshotTestCase` was written at Facebook by +Jonathan Dann with significant contributions by +Todd Krabach. + +Today it is maintained by [Uber](https://github.com/uber). + +## License + +`iOSSnapshotTestCase` is MIT–licensed. See [`LICENSE`](https://github.com/uber/ios-snapshot-test-case/blob/master/LICENSE). diff --git a/Example/Pods/lottie-ios/LICENSE b/Example/Pods/lottie-ios/LICENSE new file mode 100644 index 0000000..55bb178 --- /dev/null +++ b/Example/Pods/lottie-ios/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Airbnb, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Example/Pods/lottie-ios/README.md b/Example/Pods/lottie-ios/README.md new file mode 100644 index 0000000..435c99e --- /dev/null +++ b/Example/Pods/lottie-ios/README.md @@ -0,0 +1,109 @@ +# Lottie for iOS, macOS (and [Android](https://github.com/airbnb/lottie-android) and [React Native](https://github.com/airbnb/lottie-react-native)) +[![Version](https://img.shields.io/cocoapods/v/lottie-ios.svg?style=flat)](https://cocoapods.org/pods/lottie-ios)[![License](https://img.shields.io/cocoapods/l/lottie-ios.svg?style=flat)](https://cocoapods.org/pods/lottie-ios)[![Platform](https://img.shields.io/cocoapods/p/lottie-ios.svg?style=flat)](https://cocoapods.org/pods/lottie-ios) + +# View documentation, FAQ, help, examples, and more at [airbnb.io/lottie](http://airbnb.io/lottie/) + +Lottie is a mobile library for Android and iOS that natively renders vector based animations and art in realtime with minimal code. + +Lottie loads and renders animations and vectors exported in the bodymovin JSON format. Bodymovin JSON can be created and exported from After Effects with [bodymovin](https://github.com/bodymovin/bodymovin), Sketch with [Lottie Sketch Export](https://github.com/buba447/Lottie-Sketch-Export), and from [Haiku](https://www.haiku.ai). + +For the first time, designers can create **and ship** beautiful animations without an engineer painstakingly recreating it by hand. +Since the animation is backed by JSON they are extremely small in size but can be large in complexity! +Animations can be played, resized, looped, sped up, slowed down, reversed, and even interactively scrubbed. +Lottie can play or loop just a portion of the animation as well, the possibilities are endless! +Animations can even be ***changed at runtime*** in various ways! Change the color, position or any keyframable value! +Lottie also supports native UIViewController Transitions out of the box! + +Here is just a small sampling of the power of Lottie + +![Example1](_Gifs/Examples1.gif) +![Example2](_Gifs/Examples2.gif) + + + +![Example3](_Gifs/Examples3.gif) + +![Abcs](_Gifs/Examples4.gif) + +## Installing Lottie +Lottie supports [CocoaPods](https://cocoapods.org/) and [Carthage](https://github.com/Carthage/Carthage) (Both dynamic and static). Lottie is written in ***Swift 4.2***. +### Github Repo + +You can pull the [Lottie Github Repo](https://github.com/airbnb/lottie-ios/) and include the Lottie.xcodeproj to build a dynamic or static library. + +### CocoaPods +Add the pod to your Podfile: +```ruby +pod 'lottie-ios' +``` + +And then run: +```ruby +pod install +``` +After installing the cocoapod into your project import Lottie with +```swift +import Lottie +``` +### Carthage +Add Lottie to your Cartfile: +``` +github "airbnb/lottie-ios" "master" +``` + +And then run: +``` +carthage update +``` +In your application targets “General” tab under the “Linked Frameworks and Libraries” section, drag and drop lottie-ios.framework from the Carthage/Build/iOS directory that `carthage update` produced. + +### Swift Package Manager +``` swift +// swift-tools-version:5.1 + +import PackageDescription + +let package = Package( + name: "YourTestProject", + platforms: [ + .iOS(.v12), + ], + dependencies: [ + .package(url: "https://github.com/airbnb/lottie-ios.git", from: "3.1.2") + ], + targets: [ + .target(name: "YourTestProject", dependencies: ["Lottie"]) + ] +) +``` +And then import wherever needed: ```import Lottie``` + +#### Adding it to an existent iOS Project via Swift Package Manager + +1. Using Xcode 11 go to File > Swift Packages > Add Package Dependency +2. Paste the project URL: https://github.com/airbnb/lottie-ios +3. Click on next and select the project target + **NOTE: For MacOS you must set the `Branch` field to `lottie/macos-spm` + ![Example](_Gifs/spm-branch.png) + +If you have doubts, please, check the following links: + +[How to use](https://developer.apple.com/videos/play/wwdc2019/408/) + +[Creating Swift Packages](https://developer.apple.com/videos/play/wwdc2019/410/) + +After successfully retrieved the package and added it to your project, just import `Lottie` and you can get the full benefits of it. + +### Objective-C Support + +As of 3.0 Lottie has been completely rewritten in Swift! + +For Objective-C support please use Lottie 2.5.3. Alternatively an Objective-C branch exists and is still active. + +The official objective c branch can be found here: + +[Objective-C Branch](https://github.com/airbnb/lottie-ios/tree/lottie/objectiveC) + +Also check out the documentation regarding it here: + +[iOS Migration](http://airbnb.io/lottie/#/ios-migration) diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/AnimationContainer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/AnimationContainer.swift new file mode 100644 index 0000000..643e0e0 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/AnimationContainer.swift @@ -0,0 +1,199 @@ +// +// AnimationContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/24/19. +// + +import Foundation +import QuartzCore + +/** + The base animation container. + + This layer holds a single composition container and allows for animation of + the currentFrame property. + */ +final class AnimationContainer: CALayer { + + /// The animatable Current Frame Property + @NSManaged var currentFrame: CGFloat + + var imageProvider: AnimationImageProvider { + get { + return layerImageProvider.imageProvider + } + set { + layerImageProvider.imageProvider = newValue + } + } + + func reloadImages() { + layerImageProvider.reloadImages() + } + + var renderScale: CGFloat = 1 { + didSet { + animationLayers.forEach({ $0.renderScale = renderScale }) + } + } + + public var respectAnimationFrameRate: Bool = false + + /// Forces the view to update its drawing. + func forceDisplayUpdate() { + animationLayers.forEach( { $0.displayWithFrame(frame: currentFrame, forceUpdates: true) }) + } + + func logHierarchyKeypaths() { + print("Lottie: Logging Animation Keypaths") + animationLayers.forEach({ $0.logKeypaths(for: nil) }) + } + + func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + for layer in animationLayers { + if let foundProperties = layer.nodeProperties(for: keypath) { + for property in foundProperties { + property.setProvider(provider: valueProvider) + } + layer.displayWithFrame(frame: presentation()?.currentFrame ?? currentFrame, forceUpdates: true) + } + } + } + + func getValue(for keypath: AnimationKeypath, atFrame: CGFloat?) -> Any? { + for layer in animationLayers { + if let foundProperties = layer.nodeProperties(for: keypath), + let first = foundProperties.first { + return first.valueProvider.value(frame: atFrame ?? currentFrame) + } + } + return nil + } + + func layer(for keypath: AnimationKeypath) -> CALayer? { + for layer in animationLayers { + if let foundLayer = layer.layer(for: keypath) { + return foundLayer + } + } + return nil + } + + func animatorNodes(for keypath: AnimationKeypath) -> [AnimatorNode]? { + var results = [AnimatorNode]() + for layer in animationLayers { + if let nodes = layer.animatorNodes(for: keypath) { + results.append(contentsOf: nodes) + } + } + if results.count == 0 { + return nil + } + return results + } + + var textProvider: AnimationTextProvider { + get { return layerTextProvider.textProvider } + set { layerTextProvider.textProvider = newValue } + } + + var animationLayers: ContiguousArray + fileprivate let layerImageProvider: LayerImageProvider + fileprivate let layerTextProvider: LayerTextProvider + + init(animation: Animation, imageProvider: AnimationImageProvider, textProvider: AnimationTextProvider) { + self.layerImageProvider = LayerImageProvider(imageProvider: imageProvider, assets: animation.assetLibrary?.imageAssets) + self.layerTextProvider = LayerTextProvider(textProvider: textProvider) + self.animationLayers = [] + super.init() + bounds = animation.bounds + let layers = animation.layers.initializeCompositionLayers(assetLibrary: animation.assetLibrary, layerImageProvider: layerImageProvider, textProvider: textProvider, frameRate: CGFloat(animation.framerate)) + + var imageLayers = [ImageCompositionLayer]() + var textLayers = [TextCompositionLayer]() + + var mattedLayer: CompositionLayer? = nil + + for layer in layers.reversed() { + layer.bounds = bounds + animationLayers.append(layer) + if let imageLayer = layer as? ImageCompositionLayer { + imageLayers.append(imageLayer) + } + if let textLayer = layer as? TextCompositionLayer { + textLayers.append(textLayer) + } + if let matte = mattedLayer { + /// The previous layer requires this layer to be its matte + matte.matteLayer = layer + mattedLayer = nil + continue + } + if let matte = layer.matteType, + (matte == .add || matte == .invert) { + /// We have a layer that requires a matte. + mattedLayer = layer + } + addSublayer(layer) + } + + layerImageProvider.addImageLayers(imageLayers) + layerImageProvider.reloadImages() + layerTextProvider.addTextLayers(textLayers) + layerTextProvider.reloadTexts() + setNeedsDisplay() + } + + /// For CAAnimation Use + public override init(layer: Any) { + self.animationLayers = [] + self.layerImageProvider = LayerImageProvider(imageProvider: BlankImageProvider(), assets: nil) + self.layerTextProvider = LayerTextProvider(textProvider: DefaultTextProvider()) + super.init(layer: layer) + + guard let animationLayer = layer as? AnimationContainer else { return } + + currentFrame = animationLayer.currentFrame + + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: CALayer Animations + + override public class func needsDisplay(forKey key: String) -> Bool { + if key == "currentFrame" { + return true + } + return super.needsDisplay(forKey: key) + } + + override public func action(forKey event: String) -> CAAction? { + if event == "currentFrame" { + let animation = CABasicAnimation(keyPath: event) + animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + animation.fromValue = self.presentation()?.currentFrame + return animation + } + return super.action(forKey: event) + } + + public override func display() { + guard Thread.isMainThread else { return } + var newFrame: CGFloat = self.presentation()?.currentFrame ?? self.currentFrame + if respectAnimationFrameRate { + newFrame = floor(newFrame) + } + animationLayers.forEach( { $0.displayWithFrame(frame: newFrame, forceUpdates: false) }) + } + +} + +fileprivate class BlankImageProvider: AnimationImageProvider { + func imageForAsset(asset: ImageAsset) -> CGImage? { + return nil + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/CompositionLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/CompositionLayer.swift new file mode 100644 index 0000000..dd947ec --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/CompositionLayer.swift @@ -0,0 +1,153 @@ +// +// LayerContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation +import QuartzCore + +/** + The base class for a child layer of CompositionContainer + */ +class CompositionLayer: CALayer, KeypathSearchable { + + weak var layerDelegate: CompositionLayerDelegate? + + let transformNode: LayerTransformNode + + let contentsLayer: CALayer = CALayer() + + let maskLayer: MaskContainerLayer? + + let matteType: MatteType? + + var renderScale: CGFloat = 1 { + didSet { + self.updateRenderScale() + } + } + + var matteLayer: CompositionLayer? { + didSet { + if let matte = matteLayer { + if let type = matteType, type == .invert { + mask = InvertedMatteLayer(inputMatte: matte) + } else { + mask = matte + } + } else { + mask = nil + } + } + } + + let inFrame: CGFloat + let outFrame: CGFloat + let startFrame: CGFloat + let timeStretch: CGFloat + + init(layer: LayerModel, size: CGSize) { + self.transformNode = LayerTransformNode(transform: layer.transform) + if let masks = layer.masks { + maskLayer = MaskContainerLayer(masks: masks) + } else { + maskLayer = nil + } + self.matteType = layer.matte + self.inFrame = layer.inFrame.cgFloat + self.outFrame = layer.outFrame.cgFloat + self.timeStretch = layer.timeStretch.cgFloat + self.startFrame = layer.startTime.cgFloat + self.keypathName = layer.name + self.childKeypaths = [transformNode.transformProperties] + super.init() + self.anchorPoint = .zero + self.actions = [ + "opacity" : NSNull(), + "transform" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "sublayerTransform" : NSNull() + ] + + contentsLayer.anchorPoint = .zero + contentsLayer.bounds = CGRect(origin: .zero, size: size) + contentsLayer.actions = [ + "opacity" : NSNull(), + "transform" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "sublayerTransform" : NSNull(), + "hidden" : NSNull() + ] + addSublayer(contentsLayer) + + if let maskLayer = maskLayer { + contentsLayer.mask = maskLayer + } + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? CompositionLayer else { + fatalError("Wrong Layer Class") + } + self.transformNode = layer.transformNode + self.matteType = layer.matteType + self.inFrame = layer.inFrame + self.outFrame = layer.outFrame + self.timeStretch = layer.timeStretch + self.startFrame = layer.startFrame + self.keypathName = layer.keypathName + self.childKeypaths = [transformNode.transformProperties] + self.maskLayer = nil + super.init(layer: layer) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + final func displayWithFrame(frame: CGFloat, forceUpdates: Bool) { + transformNode.updateTree(frame, forceUpdates: forceUpdates) + let layerVisible = frame.isInRangeOrEqual(inFrame, outFrame) + /// Only update contents if current time is within the layers time bounds. + if layerVisible { + displayContentsWithFrame(frame: frame, forceUpdates: forceUpdates) + maskLayer?.updateWithFrame(frame: frame, forceUpdates: forceUpdates) + } + contentsLayer.transform = transformNode.globalTransform + contentsLayer.opacity = transformNode.opacity + contentsLayer.isHidden = !layerVisible + layerDelegate?.frameUpdated(frame: frame) + } + + func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + /// To be overridden by subclass + } + + // MARK: Keypath Searchable + + let keypathName: String + + var keypathProperties: [String : AnyNodeProperty] { + return [:] + } + + final var childKeypaths: [KeypathSearchable] + + var keypathLayer: CALayer? { + return contentsLayer + } + + func updateRenderScale() { + self.contentsScale = self.renderScale + } +} + +protocol CompositionLayerDelegate: class { + func frameUpdated(frame: CGFloat) +} + diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/ImageCompositionLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/ImageCompositionLayer.swift new file mode 100644 index 0000000..1f51b6b --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/ImageCompositionLayer.swift @@ -0,0 +1,47 @@ +// +// ImageCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import CoreGraphics +import QuartzCore + +final class ImageCompositionLayer: CompositionLayer { + + var image: CGImage? = nil { + didSet { + if let image = image { + contentsLayer.contents = image + } else { + contentsLayer.contents = nil + } + } + } + + let imageReferenceID: String + + init(imageLayer: ImageLayerModel, size: CGSize) { + self.imageReferenceID = imageLayer.referenceID + super.init(layer: imageLayer, size: size) + contentsLayer.masksToBounds = true + contentsLayer.contentsGravity = CALayerContentsGravity.resize + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? ImageCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + self.imageReferenceID = layer.imageReferenceID + self.image = nil + super.init(layer: layer) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/MaskContainerLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/MaskContainerLayer.swift new file mode 100644 index 0000000..753a8db --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/MaskContainerLayer.swift @@ -0,0 +1,168 @@ +// +// MaskContainerLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import QuartzCore + +extension MaskMode { + var usableMode: MaskMode { + switch self { + case .add: + return .add + case .subtract: + return .subtract + case .intersect: + return .intersect + case .lighten: + return .add + case .darken: + return .darken + case .difference: + return .intersect + case .none: + return .none + } + } +} + +final class MaskContainerLayer: CALayer { + + init(masks: [Mask]) { + super.init() + anchorPoint = .zero + var containerLayer = CALayer() + var firstObject: Bool = true + for mask in masks { + let maskLayer = MaskLayer(mask: mask) + maskLayers.append(maskLayer) + if mask.mode.usableMode == .none { + continue + } else if mask.mode.usableMode == .add || firstObject { + firstObject = false + containerLayer.addSublayer(maskLayer) + } else { + containerLayer.mask = maskLayer + let newContainer = CALayer() + newContainer.addSublayer(containerLayer) + containerLayer = newContainer + } + } + addSublayer(containerLayer) + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? MaskContainerLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + super.init(layer: layer) + } + + fileprivate var maskLayers: [MaskLayer] = [] + + func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { + maskLayers.forEach({ $0.updateWithFrame(frame: frame, forceUpdates: forceUpdates) }) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension CGRect { + static var veryLargeRect: CGRect { + return CGRect(x: -100_000_000, + y: -100_000_000, + width: 200_000_000, + height: 200_000_000) + } +} + +fileprivate class MaskLayer: CALayer { + + let properties: MaskNodeProperties? + + let maskLayer = CAShapeLayer() + + init(mask: Mask) { + self.properties = MaskNodeProperties(mask: mask) + super.init() + addSublayer(maskLayer) + anchorPoint = .zero + maskLayer.fillColor = mask.mode == .add ? CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 0, 0, 1]) : + CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 1, 0, 1]) + maskLayer.fillRule = CAShapeLayerFillRule.evenOdd + self.actions = [ + "opacity" : NSNull() + ] + + } + + override init(layer: Any) { + self.properties = nil + super.init(layer: layer) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { + guard let properties = properties else { return } + if properties.opacity.needsUpdate(frame: frame) || forceUpdates { + properties.opacity.update(frame: frame) + self.opacity = Float(properties.opacity.value.cgFloatValue) + } + + if properties.shape.needsUpdate(frame: frame) || forceUpdates { + properties.shape.update(frame: frame) + properties.expansion.update(frame: frame) + + let shapePath = properties.shape.value.cgPath() + var path = shapePath + if properties.mode.usableMode == .subtract && !properties.inverted || + (properties.mode.usableMode == .add && properties.inverted) { + /// Add a bounds rect to invert the mask + let newPath = CGMutablePath() + newPath.addRect(CGRect.veryLargeRect) + newPath.addPath(shapePath) + path = newPath + } + maskLayer.path = path + } + + } +} + +fileprivate class MaskNodeProperties: NodePropertyMap { + + var propertyMap: [String : AnyNodeProperty] + + var properties: [AnyNodeProperty] + + init(mask: Mask) { + self.mode = mask.mode + self.inverted = mask.inverted + self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.opacity.keyframes)) + self.shape = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.shape.keyframes)) + self.expansion = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.expansion.keyframes)) + self.propertyMap = [ + "Opacity" : opacity, + "Shape" : shape, + "Expansion" : expansion + ] + self.properties = Array(self.propertyMap.values) + } + + let mode: MaskMode + let inverted: Bool + + let opacity: NodeProperty + let shape: NodeProperty + let expansion: NodeProperty +} + diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/NullCompositionLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/NullCompositionLayer.swift new file mode 100644 index 0000000..054e245 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/NullCompositionLayer.swift @@ -0,0 +1,28 @@ +// +// NullCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation + +final class NullCompositionLayer: CompositionLayer { + + init(layer: LayerModel) { + super.init(layer: layer, size: .zero) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? NullCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + super.init(layer: layer) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/PreCompositionLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/PreCompositionLayer.swift new file mode 100644 index 0000000..078070a --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/PreCompositionLayer.swift @@ -0,0 +1,103 @@ +// +// PreCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import QuartzCore + +final class PreCompositionLayer: CompositionLayer { + + let frameRate: CGFloat + let remappingNode: NodeProperty? + fileprivate var animationLayers: [CompositionLayer] + + init(precomp: PreCompLayerModel, + asset: PrecompAsset, + layerImageProvider: LayerImageProvider, + textProvider: AnimationTextProvider, + assetLibrary: AssetLibrary?, + frameRate: CGFloat) { + self.animationLayers = [] + if let keyframes = precomp.timeRemapping?.keyframes { + self.remappingNode = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframes)) + } else { + self.remappingNode = nil + } + self.frameRate = frameRate + super.init(layer: precomp, size: CGSize(width: precomp.width, height: precomp.height)) + contentsLayer.masksToBounds = true + contentsLayer.bounds = CGRect(origin: .zero, size: CGSize(width: precomp.width, height: precomp.height)) + + let layers = asset.layers.initializeCompositionLayers(assetLibrary: assetLibrary, layerImageProvider: layerImageProvider, textProvider: textProvider, frameRate: frameRate) + + var imageLayers = [ImageCompositionLayer]() + + var mattedLayer: CompositionLayer? = nil + + for layer in layers.reversed() { + layer.bounds = bounds + animationLayers.append(layer) + if let imageLayer = layer as? ImageCompositionLayer { + imageLayers.append(imageLayer) + } + if let matte = mattedLayer { + /// The previous layer requires this layer to be its matte + matte.matteLayer = layer + mattedLayer = nil + continue + } + if let matte = layer.matteType, + (matte == .add || matte == .invert) { + /// We have a layer that requires a matte. + mattedLayer = layer + } + contentsLayer.addSublayer(layer) + } + + self.childKeypaths.append(contentsOf: layers) + + layerImageProvider.addImageLayers(imageLayers) + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? PreCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + self.frameRate = layer.frameRate + self.remappingNode = nil + self.animationLayers = [] + + super.init(layer: layer) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + let localFrame: CGFloat + if let remappingNode = remappingNode { + remappingNode.update(frame: frame) + localFrame = remappingNode.value.cgFloatValue * frameRate + } else { + localFrame = (frame - startFrame) / timeStretch + } + animationLayers.forEach( { $0.displayWithFrame(frame: localFrame, forceUpdates: forceUpdates) }) + } + + override var keypathProperties: [String : AnyNodeProperty] { + guard let remappingNode = remappingNode else { + return super.keypathProperties + } + return ["Time Remap" : remappingNode] + } + + override func updateRenderScale() { + super.updateRenderScale() + animationLayers.forEach( { $0.renderScale = renderScale } ) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/ShapeCompositionLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/ShapeCompositionLayer.swift new file mode 100644 index 0000000..7a4616d --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/ShapeCompositionLayer.swift @@ -0,0 +1,56 @@ +// +// ShapeLayerContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation +import CoreGraphics + +/** + A CompositionLayer responsible for initializing and rendering shapes + */ +final class ShapeCompositionLayer: CompositionLayer { + + let rootNode: AnimatorNode? + let renderContainer: ShapeContainerLayer? + + init(shapeLayer: ShapeLayerModel) { + let results = shapeLayer.items.initializeNodeTree() + let renderContainer = ShapeContainerLayer() + self.renderContainer = renderContainer + self.rootNode = results.rootNode + super.init(layer: shapeLayer, size: .zero) + contentsLayer.addSublayer(renderContainer) + for container in results.renderContainers { + renderContainer.insertRenderLayer(container) + } + rootNode?.updateTree(0, forceUpdates: true) + self.childKeypaths.append(contentsOf: results.childrenNodes) + } + + override init(layer: Any) { + guard let layer = layer as? ShapeCompositionLayer else { + fatalError("init(layer:) wrong class.") + } + self.rootNode = nil + self.renderContainer = nil + super.init(layer: layer) + } + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + rootNode?.updateTree(frame, forceUpdates: forceUpdates) + renderContainer?.markRenderUpdates(forFrame: frame) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func updateRenderScale() { + super.updateRenderScale() + renderContainer?.renderScale = renderScale + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/SolidCompositionLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/SolidCompositionLayer.swift new file mode 100644 index 0000000..6568c35 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/SolidCompositionLayer.swift @@ -0,0 +1,48 @@ +// +// SolidCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import QuartzCore + +final class SolidCompositionLayer: CompositionLayer { + + let colorProperty: NodeProperty? + let solidShape: CAShapeLayer = CAShapeLayer() + + init(solid: SolidLayerModel) { + let components = solid.colorHex.hexColorComponents() + self.colorProperty = NodeProperty(provider: SingleValueProvider(Color(r: Double(components.red), g: Double(components.green), b: Double(components.blue), a: 1))) + + super.init(layer: solid, size: .zero) + solidShape.path = CGPath(rect: CGRect(x: 0, y: 0, width: solid.width, height: solid.height), transform: nil) + contentsLayer.addSublayer(solidShape) + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? SolidCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + self.colorProperty = layer.colorProperty + super.init(layer: layer) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + guard let colorProperty = colorProperty else { return } + colorProperty.update(frame: frame) + solidShape.fillColor = colorProperty.value.cgColorValue + } + + override var keypathProperties: [String : AnyNodeProperty] { + guard let colorProperty = colorProperty else { return super.keypathProperties } + return ["Color" : colorProperty] + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/TextCompositionLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/TextCompositionLayer.swift new file mode 100644 index 0000000..4d30d75 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/CompLayers/TextCompositionLayer.swift @@ -0,0 +1,215 @@ +// +// TextCompositionLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import CoreGraphics +import QuartzCore +import CoreText + +/// Needed for NSMutableParagraphStyle... +#if os(OSX) +import AppKit +#else +import UIKit +#endif + +class DisabledTextLayer: CATextLayer { + override func action(forKey event: String) -> CAAction? { + return nil + } +} + +extension TextJustification { + var textAlignment: NSTextAlignment { + switch self { + case .left: + return .left + case .right: + return .right + case .center: + return .center + } + } + + var caTextAlignement: CATextLayerAlignmentMode { + switch self { + case .left: + return .left + case .right: + return .right + case .center: + return .center + } + } + +} + +final class TextCompositionLayer: CompositionLayer { + + let rootNode: TextAnimatorNode? + let textDocument: KeyframeInterpolator? + let interpolatableAnchorPoint: KeyframeInterpolator? + let interpolatableScale: KeyframeInterpolator? + + let textLayer: DisabledTextLayer = DisabledTextLayer() + let textStrokeLayer: DisabledTextLayer = DisabledTextLayer() + var textProvider: AnimationTextProvider + + init(textLayer: TextLayerModel, textProvider: AnimationTextProvider) { + var rootNode: TextAnimatorNode? + for animator in textLayer.animators { + rootNode = TextAnimatorNode(parentNode: rootNode, textAnimator: animator) + } + self.rootNode = rootNode + self.textDocument = KeyframeInterpolator(keyframes: textLayer.text.keyframes) + + self.textProvider = textProvider + + // TODO: this has to be somewhere that can be interpolated + // TODO: look for inspiration from other composite layer + self.interpolatableAnchorPoint = KeyframeInterpolator(keyframes: textLayer.transform.anchorPoint.keyframes) + self.interpolatableScale = KeyframeInterpolator(keyframes: textLayer.transform.scale.keyframes) + + super.init(layer: textLayer, size: .zero) + contentsLayer.addSublayer(self.textLayer) + contentsLayer.addSublayer(self.textStrokeLayer) + self.textLayer.masksToBounds = false + self.textStrokeLayer.masksToBounds = false + self.textLayer.isWrapped = true + self.textStrokeLayer.isWrapped = true + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + /// Used for creating shadow model layers. Read More here: https://developer.apple.com/documentation/quartzcore/calayer/1410842-init + guard let layer = layer as? TextCompositionLayer else { + fatalError("init(layer:) Wrong Layer Class") + } + self.rootNode = nil + self.textDocument = nil + + self.textProvider = DefaultTextProvider() + + self.interpolatableAnchorPoint = nil + self.interpolatableScale = nil + + super.init(layer: layer) + } + + override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { + guard let textDocument = textDocument else { return } + + textLayer.contentsScale = self.renderScale + textStrokeLayer.contentsScale = self.renderScale + + let documentUpdate = textDocument.hasUpdate(frame: frame) + let animatorUpdate = rootNode?.updateContents(frame, forceLocalUpdate: forceUpdates) ?? false + guard documentUpdate == true || animatorUpdate == true else { return } + + let text = textDocument.value(frame: frame) as! TextDocument + let anchorPoint = interpolatableAnchorPoint?.value(frame: frame) as! Vector3D + + interpolatableScale?.value(frame: frame) + rootNode?.rebuildOutputs(frame: frame) + + let fillColor = rootNode?.textOutputNode.fillColor ?? text.fillColorData.cgColorValue + let strokeColor = rootNode?.textOutputNode.strokeColor ?? text.strokeColorData?.cgColorValue + let strokeWidth = rootNode?.textOutputNode.strokeWidth ?? CGFloat(text.strokeWidth ?? 0) + let tracking = (CGFloat(text.fontSize) * (rootNode?.textOutputNode.tracking ?? CGFloat(text.tracking))) / 1000.0 + + let matrix = rootNode?.textOutputNode.xform ?? CATransform3DIdentity + let ctFont = CTFontCreateWithName(text.fontFamily as CFString, CGFloat(text.fontSize), nil) + + let textString = textProvider.textFor(keypathName: self.keypathName, sourceText: text.text) + + var attributes: [NSAttributedString.Key : Any] = [ + NSAttributedString.Key.font: ctFont, + NSAttributedString.Key.foregroundColor: fillColor, + NSAttributedString.Key.kern: tracking, + ] + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = CGFloat(text.lineHeight) + paragraphStyle.alignment = text.justification.textAlignment + attributes[NSAttributedString.Key.paragraphStyle] = paragraphStyle + + let baseAttributedString = NSAttributedString(string: textString, attributes: attributes ) + + if let strokeColor = strokeColor { + textStrokeLayer.isHidden = false + attributes[NSAttributedString.Key.strokeColor] = strokeColor + attributes[NSAttributedString.Key.strokeWidth] = strokeWidth + } else { + textStrokeLayer.isHidden = true + } + + let size: CGSize + let attributedString: NSAttributedString = NSAttributedString(string: textString, attributes: attributes ) + + if let frameSize = text.textFrameSize { + size = CGSize(width: frameSize.x, height: frameSize.y) + } else { + let framesetter = CTFramesetterCreateWithAttributedString(attributedString) + + size = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, + CFRange(location: 0,length: 0), + nil, + CGSize(width: CGFloat.greatestFiniteMagnitude, + height: CGFloat.greatestFiniteMagnitude), + nil) + } + + let baselinePosition = CTFontGetAscent(ctFont) + let textAnchor: CGPoint + switch text.justification { + case .left: + textAnchor = CGPoint(x: 0, y: baselinePosition) + case .right: + textAnchor = CGPoint(x: size.width, y: baselinePosition) + case .center: + textAnchor = CGPoint(x: size.width * 0.5, y: baselinePosition) + } + let anchor = textAnchor + anchorPoint.pointValue + let normalizedAnchor = CGPoint(x: anchor.x.remap(fromLow: 0, fromHigh: size.width, toLow: 0, toHigh: 1), + y: anchor.y.remap(fromLow: 0, fromHigh: size.height, toLow: 0, toHigh: 1)) + + if textStrokeLayer.isHidden == false { + if text.strokeOverFill ?? false { + textStrokeLayer.removeFromSuperlayer() + contentsLayer.addSublayer(textStrokeLayer) + } else { + textLayer.removeFromSuperlayer() + contentsLayer.addSublayer(textLayer) + } + textStrokeLayer.anchorPoint = normalizedAnchor + textStrokeLayer.opacity = Float(rootNode?.textOutputNode.opacity ?? 1) + textStrokeLayer.transform = CATransform3DIdentity + textStrokeLayer.frame = CGRect(origin: .zero, size: size) + textStrokeLayer.position = text.textFramePosition?.pointValue ?? CGPoint.zero + textStrokeLayer.transform = matrix + textStrokeLayer.string = attributedString + textStrokeLayer.alignmentMode = text.justification.caTextAlignement + } + + textLayer.anchorPoint = normalizedAnchor + textLayer.opacity = Float(rootNode?.textOutputNode.opacity ?? 1) + textLayer.transform = CATransform3DIdentity + textLayer.frame = CGRect(origin: .zero, size: size) + textLayer.position = text.textFramePosition?.pointValue ?? CGPoint.zero + textLayer.transform = matrix + textLayer.string = baseAttributedString + textLayer.alignmentMode = text.justification.caTextAlignement + } + + override func updateRenderScale() { + super.updateRenderScale() + textLayer.contentsScale = self.renderScale + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/CompositionLayersInitializer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/CompositionLayersInitializer.swift new file mode 100644 index 0000000..85889a0 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/CompositionLayersInitializer.swift @@ -0,0 +1,79 @@ +// +// CompositionLayersInitializer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import CoreGraphics + +extension Array where Element == LayerModel { + + func initializeCompositionLayers(assetLibrary: AssetLibrary?, + layerImageProvider: LayerImageProvider, + textProvider: AnimationTextProvider, + frameRate: CGFloat) -> [CompositionLayer] { + var compositionLayers = [CompositionLayer]() + var layerMap = [Int : CompositionLayer]() + + /// Organize the assets into a dictionary of [ID : ImageAsset] + var childLayers = [LayerModel]() + + for layer in self { + if layer.hidden == true { + let genericLayer = NullCompositionLayer(layer: layer) + compositionLayers.append(genericLayer) + layerMap[layer.index] = genericLayer + } else if let shapeLayer = layer as? ShapeLayerModel { + let shapeContainer = ShapeCompositionLayer(shapeLayer: shapeLayer) + compositionLayers.append(shapeContainer) + layerMap[layer.index] = shapeContainer + } else if let solidLayer = layer as? SolidLayerModel { + let solidContainer = SolidCompositionLayer(solid: solidLayer) + compositionLayers.append(solidContainer) + layerMap[layer.index] = solidContainer + } else if let precompLayer = layer as? PreCompLayerModel, + let assetLibrary = assetLibrary, + let precompAsset = assetLibrary.precompAssets[precompLayer.referenceID] { + let precompContainer = PreCompositionLayer(precomp: precompLayer, + asset: precompAsset, + layerImageProvider: layerImageProvider, + textProvider: textProvider, + assetLibrary: assetLibrary, + frameRate: frameRate) + compositionLayers.append(precompContainer) + layerMap[layer.index] = precompContainer + } else if let imageLayer = layer as? ImageLayerModel, + let assetLibrary = assetLibrary, + let imageAsset = assetLibrary.imageAssets[imageLayer.referenceID] { + let imageContainer = ImageCompositionLayer(imageLayer: imageLayer, size: CGSize(width: imageAsset.width, height: imageAsset.height)) + compositionLayers.append(imageContainer) + layerMap[layer.index] = imageContainer + } else if let textLayer = layer as? TextLayerModel { + let textContainer = TextCompositionLayer(textLayer: textLayer, textProvider: textProvider) + compositionLayers.append(textContainer) + layerMap[layer.index] = textContainer + } else { + let genericLayer = NullCompositionLayer(layer: layer) + compositionLayers.append(genericLayer) + layerMap[layer.index] = genericLayer + } + if layer.parent != nil { + childLayers.append(layer) + } + } + + /// Now link children with their parents + for layerModel in childLayers { + if let parentID = layerModel.parent { + let childLayer = layerMap[layerModel.index] + let parentLayer = layerMap[parentID] + childLayer?.transformNode.parentNode = parentLayer?.transformNode + } + } + + return compositionLayers + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/InvertedMatteLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/InvertedMatteLayer.swift new file mode 100644 index 0000000..17e7921 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/InvertedMatteLayer.swift @@ -0,0 +1,57 @@ +// +// InvertedMatteLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/28/19. +// + +import Foundation +import QuartzCore + +/** + A layer that inverses the alpha output of its input layer. + + WARNING: This is experimental and probably not very performant. + */ +final class InvertedMatteLayer: CALayer, CompositionLayerDelegate { + + let inputMatte: CompositionLayer? + let wrapperLayer = CALayer() + + init(inputMatte: CompositionLayer) { + self.inputMatte = inputMatte + super.init() + inputMatte.layerDelegate = self + self.anchorPoint = .zero + self.bounds = inputMatte.bounds + self.setNeedsDisplay() + } + + override init(layer: Any) { + guard let layer = layer as? InvertedMatteLayer else { + fatalError("init(layer:) wrong class.") + } + self.inputMatte = nil + super.init(layer: layer) + } + + func frameUpdated(frame: CGFloat) { + self.setNeedsDisplay() + self.displayIfNeeded() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(in ctx: CGContext) { + guard let inputMatte = inputMatte else { return } + guard let fillColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 1]) + else { return } + ctx.setFillColor(fillColor) + ctx.fill(bounds) + ctx.setBlendMode(.destinationOut) + inputMatte.render(in: ctx) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/LayerImageProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/LayerImageProvider.swift new file mode 100644 index 0000000..cb9bdb9 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/LayerImageProvider.swift @@ -0,0 +1,49 @@ +// +// LayerImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation + +/// Connects a LottieImageProvider to a group of image layers +final class LayerImageProvider { + + var imageProvider: AnimationImageProvider { + didSet { + reloadImages() + } + } + + fileprivate(set) var imageLayers: [ImageCompositionLayer] + let imageAssets: [String : ImageAsset] + + init(imageProvider: AnimationImageProvider, assets: [String : ImageAsset]?) { + self.imageProvider = imageProvider + self.imageLayers = [ImageCompositionLayer]() + if let assets = assets { + self.imageAssets = assets + } else { + self.imageAssets = [:] + } + reloadImages() + } + + func addImageLayers(_ layers: [ImageCompositionLayer]) { + for layer in layers { + if imageAssets[layer.imageReferenceID] != nil { + /// Found a linking asset in our asset library. Add layer + imageLayers.append(layer) + } + } + } + + func reloadImages() { + for imageLayer in imageLayers { + if let asset = imageAssets[imageLayer.imageReferenceID] { + imageLayer.image = imageProvider.imageForAsset(asset: asset) + } + } + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/LayerTextProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/LayerTextProvider.swift new file mode 100644 index 0000000..3435abb --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/LayerTextProvider.swift @@ -0,0 +1,36 @@ +// +// LayerTextProvider.swift +// lottie-ios-iOS +// +// Created by Alexandr Goncharov on 07/06/2019. +// + +import Foundation + +/// Connects a LottieTextProvider to a group of text layers +final class LayerTextProvider { + + var textProvider: AnimationTextProvider { + didSet { + reloadTexts() + } + } + + fileprivate(set) var textLayers: [TextCompositionLayer] + + init(textProvider: AnimationTextProvider) { + self.textProvider = textProvider + self.textLayers = [] + reloadTexts() + } + + func addTextLayers(_ layers: [TextCompositionLayer]) { + textLayers += layers + } + + func reloadTexts() { + textLayers.forEach { + $0.textProvider = textProvider + } + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/LayerTransformNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/LayerTransformNode.swift new file mode 100644 index 0000000..dd95294 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/LayerContainers/Utility/LayerTransformNode.swift @@ -0,0 +1,128 @@ +// +// LayerTransformPropertyMap.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +import CoreGraphics +import QuartzCore + +final class LayerTransformProperties: NodePropertyMap, KeypathSearchable { + + init(transform: Transform) { + + self.anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.anchorPoint.keyframes)) + self.scale = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.scale.keyframes)) + self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotation.keyframes)) + self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.opacity.keyframes)) + + var propertyMap: [String: AnyNodeProperty] = [ + "Anchor Point" : anchor, + "Scale" : scale, + "Rotation" : rotation, + "Opacity" : opacity + ] + + if let positionKeyframesX = transform.positionX?.keyframes, + let positionKeyframesY = transform.positionY?.keyframes { + let xPosition: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframesX)) + let yPosition: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframesY)) + propertyMap["X Position"] = xPosition + propertyMap["Y Position"] = yPosition + self.positionX = xPosition + self.positionY = yPosition + self.position = nil + } else if let positionKeyframes = transform.position?.keyframes { + let position: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframes)) + propertyMap["Position"] = position + self.position = position + self.positionX = nil + self.positionY = nil + } else { + self.position = nil + self.positionY = nil + self.positionX = nil + } + + self.keypathProperties = propertyMap + self.properties = Array(propertyMap.values) + } + + let keypathProperties: [String : AnyNodeProperty] + var keypathName: String = "Transform" + + var childKeypaths: [KeypathSearchable] { + return [] + } + + let properties: [AnyNodeProperty] + + let anchor: NodeProperty + let scale: NodeProperty + let rotation: NodeProperty + let position: NodeProperty? + let positionX: NodeProperty? + let positionY: NodeProperty? + let opacity: NodeProperty + +} + +class LayerTransformNode: AnimatorNode { + let outputNode: NodeOutput = PassThroughOutputNode(parent: nil) + + init(transform: Transform) { + self.transformProperties = LayerTransformProperties(transform: transform) + } + + let transformProperties: LayerTransformProperties + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + return transformProperties + } + + var parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true + + func shouldRebuildOutputs(frame: CGFloat) -> Bool { + return hasLocalUpdates || hasUpstreamUpdates + } + + func rebuildOutputs(frame: CGFloat) { + opacity = Float(transformProperties.opacity.value.cgFloatValue) * 0.01 + + let position: CGPoint + if let point = transformProperties.position?.value.pointValue { + position = point + } else if let xPos = transformProperties.positionX?.value.cgFloatValue, + let yPos = transformProperties.positionY?.value.cgFloatValue { + position = CGPoint(x: xPos, y: yPos) + } else { + position = .zero + } + + localTransform = CATransform3D.makeTransform(anchor: transformProperties.anchor.value.pointValue, + position: position, + scale: transformProperties.scale.value.sizeValue, + rotation: transformProperties.rotation.value.cgFloatValue, + skew: nil, + skewAxis: nil) + + if let parentNode = parentNode as? LayerTransformNode { + globalTransform = CATransform3DConcat(localTransform, parentNode.globalTransform) + } else { + globalTransform = localTransform + } + } + + var opacity: Float = 1 + var localTransform: CATransform3D = CATransform3DIdentity + var globalTransform: CATransform3D = CATransform3DIdentity + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Animation.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Animation.swift new file mode 100644 index 0000000..2ba1032 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Animation.swift @@ -0,0 +1,107 @@ +// +// Animation.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import Foundation + +public enum CoordinateSpace: Int, Codable { + case type2d + case type3d +} + +/** + The `Animation` model is the top level model object in Lottie. + + An `Animation` holds all of the animation data backing a Lottie Animation. + Codable, see JSON schema [here](https://github.com/airbnb/lottie-web/tree/master/docs/json). + */ +public final class Animation: Codable { + + /// The version of the JSON Schema. + let version: String + + /// The coordinate space of the composition. + let type: CoordinateSpace + + /// The start time of the composition in frameTime. + public let startFrame: AnimationFrameTime + + /// The end time of the composition in frameTime. + public let endFrame: AnimationFrameTime + + /// The frame rate of the composition. + public let framerate: Double + + /// The height of the composition in points. + let width: Int + + /// The width of the composition in points. + let height: Int + + /// The list of animation layers + let layers: [LayerModel] + + /// The list of glyphs used for text rendering + let glyphs: [Glyph]? + + /// The list of fonts used for text rendering + let fonts: FontList? + + /// Asset Library + let assetLibrary: AssetLibrary? + + /// Markers + let markers: [Marker]? + let markerMap: [String : Marker]? + + /// Return all marker names, in order, or an empty list if none are specified + public var markerNames: [String] { + guard let markers = markers else { return [] } + return markers.map { $0.name } + } + + enum CodingKeys : String, CodingKey { + case version = "v" + case type = "ddd" + case startFrame = "ip" + case endFrame = "op" + case framerate = "fr" + case width = "w" + case height = "h" + case layers = "layers" + case glyphs = "chars" + case fonts = "fonts" + case assetLibrary = "assets" + case markers = "markers" + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Animation.CodingKeys.self) + self.version = try container.decode(String.self, forKey: .version) + self.type = try container.decodeIfPresent(CoordinateSpace.self, forKey: .type) ?? .type2d + self.startFrame = try container.decode(AnimationFrameTime.self, forKey: .startFrame) + self.endFrame = try container.decode(AnimationFrameTime.self, forKey: .endFrame) + self.framerate = try container.decode(Double.self, forKey: .framerate) + self.width = try container.decode(Int.self, forKey: .width) + self.height = try container.decode(Int.self, forKey: .height) + self.layers = try container.decode([LayerModel].self, ofFamily: LayerType.self, forKey: .layers) + self.glyphs = try container.decodeIfPresent([Glyph].self, forKey: .glyphs) + self.fonts = try container.decodeIfPresent(FontList.self, forKey: .fonts) + self.assetLibrary = try container.decodeIfPresent(AssetLibrary.self, forKey: .assetLibrary) + self.markers = try container.decodeIfPresent([Marker].self, forKey: .markers) + + if let markers = markers { + var markerMap: [String : Marker] = [:] + for marker in markers { + markerMap[marker.name] = marker + } + self.markerMap = markerMap + } else { + self.markerMap = nil + } + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/Asset.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/Asset.swift new file mode 100644 index 0000000..3ebff3c --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/Asset.swift @@ -0,0 +1,27 @@ +// +// Asset.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +public class Asset: Codable { + + /// The ID of the asset + let id: String + + private enum CodingKeys : String, CodingKey { + case id = "id" + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Asset.CodingKeys.self) + if let id = try? container.decode(String.self, forKey: .id) { + self.id = id + } else { + self.id = String(try container.decode(Int.self, forKey: .id)) + } + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/AssetLibrary.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/AssetLibrary.swift new file mode 100644 index 0000000..d9dbf90 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/AssetLibrary.swift @@ -0,0 +1,48 @@ +// +// AssetLibrary.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +final class AssetLibrary: Codable { + + /// The Assets + let assets: [String : Asset] + + let imageAssets: [String : ImageAsset] + let precompAssets: [String : PrecompAsset] + + required init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + var containerForKeys = container + + var decodedAssets = [String : Asset]() + + var imageAssets = [String : ImageAsset]() + var precompAssets = [String : PrecompAsset]() + + while !container.isAtEnd { + let keyContainer = try containerForKeys.nestedContainer(keyedBy: PrecompAsset.CodingKeys.self) + if keyContainer.contains(.layers) { + let precompAsset = try container.decode(PrecompAsset.self) + decodedAssets[precompAsset.id] = precompAsset + precompAssets[precompAsset.id] = precompAsset + } else { + let imageAsset = try container.decode(ImageAsset.self) + decodedAssets[imageAsset.id] = imageAsset + imageAssets[imageAsset.id] = imageAsset + } + } + self.assets = decodedAssets + self.precompAssets = precompAssets + self.imageAssets = imageAssets + } + + func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(contentsOf: Array(assets.values)) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/ImageAsset.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/ImageAsset.swift new file mode 100644 index 0000000..aeff137 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/ImageAsset.swift @@ -0,0 +1,48 @@ +// +// ImageAsset.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +public final class ImageAsset: Asset { + + /// Image name + public let name: String + + /// Image Directory + public let directory: String + + /// Image Size + public let width: Double + + public let height: Double + + enum CodingKeys : String, CodingKey { + case name = "p" + case directory = "u" + case width = "w" + case height = "h" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ImageAsset.CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) + self.directory = try container.decode(String.self, forKey: .directory) + self.width = try container.decode(Double.self, forKey: .width) + self.height = try container.decode(Double.self, forKey: .height) + try super.init(from: decoder) + } + + override public func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(name, forKey: .name) + try container.encode(directory, forKey: .directory) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/PrecompAsset.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/PrecompAsset.swift new file mode 100644 index 0000000..963e0bb --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Assets/PrecompAsset.swift @@ -0,0 +1,30 @@ +// +// PrecompAsset.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +final class PrecompAsset: Asset { + + /// Layers of the precomp + let layers: [LayerModel] + + enum CodingKeys : String, CodingKey { + case layers = "layers" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: PrecompAsset.CodingKeys.self) + self.layers = try container.decode([LayerModel].self, ofFamily: LayerType.self, forKey: .layers) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(layers, forKey: .layers) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift new file mode 100644 index 0000000..29fe104 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift @@ -0,0 +1,40 @@ +// From: https://medium.com/@kewindannerfjordremeczki/swift-4-0-decodable-heterogeneous-collections-ecc0e6b468cf + +import Foundation + +/// To support a new class family, create an enum that conforms to this protocol and contains the different types. +protocol ClassFamily: Decodable { + /// The discriminator key. + static var discriminator: Discriminator { get } + + /// Returns the class type of the object corresponding to the value. + func getType() -> AnyObject.Type +} + +/// Discriminator key enum used to retrieve discriminator fields in JSON payloads. +enum Discriminator: String, CodingKey { + case type = "ty" +} + +extension KeyedDecodingContainer { + + /// Decode a heterogeneous list of objects for a given family. + /// - Parameters: + /// - heterogeneousType: The decodable type of the list. + /// - family: The ClassFamily enum for the type family. + /// - key: The CodingKey to look up the list in the current container. + /// - Returns: The resulting list of heterogeneousType elements. + func decode(_ heterogeneousType: [T].Type, ofFamily family: U.Type, forKey key: K) throws -> [T] { + var container = try self.nestedUnkeyedContainer(forKey: key) + var list = [T]() + var tmpContainer = container + while !container.isAtEnd { + let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self) + let family: U = try typeContainer.decode(U.self, forKey: U.discriminator) + if let type = family.getType() as? T.Type { + list.append(try tmpContainer.decode(type)) + } + } + return list + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Keyframes/Keyframe.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Keyframes/Keyframe.swift new file mode 100644 index 0000000..dc70aa1 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Keyframes/Keyframe.swift @@ -0,0 +1,128 @@ +// +// Keyframe.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import Foundation +import CoreGraphics + +/** + Keyframe represents a point in time and is the container for datatypes. + Note: This is a parent class and should not be used directly. + */ +struct Keyframe { + + /// The value of the keyframe + let value: T + /// The time in frames of the keyframe. + let time: CGFloat + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + let isHold: Bool + /// The in tangent for the time interpolation curve. + let inTangent: Vector2D? + /// The out tangent for the time interpolation curve. + let outTangent: Vector2D? + + /// The spacial in tangent of the vector. + let spatialInTangent: Vector3D? + /// The spacial out tangent of the vector. + let spatialOutTangent: Vector3D? + + /// Initialize a value-only keyframe with no time data. + init(_ value: T, + spatialInTangent: Vector3D? = nil, + spatialOutTangent: Vector3D? = nil) { + self.value = value + self.time = 0 + self.isHold = true + self.inTangent = nil + self.outTangent = nil + self.spatialInTangent = spatialInTangent + self.spatialOutTangent = spatialOutTangent + } + + /// Initialize a keyframe + init(value: T, + time: Double, + isHold: Bool, + inTangent: Vector2D?, + outTangent: Vector2D?, + spatialInTangent: Vector3D? = nil, + spatialOutTangent: Vector3D? = nil) { + self.value = value + self.time = CGFloat(time) + self.isHold = isHold + self.outTangent = outTangent + self.inTangent = inTangent + self.spatialInTangent = spatialInTangent + self.spatialOutTangent = spatialOutTangent + } + +} + +/** + A generic struct used to parse and remap keyframe json. + + Keyframe json has a couple of different variations and formats depending on the + type of keyframea and also the version of the JSON. By parsing the raw data + we can reconfigure it into a constant format. + */ +struct KeyframeData: Codable { + + /// The start value of the keyframe + let startValue: T? + /// The End value of the keyframe. Note: Newer versions animation json do not have this field. + let endValue: T? + /// The time in frames of the keyframe. + let time: Double? + /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. + let hold: Int? + + /// The in tangent for the time interpolation curve. + let inTangent: Vector2D? + /// The out tangent for the time interpolation curve. + let outTangent: Vector2D? + + /// The spacial in tangent of the vector. + let spatialInTangent: Vector3D? + /// The spacial out tangent of the vector. + let spatialOutTangent:Vector3D? + + init(startValue: T?, + endValue: T?, + time: Double?, + hold: Int?, + inTangent: Vector2D?, + outTangent: Vector2D?, + spatialInTangent: Vector3D?, + spatialOutTangent: Vector3D?) { + self.startValue = startValue + self.endValue = endValue + self.time = time + self.hold = hold + self.inTangent = inTangent + self.outTangent = outTangent + self.spatialInTangent = spatialInTangent + self.spatialOutTangent = spatialOutTangent + } + + enum CodingKeys : String, CodingKey { + case startValue = "s" + case endValue = "e" + case time = "t" + case hold = "h" + case inTangent = "i" + case outTangent = "o" + case spatialInTangent = "ti" + case spatialOutTangent = "to" + } + + var isHold: Bool { + if let hold = hold { + return hold > 0 + } + return false + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Keyframes/KeyframeGroup.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Keyframes/KeyframeGroup.swift new file mode 100644 index 0000000..bb723fe --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Keyframes/KeyframeGroup.swift @@ -0,0 +1,108 @@ +// +// KeyframeGroup.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import Foundation + +/** + Used for coding/decoding a group of Keyframes by type. + + Keyframe data is wrapped in a dictionary { "k" : KeyframeData }. + The keyframe data can either be an array of keyframes or, if no animation is present, the raw value. + This helper object is needed to properly decode the json. + */ + +final class KeyframeGroup: Codable where T: Codable, T: Interpolatable { + + let keyframes: [Keyframe] + + private enum KeyframeWrapperKey: String, CodingKey { + case keyframeData = "k" + } + + init(keyframes: [Keyframe]) { + self.keyframes = keyframes + } + + init(_ value: T) { + self.keyframes = [Keyframe(value)] + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: KeyframeWrapperKey.self) + + if let keyframeData: T = try? container.decode(T.self, forKey: .keyframeData) { + /// Try to decode raw value; No keyframe data. + self.keyframes = [Keyframe(keyframeData)] + } else { + /** + Decode and array of keyframes. + + Body Movin and Lottie deal with keyframes in different ways. + + A keyframe object in Body movin defines a span of time with a START + and an END, from the current keyframe time to the next keyframe time. + + A keyframe object in Lottie defines a singular point in time/space. + This point has an in-tangent and an out-tangent. + + To properly decode this we must iterate through keyframes while holding + reference to the previous keyframe. + */ + + var keyframesContainer = try container.nestedUnkeyedContainer(forKey: .keyframeData) + var keyframes = [Keyframe]() + var previousKeyframeData: KeyframeData? + while(!keyframesContainer.isAtEnd) { + // Ensure that Time and Value are present. + + let keyframeData = try keyframesContainer.decode(KeyframeData.self) + + guard let value: T = keyframeData.startValue ?? previousKeyframeData?.endValue, + let time = keyframeData.time else { + /// Missing keyframe data. JSON must be corrupt. + throw DecodingError.dataCorruptedError(forKey: KeyframeWrapperKey.keyframeData, in: container, debugDescription: "Missing keyframe data.") + } + + keyframes.append(Keyframe(value: value, + time: time, + isHold: keyframeData.isHold, + inTangent: previousKeyframeData?.inTangent, + outTangent: keyframeData.outTangent, + spatialInTangent: previousKeyframeData?.spatialInTangent, + spatialOutTangent: keyframeData.spatialOutTangent)) + previousKeyframeData = keyframeData + } + self.keyframes = keyframes + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: KeyframeWrapperKey.self) + + if keyframes.count == 1 { + let keyframe = keyframes[0] + try container.encode(keyframe.value, forKey: .keyframeData) + } else { + var keyframeContainer = container.nestedUnkeyedContainer(forKey: .keyframeData) + + for i in 1..(startValue: keyframe.value, + endValue: nextKeyframe.value, + time: Double(keyframe.time), + hold: keyframe.isHold ? 1 : nil, + inTangent: nextKeyframe.inTangent, + outTangent: keyframe.outTangent, + spatialInTangent: nil, + spatialOutTangent: nil) + try keyframeContainer.encode(keyframeData) + } + } + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/ImageLayerModel.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/ImageLayerModel.swift new file mode 100644 index 0000000..6961962 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/ImageLayerModel.swift @@ -0,0 +1,32 @@ +// +// ImageLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// A layer that holds an image. +final class ImageLayerModel: LayerModel { + + /// The reference ID of the image. + let referenceID: String + + private enum CodingKeys : String, CodingKey { + case referenceID = "refId" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ImageLayerModel.CodingKeys.self) + self.referenceID = try container.decode(String.self, forKey: .referenceID) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(referenceID, forKey: .referenceID) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/LayerModel.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/LayerModel.swift new file mode 100644 index 0000000..6b2667a --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/LayerModel.swift @@ -0,0 +1,150 @@ +// +// Layer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import Foundation + +/// Used for mapping a heterogeneous list to classes for parsing. +extension LayerType: ClassFamily { + static var discriminator: Discriminator = .type + + func getType() -> AnyObject.Type { + switch self { + case .precomp: + return PreCompLayerModel.self + case .solid: + return SolidLayerModel.self + case .image: + return ImageLayerModel.self + case .null: + return LayerModel.self + case .shape: + return ShapeLayerModel.self + case .text: + return TextLayerModel.self + } + } +} + +public enum LayerType: Int, Codable { + case precomp + case solid + case image + case null + case shape + case text + + public init(from decoder: Decoder) throws { + self = try LayerType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .null + } +} + +public enum MatteType: Int, Codable { + case none + case add + case invert + case unknown +} + +public enum BlendMode: Int, Codable { + case normal + case multiply + case screen + case overlay + case darken + case lighten + case colorDodge + case colorBurn + case hardLight + case softLight + case difference + case exclusion + case hue + case saturation + case color + case luminosity +} + +/** + A base top container for shapes, images, and other view objects. + */ +class LayerModel: Codable { + + /// The readable name of the layer + let name: String + + /// The index of the layer + let index: Int + + /// The type of the layer. + let type: LayerType + + /// The coordinate space + let coordinateSpace: CoordinateSpace + + /// The in time of the layer in frames. + let inFrame: Double + /// The out time of the layer in frames. + let outFrame: Double + + /// The start time of the layer in frames. + let startTime: Double + + /// The transform of the layer + let transform: Transform + + /// The index of the parent layer, if applicable. + let parent: Int? + + /// The blending mode for the layer + let blendMode: BlendMode + + /// An array of masks for the layer. + let masks: [Mask]? + + /// A number that stretches time by a multiplier + let timeStretch: Double + + /// The type of matte if any. + let matte: MatteType? + + let hidden: Bool + + private enum CodingKeys : String, CodingKey { + case name = "nm" + case index = "ind" + case type = "ty" + case coordinateSpace = "ddd" + case inFrame = "ip" + case outFrame = "op" + case startTime = "st" + case transform = "ks" + case parent = "parent" + case blendMode = "bm" + case masks = "masksProperties" + case timeStretch = "sr" + case matte = "tt" + case hidden = "hd" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: LayerModel.CodingKeys.self) + self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer" + self.index = try container.decode(Int.self, forKey: .index) + self.type = try container.decode(LayerType.self, forKey: .type) + self.coordinateSpace = try container.decodeIfPresent(CoordinateSpace.self, forKey: .coordinateSpace) ?? .type2d + self.inFrame = try container.decode(Double.self, forKey: .inFrame) + self.outFrame = try container.decode(Double.self, forKey: .outFrame) + self.startTime = try container.decode(Double.self, forKey: .startTime) + self.transform = try container.decode(Transform.self, forKey: .transform) + self.parent = try container.decodeIfPresent(Int.self, forKey: .parent) + self.blendMode = try container.decodeIfPresent(BlendMode.self, forKey: .blendMode) ?? .normal + self.masks = try container.decodeIfPresent([Mask].self, forKey: .masks) + self.timeStretch = try container.decodeIfPresent(Double.self, forKey: .timeStretch) ?? 1 + self.matte = try container.decodeIfPresent(MatteType.self, forKey: .matte) + self.hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/PreCompLayerModel.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/PreCompLayerModel.swift new file mode 100644 index 0000000..e107ea1 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/PreCompLayerModel.swift @@ -0,0 +1,50 @@ +// +// PreCompLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// A layer that holds another animation composition. +final class PreCompLayerModel: LayerModel { + + /// The reference ID of the precomp. + let referenceID: String + + /// A value that remaps time over time. + let timeRemapping: KeyframeGroup? + + /// Precomp Width + let width: Double + + /// Precomp Height + let height: Double + + private enum CodingKeys : String, CodingKey { + case referenceID = "refId" + case timeRemapping = "tm" + case width = "w" + case height = "h" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: PreCompLayerModel.CodingKeys.self) + self.referenceID = try container.decode(String.self, forKey: .referenceID) + self.timeRemapping = try container.decodeIfPresent(KeyframeGroup.self, forKey: .timeRemapping) + self.width = try container.decode(Double.self, forKey: .width) + self.height = try container.decode(Double.self, forKey: .height) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(referenceID, forKey: .referenceID) + try container.encode(timeRemapping, forKey: .timeRemapping) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/ShapeLayerModel.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/ShapeLayerModel.swift new file mode 100644 index 0000000..eb6299f --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/ShapeLayerModel.swift @@ -0,0 +1,32 @@ +// +// ShapeLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// A layer that holds vector shape objects. +final class ShapeLayerModel: LayerModel { + + /// A list of shape items. + let items: [ShapeItem] + + private enum CodingKeys : String, CodingKey { + case items = "shapes" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ShapeLayerModel.CodingKeys.self) + self.items = try container.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .items) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.items, forKey: .items) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/SolidLayerModel.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/SolidLayerModel.swift new file mode 100644 index 0000000..afa00d7 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/SolidLayerModel.swift @@ -0,0 +1,44 @@ +// +// SolidLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// A layer that holds a solid color. +final class SolidLayerModel: LayerModel { + + /// The color of the solid in Hex // Change to value provider. + let colorHex: String + + /// The Width of the color layer + let width: Double + + /// The height of the color layer + let height: Double + + private enum CodingKeys : String, CodingKey { + case colorHex = "sc" + case width = "sw" + case height = "sh" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: SolidLayerModel.CodingKeys.self) + self.colorHex = try container.decode(String.self, forKey: .colorHex) + self.width = try container.decode(Double.self, forKey: .width) + self.height = try container.decode(Double.self, forKey: .height) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(colorHex, forKey: .colorHex) + try container.encode(width, forKey: .width) + try container.encode(height, forKey: .height) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/TextLayerModel.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/TextLayerModel.swift new file mode 100644 index 0000000..f08bcef --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Layers/TextLayerModel.swift @@ -0,0 +1,44 @@ +// +// TextLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// A layer that holds text. +final class TextLayerModel: LayerModel { + + /// The text for the layer + let text: KeyframeGroup + + /// Text animators + let animators: [TextAnimator] + + private enum CodingKeys : String, CodingKey { + case textGroup = "t" + } + + private enum TextCodingKeys : String, CodingKey { + case text = "d" + case animators = "a" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: TextLayerModel.CodingKeys.self) + let textContainer = try container.nestedContainer(keyedBy: TextCodingKeys.self, forKey: .textGroup) + self.text = try textContainer.decode(KeyframeGroup.self, forKey: .text) + self.animators = try textContainer.decode([TextAnimator].self, forKey: .animators) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + var textContainer = container.nestedContainer(keyedBy: TextCodingKeys.self, forKey: .textGroup) + try textContainer.encode(text, forKey: .text) + try textContainer.encode(animators, forKey: .animators) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/DashPattern.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/DashPattern.swift new file mode 100644 index 0000000..efc7532 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/DashPattern.swift @@ -0,0 +1,24 @@ +// +// DashPattern.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation + +enum DashElementType: String, Codable { + case offset = "o" + case dash = "d" + case gap = "g" +} + +final class DashElement: Codable { + let type: DashElementType + let value: KeyframeGroup + + enum CodingKeys : String, CodingKey { + case type = "n" + case value = "v" + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/Marker.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/Marker.swift new file mode 100644 index 0000000..9c9512f --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/Marker.swift @@ -0,0 +1,23 @@ +// +// Marker.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +/// A time marker +final class Marker: Codable { + + /// The Marker Name + let name: String + + /// The Frame time of the marker + let frameTime: AnimationFrameTime + + enum CodingKeys : String, CodingKey { + case name = "cm" + case frameTime = "tm" + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/Mask.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/Mask.swift new file mode 100644 index 0000000..1b7dc49 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/Mask.swift @@ -0,0 +1,48 @@ +// +// Mask.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +enum MaskMode: String, Codable { + case add = "a" + case subtract = "s" + case intersect = "i" + case lighten = "l" + case darken = "d" + case difference = "f" + case none = "n" +} + +final class Mask: Codable { + + let mode: MaskMode + + let opacity: KeyframeGroup + + let shape: KeyframeGroup + + let inverted: Bool + + let expansion: KeyframeGroup + + enum CodingKeys : String, CodingKey { + case mode = "mode" + case opacity = "o" + case inverted = "inv" + case shape = "pt" + case expansion = "x" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Mask.CodingKeys.self) + self.mode = try container.decodeIfPresent(MaskMode.self, forKey: .mode) ?? .add + self.opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) + self.shape = try container.decode(KeyframeGroup.self, forKey: .shape) + self.inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false + self.expansion = try container.decodeIfPresent(KeyframeGroup.self, forKey: .expansion) ?? KeyframeGroup(Vector1D(0)) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/Transform.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/Transform.swift new file mode 100644 index 0000000..c7e3834 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Objects/Transform.swift @@ -0,0 +1,105 @@ +// +// Transform.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import Foundation + +/// The animatable transform for a layer. Controls position, rotation, scale, and opacity. +final class Transform: Codable { + + /// The anchor point of the transform. + let anchorPoint: KeyframeGroup + + /// The position of the transform. This is nil if the position data was split. + let position: KeyframeGroup? + + /// The positionX of the transform. This is nil if the position property is set. + let positionX: KeyframeGroup? + + /// The positionY of the transform. This is nil if the position property is set. + let positionY: KeyframeGroup? + + /// The scale of the transform + let scale: KeyframeGroup + + /// The rotation of the transform. Note: This is single dimensional rotation. + let rotation: KeyframeGroup + + /// The opacity of the transform. + let opacity: KeyframeGroup + + /// Should always be nil. + let rotationZ: KeyframeGroup? + + enum CodingKeys : String, CodingKey { + case anchorPoint = "a" + case position = "p" + case positionX = "px" + case positionY = "py" + case scale = "s" + case rotation = "r" + case rotationZ = "rz" + case opacity = "o" + } + + enum PositionCodingKeys : String, CodingKey { + case split = "s" + case positionX = "x" + case positionY = "y" + } + + + required init(from decoder: Decoder) throws { + /** + This manual override of decode is required because we want to throw an error + in the case that there is not position data. + */ + let container = try decoder.container(keyedBy: Transform.CodingKeys.self) + + // AnchorPoint + self.anchorPoint = try container.decodeIfPresent(KeyframeGroup.self, forKey: .anchorPoint) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + + // Position + if container.contains(.positionX), container.contains(.positionY) { + // Position dimensions are split into two keyframe groups + self.positionX = try container.decode(KeyframeGroup.self, forKey: .positionX) + self.positionY = try container.decode(KeyframeGroup.self, forKey: .positionY) + self.position = nil + } else if let positionKeyframes = try? container.decode(KeyframeGroup.self, forKey: .position) { + // Position dimensions are a single keyframe group. + self.position = positionKeyframes + self.positionX = nil + self.positionY = nil + } else if let positionContainer = try? container.nestedContainer(keyedBy: PositionCodingKeys.self, forKey: .position), + let positionX = try? positionContainer.decode(KeyframeGroup.self, forKey: .positionX), + let positionY = try? positionContainer.decode(KeyframeGroup.self, forKey: .positionY) { + /// Position keyframes are split and nested. + self.positionX = positionX + self.positionY = positionY + self.position = nil + } else { + /// Default value. + self.position = KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + self.positionX = nil + self.positionY = nil + } + + + // Scale + self.scale = try container.decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) + + // Rotation + if let rotationZ = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotationZ) { + self.rotation = rotationZ + } else { + self.rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) + } + self.rotationZ = nil + + // Opacity + self.opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Ellipse.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Ellipse.swift new file mode 100644 index 0000000..5e6b918 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Ellipse.swift @@ -0,0 +1,50 @@ +// +// EllipseItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +enum PathDirection: Int, Codable { + case clockwise = 1 + case userSetClockwise = 2 + case counterClockwise = 3 +} + +/// An item that define an ellipse shape +final class Ellipse: ShapeItem { + + /// The direction of the ellipse. + let direction: PathDirection + + /// The position of the ellipse + let position: KeyframeGroup + + /// The size of the ellipse + let size: KeyframeGroup + + private enum CodingKeys : String, CodingKey { + case direction = "d" + case position = "p" + case size = "s" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Ellipse.CodingKeys.self) + self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise + self.position = try container.decode(KeyframeGroup.self, forKey: .position) + self.size = try container.decode(KeyframeGroup.self, forKey: .size) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(direction, forKey: .direction) + try container.encode(position, forKey: .position) + try container.encode(size, forKey: .size) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/FillI.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/FillI.swift new file mode 100644 index 0000000..11aded7 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/FillI.swift @@ -0,0 +1,49 @@ +// +// FillShape.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +enum FillRule: Int, Codable { + case none + case nonZeroWinding + case evenOdd +} + +/// An item that defines a fill render +final class Fill: ShapeItem { + + /// The opacity of the fill + let opacity: KeyframeGroup + + /// The color keyframes for the fill + let color: KeyframeGroup + + let fillRule: FillRule + + private enum CodingKeys : String, CodingKey { + case opacity = "o" + case color = "c" + case fillRule = "r" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Fill.CodingKeys.self) + self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + self.color = try container.decode(KeyframeGroup.self, forKey: .color) + self.fillRule = try container.decodeIfPresent(FillRule.self, forKey: .fillRule) ?? .nonZeroWinding + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(color, forKey: .color) + try container.encode(fillRule, forKey: .fillRule) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/GradientFill.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/GradientFill.swift new file mode 100644 index 0000000..05627c8 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/GradientFill.swift @@ -0,0 +1,86 @@ +// +// GradientFill.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +enum GradientType: Int, Codable { + case none + case linear + case radial +} + +/// An item that define a gradient fill +final class GradientFill: ShapeItem { + + /// The opacity of the fill + let opacity: KeyframeGroup + + /// The start of the gradient + let startPoint: KeyframeGroup + + /// The end of the gradient + let endPoint: KeyframeGroup + + /// The type of gradient + let gradientType: GradientType + + /// Gradient Highlight Length. Only if type is Radial + let highlightLength: KeyframeGroup? + + /// Highlight Angle. Only if type is Radial + let highlightAngle: KeyframeGroup? + + /// The number of color points in the gradient + let numberOfColors: Int + + /// The Colors of the gradient. + let colors: KeyframeGroup<[Double]> + + private enum CodingKeys : String, CodingKey { + case opacity = "o" + case startPoint = "s" + case endPoint = "e" + case gradientType = "t" + case highlightLength = "h" + case highlightAngle = "a" + case colors = "g" + } + + private enum GradientDataKeys : String, CodingKey { + case numberOfColors = "p" + case colors = "k" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: GradientFill.CodingKeys.self) + self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + self.startPoint = try container.decode(KeyframeGroup.self, forKey: .startPoint) + self.endPoint = try container.decode(KeyframeGroup.self, forKey: .endPoint) + self.gradientType = try container.decode(GradientType.self, forKey: .gradientType) + self.highlightLength = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightLength) + self.highlightAngle = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightAngle) + let colorsContainer = try container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + self.colors = try colorsContainer.decode(KeyframeGroup<[Double]>.self, forKey: .colors) + self.numberOfColors = try colorsContainer.decode(Int.self, forKey: .numberOfColors) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(startPoint, forKey: .startPoint) + try container.encode(endPoint, forKey: .endPoint) + try container.encode(gradientType, forKey: .gradientType) + try container.encodeIfPresent(highlightLength, forKey: .highlightLength) + try container.encodeIfPresent(highlightAngle, forKey: .highlightAngle) + var colorsContainer = container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + try colorsContainer.encode(numberOfColors, forKey: .numberOfColors) + try colorsContainer.encode(colors, forKey: .colors) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/GradientStroke.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/GradientStroke.swift new file mode 100644 index 0000000..163a612 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/GradientStroke.swift @@ -0,0 +1,125 @@ +// +// GradientStroke.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +enum LineCap: Int, Codable { + case none + case butt + case round + case square +} + +enum LineJoin: Int, Codable { + case none + case miter + case round + case bevel +} + +/// An item that define an ellipse shape +final class GradientStroke: ShapeItem { + + /// The opacity of the fill + let opacity: KeyframeGroup + + /// The start of the gradient + let startPoint: KeyframeGroup + + /// The end of the gradient + let endPoint: KeyframeGroup + + /// The type of gradient + let gradientType: GradientType + + /// Gradient Highlight Length. Only if type is Radial + let highlightLength: KeyframeGroup? + + /// Highlight Angle. Only if type is Radial + let highlightAngle: KeyframeGroup? + + /// The number of color points in the gradient + let numberOfColors: Int + + /// The Colors of the gradient. + let colors: KeyframeGroup<[Double]> + + /// The width of the stroke + let width: KeyframeGroup + + /// Line Cap + let lineCap: LineCap + + /// Line Join + let lineJoin: LineJoin + + /// Miter Limit + let miterLimit: Double + + /// The dash pattern of the stroke + let dashPattern: [DashElement]? + + private enum CodingKeys : String, CodingKey { + case opacity = "o" + case startPoint = "s" + case endPoint = "e" + case gradientType = "t" + case highlightLength = "h" + case highlightAngle = "a" + case colors = "g" + case width = "w" + case lineCap = "lc" + case lineJoin = "lj" + case miterLimit = "ml" + case dashPattern = "d" + } + + private enum GradientDataKeys : String, CodingKey { + case numberOfColors = "p" + case colors = "k" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: GradientStroke.CodingKeys.self) + self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + self.startPoint = try container.decode(KeyframeGroup.self, forKey: .startPoint) + self.endPoint = try container.decode(KeyframeGroup.self, forKey: .endPoint) + self.gradientType = try container.decode(GradientType.self, forKey: .gradientType) + self.highlightLength = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightLength) + self.highlightAngle = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightAngle) + self.width = try container.decode(KeyframeGroup.self, forKey: .width) + self.lineCap = try container.decodeIfPresent(LineCap.self, forKey: .lineCap) ?? .round + self.lineJoin = try container.decodeIfPresent(LineJoin.self, forKey: .lineJoin) ?? .round + self.miterLimit = try container.decodeIfPresent(Double.self, forKey: .miterLimit) ?? 4 + // TODO Decode Color Objects instead of array. + let colorsContainer = try container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + self.colors = try colorsContainer.decode(KeyframeGroup<[Double]>.self, forKey: .colors) + self.numberOfColors = try colorsContainer.decode(Int.self, forKey: .numberOfColors) + self.dashPattern = try container.decodeIfPresent([DashElement].self, forKey: .dashPattern) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(startPoint, forKey: .startPoint) + try container.encode(endPoint, forKey: .endPoint) + try container.encode(gradientType, forKey: .gradientType) + try container.encodeIfPresent(highlightLength, forKey: .highlightLength) + try container.encodeIfPresent(highlightAngle, forKey: .highlightAngle) + try container.encode(width, forKey: .width) + try container.encode(lineCap, forKey: .lineCap) + try container.encode(lineJoin, forKey: .lineJoin) + try container.encode(miterLimit, forKey: .miterLimit) + var colorsContainer = container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) + try colorsContainer.encode(numberOfColors, forKey: .numberOfColors) + try colorsContainer.encode(colors, forKey: .colors) + try container.encodeIfPresent(dashPattern, forKey: .dashPattern) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Group.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Group.swift new file mode 100644 index 0000000..c676d04 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Group.swift @@ -0,0 +1,32 @@ +// +// GroupItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class Group: ShapeItem { + + /// A list of shape items. + let items: [ShapeItem] + + private enum CodingKeys : String, CodingKey { + case items = "it" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Group.CodingKeys.self) + self.items = try container.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .items) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(items, forKey: .items) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Merge.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Merge.swift new file mode 100644 index 0000000..3143bb5 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Merge.swift @@ -0,0 +1,41 @@ +// +// Merge.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +enum MergeMode: Int, Codable { + case none + case merge + case add + case subtract + case intersect + case exclude +} + +/// An item that define an ellipse shape +final class Merge: ShapeItem { + + /// The mode of the merge path + let mode: MergeMode + + private enum CodingKeys : String, CodingKey { + case mode = "mm" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Merge.CodingKeys.self) + self.mode = try container.decode(MergeMode.self, forKey: .mode) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(mode, forKey: .mode) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Rectangle.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Rectangle.swift new file mode 100644 index 0000000..b880e75 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Rectangle.swift @@ -0,0 +1,50 @@ +// +// Rectangle.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class Rectangle: ShapeItem { + + /// The direction of the rect. + let direction: PathDirection + + /// The position + let position: KeyframeGroup + + /// The size + let size: KeyframeGroup + + /// The Corner radius of the rectangle + let cornerRadius: KeyframeGroup + + private enum CodingKeys : String, CodingKey { + case direction = "d" + case position = "p" + case size = "s" + case cornerRadius = "r" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Rectangle.CodingKeys.self) + self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise + self.position = try container.decode(KeyframeGroup.self, forKey: .position) + self.size = try container.decode(KeyframeGroup.self, forKey: .size) + self.cornerRadius = try container.decode(KeyframeGroup.self, forKey: .cornerRadius) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(direction, forKey: .direction) + try container.encode(position, forKey: .position) + try container.encode(size, forKey: .size) + try container.encode(cornerRadius, forKey: .cornerRadius) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Repeater.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Repeater.swift new file mode 100644 index 0000000..e8780f4 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Repeater.swift @@ -0,0 +1,80 @@ +// +// Repeater.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class Repeater: ShapeItem { + + /// The number of copies to repeat + let copies: KeyframeGroup + + /// The offset of each copy + let offset: KeyframeGroup + + /// Start Opacity + let startOpacity: KeyframeGroup + + /// End opacity + let endOpacity: KeyframeGroup + + /// The rotation + let rotation: KeyframeGroup + + /// Anchor Point + let anchorPoint: KeyframeGroup + + /// Position + let position: KeyframeGroup + + /// Scale + let scale: KeyframeGroup + + private enum CodingKeys : String, CodingKey { + case copies = "c" + case offset = "o" + case transform = "tr" + } + + private enum TransformKeys : String, CodingKey { + case rotation = "r" + case startOpacity = "so" + case endOpacity = "eo" + case anchorPoint = "a" + case position = "p" + case scale = "s" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Repeater.CodingKeys.self) + self.copies = try container.decodeIfPresent(KeyframeGroup.self, forKey: .copies) ?? KeyframeGroup(Vector1D(0)) + self.offset = try container.decodeIfPresent(KeyframeGroup.self, forKey: .offset) ?? KeyframeGroup(Vector1D(0)) + let transformContainer = try container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform) + self.startOpacity = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .startOpacity) ?? KeyframeGroup(Vector1D(100)) + self.endOpacity = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .endOpacity) ?? KeyframeGroup(Vector1D(100)) + self.rotation = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) + self.position = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .position) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + self.anchorPoint = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .anchorPoint) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + self.scale = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(copies, forKey: .copies) + try container.encode(offset, forKey: .offset) + var transformContainer = container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform) + try transformContainer.encode(startOpacity, forKey: .startOpacity) + try transformContainer.encode(endOpacity, forKey: .endOpacity) + try transformContainer.encode(rotation, forKey: .rotation) + try transformContainer.encode(position, forKey: .position) + try transformContainer.encode(anchorPoint, forKey: .anchorPoint) + try transformContainer.encode(scale, forKey: .scale) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Shape.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Shape.swift new file mode 100644 index 0000000..e2681e6 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Shape.swift @@ -0,0 +1,37 @@ +// +// VectorShape.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class Shape: ShapeItem { + + /// The Path + let path: KeyframeGroup + + let direction: PathDirection? + + private enum CodingKeys : String, CodingKey { + case path = "ks" + case direction = "d" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Shape.CodingKeys.self) + self.path = try container.decode(KeyframeGroup.self, forKey: .path) + self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(path, forKey: .path) + try container.encodeIfPresent(direction, forKey: .direction) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/ShapeItem.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/ShapeItem.swift new file mode 100644 index 0000000..0444361 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/ShapeItem.swift @@ -0,0 +1,95 @@ +// +// ShapeItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// Used for mapping a heterogeneous list to classes for parsing. +extension ShapeType: ClassFamily { + + static var discriminator: Discriminator = .type + + func getType() -> AnyObject.Type { + switch self { + case .ellipse: + return Ellipse.self + case .fill: + return Fill.self + case .gradientFill: + return GradientFill.self + case .group: + return Group.self + case .gradientStroke: + return GradientStroke.self + case .merge: + return Merge.self + case .rectangle: + return Rectangle.self + case .repeater: + return Repeater.self + case .shape: + return Shape.self + case .star: + return Star.self + case .stroke: + return Stroke.self + case .trim: + return Trim.self + case .transform: + return ShapeTransform.self + default: + return ShapeItem.self + } + } +} + +enum ShapeType: String, Codable { + case ellipse = "el" + case fill = "fl" + case gradientFill = "gf" + case group = "gr" + case gradientStroke = "gs" + case merge = "mm" + case rectangle = "rc" + case repeater = "rp" + case round = "rd" + case shape = "sh" + case star = "sr" + case stroke = "st" + case trim = "tm" + case transform = "tr" + case unknown + + public init(from decoder: Decoder) throws { + self = try ShapeType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown + } +} + +/// An item belonging to a Shape Layer +class ShapeItem: Codable { + + /// The name of the shape + let name: String + + /// The type of shape + let type: ShapeType + + let hidden: Bool + + private enum CodingKeys : String, CodingKey { + case name = "nm" + case type = "ty" + case hidden = "hd" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ShapeItem.CodingKeys.self) + self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer" + self.type = try container.decode(ShapeType.self, forKey: .type) + self.hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/ShapeTransform.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/ShapeTransform.swift new file mode 100644 index 0000000..5fba165 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/ShapeTransform.swift @@ -0,0 +1,68 @@ +// +// TransformItem.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class ShapeTransform: ShapeItem { + + /// Anchor Point + let anchor: KeyframeGroup + + /// Position + let position: KeyframeGroup + + /// Scale + let scale: KeyframeGroup + + /// Rotation + let rotation: KeyframeGroup + + /// opacity + let opacity: KeyframeGroup + + /// Skew + let skew: KeyframeGroup + + /// Skew Axis + let skewAxis: KeyframeGroup + + private enum CodingKeys : String, CodingKey { + case anchor = "a" + case position = "p" + case scale = "s" + case rotation = "r" + case opacity = "o" + case skew = "sk" + case skewAxis = "sa" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: ShapeTransform.CodingKeys.self) + self.anchor = try container.decodeIfPresent(KeyframeGroup.self, forKey: .anchor) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + self.position = try container.decodeIfPresent(KeyframeGroup.self, forKey: .position) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) + self.scale = try container.decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) + self.rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) + self.opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) + self.skew = try container.decodeIfPresent(KeyframeGroup.self, forKey: .skew) ?? KeyframeGroup(Vector1D(0)) + self.skewAxis = try container.decodeIfPresent(KeyframeGroup.self, forKey: .skewAxis) ?? KeyframeGroup(Vector1D(0)) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(anchor, forKey: .anchor) + try container.encode(position, forKey: .position) + try container.encode(scale, forKey: .scale) + try container.encode(rotation, forKey: .rotation) + try container.encode(opacity, forKey: .opacity) + try container.encode(skew, forKey: .skew) + try container.encode(skewAxis, forKey: .skewAxis) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Star.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Star.swift new file mode 100644 index 0000000..b280441 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Star.swift @@ -0,0 +1,86 @@ +// +// Star.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +enum StarType: Int, Codable { + case none + case star + case polygon +} + +/// An item that define an ellipse shape +final class Star: ShapeItem { + + /// The direction of the star. + let direction: PathDirection + + /// The position of the star + let position: KeyframeGroup + + /// The outer radius of the star + let outerRadius: KeyframeGroup + + /// The outer roundness of the star + let outerRoundness: KeyframeGroup + + /// The outer radius of the star + let innerRadius: KeyframeGroup? + + /// The outer roundness of the star + let innerRoundness: KeyframeGroup? + + /// The rotation of the star + let rotation: KeyframeGroup + + /// The number of points on the star + let points: KeyframeGroup + + /// The type of star + let starType: StarType + + private enum CodingKeys : String, CodingKey { + case direction = "d" + case position = "p" + case outerRadius = "or" + case outerRoundness = "os" + case innerRadius = "ir" + case innerRoundness = "is" + case rotation = "r" + case points = "pt" + case starType = "sy" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Star.CodingKeys.self) + self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise + self.position = try container.decode(KeyframeGroup.self, forKey: .position) + self.outerRadius = try container.decode(KeyframeGroup.self, forKey: .outerRadius) + self.outerRoundness = try container.decode(KeyframeGroup.self, forKey: .outerRoundness) + self.innerRadius = try container.decodeIfPresent(KeyframeGroup.self, forKey: .innerRadius) + self.innerRoundness = try container.decodeIfPresent(KeyframeGroup.self, forKey: .innerRoundness) + self.rotation = try container.decode(KeyframeGroup.self, forKey: .rotation) + self.points = try container.decode(KeyframeGroup.self, forKey: .points) + self.starType = try container.decode(StarType.self, forKey: .starType) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(direction, forKey: .direction) + try container.encode(position, forKey: .position) + try container.encode(outerRadius, forKey: .outerRadius) + try container.encode(outerRoundness, forKey: .outerRoundness) + try container.encode(innerRadius, forKey: .innerRadius) + try container.encode(innerRoundness, forKey: .innerRoundness) + try container.encode(rotation, forKey: .rotation) + try container.encode(points, forKey: .points) + try container.encode(starType, forKey: .starType) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Stroke.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Stroke.swift new file mode 100644 index 0000000..5e043ed --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Stroke.swift @@ -0,0 +1,67 @@ +// +// Stroke.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +/// An item that define an ellipse shape +final class Stroke: ShapeItem { + + /// The opacity of the stroke + let opacity: KeyframeGroup + + /// The Color of the stroke + let color: KeyframeGroup + + /// The width of the stroke + let width: KeyframeGroup + + /// Line Cap + let lineCap: LineCap + + /// Line Join + let lineJoin: LineJoin + + /// Miter Limit + let miterLimit: Double + + /// The dash pattern of the stroke + let dashPattern: [DashElement]? + + private enum CodingKeys : String, CodingKey { + case opacity = "o" + case color = "c" + case width = "w" + case lineCap = "lc" + case lineJoin = "lj" + case miterLimit = "ml" + case dashPattern = "d" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Stroke.CodingKeys.self) + self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) + self.color = try container.decode(KeyframeGroup.self, forKey: .color) + self.width = try container.decode(KeyframeGroup.self, forKey: .width) + self.lineCap = try container.decodeIfPresent(LineCap.self, forKey: .lineCap) ?? .round + self.lineJoin = try container.decodeIfPresent(LineJoin.self, forKey: .lineJoin) ?? .round + self.miterLimit = try container.decodeIfPresent(Double.self, forKey: .miterLimit) ?? 4 + self.dashPattern = try container.decodeIfPresent([DashElement].self, forKey: .dashPattern) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(opacity, forKey: .opacity) + try container.encode(color, forKey: .color) + try container.encode(width, forKey: .width) + try container.encode(lineCap, forKey: .lineCap) + try container.encode(lineJoin, forKey: .lineJoin) + try container.encode(miterLimit, forKey: .miterLimit) + try container.encodeIfPresent(dashPattern, forKey: .dashPattern) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Trim.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Trim.swift new file mode 100644 index 0000000..dba3d67 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/ShapeItems/Trim.swift @@ -0,0 +1,53 @@ +// +// Trim.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation + +enum TrimType: Int, Codable { + case simultaneously = 1 + case individually = 2 +} +/// An item that define an ellipse shape +final class Trim: ShapeItem { + + /// The start of the trim + let start: KeyframeGroup + + /// The end of the trim + let end: KeyframeGroup + + /// The offset of the trim + let offset: KeyframeGroup + + let trimType: TrimType + + private enum CodingKeys : String, CodingKey { + case start = "s" + case end = "e" + case offset = "o" + case trimType = "m" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Trim.CodingKeys.self) + self.start = try container.decode(KeyframeGroup.self, forKey: .start) + self.end = try container.decode(KeyframeGroup.self, forKey: .end) + self.offset = try container.decode(KeyframeGroup.self, forKey: .offset) + self.trimType = try container.decode(TrimType.self, forKey: .trimType) + try super.init(from: decoder) + } + + override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(start, forKey: .start) + try container.encode(end, forKey: .end) + try container.encode(offset, forKey: .offset) + try container.encode(trimType, forKey: .trimType) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/Font.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/Font.swift new file mode 100644 index 0000000..54d7540 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/Font.swift @@ -0,0 +1,35 @@ +// +// Font.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +final class Font: Codable { + + let name: String + let familyName: String + let style: String + let ascent: Double + + private enum CodingKeys: String, CodingKey { + case name = "fName" + case familyName = "fFamily" + case style = "fStyle" + case ascent = "ascent" + } + +} + +/// A list of fonts +final class FontList: Codable { + + let fonts: [Font] + + enum CodingKeys : String, CodingKey { + case fonts = "list" + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/Glyph.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/Glyph.swift new file mode 100644 index 0000000..755f00a --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/Glyph.swift @@ -0,0 +1,72 @@ +// +// Glyph.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +/// A model that holds a vector character +final class Glyph: Codable { + + /// The character + let character: String + + /// The font size of the character + let fontSize: Double + + /// The font family of the character + let fontFamily: String + + /// The Style of the character + let fontStyle: String + + /// The Width of the character + let width: Double + + /// The Shape Data of the Character + let shapes: [ShapeItem] + + private enum CodingKeys: String, CodingKey { + case character = "ch" + case fontSize = "size" + case fontFamily = "fFamily" + case fontStyle = "style" + case width = "w" + case shapeWrapper = "data" + } + + private enum ShapeKey: String, CodingKey { + case shapes = "shapes" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Glyph.CodingKeys.self) + self.character = try container.decode(String.self, forKey: .character) + self.fontSize = try container.decode(Double.self, forKey: .fontSize) + self.fontFamily = try container.decode(String.self, forKey: .fontFamily) + self.fontStyle = try container.decode(String.self, forKey: .fontStyle) + self.width = try container.decode(Double.self, forKey: .width) + if container.contains(.shapeWrapper), + let shapeContainer = try? container.nestedContainer(keyedBy: ShapeKey.self, forKey: .shapeWrapper), + shapeContainer.contains(.shapes) { + self.shapes = try shapeContainer.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .shapes) + } else { + self.shapes = [] + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(character, forKey: .character) + try container.encode(fontSize, forKey: .fontSize) + try container.encode(fontFamily, forKey: .fontFamily) + try container.encode(fontStyle, forKey: .fontStyle) + try container.encode(width, forKey: .width) + + var shapeContainer = container.nestedContainer(keyedBy: ShapeKey.self, forKey: .shapeWrapper) + try shapeContainer.encode(shapes, forKey: .shapes) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/TextAnimator.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/TextAnimator.swift new file mode 100644 index 0000000..dd93cdf --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/TextAnimator.swift @@ -0,0 +1,99 @@ +// +// TextAnimator.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +final class TextAnimator: Codable { + + let name: String + + /// Anchor + let anchor: KeyframeGroup? + + /// Position + let position: KeyframeGroup? + + /// Scale + let scale: KeyframeGroup? + + /// Skew + let skew: KeyframeGroup? + + /// Skew Axis + let skewAxis: KeyframeGroup? + + /// Rotation + let rotation: KeyframeGroup? + + /// Opacity + let opacity: KeyframeGroup? + + /// Stroke Color + let strokeColor: KeyframeGroup? + + /// Fill Color + let fillColor: KeyframeGroup? + + /// Stroke Width + let strokeWidth: KeyframeGroup? + + /// Tracking + let tracking: KeyframeGroup? + + private enum CodingKeys: String, CodingKey { +// case textSelector = "s" TODO + case textAnimator = "a" + case name = "nm" + } + + private enum TextSelectorKeys: String, CodingKey { + case start = "s" + case end = "e" + case offset = "o" + } + + private enum TextAnimatorKeys: String, CodingKey { + case fillColor = "fc" + case strokeColor = "sc" + case strokeWidth = "sw" + case tracking = "t" + case anchor = "a" + case position = "p" + case scale = "s" + case skew = "sk" + case skewAxis = "sa" + case rotation = "r" + case opacity = "o" + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: TextAnimator.CodingKeys.self) + self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "" + let animatorContainer = try container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator) + self.fillColor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .fillColor) + self.strokeColor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .strokeColor) + self.strokeWidth = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .strokeWidth) + self.tracking = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .tracking) + self.anchor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .anchor) + self.position = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .position) + self.scale = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .scale) + self.skew = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .skew) + self.skewAxis = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .skewAxis) + self.rotation = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) + self.opacity = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) + + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var animatorContainer = container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator) + try animatorContainer.encodeIfPresent(fillColor, forKey: .fillColor) + try animatorContainer.encodeIfPresent(strokeColor, forKey: .strokeColor) + try animatorContainer.encodeIfPresent(strokeWidth, forKey: .strokeWidth) + try animatorContainer.encodeIfPresent(tracking, forKey: .tracking) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/TextDocument.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/TextDocument.swift new file mode 100644 index 0000000..dd2e747 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Model/Text/TextDocument.swift @@ -0,0 +1,70 @@ +// +// TextDocument.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/9/19. +// + +import Foundation + +enum TextJustification: Int, Codable { + case left + case right + case center +} + +final class TextDocument: Codable { + + /// The Text + let text: String + + /// The Font size + let fontSize: Double + + /// The Font Family + let fontFamily: String + + /// Justification + let justification: TextJustification + + /// Tracking + let tracking: Int + + /// Line Height + let lineHeight: Double + + /// Baseline + let baseline: Double? + + /// Fill Color data + let fillColorData: Color + + /// Scroke Color data + let strokeColorData: Color? + + /// Stroke Width + let strokeWidth: Double? + + /// Stroke Over Fill + let strokeOverFill: Bool? + + let textFramePosition: Vector3D? + + let textFrameSize: Vector3D? + + private enum CodingKeys : String, CodingKey { + case text = "t" + case fontSize = "s" + case fontFamily = "f" + case justification = "j" + case tracking = "tr" + case lineHeight = "lh" + case baseline = "ls" + case fillColorData = "fc" + case strokeColorData = "sc" + case strokeWidth = "sw" + case strokeOverFill = "of" + case textFramePosition = "ps" + case textFrameSize = "sz" + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Extensions/ItemsExtension.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Extensions/ItemsExtension.swift new file mode 100644 index 0000000..9220a63 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Extensions/ItemsExtension.swift @@ -0,0 +1,95 @@ +// +// ItemsExtension.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/18/19. +// + +import Foundation + +final class NodeTree { + var rootNode: AnimatorNode? = nil + var transform: ShapeTransform? = nil + var renderContainers: [ShapeContainerLayer] = [] + var paths: [PathOutputNode] = [] + var childrenNodes: [AnimatorNode] = [] +} + +extension Array where Element == ShapeItem { + func initializeNodeTree() -> NodeTree { + + let nodeTree = NodeTree() + + for item in self { + guard item.hidden == false, item.type != .unknown else { continue } + if let fill = item as? Fill { + let node = FillNode(parentNode: nodeTree.rootNode, fill: fill) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let stroke = item as? Stroke { + let node = StrokeNode(parentNode: nodeTree.rootNode, stroke: stroke) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let gradientFill = item as? GradientFill { + let node = GradientFillNode(parentNode: nodeTree.rootNode, gradientFill: gradientFill) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let gradientStroke = item as? GradientStroke { + let node = GradientStrokeNode(parentNode: nodeTree.rootNode, gradientStroke: gradientStroke) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let ellipse = item as? Ellipse { + let node = EllipseNode(parentNode: nodeTree.rootNode, ellipse: ellipse) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let rect = item as? Rectangle { + let node = RectangleNode(parentNode: nodeTree.rootNode, rectangle: rect) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let star = item as? Star { + switch star.starType { + case .none: + continue + case .polygon: + let node = PolygonNode(parentNode: nodeTree.rootNode, star: star) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + case .star: + let node = StarNode(parentNode: nodeTree.rootNode, star: star) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } + } else if let shape = item as? Shape { + let node = ShapeNode(parentNode: nodeTree.rootNode, shape: shape) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let trim = item as? Trim { + let node = TrimPathNode(parentNode: nodeTree.rootNode, trim: trim, upstreamPaths: nodeTree.paths) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + } else if let xform = item as? ShapeTransform { + nodeTree.transform = xform + continue + } else if let group = item as? Group { + + let tree = group.items.initializeNodeTree() + let node = GroupNode(name: group.name, parentNode: nodeTree.rootNode, tree: tree) + nodeTree.rootNode = node + nodeTree.childrenNodes.append(node) + /// Now add all child paths to current tree + nodeTree.paths.append(contentsOf: tree.paths) + nodeTree.renderContainers.append(node.container) + } + + if let pathNode = nodeTree.rootNode as? PathNode { + //// Add path container to the node tree + nodeTree.paths.append(pathNode.pathOutput) + } + + if let renderNode = nodeTree.rootNode as? RenderNode { + nodeTree.renderContainers.append(ShapeRenderLayer(renderer: renderNode.renderer)) + } + } + return nodeTree + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift new file mode 100644 index 0000000..5e10481 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift @@ -0,0 +1,47 @@ +// +// NodeProperty.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import CoreGraphics + +/// A node property that holds a reference to a T ValueProvider and a T ValueContainer. +class NodeProperty: AnyNodeProperty { + + var valueType: Any.Type { return T.self } + + var value: T { + return typedContainer.outputValue + } + + var valueContainer: AnyValueContainer { + return typedContainer + } + + var valueProvider: AnyValueProvider + + init(provider: AnyValueProvider) { + self.valueProvider = provider + self.typedContainer = ValueContainer(provider.value(frame: 0) as! T) + self.typedContainer.setNeedsUpdate() + } + + func needsUpdate(frame: CGFloat) -> Bool { + return valueContainer.needsUpdate || valueProvider.hasUpdate(frame: frame) + } + + func setProvider(provider: AnyValueProvider) { + guard provider.valueType == valueType else { return } + self.valueProvider = provider + valueContainer.setNeedsUpdate() + } + + func update(frame: CGFloat) { + typedContainer.setValue(valueProvider.value(frame: frame), forFrame: frame) + } + + fileprivate var typedContainer: ValueContainer +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift new file mode 100644 index 0000000..7691f39 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift @@ -0,0 +1,44 @@ +// +// AnyNodeProperty.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import CoreGraphics +/// A property of a node. The node property holds a provider and a container +protocol AnyNodeProperty { + + /// Returns true if the property needs to recompute its stored value + func needsUpdate(frame: CGFloat) -> Bool + + /// Updates the property for the frame + func update(frame: CGFloat) + + /// The stored value container for the property + var valueContainer: AnyValueContainer { get } + + /// The value provider for the property + var valueProvider: AnyValueProvider { get } + + /// The Type of the value provider + var valueType: Any.Type { get } + + /// Sets the value provider for the property. + func setProvider(provider: AnyValueProvider) +} + +extension AnyNodeProperty { + + /// Returns the most recently computed value for the keypath, returns nil if property wasn't found + func getValueOfType() -> T? { + return valueContainer.value as? T + } + + /// Returns the most recently computed value for the keypath, returns nil if property wasn't found + func getValue() -> Any? { + return valueContainer.value + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift new file mode 100644 index 0000000..c841686 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift @@ -0,0 +1,26 @@ +// +// AnyValueContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import CoreGraphics + +/// The container for the value of a property. +protocol AnyValueContainer: class { + + /// The stored value of the container + var value: Any { get } + + /// Notifies the provider that it should update its container + func setNeedsUpdate() + + /// When true the container needs to have its value updated by its provider + var needsUpdate: Bool { get } + + /// The frame time of the last provided update + var lastUpdateFrame: CGFloat { get } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift new file mode 100644 index 0000000..a0dbf20 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift @@ -0,0 +1,24 @@ +// +// KeypathSettable.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +import QuartzCore + +/// Protocol that provides keypath search functionality. Returns all node properties associated with a keypath. +protocol KeypathSearchable { + + /// The name of the Keypath + var keypathName: String { get } + + /// A list of properties belonging to the keypath. + var keypathProperties: [String : AnyNodeProperty] { get } + + /// Children Keypaths + var childKeypaths: [KeypathSearchable] { get } + + var keypathLayer: CALayer? { get } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift new file mode 100644 index 0000000..0fdc178 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift @@ -0,0 +1,42 @@ +// +// NodePropertyMap.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/21/19. +// + +import Foundation +import QuartzCore + +protocol NodePropertyMap { + var properties: [AnyNodeProperty] { get } +} + +extension NodePropertyMap { + + var childKeypaths: [KeypathSearchable] { + return [] + } + + var keypathLayer: CALayer? { + return nil + } + + /// Checks if the node's local contents need to be rebuilt. + func needsLocalUpdate(frame: CGFloat) -> Bool { + for property in properties { + if property.needsUpdate(frame: frame) { + return true + } + } + return false + } + + /// Rebuilds only the local nodes that have an update for the frame + func updateNodeProperties(frame: CGFloat) { + properties.forEach { (property) in + property.update(frame: frame) + } + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift new file mode 100644 index 0000000..7f13644 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift @@ -0,0 +1,43 @@ +// +// ValueContainer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import CoreGraphics + +/// A container for a node value that is Typed to T. +class ValueContainer: AnyValueContainer { + + private(set) var lastUpdateFrame: CGFloat = CGFloat.infinity + + func setValue(_ value: Any, forFrame: CGFloat) { + if let typedValue = value as? T { + needsUpdate = false + lastUpdateFrame = forFrame + outputValue = typedValue + } + } + + func setNeedsUpdate() { + needsUpdate = true + } + + var value: Any { + return outputValue as Any + } + + var outputValue: T { + didSet { + needsUpdate = false + } + } + + init(_ value: T) { + self.outputValue = value + } + + fileprivate(set) var needsUpdate: Bool = true +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift new file mode 100644 index 0000000..e33d943 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift @@ -0,0 +1,44 @@ +// +// KeyframeGroupInterpolator.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation +import CoreGraphics + +/// A value provider that produces an array of values from an array of Keyframe Interpolators +final class GroupInterpolator: AnyValueProvider where ValueType: Interpolatable { + var valueType: Any.Type { + return [ValueType].self + } + + func hasUpdate(frame: CGFloat) -> Bool { + for interpolator in keyframeInterpolators { + if interpolator.hasUpdate(frame: frame) { + return true + } + } + return false + } + + func value(frame: CGFloat) -> Any { + var output = [ValueType]() + for interpolator in keyframeInterpolators { + output.append(interpolator.value(frame: frame) as! ValueType) + } + return output + } + + /// Initialize with an array of array of keyframes. + init(keyframeGroups: [[Keyframe]]) { + var interpolators = [KeyframeInterpolator]() + for keyframes in keyframeGroups { + interpolators.append(KeyframeInterpolator(keyframes: keyframes)) + } + self.keyframeInterpolators = interpolators + } + let keyframeInterpolators: [KeyframeInterpolator] + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift new file mode 100644 index 0000000..2cc831e --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift @@ -0,0 +1,222 @@ +// +// KeyframeInterpolator.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/15/19. +// + +import Foundation +import CoreGraphics + +/// A value provider that produces a value at Time from a group of keyframes +final class KeyframeInterpolator: AnyValueProvider where ValueType: Interpolatable { + + init(keyframes: [Keyframe]) { + self.keyframes = keyframes + } + let keyframes: [Keyframe] + + var valueType: Any.Type { + return ValueType.self + } + + /** + Returns true to trigger a frame update for this interpolator. + + An interpolator will be asked if it needs to update every frame. + If the interpolator needs updating it will be asked to compute its value for + the given frame. + + Cases a keyframe should not be updated: + - If time is in span and leading keyframe is hold + - If time is after the last keyframe. + - If time is before the first keyframe + + Cases for updating a keyframe: + - If time is in the span, and is not a hold + - If time is outside of the span, and there are more keyframes + - If a value delegate is set + - If leading and trailing are both nil. + */ + func hasUpdate(frame: CGFloat) -> Bool { + if lastUpdatedFrame == nil { + return true + } + + if let leading = leadingKeyframe, + trailingKeyframe == nil, + leading.time < frame { + /// Frame is after bounds of keyframes + return false + } + if let trailing = trailingKeyframe, + leadingKeyframe == nil, + frame < trailing.time { + /// Frame is before bounds of keyframes + return false + } + if let leading = leadingKeyframe, + let trailing = trailingKeyframe, + leading.isHold, + leading.time < frame, + frame < trailing.time { + return false + } + return true + } + + fileprivate var lastUpdatedFrame: CGFloat? + + @discardableResult + func value(frame: CGFloat) -> Any { + // First set the keyframe span for the frame. + updateSpanIndices(frame: frame) + lastUpdatedFrame = frame + // If only one keyframe return its value + let progress: CGFloat + let value: ValueType + + if let leading = leadingKeyframe, + let trailing = trailingKeyframe { + /// We have leading and trailing keyframe. + progress = leading.interpolatedProgress(trailing, keyTime: frame) + value = leading.interpolate(trailing, progress: progress) + } else if let leading = leadingKeyframe { + progress = 0 + value = leading.value + } else if let trailing = trailingKeyframe { + progress = 1 + value = trailing.value + } else { + /// Satisfy the compiler. + progress = 0 + value = keyframes[0].value + } + return value + } + + fileprivate var leadingIndex: Int? = nil + fileprivate var trailingIndex: Int? = nil + fileprivate var leadingKeyframe: Keyframe? = nil + fileprivate var trailingKeyframe: Keyframe? = nil + + /// Finds the appropriate Leading and Trailing keyframe index for the given time. + fileprivate func updateSpanIndices(frame: CGFloat) { + guard keyframes.count > 0 else { + leadingIndex = nil + trailingIndex = nil + leadingKeyframe = nil + trailingKeyframe = nil + return + } + + /** + This function searches through the array to find the span of two keyframes + that contain the current time. + + We could use Array.first(where:) but that would search through the entire array + each frame. + Instead we track the last used index and search either forwards or + backwards from there. This reduces the iterations and complexity from + + O(n), where n is the length of the sequence to + O(n), where n is the number of items after or before the last used index. + + */ + + if keyframes.count == 1 { + /// Only one keyframe. Set it as first and move on. + leadingIndex = 0 + trailingIndex = nil + leadingKeyframe = keyframes[0] + trailingKeyframe = nil + return + } + + /// Sets the initial keyframes. This is often only needed for the first check. + if leadingIndex == nil && + trailingIndex == nil { + if frame < keyframes[0].time { + /// Time is before the first keyframe. Set it as the trailing. + trailingIndex = 0 + } else { + /// Time is after the first keyframe. Set the keyframe and the trailing. + leadingIndex = 0 + trailingIndex = 1 + } + } + + if let currentTrailing = trailingIndex, + keyframes[currentTrailing].time <= frame { + /// Time is after the current span. Iterate forward. + var newLeading = currentTrailing + var keyframeFound: Bool = false + while !keyframeFound { + + leadingIndex = newLeading + trailingIndex = keyframes.validIndex(newLeading + 1) + + guard let trailing = trailingIndex else { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true + continue + } + if frame < keyframes[trailing].time { + /// Keyframe in current span. + keyframeFound = true + continue + } + /// Advance the array. + newLeading = trailing + } + + } else if let currentLeading = leadingIndex, + frame < keyframes[currentLeading].time { + + /// Time is before the current span. Iterate backwards + var newTrailing = currentLeading + + var keyframeFound: Bool = false + while !keyframeFound { + + leadingIndex = keyframes.validIndex(newTrailing - 1) + trailingIndex = newTrailing + + guard let leading = leadingIndex else { + /// We have reached the end of our keyframes. Time is after the last keyframe. + keyframeFound = true + continue + } + if keyframes[leading].time <= frame { + /// Keyframe in current span. + keyframeFound = true + continue + } + /// Step back + newTrailing = leading + } + } + if let keyFrame = leadingIndex { + leadingKeyframe = keyframes[keyFrame] + } else { + leadingKeyframe = nil + } + + if let keyFrame = trailingIndex { + trailingKeyframe = keyframes[keyFrame] + } else { + trailingKeyframe = nil + } + } +} + +fileprivate extension Array { + + func validIndex(_ index: Int) -> Int? { + if 0 <= index, index < endIndex { + return index + } + return nil + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift new file mode 100644 index 0000000..ba24402 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift @@ -0,0 +1,38 @@ +// +// SingleValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +/// Returns a value for every frame. +final class SingleValueProvider: AnyValueProvider { + + var value: ValueType { + didSet { + hasUpdate = true + } + } + + init(_ value: ValueType) { + self.value = value + } + + var valueType: Any.Type { + return ValueType.self + } + + func hasUpdate(frame: CGFloat) -> Bool { + return hasUpdate + } + + func value(frame: CGFloat) -> Any { + hasUpdate = false + return value + } + + private var hasUpdate: Bool = true +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift new file mode 100644 index 0000000..3a1ab24 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift @@ -0,0 +1,247 @@ +// +// TrimPathNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/23/19. +// + +import Foundation +import QuartzCore + +final class TrimPathProperties: NodePropertyMap, KeypathSearchable { + + init(trim: Trim) { + self.keypathName = trim.name + self.start = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.start.keyframes)) + self.end = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.end.keyframes)) + self.offset = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.offset.keyframes)) + self.type = trim.trimType + self.keypathProperties = [ + "Start" : start, + "End" : end, + "Offset" : offset + ] + self.properties = Array(keypathProperties.values) + } + + let keypathProperties: [String : AnyNodeProperty] + let properties: [AnyNodeProperty] + let keypathName: String + + let start: NodeProperty + let end: NodeProperty + let offset: NodeProperty + let type: TrimType +} + +final class TrimPathNode: AnimatorNode { + + let properties: TrimPathProperties + + fileprivate let upstreamPaths: [PathOutputNode] + + init(parentNode: AnimatorNode?, trim: Trim, upstreamPaths: [PathOutputNode]) { + self.outputNode = PassThroughOutputNode(parent: parentNode?.outputNode) + self.parentNode = parentNode + self.properties = TrimPathProperties(trim: trim) + self.upstreamPaths = upstreamPaths + } + + // MARK: Animator Node + var propertyMap: NodePropertyMap & KeypathSearchable { + return properties + } + + let parentNode: AnimatorNode? + let outputNode: NodeOutput + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true + + func forceUpstreamOutputUpdates() -> Bool { + return hasLocalUpdates || hasUpstreamUpdates + } + + func rebuildOutputs(frame: CGFloat) { + /// Make sure there is a trim. + let startValue = properties.start.value.cgFloatValue * 0.01 + let endValue = properties.end.value.cgFloatValue * 0.01 + let start = min(startValue, endValue) + let end = max(startValue, endValue) + + let offset = properties.offset.value.cgFloatValue.truncatingRemainder(dividingBy: 360) / 360 + + /// No need to trim, it's a full path + if start == 0, end == 1 { + return + } + + /// All paths are empty. + if start == end { + for pathContainer in upstreamPaths { + pathContainer.removePaths(updateFrame: frame) + } + return + } + + if properties.type == .simultaneously { + /// Just trim each path + for pathContainer in upstreamPaths { + let pathObjects = pathContainer.removePaths(updateFrame: frame) + for path in pathObjects { + // We are treating each compount path as an individual path. Its subpaths are treated as a whole. + pathContainer.appendPath(path.trim(fromPosition: start, toPosition: end, offset: offset, trimSimultaneously: false), updateFrame: frame) + } + } + return + } + + /// Individual path trimming. + + /// Brace yourself for the below code. + + /// Normalize lengths with offset. + var startPosition = (start+offset).truncatingRemainder(dividingBy: 1) + var endPosition = (end+offset).truncatingRemainder(dividingBy: 1) + + if startPosition < 0 { + startPosition = 1 + startPosition + } + + if endPosition < 0 { + endPosition = 1 + endPosition + } + if startPosition == 1 { + startPosition = 0 + } + if endPosition == 0 { + endPosition = 1 + } + + + /// First get the total length of all paths. + var totalLength: CGFloat = 0 + upstreamPaths.forEach({ totalLength = totalLength + $0.totalLength }) + + /// Now determine the start and end cut lengths + let startLength = startPosition * totalLength + let endLength = endPosition * totalLength + var pathStart: CGFloat = 0 + + /// Now loop through all path containers + for pathContainer in upstreamPaths { + + let pathEnd = pathStart + pathContainer.totalLength + + if !startLength.isInRange(pathStart, pathEnd) && + endLength.isInRange(pathStart, pathEnd) { + // pathStart|=======E----------------------|pathEnd + // Cut path components, removing after end. + + let pathCutLength = endLength - pathStart + let subpaths = pathContainer.removePaths(updateFrame: frame) + var subpathStart: CGFloat = 0 + for path in subpaths { + let subpathEnd = subpathStart + path.length + if pathCutLength < subpathEnd { + /// This is the subpath that needs to be cut. + let cutLength = pathCutLength - subpathStart + let newPath = path.trim(fromPosition: 0, toPosition: cutLength / path.length, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + break + } else { + /// Add to container and move on + pathContainer.appendPath(path, updateFrame: frame) + } + if pathCutLength == subpathEnd { + /// Right on the end. The next subpath is not included. Break. + break + } + subpathStart = subpathEnd + } + + } else if !endLength.isInRange(pathStart, pathEnd) && + startLength.isInRange(pathStart, pathEnd) { + // pathStart|-------S======================|pathEnd + // + + // Cut path components, removing before beginning. + let pathCutLength = startLength - pathStart + // Clear paths from container + let subpaths = pathContainer.removePaths(updateFrame: frame) + var subpathStart: CGFloat = 0 + for path in subpaths { + let subpathEnd = subpathStart + path.length + + if subpathStart < pathCutLength, pathCutLength < subpathEnd { + /// This is the subpath that needs to be cut. + let cutLength = pathCutLength - subpathStart + let newPath = path.trim(fromPosition: cutLength / path.length, toPosition: 1, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + } else if pathCutLength <= subpathStart { + pathContainer.appendPath(path, updateFrame: frame) + } + subpathStart = subpathEnd + } + } else if endLength.isInRange(pathStart, pathEnd) && + startLength.isInRange(pathStart, pathEnd) { + // pathStart|-------S============E---------|endLength + // pathStart|=====E----------------S=======|endLength + // trim from path beginning to endLength. + + // Cut path components, removing before beginnings. + let startCutLength = startLength - pathStart + let endCutLength = endLength - pathStart + // Clear paths from container + let subpaths = pathContainer.removePaths(updateFrame: frame) + var subpathStart: CGFloat = 0 + for path in subpaths { + + let subpathEnd = subpathStart + path.length + + if !startCutLength.isInRange(subpathStart, subpathEnd) && + !endCutLength.isInRange(subpathStart, subpathEnd) { + // The whole path is included. Add + // S|==============================|E + pathContainer.appendPath(path, updateFrame: frame) + + } else if startCutLength.isInRange(subpathStart, subpathEnd) && + !endCutLength.isInRange(subpathStart, subpathEnd) { + /// The start of the path needs to be trimmed + // |-------S======================|E + let cutLength = startCutLength - subpathStart + let newPath = path.trim(fromPosition: cutLength / path.length, toPosition: 1, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + } else if !startCutLength.isInRange(subpathStart, subpathEnd) && + endCutLength.isInRange(subpathStart, subpathEnd) { + // S|=======E----------------------| + let cutLength = endCutLength - subpathStart + let newPath = path.trim(fromPosition: 0, toPosition: cutLength / path.length, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + break + } else if startCutLength.isInRange(subpathStart, subpathEnd) && + endCutLength.isInRange(subpathStart, subpathEnd) { + // |-------S============E---------| + let cutFromLength = startCutLength - subpathStart + let cutToLength = endCutLength - subpathStart + let newPath = path.trim(fromPosition: cutFromLength / path.length, toPosition: cutToLength / path.length, offset: 0, trimSimultaneously: false) + pathContainer.appendPath(newPath, updateFrame: frame) + break + } + + subpathStart = subpathEnd + } + } else if (endLength <= pathStart && pathEnd <= startLength) || + (startLength <= pathStart && endLength <= pathStart) || + (pathEnd <= startLength && pathEnd <= endLength) { + /// The Path needs to be cleared + pathContainer.removePaths(updateFrame: frame) + } + + pathStart = pathEnd + } + + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift new file mode 100644 index 0000000..2f48762 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift @@ -0,0 +1,70 @@ +// +// TransformNodeOutput.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import CoreGraphics +import QuartzCore + +class GroupOutputNode: NodeOutput { + + init(parent: NodeOutput?, rootNode: NodeOutput?) { + self.parent = parent + self.rootNode = rootNode + } + + let parent: NodeOutput? + let rootNode: NodeOutput? + var isEnabled: Bool = true + + private(set) var outputPath: CGPath? = nil + private(set) var transform: CATransform3D = CATransform3DIdentity + + func setTransform(_ xform: CATransform3D, forFrame: CGFloat) { + transform = xform + outputPath = nil + } + + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + guard isEnabled else { + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + outputPath = parent?.outputPath + return upstreamUpdates + } + + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + if upstreamUpdates { + outputPath = nil + } + let rootUpdates = rootNode?.hasOutputUpdates(forFrame) ?? false + if rootUpdates { + outputPath = nil + } + + var localUpdates: Bool = false + if outputPath == nil { + localUpdates = true + + let newPath = CGMutablePath() + if let parentNode = parent, let parentPath = parentNode.outputPath { + /// First add parent path. + newPath.addPath(parentPath) + } + var xform = CATransform3DGetAffineTransform(transform) + if let rootNode = rootNode, + let rootPath = rootNode.outputPath, + let xformedPath = rootPath.copy(using: &xform) { + /// Now add root path. Note root path is transformed. + newPath.addPath(xformedPath) + } + + outputPath = newPath + } + + return upstreamUpdates || localUpdates + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift new file mode 100644 index 0000000..8a911db --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift @@ -0,0 +1,43 @@ +// +// PassThroughOutputNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import CoreGraphics + +class PassThroughOutputNode: NodeOutput { + + init(parent: NodeOutput?) { + self.parent = parent + } + + let parent: NodeOutput? + + var hasUpdate: Bool = false + var isEnabled: Bool = true + + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + /// Changes to this node do not affect downstream nodes. + let parentUpdate = parent?.hasOutputUpdates(forFrame) ?? false + /// Changes to upstream nodes do, however, affect this nodes state. + hasUpdate = hasUpdate || parentUpdate + return parentUpdate + } + + var outputPath: CGPath? { + if let parent = parent { + return parent.outputPath + } + return nil + } + + func hasRenderUpdates(_ forFrame: CGFloat) -> Bool { + /// Return true if there are upstream updates or if this node has updates + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + hasUpdate = hasUpdate || upstreamUpdates + return hasUpdate + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift new file mode 100644 index 0000000..f71655b --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift @@ -0,0 +1,88 @@ +// +// PathNodeOutput.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import CoreGraphics + +/// A node that has an output of a BezierPath +class PathOutputNode: NodeOutput { + + init(parent: NodeOutput?) { + self.parent = parent + } + + let parent: NodeOutput? + + fileprivate(set) var outputPath: CGPath? = nil + + var lastUpdateFrame: CGFloat? = nil + var lastPathBuildFrame: CGFloat? = nil + var isEnabled: Bool = true + + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + guard isEnabled else { + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + outputPath = parent?.outputPath + return upstreamUpdates + } + + /// Ask if parent was updated + let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false + + /// If parent was updated and the path hasn't been built for this frame, clear the path. + if upstreamUpdates && lastPathBuildFrame != forFrame { + outputPath = nil + } + + if outputPath == nil { + /// If the path is clear, build the new path. + lastPathBuildFrame = forFrame + let newPath = CGMutablePath() + if let parentNode = parent, let parentPath = parentNode.outputPath { + newPath.addPath(parentPath) + } + for path in pathObjects { + for subPath in path.paths { + newPath.addPath(subPath.cgPath()) + } + } + outputPath = newPath + } + + /// Return true if there were upstream updates or if this node was updated. + return upstreamUpdates || (lastUpdateFrame == forFrame) + } + + // MARK: Internal + + fileprivate(set) var totalLength: CGFloat = 0 + fileprivate(set) var pathObjects: [CompoundBezierPath] = [] + + @discardableResult func removePaths(updateFrame: CGFloat?) -> [CompoundBezierPath] { + lastUpdateFrame = updateFrame + let returnPaths = pathObjects + outputPath = nil + totalLength = 0 + pathObjects = [] + return returnPaths + } + + func setPath(_ path: BezierPath, updateFrame: CGFloat) { + lastUpdateFrame = updateFrame + outputPath = nil + totalLength = path.length + pathObjects = [CompoundBezierPath(path: path)] + } + + func appendPath(_ path: CompoundBezierPath, updateFrame: CGFloat) { + lastUpdateFrame = updateFrame + outputPath = nil + totalLength = totalLength + path.length + pathObjects.append(path) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift new file mode 100644 index 0000000..dc78121 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift @@ -0,0 +1,72 @@ +// +// FillRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore +import CoreGraphics + +extension FillRule { + var cgFillRule: CGPathFillRule { + switch self { + case .evenOdd: + return .evenOdd + default: + return .winding + } + } + + var caFillRule: CAShapeLayerFillRule { + switch self { + case .evenOdd: + return CAShapeLayerFillRule.evenOdd + default: + return CAShapeLayerFillRule.nonZero + } + } +} + +/// A rendered for a Path Fill +final class FillRenderer: PassThroughOutputNode, Renderable { + + let shouldRenderInContext: Bool = false + + func updateShapeLayer(layer: CAShapeLayer) { + layer.fillColor = color + layer.opacity = Float(opacity) + layer.fillRule = fillRule.caFillRule + hasUpdate = false + } + + var color: CGColor? { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var fillRule: FillRule = .none { + didSet { + hasUpdate = true + } + } + + func render(_ inContext: CGContext) { + guard inContext.path != nil && inContext.path!.isEmpty == false else { + return + } + guard let color = color else { return } + hasUpdate = false + inContext.setAlpha(opacity * 0.01) + inContext.setFillColor(color) + inContext.fillPath(using: fillRule.cgFillRule) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift new file mode 100644 index 0000000..cd54a28 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift @@ -0,0 +1,126 @@ +// +// GradientFillRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +/// A rendered for a Path Fill +final class GradientFillRenderer: PassThroughOutputNode, Renderable { + + var shouldRenderInContext: Bool = true + + func updateShapeLayer(layer: CAShapeLayer) { + // Not applicable + } + + func render(_ inContext: CGContext) { + guard inContext.path != nil && inContext.path!.isEmpty == false else { + return + } + hasUpdate = false + var alphaColors = [CGColor]() + var alphaLocations = [CGFloat]() + + var gradientColors = [CGColor]() + var colorLocations = [CGFloat]() + let colorSpace = CGColorSpaceCreateDeviceRGB() + let maskColorSpace = CGColorSpaceCreateDeviceGray() + for i in 0.. ix, let color = CGColor(colorSpace: colorSpace, components: [colors[ix + 1], colors[ix + 2], colors[ix + 3], 1]) { + gradientColors.append(color) + colorLocations.append(colors[ix]) + } + } + + var drawMask = false + for i in stride(from: (numberOfColors * 4), to: colors.endIndex, by: 2) { + let alpha = colors[i + 1] + if alpha < 1 { + drawMask = true + } + if let color = CGColor(colorSpace: maskColorSpace, components: [alpha, 1]) { + alphaLocations.append(colors[i]) + alphaColors.append(color) + } + } + + inContext.setAlpha(opacity) + inContext.clip() + + /// First draw a mask is necessary. + if drawMask { + guard let maskGradient = CGGradient(colorsSpace: maskColorSpace, + colors: alphaColors as CFArray, + locations: alphaLocations), + let maskContext = CGContext(data: nil, + width: inContext.width, + height: inContext.height, + bitsPerComponent: 8, + bytesPerRow: inContext.width, + space: maskColorSpace, + bitmapInfo: 0) else { return } + let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(maskContext.height)) + maskContext.concatenate(flipVertical) + maskContext.concatenate(inContext.ctm) + if type == .linear { + maskContext.drawLinearGradient(maskGradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } else { + maskContext.drawRadialGradient(maskGradient, startCenter: start, startRadius: 0, endCenter: start, endRadius: start.distanceTo(end), options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } + /// Clips the gradient + if let alphaMask = maskContext.makeImage() { + inContext.clip(to: inContext.boundingBoxOfClipPath, mask: alphaMask) + } + } + + /// Now draw the gradient + guard let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors as CFArray, locations: colorLocations) else { return } + if type == .linear { + inContext.drawLinearGradient(gradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } else { + inContext.drawRadialGradient(gradient, startCenter: start, startRadius: 0, endCenter: start, endRadius: start.distanceTo(end), options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) + } + } + + var start: CGPoint = .zero { + didSet { + hasUpdate = true + } + } + + var numberOfColors: Int = 0 { + didSet { + hasUpdate = true + } + } + + var colors: [CGFloat] = [] { + didSet { + hasUpdate = true + } + } + + var end: CGPoint = .zero { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var type: GradientType = .none { + didSet { + hasUpdate = true + } + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift new file mode 100644 index 0000000..7639fa8 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift @@ -0,0 +1,59 @@ +// +// GradientStrokeRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +// MARK: - Renderer + +final class GradientStrokeRenderer: PassThroughOutputNode, Renderable { + + override func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + let updates = super.hasOutputUpdates(forFrame) + return updates || strokeRender.hasUpdate || gradientRender.hasUpdate + } + + var shouldRenderInContext: Bool = true + + func updateShapeLayer(layer: CAShapeLayer) { + /// Not Applicable + } + + let strokeRender: StrokeRenderer + let gradientRender: GradientFillRenderer + + override init(parent: NodeOutput?) { + self.strokeRender = StrokeRenderer(parent: nil) + self.gradientRender = GradientFillRenderer(parent: nil) + self.strokeRender.color = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 1, 1, 1]) + super.init(parent: parent) + } + + func render(_ inContext: CGContext) { + guard inContext.path != nil && inContext.path!.isEmpty == false else { + return + } + + strokeRender.hasUpdate = false + hasUpdate = false + gradientRender.hasUpdate = false + + strokeRender.setupForStroke(inContext) + + inContext.replacePathWithStrokedPath() + + /// Now draw the gradient. + gradientRender.render(inContext) + + } + + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { + return strokeRender.renderBoundsFor(boundingBox) + } + + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift new file mode 100644 index 0000000..712fc19 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift @@ -0,0 +1,162 @@ +// +// StrokeRenderer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +extension LineJoin { + var cgLineJoin: CGLineJoin { + switch self { + case .bevel: + return .bevel + case .none: + return .miter + case .miter: + return .miter + case .round: + return .round + } + } + + var caLineJoin: CAShapeLayerLineJoin { + switch self { + case .none: + return CAShapeLayerLineJoin.miter + case .miter: + return CAShapeLayerLineJoin.miter + case .round: + return CAShapeLayerLineJoin.round + case .bevel: + return CAShapeLayerLineJoin.bevel + } + } +} + +extension LineCap { + var cgLineCap: CGLineCap { + switch self { + case .none: + return .butt + case .butt: + return .butt + case .round: + return .round + case .square: + return .square + } + } + + var caLineCap: CAShapeLayerLineCap { + switch self { + case .none: + return CAShapeLayerLineCap.butt + case .butt: + return CAShapeLayerLineCap.butt + case .round: + return CAShapeLayerLineCap.round + case .square: + return CAShapeLayerLineCap.square + } + } +} + +// MARK: - Renderer + +/// A rendered that renders a stroke on a path. +final class StrokeRenderer: PassThroughOutputNode, Renderable { + + var shouldRenderInContext: Bool = false + + var color: CGColor? { + didSet { + hasUpdate = true + } + } + + var opacity: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var width: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var miterLimit: CGFloat = 0 { + didSet { + hasUpdate = true + } + } + + var lineCap: LineCap = .none { + didSet { + hasUpdate = true + } + } + + var lineJoin: LineJoin = .none { + didSet { + hasUpdate = true + } + } + + var dashPhase: CGFloat? { + didSet { + hasUpdate = true + } + } + + var dashLengths: [CGFloat]? { + didSet { + hasUpdate = true + } + } + + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { + return boundingBox.insetBy(dx: -width, dy: -width) + } + + func setupForStroke(_ inContext: CGContext) { + inContext.setLineWidth(width) + inContext.setMiterLimit(miterLimit) + inContext.setLineCap(lineCap.cgLineCap) + inContext.setLineJoin(lineJoin.cgLineJoin) + if let dashPhase = dashPhase, let lengths = dashLengths { + inContext.setLineDash(phase: dashPhase, lengths: lengths) + } else { + inContext.setLineDash(phase: 0, lengths: []) + } + } + + func render(_ inContext: CGContext) { + guard inContext.path != nil && inContext.path!.isEmpty == false else { + return + } + guard let color = color else { return } + hasUpdate = false + setupForStroke(inContext) + inContext.setAlpha(opacity) + inContext.setStrokeColor(color) + inContext.strokePath() + } + + func updateShapeLayer(layer: CAShapeLayer) { + layer.strokeColor = color + layer.opacity = Float(opacity) + layer.lineWidth = width + layer.lineJoin = lineJoin.caLineJoin + layer.lineCap = lineCap.caLineCap + layer.lineDashPhase = dashPhase ?? 0 + layer.fillColor = nil + if let dashPattern = dashLengths { + layer.lineDashPattern = dashPattern.map({ NSNumber(value: Double($0))}) + } + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift new file mode 100644 index 0000000..bc68e44 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift @@ -0,0 +1,109 @@ +// +// EllipseNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import Foundation +import QuartzCore + +final class EllipseNodeProperties: NodePropertyMap, KeypathSearchable { + + var keypathName: String + + init(ellipse: Ellipse) { + self.keypathName = ellipse.name + self.direction = ellipse.direction + self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.position.keyframes)) + self.size = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.size.keyframes)) + self.keypathProperties = [ + "Position" : position, + "Size" : size + ] + self.properties = Array(keypathProperties.values) + } + + let direction: PathDirection + let position: NodeProperty + let size: NodeProperty + + let keypathProperties: [String : AnyNodeProperty] + let properties: [AnyNodeProperty] +} + +final class EllipseNode: AnimatorNode, PathNode { + + let pathOutput: PathOutputNode + + let properties: EllipseNodeProperties + + init(parentNode: AnimatorNode?, ellipse: Ellipse) { + self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) + self.properties = EllipseNodeProperties(ellipse: ellipse) + self.parentNode = parentNode + } + + // MARK: Animator Node + + var propertyMap: NodePropertyMap & KeypathSearchable { + return properties + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true { + didSet{ + self.pathOutput.isEnabled = self.isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + let ellipseSize = properties.size.value.sizeValue + let center = properties.position.value.pointValue + + // Unfortunately we HAVE to manually build out the ellipse. + // Every Apple method constructs an ellipse from the 3 o-clock position + // After effects constructs from the Noon position. + // After effects does clockwise, but also has a flag for reversed. + + var half = ellipseSize * 0.5 + if properties.direction == .counterClockwise { + half.width = half.width * -1 + } + + + let q1 = CGPoint(x: center.x, y: center.y - half.height) + let q2 = CGPoint(x: center.x + half.width, y: center.y) + let q3 = CGPoint(x: center.x, y: center.y + half.height) + let q4 = CGPoint(x: center.x - half.width, y: center.y) + + let cp = half * EllipseNode.ControlPointConstant + + var path = BezierPath(startPoint: CurveVertex(point: q1, + inTangentRelative: CGPoint(x: -cp.width, y: 0), + outTangentRelative: CGPoint(x: cp.width, y: 0))) + path.addVertex(CurveVertex(point: q2, + inTangentRelative: CGPoint(x: 0, y: -cp.height), + outTangentRelative: CGPoint(x: 0, y: cp.height))) + + path.addVertex(CurveVertex(point: q3, + inTangentRelative: CGPoint(x: cp.width, y: 0), + outTangentRelative: CGPoint(x: -cp.width, y: 0))) + + path.addVertex(CurveVertex(point: q4, + inTangentRelative: CGPoint(x: 0, y: cp.height), + outTangentRelative: CGPoint(x: 0, y: -cp.height))) + + path.addVertex(CurveVertex(point: q1, + inTangentRelative: CGPoint(x: -cp.width, y: 0), + outTangentRelative: CGPoint(x: cp.width, y: 0))) + path.close() + pathOutput.setPath(path, updateFrame: frame) + } + + static let ControlPointConstant: CGFloat = 0.55228 + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift new file mode 100644 index 0000000..b3663e8 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift @@ -0,0 +1,132 @@ +// +// PolygonNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/21/19. +// + +import Foundation +import QuartzCore + +final class PolygonNodeProperties: NodePropertyMap, KeypathSearchable { + + var keypathName: String + + var childKeypaths: [KeypathSearchable] = [] + + init(star: Star) { + self.keypathName = star.name + self.direction = star.direction + self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: star.position.keyframes)) + self.outerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRadius.keyframes)) + self.outerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRoundness.keyframes)) + self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes)) + self.points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes)) + self.keypathProperties = [ + "Position" : position, + "Outer Radius" : outerRadius, + "Outer Roundedness" : outerRoundedness, + "Rotation" : rotation, + "Points" : points + ] + self.properties = Array(keypathProperties.values) + } + + let keypathProperties: [String : AnyNodeProperty] + let properties: [AnyNodeProperty] + + let direction: PathDirection + let position: NodeProperty + let outerRadius: NodeProperty + let outerRoundedness: NodeProperty + let rotation: NodeProperty + let points: NodeProperty +} + +final class PolygonNode: AnimatorNode, PathNode { + + let properties: PolygonNodeProperties + + let pathOutput: PathOutputNode + + init(parentNode: AnimatorNode?, star: Star) { + self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) + self.properties = PolygonNodeProperties(star: star) + self.parentNode = parentNode + } + + // MARK: Animator Node + + var propertyMap: NodePropertyMap & KeypathSearchable { + return properties + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true { + didSet{ + self.pathOutput.isEnabled = self.isEnabled + } + } + + /// Magic number needed for constructing path. + static let PolygonConstant: CGFloat = 0.25 + + func rebuildOutputs(frame: CGFloat) { + let outerRadius = properties.outerRadius.value.cgFloatValue + let outerRoundedness = properties.outerRoundedness.value.cgFloatValue * 0.01 + let numberOfPoints = properties.points.value.cgFloatValue + let rotation = properties.rotation.value.cgFloatValue + let position = properties.position.value.pointValue + + var currentAngle = (rotation - 90).toRadians() + let anglePerPoint = ((2 * CGFloat.pi) / numberOfPoints) + + var point = CGPoint(x: (outerRadius * cos(currentAngle)), + y: (outerRadius * sin(currentAngle))) + var vertices = [CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)] + + var previousPoint = point + currentAngle += anglePerPoint; + for _ in 0.. + let size: NodeProperty + let cornerRadius: NodeProperty + +} + +final class RectangleNode: AnimatorNode, PathNode { + + let properties: RectNodeProperties + + let pathOutput: PathOutputNode + + init(parentNode: AnimatorNode?, rectangle: Rectangle) { + self.properties = RectNodeProperties(rectangle: rectangle) + self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) + self.parentNode = parentNode + } + + // MARK: Animator Node + + var propertyMap: NodePropertyMap & KeypathSearchable { + return properties + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true { + didSet{ + self.pathOutput.isEnabled = self.isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + + let size = properties.size.value.sizeValue * 0.5 + let radius = min(min(properties.cornerRadius.value.cgFloatValue, size.width) , size.height) + let position = properties.position.value.pointValue + var bezierPath = BezierPath() + let points: [CurveVertex] + + if radius <= 0 { + /// No Corners + points = [ + /// Lead In + CurveVertex(point: CGPoint(x: size.width, y: -size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 1 + CurveVertex(point: CGPoint(x: size.width, y: size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 2 + CurveVertex(point: CGPoint(x: -size.width, y: size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 3 + CurveVertex(point: CGPoint(x: -size.width, y: -size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + /// Corner 4 + CurveVertex(point: CGPoint(x: size.width, y: -size.height), + inTangentRelative: .zero, + outTangentRelative: .zero) + .translated(position), + ] + } else { + let controlPoint = radius * EllipseNode.ControlPointConstant + points = [ + /// Lead In + CurveVertex( + CGPoint(x: radius, y: 0), + CGPoint(x: radius, y: 0), + CGPoint(x: radius, y: 0)) + .translated(CGPoint(x: -radius, y: radius)) + .translated(CGPoint(x: size.width, y: -size.height)) + .translated(position), + /// Corner 1 + CurveVertex( + CGPoint(x: radius, y: 0), // In tangent + CGPoint(x: radius, y: 0), // Point + CGPoint(x: radius, y: controlPoint)) + .translated(CGPoint(x: -radius, y: -radius)) + .translated(CGPoint(x: size.width, y: size.height)) + .translated(position), + CurveVertex( + CGPoint(x: controlPoint, y: radius), // In tangent + CGPoint(x: 0, y: radius), // Point + CGPoint(x: 0, y: radius)) // Out Tangent + .translated(CGPoint(x: -radius, y: -radius)) + .translated(CGPoint(x: size.width, y: size.height)) + .translated(position), + /// Corner 2 + CurveVertex( + CGPoint(x: 0, y: radius), // In tangent + CGPoint(x: 0, y: radius), // Point + CGPoint(x: -controlPoint, y: radius))// Out tangent + .translated(CGPoint(x: radius, y: -radius)) + .translated(CGPoint(x: -size.width, y: size.height)) + .translated(position), + CurveVertex( + CGPoint(x: -radius, y: controlPoint), // In tangent + CGPoint(x: -radius, y: 0), // Point + CGPoint(x: -radius, y: 0)) // Out tangent + .translated(CGPoint(x: radius, y: -radius)) + .translated(CGPoint(x: -size.width, y: size.height)) + .translated(position), + /// Corner 3 + CurveVertex( + CGPoint(x: -radius, y: 0), // In tangent + CGPoint(x: -radius, y: 0), // Point + CGPoint(x: -radius, y: -controlPoint)) // Out tangent + .translated(CGPoint(x: radius, y: radius)) + .translated(CGPoint(x: -size.width, y: -size.height)) + .translated(position), + CurveVertex( + CGPoint(x: -controlPoint, y: -radius), // In tangent + CGPoint(x: 0, y: -radius), // Point + CGPoint(x: 0, y: -radius)) // Out tangent + .translated(CGPoint(x: radius, y: radius)) + .translated(CGPoint(x: -size.width, y: -size.height)) + .translated(position), + /// Corner 4 + CurveVertex( + CGPoint(x: 0, y: -radius), // In tangent + CGPoint(x: 0, y: -radius), // Point + CGPoint(x: controlPoint, y: -radius)) // Out tangent + .translated(CGPoint(x: -radius, y: radius)) + .translated(CGPoint(x: size.width, y: -size.height)) + .translated(position), + CurveVertex( + CGPoint(x: radius, y: -controlPoint), // In tangent + CGPoint(x: radius, y: 0), // Point + CGPoint(x: radius, y: 0)) // Out tangent + .translated(CGPoint(x: -radius, y: radius)) + .translated(CGPoint(x: size.width, y: -size.height)) + .translated(position), + ] + } + let reversed = properties.direction == .counterClockwise + let pathPoints = reversed ? points.reversed() : points + for point in pathPoints { + bezierPath.addVertex(reversed ? point.reversed() : point) + } + bezierPath.close() + pathOutput.setPath(bezierPath, updateFrame: frame) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift new file mode 100644 index 0000000..ea9383b --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift @@ -0,0 +1,61 @@ +// +// PathNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/16/19. +// + +import Foundation +import CoreGraphics + +final class ShapeNodeProperties: NodePropertyMap, KeypathSearchable { + + var keypathName: String + + init(shape: Shape) { + self.keypathName = shape.name + self.path = NodeProperty(provider: KeyframeInterpolator(keyframes: shape.path.keyframes)) + self.keypathProperties = [ + "Path" : path + ] + self.properties = Array(keypathProperties.values) + } + + let path: NodeProperty + let keypathProperties: [String : AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +final class ShapeNode: AnimatorNode, PathNode { + + let properties: ShapeNodeProperties + + let pathOutput: PathOutputNode + + init(parentNode: AnimatorNode?, shape: Shape) { + self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) + self.properties = ShapeNodeProperties(shape: shape) + self.parentNode = parentNode + } + + // MARK: Animator Node + var propertyMap: NodePropertyMap & KeypathSearchable { + return properties + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true { + didSet{ + self.pathOutput.isEnabled = self.isEnabled + } + } + + func rebuildOutputs(frame: CGFloat) { + pathOutput.setPath(properties.path.value, updateFrame: frame) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift new file mode 100644 index 0000000..c305029 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift @@ -0,0 +1,183 @@ +// +// StarNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/21/19. +// + +import Foundation +import QuartzCore + +final class StarNodeProperties: NodePropertyMap, KeypathSearchable { + + var keypathName: String + + init(star: Star) { + self.keypathName = star.name + self.direction = star.direction + self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: star.position.keyframes)) + self.outerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRadius.keyframes)) + self.outerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRoundness.keyframes)) + if let innerRadiusKeyframes = star.innerRadius?.keyframes { + self.innerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: innerRadiusKeyframes)) + } else { + self.innerRadius = NodeProperty(provider: SingleValueProvider(Vector1D(0))) + } + if let innderRoundedness = star.innerRoundness?.keyframes { + self.innerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: innderRoundedness)) + } else { + self.innerRoundedness = NodeProperty(provider: SingleValueProvider(Vector1D(0))) + } + self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes)) + self.points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes)) + self.keypathProperties = [ + "Position" : position, + "Outer Radius" : outerRadius, + "Outer Roundedness" : outerRoundedness, + "Inner Radius" : innerRadius, + "Inner Roundedness" : innerRoundedness, + "Rotation" : rotation, + "Points" : points + ] + self.properties = Array(keypathProperties.values) + } + + let keypathProperties: [String : AnyNodeProperty] + let properties: [AnyNodeProperty] + + let direction: PathDirection + let position: NodeProperty + let outerRadius: NodeProperty + let outerRoundedness: NodeProperty + let innerRadius: NodeProperty + let innerRoundedness: NodeProperty + let rotation: NodeProperty + let points: NodeProperty +} + +final class StarNode: AnimatorNode, PathNode { + + let properties: StarNodeProperties + + let pathOutput: PathOutputNode + + init(parentNode: AnimatorNode?, star: Star) { + self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) + self.properties = StarNodeProperties(star: star) + self.parentNode = parentNode + } + + // MARK: Animator Node + var propertyMap: NodePropertyMap & KeypathSearchable { + return properties + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true { + didSet{ + self.pathOutput.isEnabled = self.isEnabled + } + } + + /// Magic number needed for building path data + static let PolystarConstant: CGFloat = 0.47829 + + func rebuildOutputs(frame: CGFloat) { + let outerRadius = properties.outerRadius.value.cgFloatValue + let innerRadius = properties.innerRadius.value.cgFloatValue + let outerRoundedness = properties.outerRoundedness.value.cgFloatValue * 0.01 + let innerRoundedness = properties.innerRoundedness.value.cgFloatValue * 0.01 + let numberOfPoints = properties.points.value.cgFloatValue + let rotation = properties.rotation.value.cgFloatValue + let position = properties.position.value.pointValue + + var currentAngle = (rotation - 90).toRadians() + let anglePerPoint = (2 * CGFloat.pi) / numberOfPoints + let halfAnglePerPoint = anglePerPoint / 2.0 + let partialPointAmount = numberOfPoints - floor(numberOfPoints) + + var point: CGPoint = .zero + + var partialPointRadius: CGFloat = 0 + if partialPointAmount != 0 { + currentAngle += halfAnglePerPoint * (1 - partialPointAmount) + partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius) + point.x = (partialPointRadius * cos(currentAngle)) + point.y = (partialPointRadius * sin(currentAngle)) + currentAngle += anglePerPoint * partialPointAmount / 2 + } else { + point.x = (outerRadius * cos(currentAngle)) + point.y = (outerRadius * sin(currentAngle)) + currentAngle += halfAnglePerPoint + } + + var vertices = [CurveVertex]() + vertices.append(CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)) + + var previousPoint = point + var longSegment = false + let numPoints: Int = Int(ceil(numberOfPoints) * 2) + for i in 0.. + let position: NodeProperty + let scale: NodeProperty + let rotation: NodeProperty + let opacity: NodeProperty + let skew: NodeProperty + let skewAxis: NodeProperty + + var caTransform: CATransform3D { + return CATransform3D.makeTransform(anchor: anchor.value.pointValue, + position: position.value.pointValue, + scale: scale.value.sizeValue, + rotation: rotation.value.cgFloatValue, + skew: skew.value.cgFloatValue, + skewAxis: skewAxis.value.cgFloatValue) + } +} + +final class GroupNode: AnimatorNode { + + // MARK: Properties + let groupOutput: GroupOutputNode + + let properties: GroupNodeProperties + + let rootNode: AnimatorNode? + + var container: ShapeContainerLayer = ShapeContainerLayer() + + // MARK: Initializer + init(name: String, parentNode: AnimatorNode?, tree: NodeTree) { + self.parentNode = parentNode + self.keypathName = name + self.rootNode = tree.rootNode + self.properties = GroupNodeProperties(transform: tree.transform) + self.groupOutput = GroupOutputNode(parent: parentNode?.outputNode, rootNode: rootNode?.outputNode) + var childKeypaths: [KeypathSearchable] = tree.childrenNodes + childKeypaths.append(properties) + self.childKeypaths = childKeypaths + + for childContainer in tree.renderContainers { + container.insertRenderLayer(childContainer) + } + } + + // MARK: Keypath Searchable + + let keypathName: String + + let childKeypaths: [KeypathSearchable] + + var keypathLayer: CALayer? { + return container + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + return properties + } + + var outputNode: NodeOutput { + return groupOutput + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true { + didSet { + container.isHidden = !isEnabled + } + } + + func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool { + return rootNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false + } + + func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) { + rootNode?.updateOutputs(frame, forceOutputUpdate: forceOutputUpdate) + } + + func rebuildOutputs(frame: CGFloat) { + container.opacity = Float(properties.opacity.value.cgFloatValue) * 0.01 + container.transform = properties.caTransform + groupOutput.setTransform(container.transform, forFrame: frame) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift new file mode 100644 index 0000000..28847bf --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift @@ -0,0 +1,76 @@ +// +// FillNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import Foundation +import CoreGraphics + +final class FillNodeProperties: NodePropertyMap, KeypathSearchable { + + var keypathName: String + + init(fill: Fill) { + self.keypathName = fill.name + self.color = NodeProperty(provider: KeyframeInterpolator(keyframes: fill.color.keyframes)) + self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: fill.opacity.keyframes)) + self.type = fill.fillRule + self.keypathProperties = [ + "Opacity" : opacity, + "Color" : color + ] + self.properties = Array(keypathProperties.values) + } + + let opacity: NodeProperty + let color: NodeProperty + let type: FillRule + + let keypathProperties: [String : AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +final class FillNode: AnimatorNode, RenderNode { + + let fillRender: FillRenderer + var renderer: NodeOutput & Renderable { + return fillRender + } + + let fillProperties: FillNodeProperties + + init(parentNode: AnimatorNode?, fill: Fill) { + self.fillRender = FillRenderer(parent: parentNode?.outputNode) + self.fillProperties = FillNodeProperties(fill: fill) + self.parentNode = parentNode + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + return fillProperties + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true { + didSet { + fillRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + return false + } + + func rebuildOutputs(frame: CGFloat) { + fillRender.color = fillProperties.color.value.cgColorValue + fillRender.opacity = fillProperties.opacity.value.cgFloatValue * 0.01 + fillRender.fillRule = fillProperties.type + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift new file mode 100644 index 0000000..733f235 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift @@ -0,0 +1,90 @@ +// +// GradientFillNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation +import QuartzCore + +final class GradientFillProperties: NodePropertyMap, KeypathSearchable { + + init(gradientfill: GradientFill) { + self.keypathName = gradientfill.name + self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.opacity.keyframes)) + self.startPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.startPoint.keyframes)) + self.endPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.endPoint.keyframes)) + self.colors = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.colors.keyframes)) + self.gradientType = gradientfill.gradientType + self.numberOfColors = gradientfill.numberOfColors + self.keypathProperties = [ + "Opacity" : opacity, + "Start Point" : startPoint, + "End Point" : endPoint, + "Colors" : colors + ] + self.properties = Array(keypathProperties.values) + } + + var keypathName: String + + let opacity: NodeProperty + let startPoint: NodeProperty + let endPoint: NodeProperty + let colors: NodeProperty<[Double]> + + let gradientType: GradientType + let numberOfColors: Int + + + let keypathProperties: [String : AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +final class GradientFillNode: AnimatorNode, RenderNode { + + let fillRender: GradientFillRenderer + + var renderer: NodeOutput & Renderable { + return fillRender + } + + let fillProperties: GradientFillProperties + + init(parentNode: AnimatorNode?, gradientFill: GradientFill) { + self.fillRender = GradientFillRenderer(parent: parentNode?.outputNode) + self.fillProperties = GradientFillProperties(gradientfill: gradientFill) + self.parentNode = parentNode + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + return fillProperties + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true { + didSet { + fillRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + return false + } + + func rebuildOutputs(frame: CGFloat) { + fillRender.start = fillProperties.startPoint.value.pointValue + fillRender.end = fillProperties.endPoint.value.pointValue + fillRender.opacity = fillProperties.opacity.value.cgFloatValue * 0.01 + fillRender.colors = fillProperties.colors.value.map { CGFloat($0) } + fillRender.type = fillProperties.gradientType + fillRender.numberOfColors = fillProperties.numberOfColors + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift new file mode 100644 index 0000000..de43949 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift @@ -0,0 +1,143 @@ +// +// GradientStrokeNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/23/19. +// + +import Foundation +import CoreGraphics + +// MARK: - Properties + +final class GradientStrokeProperties: NodePropertyMap, KeypathSearchable { + + var keypathName: String + + init(gradientStroke: GradientStroke) { + self.keypathName = gradientStroke.name + self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.opacity.keyframes)) + self.startPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.startPoint.keyframes)) + self.endPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.endPoint.keyframes)) + self.colors = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.colors.keyframes)) + self.gradientType = gradientStroke.gradientType + self.numberOfColors = gradientStroke.numberOfColors + self.width = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.width.keyframes)) + self.miterLimit = CGFloat(gradientStroke.miterLimit) + self.lineCap = gradientStroke.lineCap + self.lineJoin = gradientStroke.lineJoin + + if let dashes = gradientStroke.dashPattern { + var dashPatterns = [[Keyframe]]() + var dashPhase = [Keyframe]() + for dash in dashes { + if dash.type == .offset { + dashPhase = dash.value.keyframes + } else { + dashPatterns.append(dash.value.keyframes) + } + } + self.dashPattern = NodeProperty(provider: GroupInterpolator(keyframeGroups: dashPatterns)) + self.dashPhase = NodeProperty(provider: KeyframeInterpolator(keyframes: dashPhase)) + } else { + self.dashPattern = NodeProperty(provider: SingleValueProvider([Vector1D]())) + self.dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) + } + self.keypathProperties = [ + "Opacity" : opacity, + "Start Point" : startPoint, + "End Point" : endPoint, + "Colors" : colors, + "Stroke Width" : width, + "Dashes" : dashPattern, + "Dash Phase" : dashPhase + ] + self.properties = Array(keypathProperties.values) + } + + let opacity: NodeProperty + let startPoint: NodeProperty + let endPoint: NodeProperty + let colors: NodeProperty<[Double]> + let width: NodeProperty + + let dashPattern: NodeProperty<[Vector1D]> + let dashPhase: NodeProperty + + let lineCap: LineCap + let lineJoin: LineJoin + let miterLimit: CGFloat + let gradientType: GradientType + let numberOfColors: Int + + + let keypathProperties: [String : AnyNodeProperty] + let properties: [AnyNodeProperty] + +} + +// MARK: - Node + +final class GradientStrokeNode: AnimatorNode, RenderNode { + + let strokeRender: GradientStrokeRenderer + + var renderer: NodeOutput & Renderable { + return strokeRender + } + + let strokeProperties: GradientStrokeProperties + + init(parentNode: AnimatorNode?, gradientStroke: GradientStroke) { + self.strokeRender = GradientStrokeRenderer(parent: parentNode?.outputNode) + self.strokeProperties = GradientStrokeProperties(gradientStroke: gradientStroke) + self.parentNode = parentNode + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + return strokeProperties + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true { + didSet { + strokeRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + return false + } + + func rebuildOutputs(frame: CGFloat) { + /// Update gradient properties + strokeRender.gradientRender.start = strokeProperties.startPoint.value.pointValue + strokeRender.gradientRender.end = strokeProperties.endPoint.value.pointValue + strokeRender.gradientRender.opacity = strokeProperties.opacity.value.cgFloatValue + strokeRender.gradientRender.colors = strokeProperties.colors.value.map { CGFloat($0) } + strokeRender.gradientRender.type = strokeProperties.gradientType + strokeRender.gradientRender.numberOfColors = strokeProperties.numberOfColors + + /// Now update stroke properties + strokeRender.strokeRender.opacity = strokeProperties.opacity.value.cgFloatValue + strokeRender.strokeRender.width = strokeProperties.width.value.cgFloatValue + strokeRender.strokeRender.miterLimit = strokeProperties.miterLimit + strokeRender.strokeRender.lineCap = strokeProperties.lineCap + strokeRender.strokeRender.lineJoin = strokeProperties.lineJoin + + /// Get dash lengths + let dashLengths = strokeProperties.dashPattern.value.map { $0.cgFloatValue } + if dashLengths.count > 0 { + strokeRender.strokeRender.dashPhase = strokeProperties.dashPhase.value.cgFloatValue + strokeRender.strokeRender.dashLengths = dashLengths + } else { + strokeRender.strokeRender.dashLengths = nil + strokeRender.strokeRender.dashPhase = nil + } + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift new file mode 100644 index 0000000..80a4f5c --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift @@ -0,0 +1,127 @@ +// +// StrokeNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/22/19. +// + +import Foundation +import QuartzCore +// MARK: - Properties + +final class StrokeNodeProperties: NodePropertyMap, KeypathSearchable { + + init(stroke: Stroke) { + self.keypathName = stroke.name + self.color = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.color.keyframes)) + self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.opacity.keyframes)) + self.width = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.width.keyframes)) + self.miterLimit = CGFloat(stroke.miterLimit) + self.lineCap = stroke.lineCap + self.lineJoin = stroke.lineJoin + + if let dashes = stroke.dashPattern { + var dashPatterns = [[Keyframe]]() + var dashPhase = [Keyframe]() + for dash in dashes { + if dash.type == .offset { + dashPhase = dash.value.keyframes + } else { + dashPatterns.append(dash.value.keyframes) + } + } + self.dashPattern = NodeProperty(provider: GroupInterpolator(keyframeGroups: dashPatterns)) + if dashPhase.count == 0 { + self.dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) + } else { + self.dashPhase = NodeProperty(provider: KeyframeInterpolator(keyframes: dashPhase)) + } + } else { + self.dashPattern = NodeProperty(provider: SingleValueProvider([Vector1D]())) + self.dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) + } + self.keypathProperties = [ + "Opacity" : opacity, + "Color" : color, + "Stroke Width" : width, + "Dashes" : dashPattern, + "Dash Phase" : dashPhase + ] + self.properties = Array(keypathProperties.values) + } + + let keypathName: String + let keypathProperties: [String : AnyNodeProperty] + let properties: [AnyNodeProperty] + + let opacity: NodeProperty + let color: NodeProperty + let width: NodeProperty + + let dashPattern: NodeProperty<[Vector1D]> + let dashPhase: NodeProperty + + let lineCap: LineCap + let lineJoin: LineJoin + let miterLimit: CGFloat + +} + +// MARK: - Node + +/// Node that manages stroking a path +final class StrokeNode: AnimatorNode, RenderNode { + + let strokeRender: StrokeRenderer + var renderer: NodeOutput & Renderable { + return strokeRender + } + + let strokeProperties: StrokeNodeProperties + + init(parentNode: AnimatorNode?, stroke: Stroke) { + self.strokeRender = StrokeRenderer(parent: parentNode?.outputNode) + self.strokeProperties = StrokeNodeProperties(stroke: stroke) + self.parentNode = parentNode + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + return strokeProperties + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true { + didSet { + strokeRender.isEnabled = isEnabled + } + } + + func localUpdatesPermeateDownstream() -> Bool { + return false + } + + func rebuildOutputs(frame: CGFloat) { + strokeRender.color = strokeProperties.color.value.cgColorValue + strokeRender.opacity = strokeProperties.opacity.value.cgFloatValue * 0.01 + strokeRender.width = strokeProperties.width.value.cgFloatValue + strokeRender.miterLimit = strokeProperties.miterLimit + strokeRender.lineCap = strokeProperties.lineCap + strokeRender.lineJoin = strokeProperties.lineJoin + + /// Get dash lengths + let dashLengths = strokeProperties.dashPattern.value.map { $0.cgFloatValue } + if dashLengths.count > 0 { + strokeRender.dashPhase = strokeProperties.dashPhase.value.cgFloatValue + strokeRender.dashLengths = dashLengths + } else { + strokeRender.dashLengths = nil + strokeRender.dashPhase = nil + } + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift new file mode 100644 index 0000000..600de32 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift @@ -0,0 +1,251 @@ +// +// TextAnimatorNode.swift +// lottie-ios-iOS +// +// Created by Brandon Withrow on 2/19/19. +// + +import Foundation +import CoreGraphics +import QuartzCore + +final class TextAnimatorNodeProperties: NodePropertyMap, KeypathSearchable { + + let keypathName: String + + init(textAnimator: TextAnimator) { + self.keypathName = textAnimator.name + var properties = [String : AnyNodeProperty]() + + if let keyframeGroup = textAnimator.anchor { + self.anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Anchor"] = self.anchor + } else { + self.anchor = nil + } + + if let keyframeGroup = textAnimator.position { + self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Position"] = self.position + } else { + self.position = nil + } + + if let keyframeGroup = textAnimator.scale { + self.scale = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Scale"] = self.scale + } else { + self.scale = nil + } + + if let keyframeGroup = textAnimator.skew { + self.skew = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Skew"] = self.skew + } else { + self.skew = nil + } + + if let keyframeGroup = textAnimator.skewAxis { + self.skewAxis = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Skew Axis"] = self.skewAxis + } else { + self.skewAxis = nil + } + + if let keyframeGroup = textAnimator.rotation { + self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Rotation"] = self.rotation + } else { + self.rotation = nil + } + + if let keyframeGroup = textAnimator.opacity { + self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Opacity"] = self.opacity + } else { + self.opacity = nil + } + + if let keyframeGroup = textAnimator.strokeColor { + self.strokeColor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Stroke Color"] = self.strokeColor + } else { + self.strokeColor = nil + } + + if let keyframeGroup = textAnimator.fillColor { + self.fillColor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Fill Color"] = self.fillColor + } else { + self.fillColor = nil + } + + if let keyframeGroup = textAnimator.strokeWidth { + self.strokeWidth = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Stroke Width"] = self.strokeWidth + } else { + self.strokeWidth = nil + } + + if let keyframeGroup = textAnimator.tracking { + self.tracking = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) + properties["Tracking"] = self.tracking + } else { + self.tracking = nil + } + + self.keypathProperties = properties + + self.properties = Array(keypathProperties.values) + } + + let anchor: NodeProperty? + let position: NodeProperty? + let scale: NodeProperty? + let skew: NodeProperty? + let skewAxis: NodeProperty? + let rotation: NodeProperty? + let opacity: NodeProperty? + let strokeColor: NodeProperty? + let fillColor: NodeProperty? + let strokeWidth: NodeProperty? + let tracking: NodeProperty? + + let keypathProperties: [String : AnyNodeProperty] + let properties: [AnyNodeProperty] + + var caTransform: CATransform3D { + return CATransform3D.makeTransform(anchor: anchor?.value.pointValue ?? .zero, + position: position?.value.pointValue ?? .zero, + scale: scale?.value.sizeValue ?? CGSize(width: 100, height: 100), + rotation: rotation?.value.cgFloatValue ?? 0, + skew: skew?.value.cgFloatValue, + skewAxis: skewAxis?.value.cgFloatValue) + } +} + +final class TextOutputNode: NodeOutput { + + var parent: NodeOutput? { + return parentTextNode + } + + var parentTextNode: TextOutputNode? + var isEnabled: Bool = true + + init(parent: TextOutputNode?) { + self.parentTextNode = parent + } + + fileprivate var _xform: CATransform3D? + fileprivate var _opacity: CGFloat? + fileprivate var _strokeColor: CGColor? + fileprivate var _fillColor: CGColor? + fileprivate var _tracking: CGFloat? + fileprivate var _strokeWidth: CGFloat? + + var xform: CATransform3D { + get { + return _xform ?? parentTextNode?.xform ?? CATransform3DIdentity + } + set { + _xform = newValue + } + } + + var opacity: CGFloat { + get { + return _opacity ?? parentTextNode?.opacity ?? 1 + } + set { + _opacity = newValue + } + } + + var strokeColor: CGColor? { + get { + return _strokeColor ?? parentTextNode?.strokeColor + } + set { + _strokeColor = newValue + } + } + + var fillColor: CGColor? { + get { + return _fillColor ?? parentTextNode?.fillColor + } + set { + _fillColor = newValue + } + } + + var tracking: CGFloat { + get { + return _tracking ?? parentTextNode?.tracking ?? 0 + } + set { + _tracking = newValue + } + } + + var strokeWidth: CGFloat { + get { + return _strokeWidth ?? parentTextNode?.strokeWidth ?? 0 + } + set { + _strokeWidth = newValue + } + } + + + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { + // TODO Fix This + return true + } + + var outputPath: CGPath? + +} + +class TextAnimatorNode: AnimatorNode { + + let textOutputNode: TextOutputNode + + var outputNode: NodeOutput { + return textOutputNode + } + + let textAnimatorProperties: TextAnimatorNodeProperties + + init(parentNode: TextAnimatorNode?, textAnimator: TextAnimator) { + self.textOutputNode = TextOutputNode(parent: parentNode?.textOutputNode) + self.textAnimatorProperties = TextAnimatorNodeProperties(textAnimator: textAnimator) + self.parentNode = parentNode + } + + // MARK: Animator Node Protocol + + var propertyMap: NodePropertyMap & KeypathSearchable { + return textAnimatorProperties + } + + let parentNode: AnimatorNode? + var hasLocalUpdates: Bool = false + var hasUpstreamUpdates: Bool = false + var lastUpdateFrame: CGFloat? = nil + var isEnabled: Bool = true + + func localUpdatesPermeateDownstream() -> Bool { + return true + } + + func rebuildOutputs(frame: CGFloat) { + textOutputNode.xform = textAnimatorProperties.caTransform + textOutputNode.opacity = (textAnimatorProperties.opacity?.value.cgFloatValue ?? 100) * 0.01 + textOutputNode.strokeColor = textAnimatorProperties.strokeColor?.value.cgColorValue + textOutputNode.fillColor = textAnimatorProperties.fillColor?.value.cgColorValue + textOutputNode.tracking = textAnimatorProperties.tracking?.value.cgFloatValue ?? 1 + textOutputNode.strokeWidth = textAnimatorProperties.strokeWidth?.value.cgFloatValue ?? 0 + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Protocols/AnimatorNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Protocols/AnimatorNode.swift new file mode 100644 index 0000000..47edc1c --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Protocols/AnimatorNode.swift @@ -0,0 +1,197 @@ +// +// AnimatorNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/15/19. +// + +import Foundation +import QuartzCore + +/** + Defines the basic outputs of an animator node. + + */ +protocol NodeOutput { + + /// The parent node. + var parent: NodeOutput? { get } + + /// Returns true if there are any updates upstream. OutputPath must be built before returning. + func hasOutputUpdates(_ forFrame: CGFloat) -> Bool + + var outputPath: CGPath? { get } + + var isEnabled: Bool { get set } +} + +/** + The Animator Node is the base node in the render system tree. + + It defines a single node that has an output path and option input node. + At animation time the root animation node is asked to update its contents for + the current frame. + The node reaches up its chain of nodes until the first node that does not need + updating is found. Then each node updates its contents down the render pipeline. + Each node adds its local path to its input path and passes it forward. + + An animator node holds a group of interpolators. These interpolators determine + if the node needs an update for the current frame. + + */ +protocol AnimatorNode: class, KeypathSearchable { + + /** + The available properties of the Node. + + These properties are automatically updated each frame. + These properties are also settable and gettable through the dynamic + property system. + + */ + var propertyMap: NodePropertyMap & KeypathSearchable { get } + + /// The upstream input node + var parentNode: AnimatorNode? { get } + + /// The output of the node. + var outputNode: NodeOutput { get } + + /// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true. + func rebuildOutputs(frame: CGFloat) + + /// Setters for marking current node state. + var isEnabled: Bool { get set } + var hasLocalUpdates: Bool { get set } + var hasUpstreamUpdates: Bool { get set } + var lastUpdateFrame: CGFloat? { get set } + + // MARK: Optional + + /// Marks if updates to this node affect nodes downstream. + func localUpdatesPermeateDownstream() -> Bool + func forceUpstreamOutputUpdates() -> Bool + + /// Called at the end of this nodes update cycle. Always called. Optional. + func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool + func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) + + /// The default simply returns `hasLocalUpdates` + func shouldRebuildOutputs(frame: CGFloat) -> Bool +} + +/// Basic Node Logic +extension AnimatorNode { + + func shouldRebuildOutputs(frame: CGFloat) -> Bool { + return hasLocalUpdates + } + + func localUpdatesPermeateDownstream() -> Bool { + /// Optional override + return true + } + + func forceUpstreamOutputUpdates() -> Bool { + /// Optional + return false + } + + func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool { + /// Optional + return forceLocalUpdate + } + + func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) { + /// Optional + } + + @discardableResult func updateOutputs(_ frame: CGFloat, forceOutputUpdate: Bool) -> Bool { + guard isEnabled else { + // Disabled node, pass through. + lastUpdateFrame = frame + return parentNode?.updateOutputs(frame, forceOutputUpdate: forceOutputUpdate) ?? false + } + + if forceOutputUpdate == false && lastUpdateFrame != nil && lastUpdateFrame! == frame { + /// This node has already updated for this frame. Go ahead and return the results. + return hasUpstreamUpdates || hasLocalUpdates + } + + /// Ask if this node should force output updates upstream. + let forceUpstreamUpdates = forceOutputUpdate || forceUpstreamOutputUpdates() + + /// Perform upstream output updates. Optionally mark upstream updates if any. + hasUpstreamUpdates = (parentNode?.updateOutputs(frame, forceOutputUpdate: forceUpstreamUpdates) ?? false || hasUpstreamUpdates) + + /// Perform additional local output updates + performAdditionalOutputUpdates(frame, forceOutputUpdate: forceUpstreamUpdates) + + /// If there are local updates, or if updates have been force, rebuild outputs + if forceUpstreamUpdates || shouldRebuildOutputs(frame: frame) { + lastUpdateFrame = frame + rebuildOutputs(frame: frame) + } + return hasUpstreamUpdates || hasLocalUpdates + } + + + /// Rebuilds the content of this node, and upstream nodes if necessary. + @discardableResult func updateContents(_ frame: CGFloat, forceLocalUpdate: Bool) -> Bool { + guard isEnabled else { + // Disabled node, pass through. + return parentNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false + } + + if forceLocalUpdate == false && lastUpdateFrame != nil && lastUpdateFrame! == frame { + /// This node has already updated for this frame. Go ahead and return the results. + return localUpdatesPermeateDownstream() ? hasUpstreamUpdates || hasLocalUpdates : hasUpstreamUpdates + } + + /// Are there local updates? If so mark the node. + hasLocalUpdates = forceLocalUpdate ? forceLocalUpdate : propertyMap.needsLocalUpdate(frame: frame) + + /// Were there upstream updates? If so mark the node + hasUpstreamUpdates = parentNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false + + /// Perform property updates if necessary. + if hasLocalUpdates { + /// Rebuild local properties + propertyMap.updateNodeProperties(frame: frame) + } + + /// Ask the node to perform any other updates it might have. + hasUpstreamUpdates = performAdditionalLocalUpdates(frame: frame, forceLocalUpdate: forceLocalUpdate) || hasUpstreamUpdates + + /// If the node can update nodes downstream, notify them, otherwise pass on any upstream updates downstream. + return localUpdatesPermeateDownstream() ? hasUpstreamUpdates || hasLocalUpdates : hasUpstreamUpdates + } + + func updateTree(_ frame: CGFloat, forceUpdates: Bool = false) { + updateContents(frame, forceLocalUpdate: forceUpdates) + updateOutputs(frame, forceOutputUpdate: forceUpdates) + } + +} + +extension AnimatorNode { + /// Default implementation for Keypath searchable. + /// Forward all calls to the propertyMap. + + var keypathName: String { + return propertyMap.keypathName + } + + var keypathProperties: [String : AnyNodeProperty] { + return propertyMap.keypathProperties + } + + var childKeypaths: [KeypathSearchable] { + return propertyMap.childKeypaths + } + + var keypathLayer: CALayer? { + return nil + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Protocols/PathNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Protocols/PathNode.swift new file mode 100644 index 0000000..e7b2bfb --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Protocols/PathNode.swift @@ -0,0 +1,20 @@ +// +// PathNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import Foundation + +protocol PathNode { + var pathOutput: PathOutputNode { get } +} + +extension PathNode where Self: AnimatorNode { + + var outputNode: NodeOutput { + return pathOutput + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Protocols/RenderNode.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Protocols/RenderNode.swift new file mode 100644 index 0000000..4738f14 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/Protocols/RenderNode.swift @@ -0,0 +1,57 @@ +// +// RenderNode.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/17/19. +// + +import Foundation +import CoreGraphics +import QuartzCore + +/// A protocol that defines a node that holds render instructions +protocol RenderNode { + var renderer: Renderable & NodeOutput { get } +} + +/// A protocol that defines anything with render instructions +protocol Renderable { + + /// The last frame in which this node was updated. + var hasUpdate: Bool { get } + + func hasRenderUpdates(_ forFrame: CGFloat) -> Bool + + /** + Determines if the renderer requires a custom context for drawing. + If yes the shape layer will perform a custom drawing pass. + If no the shape layer will be a standard CAShapeLayer + */ + var shouldRenderInContext: Bool { get } + + /// Passes in the CAShapeLayer to update + func updateShapeLayer(layer: CAShapeLayer) + + /// Asks the renderer what the renderable bounds is for the given box. + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect + + /// Renders the shape in a custom context + func render(_ inContext: CGContext) +} + +extension RenderNode where Self: AnimatorNode { + + var outputNode: NodeOutput { + return renderer + } + +} + +extension Renderable { + + func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { + /// Optional + return boundingBox + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift new file mode 100644 index 0000000..0c3c19b --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift @@ -0,0 +1,73 @@ +// +// ShapeContainerLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import QuartzCore + +/** + The base layer that holds Shapes and Shape Renderers + */ +class ShapeContainerLayer: CALayer { + + private(set) var renderLayers: [ShapeContainerLayer] = [] + + override init() { + super.init() + self.actions = [ + "position" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "transform" : NSNull(), + "opacity" : NSNull(), + "hidden" : NSNull(), + ] + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + guard let layer = layer as? ShapeContainerLayer else { + fatalError("init(layer:) wrong class.") + } + super.init(layer: layer) + } + + var renderScale: CGFloat = 1 { + didSet { + updateRenderScale() + } + } + + func insertRenderLayer(_ layer: ShapeContainerLayer) { + renderLayers.append(layer) + insertSublayer(layer, at: 0) + } + + func markRenderUpdates(forFrame: CGFloat) { + if self.hasRenderUpdate(forFrame: forFrame) { + self.rebuildContents(forFrame: forFrame) + } + guard self.isHidden == false else { return } + renderLayers.forEach { $0.markRenderUpdates(forFrame: forFrame) } + } + + func hasRenderUpdate(forFrame: CGFloat) -> Bool { + return false + } + + func rebuildContents(forFrame: CGFloat) { + /// Override + } + + func updateRenderScale() { + self.contentsScale = self.renderScale + renderLayers.forEach( { $0.renderScale = renderScale } ) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift new file mode 100644 index 0000000..6760dad --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift @@ -0,0 +1,95 @@ +// +// RenderLayer.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/18/19. +// + +import Foundation +import QuartzCore + +/** + The layer responsible for rendering shape objects + */ +final class ShapeRenderLayer: ShapeContainerLayer { + + fileprivate(set) var renderer: Renderable & NodeOutput + + let shapeLayer: CAShapeLayer = CAShapeLayer() + + init(renderer: Renderable & NodeOutput) { + self.renderer = renderer + super.init() + self.anchorPoint = .zero + self.actions = [ + "position" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "path" : NSNull(), + "transform" : NSNull(), + "opacity" : NSNull(), + "hidden" : NSNull(), + ] + shapeLayer.actions = [ + "position" : NSNull(), + "bounds" : NSNull(), + "anchorPoint" : NSNull(), + "path" : NSNull(), + "fillColor" : NSNull(), + "strokeColor" : NSNull(), + "lineWidth" : NSNull(), + "miterLimit" : NSNull(), + "lineDashPhase" : NSNull(), + "hidden" : NSNull(), + ] + addSublayer(shapeLayer) + } + + override init(layer: Any) { + guard let layer = layer as? ShapeRenderLayer else { + fatalError("init(layer:) wrong class.") + } + self.renderer = layer.renderer + super.init(layer: layer) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func hasRenderUpdate(forFrame: CGFloat) -> Bool { + self.isHidden = !renderer.isEnabled + guard self.isHidden == false else { return false } + return renderer.hasRenderUpdates(forFrame) + } + + override func rebuildContents(forFrame: CGFloat) { + + if renderer.shouldRenderInContext { + if let newPath = renderer.outputPath { + self.bounds = renderer.renderBoundsFor(newPath.boundingBox) + } else { + self.bounds = .zero + } + self.position = bounds.origin + self.setNeedsDisplay() + } else { + shapeLayer.path = renderer.outputPath + renderer.updateShapeLayer(layer: shapeLayer) + } + } + + override func draw(in ctx: CGContext) { + if let path = renderer.outputPath { + if !path.isEmpty { + ctx.addPath(path) + } + } + renderer.render(ctx) + } + + override func updateRenderScale() { + super.updateRenderScale() + shapeLayer.contentsScale = self.renderScale + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Debugging/AnimatorNodeDebugging.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Debugging/AnimatorNodeDebugging.swift new file mode 100644 index 0000000..3421833 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Debugging/AnimatorNodeDebugging.swift @@ -0,0 +1,25 @@ +// +// AnimatorNodeDebugging.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/18/19. +// + +import Foundation + +extension AnimatorNode { + + func printNodeTree() { + parentNode?.printNodeTree() + print(String(describing: type(of: self))) + + if let group = self as? GroupNode { + print("* |Children") + group.rootNode?.printNodeTree() + print("*") + } else { + print("|") + } + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Debugging/LayerDebugging.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Debugging/LayerDebugging.swift new file mode 100644 index 0000000..c0e20fd --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Debugging/LayerDebugging.swift @@ -0,0 +1,203 @@ +// +// LayerDebugging.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/24/19. +// + +import Foundation +import QuartzCore + +struct LayerDebugStyle { + let anchorColor: CGColor + let boundsColor: CGColor + let anchorWidth: CGFloat + let boundsWidth: CGFloat +} + +protocol LayerDebugging { + var debugStyle: LayerDebugStyle { get } +} + +protocol CustomLayerDebugging { + func layerForDebugging() -> CALayer +} + +class DebugLayer: CALayer { + init(style: LayerDebugStyle) { + super.init() + zPosition = 1000 + bounds = CGRect(x: 0, y: 0, width: style.anchorWidth, height: style.anchorWidth) + backgroundColor = style.anchorColor + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public extension CALayer { + + func logLayerTree(withIndent: Int = 0) { + var string = "" + for _ in 0...withIndent { + string = string + " " + } + string = string + "|_" + String(describing: self) + print(string) + if let sublayers = sublayers { + for sublayer in sublayers { + sublayer.logLayerTree(withIndent: withIndent + 1) + } + } + } + +} + +extension CompositionLayer: CustomLayerDebugging { + func layerForDebugging() -> CALayer { + return contentsLayer + } +} + +extension CALayer { + + func setDebuggingState(visible: Bool) { + + var sublayers = self.sublayers + if let cust = self as? CustomLayerDebugging { + sublayers = cust.layerForDebugging().sublayers + } + + if let sublayers = sublayers { + for i in 0.. LayerDebugStyle { + let colorSpace = CGColorSpaceCreateDeviceRGB() + + let anchorColor = CGColor(colorSpace: colorSpace, components: [1, 0, 0, 1])! + let boundsColor = CGColor(colorSpace: colorSpace, components: [1, 1, 0, 1])! + return LayerDebugStyle(anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func topLayerStyle() -> LayerDebugStyle { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let anchorColor = CGColor(colorSpace: colorSpace, components: [1, 0.5, 0, 0])! + let boundsColor = CGColor(colorSpace: colorSpace, components: [0, 1, 0, 1])! + + return LayerDebugStyle(anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func nullLayerStyle() -> LayerDebugStyle { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let anchorColor = CGColor(colorSpace: colorSpace, components: [0, 0, 1, 0])! + let boundsColor = CGColor(colorSpace: colorSpace, components: [0, 1, 0, 1])! + + return LayerDebugStyle(anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func shapeLayerStyle() -> LayerDebugStyle { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let anchorColor = CGColor(colorSpace: colorSpace, components: [0, 1, 0, 0])! + let boundsColor = CGColor(colorSpace: colorSpace, components: [0, 1, 0, 1])! + + return LayerDebugStyle(anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } + + static func shapeRenderLayerStyle() -> LayerDebugStyle { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let anchorColor = CGColor(colorSpace: colorSpace, components: [0, 1, 1, 0])! + let boundsColor = CGColor(colorSpace: colorSpace, components: [0, 1, 0, 1])! + + return LayerDebugStyle(anchorColor: anchorColor, + boundsColor: boundsColor, + anchorWidth: 10, + boundsWidth: 2) + } +} + +extension Array where Element == LayerModel { + + var parents: [Int] { + var array = [Int]() + for layer in self { + if let parent = layer.parent { + array.append(parent) + } else { + array.append(-1) + } + } + return array + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/AnimationKeypathExtension.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/AnimationKeypathExtension.swift new file mode 100644 index 0000000..8b8e811 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/AnimationKeypathExtension.swift @@ -0,0 +1,256 @@ +// +// KeypathSearchableExtension.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +import QuartzCore + +extension KeypathSearchable { + + func animatorNodes(for keyPath: AnimationKeypath) -> [AnimatorNode]? { + // Make sure there is a current key path. + guard let currentKey = keyPath.currentKey else { return nil } + + // Now try popping the keypath for wildcard / child search + guard let nextKeypath = keyPath.popKey(keypathName) else { + // We may be on the final keypath. Check for match. + if let node = self as? AnimatorNode, + currentKey.equalsKeypath(keypathName) { + // This is the final keypath and matches self. Return.s + return [node] + } + /// Nope. Stop Search + return nil + } + + var results: [AnimatorNode] = [] + + if let node = self as? AnimatorNode, + nextKeypath.currentKey == nil { + // Keypath matched self and was the final keypath. + results.append(node) + } + + for childNode in childKeypaths { + // Check if the child has any nodes matching the next keypath. + if let foundNodes = childNode.animatorNodes(for: nextKeypath) { + results.append(contentsOf: foundNodes) + } + + // In this case the current key is fuzzy, and both child and self match the next keyname. Keep digging! + if currentKey.keyPathType == .fuzzyWildcard, + let nextKeypath = keyPath.nextKeypath, + nextKeypath.equalsKeypath(childNode.keypathName), + let foundNodes = childNode.animatorNodes(for: keyPath) { + results.append(contentsOf: foundNodes) + } + } + + guard results.count > 0 else { + return nil + } + + return results + } + + func nodeProperties(for keyPath: AnimationKeypath) -> [AnyNodeProperty]? { + guard let nextKeypath = keyPath.popKey(keypathName) else { + /// Nope. Stop Search + return nil + } + + /// Keypath matches in some way. Continue the search. + var results: [AnyNodeProperty] = [] + + /// Check if we have a property keypath yet + if let propertyKey = nextKeypath.propertyKey, + let property = keypathProperties[propertyKey] { + /// We found a property! + results.append(property) + } + + if nextKeypath.nextKeypath != nil { + /// Now check child keypaths. + for child in childKeypaths { + if let childProperties = child.nodeProperties(for: nextKeypath) { + results.append(contentsOf: childProperties) + } + } + } + + guard results.count > 0 else { + return nil + } + + return results + } + + func layer(for keyPath: AnimationKeypath) -> CALayer? { + if keyPath.nextKeypath == nil, let layerKey = keyPath.currentKey, layerKey.equalsKeypath(keypathName) { + /// We found our layer! + return keypathLayer + } + guard let nextKeypath = keyPath.popKey(keypathName) else { + /// Nope. Stop Search + return nil + } + + if nextKeypath.nextKeypath != nil { + /// Now check child keypaths. + for child in childKeypaths { + if let layer = child.layer(for: keyPath) { + return layer + } + } + } + return nil + } + + func logKeypaths(for keyPath: AnimationKeypath?) { + let newKeypath: AnimationKeypath + if let previousKeypath = keyPath { + newKeypath = previousKeypath.appendingKey(keypathName) + } else { + newKeypath = AnimationKeypath(keys: [keypathName]) + } + print(newKeypath.fullPath) + for key in keypathProperties.keys { + print(newKeypath.appendingKey(key).fullPath) + } + for child in childKeypaths { + child.logKeypaths(for: newKeypath) + } + } +} + +extension AnimationKeypath { + var currentKey: String? { + return keys.first + } + + var nextKeypath: String? { + guard keys.count > 1 else { + return nil + } + return keys[1] + } + + var propertyKey: String? { + if nextKeypath == nil { + /// There are no more keypaths. This is a property key. + return currentKey + } + if keys.count == 2, currentKey?.keyPathType == .fuzzyWildcard { + /// The next keypath is the last and the current is a fuzzy key. + return nextKeypath + } + return nil + } + + // Pops the top keypath from the stack if the keyname matches. + func popKey(_ keyname: String) -> AnimationKeypath? { + guard let currentKey = currentKey, + currentKey.equalsKeypath(keyname), + keys.count > 1 else { + // Current key either doesnt match or we are on the last key. + return nil + } + + // Pop the keypath from the stack and return the new stack. + let newKeys: [String] + + if currentKey.keyPathType == .fuzzyWildcard { + /// Dont remove if current key is a fuzzy wildcard, and if the next keypath doesnt equal keypathname + if let nextKeypath = nextKeypath, + nextKeypath.equalsKeypath(keyname) { + /// Remove next two keypaths. This keypath breaks the wildcard. + var oldKeys = keys + oldKeys.remove(at: 0) + oldKeys.remove(at: 0) + newKeys = oldKeys + } else { + newKeys = keys + } + } else { + var oldKeys = keys + oldKeys.remove(at: 0) + newKeys = oldKeys + } + + return AnimationKeypath(keys: newKeys) + } + + var fullPath: String { + return keys.joined(separator: ".") + } + + func appendingKey(_ key: String) -> AnimationKeypath { + var newKeys = keys + newKeys.append(key) + return AnimationKeypath(keys: newKeys) + } +} + + + +extension String { + var keyPathType: KeyType { + switch self { + case "*": + return .wildcard + case "**": + return .fuzzyWildcard + default: + return .specific + } + } + + func equalsKeypath(_ keyname: String) -> Bool { + if keyPathType == .wildcard || keyPathType == .fuzzyWildcard { + return true + } + if self == keyname { + return true + } + if let index = self.firstIndex(of: "*") { + // Wildcard search. + let prefix = self.prefix(upTo: index) + let suffix = self.suffix(from: self.index(after: index)) + + if prefix.count > 0 { + // Match prefix. + if keyname.count < prefix.count { + return false + } + let testPrefix = keyname.prefix(upTo: keyname.index(keyname.startIndex, offsetBy: prefix.count)) + if testPrefix != prefix { + // Prefix doesnt match + return false + } + } + if suffix.count > 0 { + // Match suffix. + if keyname.count < suffix.count { + // Suffix doesnt match + return false + } + let index = keyname.index(keyname.endIndex, offsetBy: -suffix.count) + let testSuffix = keyname.suffix(from: index) + if testSuffix != suffix { + return false + } + } + return true + } + return false + } +} + +enum KeyType { + case specific + case wildcard + case fuzzyWildcard +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/CGFloatExtensions.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/CGFloatExtensions.swift new file mode 100644 index 0000000..b7c0d9d --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/CGFloatExtensions.swift @@ -0,0 +1,149 @@ +// +// CGFloatExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import Foundation +import QuartzCore + +extension CGFloat { + + func isInRangeOrEqual(_ from: CGFloat, _ to: CGFloat) -> Bool { + return (from <= self && self <= to) + } + + func isInRange(_ from: CGFloat, _ to: CGFloat) -> Bool { + return (from < self && self < to) + } + + var squared: CGFloat { + return self * self + } + + var cubed: CGFloat { + return self * self * self + } + + var cubicRoot: CGFloat { + return CGFloat(pow(Double(self), 1.0 / 3.0)) + } + + fileprivate static func SolveQuadratic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat) -> CGFloat { + var result = (-b + sqrt(b.squared - 4 * a * c)) / (2 * a); + guard !result.isInRangeOrEqual(0, 1) else { + return result + } + + result = (-b - sqrt(b.squared - 4 * a * c)) / (2 * a); + guard !result.isInRangeOrEqual(0, 1) else { + return result + } + + return -1; + } + + fileprivate static func SolveCubic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat, _ d: CGFloat) -> CGFloat { + if (a == 0) { + return SolveQuadratic(b, c, d) + } + if (d == 0) { + return 0 + } + let a = a + var b = b + var c = c + var d = d + b /= a + c /= a + d /= a + var q = (3.0 * c - b.squared) / 9.0 + let r = (-27.0 * d + b * (9.0 * c - 2.0 * b.squared)) / 54.0 + let disc = q.cubed + r.squared + let term1 = b / 3.0 + + if (disc > 0) { + var s = r + sqrt(disc) + s = (s < 0) ? -((-s).cubicRoot) : s.cubicRoot + var t = r - sqrt(disc) + t = (t < 0) ? -((-t).cubicRoot) : t.cubicRoot + + let result = -term1 + s + t; + if result.isInRangeOrEqual(0, 1) { + return result + } + } else if (disc == 0) { + let r13 = (r < 0) ? -((-r).cubicRoot) : r.cubicRoot; + + var result = -term1 + 2.0 * r13; + if result.isInRangeOrEqual(0, 1) { + return result + } + + result = -(r13 + term1); + if result.isInRangeOrEqual(0, 1) { + return result + } + + } else { + q = -q; + var dum1 = q * q * q; + dum1 = acos(r / sqrt(dum1)); + let r13 = 2.0 * sqrt(q); + + var result = -term1 + r13 * cos(dum1 / 3.0); + if result.isInRangeOrEqual(0, 1) { + return result + } + result = -term1 + r13 * cos((dum1 + 2.0 * .pi) / 3.0); + if result.isInRangeOrEqual(0, 1) { + return result + } + result = -term1 + r13 * cos((dum1 + 4.0 * .pi) / 3.0); + if result.isInRangeOrEqual(0, 1) { + return result + } + } + + return -1; + } + + func cubicBezierInterpolate(_ P0: CGPoint, _ P1: CGPoint, _ P2: CGPoint, _ P3: CGPoint) -> CGFloat { + var t: CGFloat + if (self == P0.x) { + // Handle corner cases explicitly to prevent rounding errors + t = 0 + } else if (self == P3.x) { + t = 1 + } else { + // Calculate t + let a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x; + let b = 3 * P0.x - 6 * P1.x + 3 * P2.x; + let c = -3 * P0.x + 3 * P1.x; + let d = P0.x - self; + let tTemp = CGFloat.SolveCubic(a, b, c, d); + if (tTemp == -1) { + return -1; + } + t = tTemp + } + + // Calculate y from t + return (1 - t).cubed * P0.y + 3 * t * (1 - t).squared * P1.y + 3 * t.squared * (1 - t) * P2.y + t.cubed * P3.y; + } + + func cubicBezier(_ t: CGFloat, _ c1: CGFloat, _ c2: CGFloat, _ end: CGFloat) -> CGFloat { + let t_ = (1.0 - t) + let tt_ = t_ * t_ + let ttt_ = t_ * t_ * t_ + let tt = t * t + let ttt = t * t * t + + return self * ttt_ + + 3.0 * c1 * tt_ * t + + 3.0 * c2 * t_ * tt + + end * ttt; + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/MathKit.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/MathKit.swift new file mode 100644 index 0000000..7a2ad29 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/MathKit.swift @@ -0,0 +1,539 @@ +// +// MathKit.swift +// UIToolBox +// +// Created by Brandon Withrow on 10/10/18. +// +// From https://github.com/buba447/UIToolBox + +import Foundation +import CoreGraphics + +extension Int { + var cgFloat: CGFloat { + return CGFloat(self) + } +} + +extension Double { + var cgFloat: CGFloat { + return CGFloat(self) + } +} + +extension CGFloat: Interpolatable { + + + /** + Interpolates the receiver to the given number by Amount. + - Parameter toNumber: The number to interpolate to. + - Parameter amount: The amount to interpolate from 0-1 + + ``` + let number = 5 + let interpolated = number.interpolateTo(10, amount: 0.5) + print(interpolated) + // Result: 7.5 + ``` + + 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. + */ + func interpolateTo(_ to: CGFloat, amount: CGFloat) -> CGFloat { + return self + ((to - self) * CGFloat(amount)) + } + + func interpolateTo(_ to: CGFloat, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> CGFloat { + return interpolateTo(to, amount: amount) + } + + func remap(fromLow: CGFloat, fromHigh: CGFloat, toLow: CGFloat, toHigh: CGFloat) -> CGFloat { + guard (fromHigh - fromLow) != 0 else { + // Would produce NAN + return 0 + } + return toLow + (self - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + } + + /** + Returns a value that is clamped between the two numbers + + 1. The order of arguments does not matter. + */ + func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat { + return CGFloat(Double(self).clamp(Double(a), Double(b))) + } + + /** + Returns the difference between the receiver and the given number. + - Parameter absolute: If *true* (Default) the returned value will always be positive. + */ + func diff(_ a: CGFloat, absolute: Bool = true) -> CGFloat { + return absolute ? abs(a - self) : a - self + } + + func toRadians() -> CGFloat { return self * .pi / 180 } + func toDegrees() -> CGFloat { return self * 180 / .pi } + +} + +extension Double: Interpolatable { + + /** + Interpolates the receiver to the given number by Amount. + - Parameter toNumber: The number to interpolate to. + - Parameter amount: The amount to interpolate from 0-1 + + ``` + let number = 5 + let interpolated = number.interpolateTo(10, amount: 0.5) + print(interpolated) + // Result: 7.5 + ``` + + 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. + */ + func interpolateTo(_ to: Double, amount: CGFloat) -> Double { + return self + ((to - self) * Double(amount)) + } + + func interpolateTo(_ to: Double, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Double { + return interpolateTo(to, amount: amount) + } + + func remap(fromLow: Double, fromHigh: Double, toLow: Double, toHigh: Double) -> Double { + return toLow + (self - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) + } + + /** + Returns a value that is clamped between the two numbers + + 1. The order of arguments does not matter. + */ + func clamp(_ a: Double, _ b: Double) -> Double { + let minValue = a <= b ? a : b + let maxValue = a <= b ? b : a + return max(min(self, maxValue), minValue) + } + +} + +extension CGRect { + + /// Initializes a new CGRect with a center point and size. + init(center: CGPoint, size: CGSize) { + self.init(x: center.x - (size.width * 0.5), + y: center.y - (size.height * 0.5), + width: size.width, + height: size.height) + } + + /// Returns the total area of the rect. + var area: CGFloat { + return width * height + } + + + /// The center point of the rect. Settable. + var center: CGPoint { + get { + return CGPoint(x: midX, y: midY) + } + set { + origin = CGPoint(x: newValue.x - (size.width * 0.5), + y: newValue.y - (size.height * 0.5)) + } + } + + /// The top left point of the rect. Settable. + var topLeft: CGPoint { + get { + return CGPoint(x: minX, y: minY) + } + set { + origin = CGPoint(x: newValue.x, + y: newValue.y) + } + } + + /// The bottom left point of the rect. Settable. + var bottomLeft: CGPoint { + get { + return CGPoint(x: minX, y: maxY) + } + set { + origin = CGPoint(x: newValue.x, + y: newValue.y - size.height) + } + } + + /// The top right point of the rect. Settable. + var topRight: CGPoint { + get { + return CGPoint(x: maxX, y: minY) + } + set { + origin = CGPoint(x: newValue.x - size.width, + y: newValue.y) + } + } + + /// The bottom right point of the rect. Settable. + var bottomRight: CGPoint { + get { + return CGPoint(x: maxX, y: maxY) + } + set { + origin = CGPoint(x: newValue.x - size.width, + y: newValue.y - size.height) + } + } + + /** + Interpolates the receiver to the given rect by Amount. + - Parameter to: The rect to interpolate to. + - Parameter amount: The amount to interpolate from 0-1 + + ``` + let rect = CGRect(x:0, y:0, width: 50, height: 50) + let interpolated = rect.interpolateTo(CGRect(x:100, y:100, width: 100, height: 100), amount: 0.5) + print(interpolated) + // Result: (x: 50, y: 50, width: 75, height: 75) + ``` + + 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. + */ + func interpolateTo(_ to: CGRect, amount: CGFloat) -> CGRect { + return CGRect(x: origin.x.interpolateTo(to.origin.x, amount: amount), + y: origin.y.interpolateTo(to.origin.y, amount: amount), + width: width.interpolateTo(to.width, amount: amount), + height: height.interpolateTo(to.height, amount: amount)) + } + +} + +extension CGSize { + + /** + Interpolates the receiver to the given size by Amount. + - Parameter to: The size to interpolate to. + - Parameter amount: The amount to interpolate from 0-1 + + ``` + let size = CGSize(width: 50, height: 50) + let interpolated = rect.interpolateTo(CGSize(width: 100, height: 100), amount: 0.5) + print(interpolated) + // Result: (width: 75, height: 75) + ``` + + 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. + */ + func interpolateTo(_ to: CGSize, amount: CGFloat) -> CGSize { + return CGSize(width: width.interpolateTo(to.width, amount: amount), + height: height.interpolateTo(to.height, amount: amount)) + } + + /// Returns the scale float that will fit the receive inside of the given size. + func scaleThatFits(_ size: CGSize) -> CGFloat { + return CGFloat.minimum(width / size.width, height / size.height) + } + + /// Adds receiver size to give size. + func add(_ size: CGSize) -> CGSize { + return CGSize(width: width + size.width, height: height + size.height) + } + + /// Subtracts given size from receiver size. + func subtract(_ size: CGSize) -> CGSize { + return CGSize(width: width - size.width, height: height - size.height) + } + + /// Multiplies receiver size by the given size. + func multiply(_ size: CGSize) -> CGSize { + return CGSize(width: width * size.width, height: height * size.height) + } + + /// Operator convenience to add sizes with + + static func +(left: CGSize, right: CGSize) -> CGSize { + return left.add(right) + } + + /// Operator convenience to subtract sizes with - + static func -(left: CGSize, right: CGSize) -> CGSize { + return left.subtract(right) + } + + /// Operator convenience to multiply sizes with * + static func *(left: CGSize, right: CGFloat) -> CGSize { + return CGSize(width: left.width * right, height: left.height * right) + } + +} + +/// A struct that defines a line segment with two CGPoints +struct CGLine { + + /// The Start of the line segment. + var start: CGPoint + /// The End of the line segment. + var end: CGPoint + + /// Initializes a line segment with start and end points + init(start: CGPoint, end: CGPoint) { + self.start = start + self.end = end + } + + /// The length of the line segment. + var length: CGFloat { + return end.distanceTo(start) + } + + /// Returns a line segment that is normalized to a length of 1 + func normalize() -> CGLine { + let len = length + guard len > 0 else { + return self + } + let relativeEnd = end - start + let relativeVector = CGPoint(x: relativeEnd.x / len, y: relativeEnd.y / len) + let absoluteVector = relativeVector + start + return CGLine(start: start, end: absoluteVector) + } + + /// Trims a line segment to the given length + func trimmedToLength(_ toLength: CGFloat) -> CGLine { + let len = length + guard len > 0 else { + return self + } + let relativeEnd = end - start + let relativeVector = CGPoint(x: relativeEnd.x / len, y: relativeEnd.y / len) + let sizedVector = CGPoint(x: relativeVector.x * toLength, y: relativeVector.y * toLength) + let absoluteVector = sizedVector + start + return CGLine(start: start, end: absoluteVector) + } + + /// Flips a line vertically and horizontally from the start point. + func flipped() -> CGLine { + let relativeEnd = end - start + let flippedEnd = CGPoint(x: relativeEnd.x * -1, y: relativeEnd.y * -1) + return CGLine(start: start, end: flippedEnd + start) + } + + /// Move the line to the new start point. + func transpose(_ toPoint: CGPoint) -> CGLine { + let diff = toPoint - start + let newEnd = end + diff + return CGLine(start: toPoint, end: newEnd) + } + +} + +infix operator +| +infix operator +- + +extension CGPoint: Interpolatable { + + /// Returns the distance between the receiver and the given point. + func distanceTo(_ a: CGPoint) -> CGFloat { + let xDist = a.x - x + let yDist = a.y - y + return CGFloat(sqrt((xDist * xDist) + (yDist * yDist))) + } + + /// Returns the length between the receiver and *CGPoint.zero* + var vectorLength: CGFloat { + return distanceTo(.zero) + } + + func rounded(decimal: CGFloat) -> CGPoint { + return CGPoint(x: (round(decimal * x) / decimal), y: (round(decimal * y) / decimal)) + } + + /** + Interpolates the receiver to the given Point by Amount. + - Parameter to: The Point to interpolate to. + - Parameter amount: The amount to interpolate from 0-1 + + ``` + let point = CGPoint(width: 50, height: 50) + let interpolated = rect.interpolateTo(CGPoint(width: 100, height: 100), amount: 0.5) + print(interpolated) + // Result: (x: 75, y: 75) + ``` + + 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. + */ + + func interpolate(_ to: CGPoint, amount: CGFloat) -> CGPoint { + return CGPoint(x: x.interpolateTo(to.x, amount: amount), + y: y.interpolateTo(to.y, amount: amount)) + } + + func interpolate(_ to: CGPoint, outTangent: CGPoint, inTangent: CGPoint, amount: CGFloat, maxIterations: Int = 3, samples: Int = 20, accuracy: CGFloat = 1) -> CGPoint { + if amount == 0 { + return self + } + if amount == 1 { + return to + } + + if self.colinear(outTangent, inTangent) == true, + outTangent.colinear(inTangent, to) == true { + return interpolate(to, amount: amount) + } + + let step = 1 / CGFloat(samples) + + var points: [(point: CGPoint, distance: CGFloat)] = [(point: self, distance: 0)] + var totalLength: CGFloat = 0 + + var previousPoint = self + var previousAmount = CGFloat(0) + + var closestPoint: Int = 0 + + while previousAmount < 1 { + + previousAmount = previousAmount + step + + if previousAmount < amount { + closestPoint = closestPoint + 1 + } + + let newPoint = self.pointOnPath(to, outTangent: outTangent, inTangent: inTangent, amount: previousAmount) + let distance = previousPoint.distanceTo(newPoint) + totalLength = totalLength + distance + points.append((point: newPoint, distance: totalLength)) + previousPoint = newPoint + } + + let accurateDistance = amount * totalLength + var point = points[closestPoint] + + var foundPoint: Bool = false + + var pointAmount: CGFloat = CGFloat(closestPoint) * step + var nextPointAmount: CGFloat = pointAmount + step + + var refineIterations = 0 + while foundPoint == false { + refineIterations = refineIterations + 1 + /// First see if the next point is still less than the projected length. + let nextPoint = points[closestPoint + 1] + if nextPoint.distance < accurateDistance { + point = nextPoint + closestPoint = closestPoint + 1 + pointAmount = CGFloat(closestPoint) * step + nextPointAmount = pointAmount + step + if closestPoint == points.count { + foundPoint = true + } + continue + } + if accurateDistance < point.distance { + closestPoint = closestPoint - 1 + if closestPoint < 0 { + foundPoint = true + continue + } + point = points[closestPoint] + pointAmount = CGFloat(closestPoint) * step + nextPointAmount = pointAmount + step + continue + } + + /// Now we are certain the point is the closest point under the distance + let pointDiff = nextPoint.distance - point.distance + let proposedPointAmount = ((accurateDistance - point.distance) / pointDiff).remap(fromLow: 0, fromHigh: 1, toLow: pointAmount, toHigh: nextPointAmount) + + let newPoint = self.pointOnPath(to, outTangent: outTangent, inTangent: inTangent, amount: proposedPointAmount) + let newDistance = point.distance + point.point.distanceTo(newPoint) + pointAmount = proposedPointAmount + point = (point: newPoint, distance: newDistance) + if accurateDistance - newDistance <= accuracy || + newDistance - accurateDistance <= accuracy { + foundPoint = true + } + + if refineIterations == maxIterations { + foundPoint = true + } + } + return point.point + } + + func pointOnPath(_ to: CGPoint, outTangent: CGPoint, inTangent: CGPoint, amount: CGFloat) -> CGPoint { + let a = self.interpolate(outTangent, amount: amount) + let b = outTangent.interpolate(inTangent, amount: amount) + let c = inTangent.interpolate(to, amount: amount) + let d = a.interpolate(b, amount: amount) + let e = b.interpolate(c, amount: amount) + let f = d.interpolate(e, amount: amount) + return f + } + + func colinear(_ a: CGPoint, _ b: CGPoint) -> Bool { + let area = x * (a.y - b.y) + a.x * (b.y - y) + b.x * (y - a.y); + let accuracy: CGFloat = 0.05 + if area < accuracy && area > -accuracy { + return true + } + return false + } + + func interpolateTo(_ to: CGPoint, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> CGPoint { + guard let outTan = spatialOutTangent, + let inTan = spatialInTangent else { + return interpolate(to, amount: amount) + } + let cp1 = self + outTan + let cp2 = to + inTan + + return interpolate(to, outTangent: cp1, inTangent: cp2, amount: amount) + } + + /// Subtracts the given point from the receiving point. + func subtract(_ point: CGPoint) -> CGPoint { + return CGPoint(x: x - point.x, + y: y - point.y) + } + + /// Adds the given point from the receiving point. + func add(_ point: CGPoint) -> CGPoint { + return CGPoint(x: x + point.x, + y: y + point.y) + } + + var isZero: Bool { + return (x == 0 && y == 0) + } + + /// Operator convenience to divide points with / + static func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint { + return CGPoint(x: lhs.x / CGFloat(rhs), y: lhs.y / CGFloat(rhs)) + } + + /// Operator convenience to multiply points with * + static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint { + return CGPoint(x: lhs.x * CGFloat(rhs), y: lhs.y * CGFloat(rhs)) + } + + /// Operator convenience to add points with + + static func +(left: CGPoint, right: CGPoint) -> CGPoint { + return left.add(right) + } + + /// Operator convenience to subtract points with - + static func -(left: CGPoint, right: CGPoint) -> CGPoint { + return left.subtract(right) + } + + static func +|(left: CGPoint, right: CGFloat) -> CGPoint { + return CGPoint(x: left.x, y: left.y + right) + } + + static func +-(left: CGPoint, right: CGFloat) -> CGPoint { + return CGPoint(x: left.x + right, y: left.y) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/StringExtensions.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/StringExtensions.swift new file mode 100644 index 0000000..7ff0122 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Extensions/StringExtensions.swift @@ -0,0 +1,32 @@ +// +// StringExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import CoreGraphics + +extension String { + + func hexColorComponents() -> (red: CGFloat, green: CGFloat, blue: CGFloat) { + + var cString:String = trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + if (cString.hasPrefix("#")) { + cString.remove(at: cString.startIndex) + } + + if ((cString.count) != 6) { + return (red: 0, green: 0, blue: 0) + } + + var rgbValue:UInt32 = 0 + Scanner(string: cString).scanHexInt32(&rgbValue) + + return (red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, + green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(rgbValue & 0x0000FF) / 255.0) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Helpers/AnimationContext.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Helpers/AnimationContext.swift new file mode 100644 index 0000000..41ab6ab --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Helpers/AnimationContext.swift @@ -0,0 +1,65 @@ +// +// AnimationContext.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/1/19. +// + +import Foundation +import CoreGraphics +import QuartzCore + +/// A completion block for animations. `true` is passed in if the animation completed playing. +public typealias LottieCompletionBlock = (Bool) -> Void + +struct AnimationContext { + + init(playFrom: CGFloat, + playTo: CGFloat, + closure: LottieCompletionBlock?) { + self.playTo = playTo + self.playFrom = playFrom + self.closure = AnimationCompletionDelegate(completionBlock: closure) + } + + var playFrom: CGFloat + var playTo: CGFloat + var closure: AnimationCompletionDelegate + +} + +enum AnimationContextState { + case playing + case cancelled + case complete +} + +class AnimationCompletionDelegate: NSObject, CAAnimationDelegate { + + init(completionBlock: LottieCompletionBlock?) { + self.completionBlock = completionBlock + super.init() + } + + var animationLayer: AnimationContainer? + var animationKey: String? + var ignoreDelegate: Bool = false + var animationState: AnimationContextState = .playing + + let completionBlock: LottieCompletionBlock? + + public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { + guard ignoreDelegate == false else { return } + animationState = flag ? .complete : .cancelled + if let animationLayer = animationLayer, let key = animationKey { + animationLayer.removeAnimation(forKey: key) + if flag { + animationLayer.currentFrame = (anim as! CABasicAnimation).toValue as! CGFloat + } + } + if let completionBlock = completionBlock { + completionBlock(flag) + } + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Interpolatable/Interpolatable.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Interpolatable/Interpolatable.swift new file mode 100644 index 0000000..061e03c --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Interpolatable/Interpolatable.swift @@ -0,0 +1,18 @@ +// +// Interpolatable.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import Foundation +import CoreGraphics + +protocol Interpolatable { + + func interpolateTo(_ to: Self, + amount: CGFloat, + spatialOutTangent: CGPoint?, + spatialInTangent: CGPoint?) -> Self + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Interpolatable/InterpolatableExtensions.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Interpolatable/InterpolatableExtensions.swift new file mode 100644 index 0000000..fd5d6e5 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Interpolatable/InterpolatableExtensions.swift @@ -0,0 +1,170 @@ +// +// InterpolatableExtensions.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import Foundation +import CoreGraphics + +extension Vector1D: Interpolatable { + func interpolateTo(_ to: Vector1D, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Vector1D { + return value.interpolateTo(to.value, amount: amount).vectorValue + } +} + +extension Vector2D: Interpolatable { + func interpolateTo(_ to: Vector2D, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Vector2D { + return pointValue.interpolateTo(to.pointValue, amount: CGFloat(amount), spatialOutTangent: spatialOutTangent, spatialInTangent: spatialInTangent).vector2dValue + } + +} + +extension Vector3D: Interpolatable { + func interpolateTo(_ to: Vector3D, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Vector3D { + if spatialInTangent != nil || spatialOutTangent != nil { + // TODO Support third dimension spatial interpolation + let point = pointValue.interpolateTo(to.pointValue, amount: amount, spatialOutTangent: spatialOutTangent, spatialInTangent: spatialInTangent) + return Vector3D(x: point.x, + y: point.y, + z: CGFloat(z.interpolateTo(to.z, amount: amount))) + } + return Vector3D(x: x.interpolateTo(to.x, amount: amount), + y: y.interpolateTo(to.y, amount: amount), + z: z.interpolateTo(to.z, amount: amount)) + } +} + +extension Color: Interpolatable { + + /// Initialize a new color with Hue Saturation and Value + init(h: Double, s: Double, v: Double, a: Double) { + + let i = floor(h * 6) + let f = h * 6 - i + let p = v * (1 - s); + let q = v * (1 - f * s) + let t = v * (1 - (1 - f) * s) + + switch (i.truncatingRemainder(dividingBy: 6)) { + case 0: + self.r = v + self.g = t + self.b = p + case 1: + self.r = q + self.g = v + self.b = p + case 2: + self.r = p + self.g = v + self.b = t + case 3: + self.r = p + self.g = q + self.b = v + case 4: + self.r = t + self.g = p + self.b = v + case 5: + self.r = v + self.g = p + self.b = q + default: + self.r = 0 + self.g = 0 + self.b = 0 + } + self.a = a + } + + /// Hue Saturation Value of the color. + var hsva: (h: Double, s: Double, v: Double, a: Double) { + let maxValue = max(r, g, b) + let minValue = min(r, g, b) + + var h: Double, s: Double, v: Double = maxValue + + let d = maxValue - minValue + s = maxValue == 0 ? 0 : d / maxValue; + + if (maxValue == minValue) { + h = 0; // achromatic + } else { + switch (maxValue) { + case r: h = (g - b) / d + (g < b ? 6 : 0) + case g: h = (b - r) / d + 2 + case b: h = (r - g) / d + 4 + default: h = maxValue + } + h = h / 6 + } + return (h: h, s: s, v: v, a: a) + } + + init(y: Double, u: Double, v: Double, a: Double) { + // From https://www.fourcc.org/fccyvrgb.php + self.r = y + 1.403 * v + self.g = y - 0.344 * u + self.b = y + 1.770 * u + self.a = a + } + + var yuv: (y: Double, u: Double, v: Double, a: Double) { + /// From https://www.fourcc.org/fccyvrgb.php + let y = 0.299 * r + 0.587 * g + 0.114 * b + let u = -0.14713 * r - 0.28886 * g + 0.436 * b + let v = 0.615 * r - 0.51499 * g - 0.10001 * b + return (y: y, u: u, v: v, a: a) + } + + func interpolateTo(_ to: Color, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Color { + return Color(r: r.interpolateTo(to.r, amount: amount), + g: g.interpolateTo(to.g, amount: amount), + b: b.interpolateTo(to.b, amount: amount), + a: a.interpolateTo(to.a, amount: amount)) + } +} + +extension CurveVertex: Interpolatable { + func interpolateTo(_ to: CurveVertex, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> CurveVertex { + return CurveVertex(point: point.interpolate(to.point, amount: amount), + inTangent: inTangent.interpolate(to.inTangent, amount: amount), + outTangent: outTangent.interpolate(to.outTangent, amount: amount)) + } +} + +extension BezierPath: Interpolatable { + func interpolateTo(_ to: BezierPath, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> BezierPath { + var newPath = BezierPath() + for i in 0.. TextDocument { + if amount == 1 { + return to + } + return self + } +} + +extension Array: Interpolatable where Element == Double { + func interpolateTo(_ to: Array, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Array { + var returnArray = [Double]() + for i in 0.. CGFloat { + let startTime = time + let endTime = to.time + if keyTime <= startTime { + return 0 + } + if endTime <= keyTime { + return 1 + } + + if isHold { + return 0 + } + + let outTanPoint = outTangent?.pointValue ?? .zero + let inTanPoint = to.inTangent?.pointValue ?? CGPoint(x: 1, y: 1) + var progress: CGFloat = keyTime.remap(fromLow: startTime, fromHigh: endTime, toLow: 0, toHigh: 1) + if !outTanPoint.isZero || !inTanPoint.equalTo(CGPoint(x: 1, y: 1)) { + /// Cubic interpolation + progress = progress.cubicBezierInterpolate(.zero, outTanPoint, inTanPoint, CGPoint(x: 1, y: 1)) + } + return progress + } + + /// Interpolates the keyframes' by a progress from 0-1 + func interpolate(_ to: Keyframe, progress: CGFloat) -> T { + return value.interpolateTo(to.value, amount: progress, spatialOutTangent: spatialOutTangent?.pointValue, spatialInTangent: to.spatialInTangent?.pointValue) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/BezierPath.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/BezierPath.swift new file mode 100644 index 0000000..a2babfc --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/BezierPath.swift @@ -0,0 +1,401 @@ +// +// Shape.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/8/19. +// + +import Foundation +import CoreGraphics + +/// A container that holds instructions for creating a single, unbroken Bezier Path. +struct BezierPath { + + /// The elements of the path + fileprivate(set) var elements: [PathElement] + + /// If the path is closed or not. + fileprivate(set) var closed: Bool + + /// The total length of the path. + fileprivate(set) var length: CGFloat + + /// Initializes a new Bezier Path. + init(startPoint: CurveVertex) { + self.elements = [PathElement(vertex: startPoint)] + self.length = 0 + self.closed = false + } + + init() { + self.elements = [] + self.length = 0 + self.closed = false + } + + mutating func moveToStartPoint(_ vertex: CurveVertex) { + self.elements = [PathElement(vertex: vertex)] + self.length = 0 + } + + mutating func addVertex(_ vertex: CurveVertex) { + guard let previous = elements.last else { + addElement(PathElement(vertex: vertex)) + return + } + addElement(previous.pathElementTo(vertex)) + } + + mutating func addCurve(toPoint: CGPoint, outTangent: CGPoint, inTangent: CGPoint) { + guard let previous = elements.last else { return } + let newVertex = CurveVertex(inTangent, toPoint, toPoint) + updateVertex(CurveVertex(previous.vertex.inTangent, previous.vertex.point, outTangent), atIndex: elements.endIndex - 1, remeasure: false) + addVertex(newVertex) + } + + mutating func addLine(toPoint: CGPoint) { + guard let previous = elements.last else { return } + let newVertex = CurveVertex(point: toPoint, inTangentRelative: .zero, outTangentRelative: .zero) + updateVertex(CurveVertex(previous.vertex.inTangent, previous.vertex.point, previous.vertex.point), atIndex: elements.endIndex - 1, remeasure: false) + addVertex(newVertex) + } + + mutating func close() { + self.closed = true + } + + mutating func addElement(_ pathElement: PathElement) { + elements.append(pathElement) + length = length + pathElement.length + } + + mutating func updateVertex(_ vertex: CurveVertex, atIndex: Int, remeasure: Bool) { + if remeasure { + var newElement: PathElement + if atIndex > 0 { + let previousElement = elements[atIndex-1] + newElement = previousElement.pathElementTo(vertex) + } else { + newElement = PathElement(vertex: vertex) + } + elements[atIndex] = newElement + + if atIndex + 1 < elements.count{ + let nextElement = elements[atIndex + 1] + elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex) + } + + } else { + let oldElement = elements[atIndex] + elements[atIndex] = oldElement.updateVertex(newVertex: vertex) + } + } + + /** + Trims a path fromLength toLength with an offset. + + Length and offset are defined in the length coordinate space. + If any argument is outside the range of this path, then it will be looped over the path from finish to start. + + Cutting the curve when fromLength is less than toLength + x x x x + ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- + |Offset |fromLength toLength| | + + Cutting the curve when from Length is greater than toLength + x x x x x + oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo + | toLength| |Offset |fromLength | + + */ + func trim(fromLength: CGFloat, toLength: CGFloat, offsetLength: CGFloat) -> [BezierPath] { + guard elements.count > 1 else { + return [] + } + + if fromLength == toLength { + return [] + } + + /// Normalize lengths to the curve length. + var start = (fromLength+offsetLength).truncatingRemainder(dividingBy: length) + var end = (toLength+offsetLength).truncatingRemainder(dividingBy: length) + + if start < 0 { + start = length + start + } + + if end < 0 { + end = length + end + } + + if start == length { + start = 0 + } + if end == 0 { + end = length + } + + if start == 0 && end == length || + start == end || + start == length && end == 0 { + /// The trim encompasses the entire path. Return. + return [self] + } + + if start > end { + // Start is greater than end. Two paths are returned. + return trimPathAtLengths(positions: [(start: 0, end: end), (start: start, end: length)]) + } + + return trimPathAtLengths(positions: [(start: start, end: end)]) + } + + // MARK: File Private + + /// Trims a path by a list of positions and returns the sub paths + fileprivate func trimPathAtLengths(positions: [(start: CGFloat, end: CGFloat)]) -> [BezierPath] { + guard positions.count > 0 else { + return [] + } + var remainingPositions = positions + + var trim = remainingPositions.remove(at: 0) + + var paths = [BezierPath]() + + var runningLength: CGFloat = 0 + var finishedTrimming: Bool = false + var pathElements = elements + + var currentPath = BezierPath() + var i: Int = 0 + + while !finishedTrimming { + if pathElements.count <= i { + /// Do this for rounding errors + paths.append(currentPath) + finishedTrimming = true + continue + } + /// Loop through and add elements within start->end range. + /// Get current element + let element = pathElements[i] + + /// Calculate new running length. + let newLength = runningLength + element.length + + if newLength < trim.start { + /// Element is not included in the trim, continue. + runningLength = newLength + i = i + 1 + /// Increment index, we are done with this element. + continue + } + + if newLength == trim.start { + /// Current element IS the start element. + /// For start we want to add a zero length element. + currentPath.moveToStartPoint(element.vertex) + runningLength = newLength + i = i + 1 + /// Increment index, we are done with this element. + continue + } + + if runningLength < trim.start, trim.start < newLength, currentPath.elements.count == 0 { + /// The start of the trim is between this element and the previous, trim. + /// Get previous element. + let previousElement = pathElements[i-1] + /// Trim it + let trimLength = trim.start - runningLength + let trimResults = element.splitElementAtPosition(fromElement: previousElement, atLength: trimLength) + /// Add the right span start. + currentPath.moveToStartPoint(trimResults.rightSpan.start.vertex) + + pathElements[i] = trimResults.rightSpan.end + pathElements[i-1] = trimResults.rightSpan.start + runningLength = runningLength + trimResults.leftSpan.end.length + /// Dont increment index or the current length, the end of this path can be within this span. + continue + } + + if trim.start < newLength, newLength < trim.end { + /// Element lies within the trim span. + currentPath.addElement(element) + runningLength = newLength + i = i + 1 + continue + } + + if newLength == trim.end { + /// Element is the end element. + /// The element could have a new length if it's added right after the start node. + currentPath.addElement(element) + /// We are done with this span. + runningLength = newLength + i = i + 1 + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + if runningLength < trim.end, trim.end < newLength { + /// New element must be cut for end. + /// Get previous element. + let previousElement = pathElements[i-1] + /// Trim it + let trimLength = trim.end - runningLength + let trimResults = element.splitElementAtPosition(fromElement: previousElement, atLength: trimLength) + /// Add the left span end. + + currentPath.updateVertex(trimResults.leftSpan.start.vertex, atIndex: currentPath.elements.count - 1, remeasure: false) + currentPath.addElement(trimResults.leftSpan.end) + + pathElements[i] = trimResults.rightSpan.end + pathElements[i-1] = trimResults.rightSpan.start + runningLength = runningLength + trimResults.leftSpan.end.length + /// Dont increment index or the current length, the start of the next path can be within this span. + /// We are done with this span. + /// Allow the path to be finalized. + /// Fall through to finalize path and move to next position + } + + paths.append(currentPath) + currentPath = BezierPath() + if remainingPositions.count > 0 { + trim = remainingPositions.remove(at: 0) + } else { + finishedTrimming = true + } + } + return paths + } + +} + +extension BezierPath: Codable { + + /** + The BezierPath container is encoded and decoded from the JSON format + that defines points for a lottie animation. + + { + "c" = Bool + "i" = [[Double]], + "o" = [[Double]], + "v" = [[Double]] + } + + */ + + enum CodingKeys : String, CodingKey { + case closed = "c" + case inPoints = "i" + case outPoints = "o" + case vertices = "v" + } + + init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer + + if let keyedContainer = try? decoder.container(keyedBy: BezierPath.CodingKeys.self) { + container = keyedContainer + } else { + var unkeyedContainer = try decoder.unkeyedContainer() + container = try unkeyedContainer.nestedContainer(keyedBy: BezierPath.CodingKeys.self) + } + + self.closed = try container.decodeIfPresent(Bool.self, forKey: .closed) ?? true + + var vertexContainer = try container.nestedUnkeyedContainer(forKey: .vertices) + var inPointsContainer = try container.nestedUnkeyedContainer(forKey: .inPoints) + var outPointsContainer = try container.nestedUnkeyedContainer(forKey: .outPoints) + + guard vertexContainer.count == inPointsContainer.count, inPointsContainer.count == outPointsContainer.count else { + /// Will throw an error if vertex, inpoints, and outpoints are not the same length. + /// This error is to be expected. + throw DecodingError.dataCorruptedError(forKey: CodingKeys.vertices, + in: container, + debugDescription: "Vertex data does not match In Tangents and Out Tangents") + } + + guard let count = vertexContainer.count, count > 0 else { + self.length = 0 + self.elements = [] + return + } + + var decodedElements = [PathElement]() + + /// Create first point + let firstVertex = CurveVertex(point: try vertexContainer.decode(CGPoint.self), + inTangentRelative: try inPointsContainer.decode(CGPoint.self), + outTangentRelative: try outPointsContainer.decode(CGPoint.self)) + var previousElement = PathElement(vertex: firstVertex) + decodedElements.append(previousElement) + + var totalLength: CGFloat = 0 + while !vertexContainer.isAtEnd { + /// Get the next vertex data. + let vertex = CurveVertex(point: try vertexContainer.decode(CGPoint.self), + inTangentRelative: try inPointsContainer.decode(CGPoint.self), + outTangentRelative: try outPointsContainer.decode(CGPoint.self)) + let pathElement = previousElement.pathElementTo(vertex) + decodedElements.append(pathElement) + previousElement = pathElement + totalLength = totalLength + pathElement.length + } + if closed { + let closeElement = previousElement.pathElementTo(firstVertex) + decodedElements.append(closeElement) + totalLength = totalLength + closeElement.length + } + self.length = totalLength + self.elements = decodedElements + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: BezierPath.CodingKeys.self) + try container.encode(closed, forKey: .closed) + + var vertexContainer = container.nestedUnkeyedContainer(forKey: .vertices) + var inPointsContainer = container.nestedUnkeyedContainer(forKey: .inPoints) + var outPointsContainer = container.nestedUnkeyedContainer(forKey: .outPoints) + + /// If closed path, ignore the final element. + let finalIndex = closed ? self.elements.endIndex - 1 : self.elements.endIndex + for i in 0.. CGPath { + let cgPath = CGMutablePath() + + var previousElement: PathElement? + for element in elements { + if let previous = previousElement { + if previous.vertex.outTangentRelative.isZero && element.vertex.inTangentRelative.isZero { + cgPath.addLine(to: element.vertex.point) + } else { + cgPath.addCurve(to: element.vertex.point, control1: previous.vertex.outTangent, control2: element.vertex.inTangent) + } + } else { + cgPath.move(to: element.vertex.point) + } + previousElement = element + } + if self.closed { + cgPath.closeSubpath() + } + return cgPath + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/ColorExtension.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/ColorExtension.swift new file mode 100644 index 0000000..309031a --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/ColorExtension.swift @@ -0,0 +1,76 @@ +// +// Color.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import Foundation +import CoreGraphics + +extension Color: Codable { + + public init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + + var r1: Double + if !container.isAtEnd { + r1 = try container.decode(Double.self) + } else { + r1 = 0 + } + + var g1: Double + if !container.isAtEnd { + g1 = try container.decode(Double.self) + } else { + g1 = 0 + } + + var b1: Double + if !container.isAtEnd { + b1 = try container.decode(Double.self) + } else { + b1 = 0 + } + + var a1: Double + if !container.isAtEnd { + a1 = try container.decode(Double.self) + } else { + a1 = 1 + } + if r1 > 1, g1 > 1, b1 > 1, a1 > 1 { + r1 = r1 / 255 + g1 = g1 / 255 + b1 = b1 / 255 + a1 = a1 / 255 + } + self.r = r1 + self.g = g1 + self.b = b1 + self.a = a1 + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(r) + try container.encode(g) + try container.encode(b) + try container.encode(a) + } + +} + +extension Color { + + static var clearColor: CGColor { + return CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 0])! + } + + var cgColorValue: CGColor { + // TODO: Fix color spaces + let colorspace = CGColorSpaceCreateDeviceRGB() + return CGColor(colorSpace: colorspace, components: [CGFloat(r), CGFloat(g), CGFloat(b), CGFloat(a)]) ?? Color.clearColor + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/CompoundBezierPath.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/CompoundBezierPath.swift new file mode 100644 index 0000000..068a13d --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/CompoundBezierPath.swift @@ -0,0 +1,158 @@ +// +// CompoundBezierPath.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/14/19. +// + +import Foundation +import CoreGraphics + +/** + A collection of BezierPath objects that can be trimmed and added. + + */ +struct CompoundBezierPath { + + let paths: [BezierPath] + + let length: CGFloat + + init() { + paths = [] + length = 0 + } + + init(path: BezierPath) { + self.paths = [path] + self.length = path.length + } + + init(paths: [BezierPath], length: CGFloat) { + self.paths = paths + self.length = length + } + + init(paths: [BezierPath]) { + self.paths = paths + var l: CGFloat = 0 + for path in paths { + l = l + path.length + } + self.length = l + } + + func addPath(path: BezierPath) -> CompoundBezierPath { + var newPaths = paths + newPaths.append(path) + return CompoundBezierPath(paths: newPaths, length: length + path.length) + } + + func combine(_ compoundBezier: CompoundBezierPath) -> CompoundBezierPath { + var newPaths = paths + newPaths.append(contentsOf: compoundBezier.paths) + return CompoundBezierPath(paths: newPaths, length: length + compoundBezier.length) + } + + func trim(fromPosition: CGFloat, toPosition: CGFloat, offset: CGFloat, trimSimultaneously: Bool) -> CompoundBezierPath { + if fromPosition == toPosition { + return CompoundBezierPath() + } + + if trimSimultaneously { + /// Trim each path individually. + var newPaths = [BezierPath]() + for path in paths { + newPaths.append(contentsOf: path.trim(fromLength: fromPosition * path.length, + toLength: toPosition * path.length, + offsetLength: offset * path.length)) + } + return CompoundBezierPath(paths: newPaths) + } + + /// Normalize lengths to the curve length. + var startPosition = (fromPosition+offset).truncatingRemainder(dividingBy: 1) + var endPosition = (toPosition+offset).truncatingRemainder(dividingBy: 1) + + if startPosition < 0 { + startPosition = 1 + startPosition + } + + if endPosition < 0 { + endPosition = 1 + endPosition + } + + if startPosition == 1 { + startPosition = 0 + } + if endPosition == 0 { + endPosition = 1 + } + + if startPosition == 0 && endPosition == 1 || + startPosition == endPosition || + startPosition == 1 && endPosition == 0 { + /// The trim encompasses the entire path. Return. + return self + } + + var positions: [(start: CGFloat, end: CGFloat)] + if endPosition < startPosition { + positions = [(start: 0, end: endPosition * length), + (start: startPosition * length, end: length)] + } else { + positions = [(start: startPosition * length, end: endPosition * length)] + } + + var compoundPath = CompoundBezierPath() + var trim = positions.remove(at: 0) + var pathStartPosition: CGFloat = 0 + + var finishedTrimming: Bool = false + var i: Int = 0 + + while !finishedTrimming { + if paths.count <= i { + /// Rounding errors + finishedTrimming = true + continue + } + let path = paths[i] + + let pathEndPosition = pathStartPosition + path.length + + if pathEndPosition < trim.start { + /// Path is not included in the trim, continue. + pathStartPosition = pathEndPosition + i = i + 1 + continue + + } else if trim.start <= pathStartPosition, pathEndPosition <= trim.end { + /// Full Path is inside of trim. Add full path. + compoundPath = compoundPath.addPath(path: path) + } else { + if let trimPath = path.trim(fromLength: trim.start > pathStartPosition ? (trim.start - pathStartPosition) : 0, + toLength: trim.end < pathEndPosition ? (trim.end - pathStartPosition) : path.length, + offsetLength: 0).first { + compoundPath = compoundPath.addPath(path: trimPath) + } + } + + + if trim.end <= pathEndPosition { + /// We are done with the current trim. + /// Advance trim but remain on the same path in case the next trim overlaps it. + if positions.count > 0 { + trim = positions.remove(at: 0) + } else { + finishedTrimming = true + } + } else { + pathStartPosition = pathEndPosition + i = i + 1 + } + } + return compoundPath + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/CurveVertex.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/CurveVertex.swift new file mode 100644 index 0000000..d4b5eb1 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/CurveVertex.swift @@ -0,0 +1,177 @@ +// +// CurveVertex.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/11/19. +// + +import Foundation +import CoreGraphics + +/// A single vertex with an in and out tangent +struct CurveVertex { + + let point: CGPoint + + let inTangent: CGPoint + let outTangent: CGPoint + + /// Initializes a curve point with absolute values + init(_ inTangent: CGPoint, _ point: CGPoint, _ outTangent: CGPoint) { + self.point = point + self.inTangent = inTangent + self.outTangent = outTangent + } + + /// Initializes a curve point with relative values + init(point: CGPoint, inTangentRelative: CGPoint, outTangentRelative: CGPoint) { + self.point = point + self.inTangent = point.add(inTangentRelative) + self.outTangent = point.add(outTangentRelative) + } + + /// Initializes a curve point with absolute values + init(point: CGPoint, inTangent: CGPoint, outTangent: CGPoint) { + self.point = point + self.inTangent = inTangent + self.outTangent = outTangent + } + + var inTangentRelative: CGPoint { + return inTangent.subtract(point) + } + + var outTangentRelative: CGPoint { + return outTangent.subtract(point) + } + + func reversed() -> CurveVertex { + return CurveVertex(point: point, inTangent: outTangent, outTangent: inTangent) + } + + func translated(_ translation: CGPoint) -> CurveVertex { + return CurveVertex(point: point + translation, inTangent: inTangent + translation, outTangent: outTangent + translation) + } + + /** + Trims a path defined by two Vertices at a specific position, from 0 to 1 + + The path can be visualized below. + + F is fromVertex. + V is the vertex of the receiver. + P is the position from 0-1. + O is the outTangent of fromVertex. + F====O=========P=======I====V + + After trimming the curve can be visualized below. + + S is the returned Start vertex. + E is the returned End vertex. + T is the trim point. + TI and TO are the new tangents for the trimPoint + NO and NI are the new tangents for the startPoint and endPoints + S==NO=========TI==T==TO=======NI==E + */ + func splitCurve(toVertex: CurveVertex, position: CGFloat) -> + (start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex) { + + /// If position is less than or equal to 0, trim at start. + if position <= 0 { + return (start: CurveVertex(point: point, inTangentRelative: inTangentRelative, outTangentRelative: .zero), + trimPoint: CurveVertex(point: point, inTangentRelative: .zero, outTangentRelative: outTangentRelative), + end: toVertex) + } + + /// If position is greater than or equal to 1, trim at end. + if position >= 1 { + return (start: self, + trimPoint: CurveVertex(point: toVertex.point, inTangentRelative: toVertex.inTangentRelative, outTangentRelative: .zero), + end: CurveVertex(point: toVertex.point, inTangentRelative: .zero, outTangentRelative: toVertex.outTangentRelative)) + } + + if outTangentRelative.isZero && toVertex.inTangentRelative.isZero { + /// If both tangents are zero, then span to be trimmed is a straight line. + let trimPoint = point.interpolate(toVertex.point, amount: position) + return (start: self, + trimPoint: CurveVertex(point: trimPoint, inTangentRelative: .zero, outTangentRelative: .zero), + end: toVertex) + } + /// Cutting by amount gives incorrect length.... + /// One option is to cut by a stride until it gets close then edge it down. + /// Measuring a percentage of the spans does not equal the same as measuring a percentage of length. + /// This is where the historical trim path bugs come from. + let a = point.interpolate(outTangent, amount: position) + let b = outTangent.interpolate(toVertex.inTangent, amount: position) + let c = toVertex.inTangent.interpolate(toVertex.point, amount: position) + let d = a.interpolate(b, amount: position) + let e = b.interpolate(c, amount: position) + let f = d.interpolate(e, amount: position) + return (start: CurveVertex(point: point, inTangent: inTangent, outTangent: a), + trimPoint: CurveVertex(point: f, inTangent: d, outTangent: e), + end: CurveVertex(point: toVertex.point, inTangent: c, outTangent: toVertex.outTangent)) + } + + /** + Trims a curve of a known length to a specific length and returns the points. + + There is not a performant yet accurate way to cut a curve to a specific length. + This calls splitCurve(toVertex: position:) to split the curve and then measures + the length of the new curve. The function then iterates through the samples, + adjusting the position of the cut for a more precise cut. + Usually a single iteration is enough to get within 0.5 points of the desired + length. + + This function should probably live in PathElement, since it deals with curve + lengths. + */ + func trimCurve(toVertex: CurveVertex, atLength: CGFloat, curveLength: CGFloat, maxSamples: Int, accuracy: CGFloat = 1) -> + (start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex) { + var currentPosition = atLength / curveLength + var results = splitCurve(toVertex: toVertex, position: currentPosition) + + if maxSamples == 0 { + return results + } + + for _ in 1...maxSamples { + let length = results.start.distanceTo(results.trimPoint) + let lengthDiff = atLength - length + /// Check if length is correct. + if lengthDiff < accuracy { + return results + } + let diffPosition = max(min(((currentPosition / length) * lengthDiff), currentPosition * 0.5), currentPosition * -0.5) + currentPosition = diffPosition + currentPosition + results = splitCurve(toVertex: toVertex, position: currentPosition) + } + return results + } + + + /** + The distance from the receiver to the provided vertex. + + For lines (zeroed tangents) the distance between the two points is measured. + For curves the curve is iterated over by sample count and the points are measured. + This is ~99% accurate at a sample count of 30 + */ + func distanceTo(_ toVertex: CurveVertex, sampleCount: Int = 25) -> CGFloat { + + if outTangentRelative.isZero && toVertex.inTangentRelative.isZero { + /// Return a linear distance. + return point.distanceTo(toVertex.point) + } + + var distance: CGFloat = 0 + + var previousPoint = point + for i in 0.. PathElement { + return PathElement(length: vertex.distanceTo(toVertex), vertex: toVertex) + } + + /// Initializes a new path with length of 0 + init(vertex: CurveVertex) { + self.length = 0 + self.vertex = vertex + } + + /// Initializes a new path with length + fileprivate init(length: CGFloat, vertex: CurveVertex) { + self.length = length + self.vertex = vertex + } + + func updateVertex(newVertex: CurveVertex) -> PathElement { + return PathElement(length: length, vertex: newVertex) + } + + /// Splits an element span defined by the receiver and fromElement to a position 0-1 + func splitElementAtPosition(fromElement: PathElement, atLength: CGFloat) -> + (leftSpan: (start: PathElement, end: PathElement), rightSpan: (start: PathElement, end: PathElement)) { + /// Trim the span. Start and trim go into the first, trim and end go into second. + let trimResults = fromElement.vertex.trimCurve(toVertex: vertex, atLength: atLength, curveLength: length, maxSamples: 3) + + /// Create the elements for the break + let spanAStart = PathElement(length: fromElement.length, + vertex: CurveVertex(point: fromElement.vertex.point, + inTangent: fromElement.vertex.inTangent, + outTangent: trimResults.start.outTangent)) + /// Recalculating the length here is a waste as the trimCurve function also accurately calculates this length. + let spanAEnd = spanAStart.pathElementTo(trimResults.trimPoint) + + let spanBStart = PathElement(vertex: trimResults.trimPoint) + let spanBEnd = spanBStart.pathElementTo(trimResults.end) + return (leftSpan: (start: spanAStart, end: spanAEnd), + rightSpan: (start: spanBStart, end: spanBEnd)) + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/VectorsExtensions.swift b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/VectorsExtensions.swift new file mode 100644 index 0000000..b8c0a39 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Private/Utility/Primitives/VectorsExtensions.swift @@ -0,0 +1,218 @@ +// +// Vector.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/7/19. +// + +import Foundation +import CoreGraphics +import QuartzCore + +/** + Single value container. Needed because lottie sometimes wraps a Double in an array. + */ +extension Vector1D: Codable { + + public init(from decoder: Decoder) throws { + /// Try to decode an array of doubles + do { + var container = try decoder.unkeyedContainer() + self.value = try container.decode(Double.self) + } catch { + self.value = try decoder.singleValueContainer().decode(Double.self) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(value) + } + + var cgFloatValue: CGFloat { + return CGFloat(value) + } + +} + +extension Double { + var vectorValue: Vector1D { + return Vector1D(self) + } +} + +/** + Needed for decoding json {x: y:} to a CGPoint + */ +struct Vector2D: Codable { + + var x: Double + var y: Double + + init(x: Double, y: Double) { + self.x = x + self.y = y + } + + private enum CodingKeys : String, CodingKey { + case x = "x" + case y = "y" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Vector2D.CodingKeys.self) + + do { + let xValue: [Double] = try container.decode([Double].self, forKey: .x) + self.x = xValue[0] + } catch { + self.x = try container.decode(Double.self, forKey: .x) + } + + do { + let yValue: [Double] = try container.decode([Double].self, forKey: .y) + self.y = yValue[0] + } catch { + self.y = try container.decode(Double.self, forKey: .y) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: Vector2D.CodingKeys.self) + try container.encode(x, forKey: .x) + try container.encode(y, forKey: .y) + } + + var pointValue: CGPoint { + return CGPoint(x: x, y: y) + } +} + +extension Vector2D { + +} + +extension CGPoint { + var vector2dValue: Vector2D { + return Vector2D(x: Double(x), y: Double(y)) + } +} + +/** + A three dimensional vector. + These vectors are encoded and decoded from [Double] + */ + +extension Vector3D: Codable { + + init(x: CGFloat, y: CGFloat, z: CGFloat) { + self.x = Double(x) + self.y = Double(y) + self.z = Double(z) + } + + public init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + + if !container.isAtEnd { + self.x = try container.decode(Double.self) + } else { + self.x = 0 + } + + if !container.isAtEnd { + self.y = try container.decode(Double.self) + } else { + self.y = 0 + } + + if !container.isAtEnd { + self.z = try container.decode(Double.self) + } else { + self.z = 0 + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + try container.encode(x) + try container.encode(y) + try container.encode(z) + } + +} + +public extension Vector3D { + var pointValue: CGPoint { + return CGPoint(x: x, y: y) + } + + var sizeValue: CGSize { + return CGSize(width: x, height: y) + } +} + +extension CGPoint { + var vector3dValue: Vector3D { + return Vector3D(x: x, y: y, z: 0) + } +} + +extension CGSize { + var vector3dValue: Vector3D { + return Vector3D(x: width, y: height, z: 1) + } +} + +extension CATransform3D { + + func rotated(_ degrees: CGFloat) -> CATransform3D { + return CATransform3DRotate(self, degrees.toRadians(), 0, 0, 1) + } + + func translated(_ translation: CGPoint) -> CATransform3D { + return CATransform3DTranslate(self, translation.x, translation.y, 0) + } + + func scaled(_ scale: CGSize) -> CATransform3D { + return CATransform3DScale(self, scale.width, scale.height, 1) + } + + func skewed(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D { + return CATransform3DConcat(CATransform3D.makeSkew(skew: skew, skewAxis: skewAxis), self) + } + + static func makeSkew(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D { + let mCos = cos(skewAxis.toRadians()) + let mSin = sin(skewAxis.toRadians()) + let aTan = tan(skew.toRadians()) + + let transform1 = CATransform3D(m11: mCos, m12: mSin, m13: 0, m14: 0, + m21: -mSin, m22: mCos, m23: 0, m24: 0, + m31: 0, m32: 0, m33: 1, m34: 0, + m41: 0, m42: 0, m43: 0, m44: 1) + + let transform2 = CATransform3D(m11: 1, m12: 0, m13: 0, m14: 0, + m21: aTan, m22: 1, m23: 0, m24: 0, + m31: 0, m32: 0, m33: 1, m34: 0, + m41: 0, m42: 0, m43: 0, m44: 1) + + let transform3 = CATransform3D(m11: mCos, m12: -mSin, m13: 0, m14: 0, + m21: mSin, m22: mCos, m23: 0, m24: 0, + m31: 0, m32: 0, m33: 1, m34: 0, + m41: 0, m42: 0, m43: 0, m44: 1) + return CATransform3DConcat(transform3, CATransform3DConcat(transform2, transform1)) + } + + static func makeTransform(anchor: CGPoint, + position: CGPoint, + scale: CGSize, + rotation: CGFloat, + skew: CGFloat?, + skewAxis: CGFloat?) -> CATransform3D { + if let skew = skew, let skewAxis = skewAxis { + return CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).skewed(skew: -skew, skewAxis: skewAxis).scaled(scale * 0.01).translated(anchor * -1) + } + return CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).scaled(scale * 0.01).translated(anchor * -1) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/Animation/AnimationPublic.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/Animation/AnimationPublic.swift new file mode 100644 index 0000000..2f0a3c1 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/Animation/AnimationPublic.swift @@ -0,0 +1,196 @@ +// +// AnimationPublic.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/5/19. +// + +import Foundation +import CoreGraphics + +public extension Animation { + + // MARK: Animation (Loading) + + /** + Loads an animation model from a bundle by its name. Returns `nil` if an animation is not found. + + - Parameter name: The name of the json file without the json extension. EG "StarAnimation" + - Parameter bundle: The bundle in which the animation is located. Defaults to `Bundle.main` + - Parameter subdirectory: A subdirectory in the bundle in which the animation is located. Optional. + - Parameter animationCache: A cache for holding loaded animations. Optional. + + - Returns: Deserialized `Animation`. Optional. + */ + static func named(_ name: String, + bundle: Bundle = Bundle.main, + subdirectory: String? = nil, + animationCache: AnimationCacheProvider? = nil) -> Animation? { + /// Create a cache key for the animation. + let cacheKey = bundle.bundlePath + (subdirectory ?? "") + "/" + name + + /// Check cache for animation + if let animationCache = animationCache, + let animation = animationCache.animation(forKey: cacheKey) { + /// If found, return the animation. + return animation + } + /// Make sure the bundle has a file at the path provided. + guard let url = bundle.url(forResource: name, withExtension: "json", subdirectory: subdirectory) else { + return nil + } + + do { + /// Decode animation. + let json = try Data(contentsOf: url) + let animation = try JSONDecoder().decode(Animation.self, from: json) + animationCache?.setAnimation(animation, forKey: cacheKey) + return animation + } catch { + /// Decoding error. + print(error) + return nil + } + } + + /** + Loads an animation from a specific filepath. + - Parameter filepath: The absolute filepath of the animation to load. EG "/User/Me/starAnimation.json" + - Parameter animationCache: A cache for holding loaded animations. Optional. + + - Returns: Deserialized `Animation`. Optional. + */ + static func filepath(_ filepath: String, + animationCache: AnimationCacheProvider? = nil) -> Animation? { + + /// Check cache for animation + if let animationCache = animationCache, + let animation = animationCache.animation(forKey: filepath) { + return animation + } + + do { + /// Decode the animation. + let json = try Data(contentsOf: URL(fileURLWithPath: filepath)) + let animation = try JSONDecoder().decode(Animation.self, from: json) + animationCache?.setAnimation(animation, forKey: filepath) + return animation + } catch { + /// Decoding Error. + return nil + } + } + + /// A closure for an Animation download. The closure is passed `nil` if there was an error. + typealias DownloadClosure = (Animation?) -> Void + + /** + Loads a Lottie animation asynchronously from the URL. + + - Parameter url: The url to load the animation from. + - Parameter closure: A closure to be called when the animation has loaded. + - Parameter animationCache: A cache for holding loaded animations. + + */ + static func loadedFrom(url: URL, + closure: @escaping Animation.DownloadClosure, + animationCache: AnimationCacheProvider?) { + + if let animationCache = animationCache, let animation = animationCache.animation(forKey: url.absoluteString) { + closure(animation) + } else { + let task = URLSession.shared.dataTask(with: url) { (data, response, error) in + guard error == nil, let jsonData = data else { + DispatchQueue.main.async { + closure(nil) + } + return + } + do { + let animation = try JSONDecoder().decode(Animation.self, from: jsonData) + DispatchQueue.main.async { + animationCache?.setAnimation(animation, forKey: url.absoluteString) + closure(animation) + } + } catch { + DispatchQueue.main.async { + closure(nil) + } + } + + } + task.resume() + } + } + + // MARK: Animation (Helpers) + + /** + Markers are a way to describe a point in time by a key name. + + Markers are encoded into animation JSON. By using markers a designer can mark + playback points for a developer to use without having to worry about keeping + track of animation frames. If the animation file is updated, the developer + does not need to update playback code. + + Returns the Progress Time for the marker named. Returns nil if no marker found. + */ + func progressTime(forMarker named: String) -> AnimationProgressTime? { + guard let markers = markerMap, let marker = markers[named] else { + return nil + } + return progressTime(forFrame: marker.frameTime) + } + + /** + Markers are a way to describe a point in time by a key name. + + Markers are encoded into animation JSON. By using markers a designer can mark + playback points for a developer to use without having to worry about keeping + track of animation frames. If the animation file is updated, the developer + does not need to update playback code. + + Returns the Frame Time for the marker named. Returns nil if no marker found. + */ + func frameTime(forMarker named: String) -> AnimationFrameTime? { + guard let markers = markerMap, let marker = markers[named] else { + return nil + } + return marker.frameTime + } + + /// Converts Frame Time (Seconds * Framerate) into Progress Time (0 to 1). + func progressTime(forFrame frameTime: AnimationFrameTime) -> AnimationProgressTime { + return ((frameTime - startFrame) / (endFrame - startFrame)).clamp(0, 1) + } + + /// Converts Progress Time (0 to 1) into Frame Time (Seconds * Framerate) + func frameTime(forProgress progressTime: AnimationProgressTime) -> AnimationFrameTime { + return ((endFrame - startFrame) * progressTime) + startFrame + } + + /// Converts Frame Time (Seconds * Framerate) into Time (Seconds) + func time(forFrame frameTime: AnimationFrameTime) -> TimeInterval { + return Double(frameTime - startFrame) / framerate + } + + /// Converts Time (Seconds) into Frame Time (Seconds * Framerate) + func frameTime(forTime time: TimeInterval) -> AnimationFrameTime { + return CGFloat(time * framerate) + startFrame + } + + /// The duration in seconds of the animation. + var duration: TimeInterval { + return Double(endFrame - startFrame) / framerate + } + + /// The natural bounds in points of the animation. + var bounds: CGRect { + return CGRect(x: 0, y: 0, width: width, height: height) + } + + /// The natural size in points of the animation. + var size: CGSize { + return CGSize(width: width, height: height) + } +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/Animation/AnimationView.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/Animation/AnimationView.swift new file mode 100644 index 0000000..73126c6 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/Animation/AnimationView.swift @@ -0,0 +1,977 @@ +// +// LottieView.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/23/19. +// + +import Foundation +import QuartzCore + +/// Describes the behavior of an AnimationView when the app is moved to the background. +public enum LottieBackgroundBehavior { + /// Stop the animation and reset it to the beginning of its current play time. The completion block is called. + case stop + /// Pause the animation in its current state. The completion block is called. + case pause + /// Pause the animation and restart it when the application moves to the foreground. The completion block is stored and called when the animation completes. + case pauseAndRestore + /// Stops the animation and sets it to the end of its current play time. The completion block is called. + case forceFinish +} + +/// Defines animation loop behavior +public enum LottieLoopMode { + /// Animation is played once then stops. + case playOnce + /// Animation will loop from beginning to end until stopped. + case loop + /// Animation will play forward, then backwards and loop until stopped. + case autoReverse + /// Animation will loop from beginning to end up to defined amount of times. + case `repeat`(Float) + /// Animation will play forward, then backwards a defined amount of times. + case repeatBackwards(Float) +} + +extension LottieLoopMode: Equatable { + public static func == (lhs: LottieLoopMode, rhs: LottieLoopMode) -> Bool { + switch (lhs, rhs) { + case (.repeat(let lhsAmount), .repeat(let rhsAmount)), + (.repeatBackwards(let lhsAmount), .repeatBackwards(let rhsAmount)): + return lhsAmount == rhsAmount + case (.playOnce, .playOnce), + (.loop, .loop), + (.autoReverse, .autoReverse): + return true + default: + return false + } + } +} + +@IBDesignable +final public class AnimationView: LottieView { + + // MARK: - Public Properties + + /** + Sets the animation backing the animation view. Setting this will clear the + view's contents, completion blocks and current state. The new animation will + be loaded up and set to the beginning of its timeline. + */ + public var animation: Animation? { + didSet { + makeAnimationLayer() + } + } + + /// Set animation name from Interface Builder + @IBInspectable var animationName: String? { + didSet { + self.animation = animationName.flatMap { + Animation.named($0, animationCache: nil) + } + } + } + + /** + Describes the behavior of an AnimationView when the app is moved to the background. + + The default is `pause` which pauses the animation when the application moves to + the background. The completion block is called with `false` for completed. + */ + public var backgroundBehavior: LottieBackgroundBehavior = .pause + + /** + Sets the image provider for the animation view. An image provider provides the + animation with its required image data. + + Setting this will cause the animation to reload its image contents. + */ + public var imageProvider: AnimationImageProvider { + didSet { + reloadImages() + } + } + /** + Sets the text provider for animation view. A text provider provides the + animation with values for text layers + */ + public var textProvider: AnimationTextProvider { + didSet { + animationLayer?.textProvider = textProvider + } + } + + + /// Returns `true` if the animation is currently playing. + public var isAnimationPlaying: Bool { + return animationLayer?.animation(forKey: activeAnimationName) != nil + } + + /// Sets the loop behavior for `play` calls. Defaults to `playOnce` + public var loopMode: LottieLoopMode = .playOnce { + didSet { + updateInFlightAnimation() + } + } + + /** + When `true` the animation view will rasterize its contents when not animating. + Rasterizing will improve performance of static animations. + + Note: this will not produce crisp results at resolutions above the animations natural resolution. + + Defaults to `false` + */ + public var shouldRasterizeWhenIdle: Bool = false { + didSet { + updateRasterizationState() + } + } + + /** + Sets the current animation time with a Progress Time + + Note: Setting this will stop the current animation, if any. + Note 2: If `animation` is nil, setting this will fallback to 0 + */ + public var currentProgress: AnimationProgressTime { + set { + if let animation = animation { + currentFrame = animation.frameTime(forProgress: newValue) + } else { + currentFrame = 0 + } + } + get { + if let animation = animation { + return animation.progressTime(forFrame: currentFrame) + } else { + return 0 + } + } + } + + /** + Sets the current animation time with a time in seconds. + + Note: Setting this will stop the current animation, if any. + Note 2: If `animation` is nil, setting this will fallback to 0 + */ + public var currentTime: TimeInterval { + set { + if let animation = animation { + currentFrame = animation.frameTime(forTime: newValue) + } else { + currentFrame = 0 + } + } + get { + if let animation = animation { + return animation.time(forFrame: currentFrame) + } else { + return 0 + } + } + } + + /** + Sets the current animation time with a frame in the animations framerate. + + Note: Setting this will stop the current animation, if any. + */ + public var currentFrame: AnimationFrameTime { + set { + removeCurrentAnimation() + updateAnimationFrame(newValue) + } + get { + return animationLayer?.currentFrame ?? 0 + } + } + + /// Returns the current animation frame while an animation is playing. + public var realtimeAnimationFrame: AnimationFrameTime { + return isAnimationPlaying ? animationLayer?.presentation()?.currentFrame ?? currentFrame : currentFrame + } + + /// Returns the current animation frame while an animation is playing. + public var realtimeAnimationProgress: AnimationProgressTime { + if let animation = animation { + return animation.progressTime(forFrame: realtimeAnimationFrame) + } + return 0 + } + + /// Sets the speed of the animation playback. Defaults to 1 + public var animationSpeed: CGFloat = 1 { + didSet { + updateInFlightAnimation() + } + } + + /** + When `true` the animation will play back at the framerate encoded in the + `Animation` model. When `false` the animation will play at the framerate + of the device. + + Defaults to false + */ + public var respectAnimationFrameRate: Bool = false { + didSet { + animationLayer?.respectAnimationFrameRate = respectAnimationFrameRate + } + } + + /** + Controls the cropping of an Animation. Setting this property will crop the animation + to the current views bounds by the viewport frame. The coordinate space is specified + in the animation's coordinate space. + + Animatable. + */ + public var viewportFrame: CGRect? = nil { + didSet { + + /* + This is really ugly, but is needed to trigger a layout pass within an animation block. + Typically this happens automatically, when layout objects are UIView based. + The animation layer is a CALayer which will not implicitly grab the animation + duration of a UIView animation block. + + By setting bounds and then resetting bounds the UIView animation block's + duration and curve are captured and added to the layer. This is used in the + layout block to animate the animationLayer's position and size. + */ + let rect = bounds + self.bounds = CGRect.zero + self.bounds = rect + self.setNeedsLayout() + } + } + + // MARK: - Public Functions + + /** + Plays the animation from its current state to the end. + + - Parameter completion: An optional completion closure to be called when the animation completes playing. + */ + public func play(completion: LottieCompletionBlock? = nil) { + guard let animation = animation else { + return + } + + /// Build a context for the animation. + let context = AnimationContext(playFrom: CGFloat(animation.startFrame), + playTo: CGFloat(animation.endFrame), + closure: completion) + removeCurrentAnimation() + addNewAnimationForContext(context) + } + + /** + Plays the animation from a progress (0-1) to a progress (0-1). + + - Parameter fromProgress: The start progress of the animation. If `nil` the animation will start at the current progress. + - Parameter toProgress: The end progress of the animation. + - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used. + - Parameter completion: An optional completion closure to be called when the animation stops. + */ + public func play(fromProgress: AnimationProgressTime? = nil, + toProgress: AnimationProgressTime, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) { + guard let animation = animation else { + return + } + + removeCurrentAnimation() + if let loopMode = loopMode { + /// Set the loop mode, if one was supplied + self.loopMode = loopMode + } + let context = AnimationContext(playFrom: animation.frameTime(forProgress: fromProgress ?? currentProgress), + playTo: animation.frameTime(forProgress: toProgress), + closure: completion) + addNewAnimationForContext(context) + } + + /** + Plays the animation from a start frame to an end frame in the animation's framerate. + + - Parameter fromFrame: The start frame of the animation. If `nil` the animation will start at the current frame. + - Parameter toFrame: The end frame of the animation. + - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used. + - Parameter completion: An optional completion closure to be called when the animation stops. + */ + public func play(fromFrame: AnimationFrameTime? = nil, + toFrame: AnimationFrameTime, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) { + removeCurrentAnimation() + if let loopMode = loopMode { + /// Set the loop mode, if one was supplied + self.loopMode = loopMode + } + + let context = AnimationContext(playFrom: fromFrame ?? currentProgress, + playTo: toFrame, + closure: completion) + addNewAnimationForContext(context) + } + + /** + Plays the animation from a named marker to another marker. + + Markers are point in time that are encoded into the Animation data and assigned + a name. + + NOTE: If markers are not found the play command will exit. + + - Parameter fromMarker: The start marker for the animation playback. If `nil` the + animation will start at the current progress. + - Parameter toMarker: The end marker for the animation playback. + - Parameter loopMode: The loop behavior of the animation. If `nil` the view's `loopMode` property will be used. + - Parameter completion: An optional completion closure to be called when the animation stops. + */ + public func play(fromMarker: String? = nil, + toMarker: String, + loopMode: LottieLoopMode? = nil, + completion: LottieCompletionBlock? = nil) { + + guard let animation = animation, let markers = animation.markerMap, let to = markers[toMarker] else { + return + } + + removeCurrentAnimation() + if let loopMode = loopMode { + /// Set the loop mode, if one was supplied + self.loopMode = loopMode + } + + let fromTime: CGFloat + if let fromName = fromMarker, let from = markers[fromName] { + fromTime = CGFloat(from.frameTime) + } else { + fromTime = currentFrame + } + + let context = AnimationContext(playFrom: fromTime, + playTo: CGFloat(to.frameTime), + closure: completion) + addNewAnimationForContext(context) + } + + /** + Stops the animation and resets the view to its start frame. + + The completion closure will be called with `false` + */ + public func stop() { + removeCurrentAnimation() + currentFrame = 0 + } + + /** + Pauses the animation in its current state. + + The completion closure will be called with `false` + */ + public func pause() { + removeCurrentAnimation() + } + + /// Reloads the images supplied to the animation from the `imageProvider` + public func reloadImages() { + animationLayer?.reloadImages() + } + + /// Forces the AnimationView to redraw its contents. + public func forceDisplayUpdate() { + animationLayer?.forceDisplayUpdate() + } + + // MARK: - Public (Dynamic Properties) + + /** + + Sets a ValueProvider for the specified keypath. The value provider will be set + on all properties that match the keypath. + + Nearly all properties of a Lottie animation can be changed at runtime using a + combination of `Animation Keypaths` and `Value Providers`. + Setting a ValueProvider on a keypath will cause the animation to update its + contents and read the new Value Provider. + + A value provider provides a typed value on a frame by frame basis. + + - Parameter valueProvider: The new value provider for the properties. + - Parameter keypath: The keypath used to search for properties. + + Example: + ``` + /// A keypath that finds the color value for all `Fill 1` nodes. + let fillKeypath = AnimationKeypath(keypath: "**.Fill 1.Color") + /// A Color Value provider that returns a reddish color. + let redValueProvider = ColorValueProvider(Color(r: 1, g: 0.2, b: 0.3, a: 1)) + /// Set the provider on the animationView. + animationView.setValueProvider(redValueProvider, keypath: fillKeypath) + ``` + */ + public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + animationLayer?.setValueProvider(valueProvider, keypath: keypath) + } + /** + Reads the value of a property specified by the Keypath. + Returns nil if no property is found. + + - Parameter for: The keypath used to search for the property. + - Parameter atFrame: The Frame Time of the value to query. If nil then the current frame is used. + */ + public func getValue(for keypath: AnimationKeypath, atFrame: AnimationFrameTime?) -> Any? { + return animationLayer?.getValue(for: keypath, atFrame: atFrame) + } + + /// Logs all child keypaths. + public func logHierarchyKeypaths() { + animationLayer?.logHierarchyKeypaths() + } + + // MARK: - Public (Add Subview) + + /** + Searches for the nearest child layer to the first Keypath and adds the subview + to that layer. The subview will move and animate with the child layer. + Furthermore the subview will be in the child layers coordinate space. + + Note: if no layer is found for the keypath, then nothing happens. + + - Parameter subview: The subview to add to the found animation layer. + - Parameter keypath: The keypath used to find the animation layer. + + Example: + ``` + /// A keypath that finds `Layer 1` + let layerKeypath = AnimationKeypath(keypath: "Layer 1") + + /// Wrap the custom view in an `AnimationSubview` + let subview = AnimationSubview() + subview.addSubview(customView) + + /// Set the provider on the animationView. + animationView.addSubview(subview, forLayerAt: layerKeypath) + ``` + */ + public func addSubview(_ subview: AnimationSubview, forLayerAt keypath: AnimationKeypath) { + guard let sublayer = animationLayer?.layer(for: keypath) else { + return + } + self.setNeedsLayout() + self.layoutIfNeeded() + self.forceDisplayUpdate() + addSubview(subview) + if let subViewLayer = subview.viewLayer { + sublayer.addSublayer(subViewLayer) + } + } + + /** + Converts a CGRect from the AnimationView's coordinate space into the + coordinate space of the layer found at Keypath. + + If no layer is found, nil is returned + + - Parameter rect: The CGRect to convert. + - Parameter toLayerAt: The keypath used to find the layer. + */ + public func convert(_ rect: CGRect, toLayerAt keypath: AnimationKeypath?) -> CGRect? { + guard let animationLayer = animationLayer else { return nil } + guard let keypath = keypath else { + return viewLayer?.convert(rect, to: animationLayer) + } + guard let sublayer = animationLayer.layer(for: keypath) else { + return nil + } + self.setNeedsLayout() + self.layoutIfNeeded() + self.forceDisplayUpdate() + return animationLayer.convert(rect, to: sublayer) + } + + /** + Converts a CGPoint from the AnimationView's coordinate space into the + coordinate space of the layer found at Keypath. + + If no layer is found, nil is returned + + - Parameter point: The CGPoint to convert. + - Parameter toLayerAt: The keypath used to find the layer. + */ + public func convert(_ point: CGPoint, toLayerAt keypath: AnimationKeypath?) -> CGPoint? { + guard let animationLayer = animationLayer else { return nil } + guard let keypath = keypath else { + return viewLayer?.convert(point, to: animationLayer) + } + guard let sublayer = animationLayer.layer(for: keypath) else { + return nil + } + self.setNeedsLayout() + self.layoutIfNeeded() + self.forceDisplayUpdate() + return animationLayer.convert(point, to: sublayer) + } + + // MARK: - Public (Animation Contents) + + /** + Sets the enabled state of all animator nodes found with the keypath search. + This can be used to interactively enable / disable parts of the animation. + + - Parameter isEnabled: When true the animator nodes affect the rendering tree. When false the node is removed from the tree. + - Parameter keypath: The keypath used to find the node(s). + */ + public func setNodeIsEnabled(isEnabled: Bool, keypath: AnimationKeypath) { + guard let animationLayer = animationLayer else { return } + let nodes = animationLayer.animatorNodes(for: keypath) + if let nodes = nodes { + for node in nodes { + node.isEnabled = isEnabled + } + self.forceDisplayUpdate() + } + } + + // MARK: - Public (Markers) + + /** + Markers are a way to describe a point in time by a key name. + + Markers are encoded into animation JSON. By using markers a designer can mark + playback points for a developer to use without having to worry about keeping + track of animation frames. If the animation file is updated, the developer + does not need to update playback code. + + Returns the Progress Time for the marker named. Returns nil if no marker found. + */ + public func progressTime(forMarker named: String) -> AnimationProgressTime? { + guard let animation = animation else { + return nil + } + return animation.progressTime(forMarker: named) + } + + /** + Markers are a way to describe a point in time by a key name. + + Markers are encoded into animation JSON. By using markers a designer can mark + playback points for a developer to use without having to worry about keeping + track of animation frames. If the animation file is updated, the developer + does not need to update playback code. + + Returns the Frame Time for the marker named. Returns nil if no marker found. + */ + public func frameTime(forMarker named: String) -> AnimationFrameTime? { + guard let animation = animation else { + return nil + } + return animation.frameTime(forMarker: named) + } + + // MARK: - Public (Initializers) + + /// Initializes a LottieView with an animation. + public init(animation: Animation?, imageProvider: AnimationImageProvider? = nil, textProvider: AnimationTextProvider = DefaultTextProvider()) { + self.animation = animation + self.imageProvider = imageProvider ?? BundleImageProvider(bundle: Bundle.main, searchPath: nil) + self.textProvider = textProvider + super.init(frame: .zero) + commonInit() + makeAnimationLayer() + if let animation = animation { + frame = animation.bounds + } + } + + public init() { + self.animation = nil + self.imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil) + self.textProvider = DefaultTextProvider() + super.init(frame: .zero) + commonInit() + } + + public override init(frame: CGRect) { + self.animation = nil + self.imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil) + self.textProvider = DefaultTextProvider() + super.init(frame: .zero) + commonInit() + } + + required public init?(coder aDecoder: NSCoder) { + self.imageProvider = BundleImageProvider(bundle: Bundle.main, searchPath: nil) + self.textProvider = DefaultTextProvider() + super.init(coder: aDecoder) + commonInit() + } + + // MARK: - Public (UIView Overrides) + + override public var intrinsicContentSize: CGSize { + if let animation = animation { + return animation.bounds.size + } + return .zero + } + + override func layoutAnimation() { + guard let animation = animation, let animationLayer = animationLayer else { return } + var position = animation.bounds.center + let xform: CATransform3D + var shouldForceUpdates: Bool = false + + if let viewportFrame = self.viewportFrame { + shouldForceUpdates = self.contentMode == .redraw + + let compAspect = viewportFrame.size.width / viewportFrame.size.height + let viewAspect = bounds.size.width / bounds.size.height + let dominantDimension = compAspect > viewAspect ? bounds.size.width : bounds.size.height + let compDimension = compAspect > viewAspect ? viewportFrame.size.width : viewportFrame.size.height + let scale = dominantDimension / compDimension + + let viewportOffset = animation.bounds.center - viewportFrame.center + xform = CATransform3DTranslate(CATransform3DMakeScale(scale, scale, 1), viewportOffset.x, viewportOffset.y, 0) + position = bounds.center + } else { + switch contentMode { + case .scaleToFill: + position = bounds.center + xform = CATransform3DMakeScale(bounds.size.width / animation.size.width, + bounds.size.height / animation.size.height, + 1); + case .scaleAspectFit: + position = bounds.center + let compAspect = animation.size.width / animation.size.height + let viewAspect = bounds.size.width / bounds.size.height + let dominantDimension = compAspect > viewAspect ? bounds.size.width : bounds.size.height + let compDimension = compAspect > viewAspect ? animation.size.width : animation.size.height + let scale = dominantDimension / compDimension + xform = CATransform3DMakeScale(scale, scale, 1) + case .scaleAspectFill: + position = bounds.center + let compAspect = animation.size.width / animation.size.height + let viewAspect = bounds.size.width / bounds.size.height + let scaleWidth = compAspect < viewAspect + let dominantDimension = scaleWidth ? bounds.size.width : bounds.size.height + let compDimension = scaleWidth ? animation.size.width : animation.size.height + let scale = dominantDimension / compDimension + xform = CATransform3DMakeScale(scale, scale, 1) + case .redraw: + shouldForceUpdates = true + xform = CATransform3DIdentity + case .center: + position = bounds.center + xform = CATransform3DIdentity + case .top: + position.x = bounds.center.x + xform = CATransform3DIdentity + case .bottom: + position.x = bounds.center.x + position.y = bounds.maxY - animation.bounds.midY + xform = CATransform3DIdentity + case .left: + position.y = bounds.center.y + xform = CATransform3DIdentity + case .right: + position.y = bounds.center.y + position.x = bounds.maxX - animation.bounds.midX + xform = CATransform3DIdentity + case .topLeft: + xform = CATransform3DIdentity + case .topRight: + position.x = bounds.maxX - animation.bounds.midX + xform = CATransform3DIdentity + case .bottomLeft: + position.y = bounds.maxY - animation.bounds.midY + xform = CATransform3DIdentity + case .bottomRight: + position.x = bounds.maxX - animation.bounds.midX + position.y = bounds.maxY - animation.bounds.midY + xform = CATransform3DIdentity + + #if os(iOS) || os(tvOS) + @unknown default: + print("unsupported contentMode: \(contentMode.rawValue); please update lottie-ios") + xform = CATransform3DIdentity + #endif + } + } + + /* + UIView Animation does not implicitly set CAAnimation time or timing fuctions. + If layout is changed in an animation we must get the current animation duration + and timing function and then manually create a CAAnimation to match the UIView animation. + If layout is changed without animation, explicitly set animation duration to 0.0 + inside CATransaction to avoid unwanted artifacts. + */ + + /// Check if any animation exist on the view's layer, and grab the duration and timing functions of the animation. + if let key = viewLayer?.animationKeys()?.first, let animation = viewLayer?.animation(forKey: key) { + // The layout is happening within an animation block. Grab the animation data. + + let animationKey = "LayoutAnimation" + animationLayer.removeAnimation(forKey: animationKey) + + let positionAnimation = CABasicAnimation(keyPath: "position") + positionAnimation.fromValue = animationLayer.position + positionAnimation.toValue = position + let xformAnimation = CABasicAnimation(keyPath: "transform") + xformAnimation.fromValue = animationLayer.transform + xformAnimation.toValue = xform + + let group = CAAnimationGroup() + group.animations = [positionAnimation, xformAnimation] + group.duration = animation.duration + group.fillMode = .both + group.timingFunction = animation.timingFunction + if animation.beginTime > 0 { + group.beginTime = CACurrentMediaTime() + animation.beginTime + } + group.isRemovedOnCompletion = true + + animationLayer.position = position + animationLayer.transform = xform + animationLayer.add(group, forKey: animationKey) + } else { + CATransaction.begin() + CATransaction.setAnimationDuration(0.0) + CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .linear)) + animationLayer.position = position + animationLayer.transform = xform + CATransaction.commit() + } + + if shouldForceUpdates { + animationLayer.forceDisplayUpdate() + } + } + + // MARK: - Private (Properties) + + + var animationLayer: AnimationContainer? = nil + + fileprivate var animationContext: AnimationContext? + static private let animationName: String = "Lottie" + fileprivate var activeAnimationName: String = AnimationView.animationName + fileprivate var animationID: Int = 0 + + // MARK: - Private (Building Animation View) + + fileprivate func makeAnimationLayer() { + + /// Remove current animation if any + removeCurrentAnimation() + + if let oldAnimation = self.animationLayer { + oldAnimation.removeFromSuperlayer() + } + + invalidateIntrinsicContentSize() + + guard let animation = animation else { + return + } + + let animationLayer = AnimationContainer(animation: animation, imageProvider: imageProvider, textProvider: textProvider) + animationLayer.renderScale = self.screenScale + viewLayer?.addSublayer(animationLayer) + self.animationLayer = animationLayer + reloadImages() + animationLayer.setNeedsDisplay() + setNeedsLayout() + currentFrame = CGFloat(animation.startFrame) + } + + func updateRasterizationState() { + if isAnimationPlaying { + animationLayer?.shouldRasterize = false + } else { + animationLayer?.shouldRasterize = shouldRasterizeWhenIdle + } + } + + // MARK: - Private (Animation Playback) + + /// Updates the animation frame. Does not affect any current animations + func updateAnimationFrame(_ newFrame: CGFloat) { + CATransaction.begin() + CATransaction.setDisableActions(true) + animationLayer?.currentFrame = newFrame + CATransaction.commit() + CATransaction.setCompletionBlock { + self.animationLayer?.forceDisplayUpdate() + } + } + + @objc override func animationWillMoveToBackground() { + updateAnimationForBackgroundState() + } + + @objc override func animationWillEnterForeground() { + updateAnimationForForegroundState() + } + + override func animationMovedToWindow() { + /// Don't update any state if both the `superview` and `window` is `nil` + guard window != nil && superview != nil else { return } + + if window != nil { + updateAnimationForForegroundState() + } else { + updateAnimationForBackgroundState() + } + } + + fileprivate func updateAnimationForBackgroundState() { + if let currentContext = animationContext { + switch backgroundBehavior { + case .stop: + removeCurrentAnimation() + updateAnimationFrame(currentContext.playFrom) + case .pause: + removeCurrentAnimation() + case .pauseAndRestore: + currentContext.closure.ignoreDelegate = true + removeCurrentAnimation() + /// Keep the stale context around for when the app enters the foreground. + self.animationContext = currentContext + case .forceFinish: + removeCurrentAnimation() + updateAnimationFrame(currentContext.playTo) + } + } + } + + fileprivate var waitingToPlayAimation: Bool = false + fileprivate func updateAnimationForForegroundState() { + if let currentContext = animationContext { + if waitingToPlayAimation { + waitingToPlayAimation = false + self.addNewAnimationForContext(currentContext) + } else if backgroundBehavior == .pauseAndRestore { + /// Restore animation from saved state + updateInFlightAnimation() + } + } + } + + /// Stops the current in flight animation and freezes the animation in its current state. + fileprivate func removeCurrentAnimation() { + guard animationContext != nil else { return } + let pauseFrame = realtimeAnimationFrame + animationLayer?.removeAnimation(forKey: activeAnimationName) + updateAnimationFrame(pauseFrame) + self.animationContext = nil + } + + /// Updates an in flight animation. + fileprivate func updateInFlightAnimation() { + guard let animationContext = animationContext else { return } + + guard animationContext.closure.animationState != .complete else { + // Tried to re-add an already completed animation. Cancel. + self.animationContext = nil + return + } + + /// Tell existing context to ignore its closure + animationContext.closure.ignoreDelegate = true + + /// Make a new context, stealing the completion block from the previous. + let newContext = AnimationContext(playFrom: animationContext.playFrom, + playTo: animationContext.playTo, + closure: animationContext.closure.completionBlock) + + /// Remove current animation, and freeze the current frame. + let pauseFrame = realtimeAnimationFrame + animationLayer?.removeAnimation(forKey: activeAnimationName) + animationLayer?.currentFrame = pauseFrame + + addNewAnimationForContext(newContext) + } + + /// Adds animation to animation layer and sets the delegate. If animation layer or animation are nil, exits. + fileprivate func addNewAnimationForContext(_ animationContext: AnimationContext) { + guard let animationlayer = animationLayer, let animation = animation else { + return + } + + self.animationContext = animationContext + + guard self.window != nil else { waitingToPlayAimation = true; return } + + animationID = animationID + 1 + activeAnimationName = AnimationView.animationName + String(animationID) + + /// At this point there is no animation on animationLayer and its state is set. + + let framerate = animation.framerate + + let playFrom = animationContext.playFrom.clamp(animation.startFrame, animation.endFrame) + let playTo = animationContext.playTo.clamp(animation.startFrame, animation.endFrame) + + let duration = ((max(playFrom, playTo) - min(playFrom, playTo)) / CGFloat(framerate)) + + let playingForward: Bool = + ((animationSpeed > 0 && playFrom < playTo) || + (animationSpeed < 0 && playTo < playFrom)) + + var startFrame = currentFrame.clamp(min(playFrom, playTo), max(playFrom, playTo)) + if startFrame == playTo { + startFrame = playFrom + } + + let timeOffset: TimeInterval = playingForward ? + Double(startFrame - min(playFrom, playTo)) / framerate : + Double(max(playFrom, playTo) - startFrame) / framerate + + let layerAnimation = CABasicAnimation(keyPath: "currentFrame") + layerAnimation.fromValue = playFrom + layerAnimation.toValue = playTo + layerAnimation.speed = Float(animationSpeed) + layerAnimation.duration = TimeInterval(duration) + layerAnimation.fillMode = CAMediaTimingFillMode.both + + switch loopMode { + case .playOnce: + layerAnimation.repeatCount = 1 + case .loop: + layerAnimation.repeatCount = HUGE + case .autoReverse: + layerAnimation.repeatCount = HUGE + layerAnimation.autoreverses = true + case let .repeat(amount): + layerAnimation.repeatCount = amount + case let .repeatBackwards(amount): + layerAnimation.repeatCount = amount + layerAnimation.autoreverses = true + } + + layerAnimation.isRemovedOnCompletion = false + if timeOffset != 0 { + let currentLayerTime = viewLayer?.convertTime(CACurrentMediaTime(), from: nil) ?? 0 + layerAnimation.beginTime = currentLayerTime - (timeOffset * 1 / Double(abs(animationSpeed))) + } + layerAnimation.delegate = animationContext.closure + animationContext.closure.animationLayer = animationlayer + animationContext.closure.animationKey = activeAnimationName + + animationlayer.add(layerAnimation, forKey: activeAnimationName) + updateRasterizationState() + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/Animation/AnimationViewInitializers.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/Animation/AnimationViewInitializers.swift new file mode 100644 index 0000000..5277f3f --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/Animation/AnimationViewInitializers.swift @@ -0,0 +1,83 @@ +// +// AnimationViewInitializers.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/6/19. +// + +import Foundation + +public extension AnimationView { + + /** + Loads a Lottie animation from a JSON file in the supplied bundle. + + - Parameter name: The string name of the lottie animation with no file + extension provided. + - Parameter bundle: The bundle in which the animation is located. + Defaults to the Main bundle. + - Parameter imageProvider: An image provider for the animation's image data. + If none is supplied Lottie will search in the supplied bundle for images. + */ + convenience init(name: String, + bundle: Bundle = Bundle.main, + imageProvider: AnimationImageProvider? = nil, + animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) { + let animation = Animation.named(name, bundle: bundle, subdirectory: nil, animationCache: animationCache) + let provider = imageProvider ?? BundleImageProvider(bundle: bundle, searchPath: nil) + self.init(animation: animation, imageProvider: provider) + } + + /** + Loads a Lottie animation from a JSON file in a specific path on disk. + + - Parameter name: The absolute path of the Lottie Animation. + - Parameter imageProvider: An image provider for the animation's image data. + If none is supplied Lottie will search in the supplied filepath for images. + */ + convenience init(filePath: String, + imageProvider: AnimationImageProvider? = nil, + animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) { + let animation = Animation.filepath(filePath, animationCache: animationCache) + let provider = imageProvider ?? FilepathImageProvider(filepath: URL(fileURLWithPath: filePath).deletingLastPathComponent().path) + self.init(animation: animation, imageProvider: provider) + } + + /** + Loads a Lottie animation asynchronously from the URL + + - Parameter url: The url to load the animation from. + - Parameter imageProvider: An image provider for the animation's image data. + If none is supplied Lottie will search in the main bundle for images. + - Parameter closure: A closure to be called when the animation has loaded. + */ + convenience init(url: URL, + imageProvider: AnimationImageProvider? = nil, + closure: @escaping AnimationView.DownloadClosure, + animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) { + + if let animationCache = animationCache, let animation = animationCache.animation(forKey: url.absoluteString) { + self.init(animation: animation, imageProvider: imageProvider) + closure(nil) + } else { + + self.init(animation: nil, imageProvider: imageProvider) + + Animation.loadedFrom(url: url, closure: { (animation) in + if let animation = animation { + self.animation = animation + closure(nil) + } else { + closure(LottieDownloadError.downloadFailed) + } + }, animationCache: animationCache) + } + } + + typealias DownloadClosure = (Error?) -> Void + +} + +enum LottieDownloadError: Error { + case downloadFailed +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/AnimationCache/AnimationCacheProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/AnimationCache/AnimationCacheProvider.swift new file mode 100644 index 0000000..23c4119 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/AnimationCache/AnimationCacheProvider.swift @@ -0,0 +1,24 @@ +// +// AnimationCacheProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/5/19. +// + +import Foundation +/** + `AnimationCacheProvider` is a protocol that describes an Animation Cache. + Animation Cache is used when loading `Animation` models. Using an Animation Cache + can increase performance when loading an animation multiple times. + + Lottie comes with a prebuilt LRU Animation Cache. + */ +public protocol AnimationCacheProvider { + + func animation(forKey: String) -> Animation? + + func setAnimation(_ animation: Animation, forKey: String) + + func clearCache() + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/AnimationCache/LRUAnimationCache.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/AnimationCache/LRUAnimationCache.swift new file mode 100644 index 0000000..e63867d --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/AnimationCache/LRUAnimationCache.swift @@ -0,0 +1,54 @@ +// +// LRUAnimationCache.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/5/19. +// + +import Foundation + +/** + An Animation Cache that will store animations up to `cacheSize`. + + Once `cacheSize` is reached, the least recently used animation will be ejected. + The default size of the cache is 100. + */ +public class LRUAnimationCache: AnimationCacheProvider { + + public init() { } + + /// Clears the Cache. + public func clearCache() { + cacheMap.removeAll() + lruList.removeAll() + } + + /// The global shared Cache. + public static let sharedCache = LRUAnimationCache() + + /// The size of the cache. + public var cacheSize: Int = 100 + + public func animation(forKey: String) -> Animation? { + guard let animation = cacheMap[forKey] else { + return nil + } + if let index = lruList.firstIndex(of: forKey) { + lruList.remove(at: index) + lruList.append(forKey) + } + return animation + } + + public func setAnimation(_ animation: Animation, forKey: String) { + cacheMap[forKey] = animation + lruList.append(forKey) + if lruList.count > cacheSize { + lruList.remove(at: 0) + } + } + + fileprivate var cacheMap: [String : Animation] = [:] + fileprivate var lruList: [String] = [] + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/AnimationKeypath.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/AnimationKeypath.swift new file mode 100644 index 0000000..e9ece1b --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/AnimationKeypath.swift @@ -0,0 +1,46 @@ +// +// AnimationKeypath.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation + +/** + `AnimationKeypath` is an object that describes a keypath search for nodes in the + animation JSON. `AnimationKeypath` matches views and properties inside of `AnimationView` + to their backing `Animation` model by name. + + A keypath can be used to set properties on an existing animation, or can be validated + with an existing `Animation`. + + `AnimationKeypath` can describe a specific object, or can use wildcards for fuzzy matching + of objects. Acceptable wildcards are either "*" (star) or "**" (double star). + Single star will search a single depth for the next object. + Double star will search any depth. + + Read More at https://airbnb.io/lottie/#/ios?id=dynamic-animation-properties + + EG: + @"Layer.Shape Group.Stroke 1.Color" + Represents a specific color node on a specific stroke. + + @"**.Stroke 1.Color" + Represents the color node for every Stroke named "Stroke 1" in the animation. + */ +public struct AnimationKeypath { + + /// Creates a keypath from a dot separated string. The string is separated by "." + public init(keypath: String) { + self.keys = keypath.components(separatedBy: ".") + } + + /// Creates a keypath from a list of strings. + public init(keys: [String]) { + self.keys = keys + } + + let keys: [String] + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/AnyValueProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/AnyValueProvider.swift new file mode 100644 index 0000000..1684cd9 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/AnyValueProvider.swift @@ -0,0 +1,29 @@ +// +// AnyValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/30/19. +// + +import Foundation +import CoreGraphics + +/** + `AnyValueProvider` is a protocol that return animation data for a property at a + given time. Every fame an `AnimationView` queries all of its properties and asks + if their ValueProvider has an update. If it does the AnimationView will read the + property and update that portion of the animation. + + Value Providers can be used to dynamically set animation properties at run time. + */ +public protocol AnyValueProvider { + + /// The Type of the value provider + var valueType: Any.Type { get } + + /// Asks the provider if it has an update for the given frame. + func hasUpdate(frame: AnimationFrameTime) -> Bool + + /// Asks the provider to update the container with its value for the frame. + func value(frame: AnimationFrameTime) -> Any +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift new file mode 100644 index 0000000..4d594d2 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift @@ -0,0 +1,66 @@ +// +// ColorValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +import CoreGraphics + +/// A `ValueProvider` that returns a CGColor Value +public final class ColorValueProvider: AnyValueProvider { + + /// Returns a Color for a CGColor(Frame Time) + public typealias ColorValueBlock = (CGFloat) -> Color + + /// The color value of the provider. + public var color: Color { + didSet { + hasUpdate = true + } + } + + /// Initializes with a block provider + public init(block: @escaping ColorValueBlock) { + self.block = block + self.color = Color(r: 0, g: 0, b: 0, a: 1) + } + + /// Initializes with a single color. + public init(_ color: Color) { + self.color = color + self.block = nil + hasUpdate = true + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + return Color.self + } + + public func hasUpdate(frame: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + public func value(frame: CGFloat) -> Any { + hasUpdate = false + let newColor: Color + if let block = block { + newColor = block(frame) + } else { + newColor = color + } + return newColor + } + + // MARK: Private + + private var hasUpdate: Bool = true + + private var block: ColorValueBlock? +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift new file mode 100644 index 0000000..ecd43e1 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift @@ -0,0 +1,66 @@ +// +// DoubleValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +import CoreGraphics + +/// A `ValueProvider` that returns a CGFloat Value +public final class FloatValueProvider: AnyValueProvider { + + /// Returns a CGFloat for a CGFloat(Frame Time) + public typealias CGFloatValueBlock = (CGFloat) -> CGFloat + + public var float: CGFloat { + didSet { + hasUpdate = true + } + } + + /// Initializes with a block provider + public init(block: @escaping CGFloatValueBlock) { + self.block = block + self.float = 0 + } + + /// Initializes with a single float. + public init(_ float: CGFloat) { + self.float = float + self.block = nil + hasUpdate = true + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + return Vector1D.self + } + + public func hasUpdate(frame: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + public func value(frame: CGFloat) -> Any { + hasUpdate = false + let newCGFloat: CGFloat + if let block = block { + newCGFloat = block(frame) + } else { + newCGFloat = float + } + return Vector1D(Double(newCGFloat)) + } + + // MARK: Private + + private var hasUpdate: Bool = true + + private var block: CGFloatValueBlock? +} + diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift new file mode 100644 index 0000000..d19bfc4 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift @@ -0,0 +1,114 @@ +// +// GradientValueProvider.swift +// lottie-swift +// +// Created by Enrique Bermúdez on 10/27/19. +// + +import Foundation +import CoreGraphics + +/// A `ValueProvider` that returns a Gradient Color Value. +public final class GradientValueProvider: AnyValueProvider { + + /// Returns a [Color] for a CGFloat(Frame Time). + public typealias ColorsValueBlock = (CGFloat) -> [Color] + /// Returns a [Double](Color locations) for a CGFloat(Frame Time). + public typealias ColorLocationsBlock = (CGFloat) -> [Double] + + /// The colors values of the provider. + public var colors: [Color] { + didSet { + updateValueArray() + hasUpdate = true + } + } + + /// The color location values of the provider. + public var locations: [Double] { + didSet { + updateValueArray() + hasUpdate = true + } + } + + /// Initializes with a block provider. + public init(block: @escaping ColorsValueBlock, + locations: ColorLocationsBlock? = nil) { + self.block = block + self.locationsBlock = locations + self.colors = [] + self.locations = [] + } + + /// Initializes with an array of colors. + public init(_ colors: [Color], + locations: [Double] = []) { + self.colors = colors + self.locations = locations + updateValueArray() + hasUpdate = true + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + return [Double].self + } + + public func hasUpdate(frame: CGFloat) -> Bool { + if block != nil || locationsBlock != nil { + return true + } + return hasUpdate + } + + public func value(frame: CGFloat) -> Any { + hasUpdate = false + + if let block = block { + let newColors = block(frame) + let newLocations = locationsBlock?(frame) ?? [] + value = value(from: newColors, locations: newLocations) + } + + return value + } + + // MARK: Private + + private func value(from colors: [Color], locations: [Double]) -> [Double] { + + var colorValues = [Double]() + var alphaValues = [Double]() + var shouldAddAlphaValues = false + + for i in 0.. i ? locations[i] : + (Double(i) / Double(colors.count - 1)) + + colorValues.append(location) + colorValues.append(colors[i].r) + colorValues.append(colors[i].g) + colorValues.append(colors[i].b) + + alphaValues.append(location) + alphaValues.append(colors[i].a) + } + + return colorValues + (shouldAddAlphaValues ? alphaValues : []) + } + + private func updateValueArray() { + value = value(from: colors, locations: locations) + } + + private var hasUpdate: Bool = true + + private var block: ColorsValueBlock? + private var locationsBlock: ColorLocationsBlock? + private var value: [Double] = [] +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/PointValueProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/PointValueProvider.swift new file mode 100644 index 0000000..83579fb --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/PointValueProvider.swift @@ -0,0 +1,64 @@ +// +// PointValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +import CoreGraphics +/// A `ValueProvider` that returns a CGPoint Value +public final class PointValueProvider: AnyValueProvider { + + /// Returns a CGPoint for a CGFloat(Frame Time) + public typealias PointValueBlock = (CGFloat) -> CGPoint + + public var point: CGPoint { + didSet { + hasUpdate = true + } + } + + /// Initializes with a block provider + public init(block: @escaping PointValueBlock) { + self.block = block + self.point = .zero + } + + /// Initializes with a single point. + public init(_ point: CGPoint) { + self.point = point + self.block = nil + hasUpdate = true + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + return Vector3D.self + } + + public func hasUpdate(frame: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + public func value(frame: CGFloat) -> Any { + hasUpdate = false + let newPoint: CGPoint + if let block = block { + newPoint = block(frame) + } else { + newPoint = point + } + return newPoint.vector3dValue + } + + // MARK: Private + + private var hasUpdate: Bool = true + + private var block: PointValueBlock? +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift new file mode 100644 index 0000000..4e89320 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift @@ -0,0 +1,65 @@ +// +// SizeValueProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +import CoreGraphics + +/// A `ValueProvider` that returns a CGSize Value +public final class SizeValueProvider: AnyValueProvider { + + /// Returns a CGSize for a CGFloat(Frame Time) + public typealias SizeValueBlock = (CGFloat) -> CGSize + + public var size: CGSize { + didSet { + hasUpdate = true + } + } + + /// Initializes with a block provider + public init(block: @escaping SizeValueBlock) { + self.block = block + self.size = .zero + } + + /// Initializes with a single size. + public init(_ size: CGSize) { + self.size = size + self.block = nil + hasUpdate = true + } + + // MARK: ValueProvider Protocol + + public var valueType: Any.Type { + return Vector3D.self + } + + public func hasUpdate(frame: CGFloat) -> Bool { + if block != nil { + return true + } + return hasUpdate + } + + public func value(frame: CGFloat) -> Any { + hasUpdate = false + let newSize: CGSize + if let block = block { + newSize = block(frame) + } else { + newSize = size + } + return newSize.vector3dValue + } + + // MARK: Private + + private var hasUpdate: Bool = true + + private var block: SizeValueBlock? +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/ImageProvider/AnimationImageProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/ImageProvider/AnimationImageProvider.swift new file mode 100644 index 0000000..78cf5f3 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/ImageProvider/AnimationImageProvider.swift @@ -0,0 +1,23 @@ +// +// LottieImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import CoreGraphics + +/** + Image provider is a protocol that is used to supply images to `AnimationView`. + + Some animations require a reference to an image. The image provider loads and + provides those images to the `AnimationView`. Lottie includes a couple of + prebuilt Image Providers that supply images from a Bundle, or from a FilePath. + + Additionally custom Image Providers can be made to load images from a URL, + or to Cache images. + */ +public protocol AnimationImageProvider { + func imageForAsset(asset: ImageAsset) -> CGImage? +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/Primitives/AnimationTime.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/Primitives/AnimationTime.swift new file mode 100644 index 0000000..b765c50 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/Primitives/AnimationTime.swift @@ -0,0 +1,15 @@ +// +// AnimationTime.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/6/19. +// + +import Foundation +import CoreGraphics + +/// Defines animation time in Frames (Seconds * Framerate). +public typealias AnimationFrameTime = CGFloat + +/// Defines animation time by a progress from 0 (beginning of the animation) to 1 (end of the animation) +public typealias AnimationProgressTime = CGFloat diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/Primitives/Color.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/Primitives/Color.swift new file mode 100644 index 0000000..4550f4c --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/Primitives/Color.swift @@ -0,0 +1,41 @@ +// +// Color.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation + +public enum ColorFormatDenominator { + case One + case OneHundred + case TwoFiftyFive + + var value: Double { + switch self { + case .One: + return 1.0 + case .OneHundred: + return 100.0 + case .TwoFiftyFive: + return 255.0 + } + } +} + +public struct Color { + + public var r: Double + public var g: Double + public var b: Double + public var a: Double + + public init(r: Double, g: Double, b: Double, a: Double, denominator: ColorFormatDenominator = .One) { + self.r = r / denominator.value + self.g = g / denominator.value + self.b = b / denominator.value + self.a = a / denominator.value + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/Primitives/Vectors.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/Primitives/Vectors.swift new file mode 100644 index 0000000..a2b835a --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/Primitives/Vectors.swift @@ -0,0 +1,37 @@ +// +// Vectors.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation + +public struct Vector1D { + + public init(_ value: Double) { + self.value = value + } + + let value: Double + +} + + +/** + A three dimensional vector. + These vectors are encoded and decoded from [Double] + */ +public struct Vector3D { + + var x: Double + var y: Double + var z: Double + + public init(x: Double, y: Double, z: Double) { + self.x = x + self.y = y + self.z = z + } + +} diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/TextProvider/AnimationTextProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/TextProvider/AnimationTextProvider.swift new file mode 100644 index 0000000..3e4e8d9 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/TextProvider/AnimationTextProvider.swift @@ -0,0 +1,39 @@ +// +// AnimationImageProvider.swift +// Lottie_iOS +// +// Created by Alexandr Goncharov on 07/06/2019. +// + +import Foundation + +/** + Text provider is a protocol that is used to supply text to `AnimationView`. + */ +public protocol AnimationTextProvider: AnyObject { + func textFor(keypathName: String, sourceText: String) -> String +} + +/// Text provider that simply map values from dictionary +public final class DictionaryTextProvider: AnimationTextProvider { + + public init(_ values: [String: String]) { + self.values = values + } + + let values: [String: String] + + public func textFor(keypathName: String, sourceText: String) -> String { + return values[keypathName] ?? sourceText + } +} + +/// Default text provider. Uses text in the animation file +public final class DefaultTextProvider: AnimationTextProvider { + public func textFor(keypathName: String, sourceText: String) -> String { + return sourceText + } + + public init() {} +} + diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimatedButton.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimatedButton.swift new file mode 100644 index 0000000..51f4c53 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimatedButton.swift @@ -0,0 +1,68 @@ +// +// AnimatedButton.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit +/** + An interactive button that plays an animation when pressed. + */ +final public class AnimatedButton: AnimatedControl { + + /// Sets the play range for the given UIControlEvent. + public func setPlayRange(fromProgress: AnimationProgressTime, toProgress: AnimationProgressTime, event: UIControl.Event) { + rangesForEvents[event.rawValue] = (from: fromProgress, to: toProgress) + } + + /// Sets the play range for the given UIControlEvent. + public func setPlayRange(fromMarker fromName: String, toMarker toName: String, event: UIControl.Event) { + if let start = animationView.progressTime(forMarker: fromName), + let end = animationView.progressTime(forMarker: toName) { + rangesForEvents[event.rawValue] = (from: start, to: end) + } + } + + public override init(animation: Animation) { + super.init(animation: animation) + self.accessibilityTraits = UIAccessibilityTraits.button + } + + public override init() { + super.init() + self.accessibilityTraits = UIAccessibilityTraits.button + } + + fileprivate var rangesForEvents: [UInt : (from: CGFloat, to: CGFloat)] = [UIControl.Event.touchUpInside.rawValue : (from: 0, to: 1)] + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + let _ = super.beginTracking(touch, with: event) + let touchEvent = UIControl.Event.touchDown + if let playrange = rangesForEvents[touchEvent.rawValue] { + animationView.play(fromProgress: playrange.from, toProgress: playrange.to, loopMode: LottieLoopMode.playOnce) + } + return true + } + + public override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + super.endTracking(touch, with: event) + let touchEvent: UIControl.Event + if let touch = touch, bounds.contains(touch.location(in: self)) { + touchEvent = UIControl.Event.touchUpInside + } else { + touchEvent = UIControl.Event.touchUpOutside + } + + if let playrange = rangesForEvents[touchEvent.rawValue] { + animationView.play(fromProgress: playrange.from, toProgress: playrange.to, loopMode: LottieLoopMode.playOnce) + } + } +} +#endif diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimatedControl.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimatedControl.swift new file mode 100644 index 0000000..cf9a700 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimatedControl.swift @@ -0,0 +1,164 @@ +// +// AnimatedControl.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +/** + Lottie comes prepacked with a two Animated Controls, `AnimatedSwitch` and + `AnimatedButton`. Both of these controls are built on top of `AnimatedControl` + + `AnimatedControl` is a subclass of `UIControl` that provides an interactive + mechanism for controlling the visual state of an animation in response to + user actions. + + The `AnimatedControl` will show and hide layers depending on the current + `UIControl.State` of the control. + + Users of `AnimationControl` can set a Layer Name for each `UIControl.State`. + When the state is change the `AnimationControl` will change the visibility + of its layers. + + NOTE: Do not initialize directly. This is intended to be subclassed. + */ +open class AnimatedControl: UIControl { + + // MARK: Public + + /// The animation backing the animated control. + public var animation: Animation? { + didSet { + animationView.animation = animation + animationView.bounds = animation?.bounds ?? .zero + setNeedsLayout() + updateForState() + animationDidSet() + } + } + + /// The speed of the animation playback. Defaults to 1 + public var animationSpeed: CGFloat { + set { animationView.animationSpeed = newValue } + get { return animationView.animationSpeed } + } + + /// Sets which Animation Layer should be visible for the given state. + public func setLayer(named: String, forState: UIControl.State) { + stateMap[forState.rawValue] = named + updateForState() + } + + /// Sets a ValueProvider for the specified keypath + public func setValueProvider(_ valueProvider: AnyValueProvider, keypath: AnimationKeypath) { + animationView.setValueProvider(valueProvider, keypath: keypath) + } + + // MARK: Initializers + + public init(animation: Animation) { + self.animationView = AnimationView(animation: animation) + super.init(frame: animation.bounds) + commonInit() + } + + public init() { + self.animationView = AnimationView() + super.init(frame: .zero) + commonInit() + } + + required public init?(coder aDecoder: NSCoder) { + self.animationView = AnimationView() + super.init(coder: aDecoder) + commonInit() + } + + // MARK: UIControl Overrides + + open override var isEnabled: Bool { + didSet { + updateForState() + } + } + + open override var isSelected: Bool { + didSet { + updateForState() + } + } + + open override var isHighlighted: Bool { + didSet { + updateForState() + } + } + + open override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + updateForState() + return super.beginTracking(touch, with: event) + } + + open override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + updateForState() + return super.continueTracking(touch, with: event) + } + + open override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + updateForState() + return super.endTracking(touch, with: event) + } + + open override func cancelTracking(with event: UIEvent?) { + updateForState() + super.cancelTracking(with: event) + } + + open override var intrinsicContentSize: CGSize { + return animationView.intrinsicContentSize + } + + open func animationDidSet() { + + } + + // MARK: Private + + let animationView: AnimationView + var stateMap: [UInt : String] = [:] + + fileprivate func commonInit() { + animationView.clipsToBounds = false + clipsToBounds = true + animationView.translatesAutoresizingMaskIntoConstraints = false + animationView.backgroundBehavior = .forceFinish + addSubview(animationView) + animationView.contentMode = .scaleAspectFit + animationView.isUserInteractionEnabled = false + animationView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + animationView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + animationView.topAnchor.constraint(equalTo: topAnchor).isActive = true + animationView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } + + func updateForState() { + guard let animationLayer = animationView.animationLayer else { return } + if let layerName = stateMap[state.rawValue], + let stateLayer = animationLayer.layer(for: AnimationKeypath(keypath: layerName)) { + for layer in animationLayer.animationLayers { + layer.isHidden = true + } + stateLayer.isHidden = false + } else { + for layer in animationLayer.animationLayers { + layer.isHidden = false + } + } + } + +} +#endif diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimatedSwitch.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimatedSwitch.swift new file mode 100644 index 0000000..4d0e062 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimatedSwitch.swift @@ -0,0 +1,194 @@ +// +// AnimatedSwitch.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +/** + An interactive switch with an 'On' and 'Off' state. When the user taps on the + switch the state is toggled and the appropriate animation is played. + + Both the 'On' and 'Off' have an animation play range associated with their state. + */ +final public class AnimatedSwitch: AnimatedControl { + + /// Defines what happens when the user taps the switch while an + /// animation is still in flight + public enum CancelBehavior { + case reverse // default - plays the current animation in reverse + case none // does not update the animation when canceled + } + + /// The current state of the switch. + public var isOn: Bool { + set { + /// This is forwarded to a private variable because the animation needs to be updated without animation when set externally and with animation when set internally. + guard _isOn != newValue else { return } + updateOnState(isOn: newValue, animated: false, shouldFireHaptics: false) + } + get { + return _isOn + } + } + + /// The cancel behavior for the switch. See CancelBehavior for options + public var cancelBehavior: CancelBehavior = .reverse + + /// Set the state of the switch and specify animation and haptics + public func setIsOn(_ isOn: Bool, animated: Bool, shouldFireHaptics: Bool = true) { + guard isOn != _isOn else { return } + updateOnState(isOn: isOn, animated: animated, shouldFireHaptics: shouldFireHaptics) + } + + /// Sets the play range for the given state. When the switch is toggled, the animation range is played. + public func setProgressForState(fromProgress: AnimationProgressTime, + toProgress: AnimationProgressTime, + forOnState: Bool) { + if forOnState { + onStartProgress = fromProgress + onEndProgress = toProgress + } else { + offStartProgress = fromProgress + offEndProgress = toProgress + } + + updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false) + } + + public override init(animation: Animation) { + /// Generate a haptic generator if available. + #if os(iOS) + if #available(iOS 10.0, *) { + self.hapticGenerator = HapticGenerator() + } else { + self.hapticGenerator = NullHapticGenerator() + } + #else + self.hapticGenerator = NullHapticGenerator() + #endif + super.init(animation: animation) + updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false) + self.accessibilityTraits = UIAccessibilityTraits.button + } + + public override init() { + /// Generate a haptic generator if available. + #if os(iOS) + if #available(iOS 10.0, *) { + self.hapticGenerator = HapticGenerator() + } else { + self.hapticGenerator = NullHapticGenerator() + } + #else + self.hapticGenerator = NullHapticGenerator() + #endif + super.init() + updateOnState(isOn: _isOn, animated: false, shouldFireHaptics: false) + self.accessibilityTraits = UIAccessibilityTraits.button + } + + required public init?(coder aDecoder: NSCoder) { + /// Generate a haptic generator if available. + #if os(iOS) + if #available(iOS 10.0, *) { + self.hapticGenerator = HapticGenerator() + } else { + self.hapticGenerator = NullHapticGenerator() + } + #else + self.hapticGenerator = NullHapticGenerator() + #endif + super.init(coder: aDecoder) + self.accessibilityTraits = UIAccessibilityTraits.button + } + + fileprivate var onStartProgress: CGFloat = 0 + fileprivate var onEndProgress: CGFloat = 1 + fileprivate var offStartProgress: CGFloat = 1 + fileprivate var offEndProgress: CGFloat = 0 + fileprivate var _isOn: Bool = false + fileprivate var hapticGenerator: ImpactGenerator + + // MARK: Animation State + + func updateOnState(isOn: Bool, animated: Bool, shouldFireHaptics: Bool) { + _isOn = isOn + var startProgress = isOn ? onStartProgress : offStartProgress + var endProgress = isOn ? onEndProgress : offEndProgress + let finalProgress = endProgress + + if cancelBehavior == .reverse { + let realtimeProgress = animationView.realtimeAnimationProgress + + let previousStateStart = isOn ? offStartProgress : onStartProgress + let previousStateEnd = isOn ? offEndProgress : onEndProgress + if realtimeProgress.isInRange(min(previousStateStart, previousStateEnd), + max(previousStateStart, previousStateEnd)) { + /// Animation is currently in the previous time range. Reverse the previous play. + startProgress = previousStateEnd + endProgress = previousStateStart + } + } + + updateAccessibilityLabel() + + guard animated == true else { + animationView.currentProgress = finalProgress + return + } + + if shouldFireHaptics { + self.hapticGenerator.generateImpact() + } + + animationView.play(fromProgress: startProgress, toProgress: endProgress, loopMode: LottieLoopMode.playOnce) { (finished) in + if finished == true { + self.animationView.currentProgress = finalProgress + } + } + } + + public override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + super.endTracking(touch, with: event) + updateOnState(isOn: !_isOn, animated: true, shouldFireHaptics: true) + sendActions(for: .valueChanged) + } + + public override func animationDidSet() { + updateOnState(isOn: _isOn, animated: true, shouldFireHaptics: false) + } + + // MARK: Private + + private func updateAccessibilityLabel() { + accessibilityValue = _isOn ? NSLocalizedString("On", comment: "On") : NSLocalizedString("Off", comment: "Off") + } + +} +#endif + +protocol ImpactGenerator { + func generateImpact() +} + +class NullHapticGenerator: ImpactGenerator { + func generateImpact() { + + } +} + +#if os(iOS) +@available(iOS 10.0, *) +class HapticGenerator: ImpactGenerator { + func generateImpact() { + impact.impactOccurred() + } + + fileprivate let impact = UIImpactFeedbackGenerator(style: .light) +} +#endif diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimationSubview.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimationSubview.swift new file mode 100644 index 0000000..6321041 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/AnimationSubview.swift @@ -0,0 +1,20 @@ +// +// AnimationSubview.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +/// A view that can be added to a keypath of an AnimationView +public final class AnimationSubview: UIView { + + var viewLayer: CALayer? { + return layer + } + +} +#endif diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/BundleImageProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/BundleImageProvider.swift new file mode 100644 index 0000000..7064e42 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/BundleImageProvider.swift @@ -0,0 +1,80 @@ +// +// LottieBundleImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 1/25/19. +// + +import Foundation +import CoreGraphics +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +/** + An `AnimationImageProvider` that provides images by name from a specific bundle. + The BundleImageProvider is initialized with a bundle and an optional searchPath. + */ +public class BundleImageProvider: AnimationImageProvider { + + let bundle: Bundle + let searchPath: String? + + /** + Initializes an image provider with a bundle and an optional subpath. + + Provides images for an animation from a bundle. Additionally the provider can + search a specific subpath for the images. + + - Parameter bundle: The bundle containing images for the provider. + - Parameter searchPath: The subpath is a path within the bundle to search for image assets. + + */ + public init(bundle: Bundle, searchPath: String?) { + self.bundle = bundle + self.searchPath = searchPath + } + + public func imageForAsset(asset: ImageAsset) -> CGImage? { + + if asset.name.hasPrefix("data:"), + let url = URL(string: asset.name), + let data = try? Data(contentsOf: url), + let image = UIImage(data: data) { + return image.cgImage + } + + let imagePath: String? + /// Try to find the image in the bundle. + if let searchPath = searchPath { + /// Search in the provided search path for the image + var directoryPath = URL(fileURLWithPath: searchPath) + directoryPath.appendPathComponent(asset.directory) + + if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: directoryPath.path) { + /// First search for the image in the asset provided sub directory. + imagePath = path + } else if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: searchPath) { + /// Try finding the image in the search path. + imagePath = path + } else { + imagePath = bundle.path(forResource: asset.name, ofType: nil) + } + } else { + if let path = bundle.path(forResource: asset.name, ofType: nil, inDirectory: asset.directory) { + /// First search for the image in the asset provided sub directory. + imagePath = path + } else { + /// First search for the image in bundle. + imagePath = bundle.path(forResource: asset.name, ofType: nil) + } + } + + guard let foundPath = imagePath, let image = UIImage(contentsOfFile: foundPath) else { + /// No image found. + return nil + } + return image.cgImage + } + +} +#endif diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift new file mode 100644 index 0000000..1b63d14 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/Compatibility/CompatibleAnimationKeypath.swift @@ -0,0 +1,29 @@ +// +// CompatibleAnimationKeypath.swift +// Lottie_iOS +// +// Created by Tyler Hedrick on 3/6/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) + +/// An Objective-C compatible wrapper around Lottie's AnimationKeypath +@objc +public final class CompatibleAnimationKeypath: NSObject { + + /// Creates a keypath from a dot separated string. The string is separated by "." + @objc + public init(keypath: String) { + animationKeypath = AnimationKeypath(keypath: keypath) + } + + /// Creates a keypath from a list of strings. + @objc + public init(keys: [String]) { + animationKeypath = AnimationKeypath(keys: keys) + } + + public let animationKeypath: AnimationKeypath +} +#endif diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/Compatibility/CompatibleAnimationView.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/Compatibility/CompatibleAnimationView.swift new file mode 100644 index 0000000..0123213 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/Compatibility/CompatibleAnimationView.swift @@ -0,0 +1,309 @@ +// +// CompatibleAnimationView.swift +// Lottie_iOS +// +// Created by Tyler Hedrick on 3/6/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +/// An Objective-C compatible wrapper around Lottie's Animation class. +/// Use in tandem with CompatibleAnimationView when using Lottie in Objective-C +@objc +public final class CompatibleAnimation: NSObject { + + @objc + static func named(_ name: String) -> CompatibleAnimation { + return CompatibleAnimation(name: name) + } + + @objc + public init(name: String, bundle: Bundle = Bundle.main) { + self.name = name + self.bundle = bundle + super.init() + } + + internal var animation: Animation? { + return Animation.named(name, bundle: bundle) + } + + // MARK: Private + + private let name: String + private let bundle: Bundle +} + +/// An Objective-C compatible wrapper around Lottie's AnimationView. +@objc +public final class CompatibleAnimationView: UIView { + + @objc + init(compatibleAnimation: CompatibleAnimation) { + animationView = AnimationView(animation: compatibleAnimation.animation) + self.compatibleAnimation = compatibleAnimation + super.init(frame: .zero) + commonInit() + } + + @objc + public override init(frame: CGRect) { + animationView = AnimationView() + super.init(frame: frame) + commonInit() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Public + + @objc + public var compatibleAnimation: CompatibleAnimation? { + didSet { + animationView.animation = compatibleAnimation?.animation + } + } + + @objc + public var loopAnimationCount: CGFloat = 0 { + didSet { + animationView.loopMode = loopAnimationCount == -1 ? .loop : .repeat(Float(loopAnimationCount)) + } + } + + @objc + public override var contentMode: UIView.ContentMode { + set { animationView.contentMode = newValue } + get { return animationView.contentMode } + } + + @objc + public var shouldRasterizeWhenIdle: Bool { + set { animationView.shouldRasterizeWhenIdle = newValue } + get { return animationView.shouldRasterizeWhenIdle } + } + + @objc + public var currentProgress: CGFloat { + set { animationView.currentProgress = newValue } + get { return animationView.currentProgress } + } + + @objc + public var currentTime: TimeInterval { + set { animationView.currentTime = newValue } + get { return animationView.currentTime } + } + + @objc + public var currentFrame: CGFloat { + set { animationView.currentFrame = newValue } + get { return animationView.currentFrame } + } + + @objc + public var realtimeAnimationFrame: CGFloat { + return animationView.realtimeAnimationFrame + } + + @objc + public var realtimeAnimationProgress: CGFloat { + return animationView.realtimeAnimationProgress + } + + @objc + public var animationSpeed: CGFloat { + set { animationView.animationSpeed = newValue } + get { return animationView.animationSpeed } + } + + @objc + public var respectAnimationFrameRate: Bool { + set { animationView.respectAnimationFrameRate = newValue } + get { return animationView.respectAnimationFrameRate } + } + + @objc + public func play() { + play(completion: nil) + } + + @objc + public func play(completion: ((Bool) -> Void)?) { + animationView.play(completion: completion) + } + + @objc + public func play( + fromProgress: CGFloat, + toProgress: CGFloat, + completion: ((Bool) -> Void)? = nil) + { + animationView.play( + fromProgress: fromProgress, + toProgress: toProgress, + loopMode: nil, + completion: completion) + } + + @objc + public func play( + fromFrame: CGFloat, + toFrame: CGFloat, + completion: ((Bool) -> Void)? = nil) + { + animationView.play( + fromFrame: fromFrame, + toFrame: toFrame, + loopMode: nil, + completion: completion) + } + + @objc + public func play( + fromMarker: String, + toMarker: String, + completion: ((Bool) -> Void)? = nil) + { + animationView.play( + fromMarker: fromMarker, + toMarker: toMarker, + completion: completion) + } + + @objc + public func stop() { + animationView.stop() + } + + @objc + public func pause() { + animationView.pause() + } + + @objc + public func reloadImages() { + animationView.reloadImages() + } + + @objc + public func forceDisplayUpdate() { + animationView.forceDisplayUpdate() + } + + @objc + public func getValue( + for keypath: CompatibleAnimationKeypath, + atFrame: CGFloat) -> Any? + { + return animationView.getValue( + for: keypath.animationKeypath, + atFrame: atFrame) + } + + @objc + public func logHierarchyKeypaths() { + animationView.logHierarchyKeypaths() + } + + @objc + public func setColorValue(_ color: UIColor, forKeypath keypath: CompatibleAnimationKeypath) + { + var red: CGFloat = 0 + var green: CGFloat = 0 + var blue: CGFloat = 0 + var alpha: CGFloat = 0 + // TODO: Fix color spaces + let colorspace = CGColorSpaceCreateDeviceRGB() + + let convertedColor = color.cgColor.converted(to: colorspace, intent: .defaultIntent, options: nil) + + if let components = convertedColor?.components, components.count == 4 { + red = components[0] + green = components[1] + blue = components[2] + alpha = components[3] + } else { + color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + } + + let valueProvider = ColorValueProvider(Color(r: Double(red), g: Double(green), b: Double(blue), a: Double(alpha))) + animationView.setValueProvider(valueProvider, keypath: keypath.animationKeypath) + } + + @objc + public func getColorValue(for keypath: CompatibleAnimationKeypath, atFrame: CGFloat) -> UIColor? + { + let value = animationView.getValue(for: keypath.animationKeypath, atFrame: atFrame) + guard let colorValue = value as? Color else { + return nil; + } + + return UIColor(red: CGFloat(colorValue.r), green: CGFloat(colorValue.g), blue: CGFloat(colorValue.b), alpha: CGFloat(colorValue.a)) + } + + @objc + public func addSubview( + _ subview: AnimationSubview, + forLayerAt keypath: CompatibleAnimationKeypath) + { + animationView.addSubview( + subview, + forLayerAt: keypath.animationKeypath) + } + + @objc + public func convert( + rect: CGRect, + toLayerAt keypath: CompatibleAnimationKeypath?) + -> CGRect + { + return animationView.convert( + rect, + toLayerAt: keypath?.animationKeypath) ?? .zero + } + + @objc + public func convert( + point: CGPoint, + toLayerAt keypath: CompatibleAnimationKeypath?) + -> CGPoint + { + return animationView.convert( + point, + toLayerAt: keypath?.animationKeypath) ?? .zero + } + + @objc + public func progressTime(forMarker named: String) -> CGFloat { + return animationView.progressTime(forMarker: named) ?? 0 + } + + @objc + public func frameTime(forMarker named: String) -> CGFloat { + return animationView.frameTime(forMarker: named) ?? 0 + } + + // MARK: Private + + private let animationView: AnimationView + + private func commonInit() { + translatesAutoresizingMaskIntoConstraints = false + setUpViews() + } + + private func setUpViews() { + animationView.translatesAutoresizingMaskIntoConstraints = false + addSubview(animationView) + animationView.topAnchor.constraint(equalTo: topAnchor).isActive = true + animationView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + animationView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + animationView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + } +} +#endif diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/FilepathImageProvider.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/FilepathImageProvider.swift new file mode 100644 index 0000000..e782fe8 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/FilepathImageProvider.swift @@ -0,0 +1,56 @@ +// +// FilepathImageProvider.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/1/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +/** + Provides an image for a lottie animation from a provided Bundle. + */ +public class FilepathImageProvider: AnimationImageProvider { + + let filepath: URL + + /** + Initializes an image provider with a specific filepath. + + - Parameter filepath: The absolute filepath containing the images. + + */ + public init(filepath: String) { + self.filepath = URL(fileURLWithPath: filepath) + } + + public init(filepath: URL) { + self.filepath = filepath + } + + public func imageForAsset(asset: ImageAsset) -> CGImage? { + + if asset.name.hasPrefix("data:"), + let url = URL(string: asset.name), + let data = try? Data(contentsOf: url), + let image = UIImage(data: data) { + return image.cgImage + } + + let directPath = filepath.appendingPathComponent(asset.name).path + if FileManager.default.fileExists(atPath: directPath) { + return UIImage(contentsOfFile: directPath)?.cgImage + } + + let pathWithDirectory = filepath.appendingPathComponent(asset.directory).appendingPathComponent(asset.name).path + if FileManager.default.fileExists(atPath: pathWithDirectory) { + return UIImage(contentsOfFile: pathWithDirectory)?.cgImage + } + + return nil + } + +} +#endif diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/LottieView.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/LottieView.swift new file mode 100644 index 0000000..6c320bf --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/LottieView.swift @@ -0,0 +1,62 @@ +// +// LottieView.swift +// lottie-swift-iOS +// +// Created by Brandon Withrow on 2/6/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +//public typealias LottieView = UIView + +open class LottieView: UIView { + + var viewLayer: CALayer? { + return layer + } + + func layoutAnimation() { + + } + + func animationMovedToWindow() { + + } + + open override func didMoveToWindow() { + super.didMoveToWindow() + animationMovedToWindow() + } + + var screenScale: CGFloat { + return UIScreen.main.scale + } + + func commonInit() { + contentMode = .scaleAspectFit + clipsToBounds = true + NotificationCenter.default.addObserver(self, selector: #selector(animationWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(animationWillMoveToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) + } + + open override var contentMode: UIView.ContentMode { + didSet { + setNeedsLayout() + } + } + + open override func layoutSubviews() { + super.layoutSubviews() + self.layoutAnimation() + } + + @objc func animationWillMoveToBackground() { + } + + @objc func animationWillEnterForeground() { + } + +} +#endif diff --git a/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/UIColorExtension.swift b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/UIColorExtension.swift new file mode 100644 index 0000000..7c2d931 --- /dev/null +++ b/Example/Pods/lottie-ios/lottie-swift/src/Public/iOS/UIColorExtension.swift @@ -0,0 +1,21 @@ +// +// UIColorExtension.swift +// lottie-swift +// +// Created by Brandon Withrow on 2/4/19. +// + +import Foundation +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit + +public extension UIColor { + + var lottieColorValue: Color { + var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 + getRed(&r, green: &g, blue: &b, alpha: &a) + return Color(r: Double(r), g: Double(g), b: Double(b), a: Double(a)) + } + +} +#endif diff --git a/Example/StylableUIKit.xcodeproj/project.pbxproj b/Example/StylableUIKit.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f3e4475 --- /dev/null +++ b/Example/StylableUIKit.xcodeproj/project.pbxproj @@ -0,0 +1,762 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; + 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; + 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; + 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; + 664E436C22330ACE00909BE9 /* LayerStyleGradientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664E436622330AC600909BE9 /* LayerStyleGradientTests.swift */; }; + 664E436D22330AD000909BE9 /* LayerStyleImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664E436822330AC600909BE9 /* LayerStyleImageTests.swift */; }; + 664E436E22330AD200909BE9 /* LayerStyleOutlineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664E436722330AC600909BE9 /* LayerStyleOutlineTests.swift */; }; + 666C4DAE2209A081004725B8 /* AssetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 666C4DAD2209A081004725B8 /* AssetTests.swift */; }; + 66B4800A22842A39005086B3 /* VariantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66B4800922842A39005086B3 /* VariantTests.swift */; }; + 66E050AC22567CB50082291D /* LayerStyleRoundedCornerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E050AB22567CB50082291D /* LayerStyleRoundedCornerTests.swift */; }; + 8F1B0E06231FE5CE00BBB77D /* FourthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F1B0E05231FE5CE00BBB77D /* FourthViewController.swift */; }; + 8F1B0E0B232007A800BBB77D /* loader_dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 8F1B0E0A232007A800BBB77D /* loader_dark.json */; }; + 8F9C3DAD21FB276800C95927 /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F9C3DAB21FB276800C95927 /* SecondViewController.swift */; }; + 8F9C3DAE21FB276800C95927 /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F9C3DAC21FB276800C95927 /* FirstViewController.swift */; }; + 8F9C3DB321FB277B00C95927 /* Stylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F9C3DB021FB277B00C95927 /* Stylist.swift */; }; + 8F9C3DB421FB277B00C95927 /* StylistIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F9C3DB121FB277B00C95927 /* StylistIdentifiers.swift */; }; + 8F9C3DB821FF0CB500C95927 /* LayerStyleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F9C3DB721FF0CB500C95927 /* LayerStyleTests.swift */; }; + 8F9C3DBD21FF10C400C95927 /* StylableLabelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F9C3DBC21FF10C400C95927 /* StylableLabelTests.swift */; }; + 8FFCC9B321FF17C1006FC3DE /* StylableTextFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FFCC9B221FF17C1006FC3DE /* StylableTextFieldTests.swift */; }; + 8FFCC9B521FF1A84006FC3DE /* StylableTextViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FFCC9B421FF1A84006FC3DE /* StylableTextViewTests.swift */; }; + 8FFCC9B821FF1B93006FC3DE /* StylistIdentifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FFCC9B721FF1B93006FC3DE /* StylistIdentifierTests.swift */; }; + 8FFCC9BD21FF1CE2006FC3DE /* StylistMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FFCC9BA21FF1C2B006FC3DE /* StylistMocks.swift */; }; + 8FFCC9BF21FF424E006FC3DE /* TestImages.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8FFCC9BE21FF424E006FC3DE /* TestImages.xcassets */; }; + A07D836B2391469300EBD457 /* DeviceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A07D836A2391469300EBD457 /* DeviceTest.swift */; }; + C7B8BD5654595E4D50682009 /* Pods_StylableUIKit_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40C412A958FAAA984421B07F /* Pods_StylableUIKit_Tests.framework */; }; + CF1084FD94498E9C5E915800 /* Pods_StylableUIKit_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 189A07F291AA02DCBF0AC829 /* Pods_StylableUIKit_Example.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 607FACC81AFB9204008FA782 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 607FACCF1AFB9204008FA782; + remoteInfo = FabricStylist; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0990807D08F9BF30C3B3089E /* Pods-FabricStylist_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricStylist_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-FabricStylist_Example/Pods-FabricStylist_Example.release.xcconfig"; sourceTree = ""; }; + 17C2FF3CF6A42FD9D132273A /* Pods-FabricStylist_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricStylist_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FabricStylist_Example/Pods-FabricStylist_Example.debug.xcconfig"; sourceTree = ""; }; + 189A07F291AA02DCBF0AC829 /* Pods_StylableUIKit_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StylableUIKit_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 40C412A958FAAA984421B07F /* Pods_StylableUIKit_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StylableUIKit_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 43704179DC945C3CF2D98C86 /* Pods-StylableUIKit_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StylableUIKit_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.release.xcconfig"; sourceTree = ""; }; + 461D3E4F5355E739D69F1162 /* Pods-FabricStylist_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricStylist_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FabricStylist_Tests/Pods-FabricStylist_Tests.debug.xcconfig"; sourceTree = ""; }; + 607FACD01AFB9204008FA782 /* StylableUIKit_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StylableUIKit_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 607FACE51AFB9204008FA782 /* StylableUIKit_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StylableUIKit_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 664E436622330AC600909BE9 /* LayerStyleGradientTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayerStyleGradientTests.swift; sourceTree = ""; }; + 664E436722330AC600909BE9 /* LayerStyleOutlineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayerStyleOutlineTests.swift; sourceTree = ""; }; + 664E436822330AC600909BE9 /* LayerStyleImageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LayerStyleImageTests.swift; sourceTree = ""; }; + 666C4DAD2209A081004725B8 /* AssetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetTests.swift; sourceTree = ""; }; + 66B4800922842A39005086B3 /* VariantTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantTests.swift; sourceTree = ""; }; + 66E050AB22567CB50082291D /* LayerStyleRoundedCornerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LayerStyleRoundedCornerTests.swift; path = Tests/LayerStyle/LayerStyleRoundedCornerTests.swift; sourceTree = SOURCE_ROOT; }; + 8131FF05261249EC3834F28B /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + 86DC2C8FA90FEBCB7ED1F982 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; + 8F1B0E05231FE5CE00BBB77D /* FourthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FourthViewController.swift; sourceTree = ""; }; + 8F1B0E0A232007A800BBB77D /* loader_dark.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = loader_dark.json; sourceTree = ""; }; + 8F24C3B6F46B4973D2AD80CA /* Pods-StylableUIKit_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StylableUIKit_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example.debug.xcconfig"; sourceTree = ""; }; + 8F9C3DAB21FB276800C95927 /* SecondViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; + 8F9C3DAC21FB276800C95927 /* FirstViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; }; + 8F9C3DB021FB277B00C95927 /* Stylist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stylist.swift; sourceTree = ""; }; + 8F9C3DB121FB277B00C95927 /* StylistIdentifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StylistIdentifiers.swift; sourceTree = ""; }; + 8F9C3DB721FF0CB500C95927 /* LayerStyleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayerStyleTests.swift; sourceTree = ""; }; + 8F9C3DB921FF0F7500C95927 /* ReferenceImages_64 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ReferenceImages_64; sourceTree = ""; }; + 8F9C3DBC21FF10C400C95927 /* StylableLabelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StylableLabelTests.swift; sourceTree = ""; }; + 8FFA2115ADF2E3092EA2B463 /* Pods-StylableUIKit_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StylableUIKit_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.debug.xcconfig"; sourceTree = ""; }; + 8FFCC9B221FF17C1006FC3DE /* StylableTextFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StylableTextFieldTests.swift; sourceTree = ""; }; + 8FFCC9B421FF1A84006FC3DE /* StylableTextViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StylableTextViewTests.swift; sourceTree = ""; }; + 8FFCC9B721FF1B93006FC3DE /* StylistIdentifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StylistIdentifierTests.swift; sourceTree = ""; }; + 8FFCC9BA21FF1C2B006FC3DE /* StylistMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StylistMocks.swift; sourceTree = ""; }; + 8FFCC9BE21FF424E006FC3DE /* TestImages.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = TestImages.xcassets; sourceTree = ""; }; + A07D836723901B2E00EBD457 /* swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = swiftlint.yml; sourceTree = ""; }; + A07D836A2391469300EBD457 /* DeviceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTest.swift; sourceTree = ""; }; + F1ABFAF4EBE87F7544AACA1B /* Pods-FabricStylist_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricStylist_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FabricStylist_Tests/Pods-FabricStylist_Tests.release.xcconfig"; sourceTree = ""; }; + F717D81DB88C577B314FD00C /* Pods-StylableUIKit_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StylableUIKit_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests.release.xcconfig"; sourceTree = ""; }; + F969F199021A08977199A216 /* FabricStylist.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FabricStylist.podspec; path = ../FabricStylist.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 607FACCD1AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CF1084FD94498E9C5E915800 /* Pods_StylableUIKit_Example.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE21AFB9204008FA782 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C7B8BD5654595E4D50682009 /* Pods_StylableUIKit_Tests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 607FACC71AFB9204008FA782 = { + isa = PBXGroup; + children = ( + 607FACF51AFB993E008FA782 /* Podspec Metadata */, + 607FACD21AFB9204008FA782 /* Example for StylableUIKit */, + 607FACE81AFB9204008FA782 /* Tests */, + 607FACD11AFB9204008FA782 /* Products */, + C764B9FB8986407B9C4CEC34 /* Pods */, + C5B8F8943226548C61F71D90 /* Frameworks */, + ); + sourceTree = ""; + }; + 607FACD11AFB9204008FA782 /* Products */ = { + isa = PBXGroup; + children = ( + 607FACD01AFB9204008FA782 /* StylableUIKit_Example.app */, + 607FACE51AFB9204008FA782 /* StylableUIKit_Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 607FACD21AFB9204008FA782 /* Example for StylableUIKit */ = { + isa = PBXGroup; + children = ( + 8F9C3DAF21FB276C00C95927 /* Stylist */, + 8F9C3DAC21FB276800C95927 /* FirstViewController.swift */, + 8F9C3DAB21FB276800C95927 /* SecondViewController.swift */, + 607FACD51AFB9204008FA782 /* AppDelegate.swift */, + 607FACD91AFB9204008FA782 /* Main.storyboard */, + 607FACDC1AFB9204008FA782 /* Images.xcassets */, + 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, + 607FACD31AFB9204008FA782 /* Supporting Files */, + 8F1B0E07231FE69B00BBB77D /* FourthViewController */, + ); + name = "Example for StylableUIKit"; + path = StylableUIKit; + sourceTree = ""; + }; + 607FACD31AFB9204008FA782 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 607FACD41AFB9204008FA782 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 607FACE81AFB9204008FA782 /* Tests */ = { + isa = PBXGroup; + children = ( + 666C4DAC2209A05A004725B8 /* Assets */, + 8FFCC9B921FF1C1D006FC3DE /* Mocks */, + 8FFCC9B621FF1B7F006FC3DE /* Core */, + 8F9C3DBB21FF10AD00C95927 /* Components */, + 8F9C3DB921FF0F7500C95927 /* ReferenceImages_64 */, + 8F9C3DB621FF0BED00C95927 /* LayerStyle */, + 607FACE91AFB9204008FA782 /* Supporting Files */, + 8FFCC9BE21FF424E006FC3DE /* TestImages.xcassets */, + A07D836A2391469300EBD457 /* DeviceTest.swift */, + ); + path = Tests; + sourceTree = ""; + }; + 607FACE91AFB9204008FA782 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 607FACEA1AFB9204008FA782 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { + isa = PBXGroup; + children = ( + A07D836723901B2E00EBD457 /* swiftlint.yml */, + F969F199021A08977199A216 /* FabricStylist.podspec */, + 86DC2C8FA90FEBCB7ED1F982 /* README.md */, + 8131FF05261249EC3834F28B /* LICENSE */, + ); + name = "Podspec Metadata"; + sourceTree = ""; + }; + 666C4DAC2209A05A004725B8 /* Assets */ = { + isa = PBXGroup; + children = ( + 666C4DAD2209A081004725B8 /* AssetTests.swift */, + ); + path = Assets; + sourceTree = ""; + }; + 8F1B0E07231FE69B00BBB77D /* FourthViewController */ = { + isa = PBXGroup; + children = ( + 8F1B0E0A232007A800BBB77D /* loader_dark.json */, + 8F1B0E05231FE5CE00BBB77D /* FourthViewController.swift */, + ); + path = FourthViewController; + sourceTree = ""; + }; + 8F9C3DAF21FB276C00C95927 /* Stylist */ = { + isa = PBXGroup; + children = ( + 8F9C3DB021FB277B00C95927 /* Stylist.swift */, + 8F9C3DB121FB277B00C95927 /* StylistIdentifiers.swift */, + ); + path = Stylist; + sourceTree = ""; + }; + 8F9C3DB621FF0BED00C95927 /* LayerStyle */ = { + isa = PBXGroup; + children = ( + 8F9C3DB721FF0CB500C95927 /* LayerStyleTests.swift */, + 664E436622330AC600909BE9 /* LayerStyleGradientTests.swift */, + 664E436822330AC600909BE9 /* LayerStyleImageTests.swift */, + 664E436722330AC600909BE9 /* LayerStyleOutlineTests.swift */, + 66E050AB22567CB50082291D /* LayerStyleRoundedCornerTests.swift */, + ); + path = LayerStyle; + sourceTree = ""; + }; + 8F9C3DBB21FF10AD00C95927 /* Components */ = { + isa = PBXGroup; + children = ( + 8F9C3DBC21FF10C400C95927 /* StylableLabelTests.swift */, + 8FFCC9B221FF17C1006FC3DE /* StylableTextFieldTests.swift */, + 8FFCC9B421FF1A84006FC3DE /* StylableTextViewTests.swift */, + ); + path = Components; + sourceTree = ""; + }; + 8FFCC9B621FF1B7F006FC3DE /* Core */ = { + isa = PBXGroup; + children = ( + 8FFCC9B721FF1B93006FC3DE /* StylistIdentifierTests.swift */, + 66B4800922842A39005086B3 /* VariantTests.swift */, + ); + path = Core; + sourceTree = ""; + }; + 8FFCC9B921FF1C1D006FC3DE /* Mocks */ = { + isa = PBXGroup; + children = ( + 8FFCC9BA21FF1C2B006FC3DE /* StylistMocks.swift */, + ); + path = Mocks; + sourceTree = ""; + }; + C5B8F8943226548C61F71D90 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 189A07F291AA02DCBF0AC829 /* Pods_StylableUIKit_Example.framework */, + 40C412A958FAAA984421B07F /* Pods_StylableUIKit_Tests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C764B9FB8986407B9C4CEC34 /* Pods */ = { + isa = PBXGroup; + children = ( + 17C2FF3CF6A42FD9D132273A /* Pods-FabricStylist_Example.debug.xcconfig */, + 0990807D08F9BF30C3B3089E /* Pods-FabricStylist_Example.release.xcconfig */, + 461D3E4F5355E739D69F1162 /* Pods-FabricStylist_Tests.debug.xcconfig */, + F1ABFAF4EBE87F7544AACA1B /* Pods-FabricStylist_Tests.release.xcconfig */, + 8F24C3B6F46B4973D2AD80CA /* Pods-StylableUIKit_Example.debug.xcconfig */, + 43704179DC945C3CF2D98C86 /* Pods-StylableUIKit_Example.release.xcconfig */, + 8FFA2115ADF2E3092EA2B463 /* Pods-StylableUIKit_Tests.debug.xcconfig */, + F717D81DB88C577B314FD00C /* Pods-StylableUIKit_Tests.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 607FACCF1AFB9204008FA782 /* StylableUIKit_Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "StylableUIKit_Example" */; + buildPhases = ( + B7EE6939B548779D0F12C501 /* [CP] Check Pods Manifest.lock */, + 607FACCC1AFB9204008FA782 /* Sources */, + 607FACCD1AFB9204008FA782 /* Frameworks */, + 607FACCE1AFB9204008FA782 /* Resources */, + A8B9A10DBE729AF77276C687 /* [CP] Embed Pods Frameworks */, + A07D836623901A2E00EBD457 /* SwiftLint */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = StylableUIKit_Example; + productName = FabricStylist; + productReference = 607FACD01AFB9204008FA782 /* StylableUIKit_Example.app */; + productType = "com.apple.product-type.application"; + }; + 607FACE41AFB9204008FA782 /* StylableUIKit_Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "StylableUIKit_Tests" */; + buildPhases = ( + CA0FEA367CB247877C0971DB /* [CP] Check Pods Manifest.lock */, + 607FACE11AFB9204008FA782 /* Sources */, + 607FACE21AFB9204008FA782 /* Frameworks */, + 607FACE31AFB9204008FA782 /* Resources */, + D7EBBD3CB4355490CFFDDE71 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 607FACE71AFB9204008FA782 /* PBXTargetDependency */, + ); + name = StylableUIKit_Tests; + productName = Tests; + productReference = 607FACE51AFB9204008FA782 /* StylableUIKit_Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 607FACC81AFB9204008FA782 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0830; + LastUpgradeCheck = 1030; + ORGANIZATIONNAME = "LVMH Digital"; + TargetAttributes = { + 607FACCF1AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = 1030; + }; + 607FACE41AFB9204008FA782 = { + CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = 1030; + TestTargetID = 607FACCF1AFB9204008FA782; + }; + }; + }; + buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "StylableUIKit" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 607FACC71AFB9204008FA782; + productRefGroup = 607FACD11AFB9204008FA782 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 607FACCF1AFB9204008FA782 /* StylableUIKit_Example */, + 607FACE41AFB9204008FA782 /* StylableUIKit_Tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 607FACCE1AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8F1B0E0B232007A800BBB77D /* loader_dark.json in Resources */, + 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, + 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, + 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE31AFB9204008FA782 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8FFCC9BF21FF424E006FC3DE /* TestImages.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + A07D836623901A2E00EBD457 /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\" --config \"${PROJECT_DIR}/swiftlint.yml\" --path \"${PROJECT_DIR}\"\n\n"; + }; + A8B9A10DBE729AF77276C687 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/StylableUIKit/StylableUIKit.framework", + "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/StylableUIKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lottie.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-StylableUIKit_Example/Pods-StylableUIKit_Example-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + B7EE6939B548779D0F12C501 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-StylableUIKit_Example-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + CA0FEA367CB247877C0971DB /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-StylableUIKit_Tests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + D7EBBD3CB4355490CFFDDE71 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/iOSSnapshotTestCase/FBSnapshotTestCase.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSnapshotTestCase.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-StylableUIKit_Tests/Pods-StylableUIKit_Tests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 607FACCC1AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8F9C3DB321FB277B00C95927 /* Stylist.swift in Sources */, + 8F9C3DAE21FB276800C95927 /* FirstViewController.swift in Sources */, + 8F9C3DAD21FB276800C95927 /* SecondViewController.swift in Sources */, + 8F1B0E06231FE5CE00BBB77D /* FourthViewController.swift in Sources */, + 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, + 8F9C3DB421FB277B00C95927 /* StylistIdentifiers.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 607FACE11AFB9204008FA782 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8FFCC9B821FF1B93006FC3DE /* StylistIdentifierTests.swift in Sources */, + A07D836B2391469300EBD457 /* DeviceTest.swift in Sources */, + 66E050AC22567CB50082291D /* LayerStyleRoundedCornerTests.swift in Sources */, + 8FFCC9B321FF17C1006FC3DE /* StylableTextFieldTests.swift in Sources */, + 666C4DAE2209A081004725B8 /* AssetTests.swift in Sources */, + 8FFCC9BD21FF1CE2006FC3DE /* StylistMocks.swift in Sources */, + 66B4800A22842A39005086B3 /* VariantTests.swift in Sources */, + 664E436E22330AD200909BE9 /* LayerStyleOutlineTests.swift in Sources */, + 8FFCC9B521FF1A84006FC3DE /* StylableTextViewTests.swift in Sources */, + 8F9C3DBD21FF10C400C95927 /* StylableLabelTests.swift in Sources */, + 8F9C3DB821FF0CB500C95927 /* LayerStyleTests.swift in Sources */, + 664E436D22330AD000909BE9 /* LayerStyleImageTests.swift in Sources */, + 664E436C22330ACE00909BE9 /* LayerStyleGradientTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 607FACCF1AFB9204008FA782 /* StylableUIKit_Example */; + targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 607FACD91AFB9204008FA782 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 607FACDA1AFB9204008FA782 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 607FACDF1AFB9204008FA782 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 607FACED1AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + 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 = 10.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 607FACEE1AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + 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 = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 607FACF01AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8F24C3B6F46B4973D2AD80CA /* Pods-StylableUIKit_Example.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = StylableUIKit/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 607FACF11AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 43704179DC945C3CF2D98C86 /* Pods-StylableUIKit_Example.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = StylableUIKit/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 607FACF31AFB9204008FA782 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8FFA2115ADF2E3092EA2B463 /* Pods-StylableUIKit_Tests.debug.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StylableUIKit_Example.app/StylableUIKit_Example"; + }; + name = Debug; + }; + 607FACF41AFB9204008FA782 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F717D81DB88C577B314FD00C /* Pods-StylableUIKit_Tests.release.xcconfig */; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StylableUIKit_Example.app/StylableUIKit_Example"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "StylableUIKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACED1AFB9204008FA782 /* Debug */, + 607FACEE1AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "StylableUIKit_Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF01AFB9204008FA782 /* Debug */, + 607FACF11AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "StylableUIKit_Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 607FACF31AFB9204008FA782 /* Debug */, + 607FACF41AFB9204008FA782 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 607FACC81AFB9204008FA782 /* Project object */; +} diff --git a/Example/StylableUIKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example/StylableUIKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..5cbf691 --- /dev/null +++ b/Example/StylableUIKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Example/StylableUIKit.xcodeproj/xcshareddata/xcschemes/StylableUIKit_Example.xcscheme b/Example/StylableUIKit.xcodeproj/xcshareddata/xcschemes/StylableUIKit_Example.xcscheme new file mode 100644 index 0000000..5960b6f --- /dev/null +++ b/Example/StylableUIKit.xcodeproj/xcshareddata/xcschemes/StylableUIKit_Example.xcscheme @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/StylableUIKit.xcworkspace/contents.xcworkspacedata b/Example/StylableUIKit.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..c3cfccb --- /dev/null +++ b/Example/StylableUIKit.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/Example/StylableUIKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/StylableUIKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Example/StylableUIKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/StylableUIKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Example/StylableUIKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..949b678 --- /dev/null +++ b/Example/StylableUIKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + BuildSystemType + Original + + diff --git a/Example/StylableUIKit/AppDelegate.swift b/Example/StylableUIKit/AppDelegate.swift new file mode 100644 index 0000000..efafa05 --- /dev/null +++ b/Example/StylableUIKit/AppDelegate.swift @@ -0,0 +1,61 @@ +// +// AppDelegate.swift +// StylableUIKit +// +// Created by atomoil on 01/18/2019. +// Copyright (c) 2019 atomoil. All rights reserved. +// + +import UIKit +import StylableUIKit + +private func isUnderTest() -> Bool { + return Bundle.allBundles.map { $0.bundlePath }.contains { $0.hasSuffix(".xctest") } +} + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + + // If we are in a test we shouldn't load the UI - it affects the code coverage. + if isUnderTest() { + let window = UIWindow() + self.window = window + let controller = UIViewController() + controller.view.backgroundColor = .green + window.rootViewController = controller + window.makeKeyAndVisible() + return true + } + + let stylist = AppStylist() + + if let tab = window?.rootViewController as? UITabBarController, + let viewControllers = tab.viewControllers { + + zip(viewControllers, Section.allCases).forEach { + $0.style(stylist: stylist, section: $1) + } + } + + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + } + + func applicationDidEnterBackground(_ application: UIApplication) { + } + + func applicationWillEnterForeground(_ application: UIApplication) { + } + + func applicationDidBecomeActive(_ application: UIApplication) { + } + + func applicationWillTerminate(_ application: UIApplication) { + } +} diff --git a/Example/StylableUIKit/Base.lproj/LaunchScreen.xib b/Example/StylableUIKit/Base.lproj/LaunchScreen.xib new file mode 100644 index 0000000..5ae13a6 --- /dev/null +++ b/Example/StylableUIKit/Base.lproj/LaunchScreen.xib @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/StylableUIKit/Base.lproj/Main.storyboard b/Example/StylableUIKit/Base.lproj/Main.storyboard new file mode 100644 index 0000000..139734a --- /dev/null +++ b/Example/StylableUIKit/Base.lproj/Main.storyboard @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/StylableUIKit/FirstViewController.swift b/Example/StylableUIKit/FirstViewController.swift new file mode 100644 index 0000000..c52b8c1 --- /dev/null +++ b/Example/StylableUIKit/FirstViewController.swift @@ -0,0 +1,71 @@ +// +// FirstViewController.swift +// Stylable-UIKit_Example +// + +import Foundation +import UIKit + +import StylableUIKit + +enum StylableButtonMode: StylableUIKit.ButtonMode { + + case primary, secondary + + var element: StylistElement { + switch self { + case .primary: return Element.primaryButton + case .secondary: return Element.secondaryButton + } + } + + var layerStyle: StylistLayerStyle? { LayerType.background } + + var textStyle: StylistTextStyle { TextType.title } +} + +class FirstViewController: UIViewController, StylableViewController { + + var stylableSection: StylistSection = Section.first + + @IBOutlet weak private var primaryButton: StylableButton! + @IBOutlet weak private var secondaryButton: StylableButton! + @IBOutlet weak private var stylableLabel: StylableLabel! + @IBOutlet weak private var stylableTextField: StylableTextField! + @IBOutlet weak private var stylableTextView: StylableTextView! + @IBOutlet weak private var placeholderStylabelTextField: StylableTextField! + @IBOutlet weak private var stackViewWidthConstraint: NSLayoutConstraint! + @IBOutlet weak private var toolbar: UIToolbar! + @IBOutlet weak private var navbar: UINavigationBar! + @IBOutlet weak private var tabbar: UITabBar! + + override func viewDidLoad() { + super.viewDidLoad() + + self.primaryButton.style(mode: StylableButtonMode.primary, stylist: self.stylist, section: self.stylableSection) + self.secondaryButton.style(mode: StylableButtonMode.secondary, stylist: self.stylist, section: self.stylableSection) + + self.stylist.layerStyle(LayerType.background, element: Element.label, section: self.stylableSection).apply(self.stylableLabel) + self.stylist.layerStyle(LayerType.background, element: Element.textField, section: self.stylableSection).apply(self.stylableTextField) + self.stylist.layerStyle(LayerType.background, element: Element.textView, section: self.stylableSection).apply(self.stylableTextView) + self.stylist.layerStyle(LayerType.background, element: Element.toolbar, section: self.stylableSection).apply(self.toolbar) + self.stylist.layerStyle(LayerType.background, element: Element.toolbar, section: self.stylableSection).apply(self.navbar) + self.stylist.layerStyle(LayerType.background, element: Element.toolbar, section: self.stylableSection).apply(self.tabbar) + + self.placeholderStylabelTextField.setPlaceholderTextStyle(self.stylist.textStyle(TextType.secondaryText, element: Element.textField, section: Section.first)) + self.placeholderStylabelTextField.setTextStyle(self.stylist.textStyle(TextType.primaryText, element: Element.textField, section: Section.first)) + } + + @IBAction func onTapPrimaryButton(_ sender: Any) { + print("Primary tap") + self.stackViewWidthConstraint.constant = (self.stackViewWidthConstraint.constant > 250) + ? 241 : self.view.bounds.width + + } + + @IBAction func onTapSecondaryButton(_ sender: Any) { + print("Secondary tap") + self.stackViewWidthConstraint.constant = (self.stackViewWidthConstraint.constant > 250) + ? 241 : self.view.bounds.width + } +} diff --git a/Example/StylableUIKit/FourthViewController/FourthViewController.swift b/Example/StylableUIKit/FourthViewController/FourthViewController.swift new file mode 100644 index 0000000..788069f --- /dev/null +++ b/Example/StylableUIKit/FourthViewController/FourthViewController.swift @@ -0,0 +1,46 @@ +// +// ThirdViewController.swift +// Stylable-UIKit_Example +// + +import UIKit +import StylableUIKit + +enum AnimatedAsset: StylistAsset { + case loader + + var name: String { + switch self { + case .loader: + return "loader_dark" + } + } +} + +final class FourthViewController: UIViewController, StylableViewController { + var stylableSection: StylistSection = Section.fourth + + @IBOutlet private weak var postSubviewAnimatedAssetContainer: UIView! + @IBOutlet private weak var preSubviewAnimatedAssetContainer: UIView! + + override func viewDidLoad() { + super.viewDidLoad() + + // Play the asset before adding to the subview and after adding to the subview + if let animatedAsset = self.stylist.animatedAsset(AnimatedAsset.loader, element: nil, section: self.stylableSection) { + animatedAsset.playLooped() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + animatedAsset.frame = self?.preSubviewAnimatedAssetContainer.bounds ?? .zero + animatedAsset.autoresizingMask = [.flexibleHeight, .flexibleWidth] + self?.preSubviewAnimatedAssetContainer.addSubview(animatedAsset) + } + } + + if let animatedAsset = self.stylist.animatedAsset(AnimatedAsset.loader, element: nil, section: self.stylableSection) { + animatedAsset.frame = self.postSubviewAnimatedAssetContainer.bounds + animatedAsset.autoresizingMask = [.flexibleHeight, .flexibleWidth] + self.postSubviewAnimatedAssetContainer.addSubview(animatedAsset) + animatedAsset.playLooped() + } + } +} diff --git a/Example/StylableUIKit/FourthViewController/loader_dark.json b/Example/StylableUIKit/FourthViewController/loader_dark.json new file mode 100644 index 0000000..0589114 --- /dev/null +++ b/Example/StylableUIKit/FourthViewController/loader_dark.json @@ -0,0 +1 @@ +{"v":"5.4.4","fr":25,"ip":0,"op":64,"w":95,"h":95,"nm":"MAIN_COMP","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[352,315,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-8,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-26.625,159.5],[-18.125,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[20.75,159.5],[12.375,147.125]],"c":false}]},{"t":8}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-8,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-26.625,159.5],[-18.125,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[20.75,159.5],[12.375,147.125]],"c":false}]},{"t":8}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":-8,"op":317,"st":-8,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[352,315,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-26.625,159.5],[-18.125,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[20.75,159.5],[12.375,147.125]],"c":false}]},{"t":24}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-26.625,159.5],[-18.125,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[20.75,159.5],[12.375,147.125]],"c":false}]},{"t":24}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":8,"op":333,"st":8,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[352,315,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-26.625,159.5],[-18.125,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[20.75,159.5],[12.375,147.125]],"c":false}]},{"t":16}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-26.625,159.5],[-18.125,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[-4,159.5],[-4.625,147.125]],"c":false}],"e":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-3.25,187],[20.75,159.5],[12.375,147.125]],"c":false}]},{"t":16}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":325,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 1","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[87.901,-0.618,0],"ix":2},"a":{"a":0,"k":[-89.161,-374.805,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-112.685,-382.791],[-65.187,-382.791]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-73.642,-394.741],[-104.68,-394.741],[-113.135,-382.845],[-89.161,-354.87],[-65.187,-382.845]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":325,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[379.5,468.5,0],"ix":2},"a":{"a":0,"k":[-36.25,153.5,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-36.25,153.5],[-59.75,147.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.509],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":3,"s":[0],"e":[100]},{"t":10}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.526],"y":[1]},"o":{"x":[0.333],"y":[0.011]},"t":6,"s":[0],"e":[100]},{"t":13}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":328,"st":3,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-63,"ix":10},"p":{"a":0,"k":[360.25,451,0],"ix":2},"a":{"a":0,"k":[-36.25,153.5,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-36.25,153.5],[-59.75,147.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.509],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":3,"s":[0],"e":[100]},{"t":10}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.526],"y":[1]},"o":{"x":[0.333],"y":[0.011]},"t":6,"s":[0],"e":[100]},{"t":13}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":328,"st":3,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":67,"ix":10},"p":{"a":0,"k":[334,451.75,0],"ix":2},"a":{"a":0,"k":[-36.25,153.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-36.25,153.5],[-59.75,147.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.509],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":3,"s":[0],"e":[100]},{"t":10}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.526],"y":[1]},"o":{"x":[0.333],"y":[0.011]},"t":6,"s":[0],"e":[100]},{"t":13}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":328,"st":3,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[315.75,468.5,0],"ix":2},"a":{"a":0,"k":[-36.25,153.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-36.25,153.5],[-59.75,147.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.509],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":3,"s":[0],"e":[100]},{"t":10}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.526],"y":[1]},"o":{"x":[0.333],"y":[0.011]},"t":6,"s":[0],"e":[100]},{"t":13}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":3,"op":328,"st":3,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":0,"k":[322,454,0],"ix":2},"a":{"a":0,"k":[-4.5,139,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.5,139],[-4.5,123.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.509],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0],"e":[100]},{"t":7}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.526],"y":[1]},"o":{"x":[0.333],"y":[0.011]},"t":3,"s":[0],"e":[100]},{"t":10}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":325,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":45,"ix":10},"p":{"a":0,"k":[373.5,454,0],"ix":2},"a":{"a":0,"k":[-4.5,139,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.5,139],[-4.5,123.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.509],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0],"e":[100]},{"t":7}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.526],"y":[1]},"o":{"x":[0.333],"y":[0.011]},"t":3,"s":[0],"e":[100]},{"t":10}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":325,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[347.5,450.25,0],"ix":2},"a":{"a":0,"k":[-4.5,139,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-4.5,139],[-4.5,123.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.694117647059,0.658823529412,0.596078431373,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.509],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0],"e":[100]},{"t":7}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.526],"y":[1]},"o":{"x":[0.333],"y":[0.011]},"t":3,"s":[0],"e":[100]},{"t":10}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":325,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":3,"nm":"GEM","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[346.306,483.009,0],"ix":2},"a":{"a":0,"k":[85.306,0.009,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":325,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"GEM-LAMPSIS","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":48,"s":[50,55.5,0],"e":[50,57.125,0],"to":[0,0.271,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":56,"s":[50,57.125,0],"e":[50,55.5,0],"to":[0,0,0],"ti":[0,0.271,0]},{"t":64}],"ix":2},"a":{"a":0,"k":[352,478,0],"ix":1},"s":{"a":0,"k":[73,73,100],"ix":6}},"ao":0,"w":704,"h":630,"ip":48,"op":64,"st":48,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"GEM-LAMPSIS","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":32,"s":[50,55.5,0],"e":[50,57.125,0],"to":[0,0.271,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[50,57.125,0],"e":[50,55.5,0],"to":[0,0,0],"ti":[0,0.271,0]},{"t":48}],"ix":2},"a":{"a":0,"k":[352,478,0],"ix":1},"s":{"a":0,"k":[73,73,100],"ix":6}},"ao":0,"w":704,"h":630,"ip":32,"op":48,"st":32,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"GEM-LAMPSIS","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":16,"s":[50,55.5,0],"e":[50,57.125,0],"to":[0,0.271,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":24,"s":[50,57.125,0],"e":[50,55.5,0],"to":[0,0,0],"ti":[0,0.271,0]},{"t":32}],"ix":2},"a":{"a":0,"k":[352,478,0],"ix":1},"s":{"a":0,"k":[73,73,100],"ix":6}},"ao":0,"w":704,"h":630,"ip":16,"op":32,"st":16,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"GEM-LAMPSIS","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[50,55.5,0],"e":[50,57.125,0],"to":[0,0.271,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":8,"s":[50,57.125,0],"e":[50,55.5,0],"to":[0,0,0],"ti":[0,0.271,0]},{"t":16}],"ix":2},"a":{"a":0,"k":[352,478,0],"ix":1},"s":{"a":0,"k":[73,73,100],"ix":6}},"ao":0,"w":704,"h":630,"ip":0,"op":16,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/Example/StylableUIKit/Images.xcassets/AppIcon.appiconset/Contents.json b/Example/StylableUIKit/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..7006c9e --- /dev/null +++ b/Example/StylableUIKit/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,53 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/Example/StylableUIKit/Images.xcassets/Contents.json b/Example/StylableUIKit/Images.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Example/StylableUIKit/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/StylableUIKit/Images.xcassets/discount-ticket.imageset/Contents.json b/Example/StylableUIKit/Images.xcassets/discount-ticket.imageset/Contents.json new file mode 100644 index 0000000..b967c1c --- /dev/null +++ b/Example/StylableUIKit/Images.xcassets/discount-ticket.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "discount-ticket.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/StylableUIKit/Images.xcassets/discount-ticket.imageset/discount-ticket.pdf b/Example/StylableUIKit/Images.xcassets/discount-ticket.imageset/discount-ticket.pdf new file mode 100644 index 0000000..7d35c6a Binary files /dev/null and b/Example/StylableUIKit/Images.xcassets/discount-ticket.imageset/discount-ticket.pdf differ diff --git a/Example/StylableUIKit/Info.plist b/Example/StylableUIKit/Info.plist new file mode 100644 index 0000000..eb18faa --- /dev/null +++ b/Example/StylableUIKit/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + + + diff --git a/Example/StylableUIKit/SecondViewController.swift b/Example/StylableUIKit/SecondViewController.swift new file mode 100644 index 0000000..4580a3d --- /dev/null +++ b/Example/StylableUIKit/SecondViewController.swift @@ -0,0 +1,59 @@ +// +// SecondViewController.swift +// Stylable-UIKit_Example +// + +import UIKit +import StylableUIKit + +final class SecondViewController: UIViewController, StylableViewController { + var stylableSection: StylistSection = Section.second +} + +typealias Layer = (LayerFill, LayerOutline?) + +final class ThirdViewController: UIViewController, StylableViewController { + @IBOutlet private var tableView: UITableView! + var stylableSection: StylistSection = Section.third + + private let fills: [Layer] = [ + (FlatLayerFill(color: .brown), nil), + (GradientLayerFill(colors: [.blue, .orange], style: .axial(direction: .leftToRight, locations: nil)), nil), + (ImageLayerFill(image: UIImage(named: "discount-ticket")!, resizingMode: .stretch), nil), + (ImageLayerFill(image: UIImage(named: "discount-ticket")!, resizingMode: .tile), DashedLayerOutline(color: .red, width: 2, lineDashPattern: [5, 5])), + (ImageLayerFill(image: UIImage(named: "discount-ticket")!, resizingMode: .stretch, gravity: .center), LinearLayerOutline(color: .black, width: 2)), + (FlatLayerFill(color: .red), DashedLayerOutline(color: .black, width: 2, lineDashPattern: [10, 50])) + ] + + override func viewDidLoad() { + super.viewDidLoad() + self.tableView.dataSource = self + self.styleTableView() + } + + private func styleTableView() { + let fill = FlatLayerFill(color: .green) + let style = LayerStyle(fill: fill) + style.apply(self.tableView) + } +} + +extension ThirdViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.fills.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + cell.textLabel?.text = "Cell # \(indexPath.row)" + + let layer = self.fills[indexPath.row] + let style = LayerStyle(fill: layer.0, outline: layer.1) + style.apply(cell) + // We also have to set the background color of the content view and any other views that are above the background + // to clear or you won't see the background of the actual cell. This could also be done in the xib. + cell.contentView.backgroundColor = .clear + cell.textLabel?.backgroundColor = .clear + return cell + } +} diff --git a/Example/StylableUIKit/Stylist/Stylist.swift b/Example/StylableUIKit/Stylist/Stylist.swift new file mode 100644 index 0000000..5f26add --- /dev/null +++ b/Example/StylableUIKit/Stylist/Stylist.swift @@ -0,0 +1,77 @@ +// +// Stylist.swift +// Stylable-UIKit_Example +// + +import Foundation +import StylableUIKit + +class AppStylist: Stylist { + + func asset(_ identifier: StylistAsset, element: StylistElement?, section: StylistSection?) -> Asset? { + return nil + } + + func animatedAsset(_ identifier: StylistAsset, element: StylistElement?, section: StylistSection?) -> StylistAnimatedAsset? { + return identifier.name.asLottieAnimationView() + } + + func textStyle(_ style: StylistTextStyle, element: StylistElement?, section: StylistSection?) -> TextStyle { + var size: CGFloat = 10 + var color = UIColor.black + + if (style as? TextType) == TextType.primaryText { + size = (element as? Element) == Element.secondaryButton ? 12 : 14 + } + if (style as? TextType) == TextType.primaryText && (element as? Element) == Element.secondaryButton { + size = 12 + } + + if (style as? TextType) == TextType.secondaryText && (element as? Element) == Element.textField { + size = 12 + color = .red + } + + if (style as? TextType) == TextType.primaryText && (element as? Element) == Element.textField { + size = 12 + color = .blue + } + + return TextStyle(font: .systemFont(ofSize: size), textColor: color) + } + + func layerStyle(_ style: StylistLayerStyle, element: StylistElement?, section: StylistSection?) -> LayerStyle { + + if let element = element as? Element { + switch element { + case .label, .textField, .textView: + return LayerStyle(fill: GradientLayerFill(colors: [.darkGray, .green], style: .radial(center: CGPoint(x: 0.25, y: 0.25), locations: nil)), + outline: DashedLayerOutline(color: .white, width: 2, lineDashPattern: [2, 2])) + case .primaryButton: + return LayerStyle(fill: FlatLayerFill(color: .green), outline: DashedLayerOutline(color: .red, width: 2, lineDashPattern: [3, 3])) + case .secondaryButton: + return LayerStyle(fill: FlatLayerFill(color: .yellow), outline: LinearLayerOutline(color: .blue, width: 3)) + case .toolbar: + let fill = GradientLayerFill(colors: [.red, .orange, .yellow], style: .axial(direction: .topToBottom, locations: nil)) + //let fill = FlatLayerFill(color: UIColor.blue.withAlphaComponent(0.85) ) + let outline = DashedLayerOutline(color: .yellow, width: 5, lineDashPattern: [3, 3]) + //let outline = LinearLayerOutline(color:.yellow, width: 4) + return LayerStyle(fill: fill, outline: outline) + } + } + + if let section = section as? Section { + switch section { + case .first: + return LayerStyle(fill: FlatLayerFill(color: .lightGray)) + case .second: + return LayerStyle(fill: ImageLayerFill(image: UIImage(named: "discount-ticket")!, resizingMode: .stretch)) + case .third: + return LayerStyle(fill: GradientLayerFill(colors: [.red, .orange, .yellow], style: .axial(direction: .topToBottom, locations: nil))) + case .fourth: + return LayerStyle(fill: FlatLayerFill(color: .lightGray)) + } + } + return LayerStyle(fill: FlatLayerFill(color: .black)) + } +} diff --git a/Example/StylableUIKit/Stylist/StylistIdentifiers.swift b/Example/StylableUIKit/Stylist/StylistIdentifiers.swift new file mode 100644 index 0000000..96ce5b4 --- /dev/null +++ b/Example/StylableUIKit/Stylist/StylistIdentifiers.swift @@ -0,0 +1,42 @@ +// +// StylistIdentifiers.swift +// Stylable-UIKit_Example +// + +import Foundation +import StylableUIKit + +enum Section: String, StylistSection, CaseIterable { + case first + case second + case third + case fourth +} + +enum Element: String, StylistElement { + case primaryButton + case secondaryButton + case label + case textField + case textView + case toolbar +} + +enum TextType: String, StylistTextStyle { + + case title + case subtitle + case heading + case subheading + case primaryText + case secondaryText + case tertiaryText + case warningText + case successText +} + +public enum LayerType: String, StylistLayerStyle { + + case background + case line +} diff --git a/Example/Tests/Assets/AssetTests.swift b/Example/Tests/Assets/AssetTests.swift new file mode 100644 index 0000000..11c4c1b --- /dev/null +++ b/Example/Tests/Assets/AssetTests.swift @@ -0,0 +1,31 @@ +// +// AssetTests.swift +// StylableUIKit_Tests +// + +import XCTest + +@testable import StylableUIKit + +final class AssetTests: XCTestCase { + + func testReturningVariantsOnAsset() { + let selectedAsset = BasicAsset(image: UIImage(), variantType: UIControl.State.selected) + let disabledAsset = BasicAsset(image: UIImage(), variantType: UIControl.State.disabled) + let normalAsset = Asset(image: UIImage(), variants: [selectedAsset, disabledAsset]) + + XCTAssertNotNil(normalAsset.variant(UIControl.State.selected)) + XCTAssertTrue(normalAsset.variant(UIControl.State.selected)?.variantType.name == selectedAsset.variantType.name) + XCTAssertNotNil(normalAsset.variant(UIControl.State.disabled)) + XCTAssertTrue(normalAsset.variant(UIControl.State.disabled)?.variantType.name == disabledAsset.variantType.name) + XCTAssertTrue(normalAsset.variant(UIControl.State.focused) == nil) + } + + func testVariantReturnsSelfOnAsset() { + let normalAsset = Asset(image: UIImage(), variants: []) + + XCTAssertTrue(normalAsset.variantType.name == UIControl.State.normal.name) + XCTAssertTrue(normalAsset.variantType.name == normalAsset.variant(UIControl.State.normal)!.variantType.name) + } + +} diff --git a/Example/Tests/Components/StylableLabelTests.swift b/Example/Tests/Components/StylableLabelTests.swift new file mode 100644 index 0000000..784d72d --- /dev/null +++ b/Example/Tests/Components/StylableLabelTests.swift @@ -0,0 +1,146 @@ +// +// StylableLabelTests.swift +// StylableUIKit_Tests +// + +import XCTest +import FBSnapshotTestCase + +@testable import StylableUIKit + +final class StylableLabelTests: FBSnapshotTestCase { + + override func setUp() { + super.setUp() + self.recordMode = false + } + + func testStylingLabel() { + let label = StylableLabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 40))) + + let style = TextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red) + label.setTextStyle(style) + label.text = "This is a test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(label) + + FBSnapshotVerifyView(label) + } + + func testStylingLabelWithRightAlignment() { + let label = StylableLabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 40))) + + let style = TextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red, textAlignment: .right) + label.setTextStyle(style) + label.text = "This is a test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(label) + + FBSnapshotVerifyView(label) + } + + func testStylistLabelWithCenterAlignment() { + let label = StylableLabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 40))) + + let style = TextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red, textAlignment: .center) + label.setTextStyle(style) + label.text = "This is a test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(label) + + FBSnapshotVerifyView(label) + } + + func testStylingLabelWithBasicTextStyle() { + let label = StylableLabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 40))) + + let style = BasicTextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red) + label.setTextStyle(style) + label.text = "This is a test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(label) + + FBSnapshotVerifyView(label) + } + + func testStylingLabelWithUppercasedTextTransform() { + let label = StylableLabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 40))) + + let style = BasicTextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red, textTransform: .uppercased) + label.setTextStyle(style) + label.text = "This is a test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(label) + + FBSnapshotVerifyView(label) + } + + func testStylingLabelWithLowercasedTextTransform() { + let label = StylableLabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 40))) + + let style = BasicTextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red, textTransform: .lowercased) + label.setTextStyle(style) + label.text = "This is a test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(label) + + FBSnapshotVerifyView(label) + } + + func testStylingLabelWithCapitalizedTextTransform() { + let label = StylableLabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 40))) + + let style = BasicTextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red, textTransform: .capitalized) + label.setTextStyle(style) + label.text = "This is a test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(label) + + FBSnapshotVerifyView(label) + } + + func testStylingLabelWithNoneTextTransform() { + let label = StylableLabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 40))) + + let style = BasicTextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red, textTransform: .none) + label.setTextStyle(style) + label.text = "This is a test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(label) + + FBSnapshotVerifyView(label) + } +} + +/* + let label = UILabel(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 40))) + label.font = .boldSystemFont(ofSize: 12) + label.textColor = .red + label.textAlignment = .center + label.backgroundColor = .yellow + label.text = "This is a test" +*/ diff --git a/Example/Tests/Components/StylableTextFieldTests.swift b/Example/Tests/Components/StylableTextFieldTests.swift new file mode 100644 index 0000000..a5cc390 --- /dev/null +++ b/Example/Tests/Components/StylableTextFieldTests.swift @@ -0,0 +1,158 @@ +// +// StylableTextFieldTests.swift +// StylableUIKit_Tests +// + +import XCTest +import FBSnapshotTestCase + +@testable import StylableUIKit + +final class StylableTextFieldTests: FBSnapshotTestCase { + + override func setUp() { + super.setUp() + self.recordMode = false + } + + func testTextFieldStyling() { + let textField = StylableTextField(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let style = TextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red) + textField.setTextStyle(style) + + textField.text = "Text for the test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(textField) + + FBSnapshotVerifyView(textField) + } + + func testTextFieldStylingWithBasicTextStyle() { + let textField = StylableTextField(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let style = BasicTextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red) + textField.setTextStyle(style) + + textField.text = "Text for the test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(textField) + + FBSnapshotVerifyView(textField) + } + + func testTextFieldStylingWithDifferentTextAlignments() { + let textField = StylableTextField(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 50))) + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + layerStyle.apply(textField) + + textField.text = "Text for the test" + + let centerStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .center, + variantType: UIControl.State.normal) + + textField.setTextStyle(centerStyle) + + FBSnapshotVerifyView(textField, identifier: "center") + + let rightStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .right, + variantType: UIControl.State.normal) + + textField.setTextStyle(rightStyle) + + FBSnapshotVerifyView(textField, identifier: "right") + + let leftStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .left, + variantType: UIControl.State.normal) + + textField.setTextStyle(leftStyle) + + FBSnapshotVerifyView(textField, identifier: "left") + } + + func testTextFieldStylingWithDifferentTextTransforms() { + let textField = StylableTextField(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 50))) + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + layerStyle.apply(textField) + + textField.text = "Text for the test" + + let lowStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .center, + textTransform: .lowercased, + variantType: UIControl.State.normal) + + textField.setTextStyle(lowStyle) + + FBSnapshotVerifyView(textField, identifier: "lowercased") + + let upStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .center, + textTransform: .uppercased, + variantType: UIControl.State.normal) + + textField.setTextStyle(upStyle) + + FBSnapshotVerifyView(textField, identifier: "uppercased") + + let capStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .center, + textTransform: .capitalized, + variantType: UIControl.State.normal) + + textField.setTextStyle(capStyle) + + FBSnapshotVerifyView(textField, identifier: "capitalized") + } + + func testTextFieldStylingWithBasicPlaceholderTextStyle() { + let textField = StylableTextField(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let style = BasicTextStyle(font: .boldSystemFont(ofSize: 20), textColor: .blue) + textField.setPlaceholderTextStyle(style) + + textField.placeholder = "Text for the test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(textField) + + FBSnapshotVerifyView(textField, identifier: "placeholder") + } +} diff --git a/Example/Tests/Components/StylableTextViewTests.swift b/Example/Tests/Components/StylableTextViewTests.swift new file mode 100644 index 0000000..4194f2d --- /dev/null +++ b/Example/Tests/Components/StylableTextViewTests.swift @@ -0,0 +1,142 @@ +// +// StylableTextViewTests.swift +// StylableUIKit_Tests +// + +import XCTest +import FBSnapshotTestCase + +@testable import StylableUIKit + +final class StylableTextViewTests: FBSnapshotTestCase { + + override func setUp() { + super.setUp() + self.recordMode = false + } + + func testStylingTextView() { + let view = StylableTextView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let style = TextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red) + view.setTextStyle(style) + + view.text = "Text for the test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(view) + + FBSnapshotVerifyView(view) + } + + func testStylingTextVieWithBasicTextStyle() { + let view = StylableTextView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let style = BasicTextStyle(font: .boldSystemFont(ofSize: 12), textColor: .red) + view.setTextStyle(style) + + view.text = "Text for the test" + + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + + layerStyle.apply(view) + + FBSnapshotVerifyView(view) + } + + func testTextFieldStylingWithDifferentTextAlignments() { + let textView = StylableTextView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 50))) + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + layerStyle.apply(textView) + + textView.text = "Text for the test" + + let centerStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .center, + variantType: UIControl.State.normal) + + textView.setTextStyle(centerStyle) + + FBSnapshotVerifyView(textView, identifier: "center") + + let rightStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .right, + variantType: UIControl.State.normal) + + textView.setTextStyle(rightStyle) + + FBSnapshotVerifyView(textView, identifier: "right") + + let leftStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .left, + variantType: UIControl.State.normal) + + textView.setTextStyle(leftStyle) + + FBSnapshotVerifyView(textView, identifier: "left") + } + + func testTextFieldStylingWithDifferentTextTransforms() { + let textView = StylableTextView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 50))) + let fill = FlatLayerFill(color: .yellow) + let layerStyle = LayerStyle(fill: fill, outline: nil) + layerStyle.apply(textView) + + textView.text = "Text for the test" + + let lowStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .center, + textTransform: .lowercased, + variantType: UIControl.State.normal) + + textView.setTextStyle(lowStyle) + + FBSnapshotVerifyView(textView, identifier: "lowercased") + + let upStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .center, + textTransform: .uppercased, + variantType: UIControl.State.normal) + + textView.setTextStyle(upStyle) + + FBSnapshotVerifyView(textView, identifier: "uppercased") + + let capStyle = BasicTextStyle(font: .boldSystemFont(ofSize: 12), + textColor: .orange, + backgroundColor: .blue, + lineSpacing: 2, + letterSpacing: 2, + textAlignment: .center, + textTransform: .capitalized, + variantType: UIControl.State.normal) + + textView.setTextStyle(capStyle) + + FBSnapshotVerifyView(textView, identifier: "capitalized") + } +} diff --git a/Example/Tests/Core/StylistIdentifierTests.swift b/Example/Tests/Core/StylistIdentifierTests.swift new file mode 100644 index 0000000..d9853ff --- /dev/null +++ b/Example/Tests/Core/StylistIdentifierTests.swift @@ -0,0 +1,20 @@ +// +// StylistIdentifierTests.swift +// StylableUIKit_Tests +// + +import XCTest + +@testable import StylableUIKit + +final class StylistIdentifierTests: XCTestCase { + + func testEquality() { + let asset1 = MockStylistAsset(name: "Test") + let asset2 = MockStylistAsset(name: "Test") + let identifier1 = EquatableStylistIdentifier(asset1) + let identifier2 = EquatableStylistIdentifier(asset2) + + XCTAssertTrue(identifier1 == identifier2) + } +} diff --git a/Example/Tests/Core/VariantTests.swift b/Example/Tests/Core/VariantTests.swift new file mode 100644 index 0000000..65b2e90 --- /dev/null +++ b/Example/Tests/Core/VariantTests.swift @@ -0,0 +1,59 @@ +// +// VariantTests.swift +// StylableUIKit_Tests +// + +import XCTest + +@testable import StylableUIKit + +final class VariantTests: XCTestCase { + + func testReturningVariantsOnAsset() { + let selected = BasicAsset(image: UIImage(), variantType: UIControl.State.selected) + let disabled = BasicAsset(image: UIImage(), variantType: UIControl.State.disabled) + let normal = Asset(image: UIImage(), variantType: UIControl.State.normal, variants: [selected, disabled]) + + XCTAssertNotNil(normal.variant(UIControl.State.selected)) + XCTAssertTrue(normal.variant(UIControl.State.selected)?.variantType.name == selected.variantType.name) + XCTAssertNotNil(normal.variant(UIControl.State.disabled)) + XCTAssertTrue(normal.variant(UIControl.State.disabled)?.variantType.name == disabled.variantType.name) + XCTAssertTrue(normal.variant(UIControl.State.focused) == nil) + + XCTAssertTrue(normal.variantOrDefault(UIControl.State.selected).variantType.name == selected.variantType.name) + XCTAssertTrue(normal.variantOrDefault(UIControl.State.disabled).variantType.name == disabled.variantType.name) + XCTAssertTrue(normal.variantOrDefault(UIControl.State.focused).variantType.name == normal.variantType.name) + } + + func testReturningVariantsOnTextStyle() { + let selected = BasicTextStyle(font: UIFont.systemFont(ofSize: 12), textColor: .red, variantType: UIControl.State.selected) + let disabled = BasicTextStyle(font: UIFont.systemFont(ofSize: 10), textColor: .darkGray, variantType: UIControl.State.disabled) + let normal = TextStyle(font: UIFont.systemFont(ofSize: 12), textColor: .black, variants: [selected, disabled]) + + XCTAssertNotNil(normal.variant(UIControl.State.selected)) + XCTAssertTrue(normal.variant(UIControl.State.selected)?.variantType.name == selected.variantType.name) + XCTAssertNotNil(normal.variant(UIControl.State.disabled)) + XCTAssertTrue(normal.variant(UIControl.State.disabled)?.variantType.name == disabled.variantType.name) + XCTAssertTrue(normal.variant(UIControl.State.focused) == nil) + + XCTAssertTrue(normal.variantOrDefault(UIControl.State.selected).variantType.name == selected.variantType.name) + XCTAssertTrue(normal.variantOrDefault(UIControl.State.disabled).variantType.name == disabled.variantType.name) + XCTAssertTrue(normal.variantOrDefault(UIControl.State.focused).variantType.name == normal.variantType.name) + } + + func testReturningVariantsOnLayerStyle() { + let selected = BasicLayerStyle(fill: FlatLayerFill(color: .yellow), variantType: UIControl.State.selected) + let disabled = BasicLayerStyle(fill: FlatLayerFill(color: .lightGray), variantType: UIControl.State.disabled) + let normal = LayerStyle(fill: FlatLayerFill(color: .white), variants: [selected, disabled]) + + XCTAssertNotNil(normal.variant(UIControl.State.selected)) + XCTAssertTrue(normal.variant(UIControl.State.selected)?.variantType.name == selected.variantType.name) + XCTAssertNotNil(normal.variant(UIControl.State.disabled)) + XCTAssertTrue(normal.variant(UIControl.State.disabled)?.variantType.name == disabled.variantType.name) + XCTAssertTrue(normal.variant(UIControl.State.focused) == nil) + // + XCTAssertTrue(normal.variantOrDefault(UIControl.State.selected).variantType.name == selected.variantType.name) + XCTAssertTrue(normal.variantOrDefault(UIControl.State.disabled).variantType.name == disabled.variantType.name) + XCTAssertTrue(normal.variantOrDefault(UIControl.State.focused).variantType.name == normal.variantType.name) + } +} diff --git a/Example/Tests/DeviceTest.swift b/Example/Tests/DeviceTest.swift new file mode 100644 index 0000000..20fe16e --- /dev/null +++ b/Example/Tests/DeviceTest.swift @@ -0,0 +1,147 @@ +// +// DeviceTest.swift +// StylableUIKit_Tests +// + +import Foundation +import XCTest + +// Observe the current test run, and if there are failures and we aren't an iPhone 8 Plus dump a warning to the console. +private final class TestObserver: NSObject, XCTestObservation { + + private var haveBeenFailures = false + + func testSuiteDidFinish(_ testSuite: XCTestSuite) { + if let run = testSuite.testRun, run.failureCount > 0 { + haveBeenFailures = true + } + } + + func testBundleDidFinish(_ testBundle: Bundle) { + guard self.haveBeenFailures else { return } + + let modelName = UIDevice.modelName + + if !modelName.contains("iPhone 8 Plus") || [UIDevice.iosMajorVersion, UIDevice.iosMinorVersion] != [12, 2] { + print(""" +⚠️ WARNING ⚠️ +The UI test reference images were created on an iPhone 8 Plus running iOS 12.2. +These tests were run on '\(modelName)' running iOS \(UIDevice.iosVersionString) so you might have seen unexpected failures. +""") + } + } +} + +// Test case with a single (passing) test so we can register an observer for the current test run in the setup method. +final class DeviceTest: XCTestCase { + + override func setUp() { + super.setUp() + + XCTestObservationCenter.shared.addTestObserver(TestObserver()) + } + + func testToTriggerDeviceValidationSetup() { + XCTAssert(true) + } +} + +// From https://stackoverflow.com/a/26962452/13000 +private extension UIDevice { + + static let iosVersionString: String = { UIDevice.current.systemVersion }() + + private static let iosVersionComponents: [Int] = { + UIDevice.current.systemVersion.split(separator: ".") + .map(String.init) + .map(Int.init) + .map { $0 ?? 0 } + }() + + private static func iosVersionComponent(at index: Int) -> Int { + let components = UIDevice.iosVersionComponents + guard components.count > index else { return 0 } + return components[index] + } + + static let iosMajorVersion: Int = { UIDevice.iosVersionComponent(at: 0) }() + + static let iosMinorVersion: Int = { UIDevice.iosVersionComponent(at: 1) }() + + static let iosPatchVersion: Int = { UIDevice.iosVersionComponent(at: 2) }() + + static let modelName: String = { + var systemInfo = utsname() + uname(&systemInfo) + let machineMirror = Mirror(reflecting: systemInfo.machine) + let identifier = machineMirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } + + func mapToDevice(identifier: String) -> String { // swiftlint:disable:this cyclomatic_complexity + #if os(iOS) + switch identifier { + case "iPod5,1": return "iPod touch (5th generation)" + case "iPod7,1": return "iPod touch (6th generation)" + case "iPod9,1": return "iPod touch (7th generation)" + case "iPhone3,1", "iPhone3,2", "iPhone3,3": return "iPhone 4" + case "iPhone4,1": return "iPhone 4s" + case "iPhone5,1", "iPhone5,2": return "iPhone 5" + case "iPhone5,3", "iPhone5,4": return "iPhone 5c" + case "iPhone6,1", "iPhone6,2": return "iPhone 5s" + case "iPhone7,2": return "iPhone 6" + case "iPhone7,1": return "iPhone 6 Plus" + case "iPhone8,1": return "iPhone 6s" + case "iPhone8,2": return "iPhone 6s Plus" + case "iPhone9,1", "iPhone9,3": return "iPhone 7" + case "iPhone9,2", "iPhone9,4": return "iPhone 7 Plus" + case "iPhone8,4": return "iPhone SE" + case "iPhone10,1", "iPhone10,4": return "iPhone 8" + case "iPhone10,2", "iPhone10,5": return "iPhone 8 Plus" + case "iPhone10,3", "iPhone10,6": return "iPhone X" + case "iPhone11,2": return "iPhone XS" + case "iPhone11,4", "iPhone11,6": return "iPhone XS Max" + case "iPhone11,8": return "iPhone XR" + case "iPhone12,1": return "iPhone 11" + case "iPhone12,3": return "iPhone 11 Pro" + case "iPhone12,5": return "iPhone 11 Pro Max" + case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return "iPad 2" + case "iPad3,1", "iPad3,2", "iPad3,3": return "iPad (3rd generation)" + case "iPad3,4", "iPad3,5", "iPad3,6": return "iPad (4th generation)" + case "iPad6,11", "iPad6,12": return "iPad (5th generation)" + case "iPad7,5", "iPad7,6": return "iPad (6th generation)" + case "iPad7,11", "iPad7,12": return "iPad (7th generation)" + case "iPad4,1", "iPad4,2", "iPad4,3": return "iPad Air" + case "iPad5,3", "iPad5,4": return "iPad Air 2" + case "iPad11,4", "iPad11,5": return "iPad Air (3rd generation)" + case "iPad2,5", "iPad2,6", "iPad2,7": return "iPad mini" + case "iPad4,4", "iPad4,5", "iPad4,6": return "iPad mini 2" + case "iPad4,7", "iPad4,8", "iPad4,9": return "iPad mini 3" + case "iPad5,1", "iPad5,2": return "iPad mini 4" + case "iPad11,1", "iPad11,2": return "iPad mini (5th generation)" + case "iPad6,3", "iPad6,4": return "iPad Pro (9.7-inch)" + case "iPad6,7", "iPad6,8": return "iPad Pro (12.9-inch)" + case "iPad7,1", "iPad7,2": return "iPad Pro (12.9-inch) (2nd generation)" + case "iPad7,3", "iPad7,4": return "iPad Pro (10.5-inch)" + case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4":return "iPad Pro (11-inch)" + case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8":return "iPad Pro (12.9-inch) (3rd generation)" + case "AppleTV5,3": return "Apple TV" + case "AppleTV6,2": return "Apple TV 4K" + case "AudioAccessory1,1": return "HomePod" + case "i386", "x86_64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))" + default: return identifier + } + #elseif os(tvOS) + switch identifier { + case "AppleTV5,3": return "Apple TV 4" + case "AppleTV6,2": return "Apple TV 4K" + case "i386", "x86_64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))" + default: return identifier + } + #endif + } + + return mapToDevice(identifier: identifier) + }() +} diff --git a/Example/Tests/Info.plist b/Example/Tests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/Example/Tests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Example/Tests/LayerStyle/LayerStyleGradientTests.swift b/Example/Tests/LayerStyle/LayerStyleGradientTests.swift new file mode 100644 index 0000000..e0d81f1 --- /dev/null +++ b/Example/Tests/LayerStyle/LayerStyleGradientTests.swift @@ -0,0 +1,68 @@ +// +// LayerStyleTests.swift +// StylableUIKit_Tests +// + +import XCTest +import FBSnapshotTestCase + +@testable import StylableUIKit + +final class LayerStyleGradientTests: FBSnapshotTestCase { + + override func setUp() { + super.setUp() + self.recordMode = false + } + + func testViewWithAxialGradientTopToBottom() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let fill = GradientLayerFill(colors: [.blue, .orange], style: .axial(direction: .topToBottom, locations: nil)) + let style = LayerStyle(fill: fill) + + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewWithRadialGradient() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let center = CGPoint(x: 0.5, y: 0.5) + let fill = GradientLayerFill(colors: [.blue, .orange], style: .radial(center: center, locations: nil)) + let style = LayerStyle(fill: fill) + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewWithGradientAppliedManyTimes() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let color1 = UIColor.red.withAlphaComponent(0.1) + let color2 = UIColor.green.withAlphaComponent(0.1) + + let fill = GradientLayerFill(colors: [color1, color2], style: .axial(direction: .topToBottom, locations: nil)) + + let style = LayerStyle(fill: fill) + + for _ in 0 ..< 50 { + style.apply(view) + } + + FBSnapshotVerifyView(view) + } + + func testViewWithAlphaChannel() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let fill = FlatLayerFill(color: UIColor(white: 1, alpha: 0.5)) + let style = LayerStyle(fill: fill, outline: nil) + + style.apply(view) + + FBSnapshotVerifyView(view) + } + +} diff --git a/Example/Tests/LayerStyle/LayerStyleImageTests.swift b/Example/Tests/LayerStyle/LayerStyleImageTests.swift new file mode 100644 index 0000000..9c2e07c --- /dev/null +++ b/Example/Tests/LayerStyle/LayerStyleImageTests.swift @@ -0,0 +1,100 @@ +// +// LayerStyleTests.swift +// StylableUIKit_Tests +// + +import XCTest +import FBSnapshotTestCase + +@testable import StylableUIKit + +final class LayerStyleImageTests: FBSnapshotTestCase { + + override func setUp() { + super.setUp() + self.recordMode = false + } + + func testViewWithImageStretched() { + let image = UIImage(named: "cut-icon", in: Bundle(for: type(of: self)), compatibleWith: nil)! + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 100))) + let fill = ImageLayerFill(image: image, resizingMode: .stretch) + + let style = LayerStyle(fill: fill) + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewWithImageStretchedWithResize() { + let image = UIImage(named: "cut-icon", in: Bundle(for: type(of: self)), compatibleWith: nil)! + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 100))) + let fill = ImageLayerFill(image: image, resizingMode: .stretch, gravity: .resize) + + let style = LayerStyle(fill: fill) + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewWithImageStretchedWithResizeAspect() { + let image = UIImage(named: "cut-icon", in: Bundle(for: type(of: self)), compatibleWith: nil)! + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 100))) + let fill = ImageLayerFill(image: image, resizingMode: .stretch, gravity: .resizeAspect) + + let style = LayerStyle(fill: fill) + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewWithImageStretchedWithResizeAspectFill() { + let image = UIImage(named: "cut-icon", in: Bundle(for: type(of: self)), compatibleWith: nil)! + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 100))) + let fill = ImageLayerFill(image: image, resizingMode: .stretch, gravity: .resizeAspectFill) + + let style = LayerStyle(fill: fill) + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewWithImageTiled() { + let image = UIImage(named: "cut-icon", in: Bundle(for: type(of: self)), compatibleWith: nil)! + let smallerImage = self.resizeImage(image, to: CGSize(width: 20, height: 20)) + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + let fill = ImageLayerFill(image: smallerImage, resizingMode: .tile) + + let style = LayerStyle(fill: fill) + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewWithImageAppliedManyTimes() { + // image being loaded has transparency, so using a container with a blue background to prove the transparency is respected + let image = UIImage(named: "bw-gradient-50perc-trans", in: Bundle(for: type(of: self)), compatibleWith: nil)! + let containerView = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + containerView.backgroundColor = .blue + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + let fill = ImageLayerFill(image: image, resizingMode: .stretch) + + let style = LayerStyle(fill: fill) + //style.apply(view) + + for _ in 0 ..< 50 { + style.apply(view) + } + + containerView.addSubview(view) + FBSnapshotVerifyView(containerView) + } + + private func resizeImage(_ image: UIImage, to size: CGSize) -> UIImage { + UIGraphicsBeginImageContextWithOptions(size, false, 0.0) + image.draw(in: CGRect(origin: .zero, size: size)) + let newImage = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return newImage + } +} diff --git a/Example/Tests/LayerStyle/LayerStyleOutlineTests.swift b/Example/Tests/LayerStyle/LayerStyleOutlineTests.swift new file mode 100644 index 0000000..1064a6a --- /dev/null +++ b/Example/Tests/LayerStyle/LayerStyleOutlineTests.swift @@ -0,0 +1,59 @@ +// +// LayerStyleTests.swift +// StylableUIKit_Tests +// + +import XCTest +import FBSnapshotTestCase + +@testable import StylableUIKit + +final class LayerStyleOutlineTests: FBSnapshotTestCase { + + override func setUp() { + super.setUp() + self.recordMode = false + } + + func testViewRedFillAndBlackDash5Outline() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + let fill = FlatLayerFill(color: UIColor.red) + let outline = DashedLayerOutline(color: UIColor.black, width: 2.0, lineDashPattern: [5, 5]) + let style = LayerStyle(fill: fill, outline: outline) + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewRedFillAndBlackDash10And5Outline() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + let fill = FlatLayerFill(color: UIColor.red) + let outline = DashedLayerOutline(color: UIColor.black, width: 2.0, lineDashPattern: [10, 5]) + let style = LayerStyle(fill: fill, outline: outline) + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewRedFillAndBlueDashEmptyPatternOutline() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + let fill = FlatLayerFill(color: UIColor.red) + let outline = DashedLayerOutline(color: UIColor.blue, width: 2.0, lineDashPattern: []) + let style = LayerStyle(fill: fill, outline: outline) + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewWithRadialGradientAndBlackDash10And5Outline() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let center = CGPoint(x: 0.5, y: 0.5) + let fill = GradientLayerFill(colors: [.blue, .orange], style: .radial(center: center, locations: nil)) + let outline = DashedLayerOutline(color: UIColor.black, width: 2.0, lineDashPattern: [10, 5]) + let style = LayerStyle(fill: fill, outline: outline) + style.apply(view) + + FBSnapshotVerifyView(view) + } +} diff --git a/Example/Tests/LayerStyle/LayerStyleRoundedCornerTests.swift b/Example/Tests/LayerStyle/LayerStyleRoundedCornerTests.swift new file mode 100644 index 0000000..4f723e4 --- /dev/null +++ b/Example/Tests/LayerStyle/LayerStyleRoundedCornerTests.swift @@ -0,0 +1,94 @@ +// +// LayerStyleRoundedCornerTests.swift +// StylableUIKit_Tests +// + +import XCTest +import FBSnapshotTestCase + +@testable import StylableUIKit + +class LayerStyleRoundedCornerTests: FBSnapshotTestCase { + + override func setUp() { + super.setUp() + self.recordMode = false + } + + func viewWithOutline(_ outline: LayerOutline) -> (UIView, UIView) { + let view = UIView(frame: CGRect(origin: CGPoint(x: 20, y: 20), size: CGSize(width: 100, height: 100))) + let fill = FlatLayerFill(color: UIColor.green) + let style = LayerStyle(fill: fill, outline: outline) + view.layer.cornerRadius = 20.0 + view.layer.masksToBounds = true + style.apply(view) + // + let container = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 140, height: 140))) + container.backgroundColor = .white + container.addSubview(view) + return (container, view) + } + + func testLinearOutlineWithRoundedCorners() { + let outline = LinearLayerOutline(color: UIColor.black, width: 2.0) + let (view, _) = viewWithOutline(outline) + + FBSnapshotVerifyView(view) + } + + func testDottedOutlineWithRoundedCorners() { + let outline = DashedLayerOutline(color: UIColor.black, width: 2.0, lineDashPattern: [5, 5]) + let (view, _) = viewWithOutline(outline) + + FBSnapshotVerifyView(view) + } + + func testResizingLinearOutlineWithRoundedCorners() { + let outline = LinearLayerOutline(color: UIColor.black, width: 2.0) + let (container, view) = viewWithOutline(outline) + + view.frame = CGRect(x: 10, y: 10, width: 120, height: 120) + + FBSnapshotVerifyView(container) + } + + func testResizingDottedOutlineWithRoundedCorners() { + let outline = DashedLayerOutline(color: UIColor.black, width: 2.0, lineDashPattern: [5, 5]) + let (container, view) = viewWithOutline(outline) + + view.frame = CGRect(x: 10, y: 10, width: 120, height: 120) + + FBSnapshotVerifyView(container) + } + + func testResizingALoadOfDottedOutlineWithRoundedCorners() { + // There is a bug / issue with using UIBezierPath(roundedRect:cornerRadius:) + // as after a point it decides to just draw a circle instead of the specified shape + // as that's not causing any in-the-wild issues, this can be a future bug fix, but this test is here to highlight it. + + let prefs: [CGFloat] = [10.0, 20.0, 30.0, 31.0, 32.0, 33.0, 35.0, 40.0, 45.0, 50.0, 60.0, 70.0] + let views: [UIView] = prefs.map { cornerRadius in + let view = UIView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 100, height: 100))) + let style = LayerStyle(fill: FlatLayerFill(color: UIColor.red), + outline: DashedLayerOutline(color: UIColor.black, width: 2.0, lineDashPattern: [5, 5])) + view.layer.cornerRadius = cornerRadius + let label = UILabel(frame: CGRect(x: 30, y: 30, width: 40, height: 20)) + label.text = "\(cornerRadius)" + view.addSubview(label) + style.apply(view) + return view + } + + let container = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 40 + (110 * views.count), height: 140))) + container.backgroundColor = .white + + for (index, view) in views.enumerated() { + view.frame.origin.x = 20 + (CGFloat(integerLiteral: index) * 110) + view.frame.origin.y = 20 + container.addSubview(view) + } + + FBSnapshotVerifyView(container) + } + +} diff --git a/Example/Tests/LayerStyle/LayerStyleTests.swift b/Example/Tests/LayerStyle/LayerStyleTests.swift new file mode 100644 index 0000000..2ec59bf --- /dev/null +++ b/Example/Tests/LayerStyle/LayerStyleTests.swift @@ -0,0 +1,55 @@ +// +// LayerStyleTests.swift +// StylableUIKit_Tests +// + +import XCTest +import FBSnapshotTestCase + +@testable import StylableUIKit + +final class LayerStyleTests: FBSnapshotTestCase { + + override func setUp() { + super.setUp() + self.recordMode = false + } + + func testViewWithRedFill() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let fill = FlatLayerFill(color: .red) + let style = LayerStyle(fill: fill, outline: nil) + + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewWithRedFillGreenOutline() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 100))) + + let fill = FlatLayerFill(color: .red) + let outline = LinearLayerOutline(color: .green, width: 2.0) + let style = LayerStyle(fill: fill, outline: outline) + + style.apply(view) + + FBSnapshotVerifyView(view) + } + + func testViewWithBasicFillAndOutlineThatResizes() { + let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 50, height: 50))) + + let fill = FlatLayerFill(color: .red) + let outline = LinearLayerOutline(color: .green, width: 2.0) + let style = LayerStyle(fill: fill, outline: outline) + + style.apply(view) + + view.frame.size = CGSize(width: 100, height: 100) + + FBSnapshotVerifyView(view) + } + +} diff --git a/Example/Tests/Mocks/StylistMocks.swift b/Example/Tests/Mocks/StylistMocks.swift new file mode 100644 index 0000000..6d12d42 --- /dev/null +++ b/Example/Tests/Mocks/StylistMocks.swift @@ -0,0 +1,12 @@ +// +// StylistMocks.swift +// Stylable-UIKit_Example +// + +import Foundation + +@testable import StylableUIKit + +struct MockStylistAsset: StylistAsset, Equatable { + let name: String +} diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithAlphaChannel@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithAlphaChannel@2x.png new file mode 100644 index 0000000..a3c0bf8 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithAlphaChannel@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithAxialGradientTopToBottom@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithAxialGradientTopToBottom@2x.png new file mode 100644 index 0000000..0d263f6 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithAxialGradientTopToBottom@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithGradientAppliedManyTimes@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithGradientAppliedManyTimes@2x.png new file mode 100644 index 0000000..a8d0722 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithGradientAppliedManyTimes@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithRadialGradient@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithRadialGradient@2x.png new file mode 100644 index 0000000..ff1e58e Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleGradientTests/testViewWithRadialGradient@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageAppliedManyTimes@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageAppliedManyTimes@2x.png new file mode 100644 index 0000000..8a4735a Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageAppliedManyTimes@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretched@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretched@2x.png new file mode 100644 index 0000000..8ad9557 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretched@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretchedWithResize@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretchedWithResize@2x.png new file mode 100644 index 0000000..ad03715 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretchedWithResize@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretchedWithResizeAspect@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretchedWithResizeAspect@2x.png new file mode 100644 index 0000000..8ad9557 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretchedWithResizeAspect@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretchedWithResizeAspectFill@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretchedWithResizeAspectFill@2x.png new file mode 100644 index 0000000..faba430 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageStretchedWithResizeAspectFill@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageTiled@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageTiled@2x.png new file mode 100644 index 0000000..f279db1 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleImageTests/testViewWithImageTiled@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewRedFillAndBlackDash10And5Outline@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewRedFillAndBlackDash10And5Outline@2x.png new file mode 100644 index 0000000..59b14cb Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewRedFillAndBlackDash10And5Outline@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewRedFillAndBlackDash5Outline@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewRedFillAndBlackDash5Outline@2x.png new file mode 100644 index 0000000..8f98a39 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewRedFillAndBlackDash5Outline@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewRedFillAndBlueDashEmptyPatternOutline@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewRedFillAndBlueDashEmptyPatternOutline@2x.png new file mode 100644 index 0000000..344b1af Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewRedFillAndBlueDashEmptyPatternOutline@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewWithRadialGradientAndBlackDash10And5Outline@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewWithRadialGradientAndBlackDash10And5Outline@2x.png new file mode 100644 index 0000000..39801fe Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleOutlineTests/testViewWithRadialGradientAndBlackDash10And5Outline@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testDottedOutlineWithRoundedCorners@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testDottedOutlineWithRoundedCorners@2x.png new file mode 100644 index 0000000..0748f4f Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testDottedOutlineWithRoundedCorners@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testLinearOutlineWithRoundedCorners@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testLinearOutlineWithRoundedCorners@2x.png new file mode 100644 index 0000000..6e19e36 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testLinearOutlineWithRoundedCorners@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testResizingALoadOfDottedOutlineWithRoundedCorners@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testResizingALoadOfDottedOutlineWithRoundedCorners@2x.png new file mode 100644 index 0000000..033d932 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testResizingALoadOfDottedOutlineWithRoundedCorners@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testResizingDottedOutlineWithRoundedCorners@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testResizingDottedOutlineWithRoundedCorners@2x.png new file mode 100644 index 0000000..41f9545 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testResizingDottedOutlineWithRoundedCorners@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testResizingLinearOutlineWithRoundedCorners@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testResizingLinearOutlineWithRoundedCorners@2x.png new file mode 100644 index 0000000..7cb8356 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleRoundedCornerTests/testResizingLinearOutlineWithRoundedCorners@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleTests/testViewWithBasicFillAndOutlineThatResizes@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleTests/testViewWithBasicFillAndOutlineThatResizes@2x.png new file mode 100644 index 0000000..32ee662 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleTests/testViewWithBasicFillAndOutlineThatResizes@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleTests/testViewWithRedFill@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleTests/testViewWithRedFill@2x.png new file mode 100644 index 0000000..77cf9af Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleTests/testViewWithRedFill@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleTests/testViewWithRedFillGreenOutline@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleTests/testViewWithRedFillGreenOutline@2x.png new file mode 100644 index 0000000..32ee662 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.LayerStyleTests/testViewWithRedFillGreenOutline@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabel@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabel@2x.png new file mode 100644 index 0000000..7e9caef Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabel@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithBasicTextStyle@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithBasicTextStyle@2x.png new file mode 100644 index 0000000..7e9caef Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithBasicTextStyle@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithCapitalizedTextTransform@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithCapitalizedTextTransform@2x.png new file mode 100644 index 0000000..6165e86 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithCapitalizedTextTransform@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithLowercasedTextTransform@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithLowercasedTextTransform@2x.png new file mode 100644 index 0000000..c6f4997 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithLowercasedTextTransform@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithNoneTextTransform@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithNoneTextTransform@2x.png new file mode 100644 index 0000000..7e9caef Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithNoneTextTransform@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithRightAlignment@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithRightAlignment@2x.png new file mode 100644 index 0000000..b5cf17b Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithRightAlignment@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithUppercasedTextTransform@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithUppercasedTextTransform@2x.png new file mode 100644 index 0000000..811e0c5 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylingLabelWithUppercasedTextTransform@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylistLabelWithCenterAlignment@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylistLabelWithCenterAlignment@2x.png new file mode 100644 index 0000000..1727a07 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableLabelTests/testStylistLabelWithCenterAlignment@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStyling@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStyling@2x.png new file mode 100644 index 0000000..1d5edf2 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStyling@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithBasicPlaceholderTextStyle_placeholder@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithBasicPlaceholderTextStyle_placeholder@2x.png new file mode 100644 index 0000000..60f6471 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithBasicPlaceholderTextStyle_placeholder@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithBasicTextStyle@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithBasicTextStyle@2x.png new file mode 100644 index 0000000..1d5edf2 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithBasicTextStyle@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextAlignments_center@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextAlignments_center@2x.png new file mode 100644 index 0000000..59f706f Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextAlignments_center@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextAlignments_left@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextAlignments_left@2x.png new file mode 100644 index 0000000..d0a09b1 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextAlignments_left@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextAlignments_right@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextAlignments_right@2x.png new file mode 100644 index 0000000..69fe853 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextAlignments_right@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextTransforms_capitalized@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextTransforms_capitalized@2x.png new file mode 100644 index 0000000..9f673a7 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextTransforms_capitalized@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextTransforms_lowercased@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextTransforms_lowercased@2x.png new file mode 100644 index 0000000..d4ba8e9 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextTransforms_lowercased@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextTransforms_uppercased@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextTransforms_uppercased@2x.png new file mode 100644 index 0000000..f1022f1 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextFieldTests/testTextFieldStylingWithDifferentTextTransforms_uppercased@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testStylingTextVieWithBasicTextStyle@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testStylingTextVieWithBasicTextStyle@2x.png new file mode 100644 index 0000000..a40443b Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testStylingTextVieWithBasicTextStyle@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testStylingTextView@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testStylingTextView@2x.png new file mode 100644 index 0000000..a40443b Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testStylingTextView@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextAlignments_center@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextAlignments_center@2x.png new file mode 100644 index 0000000..e89b56b Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextAlignments_center@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextAlignments_left@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextAlignments_left@2x.png new file mode 100644 index 0000000..0b0ddee Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextAlignments_left@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextAlignments_right@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextAlignments_right@2x.png new file mode 100644 index 0000000..004f420 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextAlignments_right@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextTransforms_capitalized@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextTransforms_capitalized@2x.png new file mode 100644 index 0000000..5b175ab Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextTransforms_capitalized@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextTransforms_lowercased@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextTransforms_lowercased@2x.png new file mode 100644 index 0000000..131d1bf Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextTransforms_lowercased@2x.png differ diff --git a/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextTransforms_uppercased@2x.png b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextTransforms_uppercased@2x.png new file mode 100644 index 0000000..7a53512 Binary files /dev/null and b/Example/Tests/ReferenceImages_64/StylableUIKit_Tests.StylableTextViewTests/testTextFieldStylingWithDifferentTextTransforms_uppercased@2x.png differ diff --git a/Example/Tests/TestImages.xcassets/Contents.json b/Example/Tests/TestImages.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Example/Tests/TestImages.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/Tests/TestImages.xcassets/bw-gradient-50perc-trans.imageset/Contents.json b/Example/Tests/TestImages.xcassets/bw-gradient-50perc-trans.imageset/Contents.json new file mode 100644 index 0000000..ab85436 --- /dev/null +++ b/Example/Tests/TestImages.xcassets/bw-gradient-50perc-trans.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "bw-gradient-50perc-trans.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "original" + } +} \ No newline at end of file diff --git a/Example/Tests/TestImages.xcassets/bw-gradient-50perc-trans.imageset/bw-gradient-50perc-trans.png b/Example/Tests/TestImages.xcassets/bw-gradient-50perc-trans.imageset/bw-gradient-50perc-trans.png new file mode 100644 index 0000000..5c10db2 Binary files /dev/null and b/Example/Tests/TestImages.xcassets/bw-gradient-50perc-trans.imageset/bw-gradient-50perc-trans.png differ diff --git a/Example/Tests/TestImages.xcassets/cut-icon.imageset/Contents.json b/Example/Tests/TestImages.xcassets/cut-icon.imageset/Contents.json new file mode 100644 index 0000000..b967c1c --- /dev/null +++ b/Example/Tests/TestImages.xcassets/cut-icon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "discount-ticket.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/Tests/TestImages.xcassets/cut-icon.imageset/discount-ticket.pdf b/Example/Tests/TestImages.xcassets/cut-icon.imageset/discount-ticket.pdf new file mode 100644 index 0000000..7d35c6a Binary files /dev/null and b/Example/Tests/TestImages.xcassets/cut-icon.imageset/discount-ticket.pdf differ diff --git a/Example/swiftlint.yml b/Example/swiftlint.yml new file mode 100644 index 0000000..9495d8b --- /dev/null +++ b/Example/swiftlint.yml @@ -0,0 +1,57 @@ +excluded: + - Pods/ + +included: + - ../StylableUIKit + +disabled_rules: + - todo + - discarded_notification_center_observer + - duplicate_imports + +opt_in_rules: + - private_outlet + +identifier_name: + excluded: + - id + +line_length: 160 + +large_tuple: 3 + +### When we started consistently formatting our RxSwift, we hit these limits pretty quickly. We don't want to disable them completely, but we do want to +### acknowledge that we do write verbose RxSwift code. + +function_body_length: + warning: 100 + +file_length: + warning: 1000 + +type_body_length: + warning: 300 + +### Some rules we made up ourselves + +custom_rules: + parenthesis_spacing: + included: ".*.swift" + name: "There should be no spacing inside parenthesis" + regex: "\\( | \\)" + message: "Prefer (arg) vs ( arg )" + match_kinds: + - argument + - attribute.builtin + - attribute.id + - buildconfig.id + - buildconfig.keyword + - identifier + - keyword + - number + - objectliteral + - parameter + - placeholder + - string + - string_interpolation_anchor + - typeidentifier diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..25e96c4 --- /dev/null +++ b/Gemfile @@ -0,0 +1,5 @@ +source "https://rubygems.org" + +ruby "2.6.5" + +gem "cocoapods" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..92c0e99 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,86 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.1) + activesupport (4.2.11.1) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + algoliasearch (1.27.1) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + claide (1.0.3) + cocoapods (1.8.4) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.8.4) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.6.6) + nap (~> 1.0) + ruby-macho (~> 1.4) + xcodeproj (>= 1.11.1, < 2.0) + cocoapods-core (1.8.4) + activesupport (>= 4.0.2, < 6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.3.0) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.1.0) + cocoapods-trunk (1.4.1) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.1.0) + colored2 (3.1.2) + concurrent-ruby (1.1.5) + escape (0.0.4) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.8.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + json (2.2.0) + minitest (5.13.0) + molinillo (0.6.6) + nanaimo (0.2.6) + nap (1.1.0) + netrc (0.11.0) + ruby-macho (1.4.0) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + xcodeproj (1.13.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods + +RUBY VERSION + ruby 2.6.5p114 + +BUNDLED WITH + 2.1.4 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e63e1eb --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Design Operations Collective + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 THE +AUTHORS OR COPYRIGHT HOLDERS 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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7dc1610 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ + +## Example + +To run the example project, clone the repo, and run `pod install` from the Example directory first. + +## Installation + +StylableUIKit is available through [CocoaPods](https://cocoapods.org). To install +it, simply add the following line to your Podfile: + +```ruby +pod 'StylableUIKit' +``` + +## Running tests + +The tests are mostly UI based, so you have to run them on an iPhone 8+, iOS 12.2 or the slight variation will break the image comparisons. diff --git a/StylableUIKit.podspec b/StylableUIKit.podspec new file mode 100644 index 0000000..cce10fc --- /dev/null +++ b/StylableUIKit.podspec @@ -0,0 +1,30 @@ +Pod::Spec.new do |s| + s.name = "StylableUIKit" + s.version = "1.0.0" + s.summary = "A protocol for applying design to iOS apps following Atomic Design Theory" + s.description = <<-DESC + A library which allows iOS apps to be skinned / themed following Atomic Design Theory + DESC + s.homepage = "https://github.com/design-ops/stylable-uikit" + s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" } + s.authors = "atomoil", "deanWomborne", "kerrmarin", "jdbarbosa", "cristianoalves92" + s.platform = :ios, "10.0" + s.source = { :git => "https://github.com/design-ops/stylable-uikit.git", :tag => "v#{s.version}" } + + s.frameworks = [ 'Foundation', 'UIKit' ] + s.swift_version = '5.0' + s.default_subspec = 'Core' + + s.subspec 'Core' do |core| + # subspec for users who don't want to use Lottie + core.source_files = "StylableUIKit/", "StylableUIKit/Classes/**/*.swift" + core.exclude_files = "StylableUIKit/Classes/Exclude", "StylableUIKit/Classes/Subspec" + end + + s.subspec 'Lottie' do |lottie| + lottie.dependency 'lottie-ios', '~> 3.1' + lottie.dependency 'StylableUIKit/Core' + lottie.source_files = "StylableUIKit/Classes/Subspec/Lottie" + lottie.compiler_flags = "-DSTYLABLE_SUPPORTS_LOTTIE" + end +end diff --git a/StylableUIKit/Classes/Assets/AnimatedAsset.swift b/StylableUIKit/Classes/Assets/AnimatedAsset.swift new file mode 100644 index 0000000..4f4971a --- /dev/null +++ b/StylableUIKit/Classes/Assets/AnimatedAsset.swift @@ -0,0 +1,44 @@ +// +// AnimatedAsset.swift +// StylableUIKit +// + +import Foundation + +public typealias StylistAnimatedAsset = UIView & AnimatedAssetMethods + +public protocol AnimatedAssetMethods { + /// play an animation once from the beginning + func playOnce() + /// play an animation once from 'fromProgress' to 'toProgress' and call 'completion' when done + func playOnce(fromProgress: CGFloat, toProgress: CGFloat, completion: ((Bool) -> Void)?) + /// play an animation from the beginning and when it reaches the end return to the beginning and continue playing + func playLooped() + /// play an animation from 'fromProgress' and when it reaches 'toProgress' return to 'fromProgress' and continue playing + func playLooped(fromProgress: CGFloat, toProgress: CGFloat) + /// stop the animation playing and return to the first frame + func stop() +} + +/// This extension allows UIImageViews to be returned from Stylist's `animatedAsset()` method +extension UIImageView: AnimatedAssetMethods { + + public func playOnce(fromProgress: CGFloat, toProgress: CGFloat, completion: ((Bool) -> Void)?) { } + + public func playLooped(fromProgress: CGFloat, toProgress: CGFloat) { } + + public func stop() { } + +} + +/// Provide convenience shortcuts for playOnce and playLooped +public extension AnimatedAssetMethods { + + func playOnce() { + self.playOnce(fromProgress: 0, toProgress: 1, completion: nil) + } + + func playLooped() { + self.playLooped(fromProgress: 0, toProgress: 1) + } +} diff --git a/StylableUIKit/Classes/Assets/Asset.swift b/StylableUIKit/Classes/Assets/Asset.swift new file mode 100644 index 0000000..2e93ef9 --- /dev/null +++ b/StylableUIKit/Classes/Assets/Asset.swift @@ -0,0 +1,38 @@ +// +// UIImage+Variants +// StylableUIKit +// + +import UIKit + +public final class Asset: BasicAsset, VariantStyleProvider { + public func variant(_ variant: StylistVariant) -> BasicAsset? { + return self.getVariant(variant, from: self.variants) + } + + public func variantOrDefault(_ variant: StylistVariant) -> BasicAsset { + return self.variant(variant) ?? self + } + + private var variants: [BasicAsset] = [] + + public init(image: UIImage, + variantType: StylistVariant = UIControl.State.normal, + variants: [BasicAsset] = []) { + super.init(image: image, + variantType: variantType) + self.variants = variants + } +} + +public class BasicAsset: VariantStyle { + public let image: UIImage + + public private(set) var variantType: StylistVariant + + public init(image: UIImage, + variantType: StylistVariant = UIControl.State.normal) { + self.image = image + self.variantType = variantType + } +} diff --git a/StylableUIKit/Classes/Components/StylableButton.swift b/StylableUIKit/Classes/Components/StylableButton.swift new file mode 100644 index 0000000..88e131d --- /dev/null +++ b/StylableUIKit/Classes/Components/StylableButton.swift @@ -0,0 +1,310 @@ + +import Foundation +import UIKit + +/// `StylistMode`, but for buttons. +public protocol ButtonMode: StylistMode { + var element: StylistElement { get } + var textStyle: StylistTextStyle { get } + var layerStyle: StylistLayerStyle? { get } +} + +public extension ButtonMode { + var name: String { self.element.name } + var layerStyle: StylistLayerStyle? { nil } +} + +/// Workaround for Swift not liking protocols as concrete types. +/// +/// You shouldn't ever need to see this type in your code, it's only public because Swift requires it. +/// +/// Ideally we would just have `ButtonMode` but Swift doesn't like it not being a concrete type when +/// we implement `StylableWithMode` on `StylableButton` so we have to wrap it here. There's an identical method on `StylableButton` which _does_ take in a +/// `ButtonMode` so you should really only need to call that; it will deal with the wrapping for you. +/// +public struct ButtonModeWrapper: ButtonMode { + + private let _element: () -> StylistElement + private let _textStyle: () -> StylistTextStyle + private let _layerStyle: () -> StylistLayerStyle? + private let _name: () -> String + + public var element: StylistElement { self._element() } + public var textStyle: StylistTextStyle { self._textStyle() } + public var layerStyle: StylistLayerStyle? { self._layerStyle() } + public var name: String { self._name() } + + init(_ wrapping: ButtonMode) { + self._textStyle = { wrapping.textStyle } + self._layerStyle = { wrapping.layerStyle } + self._element = { wrapping.element } + self._name = { wrapping.name } + } +} + +/// This UIButton can be styled with a stylist, which will set it's background, and text styles for the various UIControl.State values. The button should +/// then behave exactly as if it were a UIButton i.e. `setTitle(_:for:)` can be called and the only behaviour change would be the extra styling. +/// +/// Once the button has been styled, animated assets can be added/removed for the states using `setAnimatedAsset(_:for:)`. +open class StylableButton: UIButton, StylableWithMode { + + open override var isEnabled: Bool { + get { + return super.isEnabled + } + set { + super.isEnabled = newValue + self.updateAnimatedView() + self.updateLayerStyle() + } + } + + open override var isHighlighted: Bool { + get { + return super.isHighlighted + } + set { + super.isHighlighted = newValue + self.updateAnimatedView() + self.updateLayerStyle() + } + } + + open override var isSelected: Bool { + get { + return super.isSelected + } + set { + super.isSelected = newValue + self.updateAnimatedView() + self.updateLayerStyle() + } + } + + // MARK: Styling + + private var stylist: (stylist: Stylist, section: StylistSection)? + + /// Apply a stylist to the button. + /// + /// - note: This won't retroactively style titles which have already been set, call this early on! + /// + /// - parameter mode: The button's mode (i.e. primary, secondary etc). These modes aren't provided by this library, we don't know what your app looks like. + /// - parameter stylist: The stylist used to apply the styles for the specified mode + /// - parameter section: The app section this button will be in. + /// + public func style(mode: ButtonMode, stylist: Stylist, section: StylistSection) { + self.style(mode: ButtonModeWrapper(mode), stylist: stylist, section: section) + } + + /// Apply a stylist to the button. + /// + /// You almost certainly won't call this method, but will call the other version, passing in a ButtonMode as the mode parameter. + /// + /// - note: This won't retroactively style titles which have already been set, call this early on! + public func style(mode: ButtonModeWrapper, stylist: Stylist, section: StylistSection) { + self.stylist = (stylist, section) + + self.textStyle = stylist.textStyle(mode.textStyle, element: mode.element, section: section) + if let layerStyle = mode.layerStyle { + self.layerStyle = stylist.layerStyle(layerStyle, element: mode.element, section: section) + } + } + + // MARK: Text Styles + + private var textStyle: TextStyle? { + didSet { + // Reset all the state's text styles with the new text style + [UIControl.State.normal, .highlighted, .selected, .disabled].forEach { state in + let title = self.title(for: state) + self.setTitle(title, for: state) + } + } + } + + // Returns the best text style variant for a state, falling back to the text style of `.normal`. + private func textStyle(for state: UIControl.State) -> BasicTextStyle? { + return self.textStyle?.variant(state) ?? self.textStyle?.variant(UIControl.State.normal) + } + + // MARK: Layer Styles + + private var layerStyle: LayerStyle? { + didSet { + self.updateLayerStyle() + } + } + + private func layerStyle(for state: UIControl.State) -> BasicLayerStyle? { + return self.layerStyle?.variant(state) ?? self.layerStyle?.variant(UIControl.State.normal) + } + + private func updateLayerStyle() { + // First check if we are enabled + guard self.isEnabled else { + self.layerStyle(for: .disabled)?.apply(self) + return + } + + if self.isHighlighted { + self.layerStyle(for: .highlighted)?.apply(self) + return + } + + if self.isSelected { + self.layerStyle(for: .selected)?.apply(self) + return + } + + self.layerStyle(for: .normal)?.apply(self) + } + + // MARK: Animated assets + + private var animatedAssets: [UIControl.State.RawValue: StylistAsset] = [:] + + private var animatedAssetView: UIView? { + willSet { + self.animatedAssetView?.removeFromSuperview() + } + didSet { + if let view = self.animatedAssetView, let imageView = self.imageView { + view.translatesAutoresizingMaskIntoConstraints = false + view.isUserInteractionEnabled = false + self.addSubview(view) + + let aspectRatio = view.bounds.size.width / view.bounds.size.height + + NSLayoutConstraint.activate([ + view.widthAnchor.constraint(equalTo: view.heightAnchor, multiplier: aspectRatio), + view.centerXAnchor.constraint(equalTo: imageView.centerXAnchor), + view.centerYAnchor.constraint(equalTo: imageView.centerYAnchor), + view.widthAnchor.constraint(lessThanOrEqualTo: self.widthAnchor, multiplier: 1), + view.heightAnchor.constraint(lessThanOrEqualTo: self.heightAnchor, multiplier: 1) + ]) + } + } + } + + private func updateAnimatedView() { + guard + let stylist = self.stylist, + let asset = self.animatedAssets[self.state.rawValue] else { + self.animatedAssetView = nil + return + } + + self.animatedAssetView = stylist.stylist.animatedAsset(asset, element: nil, section: stylist.section) + } + + /// Sets the animated asset for a give state. This will only work as expected if `style(mode:stylist:section:)` has already been called. + open func setAnimatedAsset(_ asset: StylistAsset?, for state: UIControl.State) { + self.animatedAssets[state.rawValue] = asset + + if asset != nil { + self.setImage(UIImage(), for: state) + } + + if state == self.state { + self.updateAnimatedView() + } + } + + // MARK: Overrides + + open override func setTitle(_ title: String?, for state: UIControl.State) { + guard var string = self.textStyle(for: state)?.apply(title) else { + print("⚠️ StylableButton has no text style for '\(title ?? "")' - have you called style(mode:stylist:section:) yet?") + super.setTitle(title, for: state) + return + } + + if self.titleLabel?.numberOfLines == 1 { + string = string.singleLine() + } + super.setTitle(title, for: state) + super.setAttributedTitle(string, for: state) + + // If we are setting the style for normal, we need to set the style for other states of the control, + // but only if they haven't been set yet, and there is a variant for that style in our root text style + if state == .normal { + [UIControl.State.focused, .highlighted, .disabled, .selected].forEach { state in + guard self.title(for: state) != title else { return } + self.setTitle(title, for: state) + } + } + } + + /// We need to make 100% sure that the background layer style is always underneath anything that UIButton wants to lazily add in the future. + + private weak var layerStylableView: UIView? + + private var hasLayerStylableView: Bool { + return self.layerStylableView != nil + } + + override open func getLayerStylableView() -> UIView { + if let view = self.layerStylableView { return view } + + let view = UIView() + view.backgroundColor = .clear + view.isOpaque = false + view.isUserInteractionEnabled = false + view.frame = self.bounds + view.autoresizingMask = [ .flexibleWidth, .flexibleHeight ] + self.insertSubview(view, at: 0) + self.layerStylableView = view + + return view + } + + override open func insertSubview(_ view: UIView, at index: Int) { + super.insertSubview(view, at: index) + + if self.hasLayerStylableView && view != self.layerStylableView { + self.sendSubviewToBack(self.getLayerStylableView()) + } + } + + override open func sendSubviewToBack(_ view: UIView) { + super.sendSubviewToBack(view) + + if self.hasLayerStylableView && view != self.layerStylableView { + self.sendSubviewToBack(self.getLayerStylableView()) + } + } + + override open func insertSubview(_ view: UIView, belowSubview siblingSubview: UIView) { + super.insertSubview(view, belowSubview: siblingSubview) + + if self.hasLayerStylableView && view != self.layerStylableView { + self.sendSubviewToBack(self.getLayerStylableView()) + } + } + + // Multi-line UIButton's don't seem to play well with UIStackViews. I'm not sure if its + // because of the intrinsic content size or some other property. This fixes the symptoms, but the + // problem is probably fixed by submitting a radar (radar: 6155421) + // As a result of the intrinsicContentSize not being constrained to its parent (which is correct!), + // the height of the button is wrong when you have multiple lines of text. + open override var intrinsicContentSize: CGSize { + let imageViewSize = self.imageView?.intrinsicContentSize ?? .zero + let titleLabelSize = self.titleLabel?.sizeThatFits(self.bounds.size) ?? .zero + + let width = self.contentEdgeInsets.left + + self.imageEdgeInsets.left + + imageViewSize.width + + self.imageEdgeInsets.right + + self.titleEdgeInsets.left + + titleLabelSize.width + + self.titleEdgeInsets.right + + self.contentEdgeInsets.right + + let height = max(imageViewSize.height + self.imageEdgeInsets.top + self.imageEdgeInsets.bottom, + titleLabelSize.height + self.titleEdgeInsets.top + self.titleEdgeInsets.bottom) + + return CGSize(width: width, + height: self.contentEdgeInsets.top + height + self.contentEdgeInsets.bottom) + } +} diff --git a/StylableUIKit/Classes/Components/StylableLabel.swift b/StylableUIKit/Classes/Components/StylableLabel.swift new file mode 100644 index 0000000..037a4d4 --- /dev/null +++ b/StylableUIKit/Classes/Components/StylableLabel.swift @@ -0,0 +1,109 @@ +// +// StylableLabel.swift +// StylableUIKit +// + +import Foundation +import UIKit + +/// A UILabel subclass that is stylable. Setting the text or attributed text will automatically +/// style this label with the text style it has, if any. + +public final class StylableLabel: UILabel { + + private var textStyle: BasicTextStyle? + + override public var text: String? { + didSet { + guard self.text != oldValue, + let textStyle = self.textStyle else { return } + self.applyTextStyle(textStyle) + self.applyMultilineString() + } + } + + override public var attributedText: NSAttributedString? { + set { + super.attributedText = newValue.map { self.applyTextStyleTo(attributedText: $0) } + self.applyMultilineString() + } + get { + return super.attributedText + } + } + + public func setTextStyle(_ textStyle: BasicTextStyle) { + self.textStyle = textStyle + self.applyTextStyle(textStyle) + } + + private func applyTextStyle(_ textStyle: BasicTextStyle) { + self.attributedText = self.attributedText.map { self.applyTextStyleTo(attributedText: $0) } + } + + private func applyTextStyleTo(attributedText: NSAttributedString) -> NSAttributedString { + guard let textStyle = self.textStyle else { return attributedText } + return textStyle.apply(attributedText).applyingLineBreakMode(self.lineBreakMode) + } +} + +extension StylableLabel { + + /// Applies text with a given style to a Label, and if that text is only one line it removes the line spacing from the label + /// to help layout. Otherwise UIKit will add the line spacing to the bottom of a single line label, which is fantastically bad + /// for layouts. + /// + fileprivate func applyMultilineString() { + guard + self.text != nil, + let textStyle = self.textStyle else { + return + } + + // If that style results in a single line, remove the line height + if self.isSingleLine && textStyle.lineSpacing > 0 { + super.attributedText = self.attributedText?.applyingLineSpacing(0) + } + } + + /// This returns `true` if the attributed text in the label can be rendered in the current `bounds.width` of the label. Otherwise, it returns `false`. + /// + /// - warning: This, obviously, requires rendering some text twice. It's exactly as performant as you would expect. + + /// - warning: We need to be careful with how we've set the Line Break Mode. + /// i.e. if we're rendering a label which Line Break Mode is Truncate Tail, its height will still be as if it had only one line of text: + + /// "This is an example..." + + /// when, what we might want to render is: + + /// "This is an example of + /// a big text for a small + /// label to check if it + /// should be more than + /// one line" + + private var isSingleLine: Bool { + + guard + let attributedText = self.attributedText, + self.numberOfLines != 1 else { + return true + } + + // Get the height of the wrapped text + let wrappedSize = CGSize(width: self.bounds.size.width, height: CGFloat.greatestFiniteMagnitude) + let wrappedRect = attributedText.boundingRect(with: wrappedSize, + options: [ .usesLineFragmentOrigin ], + context: nil) + + // Get the height of a single line of text + let singleSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) + let singleRect = attributedText.boundingRect(with: singleSize, + options: [ .usesLineFragmentOrigin ], + context: nil) + + // Only add the line spacing if we are going to render in multiple lines + return ceil(wrappedRect.size.height) <= ceil(singleRect.size.height) + } +} diff --git a/StylableUIKit/Classes/Components/StylableTextField.swift b/StylableUIKit/Classes/Components/StylableTextField.swift new file mode 100644 index 0000000..26cac83 --- /dev/null +++ b/StylableUIKit/Classes/Components/StylableTextField.swift @@ -0,0 +1,81 @@ +// +// StylableTextField.swift +// StylableUIKit +// + +import Foundation +import UIKit + +public final class StylableTextField: UITextField { + + private var textStyle: BasicTextStyle? + private var placeholderTextStyle: BasicTextStyle? + + override public init(frame: CGRect) { + super.init(frame: frame) + + self.addTarget(self, action: #selector(ensureStyle), for: .editingDidBegin) + self.addTarget(self, action: #selector(ensureStyle), for: .editingChanged) + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + self.addTarget(self, action: #selector(ensureStyle), for: .editingDidBegin) + self.addTarget(self, action: #selector(ensureStyle), for: .editingChanged) + } + + override public var text: String? { + didSet { + guard let textStyle = self.textStyle else { return } + self.applyTextStyle(textStyle) + } + } + + override public var placeholder: String? { + didSet { + guard let textStyle = self.placeholderTextStyle else { return } + self.applyPlaceholderTextStyle(textStyle) + } + } + + override public var attributedText: NSAttributedString? { + set { super.attributedText = newValue.map { self.applyTextStyleTo(attributedText: $0, textStyle: self.textStyle) } } + get { return super.attributedText } + } + + override public var attributedPlaceholder: NSAttributedString? { + set { super.attributedPlaceholder = newValue.map { self.applyTextStyleTo(attributedText: $0, textStyle: self.placeholderTextStyle) } } + get { return super.attributedPlaceholder } + } + + public func setTextStyle(_ textStyle: BasicTextStyle) { + self.textStyle = textStyle + self.applyTextStyle(textStyle) + } + + public func setPlaceholderTextStyle(_ textStyle: BasicTextStyle) { + self.placeholderTextStyle = textStyle + self.applyPlaceholderTextStyle(textStyle) + } + + private func applyTextStyle(_ textStyle: BasicTextStyle) { + self.defaultTextAttributes = textStyle.attributes + self.attributedText = self.attributedText.map { self.applyTextStyleTo(attributedText: $0, textStyle: self.textStyle) } + } + + private func applyPlaceholderTextStyle(_ textStyle: BasicTextStyle) { + self.attributedPlaceholder = self.attributedPlaceholder.map { self.applyTextStyleTo(attributedText: $0, textStyle: self.placeholderTextStyle) } + } + + private func applyTextStyleTo(attributedText: NSAttributedString, textStyle: BasicTextStyle?) -> NSAttributedString { + guard let textStyle = textStyle else { return attributedText } + return textStyle.apply(attributedText) + .applyingAttributes([.backgroundColor: textStyle.backgroundColor ?? self.backgroundColor ?? .clear], + preservingCurrent: false) + } + + @objc private func ensureStyle() { + self.typingAttributes = self.textStyle?.attributes + } +} diff --git a/StylableUIKit/Classes/Components/StylableTextView.swift b/StylableUIKit/Classes/Components/StylableTextView.swift new file mode 100644 index 0000000..2d45ae7 --- /dev/null +++ b/StylableUIKit/Classes/Components/StylableTextView.swift @@ -0,0 +1,40 @@ +// +// StylableTextView.swift +// StylableUIKit +// + +import Foundation +import UIKit + +public final class StylableTextView: UITextView { + + private var textStyle: BasicTextStyle? + + override public var text: String? { + didSet { + guard let textStyle = self.textStyle else { return } + self.applyTextStyle(textStyle) + } + } + + override public var attributedText: NSAttributedString? { + set { super.attributedText = newValue.map { self.applyTextStyleTo(attributedText: $0) } } + get { return super.attributedText } + } + + public func setTextStyle(_ textStyle: BasicTextStyle) { + self.textStyle = textStyle + self.applyTextStyle(textStyle) + } + + private func applyTextStyle(_ textStyle: BasicTextStyle) { + self.attributedText = self.attributedText.map { self.applyTextStyleTo(attributedText: $0) } + } + + private func applyTextStyleTo(attributedText: NSAttributedString) -> NSAttributedString { + guard let textStyle = self.textStyle else { return attributedText } + return textStyle.apply(attributedText) + .applyingAttributes([.backgroundColor: textStyle.backgroundColor ?? self.backgroundColor ?? .clear], + preservingCurrent: false) + } +} diff --git a/StylableUIKit/Classes/Core/Stylist.swift b/StylableUIKit/Classes/Core/Stylist.swift new file mode 100644 index 0000000..538799f --- /dev/null +++ b/StylableUIKit/Classes/Core/Stylist.swift @@ -0,0 +1,37 @@ +// +// Stylist.swift +// StylableUIKit +// + +import Foundation +import UIKit + +/// Implement this to style the platform components. Pass it into SeamlessPlatform and it will be called to get styles +/// for the UI components +public protocol Stylist { + + func asset(_ identifier: StylistAsset, element: StylistElement?, section: StylistSection?) -> Asset? + + func animatedAsset(_ identifier: StylistAsset, element: StylistElement?, section: StylistSection?) -> StylistAnimatedAsset? + + func textStyle(_ style: StylistTextStyle, element: StylistElement?, section: StylistSection?) -> TextStyle + + func layerStyle(_ style: StylistLayerStyle, element: StylistElement?, section: StylistSection?) -> LayerStyle + +} + +/// Implement this protocol to be given a stylist. +public protocol Stylable { + func style(stylist: Stylist, section: StylistSection) +} + +public protocol StylableWithMode { + associatedtype Mode: StylistMode + + func style(mode: Mode, stylist: Stylist, section: StylistSection) +} + +/// Helpful protocol to consistently give view controllers an idea of their section. +public protocol StylableViewController: Stylable { + var stylableSection: StylistSection { get } +} diff --git a/StylableUIKit/Classes/Core/StylistIdentifier.swift b/StylableUIKit/Classes/Core/StylistIdentifier.swift new file mode 100644 index 0000000..d9b98aa --- /dev/null +++ b/StylableUIKit/Classes/Core/StylistIdentifier.swift @@ -0,0 +1,78 @@ +// +// AssetIdentifier.swift +// StylableUIKit +// + +import Foundation + +public protocol StylistIdentifier { + var name: String { get } +} + +public protocol StylistAsset: StylistIdentifier { } + +public protocol StylistElement: StylistIdentifier { } + +public protocol StylistSection: StylistIdentifier { } + +public protocol StylistMode: StylistIdentifier { } + +public protocol StylistVariant: StylistIdentifier { } + +public protocol StylistTextStyle: StylistIdentifier {} + +public protocol StylistLayerStyle: StylistIdentifier {} + +extension StylistIdentifier where Self: RawRepresentable, Self.RawValue == String { + public var name: String { + return self.rawValue + } +} + +/// Wrap asset identifiers in this struct to make it implement `Equatable`. This only works if the wrapped +/// asset identifier's type is already `Equatable`. +public struct EquatableStylistIdentifier: Equatable { + + /// The underlying asset identifier used to create this instance, but with it's type removed. + public let identifier: StylistIdentifier + + /// This block `true` if the passed in asset identifier is the same type, and `==` to the identifier use to create this instance. + /// `false` otherwise. This is used internally in the implementation of `Equatable` + fileprivate let isEqual: (_ otherIdentifier: StylistIdentifier) -> Bool + + /// Create an EquatableAssetIdentifier wrapping the passed in identifier. The passed in identifier must also be `Equatable`. + /// + /// - parameter identifier: The asset identifier to create this instance. + public init(_ identifier: U) where U: StylistIdentifier, U: Equatable { + self.identifier = identifier + self.isEqual = { ($0 as? U) == identifier } + } + + public static func == (lhs: EquatableStylistIdentifier, rhs: EquatableStylistIdentifier) -> Bool { + return lhs.isEqual(rhs.identifier) + } +} + +/// The default type for a Variant is UIControl.State, but it's a protocol so that other Variants can be created +extension UIControl.State: StylistVariant { + public var name: String { + switch self { + case .normal: + return "normal" + case .highlighted: + return "highlighted" + case .disabled: + return "disabled" + case .selected: + return "selected" + case .focused: + return "focused" + case .application: + return "application" + case .reserved: + return "reserved" + default: + return "unknown" + } + } +} diff --git a/StylableUIKit/Classes/Core/VariantStyleProvider.swift b/StylableUIKit/Classes/Core/VariantStyleProvider.swift new file mode 100644 index 0000000..3f2ab6f --- /dev/null +++ b/StylableUIKit/Classes/Core/VariantStyleProvider.swift @@ -0,0 +1,30 @@ +// +// VariantStyleProvider.swift +// StylableUIKit +// + +import Foundation + +public protocol VariantStyleProvider: VariantStyle { + associatedtype Style + + /// - returns: Either the requested variant, or nil. + func variant(_: StylistVariant) -> Style? + + /// - returns: Either the requested variant, or `self`. + func variantOrDefault(_: StylistVariant) -> Style +} + +extension VariantStyleProvider { + func getVariant(_ variant: StylistVariant, from array: [Style]) -> Style? { + guard let item = array.first(where: { $0.variantType.name == variant.name }) else { + if variant.name == self.variantType.name { return self as? Style } + return nil + } + return item + } +} + +public protocol VariantStyle { + var variantType: StylistVariant { get } +} diff --git a/StylableUIKit/Classes/Extensions/UIViewController+stylable.swift b/StylableUIKit/Classes/Extensions/UIViewController+stylable.swift new file mode 100644 index 0000000..498f997 --- /dev/null +++ b/StylableUIKit/Classes/Extensions/UIViewController+stylable.swift @@ -0,0 +1,71 @@ +// +// UIViewController+skin.swift +// StylableUIKit +// + +import Foundation +import UIKit + +private var associatedObjectHandleStylist: UInt8 = 0 +private var associatedObjectHandleObserver: UInt8 = 1 + +/// UIViewControllers can get a default implementation of Stylable using associated objects. +extension UIViewController: Stylable { + + /// Returns the stylist attached to this view controller + public var stylist: Stylist! { + + // If we have an explicit stylist, use that one + let boxed = objc_getAssociatedObject(self, &associatedObjectHandleStylist) as? BoxedStylist + if let stylist = boxed?.stylist { + return stylist + } + + // We got to the root of the tree, and there wasn't a stylist + fatalError("Stylist not set on \(self)") + } + + /// uses the Stylable protocol to get hold of `stylist` in the first place + public func style(stylist: Stylist, section: StylistSection) { + let boxed = BoxedStylist(stylist: stylist) + objc_setAssociatedObject(self, + &associatedObjectHandleStylist, + boxed, + objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + if let stylableVC = self as? StylableViewController { + if let view = self.viewIfLoaded { // if the view is loaded, then apply the style + stylist.layerStyle(DefaultLayerStyle.background, + element: nil, + section: stylableVC.stylableSection).apply(view) + } else { // if the view is not loaded then use KVO to wait until it is + let observer = self.observe(\.view, options: [.new]) { [weak self] (_, _) in + guard let self = self else { return } + self.stylist.layerStyle(DefaultLayerStyle.background, + element: nil, + section: stylableVC.stylableSection).apply(self.view) + // remove associated object (set value to nil) + objc_setAssociatedObject(self, + &associatedObjectHandleObserver, + nil, + objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + objc_setAssociatedObject(self, + &associatedObjectHandleObserver, + observer, + objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } else { + print("⚠️ WARNING: ViewController \(String(describing: self)) should implement StylableViewController") + } + } +} + +/** + You can't just store a Stylist protocol directly as an associated object + because the cast back to `as? Stylist` doesn't work when you try to + retrieve it. You can however, store a concrete type, and extract the + stylist as a property from that. This is that concrete type. + */ +private struct BoxedStylist { + let stylist: Stylist +} diff --git a/StylableUIKit/Classes/LayerStyle/LayerFill.swift b/StylableUIKit/Classes/LayerStyle/LayerFill.swift new file mode 100644 index 0000000..2ed492e --- /dev/null +++ b/StylableUIKit/Classes/LayerStyle/LayerFill.swift @@ -0,0 +1,158 @@ +// +// LayerFill.swift +// StylableUIKit +// + +import UIKit + +// MARK: - LayerFill Implementations + +public struct FlatLayerFill: LayerFill { + + let color: UIColor + + public init(color: UIColor) { + self.color = color + } + + public func draw(into view: T) { + view.getLayerStylableView().backgroundColor = self.color + } + + public func getColor() -> UIColor? { + return self.color + } +} + +/// GradientLayerFill +/// Creates a fill layer with a gradient of an array of colours. +/// Can have one of two styles: axial (e.g. left-to-right) or radial. +public struct GradientLayerFill: LayerFill { + + /// The style of the gradient + public enum Style { + case axial(direction: Direction, locations: [Double]?) + case radial(center: CGPoint, locations: [Double]?) + } + + /// The direction of an axial gradient layer. Both start and end must be between 0 and 1. + public struct Direction { + let start: CGPoint + let end: CGPoint + + public static let leftToRight = Direction(start: .zero, end: CGPoint(x: 1.0, y: 0.0)) + public static let rightToLeft = Direction(start: CGPoint(x: 1.0, y: 0.0), end: .zero) + public static let topToBottom = Direction(start: .zero, end: CGPoint(x: 0.0, y: 1.0)) + public static let bottomToTop = Direction(start: CGPoint(x: 0.0, y: 1.0), end: .zero) + + public init(start: CGPoint, end: CGPoint) { + self.start = start + self.end = end + } + } + + let colors: [UIColor] + let style: Style + + public init(colors: [UIColor], style: Style) { + self.colors = colors + self.style = style + } + + public func draw(into view: T) { + let stylableView = view.getLayerStylableView() + let gradientLayer = CAGradientLayer() + gradientLayer.frame = stylableView.bounds + gradientLayer.colors = self.colors.map { $0.cgColor } + switch style { + case .axial(let direction, let locations): + gradientLayer.startPoint = direction.start + gradientLayer.endPoint = direction.end + gradientLayer.locations = locations?.map { NSNumber(value: $0) } + gradientLayer.type = .axial + case .radial(let center, let locations): + gradientLayer.startPoint = center + gradientLayer.locations = locations?.map { NSNumber(value: $0) } + gradientLayer.type = .radial + } + + stylableView.layer.insertSublayer(gradientLayer, at: 0) + } + + public func getColor() -> UIColor? { + return colors.first + } +} + +public struct ImageLayerFill: LayerFill { + + let image: UIImage + let resizingMode: UIImage.ResizingMode + let gravity: CALayerContentsGravity + + public init(image: UIImage, resizingMode: UIImage.ResizingMode, gravity: CALayerContentsGravity = .resizeAspect) { + self.image = image + self.resizingMode = resizingMode + self.gravity = gravity + } + + public func draw(into view: T) { + let stylableView = view.getLayerStylableView() + switch self.resizingMode { + case .stretch: + self.paintImageOnLayer(stylableView.layer) + case .tile: + self.tileImageOnView(stylableView) + @unknown default: + self.paintImageOnLayer(stylableView.layer) + } + } + + private func paintImageOnLayer(_ layer: CALayer) { + layer.contents = self.image.cgImage + layer.contentsGravity = self.gravity + } + + private func tileImageOnView(_ view: UIView) { + let color = UIColor(patternImage: self.image) + let fill = FlatLayerFill(color: color) + fill.draw(into: view) + } + + public func getColor() -> UIColor? { + return nil + } +} + +// MARK: - LayerStylableViewProvider +/// Because some views can't be background styled directly + +@objc public protocol LayerStylableViewProvider { + @objc func getLayerStylableView() -> UIView +} + +extension UITableViewCell { + override open func getLayerStylableView() -> UIView { + guard let backgroundView = self.backgroundView else { + let newView = UIView(frame: self.bounds) + self.backgroundView = newView + return newView + } + return backgroundView + } +} + +extension UITableView { + override open func getLayerStylableView() -> UIView { + guard let backgroundView = self.backgroundView else { + let newView = UIView(frame: self.bounds) + self.backgroundView = newView + return newView + } + return backgroundView + } +} + +extension UIView: LayerStylableViewProvider { + open func getLayerStylableView() -> UIView { return self } +} diff --git a/StylableUIKit/Classes/LayerStyle/LayerOutline/DottedLayerOutline.swift b/StylableUIKit/Classes/LayerStyle/LayerOutline/DottedLayerOutline.swift new file mode 100644 index 0000000..c722845 --- /dev/null +++ b/StylableUIKit/Classes/LayerStyle/LayerOutline/DottedLayerOutline.swift @@ -0,0 +1,68 @@ +// +// DashedLayerOutline.swift +// StylableUIKit +// + +import UIKit + +public struct DashedLayerOutline: LayerOutline { + let color: UIColor + let width: Double + let lineDashPattern: [NSNumber] + + public init(color: UIColor, width: Double, lineDashPattern: [NSNumber]) { + self.color = color + self.width = width + self.lineDashPattern = lineDashPattern + } + + public func draw(into view: UIView) { + let stylableView = view.getLayerStylableView() + + let outline = ResizingCAShapeLayer() + + outline.fillColor = nil + outline.strokeColor = self.color.cgColor + outline.lineDashPattern = self.lineDashPattern + outline.lineWidth = CGFloat(width) + outline.frame = view.bounds + outline.setNeedsDisplay() + + stylableView.layer.addSublayer(outline) + } +} + +final class ResizingCAShapeLayer: CAShapeLayer { + + required override init() { + super.init() + self.needsDisplayOnBoundsChange = true + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.needsDisplayOnBoundsChange = true + } + + required override init(layer: Any) { + super.init(layer: layer) + self.needsDisplayOnBoundsChange = true + } + + override func draw(in ctx: CGContext) { + ctx.saveGState() + + let width = self.lineWidth + let frame = CGRect(x: self.bounds.minX + (width / 2), + y: self.bounds.minY + (width / 2), + width: self.bounds.width - width, + height: self.bounds.height - width) + + let path = UIBezierPath(roundedRect: frame, cornerRadius: self.cornerRadius) + path.close() + + self.path = path.cgPath + super.draw(in: ctx) + ctx.restoreGState() + } +} diff --git a/StylableUIKit/Classes/LayerStyle/LayerOutline/LinearLayerOutline.swift b/StylableUIKit/Classes/LayerStyle/LayerOutline/LinearLayerOutline.swift new file mode 100644 index 0000000..5765f39 --- /dev/null +++ b/StylableUIKit/Classes/LayerStyle/LayerOutline/LinearLayerOutline.swift @@ -0,0 +1,48 @@ +// +// LinearLayerOutline.swift +// StylableUIKit +// + +import UIKit + +public struct LinearLayerOutline: LayerOutline { + let color: UIColor + let width: Double + + public init(color: UIColor, width: Double) { + self.color = color + self.width = width + } + + public func draw(into view: UIView) { + let stylableView = view.getLayerStylableView() + stylableView.layerBorderColor = color + stylableView.layerBorderWidth = NSNumber(value: width) + } +} + +// MARK: - Exposed CALayer properties +/// Create dynamic properties for UIView to enable use of Appearance Proxy +fileprivate extension UIView { + + @objc dynamic var layerBackgroundColor: UIColor? { + get { + guard let cgColor = self.layer.backgroundColor else { return nil } + return UIColor(cgColor: cgColor) + } + set { self.layer.backgroundColor = newValue?.cgColor } + } + + @objc dynamic var layerBorderColor: UIColor? { + get { + guard let cgColor = self.layer.borderColor else { return nil } + return UIColor(cgColor: cgColor) + } + set { self.layer.borderColor = newValue?.cgColor } + } + + @objc dynamic var layerBorderWidth: NSNumber { + get { return NSNumber(value: Float(self.layer.borderWidth)) } + set { self.layer.borderWidth = CGFloat(newValue.doubleValue) } + } +} diff --git a/StylableUIKit/Classes/LayerStyle/LayerStyle.swift b/StylableUIKit/Classes/LayerStyle/LayerStyle.swift new file mode 100644 index 0000000..847f05d --- /dev/null +++ b/StylableUIKit/Classes/LayerStyle/LayerStyle.swift @@ -0,0 +1,127 @@ +// +// LayerStyle.swift +// StylableUIKit +// + +import UIKit + +// MARK: - Layer Style protocols & core types +public enum DefaultLayerStyle: String, StylistLayerStyle { + case background +} + +public protocol LayerFill { + func draw(into view: T) + func getColor() -> UIColor? +} + +public protocol LayerOutline { + func draw(into view: UIView) +} + +// MARK: - LayerStyle implementation + +/** + Collection of attributes to apply to views + */ +public final class LayerStyle: BasicLayerStyle, VariantStyleProvider { + public func variant(_ variant: StylistVariant) -> BasicLayerStyle? { + return self.getVariant(variant, from: self.variants) + } + + public func variantOrDefault(_ variant: StylistVariant) -> BasicLayerStyle { + return self.variant(variant) ?? self + } + + private var variants: [BasicLayerStyle] = [] + + public init(fill: LayerFill, + outline: LayerOutline? = nil, + variantType: StylistVariant = UIControl.State.normal, + variants: [BasicLayerStyle] = []) { + super.init(fill: fill, + outline: outline, + variantType: variantType) + self.variants = variants + } +} + +public class BasicLayerStyle: VariantStyle { + + private let fill: LayerFill + private let outline: LayerOutline? + public private(set) var variantType: StylistVariant = UIControl.State.normal + + public init(fill: LayerFill, + outline: LayerOutline? = nil, + variantType: StylistVariant = UIControl.State.normal) { + self.fill = fill + self.outline = outline + self.variantType = variantType + } + + public func apply(_ view: T?) { + guard let view = view else { return } + + // Create a Subview to contain LayerStyle CALayers + let subview: UIView = { + let realView = view.getLayerStylableView() + var subview = realView.subviews.filter { return $0 is LayerResizingView }.first + if subview == nil { + subview = LayerResizingView(frame: realView.bounds) + realView.addSubview(subview!) + realView.sendSubviewToBack(subview!) + } + return subview! + }() + + // clear existing layers to prevent cumulative layer build up + subview.layer.sublayers?.forEach { $0.removeFromSuperlayer() } + + switch T.self { + case is RestrictedLayerStylable.Type: + if let color = fill.getColor() { + // although swift knows that view is of type RestrictedLayerStylable + // we can't cast to that type, but we *can* use objc to do it for us + view.perform(#selector(RestrictedLayerStylable.setBackground(color:)), with: color) + } + default: + self.fill.draw(into: subview) + } + self.outline?.draw(into: subview) + + } + + public func getColor() -> UIColor? { + return self.fill.getColor() + } +} + +private final class LayerResizingView: UIView { + + override func didMoveToSuperview() { + super.didMoveToSuperview() + self.isUserInteractionEnabled = false + + self.autoresizingMask = [.flexibleWidth, .flexibleHeight] + self.translatesAutoresizingMaskIntoConstraints = true + } + + override func layoutSubviews() { + super.layoutSubviews() + self.layer.cornerRadius = self.superview?.layer.cornerRadius ?? 0 + } + + override func layoutSublayers(of layer: CALayer) { + super.layoutSublayers(of: layer) + + let previousAnimationDuration = CATransaction.animationDuration() + CATransaction.setAnimationDuration(0.0) + layer.sublayers?.forEach { + $0.frame = layer.bounds + $0.cornerRadius = self.superview?.layer.cornerRadius ?? 0 + $0.setNeedsDisplay() + } + CATransaction.setAnimationDuration(previousAnimationDuration) + } +} diff --git a/StylableUIKit/Classes/LayerStyle/RestrictedLayerStylable.swift b/StylableUIKit/Classes/LayerStyle/RestrictedLayerStylable.swift new file mode 100644 index 0000000..7756647 --- /dev/null +++ b/StylableUIKit/Classes/LayerStyle/RestrictedLayerStylable.swift @@ -0,0 +1,32 @@ +// +// RestrictedLayerStylable +// StylableUIKit +// + +import Foundation + +// MARK: - SimpleLayerStylable +/// Handle UIViews which don't play well with sub UIViews, eg UILabel and UINavigationBar where the subview sits above the text +@objc public protocol RestrictedLayerStylable { + @objc func setBackground(color: UIColor) + // func setBackground(image: UIImage) +} + +extension UILabel: RestrictedLayerStylable { + public func setBackground(color: UIColor) { + self.backgroundColor = color + } +} + +extension UINavigationBar: RestrictedLayerStylable { + public func setBackground(color: UIColor) { + self.barTintColor = color + self.setBackgroundImage(nil, for: .default) + } +} + +extension UIImageView: RestrictedLayerStylable { + public func setBackground(color: UIColor) { + self.backgroundColor = color + } +} diff --git a/StylableUIKit/Classes/Subspec/Lottie/Lottie+StylistAnimatedAsset.swift b/StylableUIKit/Classes/Subspec/Lottie/Lottie+StylistAnimatedAsset.swift new file mode 100644 index 0000000..3b6893e --- /dev/null +++ b/StylableUIKit/Classes/Subspec/Lottie/Lottie+StylistAnimatedAsset.swift @@ -0,0 +1,18 @@ +// +// Lottie+StylistAnimatedAsset.swift +// StylableUIKit +// + +import Foundation +import Lottie + +extension AnimationView: AnimatedAssetMethods { + + public func playOnce(fromProgress: CGFloat, toProgress: CGFloat, completion: ((Bool) -> Void)?) { + self.play(fromProgress: fromProgress, toProgress: toProgress, loopMode: .playOnce, completion: completion) + } + + public func playLooped(fromProgress: CGFloat, toProgress: CGFloat) { + self.play(fromProgress: fromProgress, toProgress: toProgress, loopMode: .loop, completion: nil) + } +} diff --git a/StylableUIKit/Classes/Subspec/Lottie/String+Lottie.swift b/StylableUIKit/Classes/Subspec/Lottie/String+Lottie.swift new file mode 100644 index 0000000..dddae23 --- /dev/null +++ b/StylableUIKit/Classes/Subspec/Lottie/String+Lottie.swift @@ -0,0 +1,18 @@ +// +// String+Lottie +// StylableUIKit +// + +import UIKit +import Lottie + +public extension String { + + func asLottieAnimationView(from bundle: Bundle = Bundle.main) -> StylistAnimatedAsset? { + if bundle.path(forResource: self, ofType: "json") != nil { + return AnimationView(name: self) + } + return nil + } + +} diff --git a/StylableUIKit/Classes/TextStyle/NSAttributedString+textTransform.swift b/StylableUIKit/Classes/TextStyle/NSAttributedString+textTransform.swift new file mode 100644 index 0000000..ac711e0 --- /dev/null +++ b/StylableUIKit/Classes/TextStyle/NSAttributedString+textTransform.swift @@ -0,0 +1,29 @@ +// +// NSAttributedString+textTransform.swift +// StylableUIKit +// + +import Foundation + +extension NSAttributedString { + + private func applyToWholeString(_ map: (String) throws -> String) rethrows -> NSAttributedString { + let result = NSMutableAttributedString(attributedString: self) + + result.replaceCharacters(in: NSRange(location: 0, length: result.length), with: try map(result.string)) + + return result + } + + func uppercased() -> NSAttributedString { + return self.applyToWholeString { $0.uppercased() } + } + + func lowercased() -> NSAttributedString { + return self.applyToWholeString { $0.lowercased() } + } + + var capitalized: NSAttributedString { + return self.applyToWholeString { $0.capitalized } + } +} diff --git a/StylableUIKit/Classes/TextStyle/TextStyle.swift b/StylableUIKit/Classes/TextStyle/TextStyle.swift new file mode 100644 index 0000000..45964df --- /dev/null +++ b/StylableUIKit/Classes/TextStyle/TextStyle.swift @@ -0,0 +1,124 @@ +// +// TextStyle.swift +// StylableUIKit +// + +import UIKit + +/** + Collection of attributes to apply to textual elements. + */ +public final class TextStyle: BasicTextStyle, VariantStyleProvider { + + public func variant(_ variant: StylistVariant) -> BasicTextStyle? { + return self.getVariant(variant, from: self.variants) + } + + public func variantOrDefault(_ variant: StylistVariant) -> BasicTextStyle { + return self.variant(variant) ?? self + } + + private var variants: [BasicTextStyle] = [] + + public init(font: UIFont, + textColor: UIColor, + backgroundColor: UIColor? = .clear, + lineSpacing: CGFloat = 0, + letterSpacing: CGFloat = 0, + textAlignment: NSTextAlignment = .left, + textTransform: TextTransform = .none, + variantType: StylistVariant = UIControl.State.normal, + variants: [BasicTextStyle] = []) { + super.init(font: font, + textColor: textColor, + backgroundColor: backgroundColor, + lineSpacing: lineSpacing, + letterSpacing: letterSpacing, + textAlignment: textAlignment, + textTransform: textTransform, + variantType: variantType) + self.variants = variants + } +} + +public class BasicTextStyle: VariantStyle { + public let font: UIFont + public let textColor: UIColor + public let backgroundColor: UIColor? + public let letterSpacing: CGFloat + public let lineSpacing: CGFloat + public let textAlignment: NSTextAlignment + public let variantType: StylistVariant + public let textTransform: TextTransform + + public init(font: UIFont, + textColor: UIColor, + backgroundColor: UIColor? = .clear, + lineSpacing: CGFloat = 0, + letterSpacing: CGFloat = 0, + textAlignment: NSTextAlignment = .left, + textTransform: TextTransform = .none, + variantType: StylistVariant = UIControl.State.normal) { + self.font = font + self.textColor = textColor + self.backgroundColor = backgroundColor + self.lineSpacing = lineSpacing + self.letterSpacing = letterSpacing + self.textAlignment = textAlignment + self.variantType = variantType + self.textTransform = textTransform + } +} + +extension BasicTextStyle { + + public func apply(_ string: String?) -> NSAttributedString { + let string: String = { + guard let string = string else { return "" } + switch self.textTransform { + case .uppercased: + return string.uppercased() + case .lowercased: + return string.lowercased() + case .capitalized: + return string.capitalized + case .none: + return string + } + }() + return NSAttributedString(string: string).applyingAttributes(self.attributes) + } + + public func apply(_ string: NSAttributedString?) -> NSAttributedString { + + let string: NSAttributedString = { + guard let string = string else { return NSAttributedString(string: "") } + switch self.textTransform { + case .uppercased: + return string.uppercased() + case .lowercased: + return string.lowercased() + case .capitalized: + return string.capitalized + case .none: + return string + } + }() + return string.applyingAttributes(self.attributes, preservingCurrent: false) + } +} + +extension BasicTextStyle { + var attributes: [NSAttributedString.Key: Any] { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = self.lineSpacing + paragraphStyle.alignment = self.textAlignment + let backgroundColor = self.backgroundColor ?? .clear + return [ .font: self.font, + .foregroundColor: self.textColor, + .kern: self.letterSpacing, + .backgroundColor: backgroundColor, + .paragraphStyle: paragraphStyle + ] + } +} diff --git a/StylableUIKit/Classes/TextStyle/TextTransform.swift b/StylableUIKit/Classes/TextStyle/TextTransform.swift new file mode 100644 index 0000000..6c9530d --- /dev/null +++ b/StylableUIKit/Classes/TextStyle/TextTransform.swift @@ -0,0 +1,11 @@ +// +// TextTransform.swift +// StylableUIKit +// + +public enum TextTransform { + case none + case uppercased + case lowercased + case capitalized +} diff --git a/StylableUIKit/Classes/Utils/NSAttributedString+attributes.swift b/StylableUIKit/Classes/Utils/NSAttributedString+attributes.swift new file mode 100644 index 0000000..2306d4e --- /dev/null +++ b/StylableUIKit/Classes/Utils/NSAttributedString+attributes.swift @@ -0,0 +1,172 @@ +// +// NSAttributedString+attributes.swift +// StylableUIKit +// + +import UIKit + +fileprivate extension NSMutableAttributedString { + + func addMissingAttribute(_ attr: NSAttributedString.Key, value: Any, range: NSRange) { + self.enumerateAttribute(attr, in: range, options: []) { found, range, _ in + if found == nil { + self.addAttribute(attr, value: value, range: range) + } + } + } +} + +public extension NSAttributedString { + + /** + Returns a new `NSAttributedString` with the specified attributes set. + + - parameter attributes: A dictionary of the attributes to apply + - parameter range: _(optional)_ The range to apply the new attributes to (`nil` applies to the whole string). + - parameter preservingCurrent: _(optional)_ `true` to keep previous values of each attribute in the current string, `false` to apply to the whole string. + */ + func applyingAttributes(_ attributes: [ NSAttributedString.Key: Any], range: NSRange? = nil, preservingCurrent: Bool = true) -> NSAttributedString { + let range = range ?? NSRange(location: 0, length: self.length) + + let string = NSMutableAttributedString(attributedString: self) + + for (attr, value) in attributes { + if preservingCurrent { + string.addMissingAttribute(attr, value: value, range: range) + } else { + string.addAttribute(attr, value: value, range: range) + } + } + + return string + } + + /** + Returns a new `NSAttributedString` with the specified attribute set. + + - parameter attr: The attribute to set + - parameter value: The new value to set it to + - parameter range: _(optional)_ The range to apply the new attribute to (`nil` applies to the whole string). + - parameter preservingCurrent: _(optional)_ `true` to keep previous values of `attr` in the current string, `false` to apply to the whole string. + */ + func applyingAttribute(_ attr: NSAttributedString.Key, value: Any, range: NSRange? = nil, preservingCurrent: Bool = true) -> NSAttributedString { + return self.applyingAttributes([attr: value], range: range, preservingCurrent: preservingCurrent) + } + + /** + Returns a new attributed string with the specified font applied. + + - parameter font: The font to apply + - parameter range: _(optional)_ The range to apply the new font to (`nil` applies to the whole string). + - parameter preservingCurrent: _(optional)_ `true` to keep previous fonts (where they have ben set), `false` to apply to the whole string. + */ + func applyingFont(_ font: UIFont, range: NSRange? = nil, preservingCurrent: Bool = true) -> NSAttributedString { + return self.applyingAttribute(.font, + value: font, + range: range, + preservingCurrent: preservingCurrent) + } + + /** + Returns a new attributed string with the specified foreground color applied. + + - parameter color: The color to apply + - parameter range: _(optional)_ The range to apply the new color to (`nil` applies to the whole string). + - parameter preservingCurrent: _(optional)_ `true` to keep previous foreground colours (where they have ben set), `false` to apply to the whole string. + */ + func applyingTextColor(_ color: UIColor, range: NSRange? = nil, preservingCurrent: Bool = true) -> NSAttributedString { + return self.applyingAttribute(.foregroundColor, + value: color, + range: range, + preservingCurrent: preservingCurrent) + } + + /** + Returns a new attributed string with the specified alignment and line spacing applied across its total range + */ + func applyingParagraphStyle(alignment: NSTextAlignment = .center, + lineSpacing: CGFloat = 0) -> NSAttributedString { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineSpacing = lineSpacing + paragraphStyle.alignment = alignment + + return self.applyingParagraphStyle(paragraphStyle) + } + + /** + Returns a new attributed string with the specified paragraph style applied across its total range + */ + func applyingParagraphStyle(_ paragraphStyle: NSParagraphStyle) -> NSAttributedString { + let string = NSMutableAttributedString(attributedString: self) + string.addAttribute(.paragraphStyle, + value: paragraphStyle, + range: NSRange(location: 0, length: string.length)) + return string + } + + /// This method will iterate over all the paragraph styles in the reciever (adding empty styles for sections where there is + /// no current paragraph style) and pass a mutable copy into the mutation parameter. This mutated style with then replace the + /// original style. + /// + /// - parameter range: The range over which to update the paragraph + /// - parameter mutation: The mutation to apply to the paragraph style + /// - returns: An `NSAttributedString` with the mutation applied over the entire of `range` + private func updatingParagraph(range: NSRange? = nil, mutation: (NSMutableParagraphStyle) -> Void) -> NSAttributedString { + let range = range ?? NSRange(location: 0, length: self.length) + + let string = NSMutableAttributedString(attributedString: self) + + string.enumerateAttribute(.paragraphStyle, in: range, options: []) { value, range, _ in + let style: NSMutableParagraphStyle + + if let value = value as? NSParagraphStyle, let foundStyle = value.mutableCopy() as? NSMutableParagraphStyle { + style = foundStyle + } else { + style = NSMutableParagraphStyle() + } + + mutation(style) + + string.addAttributes([.paragraphStyle: style], range: range) + } + + return string + } + + /// This will apply the specified text alignment to the string. All currently existing alignments will be overridden. + /// + /// - parameter alignment: The new alignment to be applied to the string + /// - parameter range: _(optional)_ The range of the string over which to apply the new alignment. Passing in `nil` will cover the whole string. + func applyingAlignment(_ alignment: NSTextAlignment, range: NSRange? = nil) -> NSAttributedString { + return self.updatingParagraph(range: range) { style in + style.alignment = alignment + } + } + + /// This will apply the specified line spacing to the string. All currently existing spacings will be overridden. + /// + /// - parameter spacing: The new line spacing + /// - parameter range: _(optional)_ The range of the string over which to apply the new spacing. Passing in `nil` will cover the whole string. + func applyingLineSpacing(_ spacing: CGFloat, range: NSRange? = nil) -> NSAttributedString { + return self.updatingParagraph(range: range) { style in + style.lineSpacing = spacing + } + } + + /// This will apply the specified line break mode. + /// + /// - parameter lineBreakMode: The new line break mode to be applied to the string. + func applyingLineBreakMode(_ lineBreakMode: NSLineBreakMode) -> NSAttributedString { + return self.updatingParagraph(range: nil) { style in + style.lineBreakMode = lineBreakMode + } + } + + /// Remove any line height attributes. iOS will always add the extra height to a line even if there is only one. This line height will + /// be applied over the entire string. + func singleLine() -> NSAttributedString { + return self.updatingParagraph { style in + style.lineSpacing = 0 + } + } +}