├── .github └── FUNDING.yml ├── .gitignore ├── Example ├── PulsatorDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── PulsatorDemo.xcscheme └── PulsatorDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── Image.imageset │ │ ├── Contents.json │ │ └── IPhone_5s.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── MapViewController.swift │ └── ViewController.swift ├── LICENSE ├── Package.swift ├── Pulsator-macOS ├── Info.plist └── Pulsator_macOS.h ├── Pulsator.playground ├── Pages │ ├── Customizations.xcplaygroundpage │ │ └── Contents.swift │ └── MapAnnotation.xcplaygroundpage │ │ └── Contents.swift ├── Resources │ └── IPhone_5s.png └── contents.xcplayground ├── Pulsator.podspec ├── Pulsator.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── Pulsator-macOS.xcscheme │ └── Pulsator.xcscheme ├── Pulsator.xcworkspace └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Pulsator ├── Info.plist ├── Pulsator.h └── Pulsator.swift ├── PulsatorMac ├── Info.plist └── PulsatorMac.h ├── PulsatorTests ├── Info.plist └── PulsatorTests.swift ├── README.md └── demo.gif /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [shu223] 4 | custom: ['https://paypal.me/shu223', 'https://note.com/shu223/m/me1aa6761ab16'] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | contents.xcworkspacedata 65 | -------------------------------------------------------------------------------- /Example/PulsatorDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8A77DC121CBC467000087690 /* Pulsator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A77DC111CBC467000087690 /* Pulsator.swift */; }; 11 | 8AD3F2F51F729BDB0036CC93 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD3F2EC1F729BDB0036CC93 /* AppDelegate.swift */; }; 12 | 8AD3F2F61F729BDB0036CC93 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8AD3F2ED1F729BDB0036CC93 /* Assets.xcassets */; }; 13 | 8AD3F2F71F729BDB0036CC93 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8AD3F2EE1F729BDB0036CC93 /* LaunchScreen.storyboard */; }; 14 | 8AD3F2F81F729BDB0036CC93 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8AD3F2F01F729BDB0036CC93 /* Main.storyboard */; }; 15 | 8AD3F2FA1F729BDB0036CC93 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD3F2F31F729BDB0036CC93 /* MapViewController.swift */; }; 16 | 8AD3F2FB1F729BDB0036CC93 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AD3F2F41F729BDB0036CC93 /* ViewController.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 8A47DCA71CB9226400B6CD47 /* PulsatorDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PulsatorDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 8A77DC111CBC467000087690 /* Pulsator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pulsator.swift; sourceTree = ""; }; 22 | 8AD3F2EC1F729BDB0036CC93 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 8AD3F2ED1F729BDB0036CC93 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 8AD3F2EF1F729BDB0036CC93 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 25 | 8AD3F2F11F729BDB0036CC93 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 8AD3F2F21F729BDB0036CC93 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | 8AD3F2F31F729BDB0036CC93 /* MapViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = ""; }; 28 | 8AD3F2F41F729BDB0036CC93 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 8A47DCA41CB9226400B6CD47 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 8A47DC9E1CB9226400B6CD47 = { 43 | isa = PBXGroup; 44 | children = ( 45 | 8A77DC101CBC467000087690 /* Pulsator */, 46 | 8AD3F2EB1F729BDB0036CC93 /* PulsatorDemo */, 47 | 8A47DCA81CB9226400B6CD47 /* Products */, 48 | ); 49 | sourceTree = ""; 50 | }; 51 | 8A47DCA81CB9226400B6CD47 /* Products */ = { 52 | isa = PBXGroup; 53 | children = ( 54 | 8A47DCA71CB9226400B6CD47 /* PulsatorDemo.app */, 55 | ); 56 | name = Products; 57 | sourceTree = ""; 58 | }; 59 | 8A77DC101CBC467000087690 /* Pulsator */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 8A77DC111CBC467000087690 /* Pulsator.swift */, 63 | ); 64 | name = Pulsator; 65 | path = ../Pulsator; 66 | sourceTree = ""; 67 | }; 68 | 8AD3F2EB1F729BDB0036CC93 /* PulsatorDemo */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 8AD3F2F21F729BDB0036CC93 /* Info.plist */, 72 | 8AD3F2ED1F729BDB0036CC93 /* Assets.xcassets */, 73 | 8AD3F2EE1F729BDB0036CC93 /* LaunchScreen.storyboard */, 74 | 8AD3F2F01F729BDB0036CC93 /* Main.storyboard */, 75 | 8AD3F2EC1F729BDB0036CC93 /* AppDelegate.swift */, 76 | 8AD3F2F41F729BDB0036CC93 /* ViewController.swift */, 77 | 8AD3F2F31F729BDB0036CC93 /* MapViewController.swift */, 78 | ); 79 | path = PulsatorDemo; 80 | sourceTree = ""; 81 | }; 82 | /* End PBXGroup section */ 83 | 84 | /* Begin PBXNativeTarget section */ 85 | 8A47DCA61CB9226400B6CD47 /* PulsatorDemo */ = { 86 | isa = PBXNativeTarget; 87 | buildConfigurationList = 8A47DCB91CB9226400B6CD47 /* Build configuration list for PBXNativeTarget "PulsatorDemo" */; 88 | buildPhases = ( 89 | 8A47DCA31CB9226400B6CD47 /* Sources */, 90 | 8A47DCA41CB9226400B6CD47 /* Frameworks */, 91 | 8A47DCA51CB9226400B6CD47 /* Resources */, 92 | ); 93 | buildRules = ( 94 | ); 95 | dependencies = ( 96 | ); 97 | name = PulsatorDemo; 98 | productName = Pulsator; 99 | productReference = 8A47DCA71CB9226400B6CD47 /* PulsatorDemo.app */; 100 | productType = "com.apple.product-type.application"; 101 | }; 102 | /* End PBXNativeTarget section */ 103 | 104 | /* Begin PBXProject section */ 105 | 8A47DC9F1CB9226400B6CD47 /* Project object */ = { 106 | isa = PBXProject; 107 | attributes = { 108 | LastSwiftUpdateCheck = 0730; 109 | LastUpgradeCheck = 1020; 110 | ORGANIZATIONNAME = "Shuichi Tsutsumi"; 111 | TargetAttributes = { 112 | 8A47DCA61CB9226400B6CD47 = { 113 | CreatedOnToolsVersion = 7.3; 114 | DevelopmentTeam = 9Z86A4AWDE; 115 | LastSwiftMigration = 1020; 116 | }; 117 | }; 118 | }; 119 | buildConfigurationList = 8A47DCA21CB9226400B6CD47 /* Build configuration list for PBXProject "PulsatorDemo" */; 120 | compatibilityVersion = "Xcode 3.2"; 121 | developmentRegion = en; 122 | hasScannedForEncodings = 0; 123 | knownRegions = ( 124 | en, 125 | Base, 126 | ); 127 | mainGroup = 8A47DC9E1CB9226400B6CD47; 128 | productRefGroup = 8A47DCA81CB9226400B6CD47 /* Products */; 129 | projectDirPath = ""; 130 | projectRoot = ""; 131 | targets = ( 132 | 8A47DCA61CB9226400B6CD47 /* PulsatorDemo */, 133 | ); 134 | }; 135 | /* End PBXProject section */ 136 | 137 | /* Begin PBXResourcesBuildPhase section */ 138 | 8A47DCA51CB9226400B6CD47 /* Resources */ = { 139 | isa = PBXResourcesBuildPhase; 140 | buildActionMask = 2147483647; 141 | files = ( 142 | 8AD3F2F61F729BDB0036CC93 /* Assets.xcassets in Resources */, 143 | 8AD3F2F81F729BDB0036CC93 /* Main.storyboard in Resources */, 144 | 8AD3F2F71F729BDB0036CC93 /* LaunchScreen.storyboard in Resources */, 145 | ); 146 | runOnlyForDeploymentPostprocessing = 0; 147 | }; 148 | /* End PBXResourcesBuildPhase section */ 149 | 150 | /* Begin PBXSourcesBuildPhase section */ 151 | 8A47DCA31CB9226400B6CD47 /* Sources */ = { 152 | isa = PBXSourcesBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | 8A77DC121CBC467000087690 /* Pulsator.swift in Sources */, 156 | 8AD3F2FA1F729BDB0036CC93 /* MapViewController.swift in Sources */, 157 | 8AD3F2FB1F729BDB0036CC93 /* ViewController.swift in Sources */, 158 | 8AD3F2F51F729BDB0036CC93 /* AppDelegate.swift in Sources */, 159 | ); 160 | runOnlyForDeploymentPostprocessing = 0; 161 | }; 162 | /* End PBXSourcesBuildPhase section */ 163 | 164 | /* Begin PBXVariantGroup section */ 165 | 8AD3F2EE1F729BDB0036CC93 /* LaunchScreen.storyboard */ = { 166 | isa = PBXVariantGroup; 167 | children = ( 168 | 8AD3F2EF1F729BDB0036CC93 /* Base */, 169 | ); 170 | name = LaunchScreen.storyboard; 171 | sourceTree = ""; 172 | }; 173 | 8AD3F2F01F729BDB0036CC93 /* Main.storyboard */ = { 174 | isa = PBXVariantGroup; 175 | children = ( 176 | 8AD3F2F11F729BDB0036CC93 /* Base */, 177 | ); 178 | name = Main.storyboard; 179 | sourceTree = ""; 180 | }; 181 | /* End PBXVariantGroup section */ 182 | 183 | /* Begin XCBuildConfiguration section */ 184 | 8A47DCB71CB9226400B6CD47 /* Debug */ = { 185 | isa = XCBuildConfiguration; 186 | buildSettings = { 187 | ALWAYS_SEARCH_USER_PATHS = NO; 188 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 189 | CLANG_ANALYZER_NONNULL = YES; 190 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 191 | CLANG_CXX_LIBRARY = "libc++"; 192 | CLANG_ENABLE_MODULES = YES; 193 | CLANG_ENABLE_OBJC_ARC = YES; 194 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 195 | CLANG_WARN_BOOL_CONVERSION = YES; 196 | CLANG_WARN_COMMA = YES; 197 | CLANG_WARN_CONSTANT_CONVERSION = YES; 198 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 199 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 200 | CLANG_WARN_EMPTY_BODY = YES; 201 | CLANG_WARN_ENUM_CONVERSION = YES; 202 | CLANG_WARN_INFINITE_RECURSION = YES; 203 | CLANG_WARN_INT_CONVERSION = YES; 204 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 205 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 206 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 207 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 208 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 209 | CLANG_WARN_STRICT_PROTOTYPES = YES; 210 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 211 | CLANG_WARN_UNREACHABLE_CODE = YES; 212 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 213 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 214 | COPY_PHASE_STRIP = NO; 215 | DEBUG_INFORMATION_FORMAT = dwarf; 216 | ENABLE_STRICT_OBJC_MSGSEND = YES; 217 | ENABLE_TESTABILITY = YES; 218 | GCC_C_LANGUAGE_STANDARD = gnu99; 219 | GCC_DYNAMIC_NO_PIC = NO; 220 | GCC_NO_COMMON_BLOCKS = YES; 221 | GCC_OPTIMIZATION_LEVEL = 0; 222 | GCC_PREPROCESSOR_DEFINITIONS = ( 223 | "DEBUG=1", 224 | "$(inherited)", 225 | ); 226 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 227 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 228 | GCC_WARN_UNDECLARED_SELECTOR = YES; 229 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 230 | GCC_WARN_UNUSED_FUNCTION = YES; 231 | GCC_WARN_UNUSED_VARIABLE = YES; 232 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 233 | MTL_ENABLE_DEBUG_INFO = YES; 234 | ONLY_ACTIVE_ARCH = YES; 235 | SDKROOT = iphoneos; 236 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 237 | }; 238 | name = Debug; 239 | }; 240 | 8A47DCB81CB9226400B6CD47 /* Release */ = { 241 | isa = XCBuildConfiguration; 242 | buildSettings = { 243 | ALWAYS_SEARCH_USER_PATHS = NO; 244 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 245 | CLANG_ANALYZER_NONNULL = YES; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 251 | CLANG_WARN_BOOL_CONVERSION = YES; 252 | CLANG_WARN_COMMA = YES; 253 | CLANG_WARN_CONSTANT_CONVERSION = YES; 254 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 255 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 256 | CLANG_WARN_EMPTY_BODY = YES; 257 | CLANG_WARN_ENUM_CONVERSION = YES; 258 | CLANG_WARN_INFINITE_RECURSION = YES; 259 | CLANG_WARN_INT_CONVERSION = YES; 260 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 261 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 262 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 265 | CLANG_WARN_STRICT_PROTOTYPES = YES; 266 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 270 | COPY_PHASE_STRIP = NO; 271 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 272 | ENABLE_NS_ASSERTIONS = NO; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu99; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 277 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 278 | GCC_WARN_UNDECLARED_SELECTOR = YES; 279 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 280 | GCC_WARN_UNUSED_FUNCTION = YES; 281 | GCC_WARN_UNUSED_VARIABLE = YES; 282 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 283 | MTL_ENABLE_DEBUG_INFO = NO; 284 | SDKROOT = iphoneos; 285 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 286 | VALIDATE_PRODUCT = YES; 287 | }; 288 | name = Release; 289 | }; 290 | 8A47DCBA1CB9226400B6CD47 /* Debug */ = { 291 | isa = XCBuildConfiguration; 292 | buildSettings = { 293 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 294 | DEVELOPMENT_TEAM = 9Z86A4AWDE; 295 | INFOPLIST_FILE = PulsatorDemo/Info.plist; 296 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 297 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 298 | PRODUCT_BUNDLE_IDENTIFIER = "Shuichi-Tsutsumi.Pulsator"; 299 | PRODUCT_NAME = PulsatorDemo; 300 | SWIFT_VERSION = 5.0; 301 | }; 302 | name = Debug; 303 | }; 304 | 8A47DCBB1CB9226400B6CD47 /* Release */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 308 | DEVELOPMENT_TEAM = 9Z86A4AWDE; 309 | INFOPLIST_FILE = PulsatorDemo/Info.plist; 310 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 311 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 312 | PRODUCT_BUNDLE_IDENTIFIER = "Shuichi-Tsutsumi.Pulsator"; 313 | PRODUCT_NAME = PulsatorDemo; 314 | SWIFT_VERSION = 5.0; 315 | }; 316 | name = Release; 317 | }; 318 | /* End XCBuildConfiguration section */ 319 | 320 | /* Begin XCConfigurationList section */ 321 | 8A47DCA21CB9226400B6CD47 /* Build configuration list for PBXProject "PulsatorDemo" */ = { 322 | isa = XCConfigurationList; 323 | buildConfigurations = ( 324 | 8A47DCB71CB9226400B6CD47 /* Debug */, 325 | 8A47DCB81CB9226400B6CD47 /* Release */, 326 | ); 327 | defaultConfigurationIsVisible = 0; 328 | defaultConfigurationName = Release; 329 | }; 330 | 8A47DCB91CB9226400B6CD47 /* Build configuration list for PBXNativeTarget "PulsatorDemo" */ = { 331 | isa = XCConfigurationList; 332 | buildConfigurations = ( 333 | 8A47DCBA1CB9226400B6CD47 /* Debug */, 334 | 8A47DCBB1CB9226400B6CD47 /* Release */, 335 | ); 336 | defaultConfigurationIsVisible = 0; 337 | defaultConfigurationName = Release; 338 | }; 339 | /* End XCConfigurationList section */ 340 | }; 341 | rootObject = 8A47DC9F1CB9226400B6CD47 /* Project object */; 342 | } 343 | -------------------------------------------------------------------------------- /Example/PulsatorDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/PulsatorDemo.xcodeproj/xcshareddata/xcschemes/PulsatorDemo.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 | -------------------------------------------------------------------------------- /Example/PulsatorDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Pulsator 4 | // 5 | // Created by Shuichi Tsutsumi on 4/9/16. 6 | // Copyright © 2016 Shuichi Tsutsumi. 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]? = nil) -> 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 throttle down OpenGL ES frame rates. 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 inactive 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 | -------------------------------------------------------------------------------- /Example/PulsatorDemo/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 | } -------------------------------------------------------------------------------- /Example/PulsatorDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/PulsatorDemo/Assets.xcassets/Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "IPhone_5s.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/PulsatorDemo/Assets.xcassets/Image.imageset/IPhone_5s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shu223/Pulsator/86e155f98afa68140cd0ba637af1430250443e2f/Example/PulsatorDemo/Assets.xcassets/Image.imageset/IPhone_5s.png -------------------------------------------------------------------------------- /Example/PulsatorDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Example/PulsatorDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 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 | 82 | 88 | 94 | 100 | 106 | 112 | 118 | 124 | 130 | 136 | 142 | 148 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /Example/PulsatorDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | 39 | UIViewControllerBasedStatusBarAppearance 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/PulsatorDemo/MapViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapViewController.swift 3 | // PulsatorDemo 4 | // 5 | // Created by Shuichi Tsutsumi on 6/12/16. 6 | // Copyright © 2016 Shuichi Tsutsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MapKit 11 | 12 | class MapViewController: UIViewController, MKMapViewDelegate { 13 | 14 | @IBOutlet weak var mapView: MKMapView! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | let delta = 5.0 20 | var region = MKCoordinateRegion() 21 | region.center.latitude = 50.0 22 | region.center.longitude = -100.0 23 | region.span.latitudeDelta = delta 24 | region.span.longitudeDelta = delta 25 | mapView.setRegion(region, animated: true) 26 | 27 | mapView.delegate = self 28 | addAnnotations() 29 | } 30 | 31 | override func didReceiveMemoryWarning() { 32 | super.didReceiveMemoryWarning() 33 | } 34 | 35 | private func addAnnotations() { 36 | let point = MKPointAnnotation() 37 | point.coordinate = CLLocationCoordinate2DMake(50, -100) 38 | point.title = "TITLE" 39 | point.subtitle = "Subtitle" 40 | mapView.addAnnotation(point) 41 | } 42 | 43 | // ========================================================================= 44 | // MARK: - MKMapViewDelegate 45 | 46 | func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 47 | guard !annotation.isKind(of: MKUserLocation.classForCoder()) else { return nil } 48 | 49 | return AnnotationView(annotation: annotation, reuseIdentifier: "PulsatorDemoAnnotation") 50 | } 51 | 52 | // ========================================================================= 53 | // MARK: - Actions 54 | 55 | @IBAction func backBtnTapped(sender: UIButton) { 56 | dismiss(animated: true, completion: nil) 57 | } 58 | } 59 | 60 | class AnnotationView: MKAnnotationView { 61 | 62 | override init(annotation: MKAnnotation?, reuseIdentifier: String?) { 63 | super.init(annotation: annotation, reuseIdentifier: reuseIdentifier) 64 | 65 | addHalo() 66 | } 67 | 68 | required init?(coder aDecoder: NSCoder) { 69 | fatalError("init(coder:) has not been implemented") 70 | } 71 | 72 | private func addHalo() { 73 | let pulsator = Pulsator() 74 | pulsator.position = center 75 | pulsator.numPulse = 5 76 | pulsator.radius = 40 77 | pulsator.animationDuration = 3 78 | pulsator.backgroundColor = UIColor(red: 0, green: 0.455, blue: 0.756, alpha: 1).cgColor 79 | layer.addSublayer(pulsator) 80 | pulsator.start() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Example/PulsatorDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Pulsator 4 | // 5 | // Created by Shuichi Tsutsumi on 4/9/16. 6 | // Copyright © 2016 Shuichi Tsutsumi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let kMaxRadius: CGFloat = 200 12 | let kMaxDuration: TimeInterval = 10 13 | 14 | class ViewController: UIViewController { 15 | 16 | @IBOutlet weak var sourceView: UIImageView! 17 | @IBOutlet weak var countSlider: UISlider! 18 | @IBOutlet weak var radiusSlider: UISlider! 19 | @IBOutlet weak var durationSlider: UISlider! 20 | @IBOutlet weak var rSlider: UISlider! 21 | @IBOutlet weak var gSlider: UISlider! 22 | @IBOutlet weak var bSlider: UISlider! 23 | @IBOutlet weak var aSlider: UISlider! 24 | @IBOutlet weak var countLabel: UILabel! 25 | @IBOutlet weak var radiusLabel: UILabel! 26 | @IBOutlet weak var durationLabel: UILabel! 27 | @IBOutlet weak var rLabel: UILabel! 28 | @IBOutlet weak var gLabel: UILabel! 29 | @IBOutlet weak var bLabel: UILabel! 30 | @IBOutlet weak var aLabel: UILabel! 31 | 32 | let pulsator = Pulsator() 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | 37 | sourceView.layer.superlayer?.insertSublayer(pulsator, below: sourceView.layer) 38 | setupInitialValues() 39 | pulsator.start() 40 | } 41 | 42 | override func didReceiveMemoryWarning() { 43 | super.didReceiveMemoryWarning() 44 | } 45 | 46 | override func viewDidLayoutSubviews() { 47 | super.viewDidLayoutSubviews() 48 | view.layer.layoutIfNeeded() 49 | pulsator.position = sourceView.layer.position 50 | } 51 | 52 | private func setupInitialValues() { 53 | countSlider.value = 5 54 | countChanged(sender: nil) 55 | 56 | radiusSlider.value = 0.7 57 | radiusChanged(sender: nil) 58 | 59 | durationSlider.value = 0.5 60 | durationChanged(sender: nil) 61 | 62 | rSlider.value = 0 63 | gSlider.value = 0.455 64 | bSlider.value = 0.756 65 | aSlider.value = 1 66 | colorChanged(sender: nil) 67 | } 68 | 69 | // MARK: - Actions 70 | 71 | @IBAction func countChanged(sender: UISlider?) { 72 | //you can specify the number of pulse by the property "numPulse" 73 | pulsator.numPulse = Int(countSlider.value) 74 | countLabel.text = "\(pulsator.numPulse)" 75 | } 76 | 77 | @IBAction func radiusChanged(sender: UISlider?) { 78 | pulsator.radius = CGFloat(radiusSlider.value) * kMaxRadius 79 | radiusLabel.text = String(format: "%.0f", pulsator.radius) 80 | } 81 | 82 | @IBAction func durationChanged(sender: UISlider?) { 83 | pulsator.animationDuration = Double(durationSlider.value) * kMaxDuration 84 | durationLabel.text = String(format: "%.1f", pulsator.animationDuration) 85 | } 86 | 87 | @IBAction func colorChanged(sender: UISlider?) { 88 | pulsator.backgroundColor = UIColor( 89 | red: CGFloat(rSlider.value), 90 | green: CGFloat(gSlider.value), 91 | blue: CGFloat(bSlider.value), 92 | alpha: CGFloat(aSlider.value)).cgColor 93 | rLabel.text = String(format: "%.2f", rSlider.value) 94 | gLabel.text = String(format: "%.2f", gSlider.value) 95 | bLabel.text = String(format: "%.2f", bSlider.value) 96 | aLabel.text = String(format: "%.2f", aSlider.value) 97 | } 98 | 99 | @IBAction func switchChanged(sender: UISwitch) { 100 | if sender.isOn { 101 | pulsator.start() 102 | } 103 | else { 104 | pulsator.stop() 105 | } 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 shu223 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Pulsator", 8 | platforms: [.iOS(.v9)], 9 | products: [ 10 | .library( 11 | name: "Pulsator", 12 | targets: ["Pulsator"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | .target( 20 | name: "Pulsator", 21 | dependencies: [], 22 | path: "Pulsator"), 23 | .testTarget( 24 | name: "PulsatorTests", 25 | dependencies: ["Pulsator"], 26 | path: "PulsatorTests"), 27 | ] 28 | ) 29 | -------------------------------------------------------------------------------- /Pulsator-macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSHumanReadableCopyright 22 | Copyright © 2018 Shuichi Tsutsumi. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pulsator-macOS/Pulsator_macOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // Pulsator_macOS.h 3 | // Pulsator-macOS 4 | // 5 | // Created by Shuichi Tsutsumi on 2018/05/17. 6 | // Copyright © 2018 Shuichi Tsutsumi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Pulsator_macOS. 12 | FOUNDATION_EXPORT double Pulsator_macOSVersionNumber; 13 | 14 | //! Project version string for Pulsator_macOS. 15 | FOUNDATION_EXPORT const unsigned char Pulsator_macOSVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Pulsator.playground/Pages/Customizations.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import UIKit 4 | import XCPlayground 5 | import Pulsator 6 | 7 | XCPlaygroundPage.currentPage.needsIndefiniteExecution = true 8 | 9 | let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400)) 10 | containerView.backgroundColor = UIColor.white 11 | let sourceView = UIImageView(frame: CGRect(x: 0.0, y: 0.0, width: 50.0, height: 50)) 12 | sourceView.contentMode = .scaleAspectFit 13 | sourceView.center = CGPoint(x: 200, y: 200) 14 | sourceView.image = UIImage(imageLiteralResourceName: "IPhone_5s.png") 15 | containerView.addSubview(sourceView) 16 | 17 | let pulsator = Pulsator() 18 | 19 | sourceView.layer.addSublayer(pulsator) 20 | sourceView.superview?.layer.insertSublayer(pulsator, below: sourceView.layer) 21 | pulsator.position = sourceView.center 22 | 23 | // Customizations 24 | pulsator.numPulse = 5 25 | pulsator.radius = 200 26 | pulsator.animationDuration = 5 27 | pulsator.backgroundColor = UIColor(red: 0, green: 0.455, blue: 0.756, alpha: 1).cgColor 28 | 29 | pulsator.start() 30 | 31 | 32 | XCPlaygroundPage.currentPage.liveView = containerView 33 | 34 | //: [Next](@next) 35 | -------------------------------------------------------------------------------- /Pulsator.playground/Pages/MapAnnotation.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | import UIKit 4 | import XCPlayground 5 | import Pulsator 6 | import MapKit 7 | 8 | class AnnotationView: MKAnnotationView { 9 | 10 | override init(annotation: MKAnnotation?, reuseIdentifier: String?) { 11 | super.init(annotation: annotation, reuseIdentifier: reuseIdentifier) 12 | 13 | addHalo() 14 | } 15 | 16 | required init?(coder aDecoder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | private func addHalo() { 21 | let pulsator = Pulsator() 22 | pulsator.position = center 23 | pulsator.numPulse = 5 24 | pulsator.radius = 40 25 | pulsator.animationDuration = 3 26 | pulsator.backgroundColor = UIColor(red: 0, green: 0.455, blue: 0.756, alpha: 1).cgColor 27 | layer.addSublayer(pulsator) 28 | pulsator.start() 29 | } 30 | } 31 | 32 | class MapViewController: UIViewController, MKMapViewDelegate { 33 | 34 | let mapView = MKMapView(frame: CGRect.zero) 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | 39 | mapView.frame = view.bounds 40 | mapView.delegate = self 41 | let delta = 5.0 42 | var region = MKCoordinateRegion() 43 | region.center.latitude = 50.0 44 | region.center.longitude = -100.0 45 | region.span.latitudeDelta = delta 46 | region.span.longitudeDelta = delta 47 | mapView.setRegion(region, animated: true) 48 | 49 | view.addSubview(mapView) 50 | view.backgroundColor = UIColor.yellow 51 | 52 | addAnnotations() 53 | } 54 | 55 | private func addAnnotations() { 56 | let point = MKPointAnnotation() 57 | point.coordinate = CLLocationCoordinate2DMake(50, -100) 58 | point.title = "TITLE" 59 | point.subtitle = "Subtitle" 60 | mapView.addAnnotation(point) 61 | } 62 | 63 | func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { 64 | guard !annotation.isKind(of: MKUserLocation.classForCoder()) else { return nil } 65 | 66 | return AnnotationView(annotation: annotation, reuseIdentifier: "PulsatorDemoAnnotation") 67 | } 68 | } 69 | 70 | let controller = MapViewController() 71 | XCPlaygroundPage.currentPage.liveView = controller.view 72 | XCPlaygroundPage.currentPage.needsIndefiniteExecution = true 73 | 74 | //: [Next](@next) 75 | -------------------------------------------------------------------------------- /Pulsator.playground/Resources/IPhone_5s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shu223/Pulsator/86e155f98afa68140cd0ba637af1430250443e2f/Pulsator.playground/Resources/IPhone_5s.png -------------------------------------------------------------------------------- /Pulsator.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Pulsator.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Pulsator" 3 | s.version = "0.6.0" 4 | s.summary = "Pulse animation for iOS." 5 | s.homepage = "https://github.com/shu223/Pulsator" 6 | s.screenshots = "https://github.com/shu223/Pulsator/blob/master/demo.gif?raw=true" 7 | s.license = 'MIT' 8 | s.author = { "shu223" => "shuichi0526@gmail.com" } 9 | s.source = { :git => "https://github.com/shu223/Pulsator.git", :tag => "#{s.version}" } 10 | s.social_media_url = 'https://twitter.com/shu223' 11 | s.swift_version = '5.0' 12 | 13 | s.ios.deployment_target = '8.0' 14 | s.osx.deployment_target = '10.9' 15 | s.requires_arc = true 16 | 17 | s.source_files = 'Pulsator/Pulsator.swift' 18 | 19 | s.ios.frameworks = 'UIKit', 'QuartzCore' 20 | s.osx.frameworks = 'Cocoa', 'QuartzCore' 21 | 22 | end 23 | -------------------------------------------------------------------------------- /Pulsator.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8A51842120AE6B9A002169CA /* Pulsator_macOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A51841F20AE6B9A002169CA /* Pulsator_macOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 8A77DC211CBC4B4E00087690 /* Pulsator.h in Headers */ = {isa = PBXBuildFile; fileRef = 8A77DC201CBC4B4E00087690 /* Pulsator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | 8A77DC281CBC4B4E00087690 /* Pulsator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A77DC1D1CBC4B4E00087690 /* Pulsator.framework */; }; 13 | 8A77DC2D1CBC4B4E00087690 /* PulsatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A77DC2C1CBC4B4E00087690 /* PulsatorTests.swift */; }; 14 | 8A77DC391CBC4BD000087690 /* Pulsator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A77DC381CBC4BD000087690 /* Pulsator.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | 8A77DC291CBC4B4E00087690 /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = 8A77DC141CBC4B4E00087690 /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = 8A77DC1C1CBC4B4E00087690; 23 | remoteInfo = Pulsator; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 8A51841220AE652C002169CA /* PulsatorMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PulsatorMac.h; sourceTree = ""; }; 29 | 8A51841320AE652C002169CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 8A51841D20AE6B9A002169CA /* Pulsator_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pulsator_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 8A51841F20AE6B9A002169CA /* Pulsator_macOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Pulsator_macOS.h; sourceTree = ""; }; 32 | 8A51842020AE6B9A002169CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | 8A77DC1D1CBC4B4E00087690 /* Pulsator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pulsator.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 8A77DC201CBC4B4E00087690 /* Pulsator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Pulsator.h; sourceTree = ""; }; 35 | 8A77DC221CBC4B4E00087690 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 8A77DC271CBC4B4E00087690 /* PulsatorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PulsatorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 8A77DC2C1CBC4B4E00087690 /* PulsatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PulsatorTests.swift; sourceTree = ""; }; 38 | 8A77DC2E1CBC4B4E00087690 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 8A77DC381CBC4BD000087690 /* Pulsator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pulsator.swift; sourceTree = ""; }; 40 | 8A77DC6C1CBCB67D00087690 /* Pulsator.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Pulsator.playground; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 8A51841920AE6B9A002169CA /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | 8A77DC191CBC4B4E00087690 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | 8A77DC241CBC4B4E00087690 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | 8A77DC281CBC4B4E00087690 /* Pulsator.framework in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 8A51841120AE652C002169CA /* PulsatorMac */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 8A51841220AE652C002169CA /* PulsatorMac.h */, 73 | 8A51841320AE652C002169CA /* Info.plist */, 74 | ); 75 | path = PulsatorMac; 76 | sourceTree = ""; 77 | }; 78 | 8A51841E20AE6B9A002169CA /* Pulsator-macOS */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 8A51841F20AE6B9A002169CA /* Pulsator_macOS.h */, 82 | 8A51842020AE6B9A002169CA /* Info.plist */, 83 | ); 84 | path = "Pulsator-macOS"; 85 | sourceTree = ""; 86 | }; 87 | 8A77DC131CBC4B4E00087690 = { 88 | isa = PBXGroup; 89 | children = ( 90 | 8A77DC6C1CBCB67D00087690 /* Pulsator.playground */, 91 | 8A77DC1F1CBC4B4E00087690 /* Pulsator */, 92 | 8A77DC2B1CBC4B4E00087690 /* PulsatorTests */, 93 | 8A51841120AE652C002169CA /* PulsatorMac */, 94 | 8A51841E20AE6B9A002169CA /* Pulsator-macOS */, 95 | 8A77DC1E1CBC4B4E00087690 /* Products */, 96 | ); 97 | sourceTree = ""; 98 | }; 99 | 8A77DC1E1CBC4B4E00087690 /* Products */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 8A77DC1D1CBC4B4E00087690 /* Pulsator.framework */, 103 | 8A77DC271CBC4B4E00087690 /* PulsatorTests.xctest */, 104 | 8A51841D20AE6B9A002169CA /* Pulsator_macOS.framework */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 8A77DC1F1CBC4B4E00087690 /* Pulsator */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 8A77DC381CBC4BD000087690 /* Pulsator.swift */, 113 | 8A77DC371CBC4BC300087690 /* Supporting Files */, 114 | ); 115 | path = Pulsator; 116 | sourceTree = ""; 117 | }; 118 | 8A77DC2B1CBC4B4E00087690 /* PulsatorTests */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 8A77DC2C1CBC4B4E00087690 /* PulsatorTests.swift */, 122 | 8A77DC2E1CBC4B4E00087690 /* Info.plist */, 123 | ); 124 | path = PulsatorTests; 125 | sourceTree = ""; 126 | }; 127 | 8A77DC371CBC4BC300087690 /* Supporting Files */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 8A77DC201CBC4B4E00087690 /* Pulsator.h */, 131 | 8A77DC221CBC4B4E00087690 /* Info.plist */, 132 | ); 133 | name = "Supporting Files"; 134 | sourceTree = ""; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXHeadersBuildPhase section */ 139 | 8A51841A20AE6B9A002169CA /* Headers */ = { 140 | isa = PBXHeadersBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 8A51842120AE6B9A002169CA /* Pulsator_macOS.h in Headers */, 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | 8A77DC1A1CBC4B4E00087690 /* Headers */ = { 148 | isa = PBXHeadersBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | 8A77DC211CBC4B4E00087690 /* Pulsator.h in Headers */, 152 | ); 153 | runOnlyForDeploymentPostprocessing = 0; 154 | }; 155 | /* End PBXHeadersBuildPhase section */ 156 | 157 | /* Begin PBXNativeTarget section */ 158 | 8A51841C20AE6B9A002169CA /* Pulsator-macOS */ = { 159 | isa = PBXNativeTarget; 160 | buildConfigurationList = 8A51842220AE6B9A002169CA /* Build configuration list for PBXNativeTarget "Pulsator-macOS" */; 161 | buildPhases = ( 162 | 8A51841820AE6B9A002169CA /* Sources */, 163 | 8A51841920AE6B9A002169CA /* Frameworks */, 164 | 8A51841A20AE6B9A002169CA /* Headers */, 165 | 8A51841B20AE6B9A002169CA /* Resources */, 166 | ); 167 | buildRules = ( 168 | ); 169 | dependencies = ( 170 | ); 171 | name = "Pulsator-macOS"; 172 | productName = "Pulsator-macOS"; 173 | productReference = 8A51841D20AE6B9A002169CA /* Pulsator_macOS.framework */; 174 | productType = "com.apple.product-type.framework"; 175 | }; 176 | 8A77DC1C1CBC4B4E00087690 /* Pulsator */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = 8A77DC311CBC4B4E00087690 /* Build configuration list for PBXNativeTarget "Pulsator" */; 179 | buildPhases = ( 180 | 8A77DC181CBC4B4E00087690 /* Sources */, 181 | 8A77DC191CBC4B4E00087690 /* Frameworks */, 182 | 8A77DC1A1CBC4B4E00087690 /* Headers */, 183 | 8A77DC1B1CBC4B4E00087690 /* Resources */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | ); 189 | name = Pulsator; 190 | productName = Pulsator; 191 | productReference = 8A77DC1D1CBC4B4E00087690 /* Pulsator.framework */; 192 | productType = "com.apple.product-type.framework"; 193 | }; 194 | 8A77DC261CBC4B4E00087690 /* PulsatorTests */ = { 195 | isa = PBXNativeTarget; 196 | buildConfigurationList = 8A77DC341CBC4B4E00087690 /* Build configuration list for PBXNativeTarget "PulsatorTests" */; 197 | buildPhases = ( 198 | 8A77DC231CBC4B4E00087690 /* Sources */, 199 | 8A77DC241CBC4B4E00087690 /* Frameworks */, 200 | 8A77DC251CBC4B4E00087690 /* Resources */, 201 | ); 202 | buildRules = ( 203 | ); 204 | dependencies = ( 205 | 8A77DC2A1CBC4B4E00087690 /* PBXTargetDependency */, 206 | ); 207 | name = PulsatorTests; 208 | productName = PulsatorTests; 209 | productReference = 8A77DC271CBC4B4E00087690 /* PulsatorTests.xctest */; 210 | productType = "com.apple.product-type.bundle.unit-test"; 211 | }; 212 | /* End PBXNativeTarget section */ 213 | 214 | /* Begin PBXProject section */ 215 | 8A77DC141CBC4B4E00087690 /* Project object */ = { 216 | isa = PBXProject; 217 | attributes = { 218 | LastSwiftUpdateCheck = 0730; 219 | LastUpgradeCheck = 1020; 220 | ORGANIZATIONNAME = "Shuichi Tsutsumi"; 221 | TargetAttributes = { 222 | 8A51841C20AE6B9A002169CA = { 223 | CreatedOnToolsVersion = 9.3.1; 224 | ProvisioningStyle = Manual; 225 | }; 226 | 8A77DC1C1CBC4B4E00087690 = { 227 | CreatedOnToolsVersion = 7.3; 228 | LastSwiftMigration = 1020; 229 | }; 230 | 8A77DC261CBC4B4E00087690 = { 231 | CreatedOnToolsVersion = 7.3; 232 | LastSwiftMigration = 1020; 233 | ProvisioningStyle = Manual; 234 | }; 235 | }; 236 | }; 237 | buildConfigurationList = 8A77DC171CBC4B4E00087690 /* Build configuration list for PBXProject "Pulsator" */; 238 | compatibilityVersion = "Xcode 3.2"; 239 | developmentRegion = en; 240 | hasScannedForEncodings = 0; 241 | knownRegions = ( 242 | en, 243 | Base, 244 | ); 245 | mainGroup = 8A77DC131CBC4B4E00087690; 246 | productRefGroup = 8A77DC1E1CBC4B4E00087690 /* Products */; 247 | projectDirPath = ""; 248 | projectRoot = ""; 249 | targets = ( 250 | 8A77DC1C1CBC4B4E00087690 /* Pulsator */, 251 | 8A77DC261CBC4B4E00087690 /* PulsatorTests */, 252 | 8A51841C20AE6B9A002169CA /* Pulsator-macOS */, 253 | ); 254 | }; 255 | /* End PBXProject section */ 256 | 257 | /* Begin PBXResourcesBuildPhase section */ 258 | 8A51841B20AE6B9A002169CA /* Resources */ = { 259 | isa = PBXResourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | 8A77DC1B1CBC4B4E00087690 /* Resources */ = { 266 | isa = PBXResourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | 8A77DC251CBC4B4E00087690 /* Resources */ = { 273 | isa = PBXResourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXResourcesBuildPhase section */ 280 | 281 | /* Begin PBXSourcesBuildPhase section */ 282 | 8A51841820AE6B9A002169CA /* Sources */ = { 283 | isa = PBXSourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | 8A77DC181CBC4B4E00087690 /* Sources */ = { 290 | isa = PBXSourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 8A77DC391CBC4BD000087690 /* Pulsator.swift in Sources */, 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | 8A77DC231CBC4B4E00087690 /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 8A77DC2D1CBC4B4E00087690 /* PulsatorTests.swift in Sources */, 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | }; 305 | /* End PBXSourcesBuildPhase section */ 306 | 307 | /* Begin PBXTargetDependency section */ 308 | 8A77DC2A1CBC4B4E00087690 /* PBXTargetDependency */ = { 309 | isa = PBXTargetDependency; 310 | target = 8A77DC1C1CBC4B4E00087690 /* Pulsator */; 311 | targetProxy = 8A77DC291CBC4B4E00087690 /* PBXContainerItemProxy */; 312 | }; 313 | /* End PBXTargetDependency section */ 314 | 315 | /* Begin XCBuildConfiguration section */ 316 | 8A51842320AE6B9A002169CA /* Debug */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 320 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 321 | CLANG_ENABLE_OBJC_WEAK = YES; 322 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 323 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 324 | CODE_SIGN_IDENTITY = ""; 325 | CODE_SIGN_STYLE = Manual; 326 | COMBINE_HIDPI_IMAGES = YES; 327 | DEFINES_MODULE = YES; 328 | DEVELOPMENT_TEAM = ""; 329 | DYLIB_COMPATIBILITY_VERSION = 1; 330 | DYLIB_CURRENT_VERSION = 1; 331 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 332 | FRAMEWORK_VERSION = A; 333 | GCC_C_LANGUAGE_STANDARD = gnu11; 334 | INFOPLIST_FILE = "Pulsator-macOS/Info.plist"; 335 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 336 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 337 | MACOSX_DEPLOYMENT_TARGET = 10.13; 338 | PRODUCT_BUNDLE_IDENTIFIER = "Shuichi-Tsutsumi.Pulsator-macOS"; 339 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 340 | PROVISIONING_PROFILE_SPECIFIER = ""; 341 | SDKROOT = macosx; 342 | SKIP_INSTALL = YES; 343 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 344 | SWIFT_VERSION = 5.0; 345 | }; 346 | name = Debug; 347 | }; 348 | 8A51842420AE6B9A002169CA /* Release */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 352 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 353 | CLANG_ENABLE_OBJC_WEAK = YES; 354 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 355 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 356 | CODE_SIGN_IDENTITY = ""; 357 | CODE_SIGN_STYLE = Manual; 358 | COMBINE_HIDPI_IMAGES = YES; 359 | DEFINES_MODULE = YES; 360 | DEVELOPMENT_TEAM = ""; 361 | DYLIB_COMPATIBILITY_VERSION = 1; 362 | DYLIB_CURRENT_VERSION = 1; 363 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 364 | FRAMEWORK_VERSION = A; 365 | GCC_C_LANGUAGE_STANDARD = gnu11; 366 | INFOPLIST_FILE = "Pulsator-macOS/Info.plist"; 367 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 368 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 369 | MACOSX_DEPLOYMENT_TARGET = 10.13; 370 | PRODUCT_BUNDLE_IDENTIFIER = "Shuichi-Tsutsumi.Pulsator-macOS"; 371 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 372 | PROVISIONING_PROFILE_SPECIFIER = ""; 373 | SDKROOT = macosx; 374 | SKIP_INSTALL = YES; 375 | SWIFT_VERSION = 5.0; 376 | }; 377 | name = Release; 378 | }; 379 | 8A77DC2F1CBC4B4E00087690 /* Debug */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | ALWAYS_SEARCH_USER_PATHS = NO; 383 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 384 | CLANG_ANALYZER_NONNULL = YES; 385 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 386 | CLANG_CXX_LIBRARY = "libc++"; 387 | CLANG_ENABLE_MODULES = YES; 388 | CLANG_ENABLE_OBJC_ARC = YES; 389 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 390 | CLANG_WARN_BOOL_CONVERSION = YES; 391 | CLANG_WARN_COMMA = YES; 392 | CLANG_WARN_CONSTANT_CONVERSION = YES; 393 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 394 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 395 | CLANG_WARN_EMPTY_BODY = YES; 396 | CLANG_WARN_ENUM_CONVERSION = YES; 397 | CLANG_WARN_INFINITE_RECURSION = YES; 398 | CLANG_WARN_INT_CONVERSION = YES; 399 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 400 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 401 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 402 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 403 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 404 | CLANG_WARN_STRICT_PROTOTYPES = YES; 405 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 406 | CLANG_WARN_UNREACHABLE_CODE = YES; 407 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 408 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 409 | COPY_PHASE_STRIP = NO; 410 | CURRENT_PROJECT_VERSION = 1; 411 | DEBUG_INFORMATION_FORMAT = dwarf; 412 | ENABLE_STRICT_OBJC_MSGSEND = YES; 413 | ENABLE_TESTABILITY = YES; 414 | GCC_C_LANGUAGE_STANDARD = gnu99; 415 | GCC_DYNAMIC_NO_PIC = NO; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_OPTIMIZATION_LEVEL = 0; 418 | GCC_PREPROCESSOR_DEFINITIONS = ( 419 | "DEBUG=1", 420 | "$(inherited)", 421 | ); 422 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 423 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 424 | GCC_WARN_UNDECLARED_SELECTOR = YES; 425 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 426 | GCC_WARN_UNUSED_FUNCTION = YES; 427 | GCC_WARN_UNUSED_VARIABLE = YES; 428 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 429 | MTL_ENABLE_DEBUG_INFO = YES; 430 | ONLY_ACTIVE_ARCH = YES; 431 | SDKROOT = iphoneos; 432 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 433 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 434 | TARGETED_DEVICE_FAMILY = "1,2"; 435 | VERSIONING_SYSTEM = "apple-generic"; 436 | VERSION_INFO_PREFIX = ""; 437 | }; 438 | name = Debug; 439 | }; 440 | 8A77DC301CBC4B4E00087690 /* Release */ = { 441 | isa = XCBuildConfiguration; 442 | buildSettings = { 443 | ALWAYS_SEARCH_USER_PATHS = NO; 444 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 445 | CLANG_ANALYZER_NONNULL = YES; 446 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 447 | CLANG_CXX_LIBRARY = "libc++"; 448 | CLANG_ENABLE_MODULES = YES; 449 | CLANG_ENABLE_OBJC_ARC = YES; 450 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 451 | CLANG_WARN_BOOL_CONVERSION = YES; 452 | CLANG_WARN_COMMA = YES; 453 | CLANG_WARN_CONSTANT_CONVERSION = YES; 454 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 455 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 456 | CLANG_WARN_EMPTY_BODY = YES; 457 | CLANG_WARN_ENUM_CONVERSION = YES; 458 | CLANG_WARN_INFINITE_RECURSION = YES; 459 | CLANG_WARN_INT_CONVERSION = YES; 460 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 461 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 462 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 463 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 464 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 465 | CLANG_WARN_STRICT_PROTOTYPES = YES; 466 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 467 | CLANG_WARN_UNREACHABLE_CODE = YES; 468 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 469 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 470 | COPY_PHASE_STRIP = NO; 471 | CURRENT_PROJECT_VERSION = 1; 472 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 473 | ENABLE_NS_ASSERTIONS = NO; 474 | ENABLE_STRICT_OBJC_MSGSEND = YES; 475 | GCC_C_LANGUAGE_STANDARD = gnu99; 476 | GCC_NO_COMMON_BLOCKS = YES; 477 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 478 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 479 | GCC_WARN_UNDECLARED_SELECTOR = YES; 480 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 481 | GCC_WARN_UNUSED_FUNCTION = YES; 482 | GCC_WARN_UNUSED_VARIABLE = YES; 483 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 484 | MTL_ENABLE_DEBUG_INFO = NO; 485 | SDKROOT = iphoneos; 486 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 487 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 488 | TARGETED_DEVICE_FAMILY = "1,2"; 489 | VALIDATE_PRODUCT = YES; 490 | "VALID_ARCHS[sdk=iphonesimulator*]" = ""; 491 | VERSIONING_SYSTEM = "apple-generic"; 492 | VERSION_INFO_PREFIX = ""; 493 | }; 494 | name = Release; 495 | }; 496 | 8A77DC321CBC4B4E00087690 /* Debug */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | CLANG_ENABLE_MODULES = YES; 500 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 501 | DEFINES_MODULE = YES; 502 | DYLIB_COMPATIBILITY_VERSION = 1; 503 | DYLIB_CURRENT_VERSION = 1; 504 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 505 | INFOPLIST_FILE = Pulsator/Info.plist; 506 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 507 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 508 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 509 | "LD_RUNPATH_SEARCH_PATHS[sdk=iphoneos*]" = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 510 | "LD_RUNPATH_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 511 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 512 | MACOSX_DEPLOYMENT_TARGET = 10.12; 513 | PRODUCT_BUNDLE_IDENTIFIER = "Shuichi-Tsutsumi.Pulsator"; 514 | PRODUCT_NAME = "$(TARGET_NAME)"; 515 | SKIP_INSTALL = YES; 516 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; 517 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 518 | SWIFT_VERSION = 5.0; 519 | "VALID_ARCHS[sdk=iphoneos*]" = "arm64 armv7 armv7s"; 520 | "VALID_ARCHS[sdk=iphonesimulator*]" = "i386 x86_64"; 521 | }; 522 | name = Debug; 523 | }; 524 | 8A77DC331CBC4B4E00087690 /* Release */ = { 525 | isa = XCBuildConfiguration; 526 | buildSettings = { 527 | CLANG_ENABLE_MODULES = YES; 528 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 529 | DEFINES_MODULE = YES; 530 | DYLIB_COMPATIBILITY_VERSION = 1; 531 | DYLIB_CURRENT_VERSION = 1; 532 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 533 | INFOPLIST_FILE = Pulsator/Info.plist; 534 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 535 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 536 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 537 | "LD_RUNPATH_SEARCH_PATHS[sdk=iphoneos*]" = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 538 | "LD_RUNPATH_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 539 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 540 | MACOSX_DEPLOYMENT_TARGET = 10.12; 541 | PRODUCT_BUNDLE_IDENTIFIER = "Shuichi-Tsutsumi.Pulsator"; 542 | PRODUCT_NAME = "$(TARGET_NAME)"; 543 | SKIP_INSTALL = YES; 544 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; 545 | SWIFT_VERSION = 5.0; 546 | "VALID_ARCHS[sdk=iphoneos*]" = "arm64 armv7 armv7s"; 547 | "VALID_ARCHS[sdk=iphonesimulator*]" = "i386 x86_64"; 548 | }; 549 | name = Release; 550 | }; 551 | 8A77DC351CBC4B4E00087690 /* Debug */ = { 552 | isa = XCBuildConfiguration; 553 | buildSettings = { 554 | CODE_SIGN_STYLE = Manual; 555 | DEVELOPMENT_TEAM = ""; 556 | INFOPLIST_FILE = PulsatorTests/Info.plist; 557 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 558 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 559 | PRODUCT_BUNDLE_IDENTIFIER = "Shuichi-Tsutsumi.PulsatorTests"; 560 | PRODUCT_NAME = "$(TARGET_NAME)"; 561 | PROVISIONING_PROFILE_SPECIFIER = ""; 562 | SWIFT_VERSION = 5.0; 563 | }; 564 | name = Debug; 565 | }; 566 | 8A77DC361CBC4B4E00087690 /* Release */ = { 567 | isa = XCBuildConfiguration; 568 | buildSettings = { 569 | CODE_SIGN_STYLE = Manual; 570 | DEVELOPMENT_TEAM = ""; 571 | INFOPLIST_FILE = PulsatorTests/Info.plist; 572 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 573 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 574 | PRODUCT_BUNDLE_IDENTIFIER = "Shuichi-Tsutsumi.PulsatorTests"; 575 | PRODUCT_NAME = "$(TARGET_NAME)"; 576 | PROVISIONING_PROFILE_SPECIFIER = ""; 577 | SWIFT_VERSION = 5.0; 578 | }; 579 | name = Release; 580 | }; 581 | /* End XCBuildConfiguration section */ 582 | 583 | /* Begin XCConfigurationList section */ 584 | 8A51842220AE6B9A002169CA /* Build configuration list for PBXNativeTarget "Pulsator-macOS" */ = { 585 | isa = XCConfigurationList; 586 | buildConfigurations = ( 587 | 8A51842320AE6B9A002169CA /* Debug */, 588 | 8A51842420AE6B9A002169CA /* Release */, 589 | ); 590 | defaultConfigurationIsVisible = 0; 591 | defaultConfigurationName = Release; 592 | }; 593 | 8A77DC171CBC4B4E00087690 /* Build configuration list for PBXProject "Pulsator" */ = { 594 | isa = XCConfigurationList; 595 | buildConfigurations = ( 596 | 8A77DC2F1CBC4B4E00087690 /* Debug */, 597 | 8A77DC301CBC4B4E00087690 /* Release */, 598 | ); 599 | defaultConfigurationIsVisible = 0; 600 | defaultConfigurationName = Release; 601 | }; 602 | 8A77DC311CBC4B4E00087690 /* Build configuration list for PBXNativeTarget "Pulsator" */ = { 603 | isa = XCConfigurationList; 604 | buildConfigurations = ( 605 | 8A77DC321CBC4B4E00087690 /* Debug */, 606 | 8A77DC331CBC4B4E00087690 /* Release */, 607 | ); 608 | defaultConfigurationIsVisible = 0; 609 | defaultConfigurationName = Release; 610 | }; 611 | 8A77DC341CBC4B4E00087690 /* Build configuration list for PBXNativeTarget "PulsatorTests" */ = { 612 | isa = XCConfigurationList; 613 | buildConfigurations = ( 614 | 8A77DC351CBC4B4E00087690 /* Debug */, 615 | 8A77DC361CBC4B4E00087690 /* Release */, 616 | ); 617 | defaultConfigurationIsVisible = 0; 618 | defaultConfigurationName = Release; 619 | }; 620 | /* End XCConfigurationList section */ 621 | }; 622 | rootObject = 8A77DC141CBC4B4E00087690 /* Project object */; 623 | } 624 | -------------------------------------------------------------------------------- /Pulsator.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Pulsator.xcodeproj/xcshareddata/xcschemes/Pulsator-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Pulsator.xcodeproj/xcshareddata/xcschemes/Pulsator.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Pulsator.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Pulsator/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pulsator/Pulsator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Pulsator.h 3 | // Pulsator 4 | // 5 | // Created by Shuichi Tsutsumi on 4/12/16. 6 | // Copyright © 2016 Shuichi Tsutsumi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Pulsator. 12 | FOUNDATION_EXPORT double PulsatorVersionNumber; 13 | 14 | //! Project version string for Pulsator. 15 | FOUNDATION_EXPORT const unsigned char PulsatorVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Pulsator/Pulsator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Pulsator.swift 3 | // Pulsator 4 | // 5 | // Created by Shuichi Tsutsumi on 4/9/16. 6 | // Copyright © 2016 Shuichi Tsutsumi. All rights reserved. 7 | // 8 | // Objective-C version: https://github.com/shu223/PulsingHalo 9 | 10 | #if os(iOS) 11 | import UIKit 12 | public typealias Color = UIColor 13 | 14 | internal let screenScale = UIScreen.main.scale 15 | internal let applicationWillBecomeActiveNotfication = UIApplication.willEnterForegroundNotification 16 | internal let applicationDidResignActiveNotification = UIApplication.didEnterBackgroundNotification 17 | #elseif os(macOS) 18 | import Cocoa 19 | public typealias Color = NSColor 20 | 21 | internal let screenScale = NSScreen.main?.backingScaleFactor ?? 0.0 22 | internal let applicationWillBecomeActiveNotfication = NSApplication.willBecomeActiveNotification 23 | internal let applicationDidResignActiveNotification = NSApplication.didResignActiveNotification 24 | #endif 25 | import QuartzCore 26 | 27 | internal let kPulsatorAnimationKey = "pulsator" 28 | 29 | open class Pulsator: CAReplicatorLayer, CAAnimationDelegate { 30 | 31 | fileprivate let pulse = CALayer() 32 | fileprivate var animationGroup: CAAnimationGroup! 33 | fileprivate var alpha: CGFloat = 0.45 34 | 35 | override open var backgroundColor: CGColor? { 36 | didSet { 37 | pulse.backgroundColor = backgroundColor 38 | guard let backgroundColor = backgroundColor else {return} 39 | let oldAlpha = alpha 40 | alpha = backgroundColor.alpha 41 | if alpha != oldAlpha { 42 | recreate() 43 | } 44 | } 45 | } 46 | 47 | override open var repeatCount: Float { 48 | didSet { 49 | if let animationGroup = animationGroup { 50 | animationGroup.repeatCount = repeatCount 51 | } 52 | } 53 | } 54 | 55 | // MARK: - Public Properties 56 | @objc open var animationCompletionBlock:(()->Void)? 57 | 58 | /// The number of pulse. 59 | @objc open var numPulse: Int = 1 { 60 | didSet { 61 | if numPulse < 1 { 62 | numPulse = 1 63 | } 64 | instanceCount = numPulse 65 | updateInstanceDelay() 66 | } 67 | } 68 | 69 | /// The radius of pulse. 70 | @objc open var radius: CGFloat = 60 { 71 | didSet { 72 | updatePulse() 73 | } 74 | } 75 | 76 | /// The animation duration in seconds. 77 | @objc open var animationDuration: TimeInterval = 3 { 78 | didSet { 79 | updateInstanceDelay() 80 | } 81 | } 82 | 83 | /// If this property is `true`, the instanse will be automatically removed 84 | /// from the superview, when it finishes the animation. 85 | @objc open var autoRemove = false 86 | 87 | /// fromValue for radius 88 | /// It must be smaller than 1.0 89 | @objc open var fromValueForRadius: Float = 0.0 { 90 | didSet { 91 | if fromValueForRadius >= 1.0 { 92 | fromValueForRadius = 0.0 93 | } 94 | recreate() 95 | } 96 | } 97 | 98 | /// The value of this property should be ranging from @c 0 to @c 1 (exclusive). 99 | @objc open var keyTimeForHalfOpacity: Float = 0.2 { 100 | didSet { 101 | recreate() 102 | } 103 | } 104 | 105 | /// The animation interval in seconds. 106 | @objc open var pulseInterval: TimeInterval = 0 107 | 108 | /// A function describing a timing curve of the animation. 109 | @objc open var timingFunction: CAMediaTimingFunction? = CAMediaTimingFunction(name: .default) { 110 | didSet { 111 | if let animationGroup = animationGroup { 112 | animationGroup.timingFunction = timingFunction 113 | } 114 | } 115 | } 116 | 117 | /// The value of this property showed a pulse is started 118 | @objc open var isPulsating: Bool { 119 | guard let keys = pulse.animationKeys() else {return false} 120 | return keys.count > 0 121 | } 122 | 123 | /// private properties for resuming 124 | fileprivate weak var prevSuperlayer: CALayer? 125 | fileprivate var prevLayerIndex: Int? 126 | 127 | // MARK: - Initializer 128 | 129 | override public init() { 130 | super.init() 131 | 132 | setupPulse() 133 | 134 | instanceDelay = 1 135 | repeatCount = MAXFLOAT 136 | backgroundColor = Color(red: 0, green: 0.455, blue: 0.756, alpha: 0.45).cgColor 137 | 138 | 139 | 140 | NotificationCenter.default.addObserver(self, 141 | selector: #selector(save), 142 | name: applicationDidResignActiveNotification, 143 | object: nil) 144 | 145 | NotificationCenter.default.addObserver(self, 146 | selector: #selector(resume), 147 | name: applicationWillBecomeActiveNotfication, 148 | object: nil) 149 | } 150 | 151 | override public init(layer: Any) { 152 | super.init(layer: layer) 153 | } 154 | 155 | required public init?(coder aDecoder: NSCoder) { 156 | fatalError("init(coder:) has not been implemented") 157 | } 158 | 159 | open override func removeFromSuperlayer() { 160 | super.removeFromSuperlayer() 161 | stop() 162 | NotificationCenter.default.removeObserver(self) 163 | } 164 | 165 | deinit { 166 | NotificationCenter.default.removeObserver(self) 167 | } 168 | 169 | // MARK: - Private Methods 170 | 171 | fileprivate func setupPulse() { 172 | pulse.contentsScale = screenScale 173 | pulse.opacity = 0 174 | addSublayer(pulse) 175 | updatePulse() 176 | } 177 | 178 | fileprivate func setupAnimationGroup() { 179 | let scaleAnimation = CABasicAnimation(keyPath: "transform.scale.xy") 180 | scaleAnimation.fromValue = fromValueForRadius 181 | scaleAnimation.toValue = 1.0 182 | scaleAnimation.duration = animationDuration 183 | 184 | let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity") 185 | opacityAnimation.duration = animationDuration 186 | opacityAnimation.values = [alpha, alpha * 0.5, 0.0] 187 | opacityAnimation.keyTimes = [0.0, NSNumber(value: keyTimeForHalfOpacity), 1.0] 188 | 189 | animationGroup = CAAnimationGroup() 190 | animationGroup.animations = [scaleAnimation, opacityAnimation] 191 | animationGroup.duration = animationDuration + pulseInterval 192 | animationGroup.repeatCount = repeatCount 193 | if let timingFunction = timingFunction { 194 | animationGroup.timingFunction = timingFunction 195 | } 196 | animationGroup.delegate = self 197 | } 198 | 199 | fileprivate func updatePulse() { 200 | let diameter: CGFloat = radius * 2 201 | pulse.bounds = CGRect( 202 | origin: CGPoint.zero, 203 | size: CGSize(width: diameter, height: diameter)) 204 | pulse.cornerRadius = radius 205 | pulse.backgroundColor = backgroundColor 206 | } 207 | 208 | fileprivate func updateInstanceDelay() { 209 | guard numPulse >= 1 else { fatalError() } 210 | instanceDelay = (animationDuration + pulseInterval) / Double(numPulse) 211 | } 212 | 213 | fileprivate func recreate() { 214 | guard animationGroup != nil else { return } // Not need to be recreated. 215 | stop() 216 | let when = DispatchTime.now() + Double(Int64(0.2 * double_t(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 217 | DispatchQueue.main.asyncAfter(deadline: when) { () -> Void in 218 | self.start() 219 | } 220 | } 221 | 222 | // MARK: - Internal Methods 223 | 224 | @objc internal func save() { 225 | prevSuperlayer = superlayer 226 | prevLayerIndex = prevSuperlayer?.sublayers?.firstIndex(where: {$0 === self}) 227 | } 228 | 229 | @objc internal func resume() { 230 | if let prevSuperlayer = prevSuperlayer, let prevLayerIndex = prevLayerIndex { 231 | prevSuperlayer.insertSublayer(self, at: UInt32(prevLayerIndex)) 232 | } 233 | if pulse.superlayer == nil { 234 | addSublayer(pulse) 235 | } 236 | let isAnimating = pulse.animation(forKey: kPulsatorAnimationKey) != nil 237 | // if the animationGroup is not nil, it means the animation was not stopped 238 | if let animationGroup = animationGroup, !isAnimating { 239 | pulse.add(animationGroup, forKey: kPulsatorAnimationKey) 240 | } 241 | } 242 | 243 | // MARK: - Public Methods 244 | 245 | /// Start the animation. 246 | @objc open func start() { 247 | setupPulse() 248 | setupAnimationGroup() 249 | pulse.add(animationGroup, forKey: kPulsatorAnimationKey) 250 | } 251 | 252 | /// Stop the animation. 253 | @objc open func stop() { 254 | pulse.removeAllAnimations() 255 | animationGroup = nil 256 | } 257 | 258 | 259 | // MARK: - Delegate methods for CAAnimation 260 | 261 | public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { 262 | if let keys = pulse.animationKeys(), keys.count > 0 { 263 | pulse.removeAllAnimations() 264 | } 265 | pulse.removeFromSuperlayer() 266 | 267 | if autoRemove { 268 | removeFromSuperlayer() 269 | } 270 | 271 | animationCompletionBlock?() 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /PulsatorMac/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 | NSHumanReadableCopyright 22 | Copyright © 2018 Shuichi Tsutsumi. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /PulsatorMac/PulsatorMac.h: -------------------------------------------------------------------------------- 1 | // 2 | // PulsatorMac.h 3 | // PulsatorMac 4 | // 5 | // Created by Shuichi Tsutsumi on 2018/05/17. 6 | // Copyright © 2018 Shuichi Tsutsumi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for PulsatorMac. 12 | FOUNDATION_EXPORT double PulsatorMacVersionNumber; 13 | 14 | //! Project version string for PulsatorMac. 15 | FOUNDATION_EXPORT const unsigned char PulsatorMacVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /PulsatorTests/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 | -------------------------------------------------------------------------------- /PulsatorTests/PulsatorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PulsatorTests.swift 3 | // PulsatorTests 4 | // 5 | // Created by Shuichi Tsutsumi on 4/12/16. 6 | // Copyright © 2016 Shuichi Tsutsumi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Pulsator 11 | 12 | class PulsatorTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pulsator 2 | =========== 3 | 4 | [![Badge w/ Version](http://cocoapod-badges.herokuapp.com/v/Pulsator/badge.png)](http://cocoadocs.org/docsets/Pulsator) 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | [![License](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat 7 | )](http://mit-license.org) 8 | [![Twitter](https://img.shields.io/badge/twitter-@shu223-blue.svg?style=flat)](http://twitter.com/shu223) 9 | 10 | Pulse animation for iOS written with Swift. 11 | 12 | ![](demo.gif) 13 | 14 | Great For: 15 | 16 | - **Pulses of Bluetooth, BLE, beacons (iBeacon)**, etc. 17 | - Map Annotations 18 | 19 | ## Installation 20 | 21 | ### CocoaPods 22 | 23 | Add into your Podfile. 24 | 25 | ```:Podfile 26 | pod "Pulsator" 27 | ``` 28 | 29 | Then `$ pod install` 30 | 31 | ### Carthage 32 | 33 | Add into your Cartfile. 34 | 35 | ```:Cartfile 36 | github "shu223/Pulsator" 37 | ``` 38 | 39 | Then `$ carthage update` 40 | 41 | 42 | ## How to use 43 | 44 | Just **3 lines**! 45 | 46 | Initiate and add to your view's layer, then call `start`! 47 | 48 | ```swift 49 | let pulsator = Pulsator() 50 | view.layer.addSublayer(pulsator) 51 | pulsator.start() 52 | ``` 53 | 54 | 55 | ## Customizations 56 | 57 | ### Number of Pulses 58 | 59 | Use `numPulse` property. 60 | 61 | ```swift 62 | pulsator.numPulse = 3 63 | ``` 64 | 65 | ### Radius 66 | 67 | Use `radius` property. 68 | 69 | ```swift 70 | pulsator.radius = 240.0 71 | ``` 72 | 73 | ### Color 74 | 75 | Just set the `backgroundColor` property. 76 | 77 | ```swift 78 | pulsator.backgroundColor = UIColor(red: 1, green: 1, blue: 0, alpha: 1).cgColor 79 | ``` 80 | 81 | ### Animation Duration 82 | 83 | Use following properties 84 | 85 | - `animationDuration` : duration for each pulse 86 | - `pulseInterval` : interval between pulses 87 | 88 | ### Easing 89 | 90 | You can set the `timingFunction` property. 91 | 92 | 93 | ### Repeat 94 | 95 | Use `repeatCount` property. 96 | 97 | 98 | ## Demo 99 | 100 | You can try to change the `radius`, `backgroundColor` or other properties with the demo app. 101 | 102 | - Example/PulsatorDemo.xcodeproj 103 | 104 | 105 | 106 | 107 | ## macOS support 108 | 109 | Add into your Podfile, then run `pod install`. 110 | 111 | ```:Podfile 112 | platform :osx, '10.9' 113 | 114 | target 'Pulsator' do 115 | use_frameworks! 116 | pod "Pulsator" 117 | end 118 | ``` 119 | 120 | The usage is same as iOS. 121 | 122 | ```swift 123 | let pulsator = Pulsator() 124 | view.layer?.addSublayer(pulsator) 125 | pulsator.start() 126 | ``` 127 | 128 | ## Objective-C version 129 | 130 | There is an ObjC version, but it's not maintained now. 131 | 132 | - https://github.com/shu223/PulsingHalo 133 | 134 | You can use Pulsator also with Objective-C. 135 | 136 | ``` 137 | #import "Pulsator-Swift.h" 138 | ``` 139 | 140 | 141 | ## Author 142 | 143 | **Shuichi Tsutsumi** 144 | 145 | iOS freelancer in Japan. Welcome works from abroad! 146 | 147 | 148 | Support via PayPal 149 | 150 | 151 | - PAST WORKS: [My Profile Summary](https://medium.com/@shu223/my-profile-summary-f14bfc1e7099#.vdh0i7clr) 152 | - PROFILES: [LinkedIn](https://www.linkedin.com/in/shuichi-tsutsumi-525b755b/) 153 | - BLOG: [English](https://medium.com/@shu223/) / [Japanese](http://d.hatena.ne.jp/shu223/) 154 | - CONTACTS: 155 | - [Twitter](https://twitter.com/shu223) 156 | - [Facebook](https://www.facebook.com/shuichi.tsutsumi) 157 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shu223/Pulsator/86e155f98afa68140cd0ba637af1430250443e2f/demo.gif --------------------------------------------------------------------------------