├── .gitignore ├── CHANGES ├── Images └── rsplaypausebutton-morph-animation.gif ├── LICENSE.md ├── README.md ├── RSPlayPauseButton.podspec ├── RSPlayPauseButtonDemo.xcodeproj └── project.pbxproj ├── RSPlayPauseButtonDemo ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Images.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-1024.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ └── Icon-83.5@2x.png ├── Info.plist ├── RSPlayPauseButton │ ├── RSPlayPauseButton.h │ └── RSPlayPauseButton.m ├── ViewController.h ├── ViewController.m └── main.m └── RSPlayPauseButtonDemoTests ├── Info.plist └── RSPlayPauseButtonDemoTests.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Global .gitignore 2 | # Files such as .DS_Store on OS X should be excluded system-wide: $ git config --global core.excludesfile "~/.gitignore_global_osx" 3 | 4 | # Xcode 5 | build/* 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | *.xcworkspace 15 | !default.xcworkspace 16 | xcuserdata 17 | profile 18 | *.moved-aside 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? 25 | # 26 | # Pods/ 27 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | = 1.0.1 (2014-11-19) 2 | 3 | * Add Animation Style 'None' to demo project and make it the default. (Raphael Schaad) 4 | 5 | * Fix bug where a unsupported transition style would lead to no change in state at all. (Raphael Schaad) 6 | 7 | = 1.0.0 (2014-11-15) 8 | 9 | * Initial commit (Raphael Schaad) 10 | -------------------------------------------------------------------------------- /Images/rsplaypausebutton-morph-animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelschaad/RSPlayPauseButton/3ec2071d2d684da664090e54cc9dd0ea98e16d4f/Images/rsplaypausebutton-morph-animation.gif -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | RSPlayPauseButton is free and unencumbered software created by Raphael Schaad in 2014 and released into the public domain. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RSPlayPauseButton 2 | 3 | A `UIControl` with a play/pause icon that nicely morphs between the two. 4 | 5 | ![RSPlayPauseButton Morph Animation](https://raw.githubusercontent.com/raphaelschaad/RSPlayPauseButton/master/Images/rsplaypausebutton-morph-animation.gif) 6 | 7 | You can choose from [two morphing styles](http://vimeo.com/raphaelschaad/rsplaypausebutton): Split, and Split & Rotate. 8 | 9 | If using CocoaPods, the quickest way to try it out is to type this on the command line: 10 | 11 | ```shell 12 | $ pod try RSPlayPauseButton 13 | ``` 14 | 15 | To add it to your app, copy the class `RSPlayPauseButton.h/.m` into your Xcode project or add via [CocoaPods](http://cocoapods.org) by adding this to your Podfile: 16 | 17 | ```ruby 18 | pod 'RSPlayPauseButton', '~> 1.0' 19 | ``` 20 | 21 | In your code, `#import "RSPlayPauseButton.h"`, create the control, and setup an action to toggle it: 22 | 23 | ```objective-c 24 | // ... e.g. in `-viewDidLoad:` ... 25 | RSPlayPauseButton *playPauseButton = [[RSPlayPauseButton alloc] init]; 26 | [playPauseButton addTarget:self action:@selector(playPauseButtonDidPress:) forControlEvents:UIControlEventTouchUpInside]; 27 | [self.view addSubview:playPauseButton]; 28 | } 29 | 30 | - (void)playPauseButtonDidPress:(RSPlayPauseButton *)playPauseButton { 31 | [playPauseButton setPaused:!playPauseButton.isPaused animated:YES]; 32 | } 33 | ``` 34 | 35 | Since I release RSPlayPauseButton as free and unencumbered software into the public domain you can do with it whatever you want including using it in any app on the App Store. I originally released this code in early 2014 [as a gist](https://gist.github.com/raphaelschaad/9734463) and was happy to see Google (or should one say Alphabet now?) [adding the Split style](http://littlebigdetails.com/post/119360573466/youtube-the-play-button-smoothly-animates-into) in mid-2015 to the YouTube.com player. 36 | 37 | Feel free to reach out to [@RaphaelSchaad](https://twitter.com/raphaelschaad) and say hi. 38 | -------------------------------------------------------------------------------- /RSPlayPauseButton.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "RSPlayPauseButton" 3 | spec.version = "1.0.1" 4 | spec.summary = "Play/pause button that nicely morphs between the two states" 5 | spec.description = <<-DESC 6 | A UIControl with a play/pause icon that nicely morphs between the two. 7 | You can choose from [two morphing styles](http://vimeo.com/raphaelschaad/rsplaypausebutton): Split, and Split & Rotate. 8 | DESC 9 | 10 | spec.homepage = "https://github.com/raphaelschaad/RSPlayPauseButton" 11 | spec.screenshots = "https://raw.githubusercontent.com/raphaelschaad/RSPlayPauseButton/master/Images/rsplaypausebutton-morph-animation.gif" 12 | spec.license = { :type => "Public domain", :file => "LICENSE.md" } 13 | spec.author = { "Raphael Schaad" => "raphael.schaad@gmail.com" } 14 | spec.social_media_url = "https://twitter.com/raphaelschaad" 15 | spec.platform = :ios, "7.0" 16 | spec.source = { :git => "https://github.com/raphaelschaad/RSPlayPauseButton.git", :tag => "1.0.1" } 17 | spec.source_files = "RSPlayPauseButtonDemo/RSPlayPauseButton/*.{h,m}" 18 | spec.requires_arc = true 19 | end 20 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 877B26971A14A19000B5AB09 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 877B26961A14A19000B5AB09 /* main.m */; }; 11 | 877B269A1A14A19000B5AB09 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 877B26991A14A19000B5AB09 /* AppDelegate.m */; }; 12 | 877B269D1A14A19000B5AB09 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 877B269C1A14A19000B5AB09 /* ViewController.m */; }; 13 | 877B26A01A14A19000B5AB09 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 877B269E1A14A19000B5AB09 /* Main.storyboard */; }; 14 | 877B26A21A14A19000B5AB09 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 877B26A11A14A19000B5AB09 /* Images.xcassets */; }; 15 | 877B26A51A14A19000B5AB09 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 877B26A31A14A19000B5AB09 /* LaunchScreen.xib */; }; 16 | 877B26B11A14A19000B5AB09 /* RSPlayPauseButtonDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 877B26B01A14A19000B5AB09 /* RSPlayPauseButtonDemoTests.m */; }; 17 | 877B26BD1A14A24800B5AB09 /* RSPlayPauseButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 877B26BC1A14A24800B5AB09 /* RSPlayPauseButton.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | 877B26AB1A14A19000B5AB09 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = 877B26891A14A19000B5AB09 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = 877B26901A14A19000B5AB09; 26 | remoteInfo = RSPlayPauseButtonDemo; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 877B26911A14A19000B5AB09 /* RSPlayPauseButtonDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RSPlayPauseButtonDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 877B26951A14A19000B5AB09 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | 877B26961A14A19000B5AB09 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 34 | 877B26981A14A19000B5AB09 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 35 | 877B26991A14A19000B5AB09 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 36 | 877B269B1A14A19000B5AB09 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 37 | 877B269C1A14A19000B5AB09 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 38 | 877B269F1A14A19000B5AB09 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 877B26A11A14A19000B5AB09 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 40 | 877B26A41A14A19000B5AB09 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 41 | 877B26AA1A14A19000B5AB09 /* RSPlayPauseButtonDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RSPlayPauseButtonDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 877B26AF1A14A19000B5AB09 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 877B26B01A14A19000B5AB09 /* RSPlayPauseButtonDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RSPlayPauseButtonDemoTests.m; sourceTree = ""; }; 44 | 877B26BB1A14A24800B5AB09 /* RSPlayPauseButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RSPlayPauseButton.h; sourceTree = ""; }; 45 | 877B26BC1A14A24800B5AB09 /* RSPlayPauseButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSPlayPauseButton.m; sourceTree = ""; }; 46 | /* End PBXFileReference section */ 47 | 48 | /* Begin PBXFrameworksBuildPhase section */ 49 | 877B268E1A14A19000B5AB09 /* Frameworks */ = { 50 | isa = PBXFrameworksBuildPhase; 51 | buildActionMask = 2147483647; 52 | files = ( 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | 877B26A71A14A19000B5AB09 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 877B26881A14A19000B5AB09 = { 67 | isa = PBXGroup; 68 | children = ( 69 | 877B26931A14A19000B5AB09 /* RSPlayPauseButtonDemo */, 70 | 877B26AD1A14A19000B5AB09 /* RSPlayPauseButtonDemoTests */, 71 | 877B26921A14A19000B5AB09 /* Products */, 72 | ); 73 | sourceTree = ""; 74 | }; 75 | 877B26921A14A19000B5AB09 /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 877B26911A14A19000B5AB09 /* RSPlayPauseButtonDemo.app */, 79 | 877B26AA1A14A19000B5AB09 /* RSPlayPauseButtonDemoTests.xctest */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 877B26931A14A19000B5AB09 /* RSPlayPauseButtonDemo */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 877B26981A14A19000B5AB09 /* AppDelegate.h */, 88 | 877B26991A14A19000B5AB09 /* AppDelegate.m */, 89 | 877B269B1A14A19000B5AB09 /* ViewController.h */, 90 | 877B269C1A14A19000B5AB09 /* ViewController.m */, 91 | 877B269E1A14A19000B5AB09 /* Main.storyboard */, 92 | 877B26A11A14A19000B5AB09 /* Images.xcassets */, 93 | 877B26A31A14A19000B5AB09 /* LaunchScreen.xib */, 94 | 877B26BA1A14A24800B5AB09 /* RSPlayPauseButton */, 95 | 877B26941A14A19000B5AB09 /* Supporting Files */, 96 | ); 97 | path = RSPlayPauseButtonDemo; 98 | sourceTree = ""; 99 | }; 100 | 877B26941A14A19000B5AB09 /* Supporting Files */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 877B26951A14A19000B5AB09 /* Info.plist */, 104 | 877B26961A14A19000B5AB09 /* main.m */, 105 | ); 106 | name = "Supporting Files"; 107 | sourceTree = ""; 108 | }; 109 | 877B26AD1A14A19000B5AB09 /* RSPlayPauseButtonDemoTests */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 877B26B01A14A19000B5AB09 /* RSPlayPauseButtonDemoTests.m */, 113 | 877B26AE1A14A19000B5AB09 /* Supporting Files */, 114 | ); 115 | path = RSPlayPauseButtonDemoTests; 116 | sourceTree = ""; 117 | }; 118 | 877B26AE1A14A19000B5AB09 /* Supporting Files */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 877B26AF1A14A19000B5AB09 /* Info.plist */, 122 | ); 123 | name = "Supporting Files"; 124 | sourceTree = ""; 125 | }; 126 | 877B26BA1A14A24800B5AB09 /* RSPlayPauseButton */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 877B26BB1A14A24800B5AB09 /* RSPlayPauseButton.h */, 130 | 877B26BC1A14A24800B5AB09 /* RSPlayPauseButton.m */, 131 | ); 132 | path = RSPlayPauseButton; 133 | sourceTree = ""; 134 | }; 135 | /* End PBXGroup section */ 136 | 137 | /* Begin PBXNativeTarget section */ 138 | 877B26901A14A19000B5AB09 /* RSPlayPauseButtonDemo */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = 877B26B41A14A19000B5AB09 /* Build configuration list for PBXNativeTarget "RSPlayPauseButtonDemo" */; 141 | buildPhases = ( 142 | 877B268D1A14A19000B5AB09 /* Sources */, 143 | 877B268E1A14A19000B5AB09 /* Frameworks */, 144 | 877B268F1A14A19000B5AB09 /* Resources */, 145 | ); 146 | buildRules = ( 147 | ); 148 | dependencies = ( 149 | ); 150 | name = RSPlayPauseButtonDemo; 151 | productName = RSPlayPauseButtonDemo; 152 | productReference = 877B26911A14A19000B5AB09 /* RSPlayPauseButtonDemo.app */; 153 | productType = "com.apple.product-type.application"; 154 | }; 155 | 877B26A91A14A19000B5AB09 /* RSPlayPauseButtonDemoTests */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = 877B26B71A14A19000B5AB09 /* Build configuration list for PBXNativeTarget "RSPlayPauseButtonDemoTests" */; 158 | buildPhases = ( 159 | 877B26A61A14A19000B5AB09 /* Sources */, 160 | 877B26A71A14A19000B5AB09 /* Frameworks */, 161 | 877B26A81A14A19000B5AB09 /* Resources */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | 877B26AC1A14A19000B5AB09 /* PBXTargetDependency */, 167 | ); 168 | name = RSPlayPauseButtonDemoTests; 169 | productName = RSPlayPauseButtonDemoTests; 170 | productReference = 877B26AA1A14A19000B5AB09 /* RSPlayPauseButtonDemoTests.xctest */; 171 | productType = "com.apple.product-type.bundle.unit-test"; 172 | }; 173 | /* End PBXNativeTarget section */ 174 | 175 | /* Begin PBXProject section */ 176 | 877B26891A14A19000B5AB09 /* Project object */ = { 177 | isa = PBXProject; 178 | attributes = { 179 | LastUpgradeCheck = 1030; 180 | TargetAttributes = { 181 | 877B26901A14A19000B5AB09 = { 182 | CreatedOnToolsVersion = 6.1; 183 | }; 184 | 877B26A91A14A19000B5AB09 = { 185 | CreatedOnToolsVersion = 6.1; 186 | TestTargetID = 877B26901A14A19000B5AB09; 187 | }; 188 | }; 189 | }; 190 | buildConfigurationList = 877B268C1A14A19000B5AB09 /* Build configuration list for PBXProject "RSPlayPauseButtonDemo" */; 191 | compatibilityVersion = "Xcode 3.2"; 192 | developmentRegion = en; 193 | hasScannedForEncodings = 0; 194 | knownRegions = ( 195 | en, 196 | Base, 197 | ); 198 | mainGroup = 877B26881A14A19000B5AB09; 199 | productRefGroup = 877B26921A14A19000B5AB09 /* Products */; 200 | projectDirPath = ""; 201 | projectRoot = ""; 202 | targets = ( 203 | 877B26901A14A19000B5AB09 /* RSPlayPauseButtonDemo */, 204 | 877B26A91A14A19000B5AB09 /* RSPlayPauseButtonDemoTests */, 205 | ); 206 | }; 207 | /* End PBXProject section */ 208 | 209 | /* Begin PBXResourcesBuildPhase section */ 210 | 877B268F1A14A19000B5AB09 /* Resources */ = { 211 | isa = PBXResourcesBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | 877B26A01A14A19000B5AB09 /* Main.storyboard in Resources */, 215 | 877B26A51A14A19000B5AB09 /* LaunchScreen.xib in Resources */, 216 | 877B26A21A14A19000B5AB09 /* Images.xcassets in Resources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | 877B26A81A14A19000B5AB09 /* Resources */ = { 221 | isa = PBXResourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | ); 225 | runOnlyForDeploymentPostprocessing = 0; 226 | }; 227 | /* End PBXResourcesBuildPhase section */ 228 | 229 | /* Begin PBXSourcesBuildPhase section */ 230 | 877B268D1A14A19000B5AB09 /* Sources */ = { 231 | isa = PBXSourcesBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | 877B269D1A14A19000B5AB09 /* ViewController.m in Sources */, 235 | 877B269A1A14A19000B5AB09 /* AppDelegate.m in Sources */, 236 | 877B26971A14A19000B5AB09 /* main.m in Sources */, 237 | 877B26BD1A14A24800B5AB09 /* RSPlayPauseButton.m in Sources */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | 877B26A61A14A19000B5AB09 /* Sources */ = { 242 | isa = PBXSourcesBuildPhase; 243 | buildActionMask = 2147483647; 244 | files = ( 245 | 877B26B11A14A19000B5AB09 /* RSPlayPauseButtonDemoTests.m in Sources */, 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | }; 249 | /* End PBXSourcesBuildPhase section */ 250 | 251 | /* Begin PBXTargetDependency section */ 252 | 877B26AC1A14A19000B5AB09 /* PBXTargetDependency */ = { 253 | isa = PBXTargetDependency; 254 | target = 877B26901A14A19000B5AB09 /* RSPlayPauseButtonDemo */; 255 | targetProxy = 877B26AB1A14A19000B5AB09 /* PBXContainerItemProxy */; 256 | }; 257 | /* End PBXTargetDependency section */ 258 | 259 | /* Begin PBXVariantGroup section */ 260 | 877B269E1A14A19000B5AB09 /* Main.storyboard */ = { 261 | isa = PBXVariantGroup; 262 | children = ( 263 | 877B269F1A14A19000B5AB09 /* Base */, 264 | ); 265 | name = Main.storyboard; 266 | sourceTree = ""; 267 | }; 268 | 877B26A31A14A19000B5AB09 /* LaunchScreen.xib */ = { 269 | isa = PBXVariantGroup; 270 | children = ( 271 | 877B26A41A14A19000B5AB09 /* Base */, 272 | ); 273 | name = LaunchScreen.xib; 274 | sourceTree = ""; 275 | }; 276 | /* End PBXVariantGroup section */ 277 | 278 | /* Begin XCBuildConfiguration section */ 279 | 877B26B21A14A19000B5AB09 /* Debug */ = { 280 | isa = XCBuildConfiguration; 281 | buildSettings = { 282 | ALWAYS_SEARCH_USER_PATHS = NO; 283 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 289 | CLANG_WARN_BOOL_CONVERSION = YES; 290 | CLANG_WARN_COMMA = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 293 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 294 | CLANG_WARN_EMPTY_BODY = YES; 295 | CLANG_WARN_ENUM_CONVERSION = YES; 296 | CLANG_WARN_INFINITE_RECURSION = YES; 297 | CLANG_WARN_INT_CONVERSION = YES; 298 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 299 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 300 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 301 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 302 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 303 | CLANG_WARN_STRICT_PROTOTYPES = YES; 304 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 305 | CLANG_WARN_UNREACHABLE_CODE = YES; 306 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 307 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 308 | COPY_PHASE_STRIP = NO; 309 | ENABLE_STRICT_OBJC_MSGSEND = YES; 310 | ENABLE_TESTABILITY = YES; 311 | GCC_C_LANGUAGE_STANDARD = gnu99; 312 | GCC_DYNAMIC_NO_PIC = NO; 313 | GCC_NO_COMMON_BLOCKS = YES; 314 | GCC_OPTIMIZATION_LEVEL = 0; 315 | GCC_PREPROCESSOR_DEFINITIONS = ( 316 | "DEBUG=1", 317 | "$(inherited)", 318 | ); 319 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 320 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 321 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 322 | GCC_WARN_UNDECLARED_SELECTOR = YES; 323 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 324 | GCC_WARN_UNUSED_FUNCTION = YES; 325 | GCC_WARN_UNUSED_VARIABLE = YES; 326 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 327 | MTL_ENABLE_DEBUG_INFO = YES; 328 | ONLY_ACTIVE_ARCH = YES; 329 | SDKROOT = iphoneos; 330 | TARGETED_DEVICE_FAMILY = "1,2"; 331 | }; 332 | name = Debug; 333 | }; 334 | 877B26B31A14A19000B5AB09 /* Release */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ALWAYS_SEARCH_USER_PATHS = NO; 338 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 339 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 340 | CLANG_CXX_LIBRARY = "libc++"; 341 | CLANG_ENABLE_MODULES = YES; 342 | CLANG_ENABLE_OBJC_ARC = YES; 343 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 344 | CLANG_WARN_BOOL_CONVERSION = YES; 345 | CLANG_WARN_COMMA = YES; 346 | CLANG_WARN_CONSTANT_CONVERSION = YES; 347 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 348 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 349 | CLANG_WARN_EMPTY_BODY = YES; 350 | CLANG_WARN_ENUM_CONVERSION = YES; 351 | CLANG_WARN_INFINITE_RECURSION = YES; 352 | CLANG_WARN_INT_CONVERSION = YES; 353 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 354 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 355 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 356 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 357 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 358 | CLANG_WARN_STRICT_PROTOTYPES = YES; 359 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 360 | CLANG_WARN_UNREACHABLE_CODE = YES; 361 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 362 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 363 | COPY_PHASE_STRIP = YES; 364 | ENABLE_NS_ASSERTIONS = NO; 365 | ENABLE_STRICT_OBJC_MSGSEND = YES; 366 | GCC_C_LANGUAGE_STANDARD = gnu99; 367 | GCC_NO_COMMON_BLOCKS = YES; 368 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 369 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 370 | GCC_WARN_UNDECLARED_SELECTOR = YES; 371 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 372 | GCC_WARN_UNUSED_FUNCTION = YES; 373 | GCC_WARN_UNUSED_VARIABLE = YES; 374 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 375 | MTL_ENABLE_DEBUG_INFO = NO; 376 | SDKROOT = iphoneos; 377 | TARGETED_DEVICE_FAMILY = "1,2"; 378 | VALIDATE_PRODUCT = YES; 379 | }; 380 | name = Release; 381 | }; 382 | 877B26B51A14A19000B5AB09 /* Debug */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 386 | INFOPLIST_FILE = RSPlayPauseButtonDemo/Info.plist; 387 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 388 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 389 | PRODUCT_BUNDLE_IDENTIFIER = "com.raphaelschaad.$(PRODUCT_NAME:rfc1034identifier)"; 390 | PRODUCT_NAME = "$(TARGET_NAME)"; 391 | }; 392 | name = Debug; 393 | }; 394 | 877B26B61A14A19000B5AB09 /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 398 | INFOPLIST_FILE = RSPlayPauseButtonDemo/Info.plist; 399 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 400 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 401 | PRODUCT_BUNDLE_IDENTIFIER = "com.raphaelschaad.$(PRODUCT_NAME:rfc1034identifier)"; 402 | PRODUCT_NAME = "$(TARGET_NAME)"; 403 | }; 404 | name = Release; 405 | }; 406 | 877B26B81A14A19000B5AB09 /* Debug */ = { 407 | isa = XCBuildConfiguration; 408 | buildSettings = { 409 | BUNDLE_LOADER = "$(TEST_HOST)"; 410 | FRAMEWORK_SEARCH_PATHS = ( 411 | "$(SDKROOT)/Developer/Library/Frameworks", 412 | "$(inherited)", 413 | ); 414 | GCC_PREPROCESSOR_DEFINITIONS = ( 415 | "DEBUG=1", 416 | "$(inherited)", 417 | ); 418 | INFOPLIST_FILE = RSPlayPauseButtonDemoTests/Info.plist; 419 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 420 | PRODUCT_BUNDLE_IDENTIFIER = "com.raphaelschaad.$(PRODUCT_NAME:rfc1034identifier)"; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RSPlayPauseButtonDemo.app/RSPlayPauseButtonDemo"; 423 | }; 424 | name = Debug; 425 | }; 426 | 877B26B91A14A19000B5AB09 /* Release */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | BUNDLE_LOADER = "$(TEST_HOST)"; 430 | FRAMEWORK_SEARCH_PATHS = ( 431 | "$(SDKROOT)/Developer/Library/Frameworks", 432 | "$(inherited)", 433 | ); 434 | INFOPLIST_FILE = RSPlayPauseButtonDemoTests/Info.plist; 435 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 436 | PRODUCT_BUNDLE_IDENTIFIER = "com.raphaelschaad.$(PRODUCT_NAME:rfc1034identifier)"; 437 | PRODUCT_NAME = "$(TARGET_NAME)"; 438 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RSPlayPauseButtonDemo.app/RSPlayPauseButtonDemo"; 439 | }; 440 | name = Release; 441 | }; 442 | /* End XCBuildConfiguration section */ 443 | 444 | /* Begin XCConfigurationList section */ 445 | 877B268C1A14A19000B5AB09 /* Build configuration list for PBXProject "RSPlayPauseButtonDemo" */ = { 446 | isa = XCConfigurationList; 447 | buildConfigurations = ( 448 | 877B26B21A14A19000B5AB09 /* Debug */, 449 | 877B26B31A14A19000B5AB09 /* Release */, 450 | ); 451 | defaultConfigurationIsVisible = 0; 452 | defaultConfigurationName = Release; 453 | }; 454 | 877B26B41A14A19000B5AB09 /* Build configuration list for PBXNativeTarget "RSPlayPauseButtonDemo" */ = { 455 | isa = XCConfigurationList; 456 | buildConfigurations = ( 457 | 877B26B51A14A19000B5AB09 /* Debug */, 458 | 877B26B61A14A19000B5AB09 /* Release */, 459 | ); 460 | defaultConfigurationIsVisible = 0; 461 | defaultConfigurationName = Release; 462 | }; 463 | 877B26B71A14A19000B5AB09 /* Build configuration list for PBXNativeTarget "RSPlayPauseButtonDemoTests" */ = { 464 | isa = XCConfigurationList; 465 | buildConfigurations = ( 466 | 877B26B81A14A19000B5AB09 /* Debug */, 467 | 877B26B91A14A19000B5AB09 /* Release */, 468 | ); 469 | defaultConfigurationIsVisible = 0; 470 | defaultConfigurationName = Release; 471 | }; 472 | /* End XCConfigurationList section */ 473 | }; 474 | rootObject = 877B26891A14A19000B5AB09 /* Project object */; 475 | } 476 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // RSPlayPauseButtonDemo 4 | // 5 | // Created by Raphael Schaad on 11/13/14. 6 | // 7 | // 8 | 9 | 10 | #import 11 | 12 | 13 | @interface AppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // RSPlayPauseButtonDemo 4 | // 5 | // Created by Raphael Schaad on 11/13/14. 6 | // 7 | // 8 | 9 | 10 | #import "AppDelegate.h" 11 | 12 | 13 | @implementation AppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | return YES; 18 | } 19 | 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/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 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/Images.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 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-60@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-60@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "idiom" : "ipad", 47 | "size" : "20x20", 48 | "scale" : "1x" 49 | }, 50 | { 51 | "idiom" : "ipad", 52 | "size" : "20x20", 53 | "scale" : "2x" 54 | }, 55 | { 56 | "idiom" : "ipad", 57 | "size" : "29x29", 58 | "scale" : "1x" 59 | }, 60 | { 61 | "idiom" : "ipad", 62 | "size" : "29x29", 63 | "scale" : "2x" 64 | }, 65 | { 66 | "idiom" : "ipad", 67 | "size" : "40x40", 68 | "scale" : "1x" 69 | }, 70 | { 71 | "idiom" : "ipad", 72 | "size" : "40x40", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "76x76", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-76.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "76x76", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-76@2x.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "83.5x83.5", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-83.5@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "1024x1024", 95 | "idiom" : "ios-marketing", 96 | "filename" : "Icon-1024.png", 97 | "scale" : "1x" 98 | } 99 | ], 100 | "info" : { 101 | "version" : 1, 102 | "author" : "xcode" 103 | } 104 | } -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelschaad/RSPlayPauseButton/3ec2071d2d684da664090e54cc9dd0ea98e16d4f/RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-1024.png -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelschaad/RSPlayPauseButton/3ec2071d2d684da664090e54cc9dd0ea98e16d4f/RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelschaad/RSPlayPauseButton/3ec2071d2d684da664090e54cc9dd0ea98e16d4f/RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelschaad/RSPlayPauseButton/3ec2071d2d684da664090e54cc9dd0ea98e16d4f/RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelschaad/RSPlayPauseButton/3ec2071d2d684da664090e54cc9dd0ea98e16d4f/RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raphaelschaad/RSPlayPauseButton/3ec2071d2d684da664090e54cc9dd0ea98e16d4f/RSPlayPauseButtonDemo/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ► morph ❚❚ 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/RSPlayPauseButton/RSPlayPauseButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // RSPlayPauseButton.h 3 | // 4 | // Created by Raphael Schaad on 2014-03-22. 5 | // This is free and unencumbered software released into the public domain. 6 | // 7 | 8 | 9 | #import 10 | 11 | 12 | typedef NS_ENUM(NSUInteger, RSPlayPauseButtonAnimationStyle) { 13 | RSPlayPauseButtonAnimationStyleSplit, // Default 14 | RSPlayPauseButtonAnimationStyleSplitAndRotate 15 | }; 16 | 17 | 18 | // 19 | // Displays a ⃝ with either the ► (play) or ❚❚ (pause) icon and nicely morphs between the two states. 20 | // It's targeted at iOS 8.1+ and is tintColor-aware. 21 | // 22 | @interface RSPlayPauseButton : UIControl 23 | 24 | // State 25 | @property (nonatomic, assign, getter = isPaused) BOOL paused; // Default is `YES`; setting this property doesn't animate the change 26 | - (void)setPaused:(BOOL)paused animated:(BOOL)animated; 27 | 28 | // Style 29 | @property (nonatomic, assign) RSPlayPauseButtonAnimationStyle animationStyle; // Default is `RSPlayPauseButtonAnimationStyleSplit` 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/RSPlayPauseButton/RSPlayPauseButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSPlayPauseButton.m 3 | // 4 | // Created by Raphael Schaad https://github.com/raphaelschaad on 2014-03-22. 5 | // This is free and unencumbered software released into the public domain. 6 | // 7 | 8 | 9 | #import "RSPlayPauseButton.h" 10 | 11 | 12 | static const CGFloat kScale = 1.0; 13 | static const CGFloat kBorderSize = 32.0 * kScale; 14 | static const CGFloat kBorderWidth = 3.0 * kScale; 15 | static const CGFloat kSize = kBorderSize + kBorderWidth; // The total size is the border size + 2x half the border width. 16 | static const CGFloat kPauseLineWidth = 4.0 * kScale; 17 | static const CGFloat kPauseLineHeight = 15.0 * kScale; 18 | static const CGFloat kPauseLinesSpace = 4.0 * kScale; 19 | static const CGFloat kPlayTriangleOffsetX = 2.0 * kScale; 20 | static const CGFloat kPlayTriangleTipOffsetX = 2.0 * kScale; 21 | 22 | static const CGPoint p1 = {0.0, 0.0}; // line 1, top left 23 | static const CGPoint p2 = {kPauseLineWidth, 0.0}; // line 1, top right 24 | static const CGPoint p3 = {kPauseLineWidth, kPauseLineHeight}; // line 1, bottom right 25 | static const CGPoint p4 = {0.0, kPauseLineHeight}; // line 1, bottom left 26 | 27 | static const CGPoint p5 = {kPauseLineWidth + kPauseLinesSpace, 0.0}; // line 2, top left 28 | static const CGPoint p6 = {kPauseLineWidth + kPauseLinesSpace + kPauseLineWidth, 0.0}; // line 2, top right 29 | static const CGPoint p7 = {kPauseLineWidth + kPauseLinesSpace + kPauseLineWidth, kPauseLineHeight}; // line 2, bottom right 30 | static const CGPoint p8 = {kPauseLineWidth + kPauseLinesSpace, kPauseLineHeight}; // line 2, bottom left 31 | 32 | 33 | @interface RSPlayPauseButton () 34 | 35 | @property (nonatomic, strong) CAShapeLayer *borderShapeLayer; 36 | @property (nonatomic, strong) CAShapeLayer *playPauseShapeLayer; 37 | @property (nonatomic, strong, readonly) UIBezierPath *pauseBezierPath; 38 | @property (nonatomic, strong, readonly) UIBezierPath *pauseRotateBezierPath; 39 | @property (nonatomic, strong, readonly) UIBezierPath *playBezierPath; 40 | @property (nonatomic, strong, readonly) UIBezierPath *playRotateBezierPath; 41 | 42 | @end 43 | 44 | 45 | @implementation RSPlayPauseButton 46 | 47 | #pragma mark - Accessors 48 | #pragma mark Public 49 | 50 | - (void)setPaused:(BOOL)paused 51 | { 52 | if (_paused != paused) { 53 | [self setPaused:paused animated:NO]; 54 | } 55 | } 56 | 57 | 58 | #pragma mark Private 59 | 60 | @synthesize pauseBezierPath = _pauseBezierPath; 61 | 62 | - (UIBezierPath *)pauseBezierPath 63 | { 64 | if (!_pauseBezierPath) { 65 | _pauseBezierPath = [UIBezierPath bezierPath]; 66 | 67 | // Subpath for 1. line 68 | [_pauseBezierPath moveToPoint:p1]; 69 | [_pauseBezierPath addLineToPoint:p2]; 70 | [_pauseBezierPath addLineToPoint:p3]; 71 | [_pauseBezierPath addLineToPoint:p4]; 72 | [_pauseBezierPath closePath]; 73 | 74 | // Subpath for 2. line 75 | [_pauseBezierPath moveToPoint:p5]; 76 | [_pauseBezierPath addLineToPoint:p6]; 77 | [_pauseBezierPath addLineToPoint:p7]; 78 | [_pauseBezierPath addLineToPoint:p8]; 79 | [_pauseBezierPath closePath]; 80 | } 81 | 82 | return _pauseBezierPath; 83 | } 84 | 85 | 86 | @synthesize pauseRotateBezierPath = _pauseRotateBezierPath; 87 | 88 | - (UIBezierPath *)pauseRotateBezierPath 89 | { 90 | if (!_pauseRotateBezierPath) { 91 | _pauseRotateBezierPath = [UIBezierPath bezierPath]; 92 | 93 | // Subpath for 1. line 94 | [_pauseRotateBezierPath moveToPoint:p7]; 95 | [_pauseRotateBezierPath addLineToPoint:p8]; 96 | [_pauseRotateBezierPath addLineToPoint:p5]; 97 | [_pauseRotateBezierPath addLineToPoint:p6]; 98 | [_pauseRotateBezierPath closePath]; 99 | 100 | // Subpath for 2. line 101 | [_pauseRotateBezierPath moveToPoint:p3]; 102 | [_pauseRotateBezierPath addLineToPoint:p4]; 103 | [_pauseRotateBezierPath addLineToPoint:p1]; 104 | [_pauseRotateBezierPath addLineToPoint:p2]; 105 | [_pauseRotateBezierPath closePath]; 106 | } 107 | 108 | return _pauseRotateBezierPath; 109 | } 110 | 111 | 112 | @synthesize playBezierPath = _playBezierPath; 113 | 114 | - (UIBezierPath *)playBezierPath 115 | { 116 | if (!_playBezierPath) { 117 | _playBezierPath = [UIBezierPath bezierPath]; 118 | 119 | const CGFloat kPauseLinesHalfSpace = kPauseLinesSpace / 2; 120 | const CGFloat kPauseLineHalfHeight = kPauseLineHeight / 2; 121 | 122 | CGPoint _p1 = CGPointMake(p1.x + kPlayTriangleOffsetX, p1.y); 123 | CGPoint _p2 = CGPointMake(p2.x + kPauseLinesHalfSpace, p2.y); 124 | CGPoint _p3 = CGPointMake(p3.x + kPauseLinesHalfSpace, p3.y); 125 | CGPoint _p4 = CGPointMake(p4.x + kPlayTriangleOffsetX, p4.y); 126 | 127 | CGPoint _p5 = CGPointMake(p5.x - kPauseLinesHalfSpace, p5.y); 128 | CGPoint _p6 = CGPointMake(p6.x + kPlayTriangleTipOffsetX, p6.y); 129 | CGPoint _p7 = CGPointMake(p7.x + kPlayTriangleTipOffsetX, p7.y); 130 | CGPoint _p8 = CGPointMake(p8.x - kPauseLinesHalfSpace, p8.y); 131 | 132 | const CGFloat kPlayTriangleWidth = _p6.x - _p1.x; 133 | 134 | _p2.y += kPauseLineHalfHeight * (_p2.x - kPlayTriangleOffsetX) / kPlayTriangleWidth; 135 | _p3.y -= kPauseLineHalfHeight * (_p3.x - kPlayTriangleOffsetX) / kPlayTriangleWidth; 136 | 137 | _p5.y += kPauseLineHalfHeight * (_p5.x - kPlayTriangleOffsetX) / kPlayTriangleWidth; 138 | 139 | _p6.y = kPauseLineHalfHeight; 140 | _p7.y = kPauseLineHalfHeight; 141 | 142 | _p8.y -= kPauseLineHalfHeight * (_p8.x - kPlayTriangleOffsetX) / kPlayTriangleWidth; 143 | 144 | [_playBezierPath moveToPoint:_p1]; 145 | [_playBezierPath addLineToPoint:_p2]; 146 | [_playBezierPath addLineToPoint:_p3]; 147 | [_playBezierPath addLineToPoint:_p4]; 148 | [_playBezierPath closePath]; 149 | 150 | [_playBezierPath moveToPoint:_p5]; 151 | [_playBezierPath addLineToPoint:_p6]; 152 | [_playBezierPath addLineToPoint:_p7]; 153 | [_playBezierPath addLineToPoint:_p8]; 154 | [_playBezierPath closePath]; 155 | } 156 | 157 | return _playBezierPath; 158 | } 159 | 160 | 161 | @synthesize playRotateBezierPath = _playRotateBezierPath; 162 | 163 | - (UIBezierPath *)playRotateBezierPath 164 | { 165 | if (!_playRotateBezierPath) { 166 | _playRotateBezierPath = [UIBezierPath bezierPath]; 167 | 168 | const CGFloat kPauseLineHalfHeight = kPauseLineHeight / 2; 169 | 170 | CGPoint _p1, _p2, _p3, _p4, _p5, _p6, _p7, _p8; 171 | _p1 = _p2 = _p5 = _p6 = CGPointMake(p6.x + kPlayTriangleTipOffsetX, kPauseLineHalfHeight); 172 | _p3 = _p8 = CGPointMake(p1.x + kPlayTriangleOffsetX, kPauseLineHalfHeight); 173 | _p4 = CGPointMake(p1.x + kPlayTriangleOffsetX, p1.y); 174 | _p7 = CGPointMake(p4.x + kPlayTriangleOffsetX, p4.y); 175 | 176 | [_playRotateBezierPath moveToPoint:_p1]; 177 | [_playRotateBezierPath addLineToPoint:_p2]; 178 | [_playRotateBezierPath addLineToPoint:_p3]; 179 | [_playRotateBezierPath addLineToPoint:_p4]; 180 | [_playRotateBezierPath closePath]; 181 | 182 | [_playRotateBezierPath moveToPoint:_p5]; 183 | [_playRotateBezierPath addLineToPoint:_p6]; 184 | [_playRotateBezierPath addLineToPoint:_p7]; 185 | [_playRotateBezierPath addLineToPoint:_p8]; 186 | [_playRotateBezierPath closePath]; 187 | } 188 | 189 | return _playRotateBezierPath; 190 | } 191 | 192 | 193 | #pragma mark - Life Cycle 194 | 195 | - (id)initWithFrame:(CGRect)frame 196 | { 197 | self = [super initWithFrame:frame]; 198 | if (self) { 199 | _paused = YES; 200 | 201 | [self sizeToFit]; 202 | } 203 | return self; 204 | } 205 | 206 | 207 | #pragma mark - UIView Method Overrides 208 | #pragma mark Configuring a View's Visual Appearance 209 | 210 | - (void)tintColorDidChange 211 | { 212 | // Refresh view rendering when system calls this method with a changed tint color. 213 | [self setNeedsLayout]; 214 | } 215 | 216 | 217 | #pragma mark Configuring the Resizing Behavior 218 | 219 | - (CGSize)sizeThatFits:(CGSize)size 220 | { 221 | // Ignore the current size/new size by super and instead use our default size. 222 | return CGSizeMake(kSize, kSize); 223 | } 224 | 225 | 226 | #pragma mark Laying out Subviews 227 | 228 | - (void)layoutSubviews 229 | { 230 | [super layoutSubviews]; 231 | 232 | if (!self.borderShapeLayer) { 233 | self.borderShapeLayer = [[CAShapeLayer alloc] init]; 234 | // Adjust for line width. 235 | CGRect borderRect = CGRectInset(self.bounds, ceil(kBorderWidth / 2), ceil(kBorderWidth / 2)); 236 | self.borderShapeLayer.path = [UIBezierPath bezierPathWithOvalInRect:borderRect].CGPath; 237 | self.borderShapeLayer.lineWidth = kBorderWidth; 238 | self.borderShapeLayer.fillColor = [UIColor clearColor].CGColor; 239 | [self.layer addSublayer:self.borderShapeLayer]; 240 | } 241 | self.borderShapeLayer.strokeColor = self.tintColor.CGColor; 242 | 243 | if (!self.playPauseShapeLayer) { 244 | self.playPauseShapeLayer = [[CAShapeLayer alloc] init]; 245 | CGRect playPauseRect = CGRectZero; 246 | playPauseRect.origin.x = ((self.bounds.size.width) - (kPauseLineWidth + kPauseLinesSpace + kPauseLineWidth)) / 2; 247 | playPauseRect.origin.y = ((self.bounds.size.height) - (kPauseLineHeight)) / 2; 248 | playPauseRect.size.width = kPauseLineWidth + kPauseLinesSpace + kPauseLineWidth + kPlayTriangleTipOffsetX; 249 | playPauseRect.size.height = kPauseLineHeight; 250 | self.playPauseShapeLayer.frame = playPauseRect; 251 | UIBezierPath *path = self.isPaused ? self.playRotateBezierPath : self.pauseBezierPath; 252 | self.playPauseShapeLayer.path = path.CGPath; 253 | [self.layer addSublayer:self.playPauseShapeLayer]; 254 | } 255 | self.playPauseShapeLayer.fillColor = self.tintColor.CGColor; 256 | } 257 | 258 | 259 | #pragma mark - Public Methods 260 | 261 | - (void)setPaused:(BOOL)paused animated:(BOOL)animated 262 | { 263 | if (_paused != paused) { 264 | _paused = paused; 265 | 266 | UIBezierPath *fromPath = nil; 267 | UIBezierPath *toPath = nil; 268 | if (self.animationStyle == RSPlayPauseButtonAnimationStyleSplit) { 269 | fromPath = self.isPaused ? self.pauseBezierPath : self.playBezierPath; 270 | toPath = self.isPaused ? self.playBezierPath : self.pauseBezierPath; 271 | } else if (self.animationStyle == RSPlayPauseButtonAnimationStyleSplitAndRotate) { 272 | fromPath = self.isPaused ? self.pauseBezierPath : self.playRotateBezierPath; 273 | toPath = self.isPaused ? self.playRotateBezierPath : self.pauseRotateBezierPath; 274 | } else { 275 | // Unsupported animation style -- fall back to using default animation style's "to path" but don't animate to it. 276 | toPath = self.isPaused ? self.playBezierPath : self.pauseBezierPath; 277 | animated = NO; 278 | } 279 | 280 | NSString * const kMorphAnimationKey = @"morphAnimationKey"; 281 | if (animated) { 282 | // Morph between the two states. 283 | CABasicAnimation *morphAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; 284 | 285 | CAMediaTimingFunction *timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 286 | [morphAnimation setTimingFunction:timingFunction]; 287 | 288 | // Make the new state stick. 289 | [morphAnimation setRemovedOnCompletion:NO]; 290 | [morphAnimation setFillMode:kCAFillModeForwards]; 291 | 292 | morphAnimation.duration = 0.3; 293 | morphAnimation.fromValue = (__bridge id)fromPath.CGPath; 294 | morphAnimation.toValue = (__bridge id)toPath.CGPath; 295 | 296 | [self.playPauseShapeLayer addAnimation:morphAnimation forKey:kMorphAnimationKey]; 297 | } else { 298 | // Clear out potential existing morph animations. 299 | [self.playPauseShapeLayer removeAnimationForKey:kMorphAnimationKey]; 300 | 301 | // Snap to new state. 302 | self.playPauseShapeLayer.path = toPath.CGPath; 303 | } 304 | } 305 | } 306 | 307 | 308 | @end 309 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // RSPlayPauseButtonDemo 4 | // 5 | // Created by Raphael Schaad on 11/13/14. 6 | // 7 | // 8 | 9 | 10 | #import 11 | 12 | 13 | @interface ViewController : UIViewController 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // RSPlayPauseButtonDemo 4 | // 5 | // Created by Raphael Schaad on 11/13/14. 6 | // 7 | // 8 | 9 | 10 | #import "ViewController.h" 11 | #import "RSPlayPauseButton.h" 12 | 13 | 14 | @interface ViewController () 15 | 16 | @property (nonatomic, strong) RSPlayPauseButton *playPauseButton; 17 | @property (nonatomic, strong) UILabel *label; 18 | @property (nonatomic, strong) UISegmentedControl *segmentedControl; 19 | 20 | @end 21 | 22 | 23 | @implementation ViewController 24 | 25 | #pragma mark - Life Cycle 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | 31 | if (!self.playPauseButton) { 32 | self.playPauseButton = [[RSPlayPauseButton alloc] init]; 33 | self.playPauseButton.tintColor = [UIColor colorWithRed:0.0 green:105.0/255.0 blue:92.0/255.0 alpha:1.0]; 34 | [self.playPauseButton addTarget:self action:@selector(playPauseButtonDidPress:) forControlEvents:UIControlEventTouchUpInside]; 35 | } 36 | [self.view addSubview:self.playPauseButton]; 37 | 38 | if (!self.label) { 39 | self.label = [[UILabel alloc] init]; 40 | self.label.font = [UIFont boldSystemFontOfSize:11.0]; 41 | self.label.textColor = [UIColor colorWithWhite:0.0 alpha:0.4]; 42 | self.label.text = @"ANIMATION STYLE"; 43 | } 44 | [self.view addSubview:self.label]; 45 | 46 | if (!self.segmentedControl) { 47 | self.segmentedControl = [[UISegmentedControl alloc] initWithItems:@[@"None", @"Split", @"Split & Rotate"]]; 48 | self.segmentedControl.selectedSegmentIndex = 0; 49 | [self.segmentedControl addTarget:self action:@selector(segmentedControlDidChangeValue:) forControlEvents:UIControlEventValueChanged]; 50 | } 51 | [self.view addSubview:self.segmentedControl]; 52 | } 53 | 54 | 55 | - (void)viewDidLayoutSubviews 56 | { 57 | [self.playPauseButton sizeToFit]; 58 | self.playPauseButton.center = self.view.center; 59 | 60 | [self.label sizeToFit]; 61 | CGRect labelFrame = self.label.frame; 62 | labelFrame.origin.x = floor((self.view.bounds.size.width - labelFrame.size.width) / 2); 63 | labelFrame.origin.y = self.view.bounds.origin.y + 20.0; 64 | self.label.frame = labelFrame; 65 | 66 | [self.segmentedControl sizeToFit]; 67 | CGRect segmentedControlFrame = self.segmentedControl.frame; 68 | segmentedControlFrame.origin.x = floor((self.view.bounds.size.width - segmentedControlFrame.size.width) / 2); 69 | segmentedControlFrame.origin.y = CGRectGetMaxY(self.label.frame) + 8.0; 70 | self.segmentedControl.frame = segmentedControlFrame; 71 | } 72 | 73 | 74 | #pragma mark - Actions 75 | 76 | - (void)playPauseButtonDidPress:(id)sender 77 | { 78 | if ([self.playPauseButton isEqual:sender]) { 79 | BOOL animated = self.segmentedControl.selectedSegmentIndex != 0; 80 | [self.playPauseButton setPaused:!self.playPauseButton.isPaused animated:animated]; 81 | } 82 | } 83 | 84 | 85 | - (void)segmentedControlDidChangeValue:(id)sender 86 | { 87 | if ([self.segmentedControl isEqual:sender]) { 88 | if (self.segmentedControl.selectedSegmentIndex == 1) { 89 | self.playPauseButton.animationStyle = RSPlayPauseButtonAnimationStyleSplit; 90 | } else if (self.segmentedControl.selectedSegmentIndex == 2) { 91 | self.playPauseButton.animationStyle = RSPlayPauseButtonAnimationStyleSplitAndRotate; 92 | } 93 | } 94 | } 95 | 96 | 97 | #pragma mark - Status Bar 98 | 99 | - (BOOL)prefersStatusBarHidden 100 | { 101 | return YES; 102 | } 103 | 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // RSPlayPauseButtonDemo 4 | // 5 | // Created by Raphael Schaad on 11/13/14. 6 | // 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /RSPlayPauseButtonDemoTests/RSPlayPauseButtonDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RSPlayPauseButtonDemoTests.m 3 | // RSPlayPauseButtonDemoTests 4 | // 5 | // Created by Raphael Schaad on 11/13/14. 6 | // 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface RSPlayPauseButtonDemoTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation RSPlayPauseButtonDemoTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------