├── .build └── manifest.db ├── .gitignore ├── Examples ├── Examples-macOs.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Examples-macOs.xcscheme ├── Examples-macOs │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-128.png │ │ │ ├── icon-128@2x.png │ │ │ ├── icon-16.png │ │ │ ├── icon-16@2x.png │ │ │ ├── icon-256.png │ │ │ ├── icon-256@2x.png │ │ │ ├── icon-32.png │ │ │ ├── icon-32@2x.png │ │ │ ├── icon-512.png │ │ │ └── icon-512@2x.png │ │ └── Contents.json │ ├── Examples_macOs.entitlements │ ├── Info.plist │ ├── PathSample.swift │ ├── Random.swift │ ├── SimpleTween.swift │ ├── StringSample.swift │ ├── TimelineSample.swift │ ├── TweenChain.swift │ ├── ViewExtensionSample.swift │ └── main.swift ├── Examples.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist ├── Examples │ ├── AnimateArcRadius.swift │ ├── AnimateText.swift │ ├── AppDelegate.swift │ ├── ArcOrbits.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x-1.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x-1.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x-1.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x-1.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── iTunesArtwork@2x.png │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── ChainableTweens.swift │ ├── CurveInspector.swift │ ├── CustomEasing.swift │ ├── CustomTypes.swift │ ├── DragView.swift │ ├── EaseCurves.swift │ ├── Info.plist │ ├── LaunchScreen.png │ ├── PathLoop.swift │ ├── PauseTweens.swift │ ├── ScrollAims.swift │ ├── ScrollTimeline.swift │ ├── SimpleTimeline.swift │ ├── SimpleTween.swift │ ├── TimelineBasic.swift │ ├── TouchPoint.swift │ ├── Transform3d.swift │ ├── TweenHandlers.swift │ ├── ViewExtensions.swift │ └── WindBlow.swift └── Resources │ ├── bb8-body.pdf │ ├── bb8-head.pdf │ ├── bee.pdf │ ├── card.pdf │ ├── clouds1.pdf │ ├── clouds2.pdf │ ├── clouds3.pdf │ ├── comet.pdf │ ├── dart.pdf │ ├── earth.pdf │ ├── eyepupil.pdf │ ├── fire.pdf │ ├── flower.pdf │ ├── jupyter.pdf │ ├── little-earth.pdf │ ├── little-jupyter.pdf │ ├── little-saturn.pdf │ ├── little-sun-fire.pdf │ ├── little-sun.pdf │ ├── mars.pdf │ ├── moon.pdf │ ├── orbits-background.pdf │ ├── rocket.pdf │ ├── sand.pdf │ ├── saturn.pdf │ ├── spaceman.pdf │ ├── stars.pdf │ ├── sun.pdf │ ├── sunny.pdf │ └── ufo.pdf ├── Gifs ├── bb8.gif ├── clouds.gif ├── drag.gif ├── ease-curves.gif ├── extensions.gif ├── eye.gif ├── handlers.gif ├── orbits.gif ├── path-text.gif ├── path.gif ├── random.gif ├── rotation-aim.gif ├── simple-tween.gif ├── text.gif ├── timeline-editor.gif ├── timeline-loop.gif ├── timeline-ping-pong.gif ├── timeline-play.gif ├── timeline-zoom.gif ├── tmeline-inspector.gif ├── tmeline-scroll.gif ├── touch.gif ├── tweener.gif ├── tweenvisualizer-drag.gif ├── tweenvisualizer-pinch.gif ├── tweenvisualizer-resize.gif └── tweenvisualizer.gif ├── LICENSE ├── Package.swift ├── README.md ├── Source ├── ArcAim.swift ├── BasicMath.swift ├── BezierUtils.swift ├── CGPathUtils.swift ├── Ease.swift ├── Extensions.swift ├── PDFImageRender.swift ├── PDFImageView.swift ├── PathAim.swift ├── RotationAim.swift ├── StringAim.swift ├── SupportedTypes.swift ├── Timeline.swift ├── TimelineInspector.swift ├── Tween.swift ├── TweenControl.swift ├── TweenVisualizer.swift └── Tweener.swift ├── Tests ├── LinuxMain.swift └── Tweener │ ├── Tweener.swift │ └── XCTestManifests.swift ├── Tweener iOs ├── Info.plist └── Tweener_iOS.h ├── Tweener macOS ├── Info.plist └── Tweener_macOS.h ├── Tweener tvOs ├── Info.plist └── Tweener_tvOS.h ├── Tweener watchOS ├── Info.plist └── Tweener_watchOS.h ├── Tweener.framework.zip ├── Tweener.podspec ├── Tweener.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ ├── Tweener iOs.xcscheme │ └── Tweener macOS.xcscheme ├── Tweener ├── Info.plist └── Tweener.h ├── docs ├── Classes.html ├── Classes │ ├── AnyTween.html │ ├── ArcAim.html │ ├── BasicMath.html │ ├── BezierUtils.html │ ├── CGPathUtils.html │ ├── Ease.html │ ├── PDFImageView.html │ ├── PathAim.html │ ├── RotationAim.html │ ├── StringAim.html │ ├── Timeline.html │ ├── TimelineInspector.html │ ├── Tween.html │ ├── TweenBlock.html │ └── TweenVisualizer.html ├── Enums.html ├── Enums │ ├── TimelinePlayMode.html │ └── TransitionType.html ├── Extensions.html ├── Extensions │ ├── NSBezierPath.html │ ├── NSColor.html │ ├── NSView.html │ └── UIView.html ├── Functions.html ├── Global Variables.html ├── Typealiases.html ├── badge.svg ├── css │ ├── highlight.css │ └── jazzy.css ├── docsets │ ├── Tweener.docset │ │ └── Contents │ │ │ ├── Info.plist │ │ │ └── Resources │ │ │ ├── Documents │ │ │ ├── Classes.html │ │ │ ├── Classes │ │ │ │ ├── AnyTween.html │ │ │ │ ├── ArcAim.html │ │ │ │ ├── BasicMath.html │ │ │ │ ├── BezierUtils.html │ │ │ │ ├── CGPathUtils.html │ │ │ │ ├── Ease.html │ │ │ │ ├── PDFImageView.html │ │ │ │ ├── PathAim.html │ │ │ │ ├── RotationAim.html │ │ │ │ ├── StringAim.html │ │ │ │ ├── Timeline.html │ │ │ │ ├── TimelineInspector.html │ │ │ │ ├── Tween.html │ │ │ │ ├── TweenBlock.html │ │ │ │ └── TweenVisualizer.html │ │ │ ├── Enums.html │ │ │ ├── Enums │ │ │ │ ├── TimelinePlayMode.html │ │ │ │ └── TransitionType.html │ │ │ ├── Extensions.html │ │ │ ├── Extensions │ │ │ │ ├── NSBezierPath.html │ │ │ │ ├── NSColor.html │ │ │ │ ├── NSView.html │ │ │ │ └── UIView.html │ │ │ ├── Functions.html │ │ │ ├── Global Variables.html │ │ │ ├── Typealiases.html │ │ │ ├── badge.svg │ │ │ ├── css │ │ │ │ ├── highlight.css │ │ │ │ └── jazzy.css │ │ │ ├── img │ │ │ │ ├── carat.png │ │ │ │ ├── dash.png │ │ │ │ ├── gh.png │ │ │ │ └── spinner.gif │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── jazzy.js │ │ │ │ ├── jazzy.search.js │ │ │ │ ├── jquery.min.js │ │ │ │ ├── lunr.min.js │ │ │ │ └── typeahead.jquery.js │ │ │ ├── search.json │ │ │ └── undocumented.json │ │ │ └── docSet.dsidx │ └── Tweener.tgz ├── img │ ├── carat.png │ ├── dash.png │ ├── gh.png │ └── spinner.gif ├── index.html ├── js │ ├── jazzy.js │ ├── jazzy.search.js │ ├── jquery.min.js │ ├── lunr.min.js │ └── typeahead.jquery.js ├── search.json └── undocumented.json └── index.html /.build/manifest.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/.build/manifest.db -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | 33 | Pods/ 34 | 35 | 36 | # Add this line if you want to avoid checking in source code from the Xcode workspace 37 | # *.xcworkspace 38 | 39 | # Carthage 40 | 41 | Carthage/Build 42 | Carthage/Checkouts 43 | 44 | # fastlane 45 | 46 | fastlane/report.xml 47 | fastlane/Preview.html 48 | fastlane/screenshots/**/*.png 49 | fastlane/test_output 50 | 51 | # Code Injection 52 | # 53 | # After new code Injection tools there's a generated folder /iOSInjectionProject 54 | # https://github.com/johnno1962/injectionforxcode 55 | 56 | iOSInjectionProject/ 57 | 58 | # Apple macOs garbage 59 | .DS_Store -------------------------------------------------------------------------------- /Examples/Examples-macOs.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Examples-macOs.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Examples-macOs.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Examples-macOs.xcodeproj/xcshareddata/xcschemes/Examples-macOs.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Examples-macOs 4 | // 5 | // Created by Alejandro Ramirez Varela on 14/08/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | import Tweener 11 | 12 | //@NSApplicationMain 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | 15 | var window:NSWindow! 16 | let mainView = NSView() 17 | var statusBarItem: NSStatusItem! 18 | var samplesMenu:NSMenu! 19 | 20 | var sampleIndex:Int = 0 { 21 | willSet{ 22 | samplesMenu?.item(at: sampleIndex)?.state = .off 23 | } 24 | didSet{ 25 | samplesMenu?.item(at: sampleIndex)?.state = .on 26 | Tween(target:self.mainView) 27 | .duration(0.75) 28 | .ease(.outQuad) 29 | .to(.key(\.frame, CGRect(x: -window.frame.size.width * CGFloat( sampleIndex ), 30 | y: mainView.frame.origin.y, 31 | width: mainView.frame.size.width, 32 | height: mainView.frame.size.height))) 33 | .play() 34 | } 35 | } 36 | 37 | func applicationDidFinishLaunching(_ aNotification: Notification) { 38 | 39 | //Sizing 40 | let windowSize = NSSize(width: 1024, height: 600) 41 | let screenSize = NSScreen.main?.frame.size ?? .zero 42 | let windowRect = NSMakeRect(screenSize.width/2 - windowSize.width/2, 43 | screenSize.height/2 - windowSize.height/2, 44 | windowSize.width, 45 | windowSize.height) 46 | //Create a window 47 | window = NSWindow(contentRect: windowRect, 48 | styleMask: [.miniaturizable, .closable, .titled], 49 | backing: .buffered, 50 | defer: false) 51 | window.contentView = mainView 52 | window.makeKeyAndOrderFront(nil) 53 | 54 | //Create Main view 55 | mainView.wantsLayer = true 56 | mainView.frame.size = windowSize 57 | mainView.layer?.backgroundColor = .white 58 | 59 | //Build Main Menu 60 | createMenu() 61 | 62 | //Add Samples and add to Menu. 63 | mainView.addSubview( ViewExtensionSample(frame: mainView.bounds) ) 64 | samplesMenu.insertItem(NSMenuItem( title: "NSview extensions", action: #selector(selectSample), keyEquivalent: "1"), at: 0) 65 | samplesMenu?.item(at: 0)?.state = .on 66 | 67 | mainView.addSubview( SimpleTween(frame: mainView.bounds) ) 68 | samplesMenu.insertItem(NSMenuItem( title: "Simple Tween", action: #selector(selectSample), keyEquivalent: "2"), at: 1) 69 | 70 | mainView.addSubview( PathSample(frame: mainView.bounds) ) 71 | samplesMenu.insertItem(NSMenuItem( title: "Path Tweens", action: #selector(selectSample), keyEquivalent: "3"), at: 2) 72 | 73 | mainView.addSubview( Random(frame: mainView.bounds) ) 74 | samplesMenu.insertItem(NSMenuItem( title: "Random Tweens", action: #selector(selectSample), keyEquivalent: "4"), at: 3) 75 | 76 | mainView.addSubview( TimelineSample(frame: mainView.bounds) ) 77 | samplesMenu.insertItem(NSMenuItem( title: "Timeline", action: #selector(selectSample), keyEquivalent: "5"), at: 4) 78 | 79 | mainView.addSubview( TweenChain(frame: mainView.bounds) ) 80 | samplesMenu.insertItem(NSMenuItem( title: "Tween chain", action: #selector(selectSample), keyEquivalent: "6"), at: 5) 81 | 82 | mainView.addSubview( StringSample(frame: mainView.bounds) ) 83 | samplesMenu.insertItem(NSMenuItem( title: "Animate Text", action: #selector(selectSample), keyEquivalent: "7"), at: 6) 84 | 85 | //Align samples 86 | var x:CGFloat = 0.0 87 | for view in mainView.subviews{ 88 | view.frame.origin.x = x 89 | x = x + mainView.bounds.size.width 90 | } 91 | 92 | //Update main view width. 93 | mainView.frame.size.width = x 94 | } 95 | 96 | @objc func selectSample(_ sender: NSMenuItem){ 97 | //Get selected index 98 | if let selectedIndex:Int = samplesMenu.items.firstIndex(of: sender){ 99 | sampleIndex = selectedIndex 100 | } 101 | } 102 | 103 | func createMenu(){ 104 | 105 | //Create Root item 106 | let rootMenuItem = NSMenuItem( title: "Examples Menu", action: nil, keyEquivalent: "" ) 107 | NSApplication.shared.mainMenu?.addItem(rootMenuItem) 108 | 109 | //Create root menu 110 | samplesMenu = NSMenu(title: "Examples") 111 | NSApplication.shared.mainMenu?.setSubmenu(samplesMenu, for: rootMenuItem) 112 | 113 | //Add github launch 114 | samplesMenu.addItem(NSMenuItem.separator()) 115 | let gitHubMenuItem = NSMenuItem( title: "View on GitHub", action: #selector(visitGithubSite), keyEquivalent: "") 116 | samplesMenu.addItem(gitHubMenuItem) 117 | 118 | //Add quit option 119 | samplesMenu.addItem(NSMenuItem.separator()) 120 | let quitMenuItem = NSMenuItem( title: "Quit Examples", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") 121 | samplesMenu.addItem(quitMenuItem) 122 | } 123 | 124 | @objc func visitGithubSite(){ 125 | if let url = URL(string: "https://github.com/alexrvarela/SwiftTweener/"){ 126 | NSWorkspace.shared.open(url) 127 | } 128 | } 129 | 130 | func applicationDidBecomeActive(_ notification: Notification) { 131 | NSMenu.setMenuBarVisible(true) 132 | } 133 | 134 | func applicationWillTerminate(_ aNotification: Notification) { 135 | // Insert code here to tear down your application 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon-16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon-32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon-32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon-128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon-128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon-256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon-256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon-512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon-512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-128.png -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-128@2x.png -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-16.png -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-16@2x.png -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-256.png -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-256@2x.png -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-32.png -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-32@2x.png -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-512.png -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples-macOs/Assets.xcassets/AppIcon.appiconset/icon-512@2x.png -------------------------------------------------------------------------------- /Examples/Examples-macOs/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/Examples_macOs.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 27 | NSPrincipalClass 28 | NSApplication 29 | NSSupportsAutomaticTermination 30 | 31 | NSSupportsSuddenTermination 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/Random.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Random.swift 3 | // Examples-macOs 4 | // 5 | // Created by Alejandro Ramirez Varela on 10/09/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | import Tweener 11 | 12 | class Random: NSView { 13 | 14 | var button:NSButton! 15 | var dots:[NSView] = [] 16 | 17 | override init(frame: CGRect) { 18 | 19 | super.init(frame: frame) 20 | 21 | for _ in 1...100 { 22 | let size = CGFloat.random(in: 25.0 ... 200) 23 | let dot = NSView(frame: CGRect(x:CGFloat.random(in: 0.0 ... frame.size.width - size), 24 | y:CGFloat.random(in: 0.0 ... frame.size.height - size), 25 | width:size, 26 | height:size)) 27 | dot.wantsLayer = true 28 | dot.layer?.backgroundColor = NSColor.init(hue: CGFloat.random(in: 0 ..< 1.0), saturation: 1.0, brightness: 1.0, alpha: 1.0).cgColor 29 | dot.layer!.cornerRadius = size / 2 30 | dots.append(dot) 31 | addSubview(dot) 32 | } 33 | 34 | //Create a button 35 | button = NSButton(frame: CGRect(x:( frame.size.width - 200 ) / 2.0 , 36 | y:20.0, 37 | width:200, 38 | height:50.0)) 39 | button.wantsLayer = true 40 | button.title = "ANIMATE" 41 | button.target = self 42 | button.action = #selector(animate) 43 | button.layer?.backgroundColor = .black 44 | button.layer?.cornerRadius = 7.0 45 | addSubview(button) 46 | } 47 | 48 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 49 | 50 | @objc func animate() { 51 | for dot in dots { 52 | Tween(dot) 53 | .ease(.outBack) 54 | .duration(Double.random(in: 0.25 ... 2.0)) 55 | .delay(Double.random(in: 0.25 ... 2.0)) 56 | .to(.key(\.frame, CGRect(x: CGFloat.random(in: 0.0 ... frame.size.width - dot.frame.size.width), 57 | y:CGFloat.random(in: 0.0 ... frame.size.height - dot.frame.size.height), 58 | width:dot.frame.size.width, 59 | height:dot.frame.size.height))) 60 | .play() 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/SimpleTween.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleTween.swift 3 | // Examples-macOs 4 | // 5 | // Created by Alejandro Ramirez Varela on 21/08/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Tweener 11 | 12 | class SimpleTween: NSView { 13 | 14 | var square:NSView! 15 | var button:NSButton = { 16 | //Create a button 17 | let btn = NSButton() 18 | btn.wantsLayer = true 19 | btn.title = "ADD TWEEN" 20 | btn.layer?.backgroundColor = .black 21 | btn.layer?.cornerRadius = 7.0 22 | return btn 23 | }() 24 | 25 | override init(frame: CGRect) { 26 | 27 | super.init(frame: frame) 28 | button.frame = CGRect(x:( frame.size.width - 200 ) / 2.0 , 29 | y:20.0, 30 | width:200, 31 | height:50.0) 32 | button.target = self 33 | button.action = #selector(addTween) 34 | addSubview(button) 35 | 36 | //Create a target 37 | square = NSView(frame:CGRect(x:20.0, y:self.frame.size.height - 100 - 20.0, width:100.0, height:100.0)) 38 | square.wantsLayer = true 39 | square.layer?.backgroundColor = .init(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0) 40 | square.layer?.cornerRadius = 10.0 41 | addSubview(square) 42 | 43 | 44 | } 45 | 46 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 47 | 48 | @objc func addTween() { 49 | 50 | Tween(target:square)//Target 51 | .duration(0.75)//Duration in seconds 52 | .ease(.inOutCubic)//Animation curve 53 | .from( 54 | .key(\.alphaValue, 0.25), 55 | .key(\.frame, CGRect(x:20.0, 56 | y:frame.size.height - 100 - 20.0, 57 | width:100.0, 58 | height:100.0)) 59 | ) 60 | .to( 61 | .key(\.alphaValue, 1.0), 62 | .key(\.frame,CGRect(x: frame.size.width - 280.0 - 20.0, 63 | y:frame.size.height - 280.0 - 20.0, 64 | width:280.0, 65 | height:280.0)) 66 | ) 67 | .onComplete { print("Tween 1 complete") } 68 | .after()//Creates a new tween after with same target and properties. 69 | .duration(1.0) 70 | .ease(.outBounce) 71 | .to(.key(\.alphaValue, 0.25), .key(\.frame,CGRect(x:20.0, y:frame.size.height - 100 - 20.0, width:100.0, height:100.0))) 72 | .onComplete { print("Tween 2 complete") } 73 | .play() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/StringSample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringSample.swift 3 | // Examples-macOs 4 | // 5 | // Created by Alejandro Ramirez Varela on 14/09/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Tweener 11 | 12 | class StringSample: NSView { 13 | 14 | let textView:NSTextView = { 15 | let txtView = NSTextView() 16 | txtView.font = NSFont.userFont(ofSize: 150) 17 | txtView.backgroundColor = .clear 18 | txtView.textColor = .white 19 | txtView.isEditable = false 20 | txtView.isSelectable = false 21 | return txtView 22 | }() 23 | 24 | let stringAim = StringAim() 25 | 26 | let words:Array = ["Hello", "Hola", "Bonjour", "Ciao", "Olá", "Hallo", "Ohayo", "Konnichiwa", "Ni hau", "Hej", "Guten tag", "Namaste", "Salaam", "Merhaba", "Szia"] 27 | 28 | override init(frame: CGRect) { 29 | super.init(frame: frame) 30 | 31 | self.wantsLayer = true 32 | self.layer!.backgroundColor = NSColor.magenta.cgColor 33 | addSubview(textView) 34 | 35 | //Set initial string 36 | let randomText = getRandomText() 37 | 38 | //Set initial text 39 | textView.string = randomText 40 | //Fits to text size 41 | textView.sizeToFit() 42 | //Set full width 43 | textView.frame.size.width = frame.size.width 44 | //Center 45 | textView.center(CGPoint(x:frame.size.width * 0.5, y:frame.size.height * 0.5)) 46 | 47 | //Link StringAim to target and 'String' KeyPath 48 | stringAim.bind(target:textView, keyPath: \NSTextView.string) 49 | //Set initial text 50 | stringAim.from = randomText 51 | //Set 'to 'text 52 | stringAim.to = changeText(oldText:randomText) 53 | 54 | //Start animation loop 55 | swapText() 56 | 57 | //Use timeline to repeat forever. 58 | Timeline( 59 | //Create tween with StringAim target and animate interpolation. 60 | Tween(target:stringAim) 61 | .delay(0.5) 62 | .duration(0.5) 63 | .ease(.none) 64 | .to(.key(\.interpolation, 1.0)) 65 | .onComplete { self.swapText() } 66 | ) 67 | .mode(.loop) 68 | .play() 69 | } 70 | 71 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 72 | 73 | func swapText() 74 | { 75 | stringAim.from = stringAim.to 76 | stringAim.to = changeText(oldText:stringAim.to) 77 | stringAim.interpolation = 0.0 78 | } 79 | 80 | func changeText(oldText:String) -> String 81 | { 82 | var newString = oldText 83 | 84 | while newString == oldText 85 | { 86 | newString = getRandomText() 87 | } 88 | 89 | return newString 90 | } 91 | 92 | func getRandomText() -> String 93 | { 94 | let rand = Int.random(in:0...words.count - 1) 95 | return words[rand] 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/TimelineSample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineSample.swift 3 | // Examples-macOs 4 | // 5 | // Created by Alejandro Ramirez Varela on 14/09/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Tweener 11 | 12 | //Rounded view 13 | extension NSView { 14 | static func rounded(_ radius:CGFloat = 10, size:CGSize = CGSize(width:100.0, height:100.0)) -> NSView { 15 | let view = NSView(frame:CGRect(x:0.0, y:0.0, width:size.width, height:size.height)) 16 | view.wantsLayer = true 17 | view.layer!.cornerRadius = radius 18 | return view 19 | } 20 | } 21 | 22 | //Generate randomColor 23 | extension NSColor{ 24 | public static func random() -> NSColor{ 25 | return NSColor(hue: CGFloat.random(in: 0 ..< 1.0), saturation: 1.0, brightness: 1.0, alpha: 1.0) 26 | } 27 | } 28 | 29 | class TimelineSample: NSView { 30 | 31 | let square:NSView = { 32 | let view = NSView.rounded(10, size:CGSize(width: 250, height: 250)) 33 | view.layer!.backgroundColor = NSColor.random().cgColor 34 | view.layer!.opacity = 0.5 35 | return view 36 | }() 37 | 38 | var button:NSButton = { 39 | //Create a button 40 | let btn = NSButton() 41 | //Create a backed layer 42 | btn.wantsLayer = true 43 | btn.title = "PLAY TIMELINE" 44 | btn.layer?.backgroundColor = .black 45 | btn.layer?.cornerRadius = 7.0 46 | return btn 47 | }() 48 | 49 | //Retain timeline 50 | let timeline = Timeline() 51 | 52 | override init(frame: CGRect) { 53 | super.init(frame: frame) 54 | 55 | //Add square 56 | addSubview( square ) 57 | square.center(center()) 58 | 59 | //Add button 60 | button.frame = CGRect(x:( frame.size.width - 200 ) / 2.0 , 61 | y:20.0, 62 | width:200, 63 | height:50.0) 64 | button.target = self 65 | button.action = #selector(playTimeline) 66 | addSubview(button) 67 | 68 | //Add to timeline using declarative syntax: 69 | timeline.add( 70 | //To prevent play imediatelly call remove() 71 | square.flipX(inverted: true).stop(), 72 | square.flipY().stop().delay(1.0), 73 | square.flipX().stop().delay(2.0), 74 | square.flipY(inverted: true).stop().delay(3.0) 75 | ).mode( .loop ) 76 | } 77 | 78 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 79 | 80 | @objc func playTimeline(){ 81 | 82 | square.translateLayerAnchor(CGPoint(x:0.5, y:0.5)) 83 | 84 | if timeline.isPlaying(){ timeline.pause() } else { timeline.play() } 85 | button.title = timeline.isPlaying() ? "PAUSE TIMELINE" : "PLAY TIMELINE" 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/TweenChain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweenChain.swift 3 | // Examples-macOs 4 | // 5 | // Created by Alejandro Ramirez Varela on 14/09/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Tweener 11 | 12 | class TweenChain: NSView { 13 | 14 | var squares:[NSView] = [NSView.rounded(), NSView.rounded(), NSView.rounded(), NSView.rounded()] 15 | 16 | let square:NSView = { 17 | let view = NSView.rounded(10) 18 | view.layer!.backgroundColor = NSColor.random().cgColor 19 | view.layer!.opacity = 0.5 20 | return view 21 | }() 22 | 23 | var button:NSButton = { 24 | //Create a button 25 | let btn = NSButton() 26 | btn.wantsLayer = true 27 | btn.title = "PLAY" 28 | btn.layer?.backgroundColor = .black 29 | btn.layer?.cornerRadius = 7.0 30 | return btn 31 | }() 32 | 33 | //Retain timeline 34 | let timeline = Timeline() 35 | 36 | override init(frame: CGRect) { 37 | super.init(frame: frame) 38 | 39 | //Add background squares 40 | for sqr in squares{ 41 | sqr.layer!.opacity = 0.05 42 | sqr.layer!.backgroundColor = NSColor.black.cgColor 43 | sqr.layer!.transform = CATransform3DMakeTranslation(0.0, 0.0, -100) 44 | addSubview(sqr) 45 | } 46 | 47 | //Align background squares 48 | squares[0].center(CGPoint(x: center().x - 60.0, y: center().y + 60.0)) 49 | squares[1].center(CGPoint(x: center().x + 60.0, y: center().y + 60.0)) 50 | squares[2].center(CGPoint(x: center().x + 60.0, y: center().y - 60.0)) 51 | squares[3].center(CGPoint(x: center().x - 60.0, y: center().y - 60.0)) 52 | 53 | //Add square 54 | let spacing = square.frame.size.width / 2.0 + 20.0 55 | square.center(CGPoint(x: spacing, y: spacing)) 56 | square.layerContentsRedrawPolicy = .onSetNeedsDisplay 57 | addSubview( square ) 58 | 59 | //Set initial position 60 | square.center(squares[0].center()) 61 | 62 | //Add button 63 | button.frame = CGRect(x:( frame.size.width - 200 ) / 2.0 , 64 | y:20.0, 65 | width:200, 66 | height:50.0) 67 | button.target = self 68 | button.action = #selector(playTimeline) 69 | addSubview(button) 70 | 71 | //Add tween chain to timeline: 72 | timeline.add( 73 | 74 | //Tween 1, start chain 75 | Tween(target: square) 76 | .duration(0.5) 77 | .ease(.inOutQuad) 78 | .to(.key(\.frame, squares[1].frame)) 79 | .onComplete{self.square.layer!.backgroundColor = NSColor.random().cgColor } 80 | //Tween 2, create a tween after. 81 | .after() 82 | .to(.key(\.frame, squares[2].frame)) 83 | .onComplete{self.square.layer!.backgroundColor = NSColor.random().cgColor } 84 | //Tween 3, create a tween after. 85 | .after() 86 | .to(.key(\.frame, squares[3].frame)) 87 | .onComplete{self.square.layer!.backgroundColor = NSColor.random().cgColor } 88 | //Tween 4, create a tween after. 89 | .after() 90 | .to(.key(\.frame, squares[0].frame)) 91 | .onComplete{self.square.layer!.backgroundColor = NSColor.random().cgColor } 92 | 93 | ).mode( .loop ) 94 | 95 | } 96 | 97 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 98 | 99 | @objc func playTimeline(){ 100 | 101 | if timeline.isPlaying(){ timeline.pause() } else { timeline.play() } 102 | button.title = timeline.isPlaying() ? "PAUSE" : "PLAY" 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/ViewExtensionSample.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewExtensionSample.swift 3 | // Examples-macOs 4 | // 5 | // Created by Alejandro Ramirez Varela on 24/08/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class ViewExtensionSample: NSView { 12 | 13 | let spacing:CGFloat = 30 14 | let titles:[String] = [".spring()", 15 | ".zoomIn()", 16 | ".zoomOut()", 17 | ".pop()", 18 | ".fadeIn()", 19 | ".fadeOut()", 20 | ".flyLeft()", 21 | ".flyRight()", 22 | ".flyTop()", 23 | ".flyBottom()", 24 | ".slideLeft()", 25 | ".slideRight()", 26 | ".slideTop()", 27 | ".slideBottom()", 28 | ".flipX()", 29 | ".flipY()", 30 | ".shake()", 31 | ".jiggle()", 32 | ".bounce()", 33 | ".swing()", 34 | ".spin()", 35 | ".spin(clockwise:false)", 36 | ".loop()"] 37 | 38 | var buttons:[NSButton] = [] 39 | var looping = false 40 | var playAllButton = NSButton() 41 | 42 | override init(frame: CGRect) { 43 | 44 | let cols = 6 45 | let rows = 4 46 | let size = CGSize(width: (frame.size.width - spacing) / CGFloat( cols ) - spacing, 47 | height: (frame.size.height - spacing) / CGFloat( rows ) - spacing) 48 | 49 | super.init(frame: frame) 50 | //Create a target 51 | 52 | var x = 0 53 | var y = 0 54 | let c:CGFloat = 0.5 / CGFloat( titles.count ) 55 | 56 | //Create a grid 57 | for i in 0 ..< titles.count { 58 | let button = NSButton(frame:CGRect(x:spacing + (size.width + spacing) * CGFloat( x ), 59 | y:frame.size.height - ( (size.height + spacing) * CGFloat( y ) ) - (size.height + spacing), 60 | width:size.width, 61 | height:size.height)) 62 | button.title = titles[i] 63 | button.wantsLayer = true 64 | button.layer?.backgroundColor = NSColor.init(hue: 0.5 + c * CGFloat(i), saturation: 1.0, brightness: 1.0, alpha: 1.0).cgColor 65 | button.layer?.cornerRadius = 10.0 66 | button.target = self 67 | button.action = #selector(click) 68 | button.isBordered = false 69 | buttons.append(button) 70 | addSubview(button) 71 | 72 | x += 1 73 | if x >= cols { x = 0; y += 1 } 74 | } 75 | 76 | //Try all! 77 | playAllButton.frame = CGRect(x:spacing + (size.width + spacing) * CGFloat( x ), 78 | y:frame.size.height - ( (size.height + spacing) * CGFloat( y ) ) - (size.height + spacing), 79 | width:size.width, 80 | height:size.height) 81 | playAllButton.title = "Play all!" 82 | playAllButton.wantsLayer = true 83 | playAllButton.layer!.backgroundColor = .black 84 | playAllButton.layer?.cornerRadius = 10.0 85 | playAllButton.target = self 86 | playAllButton.action = #selector(play) 87 | addSubview(playAllButton) 88 | 89 | //DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {self.playAll() } 90 | 91 | } 92 | 93 | @objc func click(_ sender: NSButton){ 94 | 95 | switch buttons.firstIndex(of: sender) { 96 | case 0: 97 | sender.spring() 98 | case 1: 99 | sender.zoomIn() 100 | sender.fadeIn() 101 | case 2: 102 | sender.zoomOut() 103 | sender.fadeIn() 104 | case 3: 105 | sender.pop() 106 | case 4: 107 | sender.fadeIn() 108 | case 5: 109 | sender.fadeOut() 110 | //Show again! 111 | .after() 112 | .delay(1.0) 113 | .duration(0.15) 114 | .to(.key(\.opacity, 1.0)) 115 | .play() 116 | case 6: 117 | sender.flyLeft() 118 | sender.fadeIn() 119 | case 7: 120 | sender.flyRight() 121 | sender.fadeIn() 122 | case 8: 123 | sender.flyTop() 124 | sender.fadeIn() 125 | case 9: 126 | sender.flyBottom() 127 | sender.fadeIn() 128 | case 10: 129 | sender.slideLeft() 130 | sender.fadeIn() 131 | case 11: 132 | sender.slideRight() 133 | sender.fadeIn() 134 | case 12: 135 | sender.slideTop() 136 | sender.fadeIn() 137 | case 13: 138 | sender.slideBottom() 139 | sender.fadeIn() 140 | case 14: 141 | sender.flipX() 142 | .onComplete { sender.layer!.transform = CATransform3DIdentity } 143 | case 15: 144 | sender.flipY() 145 | .onComplete { sender.layer!.transform = CATransform3DIdentity } 146 | case 16: 147 | sender.shake() 148 | case 17: 149 | sender.jiggle() 150 | case 18: 151 | sender.bounce() 152 | case 19: 153 | sender.swing() 154 | case 20: 155 | sender.spin() 156 | case 21: 157 | sender.spin(clockwise:false) 158 | case 22: 159 | if looping { sender.stopLoop()} else { sender.loop()} 160 | looping = !looping 161 | sender.title = looping ? ".stopLoop()" : ".loop()" 162 | default: 163 | break 164 | } 165 | } 166 | 167 | @objc func play(_ sender: NSButton){ playAll() } 168 | 169 | func playAll(){ 170 | for (index, _) in buttons.enumerated() { 171 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.25 * Double( index )) { self.click( self.buttons[ index ] ) } 172 | } 173 | } 174 | 175 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 176 | } 177 | -------------------------------------------------------------------------------- /Examples/Examples-macOs/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // Examples-macOs 4 | // 5 | // Created by Alejandro Ramirez Varela on 21/08/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import AppKit 10 | 11 | let delegate = AppDelegate() 12 | NSApplication.shared.delegate = delegate 13 | NSApplication.shared.mainMenu = NSMenu(title: "Main Menu") 14 | _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) 15 | -------------------------------------------------------------------------------- /Examples/Examples.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/Examples.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Examples/Examples/AnimateText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimateText.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/29/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class AnimateText:UIView, FreezeProtocol 13 | { 14 | let label:UILabel = UILabel() 15 | let words:Array = ["Hello", "Hola", "Bonjour", "Ciao", "Olá", "Hallo", "Ohayo", "Konnichiwa", "Ni hau", "Hej", "Guten tag", "Namaste", "Salaam", "Merhaba", "Szia"] 16 | let aim:StringAim = StringAim() 17 | var frozen = false 18 | 19 | override init(frame: CGRect) 20 | { 21 | super.init(frame: frame) 22 | let purpleColor = UIColor(red:65.0/255.0, 23 | green:50.0/255.0, 24 | blue:160.0/255, 25 | alpha:1.0) 26 | 27 | backgroundColor = purpleColor 28 | 29 | //Set initial string 30 | let randomText = getRandomText() 31 | 32 | //Set initial strings 33 | label.frame = CGRect(x:20.0, 34 | y:center.y - 60.0, 35 | width:self.frame.size.width - 40.0, 36 | height:60.0) 37 | label.font = UIFont.systemFont(ofSize: 58.0, weight: .light) 38 | label.textColor = UIColor.white 39 | label.text = randomText 40 | addSubview(label) 41 | 42 | aim.from = randomText 43 | aim.to = changeText(oldText:randomText) 44 | aim.bind(target: label, keyPath: \UILabel.text!) 45 | // aim.target = label 46 | 47 | //Create buttons 48 | let buttonLabels = ["lenght", "linear", "random"] 49 | let buttonSelectors = [#selector(lenght), #selector(linear), #selector(random)] 50 | let w:CGFloat = (self.frame.size.width - 20.0 * CGFloat(buttonLabels.count + 1) ) / CGFloat(buttonLabels.count) 51 | 52 | for (index, label) in buttonLabels.enumerated() 53 | { 54 | let x = (20.0 + w) * CGFloat(index) 55 | 56 | let button = UIButton(frame: CGRect(x:20.0 + x, 57 | y:self.frame.size.height - 70.0, 58 | width:w, 59 | height:50.0)) 60 | addSubview(button) 61 | button.setTitle(label, for:.normal) 62 | button.addTarget(self, action:buttonSelectors[index], for:.touchUpInside) 63 | button.setTitleColor(purpleColor, for:.normal) 64 | button.backgroundColor = UIColor.white 65 | button.layer.cornerRadius = 7.0 66 | addSubview(button) 67 | } 68 | 69 | //Start animating 70 | swapText() 71 | 72 | //Freeze 73 | freeze() 74 | } 75 | 76 | required init?(coder aDecoder: NSCoder) { 77 | fatalError("init(coder:) has not been implemented") 78 | } 79 | 80 | func swapText() 81 | { 82 | if frozen { return } 83 | 84 | aim.from = aim.to 85 | aim.to = changeText(oldText:aim.to) 86 | aim.interpolation = 0.0 87 | 88 | Tweener.removeTweens(target: aim) 89 | 90 | Tween(target: aim, 91 | duration: 0.5, 92 | ease: Ease.none, 93 | delay: 0.0, 94 | to: [.key(\.interpolation, 1.0)], 95 | completion: { 96 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { 97 | self.swapText() 98 | }) 99 | }).play() 100 | } 101 | 102 | func changeText(oldText:String) -> String 103 | { 104 | var newString = oldText 105 | 106 | while newString == oldText 107 | { 108 | newString = getRandomText() 109 | } 110 | 111 | return newString 112 | } 113 | 114 | func getRandomText() -> String 115 | { 116 | let rand = Int.random(in:0...words.count - 1) 117 | return words[rand] 118 | } 119 | 120 | @objc func lenght(){aim.transitionType = .lenght} 121 | @objc func linear(){aim.transitionType = .linear} 122 | @objc func random(){aim.transitionType = .random} 123 | 124 | func freeze() 125 | { 126 | Tweener.removeTweens(target: aim) 127 | frozen = true 128 | } 129 | 130 | func warm() 131 | { 132 | frozen = false 133 | swapText() 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Examples/Examples/ArcOrbits.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArcOrbits.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/29/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class ArcOrbits:UIView, FreezeProtocol 13 | { 14 | let background:PDFImageView = PDFImageView(bundlename: "orbits-background") 15 | let sunFire:PDFImageView = PDFImageView(bundlename: "little-sun-fire") 16 | let fireAim:RotationAim = RotationAim() 17 | let sun:PDFImageView = PDFImageView(bundlename:"little-sun") 18 | let earth:PDFImageView = PDFImageView(bundlename:"little-earth") 19 | let earthAim:ArcAim = ArcAim() 20 | let moon:UIView = UIView() 21 | let moonAim:ArcAim = ArcAim() 22 | let mars:UIView = UIView() 23 | let marsAim:ArcAim = ArcAim() 24 | let jupyter:PDFImageView = PDFImageView(bundlename: "little-jupyter") 25 | let jupyterAim:ArcAim = ArcAim() 26 | let saturn:PDFImageView = PDFImageView(bundlename: "little-saturn") 27 | let saturnAim:ArcAim = ArcAim() 28 | var timelines:Array = [] 29 | 30 | override init(frame: CGRect){ 31 | super.init(frame: frame) 32 | 33 | backgroundColor = UIColor(red:52.0/255, 34 | green:52.0/255, 35 | blue:71.0/255, 36 | alpha:1.0) 37 | clipsToBounds = true 38 | 39 | addSubview(background) 40 | background.center = center 41 | 42 | addSubview(sunFire) 43 | sunFire.center = center 44 | fireAim.target = sunFire 45 | 46 | //fire timeline 47 | let timeline = Timeline() 48 | timeline.playMode = .loop 49 | timeline.add(Tween(target: fireAim, 50 | duration: 5.0, 51 | to: [.key(\.angle, 360.0)])) 52 | timeline.play() 53 | timelines.append(timeline) 54 | 55 | //sun 56 | addSubview(sun) 57 | sun.center = center 58 | 59 | //earth 60 | addSubview(earth) 61 | //earth aim 62 | earthAim.target = earth 63 | earthAim.radius = 100 64 | earthAim.center = center 65 | //earth timeline 66 | addTimeline(target:earthAim, duration:5.0) 67 | 68 | //moon 69 | moon.frame = CGRect(x:0.0, y:0.0, width:3.0, height:3.0) 70 | moon.backgroundColor = UIColor.white 71 | moon.layer.cornerRadius = 1.5 72 | earth.addSubview(moon) 73 | //moon aim 74 | moonAim.target = moon 75 | moonAim.radius = 11 76 | moonAim.center = CGPoint(x:earth.frame.size.width / 2.0, y:earth.frame.size.height / 2.0) 77 | //moon timeline 78 | addTimeline(target:moonAim, duration:1.0) 79 | 80 | //mars 81 | mars.frame = CGRect(x:0.0, y:0.0, width:10.0, height:10.0) 82 | mars.backgroundColor = UIColor(red:200.0/255.0, green:90.0/255.0, blue:60.0/255.0, alpha:1.0) 83 | mars.layer.cornerRadius = 5.0 84 | addSubview(mars) 85 | //mars aim 86 | marsAim.target = mars 87 | marsAim.radius = 150.0 88 | marsAim.center = center 89 | //mars timeline 90 | addTimeline(target:marsAim, duration:7.0) 91 | 92 | //jupyter 93 | addSubview(jupyter) 94 | //jupyter aim 95 | jupyterAim.target = jupyter 96 | jupyterAim.radius = 200 97 | jupyterAim.center = center 98 | //jupyter timeline 99 | addTimeline(target:jupyterAim, duration:9.0) 100 | 101 | //saturn 102 | addSubview(saturn) 103 | //saturn aim 104 | saturnAim.target = saturn 105 | saturnAim.radius = 250 106 | saturnAim.center = center 107 | //saturn timeline 108 | addTimeline(target:saturnAim, duration:11.0) 109 | 110 | //Freeze 111 | freeze() 112 | } 113 | 114 | required init?(coder aDecoder: NSCoder) { 115 | fatalError("init(coder:) has not been implemented") 116 | } 117 | 118 | func addTimeline(target:ArcAim, duration:Double) 119 | { 120 | let timeline = Timeline() 121 | timeline.playMode = .loop 122 | timeline.play() 123 | timeline.add(Tween(target: target, duration: duration, to: [.key(\.arcAngle, 360.0)])) 124 | timelines.append(timeline) 125 | } 126 | 127 | func freeze() 128 | { 129 | for timeline in timelines { timeline.pause() } 130 | } 131 | 132 | func warm() 133 | { 134 | for timeline in timelines { timeline.play() } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x-1.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x-1.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x-1.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x-1.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "iTunesArtwork@2x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x-1.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /Examples/Examples/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/Examples/Base.lproj/LaunchScreen.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 | 42 | -------------------------------------------------------------------------------- /Examples/Examples/ChainableTweens.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChainableTweens.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 12/08/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | extension CAShapeLayer { 13 | static func rectangle(roundedRect: CGRect, cornorRadius: CGFloat, color: UIColor) -> CAShapeLayer { 14 | let path = UIBezierPath(roundedRect: roundedRect, cornerRadius: cornorRadius) 15 | let shape = CAShapeLayer() 16 | shape.path = path.cgPath 17 | shape.fillColor = color.cgColor 18 | return shape 19 | } 20 | } 21 | 22 | //Rounded view 23 | extension UIView { 24 | static func rounded(_ radius:CGFloat = 10) -> UIView { 25 | let view = UIView(frame:CGRect(x:0.0, y:0.0, width:100.0, height:100.0)) 26 | view .layer.cornerRadius = radius 27 | return view 28 | } 29 | } 30 | 31 | //Generate randomColor 32 | extension UIColor{ 33 | public static func random() -> UIColor{ 34 | return UIColor(hue: CGFloat.random(in: 0 ..< 1.0), saturation: 1.0, brightness: 1.0, alpha: 1.0) 35 | } 36 | } 37 | 38 | class ChainableTweens: UIView, FreezeProtocol { 39 | 40 | var squares:[UIView] = [UIView.rounded(), UIView.rounded(), UIView.rounded(), UIView.rounded()] 41 | 42 | let square:UIView = { 43 | let view = UIView.rounded(10) 44 | view.backgroundColor = UIColor.random() 45 | view.alpha = 0.5 46 | return view 47 | }() 48 | 49 | let button:UIButton = { 50 | let button = UIButton() 51 | button.setTitle("PLAY TIMELINE", for: .normal) 52 | button.setTitleColor(UIColor.white, for: .normal) 53 | button.backgroundColor = .black 54 | button.layer.cornerRadius = 7.0 55 | return button 56 | }() 57 | 58 | //Retain timeline 59 | let timeline = Timeline() 60 | 61 | override init(frame: CGRect) { 62 | super.init(frame: frame) 63 | 64 | //Add background squares 65 | for sqr in squares{ 66 | sqr.backgroundColor = .black 67 | sqr.alpha = 0.05 68 | sqr.layer.transform = CATransform3DMakeTranslation(0.0, 0.0, -100) 69 | addSubview(sqr) 70 | } 71 | 72 | //Align background squares 73 | squares[0].center = CGPoint(x: center.x - 60.0, y: center.y - 60.0) 74 | squares[1].center = CGPoint(x: center.x + 60.0, y: center.y - 60.0) 75 | squares[2].center = CGPoint(x: center.x + 60.0, y: center.y + 60.0) 76 | squares[3].center = CGPoint(x: center.x - 60.0, y: center.y + 60.0) 77 | 78 | //Add square 79 | let spacing = square.frame.size.width / 2.0 + 20.0 80 | square.center = CGPoint(x: spacing, y: spacing) 81 | addSubview( square ) 82 | 83 | //Set initial position 84 | square.center = squares[0].center 85 | 86 | //Add button 87 | addSubview(button) 88 | button.frame = CGRect(x:20.0, y:frame.size.height - 70.0, width:frame.size.width - 40.0, height:50.0) 89 | button.addTarget(self, action: #selector(playTimeline), for: .touchUpInside) 90 | 91 | //Add to timeline using declarative syntax: 92 | timeline.add( 93 | 94 | //Tween 1, start chain 95 | Tween(square) 96 | .ease(.inOutQuad) 97 | .to(.key(\.center, squares[1].center) ) 98 | .onStart { 99 | //Change color 100 | Tween(self.square) 101 | .to(.key(\.backgroundColor!, .random())) 102 | .play() 103 | self.square.flipX(inverted: true) 104 | } 105 | ///.onComplete { print("Tween 1 complete") } 106 | 107 | //Tween 2 108 | .after() 109 | .to(.key(\.center, squares[2].center) ) 110 | .onStart { 111 | //Change color 112 | Tween(self.square) 113 | .to(.key(\.backgroundColor!, .random())) 114 | .play() 115 | self.square.flipY() 116 | } 117 | .onComplete { print("Tween 2 complete") } 118 | 119 | //Tween 3 120 | .after() 121 | .to(.key(\.center, squares[3].center) ) 122 | .onStart { 123 | //Change color 124 | Tween(self.square) 125 | .to(.key(\.backgroundColor!, .random())) 126 | .play() 127 | self.square.flipX() 128 | } 129 | .onComplete { print("Tween 3 complete") } 130 | 131 | //Tween 4 132 | .after() 133 | .to(.key(\.center, squares[0].center) ) 134 | .onStart { 135 | //Change color 136 | Tween(self.square) 137 | .to(.key(\.backgroundColor!, .random())) 138 | .play() 139 | self.square.flipY(inverted: true) 140 | } 141 | .onComplete { print("Tween 4 complete") } 142 | 143 | // Put in the front another Tween Type. 144 | .after( button.shake() ) 145 | .onComplete { print("Button shake complete") } 146 | 147 | ).mode( .loop ) 148 | } 149 | 150 | required init?(coder aDecoder: NSCoder) { 151 | fatalError("init(coder:) has not been implemented") 152 | } 153 | 154 | @objc func playTimeline(){ 155 | if timeline.isPlaying(){ timeline.pause() } else { timeline.play() } 156 | 157 | button.setTitle(timeline.isPlaying() ? "PAUSE TIMELINE" : "PLAY TIMELINE", for: .normal) 158 | } 159 | 160 | func freeze() { if timeline.isPlaying(){ timeline.pause() } } 161 | 162 | func warm(){ } 163 | } 164 | -------------------------------------------------------------------------------- /Examples/Examples/CurveInspector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurveInspector.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/5/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class CurveInspector: UIControl 13 | { 14 | 15 | public var _ease:Ease = .none 16 | public let label:UILabel = UILabel() 17 | let border:UIView = UIView() 18 | let curve:UIView = UIView() 19 | let asset:UIView = UIView() 20 | let linex:UIView = UIView() 21 | let liney:UIView = UIView() 22 | let playIcon:PDFImageView = PDFImageView() 23 | 24 | override init(frame:CGRect) 25 | { 26 | super.init(frame:frame) 27 | 28 | backgroundColor = UIColor.clear 29 | 30 | //Label 31 | label.frame = CGRect(x:0.0, 32 | y:0.0, 33 | width:self.frame.size.width, 34 | height:40.0) 35 | label.textColor = UIColor.black 36 | label.font = UIFont.systemFont(ofSize: 24.0) 37 | label.text = "EaseType" 38 | addSubview(label) 39 | 40 | let graphFrame:CGRect = CGRect(x:0.0, 41 | y:40.0, 42 | width:self.frame.size.width, 43 | height:self.frame.size.height - 110.0) 44 | 45 | //Border 46 | border.frame = graphFrame 47 | border.isUserInteractionEnabled = false 48 | border.layer.borderColor = UIColor.black.cgColor 49 | border.layer.borderWidth = 1.0 50 | addSubview(border) 51 | 52 | //Curve 53 | curve.frame = graphFrame 54 | curve.isUserInteractionEnabled = false 55 | addSubview(curve) 56 | 57 | //Asset 58 | asset.frame = CGRect(x:0.0, 59 | y:frame.size.height - 50.0, 60 | width:50.0, 61 | height:50.0) 62 | 63 | asset.isUserInteractionEnabled = false 64 | asset.layer.cornerRadius = 50.0/2.0 65 | asset.backgroundColor = UIColor(red:1.0, green:48.0 / 255.0, blue:130 / 255.0, alpha:1.0) 66 | addSubview(asset) 67 | 68 | //Line x 69 | linex.frame = CGRect(x:0.0, 70 | y:graphFrame.origin.y, 71 | width:1.0, 72 | height:graphFrame.size.height) 73 | linex.isUserInteractionEnabled = false 74 | linex.backgroundColor = UIColor(red:0.0, green:0.0, blue:0.0, alpha:0.1) 75 | addSubview(linex) 76 | 77 | //Line y 78 | liney.frame = CGRect(x:0.0, 79 | y:graphFrame.origin.y, 80 | width:graphFrame.size.width, 81 | height:1.0) 82 | liney.isUserInteractionEnabled = false 83 | liney.backgroundColor = UIColor(red:0.0, 84 | green:202.0 / 255.0, 85 | blue:255.0 / 255.0, 86 | alpha:1.0) 87 | addSubview(liney) 88 | 89 | //TOD:Play icon 90 | 91 | //Add play action 92 | addTarget(self, action:#selector(play), for: .touchUpInside) 93 | 94 | playIcon.loadFromBundle("moon") 95 | } 96 | 97 | required init?(coder aDecoder: NSCoder) { 98 | fatalError("init(coder:) has not been implemented") 99 | } 100 | 101 | //setter/getter var 102 | var ease:Ease 103 | { 104 | set{ 105 | _ease = newValue 106 | //Remove old layer 107 | curve.layer.sublayers = nil 108 | 109 | let path:UIBezierPath = UIBezierPath() 110 | path.move(to: CGPoint(x:0.0, y:curve.frame.size.height)) 111 | 112 | let segments:Int = 200 113 | 114 | let b:Double = Double(curve.frame.size.height) 115 | let c:Double = 0.0 - b 116 | 117 | for i:Int in 0 ... segments 118 | { 119 | let interpolation:Double = (1.0 / Double(segments)) * Double(i) 120 | let x:CGFloat = curve.frame.size.width * CGFloat(interpolation) 121 | let y:CGFloat = CGFloat(_ease.equation(interpolation, b, c, 1.0)) 122 | path.addLine(to: CGPoint(x:x, y:y)) 123 | } 124 | 125 | //Add end point 126 | path.addLine(to: CGPoint(x:curve.frame.size.width, y:0.0)) 127 | 128 | //add sublayer 129 | curve.layer.addSublayer(makeStrokedLayer(path:path)) 130 | } 131 | get{return _ease} 132 | } 133 | 134 | func makeStrokedLayer(path:UIBezierPath) -> CAShapeLayer 135 | { 136 | let shapeLayer:CAShapeLayer = CAShapeLayer() 137 | 138 | shapeLayer.fillColor = nil 139 | shapeLayer.strokeColor = UIColor(red:1.0, green:48.0 / 255.0, blue:130 / 255.0, alpha:1.0).cgColor 140 | shapeLayer.lineWidth = 2.0 141 | shapeLayer.path = path.cgPath 142 | 143 | return shapeLayer 144 | } 145 | 146 | @objc func play() 147 | { 148 | //Asset 149 | //TODO: 150 | Tweener.removeTweens(target:asset) 151 | var assetFrame:CGRect = asset.frame 152 | assetFrame.origin.x = 0.0 153 | 154 | asset.frame = assetFrame 155 | assetFrame.origin.x = frame.size.width - asset.frame.size.width 156 | Tween(asset, 157 | duration: 1.0, 158 | ease:ease, 159 | to:[.key(\.frame, assetFrame)]).play() 160 | 161 | //Line x 162 | 163 | //Initial state 164 | var lineFrame:CGRect = linex.frame 165 | lineFrame.origin.x = 0.0 166 | linex.frame = lineFrame 167 | 168 | //Change 169 | lineFrame.origin.x = frame.size.width - 1.0 170 | 171 | Tween(target:linex, 172 | duration: 1.0, 173 | ease:Ease.none, 174 | to:[.key(\.frame, lineFrame)]).play() 175 | 176 | //Initial state 177 | lineFrame = liney.frame 178 | lineFrame.origin.y = curve.frame.origin.y + curve.frame.size.height 179 | liney.frame = lineFrame 180 | //Change 181 | lineFrame.origin.y = curve.frame.origin.y 182 | 183 | Tween(target:liney, 184 | duration: 1.0, 185 | ease:ease, 186 | to:[.key(\.frame, lineFrame)]).play() 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /Examples/Examples/CustomEasing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomEasing.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 7/10/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | //Custom ease. 13 | extension Ease{ 14 | public static let custom = Ease(equation:{ (t, b, c, d) in 15 | //Play with code here! 16 | if t < d/2 {return Ease.inBack.equation(t*2, b, c/2, d)} 17 | return Ease.outElastic.equation((t*2)-d, b+c/2, c/2, d) 18 | }) 19 | } 20 | 21 | class CustomEasing: UIView 22 | { 23 | //Asset 24 | override init(frame: CGRect){ 25 | super.init(frame: frame) 26 | 27 | let spacing:CGFloat = 20.0 28 | let inspector:CurveInspector = CurveInspector(frame:CGRect(x:spacing, 29 | y:spacing, 30 | width:self.frame.size.width - spacing * 2.0, 31 | height:360)) 32 | inspector.ease = .custom 33 | inspector.label.text = "Custom ease" 34 | addSubview(inspector) 35 | } 36 | 37 | required init?(coder aDecoder: NSCoder) { 38 | fatalError("init(coder:) has not been implemented") 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Examples/Examples/CustomTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomTypes.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 05/09/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | //An example of a 3-Dimension custom type. 13 | public struct Vector3{ 14 | var x, y, z: Double 15 | func buffer() -> [Double] { return [x, y, z] } 16 | static func zero() -> Vector3 { return Vector3(x:0.0, y:0.0, z:0.0) } 17 | static var random: Vector3 { 18 | return Vector3( x:.random(in: 0...1.0), 19 | y:.random(in: 0...1.0), 20 | z:.random(in: 0...1.0) 21 | ) 22 | } 23 | } 24 | 25 | class CustomTypes:UIView, FreezeProtocol 26 | { 27 | //Declare Assets 28 | let square:UIView = { 29 | let view = UIView(frame:CGRect(x:20.0, y:20.0, width:250.0, height:250.0)) 30 | view.backgroundColor = UIColor(hue: CGFloat.random(in: 0 ..< 1.0), saturation: 1.0, brightness: 1.0, alpha: 1.0) 31 | return view 32 | }() 33 | let button:UIButton = { 34 | let button = UIButton() 35 | button.setTitle("RANDOM ROTATION", for:.normal) 36 | button.setTitleColor(UIColor.white, for:.normal) 37 | button.backgroundColor = UIColor.black 38 | button.layer.cornerRadius = 7.0 39 | return button 40 | }() 41 | 42 | //Use as property 43 | var point3d:Vector3 = Vector3.zero() { 44 | didSet{ 45 | //Do somethig 46 | print("Pont3D updated!") 47 | } 48 | } 49 | 50 | //Use as block 51 | var updateBlock:TweenBlock! 52 | 53 | override init(frame: CGRect) { 54 | super.init(frame: frame) 55 | 56 | //Tell Tweener how convert from Array buffer to type and from Type to array buffer. 57 | Tweener.addType(toType:{ values in return Vector3(x:values[0], y:values[1], z:values[2])}, 58 | toArray:{ point in return point.buffer() }) 59 | 60 | //Create an update Block with initial value, to use 'self', declare after call super.init(). 61 | updateBlock = TweenBlock(value:Vector3.zero()){ value in 62 | 63 | //Decide what to do with updated value here. 64 | 65 | //Create a identity matrix 66 | var perspective = CATransform3DIdentity 67 | //Add perspective 68 | perspective.m34 = 1.0 / 500.0 69 | //Rotate x 70 | perspective = CATransform3DRotate(perspective, BasicMath.toRadians(degree:CGFloat(value.x)), 0, 1, 0) 71 | //Rotate y 72 | perspective = CATransform3DRotate(perspective, BasicMath.toRadians(degree:CGFloat(value.y)), 1, 0, 0) 73 | //Rotate z 74 | perspective = CATransform3DRotate(perspective, BasicMath.toRadians(degree:CGFloat(value.z)), 0, 0, 1) 75 | 76 | //Apply tranformation 77 | self.square.layer.transform = perspective 78 | } 79 | 80 | square.center = center 81 | addSubview(square) 82 | 83 | button.addTarget(self, action:#selector(addTween), for:.touchUpInside) 84 | self.button.frame = CGRect(x:20.0, 85 | y:self.frame.size.height - 70.0, 86 | width:self.frame.size.width - 40.0, 87 | height:50.0) 88 | addSubview(button) 89 | 90 | } 91 | 92 | required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 93 | 94 | @objc func addTween() 95 | { 96 | //Animate property 97 | Tween(self) 98 | .ease(.inBounce) 99 | .duration(1.0) 100 | .to(.key(\.point3d, .random)) 101 | .play() 102 | 103 | //Animate with block 104 | Tween(target: updateBlock) 105 | .ease(.outBack) 106 | .duration(1.0) 107 | .to(.key(\TweenBlock.value, 108 | Vector3(x:Double.random(in: -360...360), 109 | y:Double.random(in: -360...360), 110 | z:Double.random(in: -360...360)))) 111 | .play() 112 | 113 | Tween(target: square) 114 | .duration(1.0) 115 | .to(.key(\.backgroundColor!, .random())) 116 | .play() 117 | } 118 | 119 | func freeze() 120 | { 121 | Tweener.pauseTweens(target: square) 122 | } 123 | 124 | func warm() 125 | { 126 | Tweener.resumeTweens(target: square) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Examples/Examples/DragView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DragView.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/6/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class DragView:UIView 13 | { 14 | var dragView:UIView? 15 | var frameOrigin:CGPoint = CGPoint.zero 16 | var viewIndex:Int = -1 17 | 18 | override init(frame: CGRect) 19 | { 20 | super.init(frame: frame) 21 | 22 | backgroundColor = UIColor(red:55.0/255.0, green:65.0/255.0, blue:80.0/255, alpha:1.0) 23 | 24 | //Set color list 25 | let colors:Array = [UIColor(red:255.0/255.0, green:120.0/255.0, blue:180.0/255, alpha:1.0), 26 | UIColor(red:80.0/255.0, green:220.0/255.0, blue:170.0/255, alpha:1.0), 27 | UIColor(red:110.0/255.0, green:100.0/255.0, blue:240.0/255, alpha:1.0)] 28 | 29 | //Create assets 30 | 31 | for (index, color) in colors.enumerated() 32 | { 33 | let view:PDFImageView = PDFImageView() 34 | view.loadFromBundle("card") 35 | view.scale = Double((self.frame.size.width - 40.0) / view.frame.size.width) 36 | view.frame = CGRect(x:20.0, 37 | y:20.0 + (view.frame.size.height + 20.0) * CGFloat(index), 38 | width:view.frame.size.width, 39 | height:view.frame.size.height) 40 | view.backgroundColor = color 41 | view.layer.cornerRadius = 10.0 42 | addSubview(view) 43 | } 44 | 45 | //Add pan gesture recognizer 46 | let panRecognizer:UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(pan(recognizer:))) 47 | panRecognizer.maximumNumberOfTouches = 1 48 | addGestureRecognizer(panRecognizer) 49 | } 50 | 51 | required init?(coder aDecoder: NSCoder) { 52 | fatalError("init(coder:) has not been implemented") 53 | } 54 | 55 | @objc func pan(recognizer:UIPanGestureRecognizer) 56 | { 57 | var p:CGPoint = CGPoint.zero 58 | let translation:CGPoint = recognizer.translation(in: self) 59 | 60 | if recognizer.numberOfTouches > 0 { p = recognizer.location(ofTouch: 0, in: self) 61 | 62 | if recognizer.state == .began 63 | { 64 | for (index, view) in subviews.enumerated() 65 | { 66 | if (view.frame.contains(p)) 67 | { 68 | dragView = view 69 | viewIndex = index 70 | frameOrigin = view.frame.origin 71 | break 72 | } 73 | } 74 | } 75 | else if recognizer.state == .changed 76 | { 77 | if dragView != nil 78 | { 79 | dragView!.frame = CGRect(x:frameOrigin.x + translation.x, 80 | y:frameOrigin.y + translation.y, 81 | width:dragView!.frame.size.width, 82 | height:dragView!.frame.size.height) 83 | 84 | var currentIndex:Int = viewIndex 85 | 86 | for index:Int in 0 ... subviews.count - 1 87 | { 88 | let _y:CGFloat = 20.0 + (dragView!.frame.size.height + 20.0) * CGFloat(index) + dragView!.frame.size.height 89 | 90 | if (dragView!.frame.origin.y < _y) 91 | { 92 | currentIndex = index 93 | break 94 | } 95 | } 96 | 97 | if currentIndex != viewIndex 98 | { 99 | //Swap 100 | viewIndex = currentIndex 101 | insertSubview(dragView!, at:currentIndex) 102 | alingViews() 103 | } 104 | } 105 | } 106 | }else 107 | { 108 | //Touches ended 109 | dragView = nil 110 | alingViews() 111 | } 112 | } 113 | 114 | func alingViews() 115 | { 116 | for (index, view) in subviews.enumerated() 117 | { 118 | if dragView == nil || viewIndex != index 119 | { 120 | let destinationFrame:CGRect = CGRect(x:20.0, 121 | y:20.0 + (view.frame.size.height + 20.0) * CGFloat(index), 122 | width:view.frame.size.width, 123 | height:view.frame.size.height) 124 | //Animate. 125 | Tween(target:view, 126 | duration:0.25, 127 | ease:.outQuad, 128 | to:[.key(\.frame, destinationFrame)]).play() 129 | } 130 | 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Examples/Examples/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | 33 | UISupportedInterfaceOrientations~ipad 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationPortraitUpsideDown 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Examples/Examples/LaunchScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Examples/LaunchScreen.png -------------------------------------------------------------------------------- /Examples/Examples/PauseTweens.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PauseTweens.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/14/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class PauseTweens:UIView, FreezeProtocol 13 | { 14 | let clouds1 = UIView() 15 | let clouds2 = UIView() 16 | let clouds3 = UIView() 17 | 18 | let pauseButton = UIButton() 19 | var paused:Bool = false 20 | 21 | override init(frame: CGRect) { 22 | super.init(frame: frame) 23 | 24 | backgroundColor = UIColor(red:0.0/255.0, 25 | green:230.0/255.0, 26 | blue:240.0/255, 27 | alpha:1.0) 28 | clipsToBounds = true 29 | 30 | let screenSize = UIScreen.main.bounds.size 31 | 32 | let pdfView = PDFImageView() 33 | pdfView.loadFromBundle("clouds1") 34 | 35 | clouds1.frame = pdfView.bounds 36 | clouds1.backgroundColor = UIColor(patternImage: pdfView.image!) 37 | clouds1.frame = CGRect(x:0.0, 38 | y:screenSize.height * 0.2, 39 | width:clouds1.frame.size.width * 2.0, 40 | height:clouds1.frame.size.height) 41 | addSubview(clouds1) 42 | 43 | pdfView.loadFromBundle("clouds2") 44 | clouds2.frame = pdfView.bounds 45 | clouds2.backgroundColor = UIColor(patternImage: pdfView.image!) 46 | clouds2.frame = CGRect(x:0.0, 47 | y:screenSize.height * 0.35, 48 | width:clouds2.frame.size.width * 2.0, 49 | height:clouds2.frame.size.height) 50 | addSubview(clouds2) 51 | 52 | pdfView.loadFromBundle("clouds3") 53 | clouds3.frame = pdfView.bounds 54 | clouds3.backgroundColor = UIColor(patternImage: pdfView.image!) 55 | clouds3.frame = CGRect(x:0.0, 56 | y:screenSize.height * 0.5, 57 | width:clouds3.frame.size.width * 2.0, 58 | height:clouds3.frame.size.height) 59 | addSubview(clouds3) 60 | 61 | cloud1Tween() 62 | cloud2Tween() 63 | cloud3Tween() 64 | 65 | //Freeze 66 | freeze() 67 | 68 | pauseButton.frame = CGRect(x:20.0, 69 | y:frame.size.height - 70.0, 70 | width:frame.size.width - 40.0, 71 | height:50.0) 72 | 73 | pauseButton.setTitle("PAUSE TWEENS", for: .normal) 74 | pauseButton.addTarget(self, action: #selector(playPause), for: .touchUpInside) 75 | pauseButton.setTitleColor(UIColor.white, for: .normal) 76 | pauseButton.backgroundColor = UIColor(red:0.0/255.0, green:230.0/255.0, blue:240.0/255, alpha:1.0) 77 | pauseButton.layer.cornerRadius = 7.0 78 | addSubview(pauseButton) 79 | 80 | } 81 | 82 | required init?(coder aDecoder: NSCoder) { 83 | fatalError("init(coder:) has not been implemented") 84 | } 85 | 86 | @objc func playPause() 87 | { 88 | paused = !paused 89 | 90 | if (paused) 91 | { 92 | pauseButton.setTitle("RESUME TWEENS", for: .normal) 93 | Tweener.pauseTweens(target:clouds1) 94 | Tweener.pauseTweens(target:clouds2) 95 | Tweener.pauseTweens(target:clouds3) 96 | } 97 | else 98 | { 99 | pauseButton.setTitle("PAUSE TWEENS", for: .normal) 100 | Tweener.resumeTweens(target:clouds1) 101 | Tweener.resumeTweens(target:clouds2) 102 | Tweener.resumeTweens(target:clouds3) 103 | } 104 | } 105 | 106 | func cloud1Tween() 107 | { 108 | var newFrame = clouds1.frame 109 | newFrame.origin.x = -clouds1.frame.size.width / 2.0 110 | 111 | Tween(clouds1, duration:6.0, ease:.none, delay: 0.0, 112 | to:[.key(\.frame, newFrame)], 113 | completion: { 114 | var resetFrame = self.clouds1.frame 115 | resetFrame.origin.x = 0.0 116 | self.clouds1.frame = resetFrame 117 | self.cloud1Tween() 118 | }).play() 119 | } 120 | 121 | 122 | func cloud2Tween() 123 | { 124 | var newFrame = clouds2.frame 125 | newFrame.origin.x = -clouds2.frame.size.width / 2.0 126 | 127 | Tween(clouds2, 128 | duration:4.0, 129 | ease:.none, 130 | delay:0.0, 131 | to:[.key(\.frame,newFrame)], 132 | completion:{ 133 | var resetFrame = self.clouds2.frame 134 | resetFrame.origin.x = 0 135 | self.clouds2.frame = resetFrame 136 | self.cloud2Tween() 137 | }).play() 138 | } 139 | 140 | func cloud3Tween() 141 | { 142 | var newFrame = clouds3.frame 143 | newFrame.origin.x = -clouds3.frame.size.width / 2.0 144 | 145 | Tween(clouds3, 146 | duration:2.0, 147 | ease:.none, 148 | delay:0.0, 149 | to:[.key(\.frame, newFrame)], 150 | completion:{ 151 | var resetFrame = self.clouds3.frame 152 | resetFrame.origin.x = 0 153 | self.clouds3.frame = resetFrame 154 | self.cloud3Tween()} 155 | ).play() 156 | } 157 | 158 | func freeze() 159 | { 160 | if(!paused){playPause()} 161 | } 162 | 163 | func warm() 164 | { 165 | if(paused){playPause()} 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /Examples/Examples/ScrollAims.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollAims.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/29/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class ScrollAims:UIView, UIScrollViewDelegate 13 | { 14 | let scrollview:UIScrollView = UIScrollView() 15 | let sand:UIView = UIView() 16 | let body:PDFImageView = PDFImageView(bundlename: "bb8-body") 17 | let head:PDFImageView = PDFImageView(bundlename: "bb8-head") 18 | let rotationAim:RotationAim = RotationAim() 19 | let arcAim:ArcAim = ArcAim() 20 | var lastOffset:CGFloat = 0.0 21 | 22 | override init(frame: CGRect){ 23 | super.init(frame: frame) 24 | 25 | backgroundColor = UIColor(red:222.0/255.0, 26 | green:255.0/255.0, 27 | blue:220.0/255, 28 | alpha:1.0) 29 | 30 | //Scrollview 31 | scrollview.frame = bounds 32 | scrollview.contentSize = CGSize(width:self.frame.size.width * 5.0, height:self.frame.size.height) 33 | scrollview.delegate = self 34 | addSubview(scrollview) 35 | 36 | //Sand background 37 | let pdfView = PDFImageView(bundlename: "sand") 38 | 39 | //body 40 | 41 | body.center = center 42 | addSubview(body) 43 | 44 | head.center = CGPoint(x:center.x, 45 | y:center.y - ((body.frame.size.height + head.frame.size.height) / 2.0) + 15.0) 46 | //+ self.head.frame.size.height 47 | addSubview(head) 48 | 49 | sand.frame = pdfView.bounds 50 | sand.backgroundColor = UIColor(patternImage: pdfView.image!) 51 | sand.frame = CGRect(x:-self.frame.size.width, 52 | y:(self.frame.size.height + body.frame.size.height) / 2.0, 53 | width:scrollview.contentSize.width + self.frame.size.width * 2.0, 54 | height:(self.frame.size.height - body.frame.size.height) / 2.0) 55 | 56 | scrollview.addSubview(sand) 57 | //head 58 | 59 | //Setup rotation Aim 60 | rotationAim.target = body 61 | 62 | //Setup arc Aim 63 | arcAim.target = head 64 | arcAim.radius = body.frame.size.width * 0.7 65 | arcAim.orientToArc = true 66 | arcAim.arcAngleOffset = -90.0 67 | arcAim.angleOffset = -90.0 68 | arcAim.center = center 69 | 70 | } 71 | 72 | required init?(coder aDecoder: NSCoder) { 73 | fatalError("init(coder:) has not been implemented") 74 | } 75 | 76 | //Control aims here! 77 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 78 | 79 | //Rotation, value 532.0 is total lenght of a bb8's body circle 80 | rotationAim.distance = (scrollview.contentOffset.x / 537.0) 81 | 82 | //Calculate velocity 83 | var velocity = scrollview.contentOffset.x - lastOffset 84 | 85 | //Set max velocity 86 | let max:CGFloat = 35.0 87 | if velocity < -max {velocity = -max} 88 | if velocity > max {velocity = max} 89 | 90 | //Calculate interpolation 91 | let interpolation = velocity / max 92 | 93 | //Arc angle 94 | arcAim.arcAngle = 45.0 * interpolation 95 | 96 | //Store last scroll offset to calculate velocity 97 | lastOffset = scrollview.contentOffset.x 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /Examples/Examples/SimpleTimeline.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleTimeline.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/14/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | import Tweener 12 | 13 | class SimpleTimeline:UIView, FreezeProtocol 14 | { 15 | 16 | let circle:UIView = UIView() 17 | let timeline:Timeline = Timeline() 18 | 19 | override init(frame: CGRect) { 20 | 21 | super.init(frame: frame) 22 | 23 | //Set background color 24 | backgroundColor = UIColor(red:55.0/255.0, green:65.0/255.0, blue:80.0/255, alpha:1.0) 25 | 26 | //Initialize target 27 | circle.frame = CGRect(x: (self.frame.size.width - 200.0) / 2.0, 28 | y: 20.0, 29 | width: 200.0, 30 | height: 200.0) 31 | 32 | circle.backgroundColor = UIColor(red: 80.0 / 255.0, 33 | green: 220.0 / 255.0, 34 | blue: 170.0 / 255, 35 | alpha: 1.0) 36 | circle.layer.cornerRadius = 100.0 37 | addSubview(circle) 38 | 39 | //Declare destination frame 40 | let newFrame = CGRect(x:circle.frame.origin.x, 41 | y:self.frame.size.height - circle.frame.size.height - 20.0, 42 | width:circle.frame.size.width, 43 | height:circle.frame.size.height) 44 | 45 | //Add a tween to timeline 46 | timeline.add( Tween(target:circle, 47 | duration:1.5, 48 | ease:.outBounce, 49 | to:[.key(\.frame, newFrame)]) ) 50 | 51 | //Setup timeline 52 | timeline.playMode = .loop 53 | timeline.play() 54 | 55 | //Freeze 56 | freeze() 57 | 58 | //Add gesture recognizer 59 | addGestureRecognizer(UITapGestureRecognizer(target:self, 60 | action:#selector(tap))) 61 | } 62 | 63 | required init?(coder aDecoder: NSCoder) { 64 | fatalError("init(coder:) has not been implemented") 65 | } 66 | 67 | @objc func tap(sender:UITapGestureRecognizer ) 68 | { 69 | //Verify current playmode 70 | if (timeline.playMode == .loop) 71 | { 72 | //Change playmode 73 | timeline.playMode = .pingPong 74 | //Update target color 75 | circle.backgroundColor = UIColor(red: 255.0 / 255.0, 76 | green: 120.0 / 255.0, 77 | blue: 180.0 / 255, 78 | alpha: 1.0) 79 | }else 80 | { 81 | //Change playmode 82 | timeline.playMode = .loop 83 | //Disable reverse 84 | timeline.reverse = false 85 | //Update target color 86 | circle.backgroundColor = UIColor(red: 80.0 / 255.0, 87 | green: 220.0 / 255.0, 88 | blue: 170.0 / 255, 89 | alpha: 1.0) 90 | 91 | } 92 | } 93 | 94 | func freeze() 95 | { 96 | timeline.pause() 97 | } 98 | 99 | func warm() 100 | { 101 | timeline.play() 102 | } 103 | 104 | } 105 | 106 | -------------------------------------------------------------------------------- /Examples/Examples/SimpleTween.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleTween.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/1/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class SimpleTween:UIView, FreezeProtocol 13 | { 14 | let square:UIView = UIView(frame:CGRect(x:20.0, y:20.0, width:100.0, height:100.0)) 15 | let button:UIButton = UIButton() 16 | 17 | override init(frame: CGRect) { 18 | super.init(frame: frame) 19 | 20 | square.backgroundColor = UIColor.blue 21 | addSubview(square) 22 | 23 | self.button.frame = CGRect(x:20.0, 24 | y:self.frame.size.height - 70.0, 25 | width:self.frame.size.width - 40.0, 26 | height:50.0) 27 | addSubview(self.button) 28 | button.setTitle("ADD TWEEN", for:.normal) 29 | button.addTarget(self, action:#selector(addTween), for:.touchUpInside) 30 | button.setTitleColor(UIColor.white, for:.normal) 31 | button.backgroundColor = UIColor.black 32 | button.layer.cornerRadius = 7.0 33 | addSubview(button) 34 | } 35 | 36 | required init?(coder aDecoder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | @objc func addTween() 41 | { 42 | //Set initial states 43 | square.alpha = 0.25 44 | square.frame = CGRect(x:20.0, y:20.0, width:100.0, height:100.0) 45 | square.backgroundColor = UIColor.blue 46 | 47 | //Create tween 48 | Tween(square) 49 | .duration(1.0) 50 | .ease(.inOutCubic) 51 | .to( 52 | .key(\.alpha, 1.0), 53 | .key(\.frame, CGRect(x:20.0, 54 | y:20.0, 55 | width:UIScreen.main.bounds.width - 40, 56 | height:UIScreen.main.bounds.width - 40)), 57 | .key(\.backgroundColor!, .red)//NOTE:This property is an optional, add ! to keypath. 58 | ) 59 | .onComplete { print("Tween complete") } 60 | .after()//Creates a new tween after with same target and properties. 61 | .duration(1.0) 62 | .ease(Ease.outBounce) 63 | .to( 64 | .key(\.alpha, 0.25), 65 | .key(\.frame, CGRect(x:20.0, y:20.0, width:100.0, height:100.0)), 66 | .key(\.backgroundColor!, UIColor.blue) 67 | ) 68 | .play() 69 | } 70 | 71 | func freeze() 72 | { 73 | Tweener.pauseTweens(target: square) 74 | } 75 | 76 | func warm() 77 | { 78 | Tweener.resumeTweens(target: square) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Examples/Examples/TimelineBasic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineBasic.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/24/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import UIKit 12 | import Tweener 13 | 14 | 15 | class TimelineBasic:UIView, FreezeProtocol 16 | { 17 | let asset1 = UIView(frame: CGRect(x:0.0, y:20.0 + 75.0, width:50.0, height:50.0)) 18 | let asset2 = UIView(frame: CGRect(x:0.0, y:20.0 + 75.0 * 2, width:50.0, height:50.0)) 19 | let asset3 = UIView(frame:CGRect(x:0.0, y:20.0 + 75.0 * 3, width:50.0, height:50.0)) 20 | let asset4 = UIView(frame:CGRect(x:0.0, y:20.0 + 75.0 * 4, width:50.0, height:50.0)) 21 | 22 | let timeline = Timeline() 23 | let inspector = TimelineInspector() 24 | 25 | //Create rotation var for asset 3 26 | public var rotationAngle:CGFloat{ 27 | didSet { 28 | asset3.transform = CGAffineTransform(rotationAngle:rotationAngle * CGFloat.pi / 180.0) 29 | } 30 | } 31 | 32 | override init(frame: CGRect) { 33 | 34 | rotationAngle = 0.0 35 | 36 | super.init(frame:frame) 37 | 38 | backgroundColor = UIColor(red:116.0 / 255.0, 39 | green:244.0 / 255.0, 40 | blue:234.0 / 255.0, 41 | alpha:1.0) 42 | 43 | //---- Shape 1 ---- 44 | asset1.backgroundColor = UIColor.white 45 | let polyShape = CAShapeLayer() 46 | polyShape.path = makePolygon(divisions:5, radius:25.0, origin:CGPoint(x:25.0, y:25.0)) 47 | asset1.layer.mask = polyShape 48 | addSubview(asset1) 49 | 50 | //---- Shape 2 ---- 51 | asset2.backgroundColor = UIColor.white 52 | asset2.layer.cornerRadius = 25.0//make circle 53 | addSubview(asset2) 54 | 55 | //---- Shape 3 ---- 56 | 57 | asset3.backgroundColor = UIColor.white 58 | let triangleShape = CAShapeLayer() 59 | triangleShape.path = makePolygon(divisions:3, radius:25.0, origin:CGPoint(x:25.0, y:25.0)) 60 | asset3.layer.mask = triangleShape 61 | addSubview(asset3) 62 | 63 | //---- Shape 4 ---- 64 | asset4.backgroundColor = UIColor.white 65 | addSubview(asset4) 66 | 67 | //TWEEN 1 68 | var newFrame = asset1.frame 69 | newFrame.origin.x = bounds.size.width - asset1.frame.size.width 70 | 71 | timeline.add(Tween(target:asset1, 72 | duration:1.0, 73 | ease:.outQuad, 74 | to:[.key(\.frame, newFrame), 75 | .key(\.alpha, 0.25)] 76 | )) 77 | 78 | //TWEEN 2 79 | 80 | //set inital value 81 | newFrame = asset2.frame 82 | 83 | //set start value 84 | newFrame.origin.x = bounds.size.width - asset2.frame.size.width 85 | 86 | //add tween to timeline, pass target, parameters and key paths 87 | timeline.add(Tween(target:asset2, 88 | duration:1.0, 89 | ease:.inOutBounce, 90 | to:[.key(\.frame, newFrame)] 91 | )) 92 | 93 | //TWEEN 94 | //Add tween to timeline. 95 | timeline.add(Tween(target:self, 96 | duration:1.0, 97 | ease:.outQuad, 98 | to:[.key(\.rotationAngle, 360)] 99 | )) 100 | 101 | //TWEEN 4 102 | //Update destination frame 103 | newFrame = CGRect(x:asset4.frame.origin.x, 104 | y:asset4.frame.origin.y, 105 | width:100.0, 106 | height:100.0) 107 | 108 | //Add tween to timeline. 109 | timeline.add(Tween(target:asset4, 110 | duration:1.0, 111 | ease:.outElastic, 112 | delay:1.0, 113 | to:[.key(\.frame, newFrame)] 114 | )) 115 | 116 | //Link timeline to inspector 117 | inspector.timeline = timeline 118 | addSubview(inspector) 119 | 120 | //Freeze 121 | freeze() 122 | } 123 | 124 | required init?(coder aDecoder: NSCoder) { 125 | fatalError("init(coder:) has not been implemented") 126 | } 127 | 128 | func makePolygon(divisions:Int, radius:CGFloat, origin:CGPoint) -> CGPath 129 | { 130 | var divisions = divisions 131 | if divisions < 3 {divisions = 3} 132 | let path = CGMutablePath() 133 | 134 | let fragment:CGFloat = 360.0 / CGFloat(divisions) 135 | 136 | for indexDivision in 0 ... divisions 137 | { 138 | let point = BasicMath.arcRotationPoint(angle: BasicMath.toRadians(degree: fragment * CGFloat(indexDivision)), 139 | radius: radius) 140 | if indexDivision == 0{path.move(to: CGPoint(x:origin.x + point.x, y:origin.y + point.y))} 141 | else{path.addLine(to: CGPoint(x:origin.x + point.x, y:origin.y + point.y))} 142 | } 143 | 144 | path.closeSubpath() 145 | 146 | return path 147 | } 148 | 149 | func freeze() 150 | { 151 | timeline.pause() 152 | } 153 | 154 | func warm() 155 | { 156 | timeline.play() 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /Examples/Examples/TouchPoint.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchPoint.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/5/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class TouchPoint:UIView, FreezeProtocol 13 | { 14 | override init(frame: CGRect) 15 | { 16 | super.init(frame: frame) 17 | 18 | backgroundColor = UIColor(red:69.0/255.0, green:255.0/255.0, blue:247.0/255, alpha:1.0) 19 | clipsToBounds = true 20 | makeAssets() 21 | } 22 | 23 | required init?(coder aDecoder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | func makeAssets() 28 | { 29 | let colors:Array = [ 30 | UIColor(red:66.0/255.0, green:0.0, blue:162/255, alpha:1.0), 31 | UIColor(red:129.0/255.0, green:16.0/255, blue:152.0/255.0, alpha:1.0), 32 | UIColor(red:192.0/255.0, green:32.0/255.0, blue:141.0/255.0, alpha:1.0), 33 | UIColor(red:255.0/255.0, green:48.0/255.0, blue:131.0/255.0, alpha:1.0), 34 | UIColor(red:255.0/255.0, green:117.0/255.0, blue:131.0/255.0, alpha:1.0), 35 | UIColor(red:255.0/255.0, green:186.0/255.0, blue:131.0/255.0, alpha:1.0), 36 | UIColor(red:255.0/255.0, green:255.0/255.0, blue:131.0/255.0, alpha:1.0), 37 | UIColor(red:193.0/255.0, green:255.0/255.0, blue:170.0/255.0, alpha:1.0), 38 | UIColor(red:131.0/255.0, green:255.0/255.0, blue:208.0/255.0, alpha:1.0) 39 | ] 40 | 41 | for (index, color) in colors.enumerated() 42 | { 43 | let v:UIView = UIView(frame:CGRect(x:0.0, 44 | y:0.0, 45 | width:60.0 + 80.0 * CGFloat(index), 46 | height:60.0 + 80.0 * CGFloat(index))) 47 | v.backgroundColor = color 48 | v.layer.cornerRadius = v.frame.size.width / 2.0 49 | v.center = center 50 | insertSubview(v, at:0) 51 | } 52 | } 53 | 54 | override func touchesBegan(_ touches: Set, with event: UIEvent?) 55 | { 56 | let touch:UITouch = event!.allTouches!.first! 57 | let p:CGPoint = touch.location(in: self) 58 | 59 | //Animate. 60 | 61 | for index in 0 ... subviews.count - 1 62 | { 63 | Tween(target:subviews[index], 64 | duration:2.0, 65 | ease:.outElastic, 66 | delay:0.025 * Double(subviews.count - index), 67 | to:[.key(\UIView.center, p)]).play() 68 | } 69 | } 70 | 71 | func freeze() 72 | { 73 | for index in 0 ... subviews.count - 1 74 | { 75 | Tweener.pauseTweens(target: subviews[index]) 76 | } 77 | } 78 | 79 | func warm() 80 | { 81 | for index in 0 ... subviews.count - 1 82 | { 83 | Tweener.resumeTweens(target: subviews[index]) 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Examples/Examples/Transform3d.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Transform3d.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/10/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | 10 | import UIKit 11 | import Tweener 12 | 13 | class Transform3d:UIView 14 | { 15 | let circle = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 250.0, height: 250.0)) 16 | var startPoint:CGPoint = .zero 17 | 18 | override init(frame: CGRect) 19 | { 20 | super.init(frame: frame) 21 | 22 | backgroundColor = UIColor(red:116.0 / 255.0, green:244.0 / 255.0, blue:234.0 / 255.0, alpha:1.0) 23 | 24 | circle.backgroundColor = .white 25 | circle.layer.cornerRadius = 125.0 26 | circle.center = center 27 | addSubview(circle) 28 | 29 | let label = UILabel() 30 | label.textColor = UIColor(red:116.0 / 255.0, green:244.0 / 255.0, blue:234.0 / 255.0, alpha:1.0) 31 | label.font = UIFont.systemFont(ofSize: 18, weight: .regular) 32 | label.text = "drag circle" 33 | label.sizeToFit() 34 | label.frame = CGRect(x: (circle.frame.size.width - label.frame.size.width) / 2.0, 35 | y: (circle.frame.size.height - label.frame.size.height) / 2.0, 36 | width: label.frame.size.width, 37 | height: label.frame.size.height) 38 | circle.addSubview(label) 39 | 40 | //Add pan gesture recognizer 41 | let panRecognizer:UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(pan(recognizer:))) 42 | panRecognizer.maximumNumberOfTouches = 1 43 | addGestureRecognizer(panRecognizer) 44 | } 45 | 46 | required init?(coder aDecoder: NSCoder) { 47 | fatalError("init(coder:) has not been implemented") 48 | } 49 | 50 | @objc func pan(recognizer:UIPanGestureRecognizer) 51 | { 52 | if recognizer.numberOfTouches > 0 { 53 | 54 | let p = recognizer.location(ofTouch: 0, in: self) 55 | let translation:CGPoint = recognizer.translation(in: self) 56 | 57 | if recognizer.state == .began 58 | { 59 | //Store origin point 60 | startPoint = p 61 | //Remove existing tweens 62 | Tweener.removeTweens(target:circle.layer) 63 | } 64 | else if recognizer.state == .changed 65 | { 66 | let lenght = BasicMath.length(start: startPoint, end: CGPoint(x:startPoint.x + translation.x, 67 | y:startPoint.y + translation.y)) 68 | let angle = BasicMath.angle(start:center, end: CGPoint(x:center.x + translation.x, 69 | y:center.y + translation.y)) 70 | let aixs:CGPoint = BasicMath.arcRotationPoint(angle:angle * -1.0, radius: 1.0) 71 | 72 | //Rotate matrix 73 | circle.layer.transform = CATransform3DRotate(CATransform3DIdentity, 74 | BasicMath.toRadians(degree:lenght), 75 | aixs.y, 76 | aixs.x, 77 | 0.0) 78 | } 79 | }else 80 | { 81 | //Touches ended, animate. 82 | Tween(target: circle.layer, 83 | duration: 0.25, 84 | ease:.outQuad, 85 | to: [.key(\.transform, CATransform3DIdentity)]).play() 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Examples/Examples/TweenHandlers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweenHandlers.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/1/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class TweenHandlers:UIView, FreezeProtocol 13 | { 14 | let circle:UIView = UIView(frame:CGRect(x:20.0, y:20.0, width:50.0, height:50.0)) 15 | let button:UIButton = UIButton() 16 | 17 | override init(frame: CGRect) { 18 | super.init(frame: frame) 19 | circle.backgroundColor = UIColor.black 20 | circle.layer.cornerRadius = 25.0 21 | addSubview(circle) 22 | 23 | button.frame = CGRect(x:20.0, 24 | y:self.frame.size.height - 70.0, 25 | width:self.frame.size.width - 40.0, 26 | height:50.0) 27 | addSubview(button) 28 | button.setTitle("ADD TWEEN", for:.normal) 29 | button.addTarget(self, action:#selector(addTween), for:.touchUpInside) 30 | button.setTitleColor(UIColor.white, for:.normal) 31 | button.backgroundColor = UIColor.black 32 | button.layer.cornerRadius = 7.0 33 | addSubview(button) 34 | } 35 | 36 | required init?(coder aDecoder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | @objc func addTween() 41 | { 42 | //Set initial value 43 | self.circle.frame = CGRect(x:20.0, y:20.0, width:50.0, height:50.0) 44 | 45 | //Create tween 46 | let tween:Tween = Tween(target:circle,//Target 47 | duration:1.0,//One second 48 | ease:.inOutCubic,//Transition 49 | delay:1.0,//One second delay 50 | to:[.key(\.frame, CGRect(x:250.0, y:20.0, width:50.0, height:50.0))]) 51 | 52 | //set initial value 53 | self.backgroundColor = UIColor.white 54 | 55 | //Setup handlers 56 | tween.onStart = { 57 | self.backgroundColor = UIColor.green 58 | } 59 | 60 | tween.onUpdate = { 61 | //Place stuff here! 62 | } 63 | 64 | tween.onComplete = { 65 | self.backgroundColor = UIColor.red 66 | } 67 | 68 | tween.onOverwrite = { 69 | self.backgroundColor = UIColor.blue 70 | } 71 | 72 | //Add tween 73 | tween.play() 74 | } 75 | 76 | func freeze() 77 | { 78 | Tweener.pauseTweens(target: circle) 79 | } 80 | 81 | func warm() 82 | { 83 | Tweener.resumeTweens(target: circle) 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /Examples/Examples/ViewExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewExtensions.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 12/08/20. 6 | // Copyright © 2020 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class ViewExtensions: UIScrollView { 13 | 14 | let spacing:CGFloat = 30 15 | let titles:[String] = [".spring()", 16 | ".zoomIn()", 17 | ".zoomOut()", 18 | ".pop()", 19 | ".fadeIn()", 20 | ".fadeOut()", 21 | ".flyLeft()", 22 | ".flyRight()", 23 | ".flyTop()", 24 | ".flyBottom()", 25 | ".slideLeft()", 26 | ".slideRight()", 27 | ".slideTop()", 28 | ".slideBottom()", 29 | ".flipX()", 30 | ".flipY()", 31 | ".shake()", 32 | ".jiggle()", 33 | ".bounce()", 34 | ".swing()", 35 | ".spin()", 36 | ".spin(clockwise..", 37 | ".loop()"] 38 | 39 | var buttons:[UIButton] = [] 40 | var looping = false 41 | 42 | override init(frame: CGRect) { 43 | 44 | super.init(frame: frame) 45 | 46 | //Setup grid. 47 | let cols = 2 48 | let size = (frame.size.width - spacing) / CGFloat( cols ) - spacing 49 | var x = 0 50 | var y = 0 51 | var contentHeight:CGFloat = 0 52 | let c:CGFloat = 0.5 / CGFloat(titles.count) 53 | //Create a grid 54 | for i in 0 ..< titles.count { 55 | let button = UIButton(frame:CGRect(x:spacing + (size + spacing) * CGFloat( x ), 56 | y:spacing + (size + spacing) * CGFloat( y ), 57 | width:size, 58 | height:size)) 59 | button.setTitleColor(.white, for: .normal) 60 | button.setTitle(titles[i], for: .normal) 61 | button.backgroundColor = UIColor(hue: 0.5 + c * CGFloat(i), saturation: 1.0, brightness: 1.0, alpha: 1.0) 62 | button.layer.cornerRadius = 10.0 63 | button.addTarget(self, action: #selector(click), for: .touchUpInside) 64 | buttons.append(button) 65 | addSubview(button) 66 | 67 | contentHeight = button.frame.origin.y + button.frame.size.height + CGFloat( spacing ) 68 | 69 | x += 1 70 | if x >= cols { x = 0; y += 1 } 71 | } 72 | 73 | self.contentSize = CGSize(width: bounds.width, height: contentHeight) 74 | } 75 | 76 | required init?(coder aDecoder: NSCoder) { 77 | fatalError("init(coder:) has not been implemented") 78 | } 79 | 80 | @objc func click(_ sender: UIButton){ 81 | 82 | switch buttons.firstIndex(of: sender) { 83 | case 0: 84 | sender.spring() 85 | case 1: 86 | sender.zoomIn() 87 | sender.fadeIn() 88 | case 2: 89 | sender.zoomOut() 90 | sender.fadeIn() 91 | case 3: 92 | sender.pop() 93 | case 4: 94 | sender.fadeIn() 95 | case 5: 96 | sender.fadeOut() 97 | //Show again! 98 | .after() 99 | .duration(0.15) 100 | .to(.key(\.opacity, 1.0))//from:[\CALayer.opacity : 0.0], 101 | .play() 102 | case 6: 103 | sender.flyLeft() 104 | sender.fadeIn() 105 | case 7: 106 | sender.flyRight() 107 | sender.fadeIn() 108 | case 8: 109 | sender.flyTop() 110 | sender.fadeIn() 111 | case 9: 112 | sender.flyBottom() 113 | sender.fadeIn() 114 | case 10: 115 | sender.slideLeft() 116 | sender.fadeIn() 117 | case 11: 118 | sender.slideRight() 119 | sender.fadeIn() 120 | case 12: 121 | sender.slideTop() 122 | sender.fadeIn() 123 | case 13: 124 | sender.slideBottom() 125 | sender.fadeIn() 126 | case 14: 127 | sender.flipX() 128 | .onComplete { sender.layer.transform = CATransform3DIdentity } 129 | case 15: 130 | sender.flipY() 131 | .onComplete { sender.layer.transform = CATransform3DIdentity } 132 | case 16: 133 | sender.shake() 134 | case 17: 135 | sender.jiggle() 136 | case 18: 137 | sender.bounce() 138 | case 19: 139 | sender.swing() 140 | case 20: 141 | sender.spin() 142 | case 21: 143 | sender.spin(clockwise:false) 144 | case 22: 145 | if looping { sender.stopLoop()} else { sender.loop()} 146 | looping = !looping 147 | sender.setTitle(looping ? ".stopLoop()" : ".loop()", for: .normal) 148 | default: 149 | break 150 | } 151 | } 152 | 153 | @objc func play(_ sender: UIButton){ playAll() } 154 | 155 | func playAll(){ 156 | for (index, _) in buttons.enumerated() { 157 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.25 * Double( index )) { self.click( self.buttons[ index ] ) } 158 | } 159 | } 160 | 161 | 162 | } 163 | -------------------------------------------------------------------------------- /Examples/Examples/WindBlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WindBlow.swift 3 | // Examples 4 | // 5 | // Created by Alejandro Ramirez Varela on 6/29/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Tweener 11 | 12 | class WindBlow:UIView, FreezeProtocol 13 | { 14 | let dart:PDFImageView = PDFImageView(bundlename: "dart") 15 | let rotation:RotationAim = RotationAim() 16 | 17 | override init(frame: CGRect) 18 | { 19 | super.init(frame: frame) 20 | 21 | backgroundColor = UIColor(red:222.0/255.0, 22 | green:255.0/255.0, 23 | blue:220.0/255, 24 | alpha:1.0) 25 | 26 | let sunny = PDFImageView(bundlename: "sunny") 27 | sunny.scale = 0.75 28 | sunny.frame = CGRect(x:center.x - sunny.frame.size.width / 2.0, 29 | y:(center.y - sunny.frame.size.height) / 2.0, 30 | width:sunny.frame.size.width, 31 | height:sunny.frame.size.height) 32 | 33 | addSubview(sunny) 34 | 35 | let grass = UIView(frame:CGRect(x:0.0, 36 | y:self.frame.size.height * 0.9, 37 | width:self.frame.size.width, 38 | height:self.frame.size.height * 0.1)) 39 | grass.backgroundColor = UIColor(red:130.0/255.0, 40 | green:255.0/255.0, 41 | blue:170.0/255, 42 | alpha:1.0) 43 | addSubview(grass) 44 | 45 | let stick = UIView(frame:CGRect(x:center.x - 5.0, 46 | y:self.frame.size.height - 220.0, 47 | width:10.0, 48 | height:220.0)) 49 | 50 | stick.backgroundColor = UIColor(red:0.0, green:0.0, blue:140.0/255, alpha:1.0) 51 | addSubview(stick) 52 | 53 | 54 | dart.center = CGPoint(x:center.x, y:self.frame.size.height - 220.0) 55 | addSubview(dart) 56 | 57 | rotation.target = dart 58 | 59 | //Start animation 60 | animate() 61 | 62 | //Freeze 63 | freeze() 64 | } 65 | 66 | required init?(coder aDecoder: NSCoder) { 67 | fatalError("init(coder:) has not been implemented") 68 | } 69 | 70 | func animate() 71 | { 72 | let random = CGFloat.random(in: 1.0...5.0) 73 | 74 | Tween(target: rotation, 75 | duration: Double(random) * 2.0, 76 | ease: Ease.inOutQuad, 77 | delay: 0.0, 78 | to:[.key(\.distance, random)], 79 | completion: { self.animate() }).play() 80 | } 81 | 82 | func freeze() 83 | { 84 | Tweener.pauseTweens(target: rotation) 85 | } 86 | 87 | func warm() 88 | { 89 | Tweener.resumeTweens(target: rotation) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Examples/Resources/bb8-body.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/bb8-body.pdf -------------------------------------------------------------------------------- /Examples/Resources/bb8-head.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/bb8-head.pdf -------------------------------------------------------------------------------- /Examples/Resources/bee.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/bee.pdf -------------------------------------------------------------------------------- /Examples/Resources/card.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/card.pdf -------------------------------------------------------------------------------- /Examples/Resources/clouds1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/clouds1.pdf -------------------------------------------------------------------------------- /Examples/Resources/clouds2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/clouds2.pdf -------------------------------------------------------------------------------- /Examples/Resources/clouds3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/clouds3.pdf -------------------------------------------------------------------------------- /Examples/Resources/comet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/comet.pdf -------------------------------------------------------------------------------- /Examples/Resources/dart.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/dart.pdf -------------------------------------------------------------------------------- /Examples/Resources/earth.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/earth.pdf -------------------------------------------------------------------------------- /Examples/Resources/eyepupil.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/eyepupil.pdf -------------------------------------------------------------------------------- /Examples/Resources/fire.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/fire.pdf -------------------------------------------------------------------------------- /Examples/Resources/flower.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/flower.pdf -------------------------------------------------------------------------------- /Examples/Resources/jupyter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/jupyter.pdf -------------------------------------------------------------------------------- /Examples/Resources/little-earth.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/little-earth.pdf -------------------------------------------------------------------------------- /Examples/Resources/little-jupyter.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/little-jupyter.pdf -------------------------------------------------------------------------------- /Examples/Resources/little-saturn.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/little-saturn.pdf -------------------------------------------------------------------------------- /Examples/Resources/little-sun-fire.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/little-sun-fire.pdf -------------------------------------------------------------------------------- /Examples/Resources/little-sun.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/little-sun.pdf -------------------------------------------------------------------------------- /Examples/Resources/mars.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/mars.pdf -------------------------------------------------------------------------------- /Examples/Resources/moon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/moon.pdf -------------------------------------------------------------------------------- /Examples/Resources/orbits-background.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/orbits-background.pdf -------------------------------------------------------------------------------- /Examples/Resources/rocket.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/rocket.pdf -------------------------------------------------------------------------------- /Examples/Resources/sand.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/sand.pdf -------------------------------------------------------------------------------- /Examples/Resources/saturn.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/saturn.pdf -------------------------------------------------------------------------------- /Examples/Resources/spaceman.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/spaceman.pdf -------------------------------------------------------------------------------- /Examples/Resources/stars.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/stars.pdf -------------------------------------------------------------------------------- /Examples/Resources/sun.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/sun.pdf -------------------------------------------------------------------------------- /Examples/Resources/sunny.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/sunny.pdf -------------------------------------------------------------------------------- /Examples/Resources/ufo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Examples/Resources/ufo.pdf -------------------------------------------------------------------------------- /Gifs/bb8.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/bb8.gif -------------------------------------------------------------------------------- /Gifs/clouds.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/clouds.gif -------------------------------------------------------------------------------- /Gifs/drag.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/drag.gif -------------------------------------------------------------------------------- /Gifs/ease-curves.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/ease-curves.gif -------------------------------------------------------------------------------- /Gifs/extensions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/extensions.gif -------------------------------------------------------------------------------- /Gifs/eye.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/eye.gif -------------------------------------------------------------------------------- /Gifs/handlers.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/handlers.gif -------------------------------------------------------------------------------- /Gifs/orbits.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/orbits.gif -------------------------------------------------------------------------------- /Gifs/path-text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/path-text.gif -------------------------------------------------------------------------------- /Gifs/path.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/path.gif -------------------------------------------------------------------------------- /Gifs/random.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/random.gif -------------------------------------------------------------------------------- /Gifs/rotation-aim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/rotation-aim.gif -------------------------------------------------------------------------------- /Gifs/simple-tween.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/simple-tween.gif -------------------------------------------------------------------------------- /Gifs/text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/text.gif -------------------------------------------------------------------------------- /Gifs/timeline-editor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/timeline-editor.gif -------------------------------------------------------------------------------- /Gifs/timeline-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/timeline-loop.gif -------------------------------------------------------------------------------- /Gifs/timeline-ping-pong.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/timeline-ping-pong.gif -------------------------------------------------------------------------------- /Gifs/timeline-play.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/timeline-play.gif -------------------------------------------------------------------------------- /Gifs/timeline-zoom.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/timeline-zoom.gif -------------------------------------------------------------------------------- /Gifs/tmeline-inspector.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/tmeline-inspector.gif -------------------------------------------------------------------------------- /Gifs/tmeline-scroll.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/tmeline-scroll.gif -------------------------------------------------------------------------------- /Gifs/touch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/touch.gif -------------------------------------------------------------------------------- /Gifs/tweener.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/tweener.gif -------------------------------------------------------------------------------- /Gifs/tweenvisualizer-drag.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/tweenvisualizer-drag.gif -------------------------------------------------------------------------------- /Gifs/tweenvisualizer-pinch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/tweenvisualizer-pinch.gif -------------------------------------------------------------------------------- /Gifs/tweenvisualizer-resize.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/tweenvisualizer-resize.gif -------------------------------------------------------------------------------- /Gifs/tweenvisualizer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Gifs/tweenvisualizer.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Alejandro Ramirez Varela 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Tweener", 8 | platforms: [ 9 | .macOS(.v10_12), 10 | .iOS(.v10), 11 | .tvOS(.v10), 12 | .watchOS(.v3), 13 | ], 14 | products: [ 15 | .library( name: "Tweener", targets: ["Tweener"]), 16 | ], 17 | targets: [ 18 | .target( name: "Tweener", dependencies: [], path: "Source"), 19 | .testTarget(name: "TweenerTests", dependencies: ["Tweener"], path: "Tests"), 20 | ], 21 | swiftLanguageVersions: [.v5] 22 | ) 23 | -------------------------------------------------------------------------------- /Source/ArcAim.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArcAim.swift 3 | // Tweener 4 | // 5 | // Created by Alejandro Ramirez Varela on 5/28/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import AppKit 13 | #endif 14 | 15 | /// Updates view's position over circular radius. 16 | public class ArcAim : RotationAim 17 | { 18 | private var _orientToArc:Bool = false 19 | private var _center:CGPoint = CGPoint.zero 20 | private var _radius:CGFloat = 0.0 21 | private var _arcAngle:CGFloat = 0.0 22 | private var _arcAngleOffset:CGFloat = 0.0 23 | private var _arcPoint:CGPoint = CGPoint.zero 24 | 25 | /// Angle offset which adds to current angle to change start point. 26 | public var arcAngleOffset:CGFloat 27 | { 28 | set { 29 | _arcAngleOffset = BasicMath.toRadians(degree:newValue) 30 | update() 31 | } 32 | get {return _arcAngleOffset} 33 | } 34 | 35 | /// Angle degree to center view. 36 | public var arcAngle:CGFloat 37 | { 38 | set { 39 | _arcAngle = BasicMath.toRadians(degree: newValue) 40 | update() 41 | } 42 | get {return _arcAngle} 43 | } 44 | 45 | /// Angle radius to center view. 46 | public var radius:CGFloat 47 | { 48 | set{ 49 | _radius = newValue 50 | update() 51 | } 52 | get {return _radius} 53 | } 54 | 55 | /// Sets the center of rotation. 56 | public var center:CGPoint 57 | { 58 | set { 59 | _center = newValue 60 | update() 61 | } 62 | get {return _center} 63 | } 64 | 65 | ///Sets the desired angle by calculating point's angle from current center. 66 | public var arcPoint:CGPoint 67 | { 68 | set { 69 | _arcPoint = newValue 70 | _arcAngle = BasicMath.angle(start:_center, end:_arcPoint) 71 | update() 72 | } 73 | get {return _arcPoint} 74 | } 75 | 76 | ///Sets the desired angle by calculating point's angle from current center. 77 | public var orientToArc:Bool 78 | { 79 | set { 80 | _orientToArc = newValue 81 | update() 82 | } 83 | get {return _orientToArc} 84 | } 85 | /// Internal function to calculate and update view's position and rotation. 86 | private func update() 87 | { 88 | if self.target != nil { 89 | 90 | let rotation:CGPoint = BasicMath.arcRotationPoint(angle: _arcAngle + _arcAngleOffset, 91 | radius:_radius) 92 | //Update position 93 | #if os(iOS) || os(tvOS) 94 | self.target!.center = CGPoint(x:_center.x + rotation.x, y:_center.y + rotation.y) 95 | #elseif os(macOS) 96 | self.target!.center( CGPoint(x:_center.x + rotation.x, y:_center.y + rotation.y) ) 97 | #endif 98 | 99 | //Orient if is 100 | if _orientToArc {self.orientation = _center} 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Source/BasicMath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicMath.swift 3 | // Tweener 4 | // 5 | // Created by Alejandro Ramirez Varela on 5/28/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) || os(tvOS) 12 | import UIKit 13 | #elseif os(macOS) 14 | import AppKit 15 | #elseif os(watchOS) 16 | import WatchKit 17 | #endif 18 | 19 | /// A set of basic math functions. 20 | public class BasicMath 21 | { 22 | ///Time's round precision . 23 | static let timePrecision:Double = 10000000000000 24 | 25 | ///Round last digits to prevent Tween overwritting cause create and add Tweens takes a bit of computational effort. 26 | static func roundTime(_ t:Double) -> Double{ return Double(round(timePrecision * t) / timePrecision) } 27 | 28 | ///A function to calculate time intersections, more and less, not equal accuracy. 29 | static func intersects(x1:Double, x2:Double, y1:Double, y2:Double) -> Bool{ 30 | return roundTime(x2) > roundTime(y1) && roundTime(y2) > roundTime(x1) 31 | } 32 | 33 | /**Convert degree to radian. 34 | - Parameter degree: A `Double` value to turn into radian. 35 | - Returns: A calculated radian in `Double` value type. 36 | */ 37 | public static func toRadians(degree:Double) -> Double 38 | { 39 | return degree * Double.pi / 180.0 40 | } 41 | 42 | /**Convert degree to radian. 43 | - Parameter degree: A `Float` value to turn into radian. 44 | - Returns: A calculated radian in `Float` value type. 45 | */ 46 | public static func toRadians(degree:Float) -> Float 47 | { 48 | return degree * Float.pi / 180.0 49 | } 50 | 51 | /**Convert degree to radian. 52 | - Parameter degree: A `CGFloat` value to turn into radian. 53 | - Returns: A calculated radian in `CGFloat` value type. 54 | */ 55 | public static func toRadians(degree:CGFloat) -> CGFloat 56 | { 57 | return degree * CGFloat.pi / 180.0 58 | } 59 | 60 | /**Convert radian to degree. 61 | - Parameter radian: A `Double` value to turn into degree. 62 | - Returns: A calculated radian in `Double` value type. 63 | */ 64 | public static func toDegrees(radian:Double) -> Double 65 | { 66 | return radian * 180.0 / Double.pi 67 | } 68 | 69 | /**Convert radian to degree. 70 | - Parameter radian: A Float value to turn into degree. 71 | */ 72 | public static func toDegrees(radian:Float) -> Float 73 | { 74 | return radian * 180.0 / Float.pi 75 | } 76 | 77 | /**Convert radian to degree. 78 | - Parameter radian: A CGFloat value to turn into degree. 79 | */ 80 | public static func toDegrees(radian:CGFloat) -> CGFloat 81 | { 82 | return radian * 180.0 / CGFloat.pi 83 | } 84 | 85 | /**Calculate angle rotation between 2 points. 86 | - Parameter start: CGPoint 1. 87 | - Parameter end: CGPoint 2. 88 | */ 89 | public static func angle(start:CGPoint, end:CGPoint) -> CGFloat 90 | { 91 | return atan2(end.y - start.y, end.x - start.x) 92 | } 93 | 94 | /**Calculate length (distance) between 2 points. 95 | - Parameter start: CGPoint 1. 96 | - Parameter end: CGPoint 2. 97 | */ 98 | public static func length(start:CGPoint, end:CGPoint) -> CGFloat 99 | { 100 | let a:CGFloat = start.x - end.x 101 | let b:CGFloat = start.y - end.y 102 | return sqrt(a * a + b * b) 103 | } 104 | 105 | /**Calculate point location into circular angle rotation. 106 | - Parameter radius: CGFloat. 107 | - Parameter angle: CGFloat. 108 | - Returns: A calculated CGPoint postiton. 109 | */ 110 | public static func arcRotationPoint(angle:CGFloat, radius:CGFloat) -> CGPoint 111 | { 112 | let x:CGFloat = cos(angle) * radius 113 | let y:CGFloat = sin(angle) * radius 114 | return CGPoint(x:x, y:y) 115 | } 116 | 117 | /**Calculate point location into elliptical angle rotation. 118 | - Parameter xradius: CGFloat. 119 | - Parameter yradius: CGFloat. 120 | - Parameter angle: CGFloat. 121 | - Returns: A calculated CGPoint postiton. 122 | */ 123 | public static func ellipseRotationPoint(angle:CGFloat, xradius:CGFloat, yradius:CGFloat) -> CGPoint 124 | { 125 | let x:CGFloat = cos(angle) * xradius 126 | let y:CGFloat = sin(angle) * yradius 127 | return CGPoint(x:x, y:y) 128 | } 129 | 130 | //MARK: - Bezier functions. 131 | 132 | /// Calculates Quadratic tangent. 133 | public static func quadTangent(t:CGFloat, a:CGFloat, b:CGFloat, c:CGFloat) -> CGFloat 134 | { 135 | return (2 * (1 - t) * (b - a)) + (2 * t * (c - b)) 136 | } 137 | 138 | /// Calculates Cubic tangent 139 | public static func cubicTangent(t:CGFloat, a:CGFloat, b:CGFloat, c:CGFloat, d:CGFloat) -> CGFloat 140 | { 141 | return (3 * pow(1 - t, 2) * (b - a)) + 142 | (6 * (1 - t) * t * (c - b)) + 143 | (3 * pow(t, 2) * (d - c)) 144 | } 145 | 146 | /// Calculates Linear interpolation 147 | public static func linear(t:CGFloat, a:CGFloat, b:CGFloat) -> CGFloat 148 | { 149 | return a + (b - a) * t 150 | } 151 | 152 | /// Calculates Quadratic interpolation 153 | public static func quad(t:CGFloat, a:CGFloat, b:CGFloat, c:CGFloat) -> CGFloat 154 | { 155 | return (pow(1 - t, 2) * a) 156 | + (2 * (1 - t) * t * b) 157 | + (pow(t, 2) * c) 158 | } 159 | 160 | /// Calculates Cubic interpolation 161 | public static func cubic(t:CGFloat, a:CGFloat, b:CGFloat, c:CGFloat, d:CGFloat) -> CGFloat 162 | { 163 | return (pow(1 - t, 3) * a) + 164 | (3 * pow(1 - t, 2) * t * b) + 165 | (3 * (1 - t) * pow(t, 2) * c) + 166 | (pow(t, 3) * d) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Source/BezierUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BezierUtils.swift 3 | // Tweener 4 | // 5 | // Created by Alejandro Ramirez Varela on 5/28/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import AppKit 13 | #endif 14 | 15 | public class BezierUtils 16 | { 17 | //Linear interpolation between 2 points 18 | public static func linearInterpolation(time:CGFloat, start:CGPoint, end:CGPoint) -> CGPoint 19 | { 20 | return CGPoint(x:BasicMath.linear(t:time, a:start.x, b:end.x), 21 | y:BasicMath.linear(t:time, a:start.y, b:end.y)) 22 | } 23 | 24 | //Quadratic interpolation between 3 points 25 | public static func quadInterpolation(time:CGFloat, start:CGPoint, control:CGPoint, end:CGPoint) -> CGPoint 26 | { 27 | let x:CGFloat = BasicMath.quad(t:time, a:start.x, b:control.x, c:end.x) 28 | let y:CGFloat = BasicMath.quad(t:time, a:start.y, b:control.y, c:end.y) 29 | return CGPoint(x:x, y:y) 30 | } 31 | 32 | //Bezier Cubic interpolation between 4 points 33 | public static func bezierCubicInterpolation(time:CGFloat, start:CGPoint, controlStart:CGPoint, controlEnd:CGPoint, end:CGPoint) -> CGPoint 34 | { 35 | return CGPoint(x:BasicMath.cubic(t:time, a:start.x, b:controlStart.x, c:controlEnd.x, d:end.x), 36 | y:BasicMath.cubic(t:time, a:start.y, b:controlStart.y, c:controlEnd.y, d:end.y)) 37 | } 38 | 39 | //Quadratic angle 40 | public static func quadAngle(time:CGFloat, start:CGPoint, control:CGPoint, end:CGPoint) -> CGFloat 41 | { 42 | let x:CGFloat = BasicMath.quadTangent(t:time, a:start.x, b:control.x, c:end.x) 43 | let y:CGFloat = BasicMath.quadTangent(t:time, a:start.y, b:control.y, c:end.y) 44 | return atan2(y, x) 45 | } 46 | 47 | //Bezier cubic angle 48 | public static func bezierCubicAngle(time:CGFloat, start:CGPoint, controlStart:CGPoint, controlEnd:CGPoint, end:CGPoint) -> CGFloat 49 | { 50 | let x:CGFloat = BasicMath.cubicTangent(t:time, a:start.x, b:controlStart.x, c:controlEnd.x, d:end.x) 51 | let y:CGFloat = BasicMath.cubicTangent(t:time, a:start.y, b:controlStart.y, c:controlEnd.y, d:end.y) 52 | return atan2(y, x) 53 | } 54 | 55 | //Quad lenght 56 | public static func quadLength(start:CGPoint, control:CGPoint, end:CGPoint) -> CGFloat 57 | { 58 | //TODO:Pass inout array to collect points 59 | var divisions:CGFloat = 50.0 60 | var step:CGFloat = 1.0 / divisions 61 | 62 | //Optimize number of divisions 63 | let testPoint:CGPoint = quadInterpolation(time:step, start:start, control:control, end:end) 64 | let testLength:CGFloat = CGFloat(BasicMath.length(start:start, end:testPoint)) 65 | 66 | //fit divisions 67 | divisions = divisions * (testLength / 5.0) 68 | step = 1.0 / divisions 69 | 70 | var length:CGFloat = 0.0 71 | var prevPoint:CGPoint = start 72 | 73 | for i:Int in 1 ... Int(divisions) 74 | { 75 | let point:CGPoint = quadInterpolation(time:CGFloat(i) * step, start:start, control:control, end:end) 76 | length += CGFloat(BasicMath.length(start: prevPoint, end:point)) 77 | prevPoint = point 78 | } 79 | 80 | return length 81 | } 82 | 83 | 84 | //Bezier cubic lenght 85 | public static func bezierCubicLength(start:CGPoint, controlStart:CGPoint, controlEnd:CGPoint, end:CGPoint) -> CGFloat 86 | { 87 | //TODO:Pass array pointer to collect points 88 | 89 | var divisions:CGFloat = 50.0 90 | var step:CGFloat = 1.0 / divisions 91 | 92 | //TODO:optimize number of divisions 93 | let testPoint:CGPoint = bezierCubicInterpolation(time:step, start: start, controlStart: controlStart, controlEnd: controlEnd, end: end) 94 | let testLength:CGFloat = CGFloat(BasicMath.length(start:start, end:testPoint)) 95 | 96 | //fit divisions 97 | divisions = divisions * (testLength / 5.0) 98 | step = 1.0 / divisions 99 | 100 | var length:CGFloat = 0.0 101 | var prevPoint:CGPoint = start 102 | 103 | for i:Int in 1 ... Int(divisions) 104 | { 105 | let point:CGPoint = bezierCubicInterpolation(time:CGFloat(i) * step, start: start, controlStart: controlStart, controlEnd: controlEnd, end: end) 106 | length += CGFloat(BasicMath.length(start: prevPoint, end:point)) 107 | prevPoint = point 108 | } 109 | 110 | return length 111 | } 112 | 113 | //Calculate entire path lenght 114 | public static func bezierPathLength(path:CGPath) -> CGFloat 115 | { 116 | //TODO: pass array to collect separate lenghts 117 | return CGFloat(0.0) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Source/CGPathUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPathUtils.swift 3 | // Tweener 4 | // 5 | // Created by Alejandro Ramirez Varela on 7/16/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) || os(tvOS) 12 | import UIKit 13 | #elseif os(macOS) 14 | import AppKit 15 | #endif 16 | 17 | /// A set of CGPath utilities. 18 | public class CGPathUtils{ 19 | /// Gets the CGPath from a Font. 20 | public static func getFontPath(string:String, fontName:String, fontSize:CGFloat) -> CGPath 21 | { 22 | let letters = CGMutablePath() 23 | let ctFont = CTFontCreateWithName(fontName as CFString, fontSize, nil) 24 | let attrString = NSAttributedString(string: string, attributes: [kCTFontAttributeName as NSAttributedString.Key : ctFont]) 25 | let line = CTLineCreateWithAttributedString(attrString) 26 | let runArray = CTLineGetGlyphRuns(line) 27 | 28 | for runIndex in 0 ..< CFArrayGetCount(runArray) 29 | { 30 | let ctRun = unsafeBitCast(CFArrayGetValueAtIndex(runArray, runIndex), to: CTRun.self) 31 | let dictRef = CTRunGetAttributes(ctRun) 32 | let dict = dictRef as NSDictionary 33 | let runFont = dict[kCTFontAttributeName as String] as! CTFont 34 | 35 | for runGlyphIndex in 0 ..< CTRunGetGlyphCount(ctRun) 36 | { 37 | let thisGlyphRange = CFRangeMake(runGlyphIndex, 1) 38 | var glyph = CGGlyph() 39 | var position = CGPoint.zero 40 | 41 | CTRunGetGlyphs(ctRun, thisGlyphRange, &glyph) 42 | CTRunGetPositions(ctRun, thisGlyphRange, &position) 43 | 44 | let letter = CTFontCreatePathForGlyph(runFont, glyph, nil) 45 | let transform = CGAffineTransform(translationX: position.x, y: position.y) 46 | if let letter = letter {letters.addPath(letter, transform: transform)} 47 | } 48 | } 49 | 50 | return letters 51 | } 52 | /// Flips a CGPath vertically. 53 | public static func flipPathVertically(path:CGPath) -> CGPath 54 | { 55 | let boundingBox = path.boundingBox 56 | let transformPath = CGMutablePath() 57 | let scale = CGAffineTransform(scaleX: 1.0, y: -1.0) 58 | transformPath.addPath(path, transform: scale) 59 | 60 | let translatePath = CGMutablePath() 61 | let translate = CGAffineTransform(translationX: 0.0, y: boundingBox.size.height) 62 | translatePath.addPath(transformPath, transform: translate) 63 | 64 | return translatePath 65 | } 66 | 67 | /// Translates x and y CGPath's points. 68 | public static func translatePath(path:CGPath, x:CGFloat, y:CGFloat) -> CGPath 69 | { 70 | let translatePath = CGMutablePath() 71 | let translate = CGAffineTransform(translationX:x, y:y) 72 | translatePath.addPath(path, transform: translate) 73 | 74 | return translatePath 75 | } 76 | 77 | /// Makes a rounded CGPath rectangle. 78 | public static func makeRoundRect(rect:CGRect, cornerRadius:CGFloat) -> CGPath 79 | { 80 | var cornerRadius = cornerRadius 81 | 82 | if (rect.size.width / 2 < cornerRadius) 83 | {cornerRadius = rect.size.width / 2} 84 | 85 | if rect.size.height / 2 < cornerRadius 86 | {cornerRadius = rect.size.height / 2} 87 | 88 | let path = CGMutablePath() 89 | 90 | path.move(to: CGPoint(x:rect.origin.x, y:rect.origin.y + rect.size.height - cornerRadius)) 91 | 92 | path.addArc(tangent1End: CGPoint(x:rect.origin.x, 93 | y:rect.origin.y), 94 | tangent2End: CGPoint(x:rect.origin.x + rect.size.width, 95 | y:rect.origin.y), 96 | radius: cornerRadius) 97 | 98 | path.addArc(tangent1End: CGPoint(x:rect.origin.x + rect.size.width, 99 | y:rect.origin.y), 100 | tangent2End: CGPoint(x:rect.origin.x + rect.size.width, 101 | y:rect.origin.y + rect.size.height), 102 | radius: cornerRadius) 103 | 104 | path.addArc(tangent1End: CGPoint(x:rect.origin.x + rect.size.width, 105 | y:rect.origin.y + rect.size.height), 106 | tangent2End: CGPoint(x:rect.origin.x, 107 | y:rect.origin.y + rect.size.height), 108 | radius: cornerRadius) 109 | 110 | path.addArc(tangent1End: CGPoint(x:rect.origin.x, 111 | y:rect.origin.y + rect.size.height), 112 | tangent2End: CGPoint(x:rect.origin.x, 113 | y:rect.origin.y), 114 | radius: cornerRadius) 115 | 116 | path.closeSubpath() 117 | 118 | return path 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Source/PDFImageRender.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDFImageView.m 3 | // Tweener 4 | // 5 | // Created by Alejandro Ramirez Varela on 2/15/16. 6 | // Copyright © 2016 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | //TODO:Add macOS support. 10 | #if os(iOS) 11 | import UIKit 12 | 13 | /// Renders a PDF document in to a UIImage. 14 | open class PDFImageRender 15 | { 16 | //public static var staticBundle:Bundle? 17 | 18 | public var data: Data? 19 | public var document: CGPDFDocument? 20 | public var pageCount: Int 21 | { 22 | if self.document == nil{return 0} 23 | else {return (self.document?.numberOfPages)!} 24 | } 25 | 26 | public func setPDFData(data:Data?) 27 | { 28 | if (data != nil) 29 | { 30 | self.data = data 31 | //TODO:validate file 32 | let provider: CGDataProvider = CGDataProvider(data: self.data! as CFData )! 33 | self.document = CGPDFDocument(provider) 34 | if self.document == nil {print("document nil")} 35 | }else 36 | { 37 | print("Error invalid file") 38 | } 39 | } 40 | 41 | public func loadFromBundle(filename: String) 42 | { 43 | let path:String = Bundle.main.path(forResource: filename, ofType: ".pdf")! 44 | loadFile(path: path) 45 | } 46 | 47 | public func loadFile(path:String) 48 | { 49 | setPDFData(data: FileManager.default.contents(atPath: path)) 50 | } 51 | 52 | /// Get current page size 53 | public func getPageSize(page:Int) ->CGSize 54 | { 55 | if self.data == nil {return CGSize.zero} 56 | return self.document!.page(at: page)!.getBoxRect(.cropBox).size 57 | } 58 | /// Render page 59 | public func renderPage(page: Int, scale:Double) -> UIImage? 60 | { 61 | if self.document == nil {return nil} 62 | 63 | //Add retina scale support 64 | let retinaScale: CGFloat = CGFloat(scale) * UIScreen.main.scale 65 | 66 | //Get page 67 | let pdfPage: CGPDFPage = self.document!.page(at: page)! 68 | 69 | //Get the rectangle of the cropped inside 70 | var mediaRect: CGRect = pdfPage.getBoxRect(.cropBox) 71 | mediaRect.size.width *= retinaScale 72 | mediaRect.size.height *= retinaScale 73 | 74 | //Get graphic context 75 | UIGraphicsBeginImageContext(mediaRect.size) 76 | let context: CGContext = UIGraphicsGetCurrentContext()! 77 | 78 | //Clear 79 | context.clear(mediaRect) 80 | 81 | //Flip y coordinates 82 | context.scaleBy(x: retinaScale, y: -retinaScale) 83 | context.translateBy(x: 0.0, y: -(mediaRect.size.height / retinaScale)) 84 | 85 | //Draw it and generate UIImage 86 | context.drawPDFPage(pdfPage) 87 | let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()! 88 | UIGraphicsEndImageContext() 89 | 90 | //Return new image with retina scale 91 | return UIImage(cgImage: image.cgImage!, scale: UIScreen.main.scale, orientation: image.imageOrientation) 92 | } 93 | } 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /Source/PDFImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDFImageView.m 3 | // Tweener 4 | // 5 | // Created by Alejandro Ramirez Varela on 3/16/18. 6 | // Copyright © 2018 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | /// Creates a UIImageView which renders and displays a PDF document's CGImage. 12 | open class PDFImageView : UIImageView 13 | { 14 | public let pdf:PDFImageRender = PDFImageRender() 15 | 16 | private var _scale: Double = 1.0 17 | 18 | /// Changes render scale, affects UIImageView's size.. 19 | public var scale: Double 20 | { 21 | set 22 | { 23 | if newValue > 0.0 24 | { 25 | _scale = newValue 26 | updateImage() 27 | } 28 | } 29 | 30 | get{return _scale} 31 | } 32 | 33 | private var _currentPage: Int = 1 34 | 35 | /// Changes document's page, first page is default. 36 | public var currentPage: Int 37 | { 38 | set 39 | { 40 | if (newValue <= self.pdf.pageCount && newValue > 0) 41 | { 42 | _currentPage = newValue 43 | updateImage() 44 | } 45 | } 46 | get 47 | { 48 | if self.pdf.data == nil 49 | { 50 | return 0 51 | } 52 | else 53 | { 54 | return _currentPage 55 | } 56 | } 57 | } 58 | 59 | /// Initializer. 60 | public convenience init() 61 | { 62 | self.init(frame: CGRect.zero) 63 | } 64 | 65 | /// UIView's init(frame: CGRect) initializer. 66 | public override init(frame: CGRect) 67 | { 68 | super.init(frame: frame) 69 | } 70 | 71 | /// Initializes with a pdf file name located in App's main bundle. 72 | /// - Parameter bundlename: String with pdf document's name in bundle. 73 | public convenience init(bundlename:String) 74 | { 75 | self.init() 76 | loadFromBundle(bundlename) 77 | } 78 | 79 | /// Initializes with a file path to resource. 80 | /// - Parameter filepath: String with document's name in bundle. 81 | public convenience init(filepath:String) 82 | { 83 | self.init() 84 | loadFile(filepath) 85 | } 86 | 87 | required public init?(coder aDecoder: NSCoder) 88 | { 89 | super.init(coder: aDecoder) 90 | } 91 | 92 | /// Loads a document located in App's main bundle. 93 | /// - Parameter filename: String with document's name in bundle. 94 | public func loadFromBundle(_ filename: String) 95 | { 96 | self.pdf.loadFromBundle(filename: filename) 97 | if self.pdf.data != nil{updateImage()} 98 | } 99 | 100 | /// Loads a file from path to resource. 101 | /// - Parameter path: String with document's name in bundle. 102 | public func loadFile(_ path:String) 103 | { 104 | self.pdf.loadFile(path: path) 105 | if self.pdf.data != nil{updateImage()} 106 | } 107 | 108 | /// Refresh pdf's image. 109 | public func updateImage() 110 | { 111 | if self.pdf.document != nil 112 | { 113 | self.image = self.pdf.renderPage(page:self.currentPage, scale:self.scale) 114 | self.frame = CGRect(x: self.frame.origin.x, 115 | y: self.frame.origin.y, 116 | width: self.image!.size.width, 117 | height: self.image!.size.height) 118 | } 119 | } 120 | } 121 | #endif 122 | -------------------------------------------------------------------------------- /Source/RotationAim.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RotationAim.swift 3 | // Tweener 4 | // 5 | // Created by Alejandro Ramirez Varela on 5/28/19. 6 | // Copyright © 2019 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | import UIKit 11 | #elseif os(macOS) 12 | import AppKit 13 | #endif 14 | 15 | 16 | public typealias AimRotationHandler = (CGFloat) -> Void 17 | 18 | /// Roatates view's CALayer over z-axis. 19 | public class RotationAim 20 | { 21 | ///An optional block to handle updates. 22 | public var onUpdateRotation:AimRotationHandler? 23 | 24 | #if os(iOS) || os(tvOS) 25 | /// UIView target. 26 | public var target:UIView? 27 | #elseif os(macOS) 28 | /// NSView target. 29 | public var target:NSView? 30 | #endif 31 | 32 | private var _rotation:CGFloat = 0.0//in radians 33 | private var _rotationOffset:CGFloat = 0.0 34 | private var _distance:CGFloat = 0.0 35 | private var _orientation:CGPoint = CGPoint.zero 36 | 37 | public init(){} 38 | 39 | #if os(iOS) || os(tvOS) 40 | /** 41 | Initializer. 42 | - Parameter target: An UIView to handle rotation. 43 | */ 44 | public convenience init(target:UIView) 45 | { 46 | self.init() 47 | self.target = target 48 | } 49 | #elseif os(macOS) 50 | /** 51 | Initializer. 52 | - Parameter target: An NSView to handle rotation. 53 | */ 54 | public convenience init(target:NSView) 55 | { 56 | self.init() 57 | if target.layer == nil {target.wantsLayer = true} 58 | target.layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5) 59 | self.target = target 60 | } 61 | #endif 62 | 63 | ///Sets Rotation in degrees. 64 | public var angle:CGFloat 65 | { 66 | set { rotation = BasicMath.toRadians(degree:newValue)} 67 | get { return BasicMath.toDegrees(radian:_rotation)} 68 | } 69 | 70 | /// Adds an offset to adjust current angle in degrees. 71 | public var angleOffset:CGFloat 72 | { 73 | set { rotationOffset = BasicMath.toRadians(degree:newValue)} 74 | get { return BasicMath.toDegrees(radian:_rotationOffset)} 75 | } 76 | 77 | /// Sets Rotation in radians. 78 | public var rotation:CGFloat 79 | { 80 | set { 81 | _rotation = newValue 82 | 83 | if onUpdateRotation != nil { onUpdateRotation!(_rotation + _rotationOffset) } 84 | 85 | //Apply transform 86 | #if os(iOS) || os(tvOS) 87 | self.target?.layer.transform = CATransform3DMakeRotation(_rotation + _rotationOffset, 0.0, 0.0, 1.0) 88 | #elseif os(macOS) 89 | self.target?.layer?.transform = CATransform3DMakeRotation(_rotation + _rotationOffset, 0.0, 0.0, 1.0) 90 | #endif 91 | } 92 | get {return _rotation} 93 | } 94 | 95 | /// Adds an offset to adjust current angle in radians. 96 | public var rotationOffset:CGFloat 97 | { 98 | set { 99 | _rotationOffset = newValue 100 | //update rotation 101 | rotation = _rotation 102 | } 103 | get {return _rotationOffset} 104 | } 105 | 106 | ///Converts linear distance to rotation angle. 107 | public var distance:CGFloat 108 | { 109 | set { 110 | //Store value 111 | _distance = newValue 112 | //Refresh Angle 113 | angle = (newValue - floor(newValue)) * 360.0 114 | } 115 | get {return _distance} 116 | } 117 | 118 | ///Orients target to specific point. 119 | public var orientation:CGPoint 120 | { 121 | set { 122 | _orientation = newValue 123 | //TODO:add offset? 124 | #if os(iOS) || os(tvOS) 125 | rotation = CGFloat(BasicMath.angle(start:target!.center, end:newValue)) 126 | #elseif os(macOS) 127 | rotation = CGFloat(BasicMath.angle(start:target!.center(), end:newValue)) 128 | #endif 129 | } 130 | get {return _orientation} 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import TweenerTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += TweenerPackageTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/Tweener/Tweener.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import TweenerPackage 3 | 4 | final class TweenerTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | //XCTAssertEqual(Tweener(), "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Tests/Tweener/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(TweenerPackageTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tweener iOs/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tweener iOs/Tweener_iOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tweener_iOS.h 3 | // Tweener iOS 4 | // 5 | // Created by Alejandro Ramirez Varela on 10/19/19. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for Tweener_iOS. 11 | FOUNDATION_EXPORT double Tweener_iOSVersionNumber; 12 | 13 | //! Project version string for Tweener_iOS. 14 | FOUNDATION_EXPORT const unsigned char Tweener_iOSVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Tweener macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tweener macOS/Tweener_macOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tweener_macOS.h 3 | // Tweener macOS 4 | // 5 | // Created by Alejandro Ramirez Varela on 10/19/19. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for Tweener_macOS. 11 | FOUNDATION_EXPORT double Tweener_macOSVersionNumber; 12 | 13 | //! Project version string for Tweener_macOS. 14 | FOUNDATION_EXPORT const unsigned char Tweener_macOSVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Tweener tvOs/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tweener tvOs/Tweener_tvOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tweener_tvOS.h 3 | // Tweener tvOS 4 | // 5 | // Created by Alejandro Ramirez Varela on 10/19/19. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for Tweener_tvOS. 11 | FOUNDATION_EXPORT double Tweener_tvOSVersionNumber; 12 | 13 | //! Project version string for Tweener_tvOS. 14 | FOUNDATION_EXPORT const unsigned char Tweener_tvOSVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Tweener watchOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tweener watchOS/Tweener_watchOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tweener_watchOS.h 3 | // Tweener watchOS 4 | // 5 | // Created by Alejandro Ramirez Varela on 10/19/19. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for Tweener_watchOS. 11 | FOUNDATION_EXPORT double Tweener_watchOSVersionNumber; 12 | 13 | //! Project version string for Tweener_watchOS. 14 | FOUNDATION_EXPORT const unsigned char Tweener_watchOSVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | 18 | 19 | -------------------------------------------------------------------------------- /Tweener.framework.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/Tweener.framework.zip -------------------------------------------------------------------------------- /Tweener.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Tweener' 3 | s.version = '2.1.1' 4 | s.summary = 'Swift animation engine, make more powerful and creative Apps.' 5 | s.homepage = 'https://github.com/alexrvarela/SwiftTweener' 6 | s.license = { :type => 'MIT', :file => 'LICENSE' } 7 | s.author = { 'alexrvarela' => 'https://github.com/alexrvarela' } 8 | s.source = { :git => 'https://github.com/alexrvarela/SwiftTweener.git', :tag => s.version.to_s } 9 | s.social_media_url = 'https://twitter.com/alexrvarela' 10 | s.ios.deployment_target = '10.0' 11 | s.swift_version = "5.0" 12 | s.source_files = 'Source/*.{swift}' 13 | end 14 | -------------------------------------------------------------------------------- /Tweener.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tweener.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tweener.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tweener.xcodeproj/xcshareddata/xcschemes/Tweener iOs.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Tweener.xcodeproj/xcshareddata/xcschemes/Tweener macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Tweener/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tweener/Tweener.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tweener.h 3 | // Tweener 4 | // 5 | // Created by Alejandro Ramirez on 9/7/18. 6 | // Copyright © 2018 Alejandro Ramirez Varela. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Tweener. 12 | FOUNDATION_EXPORT double TweenerVersionNumber; 13 | 14 | //! Project version string for Tweener. 15 | FOUNDATION_EXPORT const unsigned char TweenerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 86% 23 | 24 | 25 | 86% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/docsets/Tweener.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.tweener 7 | CFBundleName 8 | Tweener 9 | DocSetPlatformFamily 10 | tweener 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/Tweener.docset/Contents/Resources/Documents/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 85% 23 | 24 | 25 | 85% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docsets/Tweener.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/docsets/Tweener.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/docs/docsets/Tweener.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/Tweener.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/docs/docsets/Tweener.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/Tweener.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/docs/docsets/Tweener.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/Tweener.docset/Contents/Resources/Documents/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/docs/docsets/Tweener.docset/Contents/Resources/Documents/img/spinner.gif -------------------------------------------------------------------------------- /docs/docsets/Tweener.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | 61 | // KaTeX rendering 62 | if ("katex" in window) { 63 | $($('.math').each( (_, element) => { 64 | katex.render(element.textContent, element, { 65 | displayMode: $(element).hasClass('m-block'), 66 | throwOnError: false, 67 | trust: true 68 | }); 69 | })) 70 | } 71 | -------------------------------------------------------------------------------- /docs/docsets/Tweener.docset/Contents/Resources/Documents/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $typeahead = $('[data-typeahead]'); 3 | var $form = $typeahead.parents('form'); 4 | var searchURL = $form.attr('action'); 5 | 6 | function displayTemplate(result) { 7 | return result.name; 8 | } 9 | 10 | function suggestionTemplate(result) { 11 | var t = '
'; 12 | t += '' + result.name + ''; 13 | if (result.parent_name) { 14 | t += '' + result.parent_name + ''; 15 | } 16 | t += '
'; 17 | return t; 18 | } 19 | 20 | $typeahead.one('focus', function() { 21 | $form.addClass('loading'); 22 | 23 | $.getJSON(searchURL).then(function(searchData) { 24 | const searchIndex = lunr(function() { 25 | this.ref('url'); 26 | this.field('name'); 27 | this.field('abstract'); 28 | for (const [url, doc] of Object.entries(searchData)) { 29 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 30 | } 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3, 37 | autoselect: true 38 | }, 39 | { 40 | limit: 10, 41 | display: displayTemplate, 42 | templates: { suggestion: suggestionTemplate }, 43 | source: function(query, sync) { 44 | const lcSearch = query.toLowerCase(); 45 | const results = searchIndex.query(function(q) { 46 | q.term(lcSearch, { boost: 100 }); 47 | q.term(lcSearch, { 48 | boost: 10, 49 | wildcard: lunr.Query.wildcard.TRAILING 50 | }); 51 | }).map(function(result) { 52 | var doc = searchData[result.ref]; 53 | doc.url = result.ref; 54 | return doc; 55 | }); 56 | sync(results); 57 | } 58 | } 59 | ); 60 | $form.removeClass('loading'); 61 | $typeahead.trigger('focus'); 62 | }); 63 | }); 64 | 65 | var baseURL = searchURL.slice(0, -"search.json".length); 66 | 67 | $typeahead.on('typeahead:select', function(e, result) { 68 | window.location = baseURL + result.url; 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /docs/docsets/Tweener.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/docs/docsets/Tweener.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/Tweener.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/docs/docsets/Tweener.tgz -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/docs/img/gh.png -------------------------------------------------------------------------------- /docs/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexrvarela/SwiftTweener/67767bb5ca0084d2a9629ae8ca6bd87177692fe6/docs/img/spinner.gif -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token'); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | 61 | // KaTeX rendering 62 | if ("katex" in window) { 63 | $($('.math').each( (_, element) => { 64 | katex.render(element.textContent, element, { 65 | displayMode: $(element).hasClass('m-block'), 66 | throwOnError: false, 67 | trust: true 68 | }); 69 | })) 70 | } 71 | -------------------------------------------------------------------------------- /docs/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $typeahead = $('[data-typeahead]'); 3 | var $form = $typeahead.parents('form'); 4 | var searchURL = $form.attr('action'); 5 | 6 | function displayTemplate(result) { 7 | return result.name; 8 | } 9 | 10 | function suggestionTemplate(result) { 11 | var t = '
'; 12 | t += '' + result.name + ''; 13 | if (result.parent_name) { 14 | t += '' + result.parent_name + ''; 15 | } 16 | t += '
'; 17 | return t; 18 | } 19 | 20 | $typeahead.one('focus', function() { 21 | $form.addClass('loading'); 22 | 23 | $.getJSON(searchURL).then(function(searchData) { 24 | const searchIndex = lunr(function() { 25 | this.ref('url'); 26 | this.field('name'); 27 | this.field('abstract'); 28 | for (const [url, doc] of Object.entries(searchData)) { 29 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 30 | } 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3, 37 | autoselect: true 38 | }, 39 | { 40 | limit: 10, 41 | display: displayTemplate, 42 | templates: { suggestion: suggestionTemplate }, 43 | source: function(query, sync) { 44 | const lcSearch = query.toLowerCase(); 45 | const results = searchIndex.query(function(q) { 46 | q.term(lcSearch, { boost: 100 }); 47 | q.term(lcSearch, { 48 | boost: 10, 49 | wildcard: lunr.Query.wildcard.TRAILING 50 | }); 51 | }).map(function(result) { 52 | var doc = searchData[result.ref]; 53 | doc.url = result.ref; 54 | return doc; 55 | }); 56 | sync(results); 57 | } 58 | } 59 | ); 60 | $form.removeClass('loading'); 61 | $typeahead.trigger('focus'); 62 | }); 63 | }); 64 | 65 | var baseURL = searchURL.slice(0, -"search.json".length); 66 | 67 | $typeahead.on('typeahead:select', function(e, result) { 68 | window.location = baseURL + result.url; 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |

Home of github pages

2 | --------------------------------------------------------------------------------