├── README.md ├── WXNearbyRadar.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── xinwu.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── xinwu.xcuserdatad │ └── xcschemes │ ├── WXNearbyRadar.xcscheme │ └── xcschememanagement.plist ├── WXNearbyRadar ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── avatar.imageset │ │ ├── Contents.json │ │ └── avatar.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── GradientAngleCALayer.swift ├── Info.plist ├── RadarView.swift └── ViewController.swift └── demo.gif /README.md: -------------------------------------------------------------------------------- 1 | 2 | WXNearbyRadar 3 | ==================== 4 | This is a simple demo shows the Animation and UI of looking for nearby people. 5 | 6 | 7 | 8 | ## Requirements 9 | * Xcode 7 or higher 10 | * iOS 9 or higher 11 | 12 | ## Demo 13 | Build and run the `WXNearbyRadar` project in Xcode 14 | 15 | ## Contact 16 | 17 | - iridescent330@gmail.com 18 | 19 | ## License 20 | 21 | WXNearbyRadar is available under the MIT license. 22 | 23 | Copyright © 2016 Xin Wu. 24 | 25 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 28 | -------------------------------------------------------------------------------- /WXNearbyRadar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 683B28951C7D0E4600CD4FB6 /* GradientAngleCALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683B28941C7D0E4600CD4FB6 /* GradientAngleCALayer.swift */; }; 11 | 6866B9871C7C04E000CDD62A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6866B9861C7C04E000CDD62A /* AppDelegate.swift */; }; 12 | 6866B9891C7C04E000CDD62A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6866B9881C7C04E000CDD62A /* ViewController.swift */; }; 13 | 6866B98C1C7C04E000CDD62A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6866B98A1C7C04E000CDD62A /* Main.storyboard */; }; 14 | 6866B98E1C7C04E000CDD62A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6866B98D1C7C04E000CDD62A /* Assets.xcassets */; }; 15 | 6866B9911C7C04E000CDD62A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6866B98F1C7C04E000CDD62A /* LaunchScreen.storyboard */; }; 16 | 6866B9991C7C050800CDD62A /* RadarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6866B9981C7C050800CDD62A /* RadarView.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 683B28941C7D0E4600CD4FB6 /* GradientAngleCALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradientAngleCALayer.swift; sourceTree = ""; }; 21 | 6866B9831C7C04E000CDD62A /* WXNearbyRadar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WXNearbyRadar.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 6866B9861C7C04E000CDD62A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 6866B9881C7C04E000CDD62A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 24 | 6866B98B1C7C04E000CDD62A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | 6866B98D1C7C04E000CDD62A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 6866B9901C7C04E000CDD62A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | 6866B9921C7C04E000CDD62A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 6866B9981C7C050800CDD62A /* RadarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadarView.swift; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 6866B9801C7C04E000CDD62A /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 6866B97A1C7C04E000CDD62A = { 43 | isa = PBXGroup; 44 | children = ( 45 | 6866B9851C7C04E000CDD62A /* WXNearbyRadar */, 46 | 6866B9841C7C04E000CDD62A /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | 6866B9841C7C04E000CDD62A /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 6866B9831C7C04E000CDD62A /* WXNearbyRadar.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | 6866B9851C7C04E000CDD62A /* WXNearbyRadar */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 6866B9861C7C04E000CDD62A /* AppDelegate.swift */, 62 | 6866B9881C7C04E000CDD62A /* ViewController.swift */, 63 | 6866B9981C7C050800CDD62A /* RadarView.swift */, 64 | 683B28941C7D0E4600CD4FB6 /* GradientAngleCALayer.swift */, 65 | 6866B98A1C7C04E000CDD62A /* Main.storyboard */, 66 | 6866B98D1C7C04E000CDD62A /* Assets.xcassets */, 67 | 6866B98F1C7C04E000CDD62A /* LaunchScreen.storyboard */, 68 | 6866B9921C7C04E000CDD62A /* Info.plist */, 69 | ); 70 | path = WXNearbyRadar; 71 | sourceTree = ""; 72 | }; 73 | /* End PBXGroup section */ 74 | 75 | /* Begin PBXNativeTarget section */ 76 | 6866B9821C7C04E000CDD62A /* WXNearbyRadar */ = { 77 | isa = PBXNativeTarget; 78 | buildConfigurationList = 6866B9951C7C04E000CDD62A /* Build configuration list for PBXNativeTarget "WXNearbyRadar" */; 79 | buildPhases = ( 80 | 6866B97F1C7C04E000CDD62A /* Sources */, 81 | 6866B9801C7C04E000CDD62A /* Frameworks */, 82 | 6866B9811C7C04E000CDD62A /* Resources */, 83 | ); 84 | buildRules = ( 85 | ); 86 | dependencies = ( 87 | ); 88 | name = WXNearbyRadar; 89 | productName = WXNearbyRadar; 90 | productReference = 6866B9831C7C04E000CDD62A /* WXNearbyRadar.app */; 91 | productType = "com.apple.product-type.application"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | 6866B97B1C7C04E000CDD62A /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | LastSwiftUpdateCheck = 0720; 100 | LastUpgradeCheck = 0720; 101 | ORGANIZATIONNAME = "Xin Wu"; 102 | TargetAttributes = { 103 | 6866B9821C7C04E000CDD62A = { 104 | CreatedOnToolsVersion = 7.2.1; 105 | SystemCapabilities = { 106 | com.apple.BackgroundModes = { 107 | enabled = 1; 108 | }; 109 | }; 110 | }; 111 | }; 112 | }; 113 | buildConfigurationList = 6866B97E1C7C04E000CDD62A /* Build configuration list for PBXProject "WXNearbyRadar" */; 114 | compatibilityVersion = "Xcode 3.2"; 115 | developmentRegion = English; 116 | hasScannedForEncodings = 0; 117 | knownRegions = ( 118 | en, 119 | Base, 120 | ); 121 | mainGroup = 6866B97A1C7C04E000CDD62A; 122 | productRefGroup = 6866B9841C7C04E000CDD62A /* Products */; 123 | projectDirPath = ""; 124 | projectRoot = ""; 125 | targets = ( 126 | 6866B9821C7C04E000CDD62A /* WXNearbyRadar */, 127 | ); 128 | }; 129 | /* End PBXProject section */ 130 | 131 | /* Begin PBXResourcesBuildPhase section */ 132 | 6866B9811C7C04E000CDD62A /* Resources */ = { 133 | isa = PBXResourcesBuildPhase; 134 | buildActionMask = 2147483647; 135 | files = ( 136 | 6866B9911C7C04E000CDD62A /* LaunchScreen.storyboard in Resources */, 137 | 6866B98E1C7C04E000CDD62A /* Assets.xcassets in Resources */, 138 | 6866B98C1C7C04E000CDD62A /* Main.storyboard in Resources */, 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXResourcesBuildPhase section */ 143 | 144 | /* Begin PBXSourcesBuildPhase section */ 145 | 6866B97F1C7C04E000CDD62A /* Sources */ = { 146 | isa = PBXSourcesBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | 6866B9891C7C04E000CDD62A /* ViewController.swift in Sources */, 150 | 6866B9991C7C050800CDD62A /* RadarView.swift in Sources */, 151 | 6866B9871C7C04E000CDD62A /* AppDelegate.swift in Sources */, 152 | 683B28951C7D0E4600CD4FB6 /* GradientAngleCALayer.swift in Sources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXSourcesBuildPhase section */ 157 | 158 | /* Begin PBXVariantGroup section */ 159 | 6866B98A1C7C04E000CDD62A /* Main.storyboard */ = { 160 | isa = PBXVariantGroup; 161 | children = ( 162 | 6866B98B1C7C04E000CDD62A /* Base */, 163 | ); 164 | name = Main.storyboard; 165 | sourceTree = ""; 166 | }; 167 | 6866B98F1C7C04E000CDD62A /* LaunchScreen.storyboard */ = { 168 | isa = PBXVariantGroup; 169 | children = ( 170 | 6866B9901C7C04E000CDD62A /* Base */, 171 | ); 172 | name = LaunchScreen.storyboard; 173 | sourceTree = ""; 174 | }; 175 | /* End PBXVariantGroup section */ 176 | 177 | /* Begin XCBuildConfiguration section */ 178 | 6866B9931C7C04E000CDD62A /* Debug */ = { 179 | isa = XCBuildConfiguration; 180 | buildSettings = { 181 | ALWAYS_SEARCH_USER_PATHS = NO; 182 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 183 | CLANG_CXX_LIBRARY = "libc++"; 184 | CLANG_ENABLE_MODULES = YES; 185 | CLANG_ENABLE_OBJC_ARC = YES; 186 | CLANG_WARN_BOOL_CONVERSION = YES; 187 | CLANG_WARN_CONSTANT_CONVERSION = YES; 188 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 189 | CLANG_WARN_EMPTY_BODY = YES; 190 | CLANG_WARN_ENUM_CONVERSION = YES; 191 | CLANG_WARN_INT_CONVERSION = YES; 192 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 193 | CLANG_WARN_UNREACHABLE_CODE = YES; 194 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 195 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 196 | COPY_PHASE_STRIP = NO; 197 | DEBUG_INFORMATION_FORMAT = dwarf; 198 | ENABLE_STRICT_OBJC_MSGSEND = YES; 199 | ENABLE_TESTABILITY = YES; 200 | GCC_C_LANGUAGE_STANDARD = gnu99; 201 | GCC_DYNAMIC_NO_PIC = NO; 202 | GCC_NO_COMMON_BLOCKS = YES; 203 | GCC_OPTIMIZATION_LEVEL = 0; 204 | GCC_PREPROCESSOR_DEFINITIONS = ( 205 | "DEBUG=1", 206 | "$(inherited)", 207 | ); 208 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 209 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 210 | GCC_WARN_UNDECLARED_SELECTOR = YES; 211 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 212 | GCC_WARN_UNUSED_FUNCTION = YES; 213 | GCC_WARN_UNUSED_VARIABLE = YES; 214 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 215 | MTL_ENABLE_DEBUG_INFO = YES; 216 | ONLY_ACTIVE_ARCH = YES; 217 | SDKROOT = iphoneos; 218 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 219 | }; 220 | name = Debug; 221 | }; 222 | 6866B9941C7C04E000CDD62A /* Release */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | ALWAYS_SEARCH_USER_PATHS = NO; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_WARN_BOOL_CONVERSION = YES; 231 | CLANG_WARN_CONSTANT_CONVERSION = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INT_CONVERSION = YES; 236 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 237 | CLANG_WARN_UNREACHABLE_CODE = YES; 238 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 239 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 240 | COPY_PHASE_STRIP = NO; 241 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 242 | ENABLE_NS_ASSERTIONS = NO; 243 | ENABLE_STRICT_OBJC_MSGSEND = YES; 244 | GCC_C_LANGUAGE_STANDARD = gnu99; 245 | GCC_NO_COMMON_BLOCKS = YES; 246 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 247 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 248 | GCC_WARN_UNDECLARED_SELECTOR = YES; 249 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 250 | GCC_WARN_UNUSED_FUNCTION = YES; 251 | GCC_WARN_UNUSED_VARIABLE = YES; 252 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 253 | MTL_ENABLE_DEBUG_INFO = NO; 254 | SDKROOT = iphoneos; 255 | VALIDATE_PRODUCT = YES; 256 | }; 257 | name = Release; 258 | }; 259 | 6866B9961C7C04E000CDD62A /* Debug */ = { 260 | isa = XCBuildConfiguration; 261 | buildSettings = { 262 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 263 | CLANG_ENABLE_MODULES = YES; 264 | INFOPLIST_FILE = WXNearbyRadar/Info.plist; 265 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 266 | PRODUCT_BUNDLE_IDENTIFIER = XinWu.WXNearbyRadar; 267 | PRODUCT_NAME = "$(TARGET_NAME)"; 268 | SWIFT_INSTALL_OBJC_HEADER = NO; 269 | SWIFT_OBJC_BRIDGING_HEADER = ""; 270 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 271 | }; 272 | name = Debug; 273 | }; 274 | 6866B9971C7C04E000CDD62A /* Release */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 278 | CLANG_ENABLE_MODULES = YES; 279 | INFOPLIST_FILE = WXNearbyRadar/Info.plist; 280 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 281 | PRODUCT_BUNDLE_IDENTIFIER = XinWu.WXNearbyRadar; 282 | PRODUCT_NAME = "$(TARGET_NAME)"; 283 | SWIFT_INSTALL_OBJC_HEADER = NO; 284 | SWIFT_OBJC_BRIDGING_HEADER = ""; 285 | }; 286 | name = Release; 287 | }; 288 | /* End XCBuildConfiguration section */ 289 | 290 | /* Begin XCConfigurationList section */ 291 | 6866B97E1C7C04E000CDD62A /* Build configuration list for PBXProject "WXNearbyRadar" */ = { 292 | isa = XCConfigurationList; 293 | buildConfigurations = ( 294 | 6866B9931C7C04E000CDD62A /* Debug */, 295 | 6866B9941C7C04E000CDD62A /* Release */, 296 | ); 297 | defaultConfigurationIsVisible = 0; 298 | defaultConfigurationName = Release; 299 | }; 300 | 6866B9951C7C04E000CDD62A /* Build configuration list for PBXNativeTarget "WXNearbyRadar" */ = { 301 | isa = XCConfigurationList; 302 | buildConfigurations = ( 303 | 6866B9961C7C04E000CDD62A /* Debug */, 304 | 6866B9971C7C04E000CDD62A /* Release */, 305 | ); 306 | defaultConfigurationIsVisible = 0; 307 | defaultConfigurationName = Release; 308 | }; 309 | /* End XCConfigurationList section */ 310 | }; 311 | rootObject = 6866B97B1C7C04E000CDD62A /* Project object */; 312 | } 313 | -------------------------------------------------------------------------------- /WXNearbyRadar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WXNearbyRadar.xcodeproj/project.xcworkspace/xcuserdata/xinwu.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx0165927473/WXNearbyRadar/d954cf6455d0b9769dc9c515459cafbd503a48f8/WXNearbyRadar.xcodeproj/project.xcworkspace/xcuserdata/xinwu.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /WXNearbyRadar.xcodeproj/xcuserdata/xinwu.xcuserdatad/xcschemes/WXNearbyRadar.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 | -------------------------------------------------------------------------------- /WXNearbyRadar.xcodeproj/xcuserdata/xinwu.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | WXNearbyRadar.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 6866B9821C7C04E000CDD62A 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /WXNearbyRadar/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // WXNearbyRadar 4 | // 5 | // Created by Xin Wu on 2/22/16. 6 | // Copyright © 2016 Xin Wu. 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: [NSObject: AnyObject]?) -> 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 | -------------------------------------------------------------------------------- /WXNearbyRadar/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 | } -------------------------------------------------------------------------------- /WXNearbyRadar/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /WXNearbyRadar/Assets.xcassets/avatar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "avatar.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 | } -------------------------------------------------------------------------------- /WXNearbyRadar/Assets.xcassets/avatar.imageset/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx0165927473/WXNearbyRadar/d954cf6455d0b9769dc9c515459cafbd503a48f8/WXNearbyRadar/Assets.xcassets/avatar.imageset/avatar.png -------------------------------------------------------------------------------- /WXNearbyRadar/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 | -------------------------------------------------------------------------------- /WXNearbyRadar/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /WXNearbyRadar/GradientAngleCALayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GradientAngleCALayer.swift 3 | // WXNearbyRadar 4 | // 5 | // Created by Xin Wu on 2/23/16. 6 | // Copyright © 2016 Xin Wu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class GradientAngleCALayer: CALayer { 12 | 13 | private struct Constants { 14 | static let MaxAngle = 2 * M_PI 15 | static let MaxHue = 255.0 16 | } 17 | 18 | private struct Transition { 19 | let fromLocation: Double 20 | let toLocation: Double 21 | let fromColor: UIColor 22 | let toColor: UIColor 23 | 24 | func colorForPercent(percent: Double) -> UIColor { 25 | let normalizedPercent = percent.convertFromRange(min: fromLocation, max: toLocation, toRangeMin: 0.0, max: 1.0) 26 | return UIColor.lerp(from: fromColor.rgba, to: toColor.rgba, percent: CGFloat(normalizedPercent)) 27 | } 28 | } 29 | 30 | // MARK: - Properties 31 | 32 | /// The array of UIColor objects defining the color of each gradient stop. 33 | /// Defaults to empty array. Animatable. 34 | 35 | internal var colors = [UIColor]() { didSet { setNeedsDisplay() } } 36 | 37 | /// The array of Double values defining the location of each 38 | /// gradient stop as a value in the range [0,1]. The values must be 39 | /// monotonically increasing. If empty array is given, the stops are 40 | /// assumed to spread uniformly across the [0,1] range. 41 | /// Defaults to nil. Animatable. 42 | 43 | internal var locations = [Double]() { didSet { setNeedsDisplay() } } 44 | 45 | private var transitions = [Transition]() 46 | 47 | internal override func drawInContext(ctx: CGContext) { 48 | UIGraphicsPushContext(ctx) 49 | drawRect(CGContextGetClipBoundingBox(ctx)) 50 | UIGraphicsPopContext() 51 | } 52 | 53 | private func drawRect(rect: CGRect) { 54 | loadTransitions() 55 | 56 | let center = CGPoint(x: rect.midX, y: rect.midY) 57 | let longerSide = max(rect.width, rect.height) 58 | let radius = Double(longerSide) * M_SQRT2 59 | var angle = 0.0 60 | let step = M_PI_2 / radius 61 | 62 | while angle <= Constants.MaxAngle { 63 | let pointX = radius * cos(angle) + Double(center.x) 64 | let pointY = radius * sin(angle) + Double(center.y) 65 | let startPoint = CGPoint(x: pointX, y: pointY) 66 | 67 | let line = UIBezierPath() 68 | line.moveToPoint(startPoint) 69 | line.addLineToPoint(center) 70 | 71 | colorForAngle(angle).setStroke() 72 | line.stroke() 73 | 74 | angle += step 75 | } 76 | } 77 | 78 | private func colorForAngle(angle: Double) -> UIColor { 79 | let percent = angle.convertFromRangeZeroToMax(Constants.MaxAngle, toRangeZeroToMax: 1.0) 80 | guard let transition = transitionForPercent(percent) else { return spectrumColorForAngle(angle) } 81 | return transition.colorForPercent(percent) 82 | } 83 | 84 | private func spectrumColorForAngle(angle: Double) -> UIColor { 85 | let hue = angle.convertFromRangeZeroToMax(Constants.MaxAngle, toRangeZeroToMax: Constants.MaxHue) 86 | return UIColor(hue: CGFloat(hue / Constants.MaxHue), saturation: 1.0, brightness: 1.0, alpha: 1.0) 87 | } 88 | 89 | private func loadTransitions() { 90 | transitions.removeAll() 91 | 92 | if colors.count > 1 { 93 | let transitionsCount = colors.count - 1 94 | let locationStep = 1.0 / Double(transitionsCount) 95 | 96 | for i in 0 ..< transitionsCount { 97 | let fromLocation, toLocation: Double 98 | let fromColor, toColor: UIColor 99 | 100 | if locations.count == colors.count { 101 | fromLocation = locations[i] 102 | toLocation = locations[i + 1] 103 | } else { 104 | fromLocation = locationStep * Double(i) 105 | toLocation = locationStep * Double(i + 1) 106 | } 107 | 108 | fromColor = colors[i] 109 | toColor = colors[i + 1] 110 | 111 | let transition = Transition(fromLocation: fromLocation, toLocation: toLocation, fromColor: fromColor, toColor: toColor) 112 | transitions.append(transition) 113 | } 114 | } 115 | } 116 | 117 | private func transitionForPercent(percent: Double) -> Transition? { 118 | let filtered = transitions.filter { percent >= $0.fromLocation && percent < $0.toLocation } 119 | let defaultTransition = percent <= 0.5 ? transitions.first : transitions.last 120 | return filtered.first ?? defaultTransition 121 | } 122 | 123 | } 124 | 125 | // MARK: - Extensions 126 | 127 | private extension Double { 128 | 129 | func convertFromRange(min oldMin: Double, max oldMax: Double, toRangeMin newMin: Double, max newMax: Double) -> Double { 130 | let oldRange, newRange, newValue: Double 131 | oldRange = (oldMax - oldMin) 132 | if (oldRange == 0.0) { 133 | newValue = newMin 134 | } else { 135 | newRange = (newMax - newMin) 136 | newValue = (((self - oldMin) * newRange) / oldRange) + newMin 137 | } 138 | return newValue 139 | } 140 | 141 | func convertFromRangeZeroToMax(currentMaxValue: Double, toRangeZeroToMax newMaxValue: Double) -> Double { 142 | return ((self * newMaxValue) / currentMaxValue) 143 | } 144 | 145 | } 146 | 147 | private extension UIColor { 148 | 149 | struct RGBA { 150 | var red: CGFloat = 0.0 151 | var green: CGFloat = 0.0 152 | var blue: CGFloat = 0.0 153 | var alpha: CGFloat = 0.0 154 | 155 | init(color: UIColor) { 156 | color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) 157 | } 158 | } 159 | 160 | var rgba: RGBA { 161 | return RGBA(color: self) 162 | } 163 | 164 | class func lerp(from from: UIColor.RGBA, to: UIColor.RGBA, percent: CGFloat) -> UIColor { 165 | let red = from.red + percent * (to.red - from.red) 166 | let green = from.green + percent * (to.green - from.green) 167 | let blue = from.blue + percent * (to.blue - from.blue) 168 | let alpha = from.alpha + percent * (to.alpha - from.alpha) 169 | return UIColor(red: red, green: green, blue: blue, alpha: alpha) 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /WXNearbyRadar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSLocationWhenInUsageDescription 6 | 7 | NSLocationAlwaysUsageDescription 8 | 9 | CFBundleDevelopmentRegion 10 | en 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1 27 | LSRequiresIPhoneOS 28 | 29 | UIBackgroundModes 30 | 31 | location 32 | 33 | UILaunchStoryboardName 34 | LaunchScreen 35 | UIMainStoryboardFile 36 | Main 37 | UIRequiredDeviceCapabilities 38 | 39 | armv7 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /WXNearbyRadar/RadarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Radar.swift 3 | // RadarAnim 4 | // 5 | // Created by Xin Wu on 2/21/16. 6 | // Copyright © 2016 Xin Wu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RadarView: UIView { 12 | 13 | /* 14 | // Only override drawRect: if you perform custom drawing. 15 | // An empty implementation adversely affects performance during animation. 16 | override func drawRect(rect: CGRect) { 17 | // Drawing code 18 | } 19 | */ 20 | 21 | override class func layerClass() -> AnyClass { 22 | return GradientAngleCALayer.classForCoder() 23 | } 24 | 25 | override init(frame: CGRect) { 26 | super.init(frame: frame) 27 | 28 | self.backgroundColor = UIColor.clearColor() 29 | self.opaque = false 30 | // Change the color of scan animation 31 | let colors = [UIColor(red: 1, green: 1, blue: 1, alpha: 0), 32 | UIColor(red: 1, green: 1, blue: 1, alpha: 0), 33 | UIColor(red: 1, green: 1, blue: 1, alpha: 0), 34 | UIColor(red: 192/255, green: 57/255, blue: 43/255, alpha: 1.0)] 35 | 36 | // AngleGradientLayer by Pavel Ivashkov 37 | let l = self.layer as! GradientAngleCALayer 38 | l.colors = colors 39 | 40 | // Since our gradient layer is built as an image, 41 | // we need to scale it to match the display of the device. 42 | l.contentsScale = UIScreen.mainScreen().scale 43 | 44 | l.cornerRadius = CGRectGetWidth(self.bounds) / 2 45 | 46 | self.clipsToBounds = true 47 | self.userInteractionEnabled = false 48 | 49 | } 50 | 51 | required init?(coder aDecoder: NSCoder) { 52 | fatalError("init(coder:) has not been implemented") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /WXNearbyRadar/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // WXNearbyRadar 4 | // 5 | // Created by Xin Wu on 2/22/16. 6 | // Copyright © 2016 Xin Wu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MapKit 11 | 12 | class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate { 13 | 14 | 15 | let locManager = CLLocationManager() 16 | let mapView = MKMapView() 17 | var radarView : RadarView! 18 | var userAvatarImageView : UIImageView! 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | // Location Manager Init 24 | locManager.delegate = self 25 | locManager.desiredAccuracy = kCLLocationAccuracyBest 26 | locManager.requestAlwaysAuthorization() 27 | locManager.startUpdatingLocation() 28 | 29 | // Map View Init 30 | mapView.frame = CGRectMake(10, 100, self.view.frame.width - 20, self.view.frame.width - 20) 31 | mapView.delegate = self 32 | mapView.userInteractionEnabled = false 33 | mapView.zoomEnabled = false 34 | mapView.layer.cornerRadius = mapView.frame.height / 2 35 | self.view.addSubview(mapView) 36 | 37 | // Radar View Init 38 | radarView = RadarView.init(frame: CGRectMake(0, 0, mapView.frame.size.width, mapView.frame.size.height)) 39 | radarView.layer.contentsScale = UIScreen.mainScreen().scale 40 | radarView.alpha = 0.8 41 | mapView.addSubview(radarView) 42 | 43 | // User Avatar Init 44 | userAvatarImageView = UIImageView(image: UIImage(named: "avatar")) 45 | userAvatarImageView.backgroundColor = UIColor.grayColor() 46 | userAvatarImageView.frame.size = CGSizeMake(mapView.frame.width / 3.5, mapView.frame.height / 3.5) 47 | userAvatarImageView.frame.origin = CGPointMake(mapView.center.x - userAvatarImageView.frame.width / 2, mapView.center.y - userAvatarImageView.frame.height / 2) 48 | userAvatarImageView.layer.cornerRadius = userAvatarImageView.bounds.width / 2 49 | userAvatarImageView.layer.borderWidth = 3 50 | userAvatarImageView.layer.borderColor = UIColor.whiteColor().CGColor 51 | userAvatarImageView.clipsToBounds = true 52 | self.view.addSubview(userAvatarImageView) 53 | 54 | // Add ripple animation to avatar 55 | let tapGestureRecognizer = UITapGestureRecognizer(target:self, action:Selector("tapAvatarImageAction:")) 56 | userAvatarImageView.userInteractionEnabled = true 57 | userAvatarImageView.addGestureRecognizer(tapGestureRecognizer) 58 | 59 | // Start spinning the radar and ripple anim 60 | spinRadar() 61 | NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: Selector("tapAvatarImageAction:"), userInfo: nil, repeats: true) 62 | 63 | } 64 | 65 | override func didReceiveMemoryWarning() { 66 | super.didReceiveMemoryWarning() 67 | // Dispose of any resources that can be recreated. 68 | } 69 | 70 | // MARK: Radar Animation 71 | func spinRadar() { 72 | let spin = CABasicAnimation(keyPath: "transform.rotation") 73 | // Change spinning speed here 74 | spin.duration = 2 75 | spin.toValue = NSNumber(double: M_PI) 76 | spin.cumulative = true 77 | spin.removedOnCompletion = false 78 | spin.repeatCount = MAXFLOAT; 79 | radarView.layer.addAnimation(spin, forKey: "spinRadarView") 80 | } 81 | 82 | // MARK: Location Delegate Methods 83 | func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 84 | let location = locations.last 85 | 86 | let center = CLLocationCoordinate2D(latitude: (location?.coordinate.latitude)!, longitude: (location?.coordinate.longitude)!) 87 | 88 | // Change how detail you want the map to show (smaller means more detailed) 89 | let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)) 90 | 91 | mapView.setRegion(region, animated: false) 92 | locManager.stopUpdatingLocation() 93 | } 94 | 95 | func locationManager(manager: CLLocationManager, didFailWithError error: NSError) { 96 | print("Error: " + error.localizedDescription) 97 | } 98 | 99 | // MARK: Ripple Animation 100 | func tapAvatarImageAction(img: AnyObject) { 101 | let stroke = UIColor(red: 192/255, green: 57/255, blue: 43/255, alpha: 1.0) 102 | let pathFrame = CGRectMake(-CGRectGetMidX(userAvatarImageView.bounds), -CGRectGetMidY(userAvatarImageView.bounds), userAvatarImageView.bounds.size.width, userAvatarImageView.bounds.size.height) 103 | let path = UIBezierPath(roundedRect: pathFrame, cornerRadius: mapView.layer.cornerRadius) 104 | let shapePosition = mapView.convertPoint(mapView.center, fromView: nil) 105 | 106 | let circleShape = CAShapeLayer() 107 | circleShape.path = path.CGPath 108 | circleShape.position = shapePosition 109 | circleShape.fillColor = UIColor.clearColor().CGColor 110 | circleShape.opacity = 0 111 | circleShape.strokeColor = stroke.CGColor 112 | circleShape.lineWidth = 1.3 113 | mapView.layer.addSublayer(circleShape) 114 | 115 | CATransaction.begin() 116 | CATransaction.setCompletionBlock { () -> Void in 117 | circleShape.removeFromSuperlayer() 118 | } 119 | 120 | let scaleAnimation = CABasicAnimation(keyPath: "transform.scale") 121 | scaleAnimation.fromValue = NSValue(CATransform3D: CATransform3DIdentity) 122 | scaleAnimation.toValue = NSValue(CATransform3D: CATransform3DMakeScale(3.5, 3.5, 1)) 123 | 124 | let alphaAnimation = CABasicAnimation(keyPath: "opacity") 125 | alphaAnimation.fromValue = 1 126 | alphaAnimation.toValue = 0 127 | 128 | let animation = CAAnimationGroup() 129 | animation.animations = [scaleAnimation, alphaAnimation] 130 | animation.duration = 1 131 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) 132 | circleShape.addAnimation(animation, forKey: nil) 133 | 134 | CATransaction.commit() 135 | } 136 | 137 | } 138 | 139 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wx0165927473/WXNearbyRadar/d954cf6455d0b9769dc9c515459cafbd503a48f8/demo.gif --------------------------------------------------------------------------------