├── .gitignore ├── LICENSE ├── README.md ├── Resources └── example1.gif ├── TTFortuneWheel.podspec ├── TTFortuneWheel.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── TTFortuneWheel.xcscheme ├── TTFortuneWheel ├── FortuneWheelLayer.swift ├── Info.plist ├── Resources │ └── Assets.xcassets │ │ ├── Contents.json │ │ └── niddleImage.imageset │ │ ├── Contents.json │ │ ├── point@2x.png │ │ └── point@3x.png ├── SpinningWheelAnimator.swift ├── SpinningWheelSlice .swift ├── SpinningWheelSliceImpl.swift ├── TTFortuneWheel.h ├── TTFortuneWheel.swift └── Utils.swift └── TTFortuneWheelSample ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── nidleImage.imageset │ ├── Contents.json │ ├── nidleImage@2x.png │ └── nidleImage@3x.png ├── spin.imageset │ ├── Contents.json │ ├── spin@2x.png │ └── spin@3x.png ├── spinningWhelAxel.imageset │ ├── Contents.json │ ├── axel@2x.png │ └── axel@3x.png └── wheelFrame.imageset │ ├── Contents.json │ ├── frame@2x.png │ └── frame@3x.png ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── CarnivalWheelSlice.swift ├── Info.plist ├── Resources ├── BEBAS___.ttf ├── Chunkfive.otf ├── Lobster_1.3.otf └── Phosphate.ttc └── ViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | # mac 4 | .DS_Store 5 | 6 | ### Swift ### 7 | # Xcode 8 | # 9 | build/ 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | *.xccheckout 20 | *.moved-aside 21 | DerivedData 22 | *.hmap 23 | *.ipa 24 | *.xcuserstate -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Tapptitude 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TTFortuneWheel-iOS 2 | 3 | ![](https://img.shields.io/badge/Swift-4.0-green.svg?style=flat) 4 | [![Platform](https://img.shields.io/cocoapods/p/TTFortuneWheel.svg)](https://cocoapods.org/pods/TTFortuneWheel) 5 | [![Version](https://img.shields.io/cocoapods/v/TTFortuneWheel.svg)](https://cocoapods.org/pods/TTFortuneWheel) 6 | [![License](https://img.shields.io/cocoapods/l/TTFortuneWheel.svg?style=flat)](http://cocoapods.org/pods/TTFortuneWheel) 7 | [![Twitter](https://img.shields.io/badge/Twitter-@Tapptitude-blue.svg?style=flat)](http://twitter.com/Tapptitude) 8 | 9 | ## About 10 | Fortune spinning wheel that supports custom drawing and dynamic content. 11 | 12 | This project is maintained by Tapptitude, a mobile app development agency specialized in building high-quality iOS and Android mobile apps, for startups and brands alike. A lively team of skilled app developers and app designers based in Europe, we provide full-stack mobile app development services to entrepreneurs looking to innovate on mobile. 13 | 14 | ![](Resources/example1.gif) 15 | 16 | ## Features: 17 | - Dynamic content 18 | - Each slice can have it's own style and angle 19 | - Drawn and animated using CoreGraphics 20 | - Customize(Font, Slice Color, Stroke, Frame color, width and shadow) 21 | - Predictible rotation to any slice 22 | - Exposes methods for custom Core Graphics drawings for each slice 23 | - Dynamic size 24 | 25 | ## Requirements 26 | 27 | - iOS 9.0+ 28 | - Xcode 8.2+ 29 | 30 | ## Installation 31 | 32 | _CocoaPods_ 33 | 34 | _Swift 5.0_ 35 | 36 | ```ruby 37 | pod 'TTFortuneWheel' 38 | ``` 39 | 40 | _Swift 4.2_ 41 | 42 | ```ruby 43 | pod 'TTFortuneWheel', '~>0.1.6' 44 | ``` 45 | 46 | _Swift 4.0_ 47 | 48 | ```ruby 49 | pod 'TTFortuneWheel', '~>0.1.5' 50 | ``` 51 | 52 | _Carthage_ 53 | 54 | ``` 55 | github "tapptitude/TTFortuneWheel-iOS" 56 | ``` 57 | 58 | _Manually_ 59 | 60 | Add contents of TTFortuneWheel folder to your project. 61 | 62 | ## Usage 63 | 64 | You can place the FortuneWheel in your storyboard or add it as sa subview programatically. 65 | 66 | ```swift 67 | 68 | override func viewDidLoad() { 69 | super.viewDidLoad() 70 | 71 | let slices = [ FortuneWheelSlice(title: "Slice 1"), 72 | FortuneWheelSlice(title: "Slice 2"), 73 | FortuneWheelSlice(title: "Slice 3")] 74 | 75 | let fortuneWheel = TTFortuneWheel(frame: frame, slices:slices) 76 | spinningWheel.equalSlices = true 77 | self.view.addSubview(fortuneWheel) 78 | } 79 | ``` 80 | ## How to customize? 81 | 82 | In order to provide custom drawing for slices you have to implement `FortuneWheelSliceProtocol`. 83 | or can use the preconfiugred `FortuneWheelSliceImpl` class. 84 | 85 | ## Contribution 86 | 87 | Feel free to Fork, submit Pull Requests or send us your feedback and suggestions! 88 | 89 | 90 | ## License 91 | 92 | TTFortuneWheel is available under the MIT license. See the LICENSE file for more info. 93 | -------------------------------------------------------------------------------- /Resources/example1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/Resources/example1.gif -------------------------------------------------------------------------------- /TTFortuneWheel.podspec: -------------------------------------------------------------------------------- 1 | 2 | # 3 | 4 | Pod::Spec.new do |s| 5 | s.name = 'TTFortuneWheel' 6 | s.version = '0.1.8' 7 | s.summary = 'Fortune spinning wheel that supports custom drawing.' 8 | s.homepage = 'https://tapptitude.com/' 9 | s.license = { :type => 'MIT', :file => 'LICENSE' } 10 | s.author = { 'Efraim Budusan' => 'efraim.budusan@tapptitude.com' } 11 | s.source = { :git => 'https://github.com/tapptitude/TTFortuneWheel-iOS.git', :tag => s.version.to_s } 12 | s.ios.deployment_target = '9.0' 13 | s.requires_arc = true 14 | s.source_files = 'TTFortuneWheel/*.{swift}' 15 | s.resource_bundles = { 16 | 'TTFortuneWheel' => ['TTFortuneWheel/Resources/**/*'] 17 | } 18 | s.swift_version = '5.0' 19 | end 20 | -------------------------------------------------------------------------------- /TTFortuneWheel.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D7043A2D1FE29CBB0064F43D /* TTFortuneWheel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D74F36A91FA86966003D0416 /* TTFortuneWheel.framework */; }; 11 | D7043A2E1FE29D310064F43D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D7D7F0451FA88B3B0042B5C6 /* Assets.xcassets */; }; 12 | D7043A301FE29DAD0064F43D /* TTFortuneWheel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D74F36A91FA86966003D0416 /* TTFortuneWheel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 13 | D70B495D1FA86B9100231E07 /* SpinningWheelAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70B49571FA86B9100231E07 /* SpinningWheelAnimator.swift */; }; 14 | D70B495F1FA86B9100231E07 /* FortuneWheelLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70B49581FA86B9100231E07 /* FortuneWheelLayer.swift */; }; 15 | D70B49611FA86B9100231E07 /* SpinningWheelSlice .swift in Sources */ = {isa = PBXBuildFile; fileRef = D70B49591FA86B9100231E07 /* SpinningWheelSlice .swift */; }; 16 | D70B49631FA86B9100231E07 /* SpinningWheelSliceImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70B495A1FA86B9100231E07 /* SpinningWheelSliceImpl.swift */; }; 17 | D70B49651FA86B9100231E07 /* TTFortuneWheel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70B495B1FA86B9100231E07 /* TTFortuneWheel.swift */; }; 18 | D70B49671FA86B9100231E07 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70B495C1FA86B9100231E07 /* Utils.swift */; }; 19 | D74F36AE1FA86966003D0416 /* TTFortuneWheel.h in Headers */ = {isa = PBXBuildFile; fileRef = D74F36AC1FA86966003D0416 /* TTFortuneWheel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 20 | D74F36BB1FA86AD7003D0416 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F36BA1FA86AD7003D0416 /* AppDelegate.swift */; }; 21 | D74F36BD1FA86AD7003D0416 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74F36BC1FA86AD7003D0416 /* ViewController.swift */; }; 22 | D74F36C01FA86AD7003D0416 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D74F36BE1FA86AD7003D0416 /* Main.storyboard */; }; 23 | D74F36C21FA86AD7003D0416 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D74F36C11FA86AD7003D0416 /* Assets.xcassets */; }; 24 | D74F36C51FA86AD7003D0416 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D74F36C31FA86AD7003D0416 /* LaunchScreen.storyboard */; }; 25 | D7E515CE1FA9C3C900218F8A /* CarnivalWheelSlice.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E515CD1FA9C3C900218F8A /* CarnivalWheelSlice.swift */; }; 26 | D7E515D81FA9C63C00218F8A /* BEBAS___.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7E515D31FA9C61600218F8A /* BEBAS___.ttf */; }; 27 | D7E515D91FA9C63C00218F8A /* Phosphate.ttc in Resources */ = {isa = PBXBuildFile; fileRef = D7E515D21FA9C60900218F8A /* Phosphate.ttc */; }; 28 | D7E515DA1FA9C63C00218F8A /* Lobster_1.3.otf in Resources */ = {isa = PBXBuildFile; fileRef = D7E515D11FA9C5E800218F8A /* Lobster_1.3.otf */; }; 29 | D7E515DB1FA9C63C00218F8A /* Chunkfive.otf in Resources */ = {isa = PBXBuildFile; fileRef = D7E515D01FA9C5D800218F8A /* Chunkfive.otf */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | D7043A311FE29DAD0064F43D /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = D74F36A01FA86966003D0416 /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = D74F36A81FA86966003D0416; 38 | remoteInfo = TTFortuneWheel; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXCopyFilesBuildPhase section */ 43 | D7043A331FE29DAD0064F43D /* Embed Frameworks */ = { 44 | isa = PBXCopyFilesBuildPhase; 45 | buildActionMask = 2147483647; 46 | dstPath = ""; 47 | dstSubfolderSpec = 10; 48 | files = ( 49 | D7043A301FE29DAD0064F43D /* TTFortuneWheel.framework in Embed Frameworks */, 50 | ); 51 | name = "Embed Frameworks"; 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXCopyFilesBuildPhase section */ 55 | 56 | /* Begin PBXFileReference section */ 57 | D70B49571FA86B9100231E07 /* SpinningWheelAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpinningWheelAnimator.swift; sourceTree = ""; }; 58 | D70B49581FA86B9100231E07 /* FortuneWheelLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FortuneWheelLayer.swift; sourceTree = ""; }; 59 | D70B49591FA86B9100231E07 /* SpinningWheelSlice .swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SpinningWheelSlice .swift"; sourceTree = ""; }; 60 | D70B495A1FA86B9100231E07 /* SpinningWheelSliceImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpinningWheelSliceImpl.swift; sourceTree = ""; }; 61 | D70B495B1FA86B9100231E07 /* TTFortuneWheel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TTFortuneWheel.swift; sourceTree = ""; }; 62 | D70B495C1FA86B9100231E07 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 63 | D74F36A91FA86966003D0416 /* TTFortuneWheel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TTFortuneWheel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | D74F36AC1FA86966003D0416 /* TTFortuneWheel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TTFortuneWheel.h; sourceTree = ""; }; 65 | D74F36AD1FA86966003D0416 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66 | D74F36B81FA86AD7003D0416 /* TTFortuneWheelSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TTFortuneWheelSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | D74F36BA1FA86AD7003D0416 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 68 | D74F36BC1FA86AD7003D0416 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 69 | D74F36BF1FA86AD7003D0416 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 70 | D74F36C11FA86AD7003D0416 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 71 | D74F36C41FA86AD7003D0416 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 72 | D74F36C61FA86AD7003D0416 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | D7D7F0451FA88B3B0042B5C6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 74 | D7E515CD1FA9C3C900218F8A /* CarnivalWheelSlice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarnivalWheelSlice.swift; sourceTree = ""; }; 75 | D7E515D01FA9C5D800218F8A /* Chunkfive.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Chunkfive.otf; sourceTree = ""; }; 76 | D7E515D11FA9C5E800218F8A /* Lobster_1.3.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Lobster_1.3.otf; sourceTree = ""; }; 77 | D7E515D21FA9C60900218F8A /* Phosphate.ttc */ = {isa = PBXFileReference; lastKnownFileType = file; path = Phosphate.ttc; sourceTree = ""; }; 78 | D7E515D31FA9C61600218F8A /* BEBAS___.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "BEBAS___.ttf"; sourceTree = ""; }; 79 | /* End PBXFileReference section */ 80 | 81 | /* Begin PBXFrameworksBuildPhase section */ 82 | D74F36A51FA86966003D0416 /* Frameworks */ = { 83 | isa = PBXFrameworksBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | D74F36B51FA86AD7003D0416 /* Frameworks */ = { 90 | isa = PBXFrameworksBuildPhase; 91 | buildActionMask = 2147483647; 92 | files = ( 93 | D7043A2D1FE29CBB0064F43D /* TTFortuneWheel.framework in Frameworks */, 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | D74F369F1FA86966003D0416 = { 101 | isa = PBXGroup; 102 | children = ( 103 | D74F36AB1FA86966003D0416 /* TTFortuneWheel */, 104 | D74F36B91FA86AD7003D0416 /* TTFortuneWheelSample */, 105 | D74F36AA1FA86966003D0416 /* Products */, 106 | D798CC7C1FA86FA200FE2880 /* Frameworks */, 107 | ); 108 | sourceTree = ""; 109 | }; 110 | D74F36AA1FA86966003D0416 /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | D74F36A91FA86966003D0416 /* TTFortuneWheel.framework */, 114 | D74F36B81FA86AD7003D0416 /* TTFortuneWheelSample.app */, 115 | ); 116 | name = Products; 117 | sourceTree = ""; 118 | }; 119 | D74F36AB1FA86966003D0416 /* TTFortuneWheel */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | D74F36AC1FA86966003D0416 /* TTFortuneWheel.h */, 123 | D70B49571FA86B9100231E07 /* SpinningWheelAnimator.swift */, 124 | D70B49581FA86B9100231E07 /* FortuneWheelLayer.swift */, 125 | D70B49591FA86B9100231E07 /* SpinningWheelSlice .swift */, 126 | D70B495A1FA86B9100231E07 /* SpinningWheelSliceImpl.swift */, 127 | D70B495B1FA86B9100231E07 /* TTFortuneWheel.swift */, 128 | D70B495C1FA86B9100231E07 /* Utils.swift */, 129 | D74F36AD1FA86966003D0416 /* Info.plist */, 130 | D7D7F0461FA88B420042B5C6 /* Resources */, 131 | ); 132 | path = TTFortuneWheel; 133 | sourceTree = ""; 134 | }; 135 | D74F36B91FA86AD7003D0416 /* TTFortuneWheelSample */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | D74F36BA1FA86AD7003D0416 /* AppDelegate.swift */, 139 | D74F36BC1FA86AD7003D0416 /* ViewController.swift */, 140 | D74F36BE1FA86AD7003D0416 /* Main.storyboard */, 141 | D74F36C11FA86AD7003D0416 /* Assets.xcassets */, 142 | D74F36C31FA86AD7003D0416 /* LaunchScreen.storyboard */, 143 | D74F36C61FA86AD7003D0416 /* Info.plist */, 144 | D7E515CD1FA9C3C900218F8A /* CarnivalWheelSlice.swift */, 145 | D7E515CF1FA9C5CE00218F8A /* Resources */, 146 | ); 147 | path = TTFortuneWheelSample; 148 | sourceTree = ""; 149 | }; 150 | D798CC7C1FA86FA200FE2880 /* Frameworks */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | ); 154 | name = Frameworks; 155 | sourceTree = ""; 156 | }; 157 | D7D7F0461FA88B420042B5C6 /* Resources */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | D7D7F0451FA88B3B0042B5C6 /* Assets.xcassets */, 161 | ); 162 | path = Resources; 163 | sourceTree = ""; 164 | }; 165 | D7E515CF1FA9C5CE00218F8A /* Resources */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | D7E515D31FA9C61600218F8A /* BEBAS___.ttf */, 169 | D7E515D21FA9C60900218F8A /* Phosphate.ttc */, 170 | D7E515D11FA9C5E800218F8A /* Lobster_1.3.otf */, 171 | D7E515D01FA9C5D800218F8A /* Chunkfive.otf */, 172 | ); 173 | path = Resources; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXGroup section */ 177 | 178 | /* Begin PBXHeadersBuildPhase section */ 179 | D74F36A61FA86966003D0416 /* Headers */ = { 180 | isa = PBXHeadersBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | D74F36AE1FA86966003D0416 /* TTFortuneWheel.h in Headers */, 184 | ); 185 | runOnlyForDeploymentPostprocessing = 0; 186 | }; 187 | /* End PBXHeadersBuildPhase section */ 188 | 189 | /* Begin PBXNativeTarget section */ 190 | D74F36A81FA86966003D0416 /* TTFortuneWheel */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = D74F36B11FA86966003D0416 /* Build configuration list for PBXNativeTarget "TTFortuneWheel" */; 193 | buildPhases = ( 194 | D74F36A41FA86966003D0416 /* Sources */, 195 | D74F36A51FA86966003D0416 /* Frameworks */, 196 | D74F36A61FA86966003D0416 /* Headers */, 197 | D74F36A71FA86966003D0416 /* Resources */, 198 | ); 199 | buildRules = ( 200 | ); 201 | dependencies = ( 202 | ); 203 | name = TTFortuneWheel; 204 | productName = TTFortuneWheel; 205 | productReference = D74F36A91FA86966003D0416 /* TTFortuneWheel.framework */; 206 | productType = "com.apple.product-type.framework"; 207 | }; 208 | D74F36B71FA86AD7003D0416 /* TTFortuneWheelSample */ = { 209 | isa = PBXNativeTarget; 210 | buildConfigurationList = D74F36C71FA86AD7003D0416 /* Build configuration list for PBXNativeTarget "TTFortuneWheelSample" */; 211 | buildPhases = ( 212 | D74F36B41FA86AD7003D0416 /* Sources */, 213 | D74F36B51FA86AD7003D0416 /* Frameworks */, 214 | D74F36B61FA86AD7003D0416 /* Resources */, 215 | D7043A331FE29DAD0064F43D /* Embed Frameworks */, 216 | ); 217 | buildRules = ( 218 | ); 219 | dependencies = ( 220 | D7043A321FE29DAD0064F43D /* PBXTargetDependency */, 221 | ); 222 | name = TTFortuneWheelSample; 223 | productName = TTFortuneWheelSample; 224 | productReference = D74F36B81FA86AD7003D0416 /* TTFortuneWheelSample.app */; 225 | productType = "com.apple.product-type.application"; 226 | }; 227 | /* End PBXNativeTarget section */ 228 | 229 | /* Begin PBXProject section */ 230 | D74F36A01FA86966003D0416 /* Project object */ = { 231 | isa = PBXProject; 232 | attributes = { 233 | LastSwiftUpdateCheck = 0900; 234 | LastUpgradeCheck = 1110; 235 | ORGANIZATIONNAME = Tapptitude; 236 | TargetAttributes = { 237 | D74F36A81FA86966003D0416 = { 238 | CreatedOnToolsVersion = 9.0; 239 | LastSwiftMigration = 0820; 240 | ProvisioningStyle = Automatic; 241 | }; 242 | D74F36B71FA86AD7003D0416 = { 243 | CreatedOnToolsVersion = 9.0; 244 | ProvisioningStyle = Automatic; 245 | }; 246 | }; 247 | }; 248 | buildConfigurationList = D74F36A31FA86966003D0416 /* Build configuration list for PBXProject "TTFortuneWheel" */; 249 | compatibilityVersion = "Xcode 8.0"; 250 | developmentRegion = en; 251 | hasScannedForEncodings = 0; 252 | knownRegions = ( 253 | en, 254 | Base, 255 | ); 256 | mainGroup = D74F369F1FA86966003D0416; 257 | productRefGroup = D74F36AA1FA86966003D0416 /* Products */; 258 | projectDirPath = ""; 259 | projectRoot = ""; 260 | targets = ( 261 | D74F36A81FA86966003D0416 /* TTFortuneWheel */, 262 | D74F36B71FA86AD7003D0416 /* TTFortuneWheelSample */, 263 | ); 264 | }; 265 | /* End PBXProject section */ 266 | 267 | /* Begin PBXResourcesBuildPhase section */ 268 | D74F36A71FA86966003D0416 /* Resources */ = { 269 | isa = PBXResourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | D7043A2E1FE29D310064F43D /* Assets.xcassets in Resources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | D74F36B61FA86AD7003D0416 /* Resources */ = { 277 | isa = PBXResourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | D7E515D81FA9C63C00218F8A /* BEBAS___.ttf in Resources */, 281 | D7E515D91FA9C63C00218F8A /* Phosphate.ttc in Resources */, 282 | D7E515DA1FA9C63C00218F8A /* Lobster_1.3.otf in Resources */, 283 | D7E515DB1FA9C63C00218F8A /* Chunkfive.otf in Resources */, 284 | D74F36C51FA86AD7003D0416 /* LaunchScreen.storyboard in Resources */, 285 | D74F36C21FA86AD7003D0416 /* Assets.xcassets in Resources */, 286 | D74F36C01FA86AD7003D0416 /* Main.storyboard in Resources */, 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | /* End PBXResourcesBuildPhase section */ 291 | 292 | /* Begin PBXSourcesBuildPhase section */ 293 | D74F36A41FA86966003D0416 /* Sources */ = { 294 | isa = PBXSourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | D70B49631FA86B9100231E07 /* SpinningWheelSliceImpl.swift in Sources */, 298 | D70B495D1FA86B9100231E07 /* SpinningWheelAnimator.swift in Sources */, 299 | D70B495F1FA86B9100231E07 /* FortuneWheelLayer.swift in Sources */, 300 | D70B49651FA86B9100231E07 /* TTFortuneWheel.swift in Sources */, 301 | D70B49611FA86B9100231E07 /* SpinningWheelSlice .swift in Sources */, 302 | D70B49671FA86B9100231E07 /* Utils.swift in Sources */, 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | D74F36B41FA86AD7003D0416 /* Sources */ = { 307 | isa = PBXSourcesBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | D74F36BD1FA86AD7003D0416 /* ViewController.swift in Sources */, 311 | D74F36BB1FA86AD7003D0416 /* AppDelegate.swift in Sources */, 312 | D7E515CE1FA9C3C900218F8A /* CarnivalWheelSlice.swift in Sources */, 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | }; 316 | /* End PBXSourcesBuildPhase section */ 317 | 318 | /* Begin PBXTargetDependency section */ 319 | D7043A321FE29DAD0064F43D /* PBXTargetDependency */ = { 320 | isa = PBXTargetDependency; 321 | target = D74F36A81FA86966003D0416 /* TTFortuneWheel */; 322 | targetProxy = D7043A311FE29DAD0064F43D /* PBXContainerItemProxy */; 323 | }; 324 | /* End PBXTargetDependency section */ 325 | 326 | /* Begin PBXVariantGroup section */ 327 | D74F36BE1FA86AD7003D0416 /* Main.storyboard */ = { 328 | isa = PBXVariantGroup; 329 | children = ( 330 | D74F36BF1FA86AD7003D0416 /* Base */, 331 | ); 332 | name = Main.storyboard; 333 | sourceTree = ""; 334 | }; 335 | D74F36C31FA86AD7003D0416 /* LaunchScreen.storyboard */ = { 336 | isa = PBXVariantGroup; 337 | children = ( 338 | D74F36C41FA86AD7003D0416 /* Base */, 339 | ); 340 | name = LaunchScreen.storyboard; 341 | sourceTree = ""; 342 | }; 343 | /* End PBXVariantGroup section */ 344 | 345 | /* Begin XCBuildConfiguration section */ 346 | D74F36AF1FA86966003D0416 /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ALWAYS_SEARCH_USER_PATHS = NO; 350 | CLANG_ANALYZER_NONNULL = YES; 351 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 352 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 353 | CLANG_CXX_LIBRARY = "libc++"; 354 | CLANG_ENABLE_MODULES = YES; 355 | CLANG_ENABLE_OBJC_ARC = YES; 356 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 357 | CLANG_WARN_BOOL_CONVERSION = YES; 358 | CLANG_WARN_COMMA = YES; 359 | CLANG_WARN_CONSTANT_CONVERSION = YES; 360 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 362 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 363 | CLANG_WARN_EMPTY_BODY = YES; 364 | CLANG_WARN_ENUM_CONVERSION = YES; 365 | CLANG_WARN_INFINITE_RECURSION = YES; 366 | CLANG_WARN_INT_CONVERSION = YES; 367 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 368 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 369 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 371 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 372 | CLANG_WARN_STRICT_PROTOTYPES = YES; 373 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 374 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 375 | CLANG_WARN_UNREACHABLE_CODE = YES; 376 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 377 | CODE_SIGN_IDENTITY = "iPhone Developer"; 378 | COPY_PHASE_STRIP = NO; 379 | CURRENT_PROJECT_VERSION = 1; 380 | DEBUG_INFORMATION_FORMAT = dwarf; 381 | ENABLE_STRICT_OBJC_MSGSEND = YES; 382 | ENABLE_TESTABILITY = YES; 383 | GCC_C_LANGUAGE_STANDARD = gnu11; 384 | GCC_DYNAMIC_NO_PIC = NO; 385 | GCC_NO_COMMON_BLOCKS = YES; 386 | GCC_OPTIMIZATION_LEVEL = 0; 387 | GCC_PREPROCESSOR_DEFINITIONS = ( 388 | "DEBUG=1", 389 | "$(inherited)", 390 | ); 391 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 392 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 393 | GCC_WARN_UNDECLARED_SELECTOR = YES; 394 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 395 | GCC_WARN_UNUSED_FUNCTION = YES; 396 | GCC_WARN_UNUSED_VARIABLE = YES; 397 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 398 | MTL_ENABLE_DEBUG_INFO = YES; 399 | ONLY_ACTIVE_ARCH = YES; 400 | SDKROOT = iphoneos; 401 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 403 | SWIFT_VERSION = 4.2; 404 | VERSIONING_SYSTEM = "apple-generic"; 405 | VERSION_INFO_PREFIX = ""; 406 | }; 407 | name = Debug; 408 | }; 409 | D74F36B01FA86966003D0416 /* Release */ = { 410 | isa = XCBuildConfiguration; 411 | buildSettings = { 412 | ALWAYS_SEARCH_USER_PATHS = NO; 413 | CLANG_ANALYZER_NONNULL = YES; 414 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 415 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 416 | CLANG_CXX_LIBRARY = "libc++"; 417 | CLANG_ENABLE_MODULES = YES; 418 | CLANG_ENABLE_OBJC_ARC = YES; 419 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 420 | CLANG_WARN_BOOL_CONVERSION = YES; 421 | CLANG_WARN_COMMA = YES; 422 | CLANG_WARN_CONSTANT_CONVERSION = YES; 423 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 424 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 425 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 426 | CLANG_WARN_EMPTY_BODY = YES; 427 | CLANG_WARN_ENUM_CONVERSION = YES; 428 | CLANG_WARN_INFINITE_RECURSION = YES; 429 | CLANG_WARN_INT_CONVERSION = YES; 430 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 431 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 432 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 433 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 434 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 435 | CLANG_WARN_STRICT_PROTOTYPES = YES; 436 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 437 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 438 | CLANG_WARN_UNREACHABLE_CODE = YES; 439 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 440 | CODE_SIGN_IDENTITY = "iPhone Developer"; 441 | COPY_PHASE_STRIP = NO; 442 | CURRENT_PROJECT_VERSION = 1; 443 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 444 | ENABLE_NS_ASSERTIONS = NO; 445 | ENABLE_STRICT_OBJC_MSGSEND = YES; 446 | GCC_C_LANGUAGE_STANDARD = gnu11; 447 | GCC_NO_COMMON_BLOCKS = YES; 448 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 449 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 450 | GCC_WARN_UNDECLARED_SELECTOR = YES; 451 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 452 | GCC_WARN_UNUSED_FUNCTION = YES; 453 | GCC_WARN_UNUSED_VARIABLE = YES; 454 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 455 | MTL_ENABLE_DEBUG_INFO = NO; 456 | SDKROOT = iphoneos; 457 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 458 | SWIFT_VERSION = 4.2; 459 | VALIDATE_PRODUCT = YES; 460 | VERSIONING_SYSTEM = "apple-generic"; 461 | VERSION_INFO_PREFIX = ""; 462 | }; 463 | name = Release; 464 | }; 465 | D74F36B21FA86966003D0416 /* Debug */ = { 466 | isa = XCBuildConfiguration; 467 | buildSettings = { 468 | APPLICATION_EXTENSION_API_ONLY = NO; 469 | CLANG_ENABLE_MODULES = YES; 470 | CODE_SIGN_IDENTITY = ""; 471 | CODE_SIGN_STYLE = Automatic; 472 | DEFINES_MODULE = YES; 473 | DEVELOPMENT_TEAM = G7T599BH7B; 474 | DYLIB_COMPATIBILITY_VERSION = 1; 475 | DYLIB_CURRENT_VERSION = 1; 476 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 477 | INFOPLIST_FILE = TTFortuneWheel/Info.plist; 478 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 479 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 480 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 481 | PRODUCT_BUNDLE_IDENTIFIER = com.tapptitude.TTFortuneWheel; 482 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 483 | SKIP_INSTALL = YES; 484 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 485 | SWIFT_VERSION = 5.0; 486 | TARGETED_DEVICE_FAMILY = "1,2"; 487 | }; 488 | name = Debug; 489 | }; 490 | D74F36B31FA86966003D0416 /* Release */ = { 491 | isa = XCBuildConfiguration; 492 | buildSettings = { 493 | APPLICATION_EXTENSION_API_ONLY = NO; 494 | CLANG_ENABLE_MODULES = YES; 495 | CODE_SIGN_IDENTITY = ""; 496 | CODE_SIGN_STYLE = Automatic; 497 | DEFINES_MODULE = YES; 498 | DEVELOPMENT_TEAM = G7T599BH7B; 499 | DYLIB_COMPATIBILITY_VERSION = 1; 500 | DYLIB_CURRENT_VERSION = 1; 501 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 502 | INFOPLIST_FILE = TTFortuneWheel/Info.plist; 503 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 504 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 505 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 506 | PRODUCT_BUNDLE_IDENTIFIER = com.tapptitude.TTFortuneWheel; 507 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 508 | SKIP_INSTALL = YES; 509 | SWIFT_VERSION = 5.0; 510 | TARGETED_DEVICE_FAMILY = "1,2"; 511 | }; 512 | name = Release; 513 | }; 514 | D74F36C81FA86AD7003D0416 /* Debug */ = { 515 | isa = XCBuildConfiguration; 516 | buildSettings = { 517 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 518 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 519 | CODE_SIGN_STYLE = Automatic; 520 | DEVELOPMENT_TEAM = G7T599BH7B; 521 | INFOPLIST_FILE = TTFortuneWheelSample/Info.plist; 522 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 523 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 524 | PRODUCT_BUNDLE_IDENTIFIER = com.tapptitude.TTFortuneWheelSample; 525 | PRODUCT_NAME = "$(TARGET_NAME)"; 526 | SWIFT_VERSION = 5.0; 527 | TARGETED_DEVICE_FAMILY = "1,2"; 528 | }; 529 | name = Debug; 530 | }; 531 | D74F36C91FA86AD7003D0416 /* Release */ = { 532 | isa = XCBuildConfiguration; 533 | buildSettings = { 534 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 535 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 536 | CODE_SIGN_STYLE = Automatic; 537 | DEVELOPMENT_TEAM = G7T599BH7B; 538 | INFOPLIST_FILE = TTFortuneWheelSample/Info.plist; 539 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 540 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 541 | PRODUCT_BUNDLE_IDENTIFIER = com.tapptitude.TTFortuneWheelSample; 542 | PRODUCT_NAME = "$(TARGET_NAME)"; 543 | SWIFT_VERSION = 5.0; 544 | TARGETED_DEVICE_FAMILY = "1,2"; 545 | }; 546 | name = Release; 547 | }; 548 | /* End XCBuildConfiguration section */ 549 | 550 | /* Begin XCConfigurationList section */ 551 | D74F36A31FA86966003D0416 /* Build configuration list for PBXProject "TTFortuneWheel" */ = { 552 | isa = XCConfigurationList; 553 | buildConfigurations = ( 554 | D74F36AF1FA86966003D0416 /* Debug */, 555 | D74F36B01FA86966003D0416 /* Release */, 556 | ); 557 | defaultConfigurationIsVisible = 0; 558 | defaultConfigurationName = Release; 559 | }; 560 | D74F36B11FA86966003D0416 /* Build configuration list for PBXNativeTarget "TTFortuneWheel" */ = { 561 | isa = XCConfigurationList; 562 | buildConfigurations = ( 563 | D74F36B21FA86966003D0416 /* Debug */, 564 | D74F36B31FA86966003D0416 /* Release */, 565 | ); 566 | defaultConfigurationIsVisible = 0; 567 | defaultConfigurationName = Release; 568 | }; 569 | D74F36C71FA86AD7003D0416 /* Build configuration list for PBXNativeTarget "TTFortuneWheelSample" */ = { 570 | isa = XCConfigurationList; 571 | buildConfigurations = ( 572 | D74F36C81FA86AD7003D0416 /* Debug */, 573 | D74F36C91FA86AD7003D0416 /* Release */, 574 | ); 575 | defaultConfigurationIsVisible = 0; 576 | defaultConfigurationName = Release; 577 | }; 578 | /* End XCConfigurationList section */ 579 | }; 580 | rootObject = D74F36A01FA86966003D0416 /* Project object */; 581 | } 582 | -------------------------------------------------------------------------------- /TTFortuneWheel.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TTFortuneWheel.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TTFortuneWheel.xcodeproj/xcshareddata/xcschemes/TTFortuneWheel.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 | -------------------------------------------------------------------------------- /TTFortuneWheel/FortuneWheelLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WheelSpinnerDrawer.swift 3 | // TTFortuneWheel 4 | // 5 | // Created by Efraim Budusan on 9/29/17. 6 | // Copyright © 2017 Efraim Budusn. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import CoreGraphics 12 | 13 | 14 | open class FortuneWheelLayer: CALayer { 15 | 16 | /// Used to center the drawing such that offseted graphics(e.g Shadows, Outer Glows) are not clipped. 17 | /// Can be increased to any size if needed. 18 | open var layerInsets:UIEdgeInsets = UIEdgeInsets(top: -50, left: -50, bottom: -50, right: -50) 19 | 20 | var mainFrame:CGRect! 21 | weak var parent:TTFortuneWheel! 22 | private var initialOffset:CGFloat! 23 | 24 | public init(frame:CGRect, parent:TTFortuneWheel,initialOffset:CGFloat = 0.0) { 25 | super.init() 26 | mainFrame = CGRect(origin: CGPoint(x: abs(layerInsets.left), y: abs(layerInsets.top)), size: frame.size) 27 | self.frame = frame.inset(by: layerInsets) 28 | self.parent = parent 29 | self.initialOffset = initialOffset 30 | self.backgroundColor = UIColor.clear.cgColor 31 | self.contentsScale = UIScreen.main.scale 32 | } 33 | 34 | public required init?(coder aDecoder: NSCoder) { 35 | super.init(coder: aDecoder) 36 | } 37 | 38 | open override func draw(in ctx: CGContext) { 39 | super.draw(in: ctx) 40 | 41 | guard parent.slices != nil else { 42 | assert(false, "Slices parameter not set.") 43 | return 44 | } 45 | UIGraphicsPushContext(ctx) 46 | drawCanvas(mainFrame: mainFrame) 47 | UIGraphicsPopContext() 48 | } 49 | 50 | open func drawCanvas(mainFrame: CGRect) { 51 | //// General Declarations 52 | let context = UIGraphicsGetCurrentContext()! 53 | 54 | //// Main Group 55 | context.saveGState() 56 | if let shadow = parent.shadow { 57 | context.setShadow(offset: shadow.shadowOffset, blur: shadow.shadowBlurRadius, color: (shadow.shadowColor as! UIColor).cgColor) 58 | } 59 | context.beginTransparencyLayer(auxiliaryInfo: nil) 60 | 61 | //// Slice drawings 62 | var rotation:CGFloat = initialOffset 63 | parent.slices.enumerated().forEach { (index,element) in 64 | if let previousSlice = parent.slices[safe:(index - 1)] { 65 | rotation += (degree(of:previousSlice) + degree(of:element)) / 2 66 | } 67 | self.drawSlice(withIndex: index, in: context, forSlice: element,rotation:rotation) 68 | 69 | } 70 | 71 | //// Aditional graphics drwings 72 | parent.slices.enumerated().forEach { (index,element) in 73 | self.drawAdditionalGraphics(in: context, rotation: rotation, for: element) 74 | let previousSlice:FortuneWheelSliceProtocol = parent.slices[safe:(index - 1)] ?? element 75 | rotation += degree(of:previousSlice) 76 | } 77 | 78 | //// Frame drawing 79 | let circleFrame = UIBezierPath(ovalIn: mainFrame) 80 | parent.frameStroke.color.setStroke() 81 | circleFrame.lineWidth = parent.frameStroke.width 82 | circleFrame.stroke() 83 | 84 | context.endTransparencyLayer() 85 | context.restoreGState() 86 | } 87 | 88 | //MARK:- Computed values and expressions 89 | private var radius:CGFloat { return mainFrame.height / 2.0 } 90 | private var rotationOffset:CGFloat { return (mainFrame.width) / 2 + abs(layerInsets.top) } 91 | private func circularSegmentHeight(from degree:CGFloat) -> CGFloat { return 2 * radius * sin(degree / 2.0 * CGFloat.pi/180) } 92 | 93 | private func degree(of slice:FortuneWheelSliceProtocol) -> CGFloat { 94 | return parent.sliceDegree ?? slice.degree 95 | } 96 | 97 | //MARK:- Graphics drawings 98 | 99 | open func drawSlice(withIndex index:Int, in context:CGContext, forSlice slice:FortuneWheelSliceProtocol, rotation:CGFloat) { 100 | 101 | ///// Constats declarations 102 | let sectionWidthDegrees = degree(of: slice) 103 | let kTitleOffset: CGFloat = slice.offsetFromExterior 104 | let titleXValue: CGFloat = mainFrame.minX + kTitleOffset 105 | let kTitleWidth: CGFloat = 0.6 106 | let titleWidthCoeficient: CGFloat = sin(sectionWidthDegrees / 2.0 * CGFloat.pi/180) 107 | let titleWidthValue: CGFloat = (kTitleWidth + titleWidthCoeficient * 0.2) * radius 108 | let startAngle: CGFloat = 180 + sectionWidthDegrees / 2.0 109 | let endAngle: CGFloat = 180 - sectionWidthDegrees / 2.0 110 | let circularSegmentHeight: CGFloat = self.circularSegmentHeight(from:sectionWidthDegrees) 111 | let titleHeightValue: CGFloat = circularSegmentHeight * 1 112 | let titleYPosition: CGFloat = mainFrame.minY + mainFrame.height / 2.0 - titleHeightValue / 2.0 113 | 114 | //// Context setup 115 | context.saveGState() 116 | context.translateBy(x: rotationOffset, y: rotationOffset) 117 | context.rotate(by: rotation * CGFloat.pi/180) 118 | 119 | //// Slice drawing 120 | let sliceRect = CGRect(x: (mainFrame.minX - rotationOffset), y: (mainFrame.minY - rotationOffset), width: mainFrame.width, height: mainFrame.height) 121 | let slicePath = UIBezierPath() 122 | slicePath.addArc(withCenter: CGPoint(x: sliceRect.midX, y: sliceRect.midY), radius: sliceRect.width / 2, startAngle: -startAngle * CGFloat.pi/180, endAngle: -endAngle * CGFloat.pi/180, clockwise: true) 123 | slicePath.addLine(to: CGPoint(x: sliceRect.midX, y: sliceRect.midY)) 124 | slicePath.close() 125 | slice.backgroundColor?.setFill() 126 | slicePath.fill() 127 | 128 | //// Strike drawing 129 | if let stroke = slice.stroke { 130 | stroke.color.setStroke() 131 | slicePath.lineWidth = stroke.width 132 | slicePath.stroke() 133 | } 134 | 135 | //// Title Drawing 136 | let textRect = CGRect(x: (titleXValue - rotationOffset), y: (titleYPosition - rotationOffset), width: titleWidthValue, height: titleHeightValue) 137 | let textTextContent = slice.title 138 | 139 | //// Set title attributes 140 | let textStyle = NSMutableParagraphStyle() 141 | textStyle.alignment = .left 142 | var textFontAttributes = slice.textAttributes 143 | textFontAttributes[.paragraphStyle] = textStyle 144 | 145 | let textBoundingRect = textTextContent.boundingRect(with: CGSize(width: textRect.width, height: CGFloat.infinity), options: .usesLineFragmentOrigin, attributes: textFontAttributes, context: nil) 146 | let textTextHeight: CGFloat = textBoundingRect.height 147 | context.saveGState() 148 | 149 | context.translateBy(x: textRect.minX, y: textRect.minY + (textRect.height - textTextHeight) / 2) 150 | context.translateBy(x: textBoundingRect.width / 2, y: textBoundingRect.height / 2) 151 | context.rotate(by: self.parent.titleRotation) 152 | context.translateBy(x: -(textBoundingRect.width / 2), y: -(textBoundingRect.height / 2)) 153 | context.clip(to: CGRect(x: 0, y: 0, width: textRect.width, height: textRect.height)) 154 | textTextContent.draw(in: CGRect(x: 0, y: 0, width: textRect.width, height: textTextHeight), withAttributes: textFontAttributes) 155 | context.restoreGState() 156 | 157 | context.restoreGState() 158 | } 159 | 160 | private func drawAdditionalGraphics(in context:CGContext, rotation:CGFloat, for slice:FortuneWheelSliceProtocol) { 161 | 162 | let sectionWidthDegrees:CGFloat = degree(of: slice) 163 | let circularSegmentHeight: CGFloat = self.circularSegmentHeight(from:sectionWidthDegrees) 164 | context.saveGState() 165 | context.translateBy(x: rotationOffset, y: rotationOffset) 166 | context.rotate(by: rotation * CGFloat.pi/180) 167 | slice.drawAdditionalGraphics(in: context,circularSegmentHeight:circularSegmentHeight, radius: radius,sliceDegree:sectionWidthDegrees) 168 | context.restoreGState() 169 | 170 | } 171 | 172 | 173 | 174 | } 175 | -------------------------------------------------------------------------------- /TTFortuneWheel/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 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /TTFortuneWheel/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TTFortuneWheel/Resources/Assets.xcassets/niddleImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "point@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "point@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TTFortuneWheel/Resources/Assets.xcassets/niddleImage.imageset/point@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheel/Resources/Assets.xcassets/niddleImage.imageset/point@2x.png -------------------------------------------------------------------------------- /TTFortuneWheel/Resources/Assets.xcassets/niddleImage.imageset/point@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheel/Resources/Assets.xcassets/niddleImage.imageset/point@3x.png -------------------------------------------------------------------------------- /TTFortuneWheel/SpinningWheelAnimator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpinningWheelAnimator.swift 3 | // TTFortuneWheel 4 | // 5 | // Created by Efraim Budusan on 10/3/17. 6 | // Copyright © 2017 Efraim Budusn. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import CoreGraphics 12 | 13 | protocol SpinningAnimatorProtocol: class { 14 | var layerToAnimate:CALayer { get } 15 | } 16 | 17 | 18 | class SpinningWheelAnimator : NSObject, CAAnimationDelegate { 19 | 20 | 21 | weak var animationObject:SpinningAnimatorProtocol! 22 | 23 | internal var completionBlocks = [CAAnimation: (Bool) -> Void]() 24 | internal var updateLayerValueForCompletedAnimation : Bool = false 25 | 26 | //Configuration properties 27 | var fullRotationsUntilFinish:Int = 13 28 | var rotationTime: CFTimeInterval = 5.000 29 | 30 | init(withObjectToAnimate animationObject:SpinningAnimatorProtocol) { 31 | self.animationObject = animationObject 32 | } 33 | 34 | 35 | func addIndefiniteRotationAnimation() { 36 | 37 | let fillMode : String = CAMediaTimingFillMode.forwards.rawValue 38 | let starTransformAnim = CAKeyframeAnimation(keyPath:"transform.rotation.z") 39 | starTransformAnim.values = [0, 7000 * CGFloat.pi/180] 40 | starTransformAnim.keyTimes = [0, 1] 41 | starTransformAnim.duration = rotationTime 42 | 43 | let starRotationAnim : CAAnimationGroup = TTUtils.group(animations: [starTransformAnim], fillMode:fillMode) 44 | starRotationAnim.repeatCount = Float.infinity 45 | animationObject.layerToAnimate.add(starRotationAnim, forKey:"starRotationIndefiniteAnim") 46 | 47 | } 48 | 49 | func addRotationAnimation(completionBlock: ((_ finished: Bool) -> Void)? = nil, rotationOffset:CGFloat = 0.0){ 50 | if completionBlock != nil{ 51 | let completionAnim = CABasicAnimation(keyPath:"completionAnim") 52 | completionAnim.duration = rotationTime 53 | completionAnim.delegate = self 54 | completionAnim.setValue("rotation", forKey:"animId") 55 | completionAnim.setValue(false, forKey:"needEndAnim") 56 | let layer = animationObject.layerToAnimate 57 | layer.add(completionAnim, forKey:"rotation") 58 | if let anim = layer.animation(forKey: "rotation"){ 59 | completionBlocks[anim] = completionBlock 60 | } 61 | } 62 | 63 | let fillMode : String = CAMediaTimingFillMode.forwards.rawValue 64 | 65 | let rotation:CGFloat = CGFloat(fullRotationsUntilFinish) * 360.0 + rotationOffset 66 | 67 | ////Star animation 68 | let starTransformAnim = CAKeyframeAnimation(keyPath:"transform.rotation.z") 69 | starTransformAnim.values = [0, rotation * CGFloat.pi/180] 70 | starTransformAnim.keyTimes = [0, 1] 71 | starTransformAnim.duration = 5 72 | starTransformAnim.timingFunction = CAMediaTimingFunction(controlPoints: 0.0256, 0.874, 0.675, 1) 73 | 74 | let starRotationAnim : CAAnimationGroup = TTUtils.group(animations: [starTransformAnim], fillMode:fillMode) 75 | animationObject.layerToAnimate.add(starRotationAnim, forKey:"starRotationAnim") 76 | } 77 | 78 | //MARK: - Animation Cleanup 79 | 80 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool){ 81 | if let completionBlock = completionBlocks[anim]{ 82 | completionBlocks.removeValue(forKey: anim) 83 | if (flag && updateLayerValueForCompletedAnimation) || anim.value(forKey: "needEndAnim") as! Bool{ 84 | updateLayerValues(forAnimationId: anim.value(forKey: "animId") as! String) 85 | removeAnimations(forAnimationId: anim.value(forKey: "animId") as! String) 86 | } 87 | completionBlock(flag) 88 | } 89 | } 90 | 91 | func updateLayerValues(forAnimationId identifier: String){ 92 | if identifier == "rotation"{ 93 | TTUtils.updateValueFromPresentationLayer(forAnimation: animationObject.layerToAnimate.animation(forKey: "starRotationAnim"), theLayer:animationObject.layerToAnimate) 94 | } 95 | } 96 | 97 | func removeAnimations(forAnimationId identifier: String){ 98 | if identifier == "rotation"{ 99 | animationObject.layerToAnimate.removeAnimation(forKey: "starRotationAnim") 100 | } 101 | } 102 | 103 | func removeIndefiniteAnimation() { 104 | animationObject.layerToAnimate.removeAnimation(forKey: "starRotationIndefiniteAnim") 105 | } 106 | 107 | func removeAllAnimations(){ 108 | animationObject.layerToAnimate.removeAllAnimations() 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /TTFortuneWheel/SpinningWheelSlice .swift: -------------------------------------------------------------------------------- 1 | // 2 | // FortuneWheelSlice.swift 3 | // TTFortuneWheel 4 | // 5 | // Created by Efraim Budusan on 10/2/17. 6 | // Copyright © 2017 Efraim Budusn. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | 13 | public struct StrokeInfo { 14 | public var color:UIColor 15 | public var width:CGFloat 16 | 17 | public init(color:UIColor, width:CGFloat) { 18 | self.color = color 19 | self.width = width 20 | } 21 | } 22 | 23 | public protocol FortuneWheelSliceProtocol { 24 | 25 | //// Properties 26 | var title:String { get } 27 | var backgroundColor:UIColor? { get } 28 | var degree:CGFloat { get } 29 | var stroke:StrokeInfo? { get } 30 | var offsetFromExterior:CGFloat { get } 31 | 32 | //// Can provide any text attributes except NSMutableParagraphStyle which will allways be centered 33 | var textAttributes: [NSAttributedString.Key:Any] { get } 34 | 35 | //// Can be overriten individualy. textAttributes is used if set. 36 | var fontSize:CGFloat { get } 37 | var fontColor:UIColor { get } 38 | var font:UIFont { get } 39 | 40 | /// Implement if you want to add additional graphic to a slice. 41 | /// Note the origin of coordinate system is at the center of the main circle. 42 | /// You will have to dinamically compute the positon of your elements using the circularSegmentHeight and the radius 43 | func drawAdditionalGraphics(in context:CGContext, circularSegmentHeight:CGFloat,radius:CGFloat,sliceDegree:CGFloat) 44 | } 45 | 46 | extension FortuneWheelSliceProtocol { 47 | 48 | public func drawAdditionalGraphics(in context:CGContext, circularSegmentHeight:CGFloat,radius:CGFloat,sliceDegree:CGFloat) { } 49 | 50 | public var fontSize:CGFloat { return 18.0 } 51 | public var fontColor:UIColor { return UIColor.black } 52 | public var font:UIFont { return UIFont.systemFont(ofSize: fontSize, weight: .regular) } 53 | 54 | public var textAttributes:[NSAttributedString.Key:Any] { 55 | let textStyle = NSMutableParagraphStyle() 56 | textStyle.alignment = .left 57 | let deafultAttributes:[NSAttributedString.Key: Any] = 58 | [.font: self.font, 59 | .foregroundColor: self.fontColor, 60 | .paragraphStyle: textStyle ] 61 | return deafultAttributes 62 | } 63 | 64 | public var offsetFromExterior:CGFloat { return 10.0 } 65 | 66 | public var stroke:StrokeInfo? { return nil } 67 | 68 | public var backgroundColor:UIColor? { return UIColor.lightGray } 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /TTFortuneWheel/SpinningWheelSliceImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpinningWheelSliceImpl.swift 3 | // TTFortuneWheel 4 | // 5 | // Created by Efraim Budusan on 10/2/17. 6 | // Copyright © 2017 Efraim Budusn. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | //Just a basic implementation of spinning wheel slice 13 | public class FortuneWheelSlice: FortuneWheelSliceProtocol { 14 | 15 | public enum Style { 16 | case dark 17 | case light 18 | } 19 | 20 | public var title: String 21 | public var degree: CGFloat = 0.0 22 | 23 | public var backgroundColor: UIColor? { 24 | switch style { 25 | case .dark: return TTUtils.uiColor(from: 0x320A51) 26 | case .light: return UIColor.white 27 | } 28 | } 29 | 30 | public var fontColor: UIColor { 31 | switch style { 32 | case .dark: return UIColor.white 33 | case .light: return TTUtils.uiColor(from: 0x320A51) 34 | } 35 | } 36 | 37 | public var font: UIFont { 38 | return UIFont.systemFont(ofSize: fontSize, weight: .bold) 39 | } 40 | 41 | public var style:Style = .dark 42 | 43 | public init(title:String) { 44 | self.title = title 45 | } 46 | 47 | public convenience init(title:String, degree:CGFloat) { 48 | self.init(title:title) 49 | self.degree = degree 50 | } 51 | 52 | public func drawAdditionalGraphics(in context:CGContext, circularSegmentHeight:CGFloat,radius:CGFloat,sliceDegree:CGFloat) { 53 | let image = UIImage(named: "niddleImage", in: Bundle.sw_frameworkBundle(), compatibleWith: nil)! 54 | let centerOffset = CGPoint(x: -64, y: 17) 55 | let additionalGraphicRect = CGRect(x: centerOffset.x, y: centerOffset.y, width: 12, height: 12) 56 | let additionalGraphicPath = UIBezierPath(rect: additionalGraphicRect) 57 | context.saveGState() 58 | additionalGraphicPath.addClip() 59 | context.scaleBy(x: 1, y: -1) 60 | context.draw(image.cgImage!, in: CGRect(x: additionalGraphicRect.minX, y: -additionalGraphicRect.minY, width: image.size.width, height: image.size.height), byTiling: true) 61 | context.restoreGState() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /TTFortuneWheel/TTFortuneWheel.h: -------------------------------------------------------------------------------- 1 | // 2 | // TTFortuneWheel.h 3 | // TTFortuneWheel 4 | // 5 | // Created by Efraim Budusan on 10/31/17. 6 | // Copyright © 2017 Tapptitude. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for TTFortuneWheel. 12 | FOUNDATION_EXPORT double TTFortuneWheelVersionNumber; 13 | 14 | //! Project version string for TTFortuneWheel. 15 | FOUNDATION_EXPORT const unsigned char TTFortuneWheelVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /TTFortuneWheel/TTFortuneWheel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TTFortuneWheel.swift 3 | // TTFortuneWheel 4 | // 5 | // Created by Efraim Budusan on 9/29/17. 6 | // Copyright © 2017 Efraim Budusn. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class TTFortuneWheel: UIControl, CAAnimationDelegate, SpinningAnimatorProtocol { 12 | 13 | /// Set to true if you want all slices to be disributed evenly 14 | open var equalSlices:Bool = false 15 | 16 | open var slices:[FortuneWheelSliceProtocol]! 17 | 18 | /// UIConfiguration of the main frame 19 | open var frameStroke:StrokeInfo = StrokeInfo(color: TTUtils.uiColor(from: 0x6E17B3), width: 8) 20 | open var shadow:NSShadow? 21 | 22 | 23 | /// Set this to start drawing from that offset 24 | /// The sliced centerd to this offset will be 0 indexed one 25 | open var initialDrawingOffset:CGFloat = 0.0 26 | 27 | open var titleRotation:CGFloat = 0.0 28 | 29 | lazy private var animator:SpinningWheelAnimator = SpinningWheelAnimator(withObjectToAnimate: self) 30 | private(set) var sliceDegree:CGFloat? 31 | private(set) var wheelLayer:FortuneWheelLayer! 32 | 33 | public init(frame: CGRect, slices:[FortuneWheelSliceProtocol]) { 34 | super.init(frame: frame) 35 | self.slices = slices 36 | self.shadow = defaultShadow 37 | addWheelLayer() 38 | } 39 | 40 | override public func layoutSubviews() { 41 | super.layoutSubviews() 42 | self.layer.needsDisplayOnBoundsChange = true 43 | } 44 | 45 | public required init?(coder aDecoder: NSCoder) { 46 | super.init(coder: aDecoder) 47 | self.shadow = defaultShadow 48 | } 49 | 50 | func sliceInfoIsValid() -> Bool { 51 | if equalSlices{ return true } 52 | return slices.reduce(0, {$0 + $1.degree}) == 360 53 | } 54 | 55 | override public func draw(_ rect: CGRect) { 56 | super.draw(rect) 57 | if let existing = wheelLayer { 58 | existing.removeFromSuperlayer() 59 | } 60 | addWheelLayer() 61 | assert(sliceInfoIsValid(), "All slices must have a 360 degree combined. Set equalSlices true if you want to distribute them evenly.") 62 | if equalSlices { 63 | sliceDegree = 360.0 / CGFloat(slices.count) 64 | } 65 | } 66 | 67 | private func addWheelLayer() { 68 | wheelLayer = FortuneWheelLayer(frame:self.bounds,parent:self,initialOffset:initialDrawingOffset) 69 | self.layer.addSublayer(wheelLayer) 70 | wheelLayer.setNeedsDisplay() 71 | } 72 | 73 | internal var defaultShadow:NSShadow { 74 | let shadow = NSShadow() 75 | shadow.shadowColor = UIColor.black.withAlphaComponent(0.5) 76 | shadow.shadowOffset = CGSize(width: 0, height: 0) 77 | shadow.shadowBlurRadius = 14 78 | return shadow 79 | } 80 | 81 | //// Animation conformance 82 | internal var layerToAnimate: CALayer { 83 | return self.wheelLayer 84 | } 85 | 86 | open func startAnimating(rotationCompletionOffset:CGFloat = 0.0, _ completion:((Bool) -> Void)?) { 87 | self.stopAnimating() 88 | self.animator.addRotationAnimation(completionBlock: completion,rotationOffset:rotationCompletionOffset) 89 | } 90 | 91 | open func startAnimating(fininshIndex:Int = 0, _ completion:((Bool) -> Void)?) { 92 | let rotation = 360.0 - computeRadian(from: fininshIndex) 93 | self.startAnimating(rotationCompletionOffset: rotation, completion) 94 | } 95 | 96 | open func startAnimating() { 97 | self.animator.addIndefiniteRotationAnimation() 98 | } 99 | 100 | open func stopAnimating() { 101 | self.animator.removeAllAnimations() 102 | } 103 | 104 | open func startAnimating(fininshIndex:Int = 0, offset:CGFloat, _ completion:((Bool) -> Void)?) { 105 | let rotation = 360.0 - computeRadian(from: fininshIndex) + offset 106 | self.startAnimating(rotationCompletionOffset: rotation, completion) 107 | } 108 | 109 | private func computeRadian(from finishIndex:Int) -> CGFloat { 110 | if equalSlices { 111 | return CGFloat(finishIndex) * sliceDegree! 112 | } 113 | return slices.enumerated().filter({ $0.offset < finishIndex}).reduce(0.0, { $0 + $1.element.degree }) 114 | } 115 | } 116 | 117 | 118 | -------------------------------------------------------------------------------- /TTFortuneWheel/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // TTFortuneWheel 4 | // 5 | // Created by Efraim Budusan on 10/2/17. 6 | // Copyright © 2017 Efraim Budusn. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | #if os(iOS) 11 | import UIKit 12 | #else 13 | import Cocoa 14 | #endif 15 | 16 | public class TTUtils { 17 | 18 | public class func uiColor(from rgbValue: UInt, alpha: CGFloat = 1.0) -> UIColor { 19 | return UIColor( 20 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, 21 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, 22 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0, 23 | alpha: CGFloat(alpha) 24 | ) 25 | } 26 | 27 | //QuartzCode methods utils 28 | 29 | class func group(animations : [CAAnimation], fillMode : String!, forEffectLayer : Bool = false, sublayersCount : NSInteger = 0) -> CAAnimationGroup!{ 30 | let groupAnimation = CAAnimationGroup() 31 | groupAnimation.animations = animations 32 | 33 | if (fillMode != nil){ 34 | if let animations = groupAnimation.animations { 35 | for anim in animations { 36 | anim.fillMode = CAMediaTimingFillMode(rawValue: fillMode!) 37 | } 38 | } 39 | groupAnimation.fillMode = CAMediaTimingFillMode(rawValue: fillMode!) 40 | groupAnimation.isRemovedOnCompletion = false 41 | } 42 | 43 | if forEffectLayer{ 44 | groupAnimation.duration = TTUtils.maxDuration(ofEffectAnimation: groupAnimation, sublayersCount: sublayersCount) 45 | }else{ 46 | groupAnimation.duration = TTUtils.maxDuration(ofAnimations: animations) 47 | } 48 | 49 | return groupAnimation 50 | } 51 | 52 | class func maxDuration(ofAnimations anims: [CAAnimation]) -> CFTimeInterval{ 53 | var maxDuration: CGFloat = 0; 54 | for anim in anims { 55 | maxDuration = max(CGFloat(anim.beginTime + anim.duration) * CGFloat(anim.repeatCount == 0 ? 1.0 : anim.repeatCount) * (anim.autoreverses ? 2.0 : 1.0), maxDuration); 56 | } 57 | 58 | if maxDuration.isInfinite { 59 | return TimeInterval(NSIntegerMax) 60 | } 61 | 62 | return CFTimeInterval(maxDuration); 63 | } 64 | 65 | class func maxDuration(ofEffectAnimation anim: CAAnimation, sublayersCount : NSInteger) -> CFTimeInterval{ 66 | var maxDuration : CGFloat = 0 67 | if let groupAnim = anim as? CAAnimationGroup{ 68 | for subAnim in groupAnim.animations! as [CAAnimation]{ 69 | 70 | var delay : CGFloat = 0 71 | if let instDelay = (subAnim.value(forKey: "instanceDelay") as? NSNumber)?.floatValue{ 72 | delay = CGFloat(instDelay) * CGFloat(sublayersCount - 1); 73 | } 74 | var repeatCountDuration : CGFloat = 0; 75 | if subAnim.repeatCount > 1 { 76 | repeatCountDuration = CGFloat(subAnim.duration) * CGFloat(subAnim.repeatCount-1); 77 | } 78 | var duration : CGFloat = 0; 79 | 80 | duration = CGFloat(subAnim.beginTime) + (subAnim.autoreverses ? CGFloat(subAnim.duration) : CGFloat(0)) + delay + CGFloat(subAnim.duration) + CGFloat(repeatCountDuration); 81 | maxDuration = max(duration, maxDuration); 82 | } 83 | } 84 | 85 | if maxDuration.isInfinite { 86 | maxDuration = 1000 87 | } 88 | 89 | return CFTimeInterval(maxDuration); 90 | } 91 | 92 | class func updateValueFromAnimations(forLayers layers: [CALayer]){ 93 | CATransaction.begin() 94 | CATransaction.setDisableActions(true) 95 | 96 | for aLayer in layers{ 97 | if let keys = aLayer.animationKeys() as [String]?{ 98 | for animKey in keys{ 99 | let anim = aLayer.animation(forKey: animKey) 100 | updateValue(forAnimation: anim!, theLayer: aLayer); 101 | } 102 | } 103 | 104 | } 105 | 106 | CATransaction.commit() 107 | } 108 | 109 | class func updateValue(forAnimation anim: CAAnimation, theLayer : CALayer){ 110 | if let basicAnim = anim as? CABasicAnimation{ 111 | if (!basicAnim.autoreverses) { 112 | theLayer.setValue(basicAnim.toValue, forKeyPath: basicAnim.keyPath!) 113 | } 114 | }else if let keyAnim = anim as? CAKeyframeAnimation{ 115 | if (!keyAnim.autoreverses) { 116 | theLayer.setValue(keyAnim.values?.last, forKeyPath: keyAnim.keyPath!) 117 | } 118 | }else if let groupAnim = anim as? CAAnimationGroup{ 119 | for subAnim in groupAnim.animations! as [CAAnimation]{ 120 | updateValue(forAnimation: subAnim, theLayer: theLayer); 121 | 122 | } 123 | } 124 | } 125 | 126 | class func updateValueFromPresentationLayer(forAnimation anim: CAAnimation!, theLayer : CALayer){ 127 | if let basicAnim = anim as? CABasicAnimation{ 128 | theLayer.setValue(theLayer.presentation()?.value(forKeyPath: basicAnim.keyPath!), forKeyPath: basicAnim.keyPath!) 129 | }else if let keyAnim = anim as? CAKeyframeAnimation{ 130 | theLayer.setValue(theLayer.presentation()?.value(forKeyPath: keyAnim.keyPath!), forKeyPath: keyAnim.keyPath!) 131 | }else if let groupAnim = anim as? CAAnimationGroup{ 132 | for subAnim in groupAnim.animations! as [CAAnimation]{ 133 | updateValueFromPresentationLayer(forAnimation: subAnim, theLayer: theLayer) 134 | } 135 | } 136 | } 137 | } 138 | 139 | extension Collection where Indices.Iterator.Element == Index { 140 | 141 | /// Returns the element at the specified index iff it is within bounds, otherwise nil. 142 | subscript (safe index: Index) -> Iterator.Element? { 143 | return indices.contains(index) ? self[index] : nil 144 | } 145 | } 146 | 147 | extension Bundle { 148 | public static func sw_frameworkBundle() -> Bundle { 149 | let bundle = Bundle(for: TTUtils.self) 150 | if let path = bundle.path(forResource: "TTFortuneWheel", ofType: "bundle") { 151 | return Bundle(path: path)! 152 | } 153 | else { 154 | return bundle 155 | } 156 | } 157 | } 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /TTFortuneWheelSample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TTFortuneWheelSample 4 | // 5 | // Created by Efraim Budusan on 10/31/17. 6 | // Copyright © 2017 Tapptitude. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/nidleImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "nidleImage@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "nidleImage@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/nidleImage.imageset/nidleImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Assets.xcassets/nidleImage.imageset/nidleImage@2x.png -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/nidleImage.imageset/nidleImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Assets.xcassets/nidleImage.imageset/nidleImage@3x.png -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/spin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "spin@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "spin@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/spin.imageset/spin@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Assets.xcassets/spin.imageset/spin@2x.png -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/spin.imageset/spin@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Assets.xcassets/spin.imageset/spin@3x.png -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/spinningWhelAxel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "axel@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "axel@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/spinningWhelAxel.imageset/axel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Assets.xcassets/spinningWhelAxel.imageset/axel@2x.png -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/spinningWhelAxel.imageset/axel@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Assets.xcassets/spinningWhelAxel.imageset/axel@3x.png -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/wheelFrame.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "frame@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "frame@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/wheelFrame.imageset/frame@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Assets.xcassets/wheelFrame.imageset/frame@2x.png -------------------------------------------------------------------------------- /TTFortuneWheelSample/Assets.xcassets/wheelFrame.imageset/frame@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Assets.xcassets/wheelFrame.imageset/frame@3x.png -------------------------------------------------------------------------------- /TTFortuneWheelSample/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 | -------------------------------------------------------------------------------- /TTFortuneWheelSample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /TTFortuneWheelSample/CarnivalWheelSlice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CarnivalWheelSlice.swift 3 | // TTFortuneWheelSample 4 | // 5 | // Created by Efraim Budusan on 11/1/17. 6 | // Copyright © 2017 Tapptitude. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import TTFortuneWheel 11 | 12 | public class CarnivalWheelSlice: FortuneWheelSliceProtocol { 13 | 14 | public enum Style { 15 | case brickRed 16 | case sandYellow 17 | case babyBlue 18 | case deepBlue 19 | } 20 | 21 | public var title: String 22 | public var degree: CGFloat = 0.0 23 | 24 | public var backgroundColor: UIColor? { 25 | switch style { 26 | case .brickRed: return TTUtils.uiColor(from:0xE27230) 27 | case .sandYellow: return TTUtils.uiColor(from:0xF7D565) 28 | case .babyBlue: return TTUtils.uiColor(from:0x93D0C4) 29 | case .deepBlue: return TTUtils.uiColor(from:0x2A7F7F) 30 | } 31 | } 32 | 33 | public var fontColor: UIColor { 34 | return UIColor.white 35 | } 36 | 37 | public var offsetFromExterior:CGFloat { 38 | return 10.0 39 | } 40 | 41 | public var font: UIFont { 42 | switch style { 43 | case .brickRed: return UIFont(name: "ChunkFive", size: 22.0)! 44 | case .sandYellow: return UIFont(name: "Lobster 1.3", size: 22.0)! 45 | case .babyBlue: return UIFont(name: "Phosphate", size: 22.0)! 46 | case .deepBlue: return UIFont(name: "Bebas", size: 22.0)! 47 | } 48 | } 49 | 50 | public var stroke: StrokeInfo? { 51 | return StrokeInfo(color: UIColor.white, width: 1.0) 52 | } 53 | 54 | public var style:Style = .brickRed 55 | 56 | public init(title:String) { 57 | self.title = title 58 | } 59 | 60 | public convenience init(title:String, degree:CGFloat) { 61 | self.init(title:title) 62 | self.degree = degree 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /TTFortuneWheelSample/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 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIAppFonts 45 | 46 | BEBAS___.ttf 47 | Phosphate.ttc 48 | Lobster_1.3.otf 49 | Chunkfive.otf 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /TTFortuneWheelSample/Resources/BEBAS___.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Resources/BEBAS___.ttf -------------------------------------------------------------------------------- /TTFortuneWheelSample/Resources/Chunkfive.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Resources/Chunkfive.otf -------------------------------------------------------------------------------- /TTFortuneWheelSample/Resources/Lobster_1.3.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Resources/Lobster_1.3.otf -------------------------------------------------------------------------------- /TTFortuneWheelSample/Resources/Phosphate.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tapptitude/TTFortuneWheel-iOS/d5a7921ddf7b2cfa6a06b5e0d0798c074141822e/TTFortuneWheelSample/Resources/Phosphate.ttc -------------------------------------------------------------------------------- /TTFortuneWheelSample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TTFortuneWheelSample 4 | // 5 | // Created by Efraim Budusan on 10/31/17. 6 | // Copyright © 2017 Tapptitude. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import TTFortuneWheel 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var spinningWheel: TTFortuneWheel! 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | let slices = [ CarnivalWheelSlice.init(title: "Roller Coaster"), 18 | CarnivalWheelSlice.init(title: "Try again"), 19 | CarnivalWheelSlice.init(title: "Free\nticket"), 20 | CarnivalWheelSlice.init(title: "Teddy\nbear"), 21 | CarnivalWheelSlice.init(title: "Large popcorn"), 22 | CarnivalWheelSlice.init(title: "Balloon figures"), 23 | CarnivalWheelSlice.init(title: "Ferris Wheel"), 24 | CarnivalWheelSlice.init(title: "Pony\nRide")] 25 | spinningWheel.slices = slices 26 | spinningWheel.equalSlices = true 27 | spinningWheel.frameStroke.width = 0 28 | spinningWheel.titleRotation = CGFloat.pi 29 | spinningWheel.slices.enumerated().forEach { (pair) in 30 | let slice = pair.element as! CarnivalWheelSlice 31 | let offset = pair.offset 32 | switch offset % 4 { 33 | case 0: slice.style = .brickRed 34 | case 1: slice.style = .sandYellow 35 | case 2: slice.style = .babyBlue 36 | case 3: slice.style = .deepBlue 37 | default: slice.style = .brickRed 38 | } 39 | } 40 | } 41 | 42 | override func didReceiveMemoryWarning() { 43 | super.didReceiveMemoryWarning() 44 | 45 | // Dispose of any resources that can be recreated. 46 | } 47 | 48 | @IBAction func rotateButton(_ sender: Any) { 49 | 50 | spinningWheel.startAnimating() 51 | DispatchQueue.main.asyncAfter(deadline: .now() + 4.0) { 52 | self.spinningWheel.startAnimating(fininshIndex: 5) { (finished) in 53 | print(finished) 54 | } 55 | } 56 | } 57 | } 58 | 59 | --------------------------------------------------------------------------------