├── .gitignore ├── .gitmodules ├── Documentation ├── drag.gif ├── ellipse_slider.gif ├── jumpball.gif ├── moveonpath.gif ├── radar.gif ├── rotate_polygons.gif └── strokelines.gif ├── LICENSE ├── README.md ├── ShapeAnimation.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── ShapeAnimation.xcscmblueprint └── xcshareddata │ └── xcschemes │ ├── ShapeAnimation.xcscheme │ ├── ShapeAnimation_OSX.xcscheme │ └── ShapeAnimation_UITest.xcscheme ├── ShapeAnimation ├── Animation │ ├── AnimationLayer.swift │ ├── AnimationPair.swift │ ├── AnimationPrivate.swift │ ├── CALayer+Animation.swift │ ├── CALayer+Drag.swift │ ├── CALayer+Pause.swift │ ├── CALayer+Slide.swift │ └── CAShapeLayer+Animation.swift ├── Info.plist ├── Portability.swift ├── ShapeAnimation.h └── View │ ├── CALayer+ID.swift │ ├── CAShapeLayer+Gradient.swift │ ├── CAShapeLayer+Path.swift │ ├── CAShapeLayer+Style.swift │ ├── ShapeView+HitTest.swift │ ├── ShapeView+Image.swift │ └── ShapeView.swift ├── ShapeAnimation_OSX ├── Info.plist └── ShapeAnimation_OSX.h ├── ShapeAnimation_OSXTests ├── Info.plist └── ShapeAnimation_OSXTests.swift ├── ShapeAnimation_UITest ├── AppDelegate.swift ├── BallViewController.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── DetailViewController.swift ├── EllipseViewController.swift ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Images │ └── airship@2x.png ├── Info.plist ├── MasterVC+Drag.swift ├── MasterVC+Hamburger.swift └── MasterViewController.swift └── ShapeAnimation_UnitTests ├── DummyTests.swift ├── Info.plist └── SwiftUtilities_Tests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # CocoaPods 2 | # 3 | # We recommend against adding the Pods directory to your .gitignore. However 4 | # you should judge for yourself, the pros and cons are mentioned at: 5 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? 6 | # 7 | # Pods/ 8 | 9 | build 10 | .DS_Store 11 | xcuserdata 12 | *.xccheckout 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Vendor/SwiftGraphics"] 2 | path = Vendor/SwiftGraphics 3 | url = https://github.com/rhcad/SwiftGraphics.git 4 | -------------------------------------------------------------------------------- /Documentation/drag.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhcad/ShapeAnimation-Swift/6375f339d1bb7d85f55b90063a2c151d2235ac07/Documentation/drag.gif -------------------------------------------------------------------------------- /Documentation/ellipse_slider.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhcad/ShapeAnimation-Swift/6375f339d1bb7d85f55b90063a2c151d2235ac07/Documentation/ellipse_slider.gif -------------------------------------------------------------------------------- /Documentation/jumpball.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhcad/ShapeAnimation-Swift/6375f339d1bb7d85f55b90063a2c151d2235ac07/Documentation/jumpball.gif -------------------------------------------------------------------------------- /Documentation/moveonpath.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhcad/ShapeAnimation-Swift/6375f339d1bb7d85f55b90063a2c151d2235ac07/Documentation/moveonpath.gif -------------------------------------------------------------------------------- /Documentation/radar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhcad/ShapeAnimation-Swift/6375f339d1bb7d85f55b90063a2c151d2235ac07/Documentation/radar.gif -------------------------------------------------------------------------------- /Documentation/rotate_polygons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhcad/ShapeAnimation-Swift/6375f339d1bb7d85f55b90063a2c151d2235ac07/Documentation/rotate_polygons.gif -------------------------------------------------------------------------------- /Documentation/strokelines.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhcad/ShapeAnimation-Swift/6375f339d1bb7d85f55b90063a2c151d2235ac07/Documentation/strokelines.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For ShapeAnimation-Swift framework 4 | 5 | Copyright (c) 2015, Zhang Yungui . All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShapeAnimation-Swift 2 | 3 | Vector animation framework in Swift based on [SwiftGraphics][SwiftGraphics] for iOS 9 and OSX. 4 | With ShapeAnimation you can easily create various animations with a nice Swift based syntax. 5 | 6 | [![Travis][travis_img]][travis] 7 | 8 | [travis]: https://travis-ci.org/rhcad/ShapeAnimation-Swift 9 | [travis_img]: https://travis-ci.org/rhcad/ShapeAnimation-Swift.svg?branch=master 10 | 11 | SVG animation development with [SVGKit][SVGKit] happens on the [SVG][svg_branch] branch. 12 | [ShapeAnimation-ObjC][SAObjC] is an alternative vector animation framework in Objective-C. 13 | 14 | Also note this project has moved to Swift 2.0 which requires Xcode 7.0 and Mac OS X 10.10. 15 | 16 | [SwiftGraphics]: https://github.com/schwa/SwiftGraphics 17 | [SVGKit]: https://github.com/SVGKit/SVGKit 18 | [svg_branch]: https://github.com/rhcad/ShapeAnimation-Swift/tree/SVG 19 | [SAObjC]: https://github.com/rhcad/ShapeAnimation-ObjC 20 | 21 | ## What's included 22 | 23 | * ShapeView class which contains vector shape layers. 24 | * Helper functions to add image, text, circle, regular polygon, lines and other shapes. 25 | * Support gradient fill with animation. 26 | * Enumerate, hit-test or find layers. 27 | 28 | * Animation extension functions of CALayer. 29 | * opacityAnimation, flashAnimation, backColorAnimation 30 | * scaleAnimation, tapAnimation, transformAnimation 31 | * rotate360Degrees, rotationAnimation 32 | * shakeAnimation, moveAnimation, moveOnPathAnimation 33 | * slideToRight, slideAnimation, flipHorizontally, flipVertically 34 | * Layer dragging: constrainCenterToSuperview, bringOnScreen 35 | 36 | * Animation extension functions of CAShapeLayer. 37 | * strokeStartAnimation, strokeEndAnimation, lineWidthAnimation 38 | * strokeColorAnimation, fillColorAnimation, dashPhaseAnimation 39 | * switchPathAnimation 40 | 41 | * Group animation and cascaded animation. 42 | * animationGroup for the same layer 43 | * applyAnimations for multiple layers 44 | * Use the block-based function in apply() to play cascaded animations. 45 | * Pause, resume or stop animations. 46 | 47 | * Animations with customized properties 48 | * Use AnimationLayer class to draw customized animations. 49 | * Animation with sliders example: [EllipseViewController.swift](ShapeAnimation_UITest/EllipseViewController.swift) 50 | 51 | ![Stroke Lines](Documentation/strokelines.gif) 52 | ![Move on Path](Documentation/moveonpath.gif) 53 | ![Radar Circles](Documentation/radar.gif) 54 | 55 | ![Rotate Polygons](Documentation/rotate_polygons.gif) 56 | ![Jumping Ball](Documentation/jumpball.gif) 57 | 58 | ![Animation with Sliders](Documentation/ellipse_slider.gif) 59 | ![Drag Layers](Documentation/drag.gif) 60 | 61 | ## Usage 62 | 63 | Please see the examples in [MasterViewController.swift][MasterVC]. 64 | 65 | [MasterVC]: ShapeAnimation_UITest/MasterViewController.swift 66 | 67 | ``` Swift 68 | let layer1 = self.addLinesLayer(view, points:[(10.0,20.0),(150.0,40.0),(120.0,320.0)]) 69 | layer1.strokeEndAnimation().apply() { 70 | layer1.shakeAnimation().apply() 71 | } 72 | 73 | let la2 = self.addLinesLayer(view, points:points2, color: UIColor.blueColor()) 74 | lla2.scaleAnimation(from:1, to:1.1, repeatCount:3).apply(duration:0.3) 75 | 76 | let la3 = self.addLinesLayer(view, points:points3, color: UIColor.greenColor()) 77 | la3.flashAnimation(repeatCount:6).apply() 78 | 79 | let la4 = self.addLinesLayer(view, points:[(10.0,20.0), (150.0,40.0), (120.0,120.0)]) 80 | let a1 = la4.moveOnPathAnimation(path).setDuration(1.6) 81 | let a2 = la4.rotate360Degrees().setRepeatCount(2) 82 | animationGroup([a1, a2]).autoreverses().apply() 83 | ``` 84 | 85 | ## Help Wanted 86 | 87 | All of this code is very much a _*work in progress*_. I'm adding and changing functionality as needed. 88 | 89 | Your help wanted. Please fork this project and submit pull requests or [issues][issues]. 90 | [issues]: https://github.com/rhcad/ShapeAnimation-Swift/issues 91 | 92 | Contributions are always welcome in the following areas: 93 | 94 | * Header doc comments explaining what the functions do 95 | * Useful animation template or application examples 96 | * Fix issues about animation, Swift usage rules or translations 97 | 98 | ## License 99 | 100 | ShapeAnimation-Swift is released under a BSD License. See LICENSE file for details. 101 | 102 | ## FAQ 103 | 104 | * Could not build Objective-C module 'ShapeAnimation': 105 | - Quit Xcode and delete the sub folders in ~/Library/Developer/Xcode/DerivedData/. 106 | -------------------------------------------------------------------------------- /ShapeAnimation.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 02147CA11A836E2A001E5617 /* MasterVC+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02147CA01A836E2A001E5617 /* MasterVC+Drag.swift */; }; 11 | 02147CAC1A847EA3001E5617 /* CALayer+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02147CAB1A847EA3001E5617 /* CALayer+Drag.swift */; }; 12 | 021D756A1A89EBB3009C5129 /* CALayer+Slide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D75691A89EBB3009C5129 /* CALayer+Slide.swift */; }; 13 | 022E98131A8071A70028A198 /* ShapeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E98091A8071A70028A198 /* ShapeView.swift */; }; 14 | 022E98141A8071A70028A198 /* AnimationLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980B1A8071A70028A198 /* AnimationLayer.swift */; }; 15 | 022E98151A8071A70028A198 /* AnimationPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980C1A8071A70028A198 /* AnimationPair.swift */; }; 16 | 022E98161A8071A70028A198 /* AnimationPrivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980D1A8071A70028A198 /* AnimationPrivate.swift */; }; 17 | 022E98171A8071A70028A198 /* CALayer+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980E1A8071A70028A198 /* CALayer+Animation.swift */; }; 18 | 022E98181A8071A70028A198 /* CALayer+Pause.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980F1A8071A70028A198 /* CALayer+Pause.swift */; }; 19 | 022E98191A8071A70028A198 /* CAShapeLayer+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E98101A8071A70028A198 /* CAShapeLayer+Animation.swift */; }; 20 | 022E98201A8072270028A198 /* CAShapeLayer+Gradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E981E1A8072270028A198 /* CAShapeLayer+Gradient.swift */; }; 21 | 022E98211A8072270028A198 /* CAShapeLayer+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E981F1A8072270028A198 /* CAShapeLayer+Style.swift */; }; 22 | 02478B091A70C6A200CCAC47 /* ShapeAnimation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45DA4AC419AA62A800133F32 /* ShapeAnimation.framework */; }; 23 | 0256C6701ABEC1D8007FC099 /* Portability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0256C66F1ABEC1D8007FC099 /* Portability.swift */; }; 24 | 0267D0D31AC00FF400CEF476 /* ShapeAnimation_OSX.h in Headers */ = {isa = PBXBuildFile; fileRef = 0267D0D21AC00FF400CEF476 /* ShapeAnimation_OSX.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25 | 0267D0E71AC0101500CEF476 /* Portability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0256C66F1ABEC1D8007FC099 /* Portability.swift */; }; 26 | 0267D0E81AC0101500CEF476 /* ShapeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E98091A8071A70028A198 /* ShapeView.swift */; }; 27 | 0267D0E91AC0101500CEF476 /* ShapeView+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE57A141A81168F00420D54 /* ShapeView+Image.swift */; }; 28 | 0267D0EA1AC0101500CEF476 /* ShapeView+HitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028F675F1A8760FA00634383 /* ShapeView+HitTest.swift */; }; 29 | 0267D0EB1AC0101500CEF476 /* CALayer+ID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02887B2A1A820CAC00E800A1 /* CALayer+ID.swift */; }; 30 | 0267D0EC1AC0101500CEF476 /* CAShapeLayer+Gradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E981E1A8072270028A198 /* CAShapeLayer+Gradient.swift */; }; 31 | 0267D0ED1AC0101500CEF476 /* CAShapeLayer+Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E981F1A8072270028A198 /* CAShapeLayer+Style.swift */; }; 32 | 0267D0EE1AC0101500CEF476 /* CAShapeLayer+Path.swift in Sources */ = {isa = PBXBuildFile; fileRef = AED21A2C1A88EDE2004EFD25 /* CAShapeLayer+Path.swift */; }; 33 | 0267D0EF1AC0101500CEF476 /* CALayer+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980E1A8071A70028A198 /* CALayer+Animation.swift */; }; 34 | 0267D0F01AC0101500CEF476 /* CALayer+Slide.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D75691A89EBB3009C5129 /* CALayer+Slide.swift */; }; 35 | 0267D0F11AC0101500CEF476 /* CALayer+Drag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02147CAB1A847EA3001E5617 /* CALayer+Drag.swift */; }; 36 | 0267D0F21AC0101500CEF476 /* CAShapeLayer+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E98101A8071A70028A198 /* CAShapeLayer+Animation.swift */; }; 37 | 0267D0F31AC0101500CEF476 /* AnimationLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980B1A8071A70028A198 /* AnimationLayer.swift */; }; 38 | 0267D0F41AC0101500CEF476 /* CALayer+Pause.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980F1A8071A70028A198 /* CALayer+Pause.swift */; }; 39 | 0267D0F51AC0101500CEF476 /* AnimationPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980C1A8071A70028A198 /* AnimationPair.swift */; }; 40 | 0267D0F61AC0101500CEF476 /* AnimationPrivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022E980D1A8071A70028A198 /* AnimationPrivate.swift */; }; 41 | 0267D0F71AC0102700CEF476 /* ShapeAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 45DA4AC819AA62A800133F32 /* ShapeAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 42 | 027DC1B91A77998D00D3CF60 /* airship@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 027DC1B81A77998D00D3CF60 /* airship@2x.png */; }; 43 | 02887B2B1A820CAC00E800A1 /* CALayer+ID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02887B2A1A820CAC00E800A1 /* CALayer+ID.swift */; }; 44 | 028F67601A8760FA00634383 /* ShapeView+HitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028F675F1A8760FA00634383 /* ShapeView+HitTest.swift */; }; 45 | 02A18B1A1A8D8B0D00FF826A /* MasterVC+Hamburger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02A18B191A8D8B0D00FF826A /* MasterVC+Hamburger.swift */; }; 46 | 02CD3F091A70907600C83B5C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CD3F081A70907600C83B5C /* AppDelegate.swift */; }; 47 | 02CD3F0B1A70907600C83B5C /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CD3F0A1A70907600C83B5C /* MasterViewController.swift */; }; 48 | 02CD3F0D1A70907600C83B5C /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CD3F0C1A70907600C83B5C /* DetailViewController.swift */; }; 49 | 02CD3F101A70907600C83B5C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 02CD3F0E1A70907600C83B5C /* Main.storyboard */; }; 50 | 02CD3F121A70907600C83B5C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 02CD3F111A70907600C83B5C /* Images.xcassets */; }; 51 | 02CD3F151A70907600C83B5C /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 02CD3F131A70907600C83B5C /* LaunchScreen.xib */; }; 52 | 02CD3F2A1A70929F00C83B5C /* ShapeAnimation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45DA4AC419AA62A800133F32 /* ShapeAnimation.framework */; }; 53 | 02E8A7EC1A7A3B9500EB03C3 /* EllipseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02E8A7EB1A7A3B9500EB03C3 /* EllipseViewController.swift */; }; 54 | 4537991D19AA788B004DF99E /* DummyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4537991C19AA788B004DF99E /* DummyTests.swift */; }; 55 | 45DA4AC919AA62A800133F32 /* ShapeAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 45DA4AC819AA62A800133F32 /* ShapeAnimation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 56 | AE39DFAA1B56571500E8456F /* SwiftGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02CD3EEE1A708C8D00C83B5C /* SwiftGraphics.framework */; }; 57 | AE39DFAB1B56573800E8456F /* SwiftGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02CD3EF01A708C8D00C83B5C /* SwiftGraphics.framework */; }; 58 | AED21A2D1A88EDE2004EFD25 /* CAShapeLayer+Path.swift in Sources */ = {isa = PBXBuildFile; fileRef = AED21A2C1A88EDE2004EFD25 /* CAShapeLayer+Path.swift */; }; 59 | AEE57A151A81168F00420D54 /* ShapeView+Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE57A141A81168F00420D54 /* ShapeView+Image.swift */; }; 60 | /* End PBXBuildFile section */ 61 | 62 | /* Begin PBXContainerItemProxy section */ 63 | 02478B071A70C69F00CCAC47 /* PBXContainerItemProxy */ = { 64 | isa = PBXContainerItemProxy; 65 | containerPortal = 454D54FE19AA5CE6000E3EB3 /* Project object */; 66 | proxyType = 1; 67 | remoteGlobalIDString = 45DA4AC319AA62A800133F32; 68 | remoteInfo = ShapeAnimation; 69 | }; 70 | 0256C6591ABEC126007FC099 /* PBXContainerItemProxy */ = { 71 | isa = PBXContainerItemProxy; 72 | containerPortal = 02CD3EE31A708C8D00C83B5C /* SwiftGraphics.xcodeproj */; 73 | proxyType = 2; 74 | remoteGlobalIDString = 451D29091A8A911F00209FE3; 75 | remoteInfo = SwiftGraphics_iOS_UnitTests; 76 | }; 77 | 02CD3EED1A708C8D00C83B5C /* PBXContainerItemProxy */ = { 78 | isa = PBXContainerItemProxy; 79 | containerPortal = 02CD3EE31A708C8D00C83B5C /* SwiftGraphics.xcodeproj */; 80 | proxyType = 2; 81 | remoteGlobalIDString = 454D550719AA5CE6000E3EB3; 82 | remoteInfo = SwiftGraphics_OSX; 83 | }; 84 | 02CD3EEF1A708C8D00C83B5C /* PBXContainerItemProxy */ = { 85 | isa = PBXContainerItemProxy; 86 | containerPortal = 02CD3EE31A708C8D00C83B5C /* SwiftGraphics.xcodeproj */; 87 | proxyType = 2; 88 | remoteGlobalIDString = 45DA4AC419AA62A800133F32; 89 | remoteInfo = SwiftGraphics_iOS; 90 | }; 91 | 02CD3EF11A708C8D00C83B5C /* PBXContainerItemProxy */ = { 92 | isa = PBXContainerItemProxy; 93 | containerPortal = 02CD3EE31A708C8D00C83B5C /* SwiftGraphics.xcodeproj */; 94 | proxyType = 2; 95 | remoteGlobalIDString = 454D551219AA5CE6000E3EB3; 96 | remoteInfo = SwiftGraphicsTests; 97 | }; 98 | 02CD3EF31A708C8D00C83B5C /* PBXContainerItemProxy */ = { 99 | isa = PBXContainerItemProxy; 100 | containerPortal = 02CD3EE31A708C8D00C83B5C /* SwiftGraphics.xcodeproj */; 101 | proxyType = 2; 102 | remoteGlobalIDString = 454FE66219C77031002E4DEF; 103 | remoteInfo = SwiftGraphicsPlayground; 104 | }; 105 | 02CD3EFB1A708EBC00C83B5C /* PBXContainerItemProxy */ = { 106 | isa = PBXContainerItemProxy; 107 | containerPortal = 02CD3EE31A708C8D00C83B5C /* SwiftGraphics.xcodeproj */; 108 | proxyType = 1; 109 | remoteGlobalIDString = 45DA4AC319AA62A800133F32; 110 | remoteInfo = SwiftGraphics_iOS; 111 | }; 112 | 02CD3F281A70929600C83B5C /* PBXContainerItemProxy */ = { 113 | isa = PBXContainerItemProxy; 114 | containerPortal = 454D54FE19AA5CE6000E3EB3 /* Project object */; 115 | proxyType = 1; 116 | remoteGlobalIDString = 45DA4AC319AA62A800133F32; 117 | remoteInfo = ShapeAnimation; 118 | }; 119 | AE39DFA81B56570700E8456F /* PBXContainerItemProxy */ = { 120 | isa = PBXContainerItemProxy; 121 | containerPortal = 02CD3EE31A708C8D00C83B5C /* SwiftGraphics.xcodeproj */; 122 | proxyType = 1; 123 | remoteGlobalIDString = 454D550619AA5CE6000E3EB3; 124 | remoteInfo = SwiftGraphics_OSX; 125 | }; 126 | /* End PBXContainerItemProxy section */ 127 | 128 | /* Begin PBXFileReference section */ 129 | 02147CA01A836E2A001E5617 /* MasterVC+Drag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MasterVC+Drag.swift"; sourceTree = ""; }; 130 | 02147CAB1A847EA3001E5617 /* CALayer+Drag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CALayer+Drag.swift"; sourceTree = ""; }; 131 | 021D75691A89EBB3009C5129 /* CALayer+Slide.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CALayer+Slide.swift"; sourceTree = ""; }; 132 | 022E98091A8071A70028A198 /* ShapeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShapeView.swift; sourceTree = ""; }; 133 | 022E980B1A8071A70028A198 /* AnimationLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationLayer.swift; sourceTree = ""; }; 134 | 022E980C1A8071A70028A198 /* AnimationPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationPair.swift; sourceTree = ""; }; 135 | 022E980D1A8071A70028A198 /* AnimationPrivate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationPrivate.swift; sourceTree = ""; }; 136 | 022E980E1A8071A70028A198 /* CALayer+Animation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CALayer+Animation.swift"; sourceTree = ""; }; 137 | 022E980F1A8071A70028A198 /* CALayer+Pause.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CALayer+Pause.swift"; sourceTree = ""; }; 138 | 022E98101A8071A70028A198 /* CAShapeLayer+Animation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CAShapeLayer+Animation.swift"; sourceTree = ""; }; 139 | 022E981E1A8072270028A198 /* CAShapeLayer+Gradient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CAShapeLayer+Gradient.swift"; sourceTree = ""; }; 140 | 022E981F1A8072270028A198 /* CAShapeLayer+Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CAShapeLayer+Style.swift"; sourceTree = ""; }; 141 | 0256C66F1ABEC1D8007FC099 /* Portability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Portability.swift; sourceTree = ""; }; 142 | 0267D0CE1AC00FF400CEF476 /* ShapeAnimation_OSX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ShapeAnimation_OSX.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 143 | 0267D0D11AC00FF400CEF476 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 144 | 0267D0D21AC00FF400CEF476 /* ShapeAnimation_OSX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShapeAnimation_OSX.h; sourceTree = ""; }; 145 | 027DC1B81A77998D00D3CF60 /* airship@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "airship@2x.png"; sourceTree = ""; }; 146 | 02887B2A1A820CAC00E800A1 /* CALayer+ID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CALayer+ID.swift"; sourceTree = ""; }; 147 | 028F675F1A8760FA00634383 /* ShapeView+HitTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ShapeView+HitTest.swift"; sourceTree = ""; }; 148 | 02A18B191A8D8B0D00FF826A /* MasterVC+Hamburger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MasterVC+Hamburger.swift"; sourceTree = ""; }; 149 | 02CD3EE31A708C8D00C83B5C /* SwiftGraphics.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SwiftGraphics.xcodeproj; path = Vendor/SwiftGraphics/SwiftGraphics.xcodeproj; sourceTree = ""; }; 150 | 02CD3F041A70907600C83B5C /* ShapeAnimation_UITest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ShapeAnimation_UITest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 151 | 02CD3F071A70907600C83B5C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 152 | 02CD3F081A70907600C83B5C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 153 | 02CD3F0A1A70907600C83B5C /* MasterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; 154 | 02CD3F0C1A70907600C83B5C /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; 155 | 02CD3F0F1A70907600C83B5C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 156 | 02CD3F111A70907600C83B5C /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 157 | 02CD3F141A70907600C83B5C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 158 | 02E8A7EB1A7A3B9500EB03C3 /* EllipseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EllipseViewController.swift; sourceTree = ""; }; 159 | 4537990519AA671E004DF99E /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 160 | 4537991C19AA788B004DF99E /* DummyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DummyTests.swift; sourceTree = ""; }; 161 | 454D551219AA5CE6000E3EB3 /* ShapeAnimationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ShapeAnimationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 162 | 454D551519AA5CE6000E3EB3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 163 | 45DA4AC419AA62A800133F32 /* ShapeAnimation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ShapeAnimation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 164 | 45DA4AC719AA62A800133F32 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 165 | 45DA4AC819AA62A800133F32 /* ShapeAnimation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShapeAnimation.h; sourceTree = ""; }; 166 | AE39DFA41B56567400E8456F /* SwiftUtilities.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUtilities.framework; path = "Vendor/SwiftGraphics/Externals/SwiftUtilities/build/Debug-iphoneos/SwiftUtilities.framework"; sourceTree = ""; }; 167 | AE39DFA61B56568000E8456F /* SwiftUtilities.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUtilities.framework; path = Vendor/SwiftGraphics/Externals/SwiftUtilities/build/Debug/SwiftUtilities.framework; sourceTree = ""; }; 168 | AE39DFB31B565D9000E8456F /* SwiftUtilities.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUtilities.framework; path = "../../../Library/Developer/Xcode/DerivedData/ShapeAnimation-bjbfrttucwqehrbjhfnalpfhaaoa/Build/Products/Debug/SwiftUtilities.framework"; sourceTree = ""; }; 169 | AED21A2C1A88EDE2004EFD25 /* CAShapeLayer+Path.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CAShapeLayer+Path.swift"; sourceTree = ""; }; 170 | AEE57A141A81168F00420D54 /* ShapeView+Image.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ShapeView+Image.swift"; sourceTree = ""; }; 171 | /* End PBXFileReference section */ 172 | 173 | /* Begin PBXFrameworksBuildPhase section */ 174 | 0267D0CA1AC00FF400CEF476 /* Frameworks */ = { 175 | isa = PBXFrameworksBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | AE39DFAA1B56571500E8456F /* SwiftGraphics.framework in Frameworks */, 179 | ); 180 | runOnlyForDeploymentPostprocessing = 0; 181 | }; 182 | 02CD3F011A70907600C83B5C /* Frameworks */ = { 183 | isa = PBXFrameworksBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | 02CD3F2A1A70929F00C83B5C /* ShapeAnimation.framework in Frameworks */, 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | 454D550F19AA5CE6000E3EB3 /* Frameworks */ = { 191 | isa = PBXFrameworksBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | 02478B091A70C6A200CCAC47 /* ShapeAnimation.framework in Frameworks */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | 45DA4AC019AA62A800133F32 /* Frameworks */ = { 199 | isa = PBXFrameworksBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | AE39DFAB1B56573800E8456F /* SwiftGraphics.framework in Frameworks */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXFrameworksBuildPhase section */ 207 | 208 | /* Begin PBXGroup section */ 209 | 022E98081A8071A70028A198 /* View */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | 022E98091A8071A70028A198 /* ShapeView.swift */, 213 | AEE57A141A81168F00420D54 /* ShapeView+Image.swift */, 214 | 028F675F1A8760FA00634383 /* ShapeView+HitTest.swift */, 215 | 02887B2A1A820CAC00E800A1 /* CALayer+ID.swift */, 216 | 022E981E1A8072270028A198 /* CAShapeLayer+Gradient.swift */, 217 | 022E981F1A8072270028A198 /* CAShapeLayer+Style.swift */, 218 | AED21A2C1A88EDE2004EFD25 /* CAShapeLayer+Path.swift */, 219 | ); 220 | path = View; 221 | sourceTree = ""; 222 | }; 223 | 022E980A1A8071A70028A198 /* Animation */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | 022E980E1A8071A70028A198 /* CALayer+Animation.swift */, 227 | 021D75691A89EBB3009C5129 /* CALayer+Slide.swift */, 228 | 02147CAB1A847EA3001E5617 /* CALayer+Drag.swift */, 229 | 022E98101A8071A70028A198 /* CAShapeLayer+Animation.swift */, 230 | 022E980B1A8071A70028A198 /* AnimationLayer.swift */, 231 | 022E980F1A8071A70028A198 /* CALayer+Pause.swift */, 232 | 022E980C1A8071A70028A198 /* AnimationPair.swift */, 233 | 022E980D1A8071A70028A198 /* AnimationPrivate.swift */, 234 | ); 235 | path = Animation; 236 | sourceTree = ""; 237 | }; 238 | 0267D0CF1AC00FF400CEF476 /* ShapeAnimation_OSX */ = { 239 | isa = PBXGroup; 240 | children = ( 241 | 0267D0D21AC00FF400CEF476 /* ShapeAnimation_OSX.h */, 242 | 0267D0D01AC00FF400CEF476 /* Supporting Files */, 243 | ); 244 | path = ShapeAnimation_OSX; 245 | sourceTree = ""; 246 | }; 247 | 0267D0D01AC00FF400CEF476 /* Supporting Files */ = { 248 | isa = PBXGroup; 249 | children = ( 250 | 0267D0D11AC00FF400CEF476 /* Info.plist */, 251 | ); 252 | name = "Supporting Files"; 253 | sourceTree = ""; 254 | }; 255 | 027DC1B71A77998D00D3CF60 /* Images */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | 027DC1B81A77998D00D3CF60 /* airship@2x.png */, 259 | ); 260 | path = Images; 261 | sourceTree = ""; 262 | }; 263 | 02CD3EE41A708C8D00C83B5C /* Products */ = { 264 | isa = PBXGroup; 265 | children = ( 266 | 02CD3EEE1A708C8D00C83B5C /* SwiftGraphics.framework */, 267 | 02CD3EF01A708C8D00C83B5C /* SwiftGraphics.framework */, 268 | 02CD3EF41A708C8D00C83B5C /* SwiftGraphicsPlayground.framework */, 269 | 02CD3EF21A708C8D00C83B5C /* SwiftGraphics_OSX_UnitTests.xctest */, 270 | 0256C65A1ABEC126007FC099 /* SwiftGraphics_iOS_UnitTests.xctest */, 271 | ); 272 | name = Products; 273 | sourceTree = ""; 274 | }; 275 | 02CD3F051A70907600C83B5C /* ShapeAnimation_UITest */ = { 276 | isa = PBXGroup; 277 | children = ( 278 | 02CD3F081A70907600C83B5C /* AppDelegate.swift */, 279 | 02CD3F0A1A70907600C83B5C /* MasterViewController.swift */, 280 | 02147CA01A836E2A001E5617 /* MasterVC+Drag.swift */, 281 | 02A18B191A8D8B0D00FF826A /* MasterVC+Hamburger.swift */, 282 | 02CD3F0C1A70907600C83B5C /* DetailViewController.swift */, 283 | 02E8A7EB1A7A3B9500EB03C3 /* EllipseViewController.swift */, 284 | 02CD3F0E1A70907600C83B5C /* Main.storyboard */, 285 | 027DC1B71A77998D00D3CF60 /* Images */, 286 | 02CD3F111A70907600C83B5C /* Images.xcassets */, 287 | 02CD3F131A70907600C83B5C /* LaunchScreen.xib */, 288 | 02CD3F061A70907600C83B5C /* Supporting Files */, 289 | ); 290 | path = ShapeAnimation_UITest; 291 | sourceTree = ""; 292 | }; 293 | 02CD3F061A70907600C83B5C /* Supporting Files */ = { 294 | isa = PBXGroup; 295 | children = ( 296 | 02CD3F071A70907600C83B5C /* Info.plist */, 297 | ); 298 | name = "Supporting Files"; 299 | sourceTree = ""; 300 | }; 301 | 4514F5F71A61D2D800CC9C26 /* Frameworks */ = { 302 | isa = PBXGroup; 303 | children = ( 304 | AE39DFB31B565D9000E8456F /* SwiftUtilities.framework */, 305 | AE39DFA61B56568000E8456F /* SwiftUtilities.framework */, 306 | AE39DFA41B56567400E8456F /* SwiftUtilities.framework */, 307 | 02CD3EE31A708C8D00C83B5C /* SwiftGraphics.xcodeproj */, 308 | ); 309 | name = Frameworks; 310 | sourceTree = ""; 311 | }; 312 | 454D54FD19AA5CE6000E3EB3 = { 313 | isa = PBXGroup; 314 | children = ( 315 | 4537990519AA671E004DF99E /* README.md */, 316 | 45DA4AC519AA62A800133F32 /* ShapeAnimation */, 317 | 454D551319AA5CE6000E3EB3 /* ShapeAnimation_UnitTests */, 318 | 02CD3F051A70907600C83B5C /* ShapeAnimation_UITest */, 319 | 0267D0CF1AC00FF400CEF476 /* ShapeAnimation_OSX */, 320 | 4514F5F71A61D2D800CC9C26 /* Frameworks */, 321 | 454D550819AA5CE6000E3EB3 /* Products */, 322 | ); 323 | sourceTree = ""; 324 | }; 325 | 454D550819AA5CE6000E3EB3 /* Products */ = { 326 | isa = PBXGroup; 327 | children = ( 328 | 454D551219AA5CE6000E3EB3 /* ShapeAnimationTests.xctest */, 329 | 45DA4AC419AA62A800133F32 /* ShapeAnimation.framework */, 330 | 02CD3F041A70907600C83B5C /* ShapeAnimation_UITest.app */, 331 | 0267D0CE1AC00FF400CEF476 /* ShapeAnimation_OSX.framework */, 332 | ); 333 | name = Products; 334 | sourceTree = ""; 335 | }; 336 | 454D551319AA5CE6000E3EB3 /* ShapeAnimation_UnitTests */ = { 337 | isa = PBXGroup; 338 | children = ( 339 | 4537991C19AA788B004DF99E /* DummyTests.swift */, 340 | 454D551419AA5CE6000E3EB3 /* Supporting Files */, 341 | ); 342 | path = ShapeAnimation_UnitTests; 343 | sourceTree = ""; 344 | }; 345 | 454D551419AA5CE6000E3EB3 /* Supporting Files */ = { 346 | isa = PBXGroup; 347 | children = ( 348 | 454D551519AA5CE6000E3EB3 /* Info.plist */, 349 | ); 350 | name = "Supporting Files"; 351 | sourceTree = ""; 352 | }; 353 | 45DA4AC519AA62A800133F32 /* ShapeAnimation */ = { 354 | isa = PBXGroup; 355 | children = ( 356 | 0256C66F1ABEC1D8007FC099 /* Portability.swift */, 357 | 022E98081A8071A70028A198 /* View */, 358 | 022E980A1A8071A70028A198 /* Animation */, 359 | 45DA4AC819AA62A800133F32 /* ShapeAnimation.h */, 360 | 45DA4AC619AA62A800133F32 /* Supporting Files */, 361 | ); 362 | path = ShapeAnimation; 363 | sourceTree = ""; 364 | }; 365 | 45DA4AC619AA62A800133F32 /* Supporting Files */ = { 366 | isa = PBXGroup; 367 | children = ( 368 | 45DA4AC719AA62A800133F32 /* Info.plist */, 369 | ); 370 | name = "Supporting Files"; 371 | sourceTree = ""; 372 | }; 373 | /* End PBXGroup section */ 374 | 375 | /* Begin PBXHeadersBuildPhase section */ 376 | 0267D0CB1AC00FF400CEF476 /* Headers */ = { 377 | isa = PBXHeadersBuildPhase; 378 | buildActionMask = 2147483647; 379 | files = ( 380 | 0267D0F71AC0102700CEF476 /* ShapeAnimation.h in Headers */, 381 | 0267D0D31AC00FF400CEF476 /* ShapeAnimation_OSX.h in Headers */, 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | }; 385 | 45DA4AC119AA62A800133F32 /* Headers */ = { 386 | isa = PBXHeadersBuildPhase; 387 | buildActionMask = 2147483647; 388 | files = ( 389 | 45DA4AC919AA62A800133F32 /* ShapeAnimation.h in Headers */, 390 | ); 391 | runOnlyForDeploymentPostprocessing = 0; 392 | }; 393 | /* End PBXHeadersBuildPhase section */ 394 | 395 | /* Begin PBXNativeTarget section */ 396 | 0267D0CD1AC00FF400CEF476 /* ShapeAnimation_OSX */ = { 397 | isa = PBXNativeTarget; 398 | buildConfigurationList = 0267D0E11AC00FF400CEF476 /* Build configuration list for PBXNativeTarget "ShapeAnimation_OSX" */; 399 | buildPhases = ( 400 | 0267D0C91AC00FF400CEF476 /* Sources */, 401 | 0267D0CA1AC00FF400CEF476 /* Frameworks */, 402 | 0267D0CB1AC00FF400CEF476 /* Headers */, 403 | 0267D0CC1AC00FF400CEF476 /* Resources */, 404 | ); 405 | buildRules = ( 406 | ); 407 | dependencies = ( 408 | AE39DFA91B56570700E8456F /* PBXTargetDependency */, 409 | ); 410 | name = ShapeAnimation_OSX; 411 | productName = ShapeAnimation_OSX; 412 | productReference = 0267D0CE1AC00FF400CEF476 /* ShapeAnimation_OSX.framework */; 413 | productType = "com.apple.product-type.framework"; 414 | }; 415 | 02CD3F031A70907600C83B5C /* ShapeAnimation_UITest */ = { 416 | isa = PBXNativeTarget; 417 | buildConfigurationList = 02CD3F221A70907600C83B5C /* Build configuration list for PBXNativeTarget "ShapeAnimation_UITest" */; 418 | buildPhases = ( 419 | 02CD3F001A70907600C83B5C /* Sources */, 420 | 02CD3F011A70907600C83B5C /* Frameworks */, 421 | 02CD3F021A70907600C83B5C /* Resources */, 422 | ); 423 | buildRules = ( 424 | ); 425 | dependencies = ( 426 | 02CD3F291A70929600C83B5C /* PBXTargetDependency */, 427 | ); 428 | name = ShapeAnimation_UITest; 429 | productName = ShapeAnimation_UITest; 430 | productReference = 02CD3F041A70907600C83B5C /* ShapeAnimation_UITest.app */; 431 | productType = "com.apple.product-type.application"; 432 | }; 433 | 454D551119AA5CE6000E3EB3 /* ShapeAnimationTests */ = { 434 | isa = PBXNativeTarget; 435 | buildConfigurationList = 454D551D19AA5CE6000E3EB3 /* Build configuration list for PBXNativeTarget "ShapeAnimationTests" */; 436 | buildPhases = ( 437 | 454D550E19AA5CE6000E3EB3 /* Sources */, 438 | 454D550F19AA5CE6000E3EB3 /* Frameworks */, 439 | 454D551019AA5CE6000E3EB3 /* Resources */, 440 | ); 441 | buildRules = ( 442 | ); 443 | dependencies = ( 444 | 02478B081A70C69F00CCAC47 /* PBXTargetDependency */, 445 | ); 446 | name = ShapeAnimationTests; 447 | productName = ShapeAnimationTests; 448 | productReference = 454D551219AA5CE6000E3EB3 /* ShapeAnimationTests.xctest */; 449 | productType = "com.apple.product-type.bundle.unit-test"; 450 | }; 451 | 45DA4AC319AA62A800133F32 /* ShapeAnimation */ = { 452 | isa = PBXNativeTarget; 453 | buildConfigurationList = 45DA4AD819AA62A800133F32 /* Build configuration list for PBXNativeTarget "ShapeAnimation" */; 454 | buildPhases = ( 455 | 45DA4ABF19AA62A800133F32 /* Sources */, 456 | 45DA4AC019AA62A800133F32 /* Frameworks */, 457 | 45DA4AC119AA62A800133F32 /* Headers */, 458 | 45DA4AC219AA62A800133F32 /* Resources */, 459 | ); 460 | buildRules = ( 461 | ); 462 | dependencies = ( 463 | 02CD3EFC1A708EBC00C83B5C /* PBXTargetDependency */, 464 | ); 465 | name = ShapeAnimation; 466 | productName = ShapeAnimation; 467 | productReference = 45DA4AC419AA62A800133F32 /* ShapeAnimation.framework */; 468 | productType = "com.apple.product-type.framework"; 469 | }; 470 | /* End PBXNativeTarget section */ 471 | 472 | /* Begin PBXProject section */ 473 | 454D54FE19AA5CE6000E3EB3 /* Project object */ = { 474 | isa = PBXProject; 475 | attributes = { 476 | LastSwiftMigration = 0700; 477 | LastSwiftUpdateCheck = 0700; 478 | LastUpgradeCheck = 0700; 479 | ORGANIZATIONNAME = github.com/rhcad; 480 | TargetAttributes = { 481 | 0267D0CD1AC00FF400CEF476 = { 482 | CreatedOnToolsVersion = 6.3; 483 | }; 484 | 02CD3F031A70907600C83B5C = { 485 | CreatedOnToolsVersion = 6.1.1; 486 | }; 487 | 454D551119AA5CE6000E3EB3 = { 488 | CreatedOnToolsVersion = 6.0; 489 | }; 490 | 45DA4AC319AA62A800133F32 = { 491 | CreatedOnToolsVersion = 6.0; 492 | }; 493 | }; 494 | }; 495 | buildConfigurationList = 454D550119AA5CE6000E3EB3 /* Build configuration list for PBXProject "ShapeAnimation" */; 496 | compatibilityVersion = "Xcode 3.2"; 497 | developmentRegion = English; 498 | hasScannedForEncodings = 0; 499 | knownRegions = ( 500 | en, 501 | Base, 502 | ); 503 | mainGroup = 454D54FD19AA5CE6000E3EB3; 504 | productRefGroup = 454D550819AA5CE6000E3EB3 /* Products */; 505 | projectDirPath = ""; 506 | projectReferences = ( 507 | { 508 | ProductGroup = 02CD3EE41A708C8D00C83B5C /* Products */; 509 | ProjectRef = 02CD3EE31A708C8D00C83B5C /* SwiftGraphics.xcodeproj */; 510 | }, 511 | ); 512 | projectRoot = ""; 513 | targets = ( 514 | 45DA4AC319AA62A800133F32 /* ShapeAnimation */, 515 | 454D551119AA5CE6000E3EB3 /* ShapeAnimationTests */, 516 | 02CD3F031A70907600C83B5C /* ShapeAnimation_UITest */, 517 | 0267D0CD1AC00FF400CEF476 /* ShapeAnimation_OSX */, 518 | ); 519 | }; 520 | /* End PBXProject section */ 521 | 522 | /* Begin PBXReferenceProxy section */ 523 | 0256C65A1ABEC126007FC099 /* SwiftGraphics_iOS_UnitTests.xctest */ = { 524 | isa = PBXReferenceProxy; 525 | fileType = wrapper.cfbundle; 526 | path = SwiftGraphics_iOS_UnitTests.xctest; 527 | remoteRef = 0256C6591ABEC126007FC099 /* PBXContainerItemProxy */; 528 | sourceTree = BUILT_PRODUCTS_DIR; 529 | }; 530 | 02CD3EEE1A708C8D00C83B5C /* SwiftGraphics.framework */ = { 531 | isa = PBXReferenceProxy; 532 | fileType = wrapper.framework; 533 | path = SwiftGraphics.framework; 534 | remoteRef = 02CD3EED1A708C8D00C83B5C /* PBXContainerItemProxy */; 535 | sourceTree = BUILT_PRODUCTS_DIR; 536 | }; 537 | 02CD3EF01A708C8D00C83B5C /* SwiftGraphics.framework */ = { 538 | isa = PBXReferenceProxy; 539 | fileType = wrapper.framework; 540 | path = SwiftGraphics.framework; 541 | remoteRef = 02CD3EEF1A708C8D00C83B5C /* PBXContainerItemProxy */; 542 | sourceTree = BUILT_PRODUCTS_DIR; 543 | }; 544 | 02CD3EF21A708C8D00C83B5C /* SwiftGraphics_OSX_UnitTests.xctest */ = { 545 | isa = PBXReferenceProxy; 546 | fileType = wrapper.cfbundle; 547 | path = SwiftGraphics_OSX_UnitTests.xctest; 548 | remoteRef = 02CD3EF11A708C8D00C83B5C /* PBXContainerItemProxy */; 549 | sourceTree = BUILT_PRODUCTS_DIR; 550 | }; 551 | 02CD3EF41A708C8D00C83B5C /* SwiftGraphicsPlayground.framework */ = { 552 | isa = PBXReferenceProxy; 553 | fileType = wrapper.framework; 554 | path = SwiftGraphicsPlayground.framework; 555 | remoteRef = 02CD3EF31A708C8D00C83B5C /* PBXContainerItemProxy */; 556 | sourceTree = BUILT_PRODUCTS_DIR; 557 | }; 558 | /* End PBXReferenceProxy section */ 559 | 560 | /* Begin PBXResourcesBuildPhase section */ 561 | 0267D0CC1AC00FF400CEF476 /* Resources */ = { 562 | isa = PBXResourcesBuildPhase; 563 | buildActionMask = 2147483647; 564 | files = ( 565 | ); 566 | runOnlyForDeploymentPostprocessing = 0; 567 | }; 568 | 02CD3F021A70907600C83B5C /* Resources */ = { 569 | isa = PBXResourcesBuildPhase; 570 | buildActionMask = 2147483647; 571 | files = ( 572 | 02CD3F101A70907600C83B5C /* Main.storyboard in Resources */, 573 | 02CD3F151A70907600C83B5C /* LaunchScreen.xib in Resources */, 574 | 02CD3F121A70907600C83B5C /* Images.xcassets in Resources */, 575 | 027DC1B91A77998D00D3CF60 /* airship@2x.png in Resources */, 576 | ); 577 | runOnlyForDeploymentPostprocessing = 0; 578 | }; 579 | 454D551019AA5CE6000E3EB3 /* Resources */ = { 580 | isa = PBXResourcesBuildPhase; 581 | buildActionMask = 2147483647; 582 | files = ( 583 | ); 584 | runOnlyForDeploymentPostprocessing = 0; 585 | }; 586 | 45DA4AC219AA62A800133F32 /* Resources */ = { 587 | isa = PBXResourcesBuildPhase; 588 | buildActionMask = 2147483647; 589 | files = ( 590 | ); 591 | runOnlyForDeploymentPostprocessing = 0; 592 | }; 593 | /* End PBXResourcesBuildPhase section */ 594 | 595 | /* Begin PBXSourcesBuildPhase section */ 596 | 0267D0C91AC00FF400CEF476 /* Sources */ = { 597 | isa = PBXSourcesBuildPhase; 598 | buildActionMask = 2147483647; 599 | files = ( 600 | 0267D0E71AC0101500CEF476 /* Portability.swift in Sources */, 601 | 0267D0E81AC0101500CEF476 /* ShapeView.swift in Sources */, 602 | 0267D0E91AC0101500CEF476 /* ShapeView+Image.swift in Sources */, 603 | 0267D0EA1AC0101500CEF476 /* ShapeView+HitTest.swift in Sources */, 604 | 0267D0EB1AC0101500CEF476 /* CALayer+ID.swift in Sources */, 605 | 0267D0EC1AC0101500CEF476 /* CAShapeLayer+Gradient.swift in Sources */, 606 | 0267D0ED1AC0101500CEF476 /* CAShapeLayer+Style.swift in Sources */, 607 | 0267D0EE1AC0101500CEF476 /* CAShapeLayer+Path.swift in Sources */, 608 | 0267D0EF1AC0101500CEF476 /* CALayer+Animation.swift in Sources */, 609 | 0267D0F01AC0101500CEF476 /* CALayer+Slide.swift in Sources */, 610 | 0267D0F11AC0101500CEF476 /* CALayer+Drag.swift in Sources */, 611 | 0267D0F21AC0101500CEF476 /* CAShapeLayer+Animation.swift in Sources */, 612 | 0267D0F31AC0101500CEF476 /* AnimationLayer.swift in Sources */, 613 | 0267D0F41AC0101500CEF476 /* CALayer+Pause.swift in Sources */, 614 | 0267D0F51AC0101500CEF476 /* AnimationPair.swift in Sources */, 615 | 0267D0F61AC0101500CEF476 /* AnimationPrivate.swift in Sources */, 616 | ); 617 | runOnlyForDeploymentPostprocessing = 0; 618 | }; 619 | 02CD3F001A70907600C83B5C /* Sources */ = { 620 | isa = PBXSourcesBuildPhase; 621 | buildActionMask = 2147483647; 622 | files = ( 623 | 02A18B1A1A8D8B0D00FF826A /* MasterVC+Hamburger.swift in Sources */, 624 | 02CD3F0D1A70907600C83B5C /* DetailViewController.swift in Sources */, 625 | 02147CA11A836E2A001E5617 /* MasterVC+Drag.swift in Sources */, 626 | 02E8A7EC1A7A3B9500EB03C3 /* EllipseViewController.swift in Sources */, 627 | 02CD3F0B1A70907600C83B5C /* MasterViewController.swift in Sources */, 628 | 02CD3F091A70907600C83B5C /* AppDelegate.swift in Sources */, 629 | ); 630 | runOnlyForDeploymentPostprocessing = 0; 631 | }; 632 | 454D550E19AA5CE6000E3EB3 /* Sources */ = { 633 | isa = PBXSourcesBuildPhase; 634 | buildActionMask = 2147483647; 635 | files = ( 636 | 4537991D19AA788B004DF99E /* DummyTests.swift in Sources */, 637 | ); 638 | runOnlyForDeploymentPostprocessing = 0; 639 | }; 640 | 45DA4ABF19AA62A800133F32 /* Sources */ = { 641 | isa = PBXSourcesBuildPhase; 642 | buildActionMask = 2147483647; 643 | files = ( 644 | 02887B2B1A820CAC00E800A1 /* CALayer+ID.swift in Sources */, 645 | 022E98171A8071A70028A198 /* CALayer+Animation.swift in Sources */, 646 | 022E98161A8071A70028A198 /* AnimationPrivate.swift in Sources */, 647 | 022E98131A8071A70028A198 /* ShapeView.swift in Sources */, 648 | 022E98191A8071A70028A198 /* CAShapeLayer+Animation.swift in Sources */, 649 | 028F67601A8760FA00634383 /* ShapeView+HitTest.swift in Sources */, 650 | AED21A2D1A88EDE2004EFD25 /* CAShapeLayer+Path.swift in Sources */, 651 | 02147CAC1A847EA3001E5617 /* CALayer+Drag.swift in Sources */, 652 | 021D756A1A89EBB3009C5129 /* CALayer+Slide.swift in Sources */, 653 | AEE57A151A81168F00420D54 /* ShapeView+Image.swift in Sources */, 654 | 022E98151A8071A70028A198 /* AnimationPair.swift in Sources */, 655 | 022E98201A8072270028A198 /* CAShapeLayer+Gradient.swift in Sources */, 656 | 022E98211A8072270028A198 /* CAShapeLayer+Style.swift in Sources */, 657 | 022E98181A8071A70028A198 /* CALayer+Pause.swift in Sources */, 658 | 022E98141A8071A70028A198 /* AnimationLayer.swift in Sources */, 659 | 0256C6701ABEC1D8007FC099 /* Portability.swift in Sources */, 660 | ); 661 | runOnlyForDeploymentPostprocessing = 0; 662 | }; 663 | /* End PBXSourcesBuildPhase section */ 664 | 665 | /* Begin PBXTargetDependency section */ 666 | 02478B081A70C69F00CCAC47 /* PBXTargetDependency */ = { 667 | isa = PBXTargetDependency; 668 | target = 45DA4AC319AA62A800133F32 /* ShapeAnimation */; 669 | targetProxy = 02478B071A70C69F00CCAC47 /* PBXContainerItemProxy */; 670 | }; 671 | 02CD3EFC1A708EBC00C83B5C /* PBXTargetDependency */ = { 672 | isa = PBXTargetDependency; 673 | name = SwiftGraphics_iOS; 674 | targetProxy = 02CD3EFB1A708EBC00C83B5C /* PBXContainerItemProxy */; 675 | }; 676 | 02CD3F291A70929600C83B5C /* PBXTargetDependency */ = { 677 | isa = PBXTargetDependency; 678 | target = 45DA4AC319AA62A800133F32 /* ShapeAnimation */; 679 | targetProxy = 02CD3F281A70929600C83B5C /* PBXContainerItemProxy */; 680 | }; 681 | AE39DFA91B56570700E8456F /* PBXTargetDependency */ = { 682 | isa = PBXTargetDependency; 683 | name = SwiftGraphics_OSX; 684 | targetProxy = AE39DFA81B56570700E8456F /* PBXContainerItemProxy */; 685 | }; 686 | /* End PBXTargetDependency section */ 687 | 688 | /* Begin PBXVariantGroup section */ 689 | 02CD3F0E1A70907600C83B5C /* Main.storyboard */ = { 690 | isa = PBXVariantGroup; 691 | children = ( 692 | 02CD3F0F1A70907600C83B5C /* Base */, 693 | ); 694 | name = Main.storyboard; 695 | sourceTree = ""; 696 | }; 697 | 02CD3F131A70907600C83B5C /* LaunchScreen.xib */ = { 698 | isa = PBXVariantGroup; 699 | children = ( 700 | 02CD3F141A70907600C83B5C /* Base */, 701 | ); 702 | name = LaunchScreen.xib; 703 | sourceTree = ""; 704 | }; 705 | /* End PBXVariantGroup section */ 706 | 707 | /* Begin XCBuildConfiguration section */ 708 | 0267D0E21AC00FF400CEF476 /* Debug */ = { 709 | isa = XCBuildConfiguration; 710 | buildSettings = { 711 | CLANG_CXX_LIBRARY = "libc++"; 712 | COMBINE_HIDPI_IMAGES = YES; 713 | DEBUG_INFORMATION_FORMAT = dwarf; 714 | DEFINES_MODULE = YES; 715 | DYLIB_COMPATIBILITY_VERSION = 1; 716 | DYLIB_CURRENT_VERSION = 1; 717 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 718 | FRAMEWORK_VERSION = A; 719 | GCC_NO_COMMON_BLOCKS = YES; 720 | GCC_PREPROCESSOR_DEFINITIONS = ( 721 | "DEBUG=1", 722 | "$(inherited)", 723 | ); 724 | INFOPLIST_FILE = ShapeAnimation_OSX/Info.plist; 725 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 726 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 727 | PRODUCT_BUNDLE_IDENTIFIER = "github.rhcad.$(PRODUCT_NAME:rfc1034identifier)"; 728 | PRODUCT_NAME = "$(TARGET_NAME)"; 729 | SKIP_INSTALL = YES; 730 | }; 731 | name = Debug; 732 | }; 733 | 0267D0E31AC00FF400CEF476 /* Release */ = { 734 | isa = XCBuildConfiguration; 735 | buildSettings = { 736 | CLANG_CXX_LIBRARY = "libc++"; 737 | COMBINE_HIDPI_IMAGES = YES; 738 | COPY_PHASE_STRIP = NO; 739 | DEFINES_MODULE = YES; 740 | DYLIB_COMPATIBILITY_VERSION = 1; 741 | DYLIB_CURRENT_VERSION = 1; 742 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 743 | FRAMEWORK_VERSION = A; 744 | GCC_NO_COMMON_BLOCKS = YES; 745 | INFOPLIST_FILE = ShapeAnimation_OSX/Info.plist; 746 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 747 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 748 | PRODUCT_BUNDLE_IDENTIFIER = "github.rhcad.$(PRODUCT_NAME:rfc1034identifier)"; 749 | PRODUCT_NAME = "$(TARGET_NAME)"; 750 | SKIP_INSTALL = YES; 751 | }; 752 | name = Release; 753 | }; 754 | 02CD3F231A70907600C83B5C /* Debug */ = { 755 | isa = XCBuildConfiguration; 756 | buildSettings = { 757 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 758 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 759 | GCC_PREPROCESSOR_DEFINITIONS = ( 760 | "DEBUG=1", 761 | "$(inherited)", 762 | ); 763 | INFOPLIST_FILE = ShapeAnimation_UITest/Info.plist; 764 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 765 | PRODUCT_BUNDLE_IDENTIFIER = "github.rhcad.$(PRODUCT_NAME:rfc1034identifier)"; 766 | PRODUCT_NAME = "$(TARGET_NAME)"; 767 | SDKROOT = iphoneos; 768 | TARGETED_DEVICE_FAMILY = "1,2"; 769 | }; 770 | name = Debug; 771 | }; 772 | 02CD3F241A70907600C83B5C /* Release */ = { 773 | isa = XCBuildConfiguration; 774 | buildSettings = { 775 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 776 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 777 | INFOPLIST_FILE = ShapeAnimation_UITest/Info.plist; 778 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 779 | PRODUCT_BUNDLE_IDENTIFIER = "github.rhcad.$(PRODUCT_NAME:rfc1034identifier)"; 780 | PRODUCT_NAME = "$(TARGET_NAME)"; 781 | SDKROOT = iphoneos; 782 | TARGETED_DEVICE_FAMILY = "1,2"; 783 | VALIDATE_PRODUCT = YES; 784 | }; 785 | name = Release; 786 | }; 787 | 454D551819AA5CE6000E3EB3 /* Debug */ = { 788 | isa = XCBuildConfiguration; 789 | buildSettings = { 790 | ALWAYS_SEARCH_USER_PATHS = NO; 791 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 792 | CLANG_ENABLE_MODULES = YES; 793 | CLANG_ENABLE_OBJC_ARC = YES; 794 | CLANG_STATIC_ANALYZER_MODE = shallow; 795 | CLANG_WARN_BOOL_CONVERSION = YES; 796 | CLANG_WARN_CONSTANT_CONVERSION = YES; 797 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 798 | CLANG_WARN_EMPTY_BODY = YES; 799 | CLANG_WARN_ENUM_CONVERSION = YES; 800 | CLANG_WARN_INT_CONVERSION = YES; 801 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 802 | CLANG_WARN_UNREACHABLE_CODE = YES; 803 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 804 | COPY_PHASE_STRIP = NO; 805 | CURRENT_PROJECT_VERSION = 1; 806 | ENABLE_STRICT_OBJC_MSGSEND = YES; 807 | ENABLE_TESTABILITY = YES; 808 | GCC_C_LANGUAGE_STANDARD = gnu99; 809 | GCC_DYNAMIC_NO_PIC = NO; 810 | GCC_OPTIMIZATION_LEVEL = 0; 811 | GCC_PREPROCESSOR_DEFINITIONS = ( 812 | "DEBUG=1", 813 | "$(inherited)", 814 | ); 815 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 816 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 817 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 818 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 819 | GCC_WARN_UNDECLARED_SELECTOR = YES; 820 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 821 | GCC_WARN_UNUSED_FUNCTION = YES; 822 | GCC_WARN_UNUSED_VARIABLE = YES; 823 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 824 | MACOSX_DEPLOYMENT_TARGET = 10.10; 825 | MTL_ENABLE_DEBUG_INFO = YES; 826 | ONLY_ACTIVE_ARCH = YES; 827 | RUN_CLANG_STATIC_ANALYZER = YES; 828 | SDKROOT = macosx; 829 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 830 | VERSIONING_SYSTEM = "apple-generic"; 831 | VERSION_INFO_PREFIX = ""; 832 | }; 833 | name = Debug; 834 | }; 835 | 454D551919AA5CE6000E3EB3 /* Release */ = { 836 | isa = XCBuildConfiguration; 837 | buildSettings = { 838 | ALWAYS_SEARCH_USER_PATHS = NO; 839 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 840 | CLANG_ENABLE_MODULES = YES; 841 | CLANG_ENABLE_OBJC_ARC = YES; 842 | CLANG_STATIC_ANALYZER_MODE = shallow; 843 | CLANG_WARN_BOOL_CONVERSION = YES; 844 | CLANG_WARN_CONSTANT_CONVERSION = YES; 845 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 846 | CLANG_WARN_EMPTY_BODY = YES; 847 | CLANG_WARN_ENUM_CONVERSION = YES; 848 | CLANG_WARN_INT_CONVERSION = YES; 849 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 850 | CLANG_WARN_UNREACHABLE_CODE = YES; 851 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 852 | COPY_PHASE_STRIP = YES; 853 | CURRENT_PROJECT_VERSION = 1; 854 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 855 | ENABLE_NS_ASSERTIONS = NO; 856 | ENABLE_STRICT_OBJC_MSGSEND = YES; 857 | GCC_C_LANGUAGE_STANDARD = gnu99; 858 | GCC_TREAT_WARNINGS_AS_ERRORS = YES; 859 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 860 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 861 | GCC_WARN_UNDECLARED_SELECTOR = YES; 862 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 863 | GCC_WARN_UNUSED_FUNCTION = YES; 864 | GCC_WARN_UNUSED_VARIABLE = YES; 865 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 866 | MACOSX_DEPLOYMENT_TARGET = 10.10; 867 | MTL_ENABLE_DEBUG_INFO = NO; 868 | RUN_CLANG_STATIC_ANALYZER = YES; 869 | SDKROOT = macosx; 870 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 871 | VERSIONING_SYSTEM = "apple-generic"; 872 | VERSION_INFO_PREFIX = ""; 873 | }; 874 | name = Release; 875 | }; 876 | 454D551E19AA5CE6000E3EB3 /* Debug */ = { 877 | isa = XCBuildConfiguration; 878 | buildSettings = { 879 | COMBINE_HIDPI_IMAGES = YES; 880 | GCC_PREPROCESSOR_DEFINITIONS = ( 881 | "DEBUG=1", 882 | "$(inherited)", 883 | ); 884 | INFOPLIST_FILE = ShapeAnimation_UnitTests/Info.plist; 885 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks @loader_path/.."; 886 | PRODUCT_BUNDLE_IDENTIFIER = "github.rhcad.$(PRODUCT_NAME:rfc1034identifier)"; 887 | PRODUCT_NAME = "$(TARGET_NAME)"; 888 | SDKROOT = iphoneos; 889 | }; 890 | name = Debug; 891 | }; 892 | 454D551F19AA5CE6000E3EB3 /* Release */ = { 893 | isa = XCBuildConfiguration; 894 | buildSettings = { 895 | COMBINE_HIDPI_IMAGES = YES; 896 | INFOPLIST_FILE = ShapeAnimation_UnitTests/Info.plist; 897 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks @loader_path/.."; 898 | PRODUCT_BUNDLE_IDENTIFIER = "github.rhcad.$(PRODUCT_NAME:rfc1034identifier)"; 899 | PRODUCT_NAME = "$(TARGET_NAME)"; 900 | SDKROOT = iphoneos; 901 | }; 902 | name = Release; 903 | }; 904 | 45DA4AD419AA62A800133F32 /* Debug */ = { 905 | isa = XCBuildConfiguration; 906 | buildSettings = { 907 | CLANG_ENABLE_MODULES = YES; 908 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 909 | DEFINES_MODULE = YES; 910 | DYLIB_COMPATIBILITY_VERSION = 1; 911 | DYLIB_CURRENT_VERSION = 1; 912 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 913 | GCC_PREPROCESSOR_DEFINITIONS = ( 914 | "DEBUG=1", 915 | "$(inherited)", 916 | ); 917 | INFOPLIST_FILE = ShapeAnimation/Info.plist; 918 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 919 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 920 | OTHER_LDFLAGS = "-ObjC"; 921 | PRODUCT_BUNDLE_IDENTIFIER = "github.rhcad.$(PRODUCT_NAME:rfc1034identifier)"; 922 | PRODUCT_NAME = ShapeAnimation; 923 | SDKROOT = iphoneos; 924 | SKIP_INSTALL = YES; 925 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 926 | TARGETED_DEVICE_FAMILY = "1,2"; 927 | }; 928 | name = Debug; 929 | }; 930 | 45DA4AD519AA62A800133F32 /* Release */ = { 931 | isa = XCBuildConfiguration; 932 | buildSettings = { 933 | CLANG_ENABLE_MODULES = YES; 934 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 935 | DEFINES_MODULE = YES; 936 | DYLIB_COMPATIBILITY_VERSION = 1; 937 | DYLIB_CURRENT_VERSION = 1; 938 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 939 | INFOPLIST_FILE = ShapeAnimation/Info.plist; 940 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 941 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 942 | OTHER_LDFLAGS = "-ObjC"; 943 | PRODUCT_BUNDLE_IDENTIFIER = "github.rhcad.$(PRODUCT_NAME:rfc1034identifier)"; 944 | PRODUCT_NAME = ShapeAnimation; 945 | SDKROOT = iphoneos; 946 | SKIP_INSTALL = YES; 947 | TARGETED_DEVICE_FAMILY = "1,2"; 948 | VALIDATE_PRODUCT = YES; 949 | }; 950 | name = Release; 951 | }; 952 | /* End XCBuildConfiguration section */ 953 | 954 | /* Begin XCConfigurationList section */ 955 | 0267D0E11AC00FF400CEF476 /* Build configuration list for PBXNativeTarget "ShapeAnimation_OSX" */ = { 956 | isa = XCConfigurationList; 957 | buildConfigurations = ( 958 | 0267D0E21AC00FF400CEF476 /* Debug */, 959 | 0267D0E31AC00FF400CEF476 /* Release */, 960 | ); 961 | defaultConfigurationIsVisible = 0; 962 | defaultConfigurationName = Release; 963 | }; 964 | 02CD3F221A70907600C83B5C /* Build configuration list for PBXNativeTarget "ShapeAnimation_UITest" */ = { 965 | isa = XCConfigurationList; 966 | buildConfigurations = ( 967 | 02CD3F231A70907600C83B5C /* Debug */, 968 | 02CD3F241A70907600C83B5C /* Release */, 969 | ); 970 | defaultConfigurationIsVisible = 0; 971 | defaultConfigurationName = Release; 972 | }; 973 | 454D550119AA5CE6000E3EB3 /* Build configuration list for PBXProject "ShapeAnimation" */ = { 974 | isa = XCConfigurationList; 975 | buildConfigurations = ( 976 | 454D551819AA5CE6000E3EB3 /* Debug */, 977 | 454D551919AA5CE6000E3EB3 /* Release */, 978 | ); 979 | defaultConfigurationIsVisible = 0; 980 | defaultConfigurationName = Release; 981 | }; 982 | 454D551D19AA5CE6000E3EB3 /* Build configuration list for PBXNativeTarget "ShapeAnimationTests" */ = { 983 | isa = XCConfigurationList; 984 | buildConfigurations = ( 985 | 454D551E19AA5CE6000E3EB3 /* Debug */, 986 | 454D551F19AA5CE6000E3EB3 /* Release */, 987 | ); 988 | defaultConfigurationIsVisible = 0; 989 | defaultConfigurationName = Release; 990 | }; 991 | 45DA4AD819AA62A800133F32 /* Build configuration list for PBXNativeTarget "ShapeAnimation" */ = { 992 | isa = XCConfigurationList; 993 | buildConfigurations = ( 994 | 45DA4AD419AA62A800133F32 /* Debug */, 995 | 45DA4AD519AA62A800133F32 /* Release */, 996 | ); 997 | defaultConfigurationIsVisible = 0; 998 | defaultConfigurationName = Release; 999 | }; 1000 | /* End XCConfigurationList section */ 1001 | }; 1002 | rootObject = 454D54FE19AA5CE6000E3EB3 /* Project object */; 1003 | } 1004 | -------------------------------------------------------------------------------- /ShapeAnimation.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ShapeAnimation.xcodeproj/project.xcworkspace/xcshareddata/ShapeAnimation.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "40FDDDEC61536E12C2875DD3A3B52ABF310E0333", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "729A64D6D525F9784C8EE5D237BF81C9AEA202EE" : 0, 8 | "40FDDDEC61536E12C2875DD3A3B52ABF310E0333" : 0, 9 | "575D880AECD46D5450DCE1653E0F6C244B39D702" : 0 10 | }, 11 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "969E9941-83DA-489C-9A95-C447EC8F37B1", 12 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 13 | "729A64D6D525F9784C8EE5D237BF81C9AEA202EE" : "ShapeAnimation-Swift\/Vendor\/SwiftGraphics\/Externals\/SwiftUtilities\/", 14 | "40FDDDEC61536E12C2875DD3A3B52ABF310E0333" : "ShapeAnimation-Swift\/", 15 | "575D880AECD46D5450DCE1653E0F6C244B39D702" : "ShapeAnimation-Swift\/Vendor\/SwiftGraphics\/" 16 | }, 17 | "DVTSourceControlWorkspaceBlueprintNameKey" : "ShapeAnimation", 18 | "DVTSourceControlWorkspaceBlueprintVersion" : 203, 19 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "ShapeAnimation.xcodeproj", 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 21 | { 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/rhcad\/ShapeAnimation-Swift.git", 23 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 24 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "40FDDDEC61536E12C2875DD3A3B52ABF310E0333" 25 | }, 26 | { 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/rhcad\/SwiftGraphics.git", 28 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 29 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "575D880AECD46D5450DCE1653E0F6C244B39D702" 30 | }, 31 | { 32 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/schwa\/SwiftUtilities.git", 33 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 34 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "729A64D6D525F9784C8EE5D237BF81C9AEA202EE" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /ShapeAnimation.xcodeproj/xcshareddata/xcschemes/ShapeAnimation.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /ShapeAnimation.xcodeproj/xcshareddata/xcschemes/ShapeAnimation_OSX.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 69 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /ShapeAnimation.xcodeproj/xcshareddata/xcschemes/ShapeAnimation_UITest.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /ShapeAnimation/Animation/AnimationLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationLayer.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/29. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | #if os(OSX) 12 | public typealias DisplayLink = CDisplayLink 13 | #else 14 | public typealias DisplayLink = CADisplayLink 15 | #endif 16 | 17 | public class AnimationLayer : CALayer { 18 | 19 | public var properties:[(key:String, min:CGFloat)]! { 20 | didSet { keys = properties.map { $0.key } } 21 | willSet { 22 | if properties == nil { 23 | for (key, min) in newValue { 24 | setValue(min, forKey:key) 25 | } 26 | } 27 | } 28 | } 29 | public var draw:((AnimationLayer, CGContext) -> Void)? 30 | public var animationCreated:((String, CABasicAnimation) -> Void)? 31 | public var didStart:(() -> Void)? 32 | public var didStop :(() -> Void)? 33 | 34 | private var keys:[String]! = nil 35 | public var timer:DisplayLink? 36 | private var animations:[CAAnimation] = [] 37 | 38 | public func getProperty(key:String) -> CGFloat { 39 | if let layer = presentationLayer() as? CALayer { 40 | if let value = layer.valueForKey(key) as? NSNumber { 41 | return CGFloat(value.floatValue) 42 | } 43 | } 44 | return minValue(key) 45 | } 46 | 47 | public func setProperty(value: AnyObject?, key: String) { 48 | setValue(value, forKey:key) 49 | } 50 | 51 | // MARK: Implementation 52 | 53 | private func minValue(key:String) -> CGFloat { 54 | for (k, min) in properties { 55 | if key == k { 56 | return min 57 | } 58 | } 59 | return 0 60 | } 61 | 62 | override public init() { 63 | super.init() 64 | } 65 | 66 | override public init(layer: AnyObject) { 67 | super.init(layer: layer) 68 | if let layer = layer as? AnimationLayer { 69 | self.properties = layer.properties 70 | self.animationCreated = layer.animationCreated 71 | self.draw = layer.draw 72 | } 73 | } 74 | 75 | required public init?(coder aDecoder: NSCoder) { 76 | fatalError("init(coder:) has not been implemented") 77 | } 78 | 79 | override public func animationDidStart(anim:CAAnimation) { 80 | if let animation = anim as? CAPropertyAnimation { 81 | if keys != nil && keys.contains(animation.keyPath!) { 82 | animations.append(animation) 83 | if timer == nil { 84 | didStart?() 85 | #if os(iOS) 86 | timer = CADisplayLink(target:self, selector:Selector("animationLoop")) 87 | timer!.addToRunLoop(NSRunLoop.mainRunLoop(), forMode:NSDefaultRunLoopMode) 88 | #else 89 | timer = CDisplayLink() 90 | timer!.displayLinkBlock = { _ in 91 | if (!self.paused) { 92 | self.setNeedsDisplay() 93 | } 94 | } 95 | timer!.start() 96 | #endif 97 | } 98 | } 99 | } 100 | } 101 | 102 | override public func animationDidStop(anim:CAAnimation, finished:Bool) { 103 | if let index = animations.indexOf(anim) { 104 | animations.removeAtIndex(index) 105 | if animations.isEmpty { 106 | #if os(iOS) 107 | timer!.invalidate() 108 | #else 109 | timer!.stop() 110 | #endif 111 | timer = nil 112 | didStop?() 113 | } 114 | } 115 | } 116 | 117 | // Called when layer's property changes. 118 | override public func actionForKey(event: String) -> CAAction? { 119 | if keys != nil && keys.contains(event) { 120 | let animation = CABasicAnimation(keyPath:event) 121 | animation.fromValue = getProperty(event) 122 | animation.delegate = self 123 | animation.duration = 1.0 124 | animationCreated?(event, animation) 125 | addAnimation(animation, forKey:event) 126 | return animation 127 | } 128 | return super.actionForKey(event) 129 | } 130 | 131 | // Timer Callback 132 | internal func animationLoop() { 133 | setNeedsDisplay() 134 | } 135 | 136 | // Layer Drawing 137 | override public func drawInContext(ctx: CGContext) { 138 | super.drawInContext(ctx) 139 | CGContextSetAllowsAntialiasing(ctx, true) 140 | CGContextSetShouldAntialias(ctx, true) 141 | draw?(self, ctx) 142 | } 143 | 144 | } 145 | 146 | public extension ShapeView { 147 | 148 | public func addAnimationLayer(frame frame:CGRect, properties:[(key:String, min:CGFloat)], 149 | draw:((AnimationLayer, CGContext) -> Void)) -> AnimationLayer 150 | { 151 | let layer = AnimationLayer() 152 | layer.properties = properties 153 | layer.draw = draw 154 | addSublayer(layer, frame:frame) 155 | return layer 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /ShapeAnimation/Animation/AnimationPair.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationPair.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/20. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | // MARK: animationGroup() and applyAnimations() 12 | // animationGroup() for the same layer, and applyAnimations() for multiple layers 13 | 14 | public func animationGroup(animations:[AnimationPair], didStop:(() -> Void)? = nil) -> AnimationPair { 15 | let animation = CAAnimationGroup() 16 | let layer = animations.first!.layer 17 | 18 | animation.animations = animations.map { 19 | assert($0.layer == layer) 20 | return $0.animation 21 | } 22 | for anim in animation.animations! { 23 | animation.duration = max(animation.duration, anim.duration) 24 | } 25 | return AnimationPair(layer, animation, key:"group") 26 | } 27 | 28 | public func applyAnimations(animations:[AnimationPair], completion:(() -> Void)?) { 29 | applyAnimations(animations, timingFunction: CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut), completion: completion) 30 | } 31 | 32 | public func applyAnimations(animations:[AnimationPair], timingFunction:CAMediaTimingFunction, completion:(() -> Void)?) { 33 | CATransaction.begin() 34 | CATransaction.setAnimationTimingFunction(timingFunction) 35 | if completion != nil { 36 | CATransaction.setCompletionBlock { 37 | var finished = true 38 | for la in animations { 39 | finished = la.animation.finished && finished 40 | } 41 | AnimationDelagate.groupDidStop(completion!, finished:finished) 42 | } 43 | } 44 | 45 | var duration:CFTimeInterval = 0 46 | let start = CACurrentMediaTime() 47 | 48 | for la in animations { 49 | la.apply() 50 | duration = max(duration + max(la.animation.beginTime - start, 0), la.animation.duration) 51 | la.animation.finished = true // create delegate 52 | } 53 | CATransaction.setAnimationDuration(duration) 54 | CATransaction.commit() 55 | } 56 | 57 | // MARK: Pair of layer and animation 58 | 59 | public class AnimationPair { 60 | public let layer:CALayer 61 | public let animation:CAAnimation 62 | public let key:String 63 | 64 | init(_ layer:CALayer, _ animation:CAAnimation, key:String) { 65 | self.layer = layer 66 | self.animation = animation 67 | self.key = key 68 | } 69 | 70 | convenience init(_ layer:CALayer, _ animation:CAPropertyAnimation) { 71 | self.init(layer, animation, key:animation.keyPath!) 72 | } 73 | 74 | public func apply() { 75 | if !CAAnimation.isStopping { 76 | if let gradientLayer = layer.gradientLayer { 77 | let anim2 = animation.copy() as! CAAnimation 78 | anim2.delegate = nil 79 | if let layerid = layer.identifier { 80 | anim2.setValue(layerid + "_gradient", forKey:"layerID") 81 | } 82 | if key == "path" { 83 | gradientLayer.mask!.addAnimation(anim2, forKey:key) 84 | } else { 85 | gradientLayer.addAnimation(anim2, forKey:key) 86 | } 87 | } 88 | animation.setValue(layer.identifier, forKey:"layerID") 89 | layer.addAnimation(animation, forKey:key) 90 | } 91 | } 92 | 93 | public func apply(didStop:(() -> Void)) { 94 | animation.didStop = didStop 95 | apply() 96 | } 97 | } 98 | 99 | // MARK: Convenience setters of AnimationPair 100 | 101 | public extension AnimationPair { 102 | 103 | public func set(did:(CAAnimation) -> Void) -> AnimationPair { 104 | did(animation) 105 | return self 106 | } 107 | 108 | public func setStop(didStop:() -> Void) -> AnimationPair { 109 | animation.didStop = didStop 110 | return self 111 | } 112 | 113 | public func set(timingFunction:CAMediaTimingFunction) -> AnimationPair { 114 | animation.timingFunction = timingFunction 115 | return self 116 | } 117 | 118 | public func setFillMode(fillMode:String) -> AnimationPair { 119 | animation.fillMode = fillMode 120 | if fillMode == kCAFillModeForwards { 121 | animation.removedOnCompletion = false 122 | } 123 | return self 124 | } 125 | 126 | public func setDuration(d:CFTimeInterval) -> AnimationPair { 127 | animation.duration = d 128 | if let group = animation as? CAAnimationGroup { 129 | for sub in group.animations! { 130 | let subanim = sub as CAAnimation 131 | subanim.duration = d 132 | } 133 | } 134 | return self 135 | } 136 | 137 | public func setBeginTime(time:CFTimeInterval) -> AnimationPair { 138 | animation.beginTime = CACurrentMediaTime() + time 139 | return self 140 | } 141 | 142 | public func setBeginTime(index:Int, gap:CFTimeInterval) -> AnimationPair { 143 | animation.beginTime = CACurrentMediaTime() + Double(index) * gap 144 | return self 145 | } 146 | 147 | public func setBeginTime(index:Int, gap:CFTimeInterval, duration:CFTimeInterval) -> AnimationPair { 148 | setDuration(duration) 149 | return setBeginTime(index, gap:gap) 150 | } 151 | 152 | public func setRepeatCount(count:Float) -> AnimationPair { 153 | animation.repeatCount = count 154 | return self 155 | } 156 | 157 | public func forever() -> AnimationPair { 158 | animation.repeatCount = HUGE 159 | return self 160 | } 161 | 162 | public func autoreverses() -> AnimationPair { 163 | animation.autoreverses = true 164 | return self 165 | } 166 | 167 | public func apply(duration d:CFTimeInterval) { 168 | setDuration(d) 169 | apply() 170 | } 171 | 172 | public func apply(duration d:CFTimeInterval, didStop:(() -> Void)) { 173 | setDuration(d) 174 | animation.didStop = didStop 175 | apply() 176 | } 177 | } 178 | 179 | public extension AnimationPair { 180 | public func setTimingFunction(function:CAMediaTimingFunction) -> AnimationPair { 181 | animation.timingFunction = function 182 | return self 183 | } 184 | 185 | public func setTimingFunction(c1x:Float, _ c1y:Float, _ c2x:Float, _ c2y:Float) -> AnimationPair { 186 | animation.timingFunction = CAMediaTimingFunction(controlPoints:c1x, c1y, c2x, c2y) 187 | return self 188 | } 189 | } 190 | 191 | // MARK: setAnchorPoint just change anchorPoint, and not change position 192 | 193 | public extension CALayer { 194 | public func setAnchorPoint(point:CGPoint, fromLayer:CALayer? = nil) { 195 | let oldframe = frame 196 | if let fromLayer = fromLayer { 197 | let pt = convertPoint(point, fromLayer:fromLayer) / bounds.size 198 | anchorPoint = pt 199 | } else { 200 | anchorPoint = point 201 | } 202 | frame = oldframe 203 | } 204 | } 205 | 206 | public extension AnimationPair { 207 | public func setAnchorPoint(point:CGPoint, fromLayer:CALayer? = nil) -> AnimationPair { 208 | layer.setAnchorPoint(point, fromLayer:fromLayer) 209 | return self 210 | } 211 | } 212 | 213 | // MARK: withDisableActions 214 | 215 | public func withDisableActions(block:() -> Void) { 216 | let old = CATransaction.disableActions() 217 | CATransaction.setDisableActions(true) 218 | block() 219 | CATransaction.setDisableActions(old) 220 | } 221 | 222 | public func withDisableActions(layer:CALayer, animation:CAAnimation, key:String, block:(CALayer) -> Void) { 223 | let forwards = animation.fillMode == kCAFillModeForwards || animation.fillMode == kCAFillModeBoth 224 | if !animation.autoreverses && forwards { 225 | let old = CATransaction.disableActions() 226 | CATransaction.setDisableActions(true) 227 | block(layer) 228 | if let gradientLayer = layer.gradientLayer { 229 | block(gradientLayer) 230 | } 231 | CATransaction.setDisableActions(old) 232 | } 233 | layer.removeAnimationForKey(key) 234 | if let gradientLayer = layer.gradientLayer { 235 | gradientLayer.removeAnimationForKey(key) 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /ShapeAnimation/Animation/AnimationPrivate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationPrivate.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/20. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | private var stopping = 0 12 | 13 | internal class AnimationDelagate : NSObject { 14 | 15 | internal var didStart:((CAAnimation!) -> Void)? 16 | internal var didStop :(() -> Void)? 17 | internal var willStop :(() -> Void)? 18 | internal var finished = true 19 | 20 | override internal func animationDidStart(anim:CAAnimation) { 21 | didStart?(anim) 22 | didStart = nil 23 | } 24 | 25 | override internal func animationDidStop(anim:CAAnimation, finished:Bool) { 26 | self.finished = finished 27 | if finished { 28 | willStop?() 29 | didStop?() 30 | } else { 31 | stopping++ 32 | willStop?() 33 | didStop?() 34 | stopping-- 35 | } 36 | willStop = nil 37 | didStop = nil 38 | } 39 | 40 | internal class func groupDidStop(completion:() -> Void, finished:Bool) { 41 | if finished { 42 | completion() 43 | } else { 44 | stopping++ 45 | completion() 46 | stopping-- 47 | } 48 | } 49 | } 50 | 51 | public extension CAAnimation { 52 | 53 | public var didStart:((CAAnimation!) -> Void)? { 54 | get { 55 | let delegate = self.delegate as? AnimationDelagate 56 | return delegate?.didStart 57 | } 58 | set { 59 | if let delegate = self.delegate as? AnimationDelagate { 60 | delegate.didStart = newValue 61 | } 62 | else if newValue != nil { 63 | let delegate = AnimationDelagate() 64 | delegate.didStart = newValue 65 | self.delegate = delegate 66 | } 67 | } 68 | } 69 | 70 | public var didStop:(() -> Void)? { 71 | get { 72 | let delegate = self.delegate as? AnimationDelagate 73 | return delegate?.didStop 74 | } 75 | set { 76 | if let delegate = self.delegate as? AnimationDelagate { 77 | delegate.didStop = newValue 78 | } 79 | else if newValue != nil { 80 | let delegate = AnimationDelagate() 81 | delegate.didStop = newValue 82 | self.delegate = delegate 83 | } 84 | } 85 | } 86 | 87 | public var willStop:(() -> Void)? { 88 | get { 89 | let delegate = self.delegate as? AnimationDelagate 90 | return delegate?.willStop 91 | } 92 | set { 93 | if let delegate = self.delegate as? AnimationDelagate { 94 | delegate.willStop = newValue 95 | } 96 | else if newValue != nil { 97 | let delegate = AnimationDelagate() 98 | delegate.willStop = newValue 99 | self.delegate = delegate 100 | } 101 | } 102 | } 103 | 104 | internal var finished:Bool { 105 | get { 106 | if let delegate = self.delegate as? AnimationDelagate { 107 | return delegate.finished 108 | } 109 | return true 110 | } 111 | set { 112 | if let delegate = self.delegate as? AnimationDelagate { 113 | delegate.finished = newValue 114 | } 115 | else { 116 | let delegate = AnimationDelagate() 117 | delegate.finished = finished 118 | self.delegate = delegate 119 | } 120 | } 121 | } 122 | 123 | internal class var isStopping:Bool { return stopping > 0 } 124 | } 125 | -------------------------------------------------------------------------------- /ShapeAnimation/Animation/CALayer+Animation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+Animation.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/20. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | public extension CALayer { 12 | typealias Radians = CGFloat 13 | 14 | // MARK: opacityAnimation and flashAnimation 15 | 16 | func opacityAnimation(from from:CGFloat?, to:CGFloat, didStop:(() -> Void)? = nil) -> AnimationPair { 17 | let animation = CABasicAnimation(keyPath:"opacity") 18 | animation.fromValue = from 19 | animation.toValue = to 20 | setDefaultProperties(animation, 0, didStop) 21 | animation.willStop = { 22 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 23 | layer.opacity = Float(to) 24 | } 25 | } 26 | return AnimationPair(self, animation) 27 | } 28 | 29 | func flashAnimation(repeatCount n:Float = 2, didStop:(() -> Void)? = nil) -> AnimationPair { 30 | let apair = opacityAnimation(from:0, to:1, didStop:didStop) 31 | return apair.autoreverses().setRepeatCount(n).setDuration(0.2) 32 | } 33 | 34 | func backColorAnimation(from:CGColor? = nil, to:CGColor, didStop:(() -> Void)? = nil) -> AnimationPair { 35 | let animation = CABasicAnimation(keyPath:"backgroundColor") 36 | animation.fromValue = from 37 | animation.toValue = to 38 | setDefaultProperties(animation, backgroundColor!, didStop) 39 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 40 | animation.willStop = { 41 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 42 | layer.backgroundColor = to 43 | } 44 | } 45 | return AnimationPair(self, animation) 46 | } 47 | 48 | // MARK: scaleAnimation and tapAnimation 49 | 50 | func scaleAnimation(from from:CGFloat?, to:CGFloat, didStop:(() -> Void)? = nil) -> AnimationPair { 51 | let animation = CABasicAnimation(keyPath:"transform.scale") 52 | let oldxf = affineTransform(), oldscale = hypot(oldxf.a, oldxf.b) 53 | 54 | if let from = from { 55 | animation.fromValue = from * oldscale 56 | } 57 | animation.toValue = to * oldscale 58 | setDefaultProperties(animation, 1, didStop) 59 | animation.willStop = { 60 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 61 | let xf = CGAffineTransform(scale:to) 62 | layer.setAffineTransform(layer.affineTransform() + xf) 63 | } 64 | } 65 | return AnimationPair(self, animation) 66 | } 67 | 68 | func scaleAnimation(from from:CGFloat?, to:CGFloat, repeatCount n:Float, didStop:(() -> Void)? = nil) -> AnimationPair { 69 | return scaleAnimation(from:from, to:to, didStop:didStop) 70 | .setRepeatCount(n).setFillMode(kCAFillModeRemoved).set { $0.autoreverses = n > 1 } 71 | } 72 | 73 | func tapAnimation(didStop:(() -> Void)? = nil) -> AnimationPair { 74 | let w = max(bounds.size.width, bounds.size.height) 75 | return scaleAnimation(from:1, to:(w + 10) / w, didStop:didStop).autoreverses().setDuration(0.2) 76 | } 77 | 78 | // MARK: rotate360Degrees and rotationAnimation 79 | 80 | func rotate360Degrees(didStop:(() -> Void)? = nil) -> AnimationPair { 81 | return rotationAnimation(CGFloat(2 * M_PI), didStop:didStop) 82 | } 83 | 84 | func rotationAnimation(angle:Radians, didStop:(() -> Void)? = nil) -> AnimationPair { 85 | let animation = CABasicAnimation(keyPath:"transform.rotation") 86 | animation.additive = true 87 | animation.fromValue = 0.0 88 | animation.toValue = angle 89 | setDefaultProperties(animation, 0, didStop) 90 | animation.willStop = { 91 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 92 | let xf = CGAffineTransform(rotation:angle) 93 | layer.setAffineTransform(layer.affineTransform() + xf) 94 | } 95 | } 96 | return AnimationPair(self, animation) 97 | } 98 | 99 | // MARK: shakeAnimation, moveAnimation and moveOnPathAnimation 100 | 101 | func shakeAnimation(didStop:(() -> Void)? = nil) -> AnimationPair { 102 | let animation = CAKeyframeAnimation() 103 | animation.keyPath = "position.x" 104 | animation.values = [0, 10, -10, 10, 0] 105 | animation.duration = 0.8 106 | animation.additive = true 107 | animation.didStop = didStop 108 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 109 | return AnimationPair(self, animation, key:"shake") 110 | } 111 | 112 | func moveAnimation(from:CGPoint? = nil, to:CGPoint, relative:Bool = true, didStop:(() -> Void)? = nil) -> AnimationPair { 113 | let animation = CABasicAnimation(keyPath:"position") 114 | animation.additive = relative 115 | if let from = from { 116 | animation.fromValue = NSValue(CGPoint: from) 117 | } 118 | animation.toValue = NSValue(CGPoint: to) 119 | setDefaultProperties(animation, NSNull(), didStop) 120 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseOut) 121 | animation.willStop = { 122 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 123 | layer.position = relative ? layer.position + to : to 124 | } 125 | } 126 | return AnimationPair(self, animation) 127 | } 128 | 129 | func moveOnPathAnimation(path:CGPath, autoRotate:Bool = false, didStop:(() -> Void)? = nil) -> AnimationPair { 130 | let animation = CAKeyframeAnimation() 131 | animation.keyPath = "position" 132 | animation.path = path 133 | setDefaultProperties(animation, NSNull(), didStop) 134 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 135 | if autoRotate { 136 | animation.rotationMode = kCAAnimationRotateAuto 137 | } 138 | animation.willStop = { 139 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 140 | layer.position = path.endPoint 141 | if autoRotate { 142 | let xf = CGAffineTransform(rotation:path.endTangent.direction) 143 | layer.setAffineTransform(layer.affineTransform() + xf) 144 | } 145 | } 146 | } 147 | return AnimationPair(self, animation) 148 | } 149 | 150 | internal func setDefaultProperties(animation:CAAnimation, _ defaultFromValue:AnyObject, _ didStop:(() -> Void)?) { 151 | animation.duration = 0.8 152 | animation.removedOnCompletion = false 153 | animation.fillMode = kCAFillModeForwards 154 | animation.didStop = didStop 155 | if let basic = animation as? CABasicAnimation { 156 | if basic.fromValue == nil { 157 | if let presentation = presentationLayer() as? CALayer { 158 | basic.fromValue = presentation.valueForKeyPath(basic.keyPath!) 159 | } else { 160 | basic.fromValue = defaultFromValue 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | #if os(OSX) 168 | internal extension NSValue { 169 | convenience init(CGPoint point: CGPoint) { 170 | self.init(point:point) 171 | } 172 | var CGPointValue:CGPoint { 173 | return self.pointValue 174 | } 175 | } 176 | #endif 177 | -------------------------------------------------------------------------------- /ShapeAnimation/Animation/CALayer+Drag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+Drag.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/2/6. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | import SwiftUtilities 11 | 12 | private var LayerTapKey = 14 13 | private var LayerSelectedKey = 15 14 | 15 | public extension CALayer { 16 | func constrainCenterToSuperview(center:CGPoint) { 17 | let kEdgeBuffer:CGFloat = 4 18 | var constrain = superlayer!.bounds.insetted(dx:kEdgeBuffer, dy:kEdgeBuffer) 19 | constrain.inset(dx: frame.width / 2, dy: frame.height / 2) 20 | let pt = constrain.isEmpty ? superlayer!.bounds.mid : center.clampedTo(constrain) 21 | moveAnimation(to: pt, relative:false).apply() 22 | } 23 | 24 | func bringOnScreen() { 25 | if !superlayer!.bounds.contains(frame) { 26 | constrainCenterToSuperview(position) 27 | } 28 | } 29 | 30 | public var didTap: (() -> Void)? { 31 | get { 32 | let defv:(() -> Void)? = nil 33 | return getAssociatedWrappedObject(self, key: &LayerTapKey, defaultValue: defv) 34 | } 35 | set { 36 | setAssociatedWrappedObject(self, key: &LayerTapKey, value: newValue) 37 | } 38 | } 39 | 40 | public var selected: Bool { 41 | get { 42 | let defv:NSObject? = nil 43 | return getAssociatedWrappedObject(self, key: &LayerSelectedKey, defaultValue: defv) != nil 44 | } 45 | set { 46 | if (newValue != selected) { 47 | weak var value = self 48 | setAssociatedWrappedObject(self, key: &LayerSelectedKey, value: newValue ? value : nil) 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ShapeAnimation/Animation/CALayer+Pause.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+Pause.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/20. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | public extension CALayer { 12 | 13 | public var paused:Bool { 14 | get { 15 | return speed == 0.0 16 | } 17 | set { 18 | if newValue && !paused { 19 | let pausedTime = convertTime(CACurrentMediaTime(), fromLayer:nil) 20 | speed = 0.0 21 | timeOffset = pausedTime 22 | } 23 | else if !newValue && paused { 24 | let pausedTime = timeOffset 25 | speed = 1.0 26 | timeOffset = 0.0 27 | beginTime = 0.0 28 | beginTime = convertTime(CACurrentMediaTime(), fromLayer:nil) - pausedTime 29 | } 30 | gradientLayer?.paused = newValue 31 | #if os(iOS) 32 | if let layer = self as? AnimationLayer { 33 | layer.timer?.paused = newValue 34 | } 35 | #endif 36 | } 37 | } 38 | 39 | } 40 | 41 | public extension ShapeView { 42 | 43 | public var paused:Bool { 44 | get { 45 | var ret = false 46 | enumerateLayers { layer in 47 | ret = ret || layer.paused 48 | } 49 | return ret 50 | } 51 | set { 52 | enumerateLayers { layer in 53 | layer.paused = newValue 54 | } 55 | } 56 | } 57 | 58 | public func stop() { 59 | enumerateLayers { layer in 60 | layer.removeAllAnimations() 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /ShapeAnimation/Animation/CALayer+Slide.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+Slide.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 2/10/15. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | public extension CALayer { 12 | 13 | // MARK: Slide animations 14 | 15 | func slideToRight(didStop:(() -> Void)? = nil) -> AnimationPair { 16 | return slideAnimation(kCATransitionFromLeft, didStop:didStop) 17 | } 18 | 19 | func slideAnimation(subtype:String, didStop:(() -> Void)? = nil) -> AnimationPair { 20 | let slide = CATransition() 21 | 22 | slide.type = kCATransitionPush 23 | slide.subtype = subtype 24 | slide.duration = 0.8 25 | slide.didStop = didStop 26 | slide.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 27 | return AnimationPair(self, slide, key:"slide") 28 | } 29 | 30 | // MARK: Flip animations 31 | 32 | func flipHorizontally(didStop:(() -> Void)? = nil) -> AnimationPair { 33 | return flipAnimation(x:0, y:1, didStop:didStop) 34 | } 35 | 36 | func flipVertically(didStop:(() -> Void)? = nil) -> AnimationPair { 37 | return flipAnimation(x:1, y:0, didStop:didStop) 38 | } 39 | 40 | private func flipAnimation(x x:CGFloat, y:CGFloat, didStop:(() -> Void)? = nil) -> AnimationPair { 41 | var xf = CATransform3DIdentity 42 | xf.m34 = 1.0 / -500 43 | xf = CATransform3DRotate(xf, CGFloat(M_PI), x, y, 0.0) 44 | 45 | let animation = CABasicAnimation(keyPath:"transform") 46 | animation.additive = true 47 | animation.fromValue = NSValue(CATransform3D:CATransform3DIdentity) 48 | animation.toValue = NSValue(CATransform3D:xf) 49 | setDefaultProperties(animation, NSNull(), didStop) 50 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 51 | animation.willStop = { 52 | withDisableActions(self, animation: animation, key: "flip") { layer in 53 | layer.transform = CATransform3DConcat(xf, layer.transform) 54 | } 55 | } 56 | return AnimationPair(self, animation, key:"flip") 57 | } 58 | 59 | // MARK: transform animation 60 | 61 | func transformAnimation(from:CATransform3D? = nil, to:CATransform3D, didStop:(() -> Void)? = nil) -> AnimationPair { 62 | let animation = CABasicAnimation(keyPath:"transform") 63 | if let from = from { 64 | animation.fromValue = NSValue(CATransform3D:from) 65 | } 66 | animation.toValue = NSValue(CATransform3D:to) 67 | setDefaultProperties(animation, NSValue(CATransform3D:CATransform3DIdentity), didStop) 68 | animation.willStop = { 69 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 70 | layer.transform = to 71 | } 72 | } 73 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 74 | return AnimationPair(self, animation) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ShapeAnimation/Animation/CAShapeLayer+Animation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CAShapeLayer+Animation.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/20. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | public extension CAShapeLayer { 12 | 13 | func strokeStartAnimation(from:CGFloat? = nil, to:CGFloat = 1, didStop:(() -> Void)? = nil) -> AnimationPair { 14 | let animation = CABasicAnimation(keyPath:"strokeStart") 15 | animation.duration = 0.8 16 | animation.fromValue = from 17 | animation.toValue = to 18 | setDefaultProperties(animation, 0, didStop) 19 | if from != nil { 20 | animation.fillMode = kCAFillModeRemoved 21 | } 22 | animation.willStop = { 23 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 24 | if let layer = layer as? CAShapeLayer { 25 | layer.strokeStart = to 26 | } 27 | } 28 | } 29 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 30 | animation.didStop = didStop 31 | return AnimationPair(self, animation) 32 | } 33 | 34 | func strokeEndAnimation(from:CGFloat? = nil, to:CGFloat = 1, didStop:(() -> Void)? = nil) -> AnimationPair { 35 | let animation = CABasicAnimation(keyPath:"strokeEnd") 36 | animation.duration = 0.8 37 | animation.fromValue = from 38 | animation.toValue = to 39 | setDefaultProperties(animation, 0, didStop) 40 | if from != nil { 41 | animation.fillMode = kCAFillModeRemoved 42 | } 43 | animation.willStop = { 44 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 45 | if let layer = layer as? CAShapeLayer { 46 | layer.strokeEnd = to 47 | } 48 | } 49 | } 50 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 51 | animation.didStop = didStop 52 | return AnimationPair(self, animation) 53 | } 54 | 55 | func strokeColorAnimation(from:CGColor? = nil, to:CGColor, didStop:(() -> Void)? = nil) -> AnimationPair { 56 | let animation = CABasicAnimation(keyPath:"strokeColor") 57 | animation.fromValue = from 58 | animation.toValue = to 59 | setDefaultProperties(animation, strokeColor!, didStop) 60 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 61 | animation.willStop = { 62 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 63 | if let layer = layer as? CAShapeLayer { 64 | layer.strokeColor = to 65 | } 66 | } 67 | } 68 | return AnimationPair(self, animation) 69 | } 70 | 71 | func fillColorAnimation(from:CGColor? = nil, to:CGColor, didStop:(() -> Void)? = nil) -> AnimationPair { 72 | let animation = CABasicAnimation(keyPath:"fillColor") 73 | animation.fromValue = from 74 | animation.toValue = to 75 | setDefaultProperties(animation, fillColor!, didStop) 76 | animation.willStop = { 77 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 78 | if let layer = layer as? CAShapeLayer { 79 | layer.fillColor = to 80 | } 81 | } 82 | } 83 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 84 | return AnimationPair(self, animation) 85 | } 86 | 87 | func lineWidthAnimation(from:CGFloat? = nil, to:CGFloat, didStop:(() -> Void)? = nil) -> AnimationPair { 88 | let animation = CABasicAnimation(keyPath:"lineWidth") 89 | animation.fromValue = from 90 | animation.toValue = to 91 | setDefaultProperties(animation, lineWidth, didStop) 92 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 93 | animation.willStop = { 94 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 95 | if let layer = layer as? CAShapeLayer { 96 | layer.lineWidth = to 97 | } 98 | } 99 | } 100 | return AnimationPair(self, animation) 101 | } 102 | 103 | func dashPhaseAnimation(from:CGFloat? = nil, to:CGFloat) -> AnimationPair { 104 | let animation = CABasicAnimation(keyPath:"lineDashPhase") 105 | animation.fromValue = from 106 | animation.toValue = to 107 | setDefaultProperties(animation, lineDashPhase, nil) 108 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionLinear) 109 | return AnimationPair(self, animation) 110 | } 111 | 112 | func switchPathAnimation(to:CGPath, didStop:(() -> Void)? = nil) -> AnimationPair { 113 | let animation = CABasicAnimation(keyPath:"path") 114 | animation.toValue = to 115 | setDefaultProperties(animation, path!, didStop) 116 | animation.timingFunction = CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut) 117 | animation.willStop = { 118 | withDisableActions(self, animation: animation, key: animation.keyPath!) { layer in 119 | if let layer = layer as? CAShapeLayer { 120 | let box = to.boundingBox, frame = self.frame 121 | layer.path = to 122 | layer.frame = CGRect(x:frame.origin.x, y:frame.origin.y, w:box.maxX, h:box.maxY) 123 | layer.apply(layer.gradient) 124 | } 125 | } 126 | } 127 | return AnimationPair(self, animation) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /ShapeAnimation/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ShapeAnimation/Portability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Portability.swift 3 | // SwiftAnimShape 4 | // 5 | // Created by Zhang Yungui on 15/3/19. 6 | // Copyright (c) 2015 github.com/touchvg. All rights reserved. 7 | // 8 | 9 | #if os(OSX) 10 | import Cocoa 11 | public typealias ViewController = NSViewController 12 | public typealias View = NSView 13 | public typealias Image = NSImage 14 | public typealias Font = NSFont 15 | public typealias GestureRecognizer = NSGestureRecognizer 16 | public typealias TapRecognizer = NSClickGestureRecognizer 17 | public typealias PanRecognizer = NSPanGestureRecognizer 18 | public typealias Button = NSButton 19 | #else 20 | import UIKit 21 | public typealias ViewController = UIViewController 22 | public typealias View = UIView 23 | public typealias Image = UIImage 24 | public typealias Font = UIFont 25 | public typealias GestureRecognizer = UIGestureRecognizer 26 | public typealias TapRecognizer = UITapGestureRecognizer 27 | public typealias PanRecognizer = UIPanGestureRecognizer 28 | public typealias Button = UIButton 29 | #endif 30 | -------------------------------------------------------------------------------- /ShapeAnimation/ShapeAnimation.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShapeAnimation.h 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/20. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ShapeAnimation. 12 | FOUNDATION_EXPORT double ShapeAnimationVersionNumber; 13 | 14 | //! Project version string for ShapeAnimation. 15 | FOUNDATION_EXPORT const unsigned char ShapeAnimationVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework to import ObjC code into Swift. 18 | 19 | -------------------------------------------------------------------------------- /ShapeAnimation/View/CALayer+ID.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CALayer+ID.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/2/4. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | import SwiftUtilities 11 | 12 | private var LayerIDKey = 13 13 | 14 | public extension CALayer { 15 | 16 | public var identifier: String? { 17 | get { 18 | return getAssociatedObject(self, key: &LayerIDKey) as? String 19 | } 20 | set { 21 | if newValue != identifier { 22 | setAssociatedObject(self, key: &LayerIDKey, value: newValue!) 23 | } 24 | } 25 | } 26 | 27 | public func layerWithIdentifier(identifier:String) -> CALayer? { 28 | var ret:CALayer? 29 | enumerateLayers { layer in 30 | if let lid = layer.identifier { 31 | if lid == identifier { 32 | ret = layer 33 | } 34 | } 35 | } 36 | return ret 37 | } 38 | 39 | public func layerWithIdentifier(identifier:String, layer:CALayer) -> CALayer? { 40 | return layer.layerWithIdentifier(identifier) 41 | } 42 | } 43 | 44 | public extension ShapeView { 45 | public func layerWithIdentifier(identifier:String) -> CALayer? { 46 | return sublayer.layerWithIdentifier(identifier) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ShapeAnimation/View/CAShapeLayer+Gradient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CAShapeLayer+Gradient.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/29. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | import SwiftUtilities 11 | 12 | private var GradientLayerKey = 12 13 | 14 | public extension CAShapeLayer { 15 | public var gradient: Gradient? { 16 | get { 17 | if let layer = gradientLayer { 18 | if path!.isClosed { 19 | var style = Gradient() 20 | style.colors = layer.colors as! [CGColor]! 21 | if let locations = layer.locations { 22 | style.locations = locations.map {CGFloat($0 as NSNumber)} 23 | } 24 | style.orientation = (layer.startPoint, layer.endPoint) 25 | style.axial = layer.type == kCAGradientLayerAxial 26 | return style 27 | } 28 | } 29 | return nil 30 | } 31 | set { 32 | apply(newValue) 33 | } 34 | } 35 | 36 | public func apply(newStyle:Gradient?) { 37 | if newStyle?.colors != nil && path!.isClosed { 38 | let style = newStyle! 39 | let gradientLayer = CAGradientLayer() 40 | let maskLayer = CAShapeLayer() 41 | 42 | maskLayer.frame = bounds 43 | maskLayer.path = path 44 | maskLayer.strokeColor = nil 45 | 46 | gradientLayer.colors = style.colors! 47 | gradientLayer.locations = style.locations 48 | if let orientation = style.orientation { 49 | gradientLayer.startPoint = orientation.0 50 | gradientLayer.endPoint = orientation.1 51 | } 52 | if style.axial { 53 | gradientLayer.type = kCAGradientLayerAxial 54 | } 55 | gradientLayer.frame = frame 56 | gradientLayer.mask = maskLayer 57 | gradientLayer.contentsScale = self.contentsScale 58 | gradientLayer.setAffineTransform(affineTransform()) 59 | 60 | superlayer!.addSublayer(gradientLayer) 61 | fillColor = nil 62 | self.gradientLayer = gradientLayer 63 | } else { 64 | self.gradientLayer = nil 65 | } 66 | } 67 | } 68 | 69 | public extension CALayer { 70 | 71 | public var gradientLayer: CAGradientLayer? { 72 | get { 73 | let defv:CAGradientLayer? = nil 74 | return getAssociatedWrappedObject(self, key: &GradientLayerKey, defaultValue: defv) 75 | } 76 | set { 77 | if let oldlayer = gradientLayer { 78 | oldlayer.removeAllAnimations() 79 | oldlayer.removeFromSuperlayer() 80 | } else if newValue == nil { 81 | return 82 | } 83 | weak var layer = newValue 84 | setAssociatedWrappedObject(self, key: &GradientLayerKey, value: layer) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ShapeAnimation/View/CAShapeLayer+Path.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CAShapeLayer+Path.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 1/20/15. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | public extension CAShapeLayer { 12 | 13 | //! The path mapped to the parent layer's coordinate systems. 14 | public var pathToSuperlayer:CGPath! { 15 | get { 16 | var xf = CGAffineTransform(translation:frame.origin) 17 | return CGPathCreateCopyByTransformingPath(path, &xf) 18 | } 19 | set(v) { 20 | frame = v.boundingBox 21 | var xf = CGAffineTransform(translation:-frame.origin) 22 | path = CGPathCreateCopyByTransformingPath(v, &xf) 23 | } 24 | } 25 | 26 | public func strokingPath(lineWidth:CGFloat) -> CGPath { 27 | let s = paintStyle 28 | return CGPathCreateCopyByStrokingPath(path, nil, lineWidth, 29 | s.lineCap!, s.lineJoin!, miterLimit)! 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ShapeAnimation/View/CAShapeLayer+Style.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CAShapeLayer+Style.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/29. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | public typealias PaintStyle = SwiftGraphics.Style 12 | 13 | public extension CAShapeLayer { 14 | public var paintStyle: PaintStyle! { 15 | get { 16 | var style = PaintStyle() 17 | style.fillColor = self.fillColor 18 | style.strokeColor = self.strokeColor 19 | style.lineWidth = self.lineWidth 20 | style.miterLimit = self.miterLimit 21 | if let lineDashPattern = self.lineDashPattern { 22 | style.lineDash = lineDashPattern.map{ CGFloat($0 as NSNumber) } 23 | } 24 | style.lineDashPhase = self.lineDashPhase 25 | style.alpha = CGFloat(self.opacity) 26 | style.miterLimit = self.miterLimit 27 | // TODO flatness, blendMode 28 | 29 | switch self.lineCap { 30 | case kCALineCapRound: 31 | style.lineCap = CGLineCap.Round 32 | case kCALineCapSquare: 33 | style.lineCap = CGLineCap.Square 34 | default: 35 | style.lineCap = CGLineCap.Butt 36 | } 37 | switch self.lineJoin { 38 | case kCALineJoinMiter: 39 | style.lineJoin = CGLineJoin.Miter 40 | case kCALineJoinBevel: 41 | style.lineJoin = CGLineJoin.Bevel 42 | default: 43 | style.lineJoin = CGLineJoin.Round 44 | } 45 | return style 46 | } 47 | set { 48 | apply(newValue) 49 | } 50 | } 51 | 52 | public func apply(newStyle:PaintStyle) { 53 | self.fillColor = newStyle.fillColor 54 | if let strokeColor = newStyle.strokeColor { 55 | self.strokeColor = strokeColor 56 | } 57 | if let lineWidth = newStyle.lineWidth { 58 | self.lineWidth = lineWidth 59 | } 60 | if let lineCap = newStyle.lineCap { 61 | switch lineCap { 62 | case CGLineCap.Round: 63 | self.lineCap = kCALineCapRound 64 | case CGLineCap.Square: 65 | self.lineCap = kCALineCapSquare 66 | default: 67 | self.lineCap = kCALineCapButt 68 | } 69 | } 70 | if let lineJoin = newStyle.lineJoin { 71 | switch lineJoin { 72 | case CGLineJoin.Miter: 73 | self.lineJoin = kCALineJoinMiter 74 | case CGLineJoin.Bevel: 75 | self.lineJoin = kCALineJoinBevel 76 | default: 77 | self.lineJoin = kCALineJoinRound 78 | } 79 | } 80 | if let miterLimit = newStyle.miterLimit { 81 | self.miterLimit = miterLimit 82 | } 83 | if let lineDash = newStyle.lineDash { 84 | self.lineDashPattern = lineDash 85 | } 86 | if let lineDashPhase = newStyle.lineDashPhase { 87 | self.lineDashPhase = lineDashPhase 88 | } 89 | if let _ = newStyle.flatness { 90 | // TODO flatness 91 | } 92 | if let alpha = newStyle.alpha { 93 | self.opacity = Float(alpha) 94 | } 95 | if let _ = newStyle.blendMode { 96 | // TODO blendMode 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /ShapeAnimation/View/ShapeView+HitTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShapeView+HitTest.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 2/8/15. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | public extension CALayer { 12 | public func enumerateLayers(block:(CALayer) -> Void) { 13 | if let sublayers = sublayers { 14 | for layer in sublayers { 15 | if !layer.isKindOfClass(CAGradientLayer) { 16 | block(layer as CALayer) 17 | } 18 | } 19 | } 20 | } 21 | } 22 | 23 | public extension ShapeView { 24 | 25 | public func enumerateLayers(block:(CALayer) -> Void) { 26 | sublayer.enumerateLayers(block) 27 | } 28 | 29 | public func hitTest(point:CGPoint, filter:((CALayer) -> Bool)? = nil) -> CALayer? { 30 | var ret:CALayer? 31 | var defaultShape:CALayer? 32 | var minFrame:CGRect! 33 | func area(rect:CGRect) -> CGFloat { return rect.width * rect.height } 34 | 35 | enumerateLayers { layer in 36 | if layer.frame.contains(point) && (filter == nil || filter!(layer)) { 37 | defaultShape = layer 38 | if let shape = layer as? CAShapeLayer { 39 | if shape.hitTestPath(point) { 40 | if ret == nil || area(shape.frame) < area(minFrame) { 41 | ret = shape 42 | minFrame = shape.frame 43 | } 44 | } 45 | } 46 | else { 47 | ret = layer 48 | minFrame = layer.frame 49 | } 50 | } 51 | } 52 | return ret != nil ? ret : defaultShape 53 | } 54 | 55 | public func intersects(rect:CGRect, filter:((CALayer) -> Bool)? = nil) -> [CALayer] { 56 | var ret:[CALayer] = [] 57 | 58 | enumerateLayers { layer in 59 | if let shape = layer as? CAShapeLayer { 60 | if shape.intersects(rect) && (filter == nil || filter!(shape)) { 61 | ret.append(shape) 62 | } 63 | } 64 | else if layer.frame.contains(rect) && (filter == nil || filter!(layer)) { 65 | ret.append(layer) 66 | } 67 | } 68 | return ret 69 | } 70 | } 71 | 72 | public extension CAShapeLayer { 73 | 74 | public var isFilled : Bool { 75 | return path!.isClosed && (paintStyle.fillColor != nil || gradient?.colors != nil) 76 | } 77 | 78 | public func hitTestPath(point:CGPoint) -> Bool { 79 | let path = isFilled ? self.path : strokingPath(max(lineWidth, 10)) 80 | var xf = affineTransform().inverted() + CGAffineTransform(translation: -position) 81 | return CGPathContainsPoint(path, &xf, point, false) 82 | } 83 | 84 | public func intersects(rect:CGRect) -> Bool { 85 | let xf = affineTransform().inverted() + CGAffineTransform(translation: -position) 86 | return path!.intersects(rect * xf) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ShapeAnimation/View/ShapeView+Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShapeView+Image.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/2/3. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | public extension ShapeView { 12 | 13 | public func addRectangleLayer(frame:CGRect) -> CAShapeLayer! { 14 | return addShapeLayer(CGPathCreateWithRect(frame, nil)) 15 | } 16 | 17 | public func addCircleLayer(center c:CGPoint, radius:CGFloat) -> CAShapeLayer! { 18 | return addShapeLayer(CGPathCreateWithEllipseInRect(CGRect(center:c, radius:radius), nil)) 19 | } 20 | 21 | public func addRegularPolygonLayer(nside:Int, center:CGPoint, radius:CGFloat) -> CAShapeLayer! { 22 | return addLinesLayer(RegularPolygon(nside:nside, center:center, radius:radius).points, closed:true) 23 | } 24 | 25 | public func addLinesLayer(points:[CGPoint], closed:Bool = false) -> CAShapeLayer! { 26 | return addShapeLayer(Path(vertices:points, closed:closed).cgPath) 27 | } 28 | 29 | //! Add shape layer with path string as the ‘d’ attribute of SVG path. 30 | //! It can contain instructions ‘MmLlCcQqSsTtZz’ as described in http://www.w3.org/TR/SVGTiny12/paths.html 31 | public func addSVGPathLayer(d:String, position:CGPoint? = nil) -> CAShapeLayer! { 32 | return addShapeLayer(CGPathFromSVGPath(d), position:position) 33 | } 34 | } 35 | 36 | public extension ShapeView { 37 | 38 | public func addTextLayer(text:String, frame:CGRect, fontSize:CGFloat) -> CATextLayer! { 39 | let layer = CATextLayer() 40 | 41 | layer.string = text 42 | layer.fontSize = fontSize 43 | if let strokeColor = style.strokeColor { 44 | layer.foregroundColor = strokeColor 45 | } 46 | layer.alignmentMode = kCAAlignmentCenter 47 | layer.wrapped = true 48 | addSublayer(layer, frame:frame) 49 | 50 | return layer 51 | } 52 | 53 | public func addTextLayer(text:String, center:CGPoint, fontSize:CGFloat) -> CATextLayer! { 54 | let attr = [NSFontAttributeName: Font.systemFontOfSize(fontSize)] 55 | let size = text.sizeWithAttributes(attr) 56 | return addTextLayer(text, frame:CGRect(center:center, size:size), fontSize:fontSize) 57 | } 58 | 59 | public func addImageLayer(image:Image, center:CGPoint) -> CALayer! { 60 | let layer = CALayer() 61 | #if os(iOS) 62 | layer.contents = image.CGImage 63 | #else 64 | let ctx = NSGraphicsContext.currentContext() 65 | var rect = CGRect(size:image.size) 66 | layer.contents = image.CGImageForProposedRect(&rect, context:ctx, hints:nil)?.takeUnretainedValue() 67 | #endif 68 | addSublayer(layer, frame:CGRect(center:center, size:image.size)) 69 | return layer 70 | } 71 | 72 | public func addImageLayer(named named:String, center:CGPoint) -> CALayer? { 73 | if let image = Image(named:named) { 74 | return addImageLayer(image, center:center) 75 | } 76 | return nil 77 | } 78 | 79 | public func addImageLayer(contentsOfFile path:String, center:CGPoint) -> CALayer? { 80 | if let image = Image(contentsOfFile:path) { 81 | return addImageLayer(image, center:center) 82 | } 83 | return nil 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ShapeAnimation/View/ShapeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShapeView.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/20. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | 11 | //! View class which contains vector shape layers. 12 | public class ShapeView : View { 13 | 14 | public var style:PaintStyle = { 15 | var style = PaintStyle.defaultStyle 16 | style.lineCap = CGLineCap.Butt 17 | style.lineJoin = CGLineJoin.Round 18 | return style 19 | }() 20 | public var gradient = Gradient() 21 | 22 | #if os(OSX) 23 | public var sublayer:CALayer { return self.layer! } 24 | #else 25 | public var sublayer:CALayer { return self.layer } 26 | #endif 27 | 28 | public func addSublayer(layer:CALayer, frame:CGRect, superlayer:CALayer? = nil) { 29 | layer.frame = frame 30 | #if os(OSX) 31 | layer.contentsScale = NSScreen.mainScreen()!.backingScaleFactor 32 | #else 33 | layer.contentsScale = UIScreen.mainScreen().scale 34 | #endif 35 | if let superlayer = superlayer { 36 | superlayer.addSublayer(layer) 37 | } else { 38 | sublayer.addSublayer(layer) 39 | } 40 | } 41 | 42 | public func addShapeLayer(path:CGPath, superlayer:CALayer? = nil) -> CAShapeLayer! { 43 | return addShapeLayer(path, position:nil, superlayer:superlayer) 44 | } 45 | 46 | public func addShapeLayer(path:CGPath, center:CGPoint, superlayer:CALayer? = nil) -> CAShapeLayer! { 47 | return addShapeLayer(path, position:center - CGPoint(size:path.boundingBox.size / 2), superlayer:superlayer) 48 | } 49 | 50 | public func addShapeLayer(path:CGPath, position:CGPoint?, superlayer:CALayer? = nil) -> CAShapeLayer! { 51 | let box = path.boundingBox 52 | let size = CGSize(w: max(box.width, 0.01), h: max(box.height, 0.01)) 53 | let frame = CGRect(origin:position != nil ? position! : box.origin, size:size) 54 | var xf = CGAffineTransform(translation:-box.origin) 55 | let layer = CAShapeLayer() 56 | 57 | layer.path = box.isNull || position != nil ? path : CGPathCreateCopyByTransformingPath(path, &xf) 58 | addSublayer(layer, frame:frame, superlayer:superlayer) 59 | layer.apply(style) 60 | layer.apply(gradient) 61 | 62 | return layer 63 | } 64 | 65 | // MARK: override from UIView 66 | 67 | override public func removeFromSuperview() { 68 | enumerateLayers { $0.removeLayer() } 69 | super.removeFromSuperview() 70 | } 71 | /* 72 | private var lastBounds = CGRect.zeroRect 73 | 74 | override public func didMoveToSuperview() { 75 | super.didMoveToSuperview() 76 | lastBounds = layer.bounds 77 | } 78 | 79 | override public func layoutSubviews() { 80 | super.layoutSubviews() 81 | 82 | if lastBounds != bounds { 83 | enumerateLayers { layer in 84 | if layer.frame == self.lastBounds { 85 | layer.frame = self.bounds 86 | } 87 | } 88 | lastBounds = self.bounds 89 | } 90 | }*/ 91 | } 92 | 93 | // MARK: CALayer.removeLayer 94 | 95 | public extension CALayer { 96 | 97 | public func removeLayer() { 98 | gradientLayer = nil 99 | removeAllAnimations() 100 | removeFromSuperlayer() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ShapeAnimation_OSX/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015年 github.com/rhcad. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ShapeAnimation_OSX/ShapeAnimation_OSX.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShapeAnimation_OSX.h 3 | // ShapeAnimation_OSX 4 | // 5 | // Created by Zhang Yungui on 15/3/23. 6 | // Copyright (c) 2015年 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for ShapeAnimation_OSX. 12 | FOUNDATION_EXPORT double ShapeAnimation_OSXVersionNumber; 13 | 14 | //! Project version string for ShapeAnimation_OSX. 15 | FOUNDATION_EXPORT const unsigned char ShapeAnimation_OSXVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /ShapeAnimation_OSXTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | github.rhcad.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ShapeAnimation_OSXTests/ShapeAnimation_OSXTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShapeAnimation_OSXTests.swift 3 | // ShapeAnimation_OSXTests 4 | // 5 | // Created by Zhang Yungui on 15/3/22. 6 | // Copyright (c) 2015年 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import XCTest 11 | 12 | class ShapeAnimation_OSXTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ShapeAnimation_UITest/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/15. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(application: UIApplication, didFinishLaunchingWithOptions: [NSObject : AnyObject]?) -> Bool { 17 | return true 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /ShapeAnimation_UITest/BallViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BallViewController.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/29. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ShapeAnimation 11 | 12 | class BallViewController: DetailViewController { 13 | @IBOutlet weak var distanceSlider: UISlider! 14 | @IBOutlet weak var heightSlider: UISlider! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | } 19 | 20 | @IBAction func distanceChanged(sender: UISlider) { 21 | } 22 | 23 | @IBAction func heightChanged(sender: UISlider) { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ShapeAnimation_UITest/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ShapeAnimation_UITest/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 264 | 274 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | -------------------------------------------------------------------------------- /ShapeAnimation_UITest/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/15. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import ShapeAnimation 10 | 11 | typealias AnimationBlock = ((view:ShapeView) -> Void) 12 | 13 | class DetailViewController: UIViewController { 14 | 15 | @IBOutlet weak var animationView: ShapeView! 16 | var animationBlock : AnimationBlock? 17 | var data : AnyObject? 18 | 19 | override func viewWillAppear(animated: Bool) { 20 | super.viewWillAppear(animated) 21 | animationBlock?(view: animationView) 22 | } 23 | 24 | override func viewDidDisappear(animated: Bool) { 25 | super.viewDidDisappear(animated) 26 | animationBlock = nil 27 | data = nil 28 | } 29 | 30 | @IBAction func stop(sender: AnyObject) { 31 | animationView.stop() 32 | } 33 | 34 | @IBAction func pause(sender: UIBarButtonItem) { 35 | animationView.paused = !animationView.paused 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ShapeAnimation_UITest/EllipseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EllipseViewController.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/30. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | import ShapeAnimation 11 | import SwiftUtilities 12 | 13 | class EllipseViewController: DetailViewController { 14 | @IBOutlet weak var rxSlider: UISlider! 15 | @IBOutlet weak var rySlider: UISlider! 16 | @IBOutlet weak var angleSlider: UISlider! 17 | 18 | var animationLayer:AnimationLayer! 19 | var ballLayer:CAShapeLayer? 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | animationLayer = animationView.addAnimationLayer(frame:animationView.bounds, 25 | properties:[("rx", 20), ("ry", 20), ("angle", 0)]) { (layer, ctx) -> Void in 26 | let ellipse = self.makeEllipse(layer) 27 | ctx.strokePath(ellipse.cgpath) 28 | self.drawLabelText(ctx, ellipse.a, ellipse.b, ellipse.rotation) 29 | } 30 | animationLayer.didStart = { self.ballLayer?.removeLayer(); return } 31 | animationLayer.didStop = { self.addBallLayer() } 32 | 33 | radiusXChanged(rxSlider) 34 | radiusYChanged(rySlider) 35 | angleChanged(angleSlider) 36 | } 37 | 38 | @IBAction func radiusXChanged(sender: UISlider) { 39 | animationLayer.setProperty(sender.value, key:"rx") 40 | } 41 | 42 | @IBAction func radiusYChanged(sender: UISlider) { 43 | animationLayer.setProperty(sender.value, key:"ry") 44 | } 45 | 46 | @IBAction func angleChanged(sender: UISlider) { 47 | animationLayer.setProperty(DegreesToRadians(CGFloat(-sender.value)), key:"angle") 48 | } 49 | 50 | private func makeEllipse(layer:AnimationLayer) -> Ellipse { 51 | let rx = layer.getProperty("rx") 52 | let ry = layer.getProperty("ry") 53 | let angle = layer.getProperty("angle") 54 | 55 | return Ellipse(center:self.animationView.bounds.mid, 56 | semiMajorAxis:max(rx, ry), 57 | semiMinorAxis:min(rx, ry), 58 | rotation:angle + (rx < ry ? CGFloat(M_PI_2) : 0)) 59 | } 60 | 61 | private func drawLabelText(ctx:CGContext, _ rx:CGFloat, _ ry:CGFloat, _ angle:CGFloat) { 62 | func round1(x:CGFloat) -> CGFloat { return round(x * 10) / 10 } 63 | let deg = round1(RadiansToDegrees(angle)) 64 | let text:NSString = "rx=\(round1(rx))\nry=\(round1(ry))\nangle=\(deg)" 65 | let attr = [NSFontAttributeName: UIFont.systemFontOfSize(17)] 66 | UIGraphicsPushContext(ctx) 67 | text.drawAtPoint(CGPoint(x:10, y:10), withAttributes:attr) 68 | UIGraphicsPopContext() 69 | } 70 | 71 | private func addBallLayer() { 72 | var gradient = Gradient(colors:[(1.0,0.0,0.0), (0.1,0.0,0.0)], axial:true) 73 | gradient.orientation = (CGPoint(x:0.3, y:-0.3), CGPoint(x:0, y:1.4)) 74 | 75 | ballLayer = animationView.addCircleLayer(center:CGPoint.zeroPoint, radius:5) 76 | ballLayer?.apply(gradient) 77 | 78 | let ellipse = self.makeEllipse(animationLayer) 79 | ballLayer!.moveOnPathAnimation(ellipse.cgpath).forever() 80 | .set(CAMediaTimingFunction(name:kCAMediaTimingFunctionLinear)).apply() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ShapeAnimation_UITest/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /ShapeAnimation_UITest/Images/airship@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhcad/ShapeAnimation-Swift/6375f339d1bb7d85f55b90063a2c151d2235ac07/ShapeAnimation_UITest/Images/airship@2x.png -------------------------------------------------------------------------------- /ShapeAnimation_UITest/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarTintParameters 34 | 35 | UINavigationBar 36 | 37 | Style 38 | UIBarStyleDefault 39 | Translucent 40 | 41 | 42 | 43 | UISupportedInterfaceOrientations 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UISupportedInterfaceOrientations~ipad 50 | 51 | UIInterfaceOrientationPortrait 52 | UIInterfaceOrientationPortraitUpsideDown 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /ShapeAnimation_UITest/MasterVC+Drag.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController+Drag.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/2/5. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | import ShapeAnimation 11 | 12 | // MARK: Layer dragging and tapping demo 13 | 14 | private var triangleState = 0 15 | 16 | extension MasterViewController { 17 | 18 | func testDragLayer(viewController:DetailViewController) -> AnimationBlock { 19 | return { view in 20 | let imageName = "airship.png" 21 | if let layer = view.addImageLayer(named:imageName, center:CGPoint(x:100, y:200)) { 22 | layer.identifier = "airship1" 23 | layer.didTap = { layer.tapAnimation().apply() } 24 | } 25 | if let layer = view.addImageLayer(named:imageName, center:CGPoint(x:200, y:200)) { 26 | layer.identifier = "airship2" 27 | layer.setAffineTransform(CGAffineTransform(scale:1.5)) 28 | layer.didTap = { layer.flipHorizontally().apply() } 29 | } 30 | if let layer = view.addImageLayer(named:imageName, center:CGPoint(x:300, y:200)) { 31 | layer.identifier = "airship3" 32 | layer.didTap = { layer.rotationAnimation(CGFloat(M_PI_2)).apply(duration:0.3) } 33 | } 34 | 35 | // Add a triangle with gradient fill 36 | let triangle = view.addSVGPathLayer("M10,20L150,40 120,120Z") 37 | triangle.apply(Gradient(colors:[(0.5, 0.5, 0.9, 1.0), (0.9, 0.9, 0.3, 1.0)])) 38 | triangle.didTap = { 39 | switch ++triangleState % 4 { 40 | case 0: triangle.flipHorizontally().apply() 41 | case 1: triangle.flipVertically().apply() 42 | case 2: triangle.rotate360Degrees().apply() 43 | case 3: 44 | let path = CGPathFromSVGPath("M100,0 L41.22,180.9 195.1,69.09 4.89,69.09 158.77,180.9z") 45 | triangle.switchPathAnimation(path).apply() 46 | default: () 47 | } 48 | } 49 | 50 | viewController.data = DragGestureHandler(view) 51 | self.testHamburger(view) 52 | } 53 | } 54 | } 55 | 56 | class DragGestureHandler : NSObject { 57 | private let kVelocityDampening:CGFloat = 10 58 | weak var currentLayer:CALayer? 59 | 60 | init(_ view:UIView) { 61 | super.init() 62 | let panGesture = UIPanGestureRecognizer(target:self, action:Selector("handlePanGesture:")) 63 | let tapGesture = UITapGestureRecognizer(target:self, action:Selector("handleTapGesture:")) 64 | tapGesture.requireGestureRecognizerToFail(panGesture) 65 | view.addGestureRecognizer(panGesture) 66 | view.addGestureRecognizer(tapGesture) 67 | } 68 | 69 | func handlePanGesture(sender:UIPanGestureRecognizer) { 70 | let view = sender.view as! ShapeView! 71 | 72 | switch sender.state { 73 | case .Began: 74 | currentLayer = view.hitTest(sender.locationInView(view)) 75 | case .Changed: 76 | if let layer = currentLayer { 77 | withDisableActions { 78 | layer.position += sender.translationInView(view) 79 | if let gradientLayer = layer.gradientLayer { 80 | gradientLayer.position += sender.translationInView(view) 81 | } 82 | } 83 | sender.setTranslation(CGPoint.zeroPoint, inView:view) 84 | } 85 | case .Ended: 86 | if let layer = currentLayer { 87 | var velocity = sender.velocityInView(view) 88 | velocity /= kVelocityDampening 89 | if velocity.magnitude > 1 { 90 | let endPoint = layer.position + velocity 91 | layer.constrainCenterToSuperview(endPoint) 92 | } else { 93 | layer.bringOnScreen() 94 | } 95 | } 96 | default: () 97 | } 98 | } 99 | 100 | func handleTapGesture(sender:UIPanGestureRecognizer) { 101 | let view = sender.view as! ShapeView! 102 | if sender.state == .Ended { 103 | if let layer = view.hitTest(sender.locationInView(view)) { 104 | layer.didTap?() 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ShapeAnimation_UITest/MasterVC+Hamburger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController+Hamburger.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/2/5. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | import ShapeAnimation 11 | 12 | // MARK: Hamburger button demo 13 | 14 | extension MasterViewController { 15 | func testHamburger(view:ShapeView) { // Modified from https://github.com/robb/hamburger-button 16 | view.addSubview(HamburgerButton(frame:CGRect(x:300, y:10, w:54, h:54))) 17 | } 18 | } 19 | 20 | class HamburgerButton : UIButton { 21 | let shortStroke = CGPathFromSVGPath("M2,2 h26") 22 | let outline = CGPathFromSVGPath("M10,27C12,27,28,27,40,27 56,27,51,2,27,2 13,2,2,13,2,27 " 23 | + "2,41,13,52,27,52 41,52,52,41,52,27 52,13,42,2,27,2 13,2,2,13,2,27") // 50x50 24 | let startpath = CGPathFromSVGPath("M10,27C12,27,28,27,40,27 56,27,51,2,27,2") 25 | let endpath = CGPathFromSVGPath("M27,2C13,2,2,13,2,27") 26 | 27 | var top:CAShapeLayer! 28 | var middle:CAShapeLayer! 29 | var bottom:CAShapeLayer! 30 | 31 | var length:CGFloat! 32 | var menuStart:CGFloat! 33 | var menuEnd:CGFloat! 34 | var hamburgerStart:CGFloat! 35 | var hamburgerEnd:CGFloat! 36 | 37 | required init?(coder aDecoder: NSCoder) { 38 | super.init(coder: aDecoder) 39 | initView() 40 | } 41 | 42 | override init(frame: CGRect) { 43 | super.init(frame: frame) 44 | initView() 45 | } 46 | 47 | private func initView() { 48 | length = outline.length 49 | menuStart = startpath.length / length 50 | menuEnd = 1 - endpath.length / length 51 | hamburgerStart = 8 / length 52 | hamburgerEnd = 30 / length 53 | 54 | let view = ShapeView() 55 | top = view.addShapeLayer(shortStroke, position:CGPoint(x:14, y:27 - 9), superlayer:self.layer) 56 | bottom = view.addShapeLayer(shortStroke, position:CGPoint(x:14, y:27 + 9), superlayer:self.layer) 57 | middle = view.addShapeLayer(outline, center: CGPoint(x:27, y:27), superlayer:self.layer) 58 | 59 | for layer in [top, middle, bottom] { 60 | layer.strokeColor = CGColor.brownColor() 61 | layer.lineWidth = 4 62 | layer.lineCap = kCALineCapRound 63 | layer.masksToBounds = true 64 | 65 | let strokingPath = CGPathCreateCopyByStrokingPath(layer.path, nil, 4, CGLineCap.Round, CGLineJoin.Miter, 4) 66 | layer.bounds = CGPathGetPathBoundingBox(strokingPath) 67 | self.layer.addSublayer(layer) 68 | } 69 | 70 | middle.strokeStart = hamburgerStart 71 | middle.strokeEnd = hamburgerEnd 72 | top.setAnchorPoint(CGPoint(x:28.0 / 30.0, y:0.5)) 73 | bottom.setAnchorPoint(CGPoint(x:28.0 / 30.0, y:0.5)) 74 | 75 | addTarget(self, action: "toggle:", forControlEvents:.TouchUpInside) 76 | } 77 | 78 | var showsMenu: Bool = false { 79 | didSet { 80 | if showsMenu { 81 | middle.strokeStartAnimation(to:menuStart) 82 | .set(CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)) 83 | .apply(duration:0.5) 84 | middle.strokeEndAnimation(to:menuEnd) 85 | .set(CAMediaTimingFunction(controlPoints: 0.25, -0.4, 0.5, 1)) 86 | .apply(duration:0.6) 87 | } else { 88 | middle.strokeStartAnimation(to:hamburgerStart) 89 | .set(CAMediaTimingFunction(controlPoints: 0.25, 0, 0.5, 1.2)) 90 | .setBeginTime(0.1).apply(duration:0.5) 91 | middle.strokeEndAnimation(to:hamburgerEnd) 92 | .set(CAMediaTimingFunction(controlPoints: 0.25, 0.3, 0.5, 0.9)) 93 | .apply(duration:0.6) 94 | } 95 | 96 | let translation = CATransform3DMakeTranslation(-4, 0, 0) 97 | let xftop = CATransform3DRotate(translation, -CGFloat(M_PI_4), 0, 0, 1) 98 | let xfbot = CATransform3DRotate(translation, CGFloat(M_PI_4), 0, 0, 1) 99 | let timeFunc = CAMediaTimingFunction(controlPoints: 0.5, -0.8, 0.5, 1.85) 100 | top.transformAnimation(to:showsMenu ? xftop : CATransform3DIdentity) 101 | .set(timeFunc).setBeginTime(showsMenu ? 0.25 : 0.05).apply(duration:0.4) 102 | bottom.transformAnimation(to:showsMenu ? xfbot : CATransform3DIdentity) 103 | .set(timeFunc).setBeginTime(showsMenu ? 0.25 : 0.05).apply(duration:0.4) 104 | } 105 | } 106 | 107 | func toggle(sender: UIView!) { 108 | showsMenu = !showsMenu 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /ShapeAnimation_UITest/MasterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterViewController.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/15. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import SwiftGraphics 10 | import ShapeAnimation 11 | 12 | class MasterViewController: UITableViewController { 13 | 14 | // MARK: Segues 15 | 16 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 17 | let viewController = segue.destinationViewController as! DetailViewController 18 | 19 | switch segue.identifier! as NSString { 20 | case "Add Lines": 21 | viewController.animationBlock = testAddLines(viewController) 22 | case "Move Lines": 23 | viewController.animationBlock = testMoveLines(viewController) 24 | case "Rotate Polygons": 25 | viewController.animationBlock = testRotatePolygons(viewController) 26 | case "Radar Circles": 27 | viewController.animationBlock = testRadarCircles(viewController) 28 | case "Jumping Ball": 29 | viewController.animationBlock = testJumpingBall(viewController) 30 | case "Drag Layer": 31 | viewController.animationBlock = testDragLayer(viewController) 32 | default: () 33 | } 34 | viewController.title = NSLocalizedString(segue.identifier! as NSString as String, comment:"") 35 | } 36 | 37 | // Demo about strokeEndAnimation, lineWidthAnimation, scaleAnimation, shakeAnimation and flashAnimation. 38 | private func testAddLines(viewController:DetailViewController) -> AnimationBlock { 39 | return { (view:ShapeView) -> Void in 40 | view.style.lineWidth = 7 41 | 42 | let points1 = [CGPoint((10,20)),CGPoint((150,40)),CGPoint((120,320))] 43 | let la1 = view.addLinesLayer(points1, closed:true, color:CGColor.redColor()) 44 | la1.identifier = "triangle1" 45 | animationGroup([la1.strokeEndAnimation(), la1.lineWidthAnimation(0, to:5)]).apply() { 46 | la1.shakeAnimation().apply() 47 | } 48 | 49 | let xf2 = CGAffineTransform(tx:100, ty:0) 50 | let la2 = view.addLinesLayer(points1.map { $0 * xf2 }, closed:true, color:CGColor.purpleColor()) 51 | let la3 = view.addLinesLayer(points1.map { $0 * xf2 * xf2 }, closed:true, color:CGColor.greenColor()) 52 | 53 | la2.identifier = "triangle2" 54 | la3.identifier = "triangle3" 55 | la2.scaleAnimation(from:1, to:1.1, repeatCount:3).apply(duration:0.3) { 56 | la3.flashAnimation().apply() 57 | } 58 | } 59 | } 60 | 61 | // Demo about moveOnPathAnimation, moveAnimation, rotationAnimation, dashPhaseAnimation and animationGroup. 62 | // Rotate and move a picture and polygon with gradient fill along the path. 63 | private func testMoveLines(viewController:DetailViewController) -> AnimationBlock { 64 | return { (view:ShapeView) in 65 | let path = CGPathFromSVGPath("M120,70 C0,200 150,375 250,220 T500,220") 66 | 67 | // Add a triangle with gradient fill 68 | view.gradient.setColors([(0.5, 0.5, 0.9, 1.0), (0.9, 0.9, 0.3, 1.0)]) 69 | let points = [CGPoint((10, 20)), CGPoint((150, 40)), CGPoint((120, 120))] 70 | let layer1 = view.addLinesLayer(points, closed:true) 71 | 72 | // Move and rotate the triangle along the path 73 | let a1 = layer1.moveOnPathAnimation(path).setDuration(1.6) 74 | let a2 = layer1.rotate360Degrees().setRepeatCount(2) 75 | animationGroup([a1, a2]).autoreverses().forever().apply() 76 | 77 | // Show the path with vary dash phase and color 78 | let pathLayer = view.addLinesLayer([CGPoint.zeroPoint]) 79 | pathLayer.pathToSuperlayer = path 80 | pathLayer.lineDashPattern = [5, 5] 81 | let a4 = pathLayer.strokeColorAnimation(CGColor.lightGrayColor(), to:CGColor.greenColor()) 82 | .autoreverses().forever() 83 | let a5 = pathLayer.dashPhaseAnimation(0, to:20) 84 | animationGroup([a4, a5]).forever().apply() 85 | 86 | // Rotate and move a picture along the path 87 | if let imageLayer = view.addImageLayer(named:"airship.png", center:CGPoint(x:200, y:200)) { 88 | imageLayer.moveOnPathAnimation(path, autoRotate:true) 89 | .setBeginTime(1.0).apply(duration:2) 90 | } 91 | } 92 | } 93 | 94 | // Demo about polygon with text and gradient fill moving and rotating one by one. 95 | // Modified from http://zulko.github.io/blog/2014/09/20/vector-animations-with-python/ 96 | private func testRotatePolygons(viewController:DetailViewController) -> AnimationBlock { 97 | return { view in 98 | view.gradient.setColors([(0, 0.5, 1, 1), (0, 1, 1, 1)]) 99 | view.gradient.orientation = (CGPoint.zeroPoint, CGPoint(x:1, y:1)) 100 | 101 | var animations:[AnimationPair] = [] 102 | 103 | for (i, c) in "swift".characters.enumerate() { 104 | let polygon = RegularPolygon(nside:5, center:CGPoint(x:50 + 60*i, y:50), radius:25) 105 | let edgeLayer = view.addLinesLayer(polygon.points, closed:true) 106 | let textLayer = view.addTextLayer(String(c), frame:polygon.incircle.frame, fontSize:30) 107 | 108 | edgeLayer.identifier = "polygon\(i)" 109 | textLayer.identifier = "text\(i)" 110 | animations.append(edgeLayer.rotationAnimation(CGFloat(2 * M_PI)) 111 | .setBeginTime(i, gap:0.3, duration:1.5)) 112 | animations.append(textLayer.rotationAnimation(CGFloat(2 * M_PI)) 113 | .setBeginTime(i, gap:0.3, duration:1.5)) 114 | } 115 | applyAnimations(animations) { 116 | let movement = CGPoint(x:300) 117 | for (i, anim) in animations.enumerate() { 118 | anim.layer.moveAnimation(to:movement).setBeginTime(5 - i/2, gap:0.3).apply() 119 | } 120 | } 121 | } 122 | } 123 | 124 | // Demo about growing circles. 125 | private func testRadarCircles(viewController:DetailViewController) -> AnimationBlock { 126 | return { view in 127 | let count = 6 128 | let duration: Double = 2 129 | 130 | view.style.lineWidth = 1 131 | for i in 0.. AnimationBlock { 144 | var gradient = Gradient(colors:[(1.0,0.0,0.0), (0.1,0.0,0.0)], axial:true) 145 | gradient.orientation = (CGPoint(x:0.3, y:-0.3), CGPoint(x:0, y:1.4)) 146 | 147 | return { view in 148 | let layer = view.addAnimationLayer(frame:view.bounds, properties:[("t", 0)]) { 149 | (layer, ctx) -> Void in 150 | let W = view.layer.bounds.width 151 | let H = view.layer.bounds.height 152 | let D:CGFloat = 3, r:CGFloat = 30 // radius of ball 153 | let DJ:CGFloat = W / 6, HJ = H / 2 // distance and height of jumping 154 | let ground:CGFloat = 0.75*H 155 | 156 | let t = layer.getProperty("t") 157 | 158 | let x = (-W / 3) + (5 * W / 3) * (t / D) 159 | let y = ground - HJ * 4 * (x % DJ) * (DJ - (x % DJ)) / (DJ * DJ) 160 | let coef = (HJ - y) / HJ 161 | let sr = (1 - coef / 4) * r 162 | 163 | var sgradient = Gradient(colors: [CGColor.color(white:0, alpha:0.2-coef/5), CGColor.clearColor()], axial:true) 164 | sgradient.orientation = (CGPoint(y:0.5), CGPoint(x:1, y:0.5)) 165 | 166 | let shadow = Ellipse(center:CGPoint(x:x, y:ground + r/2), size:CGSize(w:sr*2, h:sr)) 167 | ctx.fillEllipseInRect(sgradient, rect: shadow.boundingBox) 168 | 169 | let ball = Circle(center:CGPoint(x:x, y:y), radius:r) 170 | ctx.fillEllipseInRect(gradient, rect: ball.frame) 171 | } 172 | layer.animationCreated = { $1.repeatCount=HUGE; $1.duration=10.0 } 173 | layer.setProperty(4, key: "t") 174 | } 175 | } 176 | 177 | } 178 | 179 | public extension ShapeView { 180 | 181 | public func addLinesLayer(points:[CGPoint], closed:Bool = false, color:CGColor) -> CAShapeLayer { 182 | style.strokeColor = color 183 | return addShapeLayer(Path(vertices:points, closed:closed).cgPath) 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /ShapeAnimation_UnitTests/DummyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DummyTests.swift 3 | // ShapeAnimation 4 | // 5 | // Created by Zhang Yungui on 15/1/20. 6 | // Copyright (c) 2015 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | import SwiftGraphics 12 | import ShapeAnimation 13 | 14 | class DummyTests: XCTestCase { 15 | 16 | func testOrientation() { 17 | XCTAssertEqual(CGSize(width:100, height:100).orientation, Orientation.Square) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /ShapeAnimation_UnitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ShapeAnimation_UnitTests/SwiftUtilities_Tests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUtilities_Tests.swift 3 | // SwiftUtilities Tests 4 | // 5 | // Created by Zhang Yungui on 8/12/14. 6 | // Copyright (c) 2014 github.com/rhcad. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import XCTest 11 | import SwiftUtilities 12 | 13 | class SwiftUtilities_Tests: XCTestCase { 14 | 15 | // override func setUp() { 16 | // super.setUp() 17 | // // Put setup code here. This method is called before the invocation of each test method in the class. 18 | // } 19 | // 20 | // override func tearDown() { 21 | // // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | // super.tearDown() 23 | // } 24 | 25 | func testCGPointExtensions() { 26 | let value1 = CGPoint(x:10) 27 | XCTAssertNotEqual(value1, CGPointZero) 28 | XCTAssertEqual(value1, CGPoint(x:10, y:0)) 29 | 30 | let value2 = value1 + CGPoint(y:20) 31 | XCTAssertEqual(value2, CGPoint(x:10, y:20)) 32 | 33 | let value3 = value2 * 2 34 | XCTAssertEqual(value3, CGPoint(x:20, y:40)) 35 | 36 | let value4 = value3 - CGPoint(x:1, y:1) 37 | XCTAssertEqual(value4, CGPoint(x:19, y:39)) 38 | } 39 | 40 | func testCGSizeExtensions() { 41 | let value1 = CGSize(width:10) 42 | XCTAssertNotEqual(value1, CGSizeZero) 43 | XCTAssertEqual(value1, CGSize(width:10, height:0)) 44 | 45 | let value2 = value1 + CGSize(height:20) 46 | XCTAssertEqual(value2, CGSize(width:10, height:20)) 47 | 48 | let value3 = value2 * 2 49 | XCTAssertEqual(value3, CGSize(width:20, height:40)) 50 | } 51 | 52 | func testCGRectExtensions() { 53 | let value1 = CGRect(width:100, height:200) 54 | XCTAssertEqual(value1, CGRect(x:0, y:0, width:100, height:200)) 55 | 56 | let value2 = CGRect(size:CGSize(width:100,height:200)) 57 | XCTAssertEqual(value2, CGRect(x:0, y:0, width:100, height:200)) 58 | } 59 | 60 | func testQuadrants() { 61 | XCTAssertEqual(Quadrant.fromPoint(CGPoint(x:10, y:10)), Quadrant.TopRight) 62 | XCTAssertEqual(Quadrant.fromPoint(CGPoint(x:-10, y:10)), Quadrant.TopLeft) 63 | XCTAssertEqual(Quadrant.fromPoint(CGPoint(x:10, y:-10)), Quadrant.BottomRight) 64 | XCTAssertEqual(Quadrant.fromPoint(CGPoint(x:-10, y:-10)), Quadrant.BottomLeft) 65 | XCTAssertEqual(Quadrant.fromPoint(CGPoint(x:0, y:0)), Quadrant.TopRight) 66 | 67 | let origin = CGPoint(x:10, y:10) 68 | XCTAssertEqual(Quadrant.fromPoint(CGPoint(x:15, y:15), origin:origin), Quadrant.TopRight) 69 | XCTAssertEqual(Quadrant.fromPoint(CGPoint(x:5, y:15), origin:origin), Quadrant.TopLeft) 70 | XCTAssertEqual(Quadrant.fromPoint(CGPoint(x:15, y:5), origin:origin), Quadrant.BottomRight) 71 | XCTAssertEqual(Quadrant.fromPoint(CGPoint(x:5, y:5), origin:origin), Quadrant.BottomLeft) 72 | 73 | let rect = CGRect(width:100, height:50) 74 | 75 | XCTAssertEqual(Quadrant.fromPoint(CGPoint(x:15, y:15), rect:rect), Quadrant.BottomLeft) 76 | 77 | XCTAssertEqual(Quadrant.TopRight.quadrantRectOfRect(rect), CGRect(x:50, y:25, width: 50, height:25)) 78 | XCTAssertEqual(Quadrant.BottomLeft.quadrantRectOfRect(rect), CGRect(x:0, y:0, width: 50, height:25)) 79 | } 80 | 81 | func testBoolEnum() { 82 | let b = BoolEnum(false) 83 | XCTAssertEqual(b, false) 84 | XCTAssertEqual(b, BoolEnum.False) 85 | } 86 | 87 | 88 | // func testPerformanceExample() { 89 | // // This is an example of a performance test case. 90 | // self.measureBlock() { 91 | // // Put the code you want to measure the time of here. 92 | // } 93 | // } 94 | 95 | } 96 | --------------------------------------------------------------------------------