├── README.md ├── ShootingStar.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── garchinskyr1.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── ShootingStar.xcscheme │ └── xcschememanagement.plist └── ShootingStar ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── asterisk_filled.imageset │ ├── Contents.json │ └── asterisk_filled.png └── filled_star.imageset │ ├── Contents.json │ └── filled_star.png ├── AsteriskParticle.sks ├── Base.lproj └── LaunchScreen.storyboard ├── Info.plist ├── StarParticle.sks ├── ViewController.h ├── ViewController.m └── main.m /README.md: -------------------------------------------------------------------------------- 1 | # ShootingStars 2 | Some fun Shooting Star animations built with both SpriteKit and CAEmitterLayer. Run the example project to see both in action. 3 | 4 | There are 2 ways to achieve a shooting star animation, which travels along a defined path while emitting particles. There are both some advantages, and disadvantages for each method. 5 | 6 | ## Sprite Kit Example 7 | ![sprite-kit-star](http://i.imgur.com/AKS3AuT.gif) 8 | ### Advantages 9 | - Particle emitters are much more flexible. You can attach a `targetNode` to your sprite's emitter, which allows your particles to become independent of the sprite, thus not moving along the same path as the sprite. This is not as easily achievable using `CAEmitterLayer` 10 | - Sprite Particles (`.sks` files in Xcode) come with a very nice preview playground, where you can tweak values of the particles and see the changes real-time, and not have to keep restarting your app to see the changes. 11 | - End result seems much smoother 12 | 13 | ### Disadvantages 14 | - You must add a `SKView` and associated `SKScene` on top of your view. `SKScene`'s have very different coordinate systems than regular `UIView`s. [Read more from Apple here](https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Nodes/Nodes.html). This means you may carefully translate coordinates from a `UIView` to a `SKScene` 15 | - Performance wise, using Sprite Kit seems to lag much easier (at least with this example). For example, in the sample, try to shoot off 5 SpriteKit stars, and then shoot off 5 Core Animation stars. You'll notice a lot of lag on the SpriteKit star, but not much on the CoreAnimation star 16 | 17 | ## Core Animation Example 18 | ![sprite-kit-star](http://i.imgur.com/FIXKVqG.gif) 19 | ### Advantages 20 | - You don't need to worry about thinking about converting coordinates to a points in a `SKScene`, or worry about adding a `SKView` on top of your current `UIView` 21 | - Performance (at least for this example) seems much better. You can shoot off many CA stars with little to no lag 22 | 23 | ### Disadvantages 24 | - Emitters do not have a nice playground where you can easily tweak settings and see the result real time. You must either keep restarting your app, or purchase an app that allows you to tweak settings and see the result real time. 25 | - Emitter particles, when attached to a `UIView`, follow the same path the `UIView` does, resulting in some ugly animations of your `UIView` does lots of turns with particles. 26 | 27 | ## TODO: 28 | - Swift Example 29 | 30 | -------------------------------------------------------------------------------- /ShootingStar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 301D6B481C28B0C200B67764 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 301D6B471C28B0C200B67764 /* main.m */; }; 11 | 301D6B4B1C28B0C200B67764 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 301D6B4A1C28B0C200B67764 /* AppDelegate.m */; }; 12 | 301D6B4E1C28B0C200B67764 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 301D6B4D1C28B0C200B67764 /* ViewController.m */; }; 13 | 301D6B531C28B0C200B67764 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 301D6B521C28B0C200B67764 /* Assets.xcassets */; }; 14 | 301D6B561C28B0C200B67764 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 301D6B541C28B0C200B67764 /* LaunchScreen.storyboard */; }; 15 | 301D6B5F1C28FF5200B67764 /* AsteriskParticle.sks in Resources */ = {isa = PBXBuildFile; fileRef = 301D6B5D1C28FF5200B67764 /* AsteriskParticle.sks */; }; 16 | 301D6B601C28FF5200B67764 /* StarParticle.sks in Resources */ = {isa = PBXBuildFile; fileRef = 301D6B5E1C28FF5200B67764 /* StarParticle.sks */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 301D6B431C28B0C200B67764 /* ShootingStar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ShootingStar.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 301D6B471C28B0C200B67764 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 22 | 301D6B491C28B0C200B67764 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 23 | 301D6B4A1C28B0C200B67764 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 24 | 301D6B4C1C28B0C200B67764 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 25 | 301D6B4D1C28B0C200B67764 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 26 | 301D6B521C28B0C200B67764 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | 301D6B551C28B0C200B67764 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 301D6B571C28B0C200B67764 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 301D6B5D1C28FF5200B67764 /* AsteriskParticle.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = AsteriskParticle.sks; sourceTree = ""; }; 30 | 301D6B5E1C28FF5200B67764 /* StarParticle.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = StarParticle.sks; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 301D6B401C28B0C200B67764 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 301D6B3A1C28B0C200B67764 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 301D6B451C28B0C200B67764 /* ShootingStar */, 48 | 301D6B441C28B0C200B67764 /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 301D6B441C28B0C200B67764 /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 301D6B431C28B0C200B67764 /* ShootingStar.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 301D6B451C28B0C200B67764 /* ShootingStar */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 301D6B5D1C28FF5200B67764 /* AsteriskParticle.sks */, 64 | 301D6B5E1C28FF5200B67764 /* StarParticle.sks */, 65 | 301D6B491C28B0C200B67764 /* AppDelegate.h */, 66 | 301D6B4A1C28B0C200B67764 /* AppDelegate.m */, 67 | 301D6B4C1C28B0C200B67764 /* ViewController.h */, 68 | 301D6B4D1C28B0C200B67764 /* ViewController.m */, 69 | 301D6B521C28B0C200B67764 /* Assets.xcassets */, 70 | 301D6B541C28B0C200B67764 /* LaunchScreen.storyboard */, 71 | 301D6B571C28B0C200B67764 /* Info.plist */, 72 | 301D6B461C28B0C200B67764 /* Supporting Files */, 73 | ); 74 | path = ShootingStar; 75 | sourceTree = ""; 76 | }; 77 | 301D6B461C28B0C200B67764 /* Supporting Files */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 301D6B471C28B0C200B67764 /* main.m */, 81 | ); 82 | name = "Supporting Files"; 83 | sourceTree = ""; 84 | }; 85 | /* End PBXGroup section */ 86 | 87 | /* Begin PBXNativeTarget section */ 88 | 301D6B421C28B0C200B67764 /* ShootingStar */ = { 89 | isa = PBXNativeTarget; 90 | buildConfigurationList = 301D6B5A1C28B0C200B67764 /* Build configuration list for PBXNativeTarget "ShootingStar" */; 91 | buildPhases = ( 92 | 301D6B3F1C28B0C200B67764 /* Sources */, 93 | 301D6B401C28B0C200B67764 /* Frameworks */, 94 | 301D6B411C28B0C200B67764 /* Resources */, 95 | ); 96 | buildRules = ( 97 | ); 98 | dependencies = ( 99 | ); 100 | name = ShootingStar; 101 | productName = ShootingStar; 102 | productReference = 301D6B431C28B0C200B67764 /* ShootingStar.app */; 103 | productType = "com.apple.product-type.application"; 104 | }; 105 | /* End PBXNativeTarget section */ 106 | 107 | /* Begin PBXProject section */ 108 | 301D6B3B1C28B0C200B67764 /* Project object */ = { 109 | isa = PBXProject; 110 | attributes = { 111 | LastUpgradeCheck = 0720; 112 | ORGANIZATIONNAME = garapps; 113 | TargetAttributes = { 114 | 301D6B421C28B0C200B67764 = { 115 | CreatedOnToolsVersion = 7.2; 116 | }; 117 | }; 118 | }; 119 | buildConfigurationList = 301D6B3E1C28B0C200B67764 /* Build configuration list for PBXProject "ShootingStar" */; 120 | compatibilityVersion = "Xcode 3.2"; 121 | developmentRegion = English; 122 | hasScannedForEncodings = 0; 123 | knownRegions = ( 124 | en, 125 | Base, 126 | ); 127 | mainGroup = 301D6B3A1C28B0C200B67764; 128 | productRefGroup = 301D6B441C28B0C200B67764 /* Products */; 129 | projectDirPath = ""; 130 | projectRoot = ""; 131 | targets = ( 132 | 301D6B421C28B0C200B67764 /* ShootingStar */, 133 | ); 134 | }; 135 | /* End PBXProject section */ 136 | 137 | /* Begin PBXResourcesBuildPhase section */ 138 | 301D6B411C28B0C200B67764 /* Resources */ = { 139 | isa = PBXResourcesBuildPhase; 140 | buildActionMask = 2147483647; 141 | files = ( 142 | 301D6B561C28B0C200B67764 /* LaunchScreen.storyboard in Resources */, 143 | 301D6B601C28FF5200B67764 /* StarParticle.sks in Resources */, 144 | 301D6B5F1C28FF5200B67764 /* AsteriskParticle.sks in Resources */, 145 | 301D6B531C28B0C200B67764 /* Assets.xcassets in Resources */, 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | /* End PBXResourcesBuildPhase section */ 150 | 151 | /* Begin PBXSourcesBuildPhase section */ 152 | 301D6B3F1C28B0C200B67764 /* Sources */ = { 153 | isa = PBXSourcesBuildPhase; 154 | buildActionMask = 2147483647; 155 | files = ( 156 | 301D6B4E1C28B0C200B67764 /* ViewController.m in Sources */, 157 | 301D6B4B1C28B0C200B67764 /* AppDelegate.m in Sources */, 158 | 301D6B481C28B0C200B67764 /* main.m in Sources */, 159 | ); 160 | runOnlyForDeploymentPostprocessing = 0; 161 | }; 162 | /* End PBXSourcesBuildPhase section */ 163 | 164 | /* Begin PBXVariantGroup section */ 165 | 301D6B541C28B0C200B67764 /* LaunchScreen.storyboard */ = { 166 | isa = PBXVariantGroup; 167 | children = ( 168 | 301D6B551C28B0C200B67764 /* Base */, 169 | ); 170 | name = LaunchScreen.storyboard; 171 | sourceTree = ""; 172 | }; 173 | /* End PBXVariantGroup section */ 174 | 175 | /* Begin XCBuildConfiguration section */ 176 | 301D6B581C28B0C200B67764 /* Debug */ = { 177 | isa = XCBuildConfiguration; 178 | buildSettings = { 179 | ALWAYS_SEARCH_USER_PATHS = NO; 180 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 181 | CLANG_CXX_LIBRARY = "libc++"; 182 | CLANG_ENABLE_MODULES = YES; 183 | CLANG_ENABLE_OBJC_ARC = YES; 184 | CLANG_WARN_BOOL_CONVERSION = YES; 185 | CLANG_WARN_CONSTANT_CONVERSION = YES; 186 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 187 | CLANG_WARN_EMPTY_BODY = YES; 188 | CLANG_WARN_ENUM_CONVERSION = YES; 189 | CLANG_WARN_INT_CONVERSION = YES; 190 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 191 | CLANG_WARN_UNREACHABLE_CODE = YES; 192 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 193 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 194 | COPY_PHASE_STRIP = NO; 195 | DEBUG_INFORMATION_FORMAT = dwarf; 196 | ENABLE_STRICT_OBJC_MSGSEND = YES; 197 | ENABLE_TESTABILITY = YES; 198 | GCC_C_LANGUAGE_STANDARD = gnu99; 199 | GCC_DYNAMIC_NO_PIC = NO; 200 | GCC_NO_COMMON_BLOCKS = YES; 201 | GCC_OPTIMIZATION_LEVEL = 0; 202 | GCC_PREPROCESSOR_DEFINITIONS = ( 203 | "DEBUG=1", 204 | "$(inherited)", 205 | ); 206 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 207 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 208 | GCC_WARN_UNDECLARED_SELECTOR = YES; 209 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 210 | GCC_WARN_UNUSED_FUNCTION = YES; 211 | GCC_WARN_UNUSED_VARIABLE = YES; 212 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 213 | MTL_ENABLE_DEBUG_INFO = YES; 214 | ONLY_ACTIVE_ARCH = YES; 215 | SDKROOT = iphoneos; 216 | }; 217 | name = Debug; 218 | }; 219 | 301D6B591C28B0C200B67764 /* Release */ = { 220 | isa = XCBuildConfiguration; 221 | buildSettings = { 222 | ALWAYS_SEARCH_USER_PATHS = NO; 223 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 224 | CLANG_CXX_LIBRARY = "libc++"; 225 | CLANG_ENABLE_MODULES = YES; 226 | CLANG_ENABLE_OBJC_ARC = YES; 227 | CLANG_WARN_BOOL_CONVERSION = YES; 228 | CLANG_WARN_CONSTANT_CONVERSION = YES; 229 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 230 | CLANG_WARN_EMPTY_BODY = YES; 231 | CLANG_WARN_ENUM_CONVERSION = YES; 232 | CLANG_WARN_INT_CONVERSION = YES; 233 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 234 | CLANG_WARN_UNREACHABLE_CODE = YES; 235 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 236 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 237 | COPY_PHASE_STRIP = NO; 238 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 239 | ENABLE_NS_ASSERTIONS = NO; 240 | ENABLE_STRICT_OBJC_MSGSEND = YES; 241 | GCC_C_LANGUAGE_STANDARD = gnu99; 242 | GCC_NO_COMMON_BLOCKS = YES; 243 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 244 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 245 | GCC_WARN_UNDECLARED_SELECTOR = YES; 246 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 247 | GCC_WARN_UNUSED_FUNCTION = YES; 248 | GCC_WARN_UNUSED_VARIABLE = YES; 249 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 250 | MTL_ENABLE_DEBUG_INFO = NO; 251 | SDKROOT = iphoneos; 252 | VALIDATE_PRODUCT = YES; 253 | }; 254 | name = Release; 255 | }; 256 | 301D6B5B1C28B0C200B67764 /* Debug */ = { 257 | isa = XCBuildConfiguration; 258 | buildSettings = { 259 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 260 | INFOPLIST_FILE = ShootingStar/Info.plist; 261 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 262 | PRODUCT_BUNDLE_IDENTIFIER = garapps.ShootingStar; 263 | PRODUCT_NAME = "$(TARGET_NAME)"; 264 | }; 265 | name = Debug; 266 | }; 267 | 301D6B5C1C28B0C200B67764 /* Release */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 271 | INFOPLIST_FILE = ShootingStar/Info.plist; 272 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 273 | PRODUCT_BUNDLE_IDENTIFIER = garapps.ShootingStar; 274 | PRODUCT_NAME = "$(TARGET_NAME)"; 275 | }; 276 | name = Release; 277 | }; 278 | /* End XCBuildConfiguration section */ 279 | 280 | /* Begin XCConfigurationList section */ 281 | 301D6B3E1C28B0C200B67764 /* Build configuration list for PBXProject "ShootingStar" */ = { 282 | isa = XCConfigurationList; 283 | buildConfigurations = ( 284 | 301D6B581C28B0C200B67764 /* Debug */, 285 | 301D6B591C28B0C200B67764 /* Release */, 286 | ); 287 | defaultConfigurationIsVisible = 0; 288 | defaultConfigurationName = Release; 289 | }; 290 | 301D6B5A1C28B0C200B67764 /* Build configuration list for PBXNativeTarget "ShootingStar" */ = { 291 | isa = XCConfigurationList; 292 | buildConfigurations = ( 293 | 301D6B5B1C28B0C200B67764 /* Debug */, 294 | 301D6B5C1C28B0C200B67764 /* Release */, 295 | ); 296 | defaultConfigurationIsVisible = 0; 297 | defaultConfigurationName = Release; 298 | }; 299 | /* End XCConfigurationList section */ 300 | }; 301 | rootObject = 301D6B3B1C28B0C200B67764 /* Project object */; 302 | } 303 | -------------------------------------------------------------------------------- /ShootingStar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ShootingStar.xcodeproj/xcuserdata/garchinskyr1.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /ShootingStar.xcodeproj/xcuserdata/garchinskyr1.xcuserdatad/xcschemes/ShootingStar.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ShootingStar.xcodeproj/xcuserdata/garchinskyr1.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ShootingStar.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 301D6B421C28B0C200B67764 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ShootingStar/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ShootingStar 4 | // 5 | // Created by Ryan Garchinsky on 12/21/15. 6 | // Copyright © 2015 garapps. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /ShootingStar/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ShootingStar 4 | // 5 | // Created by Ryan Garchinsky on 12/21/15. 6 | // Copyright © 2015 garapps. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | 21 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 22 | self.window.rootViewController = [[ViewController alloc] init]; 23 | [self.window makeKeyAndVisible]; 24 | 25 | return YES; 26 | } 27 | 28 | - (void)applicationWillResignActive:(UIApplication *)application { 29 | // 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. 30 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 31 | } 32 | 33 | - (void)applicationDidEnterBackground:(UIApplication *)application { 34 | // 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. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | } 37 | 38 | - (void)applicationWillEnterForeground:(UIApplication *)application { 39 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 40 | } 41 | 42 | - (void)applicationDidBecomeActive:(UIApplication *)application { 43 | // 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. 44 | } 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /ShootingStar/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /ShootingStar/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ShootingStar/Assets.xcassets/asterisk_filled.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "asterisk_filled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ShootingStar/Assets.xcassets/asterisk_filled.imageset/asterisk_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryang1428/ShootingStars/431e91e4f324f434015d4eecb97802624224c74c/ShootingStar/Assets.xcassets/asterisk_filled.imageset/asterisk_filled.png -------------------------------------------------------------------------------- /ShootingStar/Assets.xcassets/filled_star.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "filled_star.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ShootingStar/Assets.xcassets/filled_star.imageset/filled_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryang1428/ShootingStars/431e91e4f324f434015d4eecb97802624224c74c/ShootingStar/Assets.xcassets/filled_star.imageset/filled_star.png -------------------------------------------------------------------------------- /ShootingStar/AsteriskParticle.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryang1428/ShootingStars/431e91e4f324f434015d4eecb97802624224c74c/ShootingStar/AsteriskParticle.sks -------------------------------------------------------------------------------- /ShootingStar/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ShootingStar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /ShootingStar/StarParticle.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryang1428/ShootingStars/431e91e4f324f434015d4eecb97802624224c74c/ShootingStar/StarParticle.sks -------------------------------------------------------------------------------- /ShootingStar/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // ShootingStar 4 | // 5 | // Created by Ryan Garchinsky on 12/21/15. 6 | // Copyright © 2015 garapps. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /ShootingStar/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // ShootingStar 4 | // 5 | // Created by Ryan Garchinsky on 12/21/15. 6 | // Copyright © 2015 garapps. All rights reserved. 7 | // 8 | 9 | #define SCREEN_SCALE [[UIScreen mainScreen] scale] 10 | #define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI) 11 | 12 | #import "ViewController.h" 13 | #import 14 | 15 | @interface ViewController () 16 | 17 | // Sprite Kit 18 | @property (nonatomic, strong) UIButton *fireSpriteKitButton; 19 | @property (nonatomic, strong) UIButton *fireCoreAnimationButton; 20 | 21 | //Core Animation 22 | @property (nonatomic, strong) UIImageView *caStarImageView; 23 | @property (nonatomic, strong) CAEmitterLayer *caStarEmitter; 24 | 25 | @end 26 | 27 | @implementation ViewController 28 | 29 | - (instancetype)init { 30 | self = [super init]; 31 | 32 | if (self) { 33 | self.view.backgroundColor = [UIColor colorWithRed:0.57 green:0.66 blue:0.74 alpha:1]; 34 | 35 | self.fireSpriteKitButton = [UIButton buttonWithType:UIButtonTypeCustom]; 36 | self.fireSpriteKitButton.translatesAutoresizingMaskIntoConstraints = NO; 37 | [self.fireSpriteKitButton setTitle:@"Sprite Kit" forState:UIControlStateNormal]; 38 | [self.fireSpriteKitButton setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted]; 39 | [self.fireSpriteKitButton setBackgroundColor:[[UIColor whiteColor] colorWithAlphaComponent:0.3f]]; 40 | [self.fireSpriteKitButton setTintColor:[UIColor grayColor]]; 41 | [self.fireSpriteKitButton addTarget:self action:@selector(shootOffSpriteKitStarFromView:) forControlEvents:UIControlEventTouchUpInside]; 42 | 43 | self.fireCoreAnimationButton = [UIButton buttonWithType:UIButtonTypeCustom]; 44 | self.fireCoreAnimationButton.translatesAutoresizingMaskIntoConstraints = NO; 45 | [self.fireCoreAnimationButton setTitle:@"Core Animation" forState:UIControlStateNormal]; 46 | [self.fireCoreAnimationButton setTitleColor:[UIColor grayColor] forState:UIControlStateHighlighted]; 47 | [self.fireCoreAnimationButton setBackgroundColor:[[UIColor redColor] colorWithAlphaComponent:0.3f]]; 48 | [self.fireCoreAnimationButton setTintColor:[UIColor grayColor]]; 49 | [self.fireCoreAnimationButton addTarget:self action:@selector(shootOffCoreAnimationStarFromView:) forControlEvents:UIControlEventTouchUpInside]; 50 | 51 | [self.view addSubview:self.fireSpriteKitButton]; 52 | [self.view addSubview:self.fireCoreAnimationButton]; 53 | 54 | NSDictionary *views = NSDictionaryOfVariableBindings(_fireSpriteKitButton, _fireCoreAnimationButton); 55 | 56 | [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_fireSpriteKitButton(100)]|" options:0 metrics:nil views:views]]; 57 | 58 | [NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_fireSpriteKitButton][_fireCoreAnimationButton]|" options:NSLayoutFormatAlignAllBottom|NSLayoutFormatAlignAllTop metrics:nil views:views]]; 59 | 60 | [NSLayoutConstraint activateConstraints:@[[NSLayoutConstraint constraintWithItem:self.fireCoreAnimationButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.fireSpriteKitButton attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]]; 61 | } 62 | 63 | return self; 64 | } 65 | 66 | // Sprite Kit Example 67 | - (void)shootOffSpriteKitStarFromView:(UIView *)view { 68 | CGPoint viewOrigin; 69 | 70 | viewOrigin.y = 0; 71 | viewOrigin.x = (view.frame.origin.x + (view.frame.size.width / 2)) / SCREEN_SCALE; 72 | 73 | UIView *containerView = [[UIView alloc] initWithFrame:self.view.bounds]; 74 | containerView.userInteractionEnabled = NO; 75 | 76 | SKView *skView = [[SKView alloc] initWithFrame:containerView.frame]; 77 | skView.allowsTransparency = YES; 78 | [containerView addSubview:skView]; 79 | 80 | SKScene *skScene = [SKScene sceneWithSize:skView.frame.size]; 81 | skScene.scaleMode = SKSceneScaleModeFill; 82 | skScene.backgroundColor = [UIColor clearColor]; 83 | 84 | SKSpriteNode *starSprite = [SKSpriteNode spriteNodeWithImageNamed:@"filled_star"]; 85 | [starSprite setScale:0.6]; 86 | starSprite.position = viewOrigin; 87 | [skScene addChild:starSprite]; 88 | 89 | SKEmitterNode *emitter = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:@"StarParticle" ofType:@"sks"]]; 90 | SKEmitterNode *dotEmitter = [NSKeyedUnarchiver unarchiveObjectWithFile:[[NSBundle mainBundle] pathForResource:@"AsteriskParticle" ofType:@"sks"]]; 91 | 92 | [dotEmitter setParticlePosition:CGPointMake(0, -starSprite.size.height)]; 93 | [emitter setParticlePosition:CGPointMake(0, -starSprite.size.height)]; 94 | 95 | emitter.targetNode = skScene; 96 | dotEmitter.targetNode = skScene; 97 | 98 | [starSprite addChild:emitter]; 99 | [starSprite addChild:dotEmitter]; 100 | 101 | [skView presentScene:skScene]; 102 | 103 | CGMutablePathRef path = CGPathCreateMutable(); 104 | CGPathMoveToPoint(path, NULL, viewOrigin.x, viewOrigin.y); 105 | 106 | CGPoint endPoint = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height + 100); 107 | UIBezierPath *bp = [UIBezierPath new]; 108 | [bp moveToPoint:viewOrigin]; 109 | 110 | // curvy path 111 | // control points "pull" the curve to that point on the screen. You should be smarter then just using magic numbers like below. 112 | [bp addCurveToPoint:endPoint controlPoint1:CGPointMake(viewOrigin.x + 500, viewOrigin.y + 275) controlPoint2:CGPointMake(-200, skView.frame.size.height - 250)]; 113 | 114 | __weak typeof(containerView) weakView = containerView; 115 | SKAction *followline = [SKAction followPath:bp.CGPath asOffset:YES orientToPath:YES duration:3.0]; 116 | 117 | SKAction *done = [SKAction runBlock:^{ 118 | // lets delay until all particles are removed 119 | int64_t delayInSeconds = 2.2; 120 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 121 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 122 | [weakView removeFromSuperview]; 123 | }); 124 | }]; 125 | 126 | [starSprite runAction:[SKAction sequence:@[followline, done]]]; 127 | 128 | [self.view addSubview:containerView]; 129 | } 130 | 131 | 132 | 133 | // Core Animation Example 134 | - (void)shootOffCoreAnimationStarFromView:(UIView *)view { 135 | self.caStarImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"filled_star"]]; 136 | self.caStarImageView.alpha = 1.0f; 137 | CGRect imageFrame = CGRectMake(self.caStarImageView.frame.origin.x, self.caStarImageView.frame.origin.y, 50, 50); 138 | 139 | //Your image frame.origin from where the animation need to get start 140 | CGPoint viewOrigin = self.caStarImageView.frame.origin; 141 | 142 | viewOrigin.y = self.view.frame.size.height; 143 | viewOrigin.x = (view.frame.origin.x + (view.frame.size.width / 2)); 144 | 145 | self.caStarImageView.frame = imageFrame; 146 | self.caStarImageView.layer.position = viewOrigin; 147 | [self.view addSubview:self.caStarImageView]; 148 | 149 | // need to rotate the image to get it on the right tangent 150 | self.caStarImageView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(90)); 151 | 152 | // particles emitters 153 | self.caStarEmitter = [CAEmitterLayer new]; 154 | self.caStarEmitter.emitterPosition = CGPointMake((self.caStarImageView.frame.size.width / 2) - 5, self.caStarImageView.frame.size.height); 155 | self.caStarEmitter.emitterShape = kCAEmitterLayerLine; 156 | self.caStarEmitter.emitterZPosition = 10; // 3; 157 | self.caStarEmitter.emitterSize = CGSizeMake(0.5, 0.5); 158 | 159 | CAEmitterCell *star = [self makeEmitterCellWithShape:0]; 160 | CAEmitterCell *star2 = [self makeEmitterCellWithShape:0]; 161 | CAEmitterCell *star3 = [self makeEmitterCellWithShape:0]; 162 | 163 | CAEmitterCell *circle = [self makeEmitterCellWithShape:1]; 164 | CAEmitterCell *circle2 = [self makeEmitterCellWithShape:1]; 165 | CAEmitterCell *circle3 = [self makeEmitterCellWithShape:1]; 166 | 167 | self.caStarEmitter.emitterCells = @[star, star2, star3, circle, circle2, circle3]; 168 | [self.caStarImageView.layer addSublayer:self.caStarEmitter]; 169 | 170 | // Set up fade out effect 171 | CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 172 | [fadeOutAnimation setToValue:[NSNumber numberWithFloat:1.0]]; 173 | fadeOutAnimation.fillMode = kCAFillModeForwards; 174 | fadeOutAnimation.removedOnCompletion = NO; 175 | 176 | // Set up path movement 177 | CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 178 | pathAnimation.calculationMode = kCAAnimationCubicPaced; 179 | pathAnimation.fillMode = kCAFillModeForwards; 180 | pathAnimation.removedOnCompletion = NO; 181 | pathAnimation.rotationMode = kCAAnimationRotateAuto; 182 | 183 | //Setting Endpoint of the animation 184 | CGPoint endPoint = CGPointMake(self.view.frame.size.width, -100); 185 | UIBezierPath *bp = [UIBezierPath new]; 186 | [bp moveToPoint:viewOrigin]; 187 | 188 | // control points "pull" the curve to that point on the screen. You should be smarter then just using magic numbers like below. 189 | [bp addCurveToPoint:endPoint controlPoint1:CGPointMake(endPoint.x - 400, endPoint.y + 200) controlPoint2:endPoint]; 190 | 191 | pathAnimation.path = bp.CGPath; 192 | 193 | CAAnimationGroup *group = [CAAnimationGroup animation]; 194 | group.fillMode = kCAFillModeForwards; 195 | group.removedOnCompletion = NO; 196 | [group setAnimations:[NSArray arrayWithObjects:fadeOutAnimation, pathAnimation, nil]]; 197 | group.duration = 2.3f; 198 | group.delegate = self; 199 | [group setValue:self.caStarImageView forKey:@"imageViewBeingAnimated"]; 200 | 201 | [self.caStarImageView.layer addAnimation:group forKey:@"savingAnimation"]; 202 | } 203 | 204 | - (CAEmitterCell *)makeEmitterCellWithShape:(NSInteger)shape { 205 | CAEmitterCell *cell = [CAEmitterCell new]; 206 | cell.velocity = [self randFloatBetween:125 and:200]; 207 | cell.velocityRange = [self randFloatBetween:15 and:40]; 208 | cell.emissionLongitude = M_PI; 209 | cell.emissionRange = M_PI_4; 210 | cell.spin = 2; 211 | cell.spinRange = 10; 212 | cell.scale = 0.2; 213 | cell.scaleSpeed = -0.05; 214 | 215 | cell.lifetime = [self randFloatBetween:0.3 and:0.75]; 216 | cell.lifetimeRange = [self randFloatBetween:0.6 and:1.5]; 217 | 218 | if (shape == 0) { 219 | cell.color = [UIColor yellowColor].CGColor; 220 | cell.contents = (id)[[UIImage imageNamed:@"filled_star"] CGImage]; 221 | cell.birthRate = 15; 222 | } 223 | else { 224 | cell.color = [UIColor orangeColor].CGColor; 225 | cell.contents = (id)[[UIImage imageNamed:@"asterisk_filled"] CGImage]; 226 | cell.birthRate = 40; 227 | } 228 | 229 | return cell; 230 | } 231 | 232 | - (CGFloat)randFloatBetween:(float)low and:(float)high { 233 | float diff = high - low; 234 | return (((float) rand() / RAND_MAX) * diff) + low; 235 | } 236 | 237 | #pragma mark - CAAnimationGroup Delegate 238 | - (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished { 239 | if (finished) { 240 | // stop emitting particles, and wait a couple seconds so they all have time to disappear 241 | self.caStarEmitter.birthRate = 0; 242 | 243 | __weak typeof(self) weakSelf = self; 244 | int64_t delayInSeconds = 1.5; 245 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 246 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 247 | [weakSelf.caStarImageView removeFromSuperview]; 248 | }); 249 | } 250 | } 251 | 252 | @end 253 | -------------------------------------------------------------------------------- /ShootingStar/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ShootingStar 4 | // 5 | // Created by Ryan Garchinsky on 12/21/15. 6 | // Copyright © 2015 garapps. All rights reserved. 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 | --------------------------------------------------------------------------------