├── BluetoothDemo ├── .DS_Store ├── SVProgressHUD │ ├── SVProgressHUD.bundle │ │ ├── info.png │ │ ├── error.png │ │ ├── info@2x.png │ │ ├── info@3x.png │ │ ├── success.png │ │ ├── error@2x.png │ │ ├── error@3x.png │ │ ├── angle-mask.png │ │ ├── success@2x.png │ │ ├── success@3x.png │ │ ├── angle-mask@2x.png │ │ └── angle-mask@3x.png │ ├── SVProgressHUD-Prefix.pch │ ├── SVRadialGradientLayer.h │ ├── SVIndefiniteAnimatedView.h │ ├── SVProgressAnimatedView.h │ ├── SVRadialGradientLayer.m │ ├── SVProgressAnimatedView.m │ ├── SVIndefiniteAnimatedView.m │ ├── SVProgressHUD.h │ └── SVProgressHUD.m ├── BluetoothDemo.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj ├── BluetoothDemo │ ├── ViewController.h │ ├── AppDelegate.h │ ├── main.m │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── AppDelegate.m │ └── ViewController.m ├── BluetoothDemoTests │ ├── Info.plist │ └── BluetoothDemoTests.m └── BluetoothDemoUITests │ ├── Info.plist │ └── BluetoothDemoUITests.m ├── README.md ├── LICENSE └── .gitignore /BluetoothDemo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BluetoothDemo 2 | 清晰、简单、详细的搞定iOS蓝牙开发 3 | 4 | ### 说明 5 | 文件很简单,就一个控制器文件,但很详细,请仔细看每一个说明和注释,用不了多长时间 6 | 7 | 不要忘记点击右上角小星星star~ 8 | -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/info.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/error.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/info@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/info@2x.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/info@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/info@3x.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/success.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/error@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/error@2x.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/error@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/error@3x.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/angle-mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/angle-mask.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/success@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/success@2x.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/success@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/success@3x.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/angle-mask@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/angle-mask@2x.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/angle-mask@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alenpaulkevin/BluetoothDemo/HEAD/BluetoothDemo/SVProgressHUD/SVProgressHUD.bundle/angle-mask@3x.png -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'SVProgressHUD' target in the 'SVProgressHUD' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // BluetoothDemo 4 | // 5 | // Created by XuanCS on 2017/3/21. 6 | // Copyright © 2017年 XuanCS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // BluetoothDemo 4 | // 5 | // Created by XuanCS on 2017/3/21. 6 | // Copyright © 2017年 XuanCS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVRadialGradientLayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // SVRadialGradientLayer.h 3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD 4 | // 5 | // Copyright (c) 2014-2016 Tobias Tiemerding. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface SVRadialGradientLayer : CALayer 11 | 12 | @property (nonatomic) CGPoint gradientCenter; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // BluetoothDemo 4 | // 5 | // Created by XuanCS on 2017/3/21. 6 | // Copyright © 2017年 XuanCS. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVIndefiniteAnimatedView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SVIndefiniteAnimatedView.h 3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD 4 | // 5 | // Copyright (c) 2014-2016 Guillaume Campagna. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface SVIndefiniteAnimatedView : UIView 11 | 12 | @property (nonatomic, assign) CGFloat strokeThickness; 13 | @property (nonatomic, assign) CGFloat radius; 14 | @property (nonatomic, strong) UIColor *strokeColor; 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressAnimatedView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SVProgressAnimatedView.h 3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD 4 | // 5 | // Copyright (c) 2016 Tobias Tiemerding. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface SVProgressAnimatedView : UIView 11 | 12 | @property (nonatomic, assign) CGFloat radius; 13 | @property (nonatomic, assign) CGFloat strokeThickness; 14 | @property (nonatomic, strong) UIColor *strokeColor; 15 | @property (nonatomic, assign) CGFloat strokeEnd; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemoTests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemoUITests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVRadialGradientLayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // SVRadialGradientLayer.m 3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD 4 | // 5 | // Copyright (c) 2014-2016 Tobias Tiemerding. All rights reserved. 6 | // 7 | 8 | #import "SVRadialGradientLayer.h" 9 | 10 | @implementation SVRadialGradientLayer 11 | 12 | - (void)drawInContext:(CGContextRef)context { 13 | size_t locationsCount = 2; 14 | CGFloat locations[2] = {0.0f, 1.0f}; 15 | CGFloat colors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f}; 16 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 17 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, locationsCount); 18 | CGColorSpaceRelease(colorSpace); 19 | 20 | float radius = MIN(self.bounds.size.width , self.bounds.size.height); 21 | CGContextDrawRadialGradient (context, gradient, self.gradientCenter, 0, self.gradientCenter, radius, kCGGradientDrawsAfterEndLocation); 22 | CGGradientRelease(gradient); 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemoTests/BluetoothDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // BluetoothDemoTests.m 3 | // BluetoothDemoTests 4 | // 5 | // Created by XuanCS on 2017/3/21. 6 | // Copyright © 2017年 XuanCS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BluetoothDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation BluetoothDemoTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 alenpaulkevin 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 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemoUITests/BluetoothDemoUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // BluetoothDemoUITests.m 3 | // BluetoothDemoUITests 4 | // 5 | // Created by XuanCS on 2017/3/21. 6 | // Copyright © 2017年 XuanCS. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BluetoothDemoUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation BluetoothDemoUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo/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 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /.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 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | #Code Injection 56 | # 57 | # After new code Injection tools there's a generated folder /iOSInjectionProject 58 | # https://github.com/johnno1962/injectionforxcode 59 | 60 | iOSInjectionProject/ 61 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIBackgroundModes 24 | 25 | bluetooth-central 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo/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 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // BluetoothDemo 4 | // 5 | // Created by XuanCS on 2017/3/21. 6 | // Copyright © 2017年 XuanCS. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressAnimatedView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SVProgressAnimatedView.m 3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD 4 | // 5 | // Copyright (c) 2016 Tobias Tiemerding. All rights reserved. 6 | // 7 | 8 | #import "SVProgressAnimatedView.h" 9 | 10 | @interface SVProgressAnimatedView () 11 | 12 | @property (nonatomic, strong) CAShapeLayer *ringAnimatedLayer; 13 | 14 | @end 15 | 16 | @implementation SVProgressAnimatedView 17 | 18 | - (void)willMoveToSuperview:(UIView*)newSuperview { 19 | if (newSuperview) { 20 | [self layoutAnimatedLayer]; 21 | } else { 22 | [_ringAnimatedLayer removeFromSuperlayer]; 23 | _ringAnimatedLayer = nil; 24 | } 25 | } 26 | 27 | - (void)layoutAnimatedLayer { 28 | CALayer *layer = self.ringAnimatedLayer; 29 | [self.layer addSublayer:layer]; 30 | 31 | CGFloat widthDiff = CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds); 32 | CGFloat heightDiff = CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds); 33 | layer.position = CGPointMake(CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds) / 2 - widthDiff / 2, CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds) / 2 - heightDiff / 2); 34 | } 35 | 36 | - (CAShapeLayer*)ringAnimatedLayer { 37 | if(!_ringAnimatedLayer) { 38 | CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5); 39 | UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat)-M_PI_2 endAngle:(CGFloat) (M_PI + M_PI_2) clockwise:YES]; 40 | 41 | _ringAnimatedLayer = [CAShapeLayer layer]; 42 | _ringAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale]; 43 | _ringAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2); 44 | _ringAnimatedLayer.fillColor = [UIColor clearColor].CGColor; 45 | _ringAnimatedLayer.strokeColor = self.strokeColor.CGColor; 46 | _ringAnimatedLayer.lineWidth = self.strokeThickness; 47 | _ringAnimatedLayer.lineCap = kCALineCapRound; 48 | _ringAnimatedLayer.lineJoin = kCALineJoinBevel; 49 | _ringAnimatedLayer.path = smoothedPath.CGPath; 50 | } 51 | return _ringAnimatedLayer; 52 | } 53 | 54 | - (void)setFrame:(CGRect)frame { 55 | if(!CGRectEqualToRect(frame, super.frame)) { 56 | [super setFrame:frame]; 57 | 58 | if(self.superview) { 59 | [self layoutAnimatedLayer]; 60 | } 61 | } 62 | 63 | } 64 | 65 | - (void)setRadius:(CGFloat)radius { 66 | if(radius != _radius) { 67 | _radius = radius; 68 | 69 | [_ringAnimatedLayer removeFromSuperlayer]; 70 | _ringAnimatedLayer = nil; 71 | 72 | if(self.superview) { 73 | [self layoutAnimatedLayer]; 74 | } 75 | } 76 | } 77 | 78 | - (void)setStrokeColor:(UIColor*)strokeColor { 79 | _strokeColor = strokeColor; 80 | _ringAnimatedLayer.strokeColor = strokeColor.CGColor; 81 | } 82 | 83 | - (void)setStrokeThickness:(CGFloat)strokeThickness { 84 | _strokeThickness = strokeThickness; 85 | _ringAnimatedLayer.lineWidth = _strokeThickness; 86 | } 87 | 88 | - (void)setStrokeEnd:(CGFloat)strokeEnd { 89 | _strokeEnd = strokeEnd; 90 | _ringAnimatedLayer.strokeEnd = _strokeEnd; 91 | } 92 | 93 | 94 | - (CGSize)sizeThatFits:(CGSize)size { 95 | return CGSizeMake((self.radius+self.strokeThickness/2+5)*2, (self.radius+self.strokeThickness/2+5)*2); 96 | } 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVIndefiniteAnimatedView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SVIndefiniteAnimatedView.m 3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD 4 | // 5 | // Copyright (c) 2014-2016 Guillaume Campagna. All rights reserved. 6 | // 7 | 8 | #import "SVIndefiniteAnimatedView.h" 9 | #import "SVProgressHUD.h" 10 | 11 | @interface SVIndefiniteAnimatedView () 12 | 13 | @property (nonatomic, strong) CAShapeLayer *indefiniteAnimatedLayer; 14 | 15 | @end 16 | 17 | @implementation SVIndefiniteAnimatedView 18 | 19 | - (void)willMoveToSuperview:(UIView*)newSuperview { 20 | if (newSuperview) { 21 | [self layoutAnimatedLayer]; 22 | } else { 23 | [_indefiniteAnimatedLayer removeFromSuperlayer]; 24 | _indefiniteAnimatedLayer = nil; 25 | } 26 | } 27 | 28 | - (void)layoutAnimatedLayer { 29 | CALayer *layer = self.indefiniteAnimatedLayer; 30 | [self.layer addSublayer:layer]; 31 | 32 | CGFloat widthDiff = CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds); 33 | CGFloat heightDiff = CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds); 34 | layer.position = CGPointMake(CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds) / 2 - widthDiff / 2, CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds) / 2 - heightDiff / 2); 35 | } 36 | 37 | - (CAShapeLayer*)indefiniteAnimatedLayer { 38 | if(!_indefiniteAnimatedLayer) { 39 | CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5); 40 | UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat) (M_PI*3/2) endAngle:(CGFloat) (M_PI/2+M_PI*5) clockwise:YES]; 41 | 42 | _indefiniteAnimatedLayer = [CAShapeLayer layer]; 43 | _indefiniteAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale]; 44 | _indefiniteAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2); 45 | _indefiniteAnimatedLayer.fillColor = [UIColor clearColor].CGColor; 46 | _indefiniteAnimatedLayer.strokeColor = self.strokeColor.CGColor; 47 | _indefiniteAnimatedLayer.lineWidth = self.strokeThickness; 48 | _indefiniteAnimatedLayer.lineCap = kCALineCapRound; 49 | _indefiniteAnimatedLayer.lineJoin = kCALineJoinBevel; 50 | _indefiniteAnimatedLayer.path = smoothedPath.CGPath; 51 | 52 | CALayer *maskLayer = [CALayer layer]; 53 | 54 | NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]]; 55 | NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"]; 56 | NSBundle *imageBundle = [NSBundle bundleWithURL:url]; 57 | 58 | NSString *path = [imageBundle pathForResource:@"angle-mask" ofType:@"png"]; 59 | 60 | maskLayer.contents = (__bridge id)[[UIImage imageWithContentsOfFile:path] CGImage]; 61 | maskLayer.frame = _indefiniteAnimatedLayer.bounds; 62 | _indefiniteAnimatedLayer.mask = maskLayer; 63 | 64 | NSTimeInterval animationDuration = 1; 65 | CAMediaTimingFunction *linearCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; 66 | 67 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; 68 | animation.fromValue = (id) 0; 69 | animation.toValue = @(M_PI*2); 70 | animation.duration = animationDuration; 71 | animation.timingFunction = linearCurve; 72 | animation.removedOnCompletion = NO; 73 | animation.repeatCount = INFINITY; 74 | animation.fillMode = kCAFillModeForwards; 75 | animation.autoreverses = NO; 76 | [_indefiniteAnimatedLayer.mask addAnimation:animation forKey:@"rotate"]; 77 | 78 | CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; 79 | animationGroup.duration = animationDuration; 80 | animationGroup.repeatCount = INFINITY; 81 | animationGroup.removedOnCompletion = NO; 82 | animationGroup.timingFunction = linearCurve; 83 | 84 | CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; 85 | strokeStartAnimation.fromValue = @0.015; 86 | strokeStartAnimation.toValue = @0.515; 87 | 88 | CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; 89 | strokeEndAnimation.fromValue = @0.485; 90 | strokeEndAnimation.toValue = @0.985; 91 | 92 | animationGroup.animations = @[strokeStartAnimation, strokeEndAnimation]; 93 | [_indefiniteAnimatedLayer addAnimation:animationGroup forKey:@"progress"]; 94 | 95 | } 96 | return _indefiniteAnimatedLayer; 97 | } 98 | 99 | - (void)setFrame:(CGRect)frame { 100 | if(!CGRectEqualToRect(frame, super.frame)) { 101 | [super setFrame:frame]; 102 | 103 | if(self.superview) { 104 | [self layoutAnimatedLayer]; 105 | } 106 | } 107 | 108 | } 109 | 110 | - (void)setRadius:(CGFloat)radius { 111 | if(radius != _radius) { 112 | _radius = radius; 113 | 114 | [_indefiniteAnimatedLayer removeFromSuperlayer]; 115 | _indefiniteAnimatedLayer = nil; 116 | 117 | if(self.superview) { 118 | [self layoutAnimatedLayer]; 119 | } 120 | } 121 | } 122 | 123 | - (void)setStrokeColor:(UIColor*)strokeColor { 124 | _strokeColor = strokeColor; 125 | _indefiniteAnimatedLayer.strokeColor = strokeColor.CGColor; 126 | } 127 | 128 | - (void)setStrokeThickness:(CGFloat)strokeThickness { 129 | _strokeThickness = strokeThickness; 130 | _indefiniteAnimatedLayer.lineWidth = _strokeThickness; 131 | } 132 | 133 | - (CGSize)sizeThatFits:(CGSize)size { 134 | return CGSizeMake((self.radius+self.strokeThickness/2+5)*2, (self.radius+self.strokeThickness/2+5)*2); 135 | } 136 | 137 | @end 138 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo/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 | 52 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // SVProgressHUD.h 3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD 4 | // 5 | // Copyright (c) 2011-2016 Sam Vermette and contributors. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | #if __IPHONE_OS_VERSION_MAX_ALLOWED < 70000 12 | 13 | #define UI_APPEARANCE_SELECTOR 14 | 15 | #endif 16 | 17 | extern NSString * const SVProgressHUDDidReceiveTouchEventNotification; 18 | extern NSString * const SVProgressHUDDidTouchDownInsideNotification; 19 | extern NSString * const SVProgressHUDWillDisappearNotification; 20 | extern NSString * const SVProgressHUDDidDisappearNotification; 21 | extern NSString * const SVProgressHUDWillAppearNotification; 22 | extern NSString * const SVProgressHUDDidAppearNotification; 23 | 24 | extern NSString * const SVProgressHUDStatusUserInfoKey; 25 | 26 | typedef NS_ENUM(NSInteger, SVProgressHUDStyle) { 27 | SVProgressHUDStyleLight, // default style, white HUD with black text, HUD background will be blurred on iOS 8 and above 28 | SVProgressHUDStyleDark, // black HUD and white text, HUD background will be blurred on iOS 8 and above 29 | SVProgressHUDStyleCustom // uses the fore- and background color properties 30 | }; 31 | 32 | typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) { 33 | SVProgressHUDMaskTypeNone = 1, // default mask type, allow user interactions while HUD is displayed 34 | SVProgressHUDMaskTypeClear, // don't allow user interactions 35 | SVProgressHUDMaskTypeBlack, // don't allow user interactions and dim the UI in the back of the HUD, as on iOS 7 and above 36 | SVProgressHUDMaskTypeGradient, // don't allow user interactions and dim the UI with a a-la UIAlertView background gradient, as on iOS 6 37 | SVProgressHUDMaskTypeCustom // don't allow user interactions and dim the UI in the back of the HUD with a custom color 38 | }; 39 | 40 | typedef NS_ENUM(NSUInteger, SVProgressHUDAnimationType) { 41 | SVProgressHUDAnimationTypeFlat, // default animation type, custom flat animation (indefinite animated ring) 42 | SVProgressHUDAnimationTypeNative // iOS native UIActivityIndicatorView 43 | }; 44 | 45 | typedef void (^SVProgressHUDDismissCompletion)(void); 46 | 47 | @interface SVProgressHUD : UIView 48 | 49 | #pragma mark - Customization 50 | 51 | @property (assign, nonatomic) SVProgressHUDStyle defaultStyle UI_APPEARANCE_SELECTOR; // default is SVProgressHUDStyleLight 52 | @property (assign, nonatomic) SVProgressHUDMaskType defaultMaskType UI_APPEARANCE_SELECTOR; // default is SVProgressHUDMaskTypeNone 53 | @property (assign, nonatomic) SVProgressHUDAnimationType defaultAnimationType UI_APPEARANCE_SELECTOR; // default is SVProgressHUDAnimationTypeFlat 54 | @property (assign, nonatomic) CGSize minimumSize UI_APPEARANCE_SELECTOR; // default is CGSizeZero, can be used to avoid resizing for a larger message 55 | @property (assign, nonatomic) CGFloat ringThickness UI_APPEARANCE_SELECTOR; // default is 2 pt 56 | @property (assign, nonatomic) CGFloat ringRadius UI_APPEARANCE_SELECTOR; // default is 18 pt 57 | @property (assign, nonatomic) CGFloat ringNoTextRadius UI_APPEARANCE_SELECTOR; // default is 24 pt 58 | @property (assign, nonatomic) CGFloat cornerRadius UI_APPEARANCE_SELECTOR; // default is 14 pt 59 | @property (strong, nonatomic) UIFont *font UI_APPEARANCE_SELECTOR; // default is [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] 60 | @property (strong, nonatomic) UIColor *backgroundColor UI_APPEARANCE_SELECTOR; // default is [UIColor whiteColor] 61 | @property (strong, nonatomic) UIColor *foregroundColor UI_APPEARANCE_SELECTOR; // default is [UIColor blackColor] 62 | @property (strong, nonatomic) UIColor *backgroundLayerColor UI_APPEARANCE_SELECTOR; // default is [UIColor colorWithWhite:0 alpha:0.4] 63 | @property (strong, nonatomic) UIImage *infoImage UI_APPEARANCE_SELECTOR; // default is the bundled info image provided by Freepik 64 | @property (strong, nonatomic) UIImage *successImage UI_APPEARANCE_SELECTOR; // default is the bundled success image provided by Freepik 65 | @property (strong, nonatomic) UIImage *errorImage UI_APPEARANCE_SELECTOR; // default is the bundled error image provided by Freepik 66 | @property (strong, nonatomic) UIView *viewForExtension UI_APPEARANCE_SELECTOR; // default is nil, only used if #define SV_APP_EXTENSIONS is set 67 | @property (assign, nonatomic) NSTimeInterval minimumDismissTimeInterval; // default is 5.0 seconds 68 | 69 | @property (assign, nonatomic) UIOffset offsetFromCenter UI_APPEARANCE_SELECTOR; // default is 0, 0 70 | 71 | @property (assign, nonatomic) NSTimeInterval fadeInAnimationDuration; // default is 0.15 72 | @property (assign, nonatomic) NSTimeInterval fadeOutAnimationDuration; // default is 0.15 73 | 74 | 75 | + (void)setDefaultStyle:(SVProgressHUDStyle)style; // default is SVProgressHUDStyleLight 76 | + (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType; // default is SVProgressHUDMaskTypeNone 77 | + (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type; // default is SVProgressHUDAnimationTypeFlat 78 | + (void)setMinimumSize:(CGSize)minimumSize; // default is CGSizeZero, can be used to avoid resizing for a larger message 79 | + (void)setRingThickness:(CGFloat)ringThickness; // default is 2 pt 80 | + (void)setRingRadius:(CGFloat)radius; // default is 18 pt 81 | + (void)setRingNoTextRadius:(CGFloat)radius; // default is 24 pt 82 | + (void)setCornerRadius:(CGFloat)cornerRadius; // default is 14 pt 83 | + (void)setFont:(UIFont*)font; // default is [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] 84 | + (void)setForegroundColor:(UIColor*)color; // default is [UIColor blackColor], only used for SVProgressHUDStyleCustom 85 | + (void)setBackgroundColor:(UIColor*)color; // default is [UIColor whiteColor], only used for SVProgressHUDStyleCustom 86 | + (void)setBackgroundLayerColor:(UIColor*)color; // default is [UIColor colorWithWhite:0 alpha:0.5], only used for SVProgressHUDMaskTypeBlack 87 | + (void)setInfoImage:(UIImage*)image; // default is the bundled info image provided by Freepik 88 | + (void)setSuccessImage:(UIImage*)image; // default is the bundled success image provided by Freepik 89 | + (void)setErrorImage:(UIImage*)image; // default is the bundled error image provided by Freepik 90 | + (void)setViewForExtension:(UIView*)view; // default is nil, only used if #define SV_APP_EXTENSIONS is set 91 | + (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval; // default is 5.0 seconds 92 | + (void)setFadeInAnimationDuration:(NSTimeInterval)duration; // default is 0.15 seconds 93 | + (void)setFadeOutAnimationDuration:(NSTimeInterval)duration; // default is 0.15 seconds 94 | 95 | #pragma mark - Show Methods 96 | 97 | + (void)show; 98 | + (void)showWithMaskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use show and setDefaultMaskType: instead."))); 99 | + (void)showWithStatus:(NSString*)status; 100 | + (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showWithStatus: and setDefaultMaskType: instead."))); 101 | 102 | + (void)showProgress:(float)progress; 103 | + (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showProgress: and setDefaultMaskType: instead."))); 104 | + (void)showProgress:(float)progress status:(NSString*)status; 105 | + (void)showProgress:(float)progress status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showProgress:status: and setDefaultMaskType: instead."))); 106 | 107 | + (void)setStatus:(NSString*)status; // change the HUD loading status while it's showing 108 | 109 | // stops the activity indicator, shows a glyph + status, and dismisses the HUD a little bit later 110 | + (void)showInfoWithStatus:(NSString*)status; 111 | + (void)showInfoWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showInfoWithStatus: and setDefaultMaskType: instead."))); 112 | + (void)showSuccessWithStatus:(NSString*)status; 113 | + (void)showSuccessWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showSuccessWithStatus: and setDefaultMaskType: instead."))); 114 | + (void)showErrorWithStatus:(NSString*)status; 115 | + (void)showErrorWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showErrorWithStatus: and setDefaultMaskType: instead."))); 116 | 117 | // shows a image + status, use 28x28 white PNGs 118 | + (void)showImage:(UIImage*)image status:(NSString*)status; 119 | + (void)showImage:(UIImage*)image status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showImage:status: and setDefaultMaskType: instead."))); 120 | 121 | + (void)setOffsetFromCenter:(UIOffset)offset; 122 | + (void)resetOffsetFromCenter; 123 | 124 | + (void)popActivity; // decrease activity count, if activity count == 0 the HUD is dismissed 125 | + (void)dismiss; 126 | + (void)dismissWithDelay:(NSTimeInterval)delay; 127 | + (void)dismissWithCompletion:(SVProgressHUDDismissCompletion)completion; 128 | + (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion; 129 | 130 | + (BOOL)isVisible; 131 | 132 | + (NSTimeInterval)displayDurationForString:(NSString*)string; 133 | 134 | @end 135 | 136 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // BluetoothDemo 4 | // 5 | // Created by XuanCS on 2017/3/21. 6 | // Copyright © 2017年 XuanCS. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "SVProgressHUD.h" 11 | #import 12 | 13 | #ifdef DEBUG 14 | #define NSLog(format, ...) printf("\n[%s] %s [第%d行] %s\n", __TIME__, __FUNCTION__, __LINE__, [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]); 15 | #else 16 | #define NSLog(format, ...) 17 | #endif 18 | 19 | #define kPeripheralName @"WooTop.Patch" // 设备名字 20 | #define kServiceUUID @"FFF0" // 服务的UUID 21 | #define kCharacteristicUUID @"FFF6" // 特征的UUID 22 | 23 | @interface ViewController () 24 | 25 | @property (nonatomic, strong) NSMutableArray *foudArray; 26 | 27 | @property (nonatomic, strong) CBCentralManager *centralManager; 28 | 29 | @property (nonatomic, strong) CBPeripheral *selectedPeripheral; 30 | 31 | @end 32 | 33 | @implementation ViewController 34 | 35 | - (void)viewDidLoad { 36 | [super viewDidLoad]; 37 | // Do any additional setup after loading the view, typically from a nib. 38 | 39 | self.title = @"蓝牙连接"; 40 | [SVProgressHUD showWithStatus:@"连接设备中"]; 41 | 42 | // 创建之后会马上检查蓝牙的状态,nil默认为主线程 43 | self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; 44 | } 45 | 46 | #pragma mark - 蓝牙代理方法 47 | 48 | // 蓝牙状态发生改变,这个方法一定要实现 49 | - (void)centralManagerDidUpdateState:(CBCentralManager *)central 50 | { 51 | // 蓝牙状态可用 52 | if (central.state == CBCentralManagerStatePoweredOn) { 53 | 54 | // 如果蓝牙支持后台模式,一定要指定服务,否则在后台断开连接不上,如果不支持,可设为nil, option里的CBCentralManagerScanOptionAllowDuplicatesKey默认为NO, 如果设置为YES,允许搜索到重名,会很耗电 55 | [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:nil]; 56 | } 57 | else { 58 | NSLog(@"蓝牙状态异常, 请检查后重试"); 59 | } 60 | } 61 | 62 | 63 | /** 64 | * 发现设备 65 | * @param peripheral 设备 66 | * @param advertisementData 广播内容 67 | * @param RSSI 信号强度 68 | */ 69 | - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI 70 | { 71 | // 判断是否是你需要连接的设备 72 | if ([peripheral.name isEqualToString:kPeripheralName]) { 73 | peripheral.delegate = self; 74 | self.selectedPeripheral = peripheral; 75 | // 开始连接设备 76 | [self.centralManager connectPeripheral:self.selectedPeripheral options:nil]; 77 | } 78 | } 79 | 80 | 81 | /** 82 | * 已经连接上设备 83 | */ 84 | - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral 85 | { 86 | // 停止扫描 87 | [self.centralManager stopScan]; 88 | // 发现服务 89 | [self.selectedPeripheral discoverServices:nil]; 90 | } 91 | 92 | 93 | /** 94 | * 已经发现服务 95 | */ 96 | - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error 97 | { 98 | for (CBService *service in peripheral.services) { 99 | if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) { 100 | // 根据你要的那个服务去发现特性 101 | [self.selectedPeripheral discoverCharacteristics:nil forService:service]; 102 | } 103 | 104 | // 这里我是根据 180A 用来获取Mac地址,没什么实际作用,可删掉 105 | if ([service.UUID isEqual:[CBUUID UUIDWithString:@"180A"]]) { 106 | [self.selectedPeripheral discoverCharacteristics:nil forService:service]; 107 | } 108 | } 109 | } 110 | 111 | 112 | /** 113 | * 已经发现特性 114 | */ 115 | - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error 116 | { 117 | [SVProgressHUD showSuccessWithStatus:@"连接成功"]; 118 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 119 | [SVProgressHUD dismiss]; 120 | }); 121 | 122 | for (CBCharacteristic *characteristic in service.characteristics) { 123 | if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2A23"]]) { 124 | // 这里是读取Mac地址, 可不要, 数据固定, 用readValueForCharacteristic, 不用setNotifyValue:setNotifyValue 125 | [self.selectedPeripheral readValueForCharacteristic:characteristic]; 126 | } 127 | 128 | 129 | if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) { 130 | // 订阅特性,当数据频繁改变时,一般用它, 不用readValueForCharacteristic 131 | [peripheral setNotifyValue:YES forCharacteristic:characteristic]; 132 | 133 | // 获取电池电量 134 | unsigned char send[4] = {0x5d, 0x08, 0x01, 0x3b}; 135 | NSData *sendData = [NSData dataWithBytes:send length:4]; 136 | 137 | // 这里的type类型有两种 CBCharacteristicWriteWithResponse CBCharacteristicWriteWithoutResponse,它的属性枚举可以组合 138 | [self.selectedPeripheral writeValue:sendData forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse]; 139 | 140 | /* 141 | characteristic 属性 142 | typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) { 143 | CBCharacteristicPropertyBroadcast = 0x01, 144 | CBCharacteristicPropertyRead = 0x02, 145 | CBCharacteristicPropertyWriteWithoutResponse = 0x04, 146 | CBCharacteristicPropertyWrite = 0x08, 147 | CBCharacteristicPropertyNotify = 0x10, 148 | CBCharacteristicPropertyIndicate = 0x20, 149 | CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40, 150 | CBCharacteristicPropertyExtendedProperties = 0x80, 151 | CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100, 152 | CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200 153 | }; 154 | */ 155 | 156 | NSLog(@"%@",characteristic); 157 | // 打印结果为 158 | 159 | // 我的结果 为 0x16 (0x08 & 0x16)结果不成立, (0x04 & 0x16)结果成立,那写入类型就是 CBCharacteristicPropertyWriteWithoutResponse 160 | } 161 | } 162 | } 163 | 164 | 165 | /** 166 | * 数据更新的回调 167 | */ 168 | - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 169 | { 170 | // 这里收到的数据都是16进制,有两种转换,一种就直接转字符串,另一种是转byte数组,看用哪种方便 171 | 172 | // 直接转字符串 173 | NSString *orStr = characteristic.value.description; 174 | NSString *str = [orStr substringWithRange:NSMakeRange(1, orStr.length - 2)]; 175 | NSString *dataStr = [str stringByReplacingOccurrencesOfString:@" " withString:@""]; 176 | NSLog(@"dataStr = %@",dataStr); 177 | 178 | // 转Byte数组 179 | Byte *byte = (Byte *)characteristic.value.bytes; 180 | 181 | //_______________________________________________________________________________________________________________ 182 | // 解析你的协议,附几个解协议或许能用到的函数 183 | 184 | // unsigned long value = strtoul([subStr UTF8String], 0, 16); 把16进制的字符串,按十进制整数输出 185 | // [self binaryDataWithStr:str]; 十六进制的字符串转换成2进制的字符串,自己写的函数,用于复杂协议的戒子 186 | // [self byteWithInteger:num], 十进制转字节,用于发送命令; 187 | } 188 | 189 | 190 | /** 191 | * 设备连接断开 192 | */ 193 | - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error 194 | { 195 | // 让它自动重连 196 | // [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:nil]; 197 | } 198 | 199 | 200 | /** 201 | * 写入成功的回调, 如果类型是CBCharacteristicWriteWithoutResponse,不会走这个方法; 202 | */ 203 | - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 204 | { 205 | [self.selectedPeripheral readValueForCharacteristic:characteristic]; 206 | } 207 | 208 | 209 | #pragma mark - 主动断开和重连连接设备 210 | 211 | /** 212 | * 断开连接 213 | */ 214 | - (IBAction)cancelConnect:(id)sender { 215 | if (self.selectedPeripheral) { 216 | [self.centralManager cancelPeripheralConnection:self.selectedPeripheral]; 217 | } 218 | } 219 | 220 | /** 221 | * 重新连接 222 | */ 223 | - (IBAction)reConnect:(id)sender { 224 | [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:nil]; 225 | } 226 | 227 | #pragma mark - 把十六进制的字符串转换成2进制的字符串 228 | 229 | - (NSString *)binaryDataWithStr:(NSString *)str 230 | { 231 | NSMutableString *mutStr = [[NSMutableString alloc] init]; 232 | for (NSInteger j = 0; j < str.length; j++) { 233 | char c = [str characterAtIndex:j]; 234 | char cs = toupper(c); 235 | int a = 0; 236 | NSMutableArray *mutArr = [NSMutableArray array]; 237 | if (cs >= 'A' && cs <='F') { 238 | a = cs - 'A' + 10; 239 | } else 240 | { 241 | a = cs - '0'; 242 | } 243 | int n = 1; 244 | for(int i = 0; n != 0; i++)// 判断条件 n!=0 245 | { 246 | NSInteger m = a % 2; 247 | [mutArr addObject:@(m)]; 248 | a = a / 2; 249 | n = a; 250 | } 251 | if (mutArr.count < 4) { 252 | NSInteger h = 4 - mutArr.count; 253 | for (int i = 0; i < h; i++) { 254 | [mutArr addObject:@(0)]; 255 | } 256 | } 257 | for (int i = 0; i < 2; i++) { 258 | [mutArr exchangeObjectAtIndex:i withObjectAtIndex:3 - i]; 259 | } 260 | for (int i = 0; i < 4; i++) { 261 | NSInteger dataStr = [mutArr[i] integerValue]; 262 | [mutStr appendFormat:@"%ld",dataStr]; 263 | } 264 | } 265 | return mutStr; 266 | } 267 | 268 | #pragma mark - 把十进制转成字节,用于发送命令; 269 | 270 | - (Byte)byteWithInteger:(NSInteger)num 271 | { 272 | Byte btye = (Byte)0xff# 273 | return btye; 274 | } 275 | 276 | 277 | 278 | 279 | @end 280 | -------------------------------------------------------------------------------- /BluetoothDemo/BluetoothDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6DB364C81E82056A0017A67B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DB364C71E82056A0017A67B /* main.m */; }; 11 | 6DB364CB1E82056A0017A67B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DB364CA1E82056A0017A67B /* AppDelegate.m */; }; 12 | 6DB364CE1E82056A0017A67B /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DB364CD1E82056A0017A67B /* ViewController.m */; }; 13 | 6DB364D11E82056A0017A67B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6DB364CF1E82056A0017A67B /* Main.storyboard */; }; 14 | 6DB364D31E82056A0017A67B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6DB364D21E82056A0017A67B /* Assets.xcassets */; }; 15 | 6DB364D61E82056A0017A67B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6DB364D41E82056A0017A67B /* LaunchScreen.storyboard */; }; 16 | 6DB364E11E82056A0017A67B /* BluetoothDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DB364E01E82056A0017A67B /* BluetoothDemoTests.m */; }; 17 | 6DB364EC1E82056A0017A67B /* BluetoothDemoUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DB364EB1E82056A0017A67B /* BluetoothDemoUITests.m */; }; 18 | 6DFE3ACC1E831EBA004BFA43 /* SVIndefiniteAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DFE3AC31E831EBA004BFA43 /* SVIndefiniteAnimatedView.m */; }; 19 | 6DFE3ACD1E831EBA004BFA43 /* SVProgressAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DFE3AC51E831EBA004BFA43 /* SVProgressAnimatedView.m */; }; 20 | 6DFE3ACE1E831EBA004BFA43 /* SVProgressHUD.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 6DFE3AC71E831EBA004BFA43 /* SVProgressHUD.bundle */; }; 21 | 6DFE3ACF1E831EBA004BFA43 /* SVProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DFE3AC91E831EBA004BFA43 /* SVProgressHUD.m */; }; 22 | 6DFE3AD01E831EBA004BFA43 /* SVRadialGradientLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 6DFE3ACB1E831EBA004BFA43 /* SVRadialGradientLayer.m */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 6DB364DD1E82056A0017A67B /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 6DB364BB1E82056A0017A67B /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 6DB364C21E82056A0017A67B; 31 | remoteInfo = BluetoothDemo; 32 | }; 33 | 6DB364E81E82056A0017A67B /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 6DB364BB1E82056A0017A67B /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 6DB364C21E82056A0017A67B; 38 | remoteInfo = BluetoothDemo; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 6DB364C31E82056A0017A67B /* BluetoothDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BluetoothDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 6DB364C71E82056A0017A67B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 45 | 6DB364C91E82056A0017A67B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 46 | 6DB364CA1E82056A0017A67B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 47 | 6DB364CC1E82056A0017A67B /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 48 | 6DB364CD1E82056A0017A67B /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 49 | 6DB364D01E82056A0017A67B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 50 | 6DB364D21E82056A0017A67B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | 6DB364D51E82056A0017A67B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | 6DB364D71E82056A0017A67B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | 6DB364DC1E82056A0017A67B /* BluetoothDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BluetoothDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 6DB364E01E82056A0017A67B /* BluetoothDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BluetoothDemoTests.m; sourceTree = ""; }; 55 | 6DB364E21E82056A0017A67B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | 6DB364E71E82056A0017A67B /* BluetoothDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BluetoothDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 6DB364EB1E82056A0017A67B /* BluetoothDemoUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BluetoothDemoUITests.m; sourceTree = ""; }; 58 | 6DB364ED1E82056A0017A67B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | 6DFE3AC21E831EBA004BFA43 /* SVIndefiniteAnimatedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVIndefiniteAnimatedView.h; sourceTree = ""; }; 60 | 6DFE3AC31E831EBA004BFA43 /* SVIndefiniteAnimatedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVIndefiniteAnimatedView.m; sourceTree = ""; }; 61 | 6DFE3AC41E831EBA004BFA43 /* SVProgressAnimatedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressAnimatedView.h; sourceTree = ""; }; 62 | 6DFE3AC51E831EBA004BFA43 /* SVProgressAnimatedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressAnimatedView.m; sourceTree = ""; }; 63 | 6DFE3AC61E831EBA004BFA43 /* SVProgressHUD-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SVProgressHUD-Prefix.pch"; sourceTree = ""; }; 64 | 6DFE3AC71E831EBA004BFA43 /* SVProgressHUD.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = SVProgressHUD.bundle; sourceTree = ""; }; 65 | 6DFE3AC81E831EBA004BFA43 /* SVProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressHUD.h; sourceTree = ""; }; 66 | 6DFE3AC91E831EBA004BFA43 /* SVProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressHUD.m; sourceTree = ""; }; 67 | 6DFE3ACA1E831EBA004BFA43 /* SVRadialGradientLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVRadialGradientLayer.h; sourceTree = ""; }; 68 | 6DFE3ACB1E831EBA004BFA43 /* SVRadialGradientLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVRadialGradientLayer.m; sourceTree = ""; }; 69 | /* End PBXFileReference section */ 70 | 71 | /* Begin PBXFrameworksBuildPhase section */ 72 | 6DB364C01E82056A0017A67B /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | 6DB364D91E82056A0017A67B /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | 6DB364E41E82056A0017A67B /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | /* End PBXFrameworksBuildPhase section */ 94 | 95 | /* Begin PBXGroup section */ 96 | 6DB364BA1E82056A0017A67B = { 97 | isa = PBXGroup; 98 | children = ( 99 | 6DFE3AC11E831EBA004BFA43 /* SVProgressHUD */, 100 | 6DB364C51E82056A0017A67B /* BluetoothDemo */, 101 | 6DB364DF1E82056A0017A67B /* BluetoothDemoTests */, 102 | 6DB364EA1E82056A0017A67B /* BluetoothDemoUITests */, 103 | 6DB364C41E82056A0017A67B /* Products */, 104 | ); 105 | sourceTree = ""; 106 | }; 107 | 6DB364C41E82056A0017A67B /* Products */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 6DB364C31E82056A0017A67B /* BluetoothDemo.app */, 111 | 6DB364DC1E82056A0017A67B /* BluetoothDemoTests.xctest */, 112 | 6DB364E71E82056A0017A67B /* BluetoothDemoUITests.xctest */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | 6DB364C51E82056A0017A67B /* BluetoothDemo */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 6DB364C91E82056A0017A67B /* AppDelegate.h */, 121 | 6DB364CA1E82056A0017A67B /* AppDelegate.m */, 122 | 6DB364CC1E82056A0017A67B /* ViewController.h */, 123 | 6DB364CD1E82056A0017A67B /* ViewController.m */, 124 | 6DB364CF1E82056A0017A67B /* Main.storyboard */, 125 | 6DB364D21E82056A0017A67B /* Assets.xcassets */, 126 | 6DB364D41E82056A0017A67B /* LaunchScreen.storyboard */, 127 | 6DB364D71E82056A0017A67B /* Info.plist */, 128 | 6DB364C61E82056A0017A67B /* Supporting Files */, 129 | ); 130 | path = BluetoothDemo; 131 | sourceTree = ""; 132 | }; 133 | 6DB364C61E82056A0017A67B /* Supporting Files */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 6DB364C71E82056A0017A67B /* main.m */, 137 | ); 138 | name = "Supporting Files"; 139 | sourceTree = ""; 140 | }; 141 | 6DB364DF1E82056A0017A67B /* BluetoothDemoTests */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 6DB364E01E82056A0017A67B /* BluetoothDemoTests.m */, 145 | 6DB364E21E82056A0017A67B /* Info.plist */, 146 | ); 147 | path = BluetoothDemoTests; 148 | sourceTree = ""; 149 | }; 150 | 6DB364EA1E82056A0017A67B /* BluetoothDemoUITests */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 6DB364EB1E82056A0017A67B /* BluetoothDemoUITests.m */, 154 | 6DB364ED1E82056A0017A67B /* Info.plist */, 155 | ); 156 | path = BluetoothDemoUITests; 157 | sourceTree = ""; 158 | }; 159 | 6DFE3AC11E831EBA004BFA43 /* SVProgressHUD */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 6DFE3AC21E831EBA004BFA43 /* SVIndefiniteAnimatedView.h */, 163 | 6DFE3AC31E831EBA004BFA43 /* SVIndefiniteAnimatedView.m */, 164 | 6DFE3AC41E831EBA004BFA43 /* SVProgressAnimatedView.h */, 165 | 6DFE3AC51E831EBA004BFA43 /* SVProgressAnimatedView.m */, 166 | 6DFE3AC61E831EBA004BFA43 /* SVProgressHUD-Prefix.pch */, 167 | 6DFE3AC71E831EBA004BFA43 /* SVProgressHUD.bundle */, 168 | 6DFE3AC81E831EBA004BFA43 /* SVProgressHUD.h */, 169 | 6DFE3AC91E831EBA004BFA43 /* SVProgressHUD.m */, 170 | 6DFE3ACA1E831EBA004BFA43 /* SVRadialGradientLayer.h */, 171 | 6DFE3ACB1E831EBA004BFA43 /* SVRadialGradientLayer.m */, 172 | ); 173 | path = SVProgressHUD; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXGroup section */ 177 | 178 | /* Begin PBXNativeTarget section */ 179 | 6DB364C21E82056A0017A67B /* BluetoothDemo */ = { 180 | isa = PBXNativeTarget; 181 | buildConfigurationList = 6DB364F01E82056A0017A67B /* Build configuration list for PBXNativeTarget "BluetoothDemo" */; 182 | buildPhases = ( 183 | 6DB364BF1E82056A0017A67B /* Sources */, 184 | 6DB364C01E82056A0017A67B /* Frameworks */, 185 | 6DB364C11E82056A0017A67B /* Resources */, 186 | ); 187 | buildRules = ( 188 | ); 189 | dependencies = ( 190 | ); 191 | name = BluetoothDemo; 192 | productName = BluetoothDemo; 193 | productReference = 6DB364C31E82056A0017A67B /* BluetoothDemo.app */; 194 | productType = "com.apple.product-type.application"; 195 | }; 196 | 6DB364DB1E82056A0017A67B /* BluetoothDemoTests */ = { 197 | isa = PBXNativeTarget; 198 | buildConfigurationList = 6DB364F31E82056A0017A67B /* Build configuration list for PBXNativeTarget "BluetoothDemoTests" */; 199 | buildPhases = ( 200 | 6DB364D81E82056A0017A67B /* Sources */, 201 | 6DB364D91E82056A0017A67B /* Frameworks */, 202 | 6DB364DA1E82056A0017A67B /* Resources */, 203 | ); 204 | buildRules = ( 205 | ); 206 | dependencies = ( 207 | 6DB364DE1E82056A0017A67B /* PBXTargetDependency */, 208 | ); 209 | name = BluetoothDemoTests; 210 | productName = BluetoothDemoTests; 211 | productReference = 6DB364DC1E82056A0017A67B /* BluetoothDemoTests.xctest */; 212 | productType = "com.apple.product-type.bundle.unit-test"; 213 | }; 214 | 6DB364E61E82056A0017A67B /* BluetoothDemoUITests */ = { 215 | isa = PBXNativeTarget; 216 | buildConfigurationList = 6DB364F61E82056A0017A67B /* Build configuration list for PBXNativeTarget "BluetoothDemoUITests" */; 217 | buildPhases = ( 218 | 6DB364E31E82056A0017A67B /* Sources */, 219 | 6DB364E41E82056A0017A67B /* Frameworks */, 220 | 6DB364E51E82056A0017A67B /* Resources */, 221 | ); 222 | buildRules = ( 223 | ); 224 | dependencies = ( 225 | 6DB364E91E82056A0017A67B /* PBXTargetDependency */, 226 | ); 227 | name = BluetoothDemoUITests; 228 | productName = BluetoothDemoUITests; 229 | productReference = 6DB364E71E82056A0017A67B /* BluetoothDemoUITests.xctest */; 230 | productType = "com.apple.product-type.bundle.ui-testing"; 231 | }; 232 | /* End PBXNativeTarget section */ 233 | 234 | /* Begin PBXProject section */ 235 | 6DB364BB1E82056A0017A67B /* Project object */ = { 236 | isa = PBXProject; 237 | attributes = { 238 | LastUpgradeCheck = 0820; 239 | ORGANIZATIONNAME = XuanCS; 240 | TargetAttributes = { 241 | 6DB364C21E82056A0017A67B = { 242 | CreatedOnToolsVersion = 8.2.1; 243 | ProvisioningStyle = Automatic; 244 | SystemCapabilities = { 245 | com.apple.BackgroundModes = { 246 | enabled = 1; 247 | }; 248 | }; 249 | }; 250 | 6DB364DB1E82056A0017A67B = { 251 | CreatedOnToolsVersion = 8.2.1; 252 | ProvisioningStyle = Automatic; 253 | TestTargetID = 6DB364C21E82056A0017A67B; 254 | }; 255 | 6DB364E61E82056A0017A67B = { 256 | CreatedOnToolsVersion = 8.2.1; 257 | ProvisioningStyle = Automatic; 258 | TestTargetID = 6DB364C21E82056A0017A67B; 259 | }; 260 | }; 261 | }; 262 | buildConfigurationList = 6DB364BE1E82056A0017A67B /* Build configuration list for PBXProject "BluetoothDemo" */; 263 | compatibilityVersion = "Xcode 3.2"; 264 | developmentRegion = English; 265 | hasScannedForEncodings = 0; 266 | knownRegions = ( 267 | en, 268 | Base, 269 | ); 270 | mainGroup = 6DB364BA1E82056A0017A67B; 271 | productRefGroup = 6DB364C41E82056A0017A67B /* Products */; 272 | projectDirPath = ""; 273 | projectRoot = ""; 274 | targets = ( 275 | 6DB364C21E82056A0017A67B /* BluetoothDemo */, 276 | 6DB364DB1E82056A0017A67B /* BluetoothDemoTests */, 277 | 6DB364E61E82056A0017A67B /* BluetoothDemoUITests */, 278 | ); 279 | }; 280 | /* End PBXProject section */ 281 | 282 | /* Begin PBXResourcesBuildPhase section */ 283 | 6DB364C11E82056A0017A67B /* Resources */ = { 284 | isa = PBXResourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | 6DB364D61E82056A0017A67B /* LaunchScreen.storyboard in Resources */, 288 | 6DB364D31E82056A0017A67B /* Assets.xcassets in Resources */, 289 | 6DB364D11E82056A0017A67B /* Main.storyboard in Resources */, 290 | 6DFE3ACE1E831EBA004BFA43 /* SVProgressHUD.bundle in Resources */, 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | 6DB364DA1E82056A0017A67B /* Resources */ = { 295 | isa = PBXResourcesBuildPhase; 296 | buildActionMask = 2147483647; 297 | files = ( 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | 6DB364E51E82056A0017A67B /* Resources */ = { 302 | isa = PBXResourcesBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | /* End PBXResourcesBuildPhase section */ 309 | 310 | /* Begin PBXSourcesBuildPhase section */ 311 | 6DB364BF1E82056A0017A67B /* Sources */ = { 312 | isa = PBXSourcesBuildPhase; 313 | buildActionMask = 2147483647; 314 | files = ( 315 | 6DFE3AD01E831EBA004BFA43 /* SVRadialGradientLayer.m in Sources */, 316 | 6DB364CE1E82056A0017A67B /* ViewController.m in Sources */, 317 | 6DFE3ACD1E831EBA004BFA43 /* SVProgressAnimatedView.m in Sources */, 318 | 6DB364CB1E82056A0017A67B /* AppDelegate.m in Sources */, 319 | 6DB364C81E82056A0017A67B /* main.m in Sources */, 320 | 6DFE3ACC1E831EBA004BFA43 /* SVIndefiniteAnimatedView.m in Sources */, 321 | 6DFE3ACF1E831EBA004BFA43 /* SVProgressHUD.m in Sources */, 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | }; 325 | 6DB364D81E82056A0017A67B /* Sources */ = { 326 | isa = PBXSourcesBuildPhase; 327 | buildActionMask = 2147483647; 328 | files = ( 329 | 6DB364E11E82056A0017A67B /* BluetoothDemoTests.m in Sources */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | 6DB364E31E82056A0017A67B /* Sources */ = { 334 | isa = PBXSourcesBuildPhase; 335 | buildActionMask = 2147483647; 336 | files = ( 337 | 6DB364EC1E82056A0017A67B /* BluetoothDemoUITests.m in Sources */, 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | /* End PBXSourcesBuildPhase section */ 342 | 343 | /* Begin PBXTargetDependency section */ 344 | 6DB364DE1E82056A0017A67B /* PBXTargetDependency */ = { 345 | isa = PBXTargetDependency; 346 | target = 6DB364C21E82056A0017A67B /* BluetoothDemo */; 347 | targetProxy = 6DB364DD1E82056A0017A67B /* PBXContainerItemProxy */; 348 | }; 349 | 6DB364E91E82056A0017A67B /* PBXTargetDependency */ = { 350 | isa = PBXTargetDependency; 351 | target = 6DB364C21E82056A0017A67B /* BluetoothDemo */; 352 | targetProxy = 6DB364E81E82056A0017A67B /* PBXContainerItemProxy */; 353 | }; 354 | /* End PBXTargetDependency section */ 355 | 356 | /* Begin PBXVariantGroup section */ 357 | 6DB364CF1E82056A0017A67B /* Main.storyboard */ = { 358 | isa = PBXVariantGroup; 359 | children = ( 360 | 6DB364D01E82056A0017A67B /* Base */, 361 | ); 362 | name = Main.storyboard; 363 | sourceTree = ""; 364 | }; 365 | 6DB364D41E82056A0017A67B /* LaunchScreen.storyboard */ = { 366 | isa = PBXVariantGroup; 367 | children = ( 368 | 6DB364D51E82056A0017A67B /* Base */, 369 | ); 370 | name = LaunchScreen.storyboard; 371 | sourceTree = ""; 372 | }; 373 | /* End PBXVariantGroup section */ 374 | 375 | /* Begin XCBuildConfiguration section */ 376 | 6DB364EE1E82056A0017A67B /* Debug */ = { 377 | isa = XCBuildConfiguration; 378 | buildSettings = { 379 | ALWAYS_SEARCH_USER_PATHS = NO; 380 | CLANG_ANALYZER_NONNULL = YES; 381 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 382 | CLANG_CXX_LIBRARY = "libc++"; 383 | CLANG_ENABLE_MODULES = YES; 384 | CLANG_ENABLE_OBJC_ARC = YES; 385 | CLANG_WARN_BOOL_CONVERSION = YES; 386 | CLANG_WARN_CONSTANT_CONVERSION = YES; 387 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 388 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 389 | CLANG_WARN_EMPTY_BODY = YES; 390 | CLANG_WARN_ENUM_CONVERSION = YES; 391 | CLANG_WARN_INFINITE_RECURSION = YES; 392 | CLANG_WARN_INT_CONVERSION = YES; 393 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 394 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 395 | CLANG_WARN_UNREACHABLE_CODE = YES; 396 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 397 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 398 | COPY_PHASE_STRIP = NO; 399 | DEBUG_INFORMATION_FORMAT = dwarf; 400 | ENABLE_STRICT_OBJC_MSGSEND = YES; 401 | ENABLE_TESTABILITY = YES; 402 | GCC_C_LANGUAGE_STANDARD = gnu99; 403 | GCC_DYNAMIC_NO_PIC = NO; 404 | GCC_NO_COMMON_BLOCKS = YES; 405 | GCC_OPTIMIZATION_LEVEL = 0; 406 | GCC_PREPROCESSOR_DEFINITIONS = ( 407 | "DEBUG=1", 408 | "$(inherited)", 409 | ); 410 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 411 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 412 | GCC_WARN_UNDECLARED_SELECTOR = YES; 413 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 414 | GCC_WARN_UNUSED_FUNCTION = YES; 415 | GCC_WARN_UNUSED_VARIABLE = YES; 416 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 417 | MTL_ENABLE_DEBUG_INFO = YES; 418 | ONLY_ACTIVE_ARCH = YES; 419 | SDKROOT = iphoneos; 420 | TARGETED_DEVICE_FAMILY = "1,2"; 421 | }; 422 | name = Debug; 423 | }; 424 | 6DB364EF1E82056A0017A67B /* Release */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | ALWAYS_SEARCH_USER_PATHS = NO; 428 | CLANG_ANALYZER_NONNULL = YES; 429 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 430 | CLANG_CXX_LIBRARY = "libc++"; 431 | CLANG_ENABLE_MODULES = YES; 432 | CLANG_ENABLE_OBJC_ARC = YES; 433 | CLANG_WARN_BOOL_CONVERSION = YES; 434 | CLANG_WARN_CONSTANT_CONVERSION = YES; 435 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 436 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 437 | CLANG_WARN_EMPTY_BODY = YES; 438 | CLANG_WARN_ENUM_CONVERSION = YES; 439 | CLANG_WARN_INFINITE_RECURSION = YES; 440 | CLANG_WARN_INT_CONVERSION = YES; 441 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 442 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 443 | CLANG_WARN_UNREACHABLE_CODE = YES; 444 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 445 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 446 | COPY_PHASE_STRIP = NO; 447 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 448 | ENABLE_NS_ASSERTIONS = NO; 449 | ENABLE_STRICT_OBJC_MSGSEND = YES; 450 | GCC_C_LANGUAGE_STANDARD = gnu99; 451 | GCC_NO_COMMON_BLOCKS = YES; 452 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 453 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 454 | GCC_WARN_UNDECLARED_SELECTOR = YES; 455 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 456 | GCC_WARN_UNUSED_FUNCTION = YES; 457 | GCC_WARN_UNUSED_VARIABLE = YES; 458 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 459 | MTL_ENABLE_DEBUG_INFO = NO; 460 | SDKROOT = iphoneos; 461 | TARGETED_DEVICE_FAMILY = "1,2"; 462 | VALIDATE_PRODUCT = YES; 463 | }; 464 | name = Release; 465 | }; 466 | 6DB364F11E82056A0017A67B /* Debug */ = { 467 | isa = XCBuildConfiguration; 468 | buildSettings = { 469 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 470 | DEVELOPMENT_TEAM = ""; 471 | INFOPLIST_FILE = BluetoothDemo/Info.plist; 472 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 474 | PRODUCT_BUNDLE_IDENTIFIER = XuanCS.BluetoothDemo; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | }; 477 | name = Debug; 478 | }; 479 | 6DB364F21E82056A0017A67B /* Release */ = { 480 | isa = XCBuildConfiguration; 481 | buildSettings = { 482 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 483 | DEVELOPMENT_TEAM = ""; 484 | INFOPLIST_FILE = BluetoothDemo/Info.plist; 485 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 486 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 487 | PRODUCT_BUNDLE_IDENTIFIER = XuanCS.BluetoothDemo; 488 | PRODUCT_NAME = "$(TARGET_NAME)"; 489 | }; 490 | name = Release; 491 | }; 492 | 6DB364F41E82056A0017A67B /* Debug */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | BUNDLE_LOADER = "$(TEST_HOST)"; 496 | INFOPLIST_FILE = BluetoothDemoTests/Info.plist; 497 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 498 | PRODUCT_BUNDLE_IDENTIFIER = XuanCS.BluetoothDemoTests; 499 | PRODUCT_NAME = "$(TARGET_NAME)"; 500 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BluetoothDemo.app/BluetoothDemo"; 501 | }; 502 | name = Debug; 503 | }; 504 | 6DB364F51E82056A0017A67B /* Release */ = { 505 | isa = XCBuildConfiguration; 506 | buildSettings = { 507 | BUNDLE_LOADER = "$(TEST_HOST)"; 508 | INFOPLIST_FILE = BluetoothDemoTests/Info.plist; 509 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 510 | PRODUCT_BUNDLE_IDENTIFIER = XuanCS.BluetoothDemoTests; 511 | PRODUCT_NAME = "$(TARGET_NAME)"; 512 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BluetoothDemo.app/BluetoothDemo"; 513 | }; 514 | name = Release; 515 | }; 516 | 6DB364F71E82056A0017A67B /* Debug */ = { 517 | isa = XCBuildConfiguration; 518 | buildSettings = { 519 | INFOPLIST_FILE = BluetoothDemoUITests/Info.plist; 520 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 521 | PRODUCT_BUNDLE_IDENTIFIER = XuanCS.BluetoothDemoUITests; 522 | PRODUCT_NAME = "$(TARGET_NAME)"; 523 | TEST_TARGET_NAME = BluetoothDemo; 524 | }; 525 | name = Debug; 526 | }; 527 | 6DB364F81E82056A0017A67B /* Release */ = { 528 | isa = XCBuildConfiguration; 529 | buildSettings = { 530 | INFOPLIST_FILE = BluetoothDemoUITests/Info.plist; 531 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 532 | PRODUCT_BUNDLE_IDENTIFIER = XuanCS.BluetoothDemoUITests; 533 | PRODUCT_NAME = "$(TARGET_NAME)"; 534 | TEST_TARGET_NAME = BluetoothDemo; 535 | }; 536 | name = Release; 537 | }; 538 | /* End XCBuildConfiguration section */ 539 | 540 | /* Begin XCConfigurationList section */ 541 | 6DB364BE1E82056A0017A67B /* Build configuration list for PBXProject "BluetoothDemo" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | 6DB364EE1E82056A0017A67B /* Debug */, 545 | 6DB364EF1E82056A0017A67B /* Release */, 546 | ); 547 | defaultConfigurationIsVisible = 0; 548 | defaultConfigurationName = Release; 549 | }; 550 | 6DB364F01E82056A0017A67B /* Build configuration list for PBXNativeTarget "BluetoothDemo" */ = { 551 | isa = XCConfigurationList; 552 | buildConfigurations = ( 553 | 6DB364F11E82056A0017A67B /* Debug */, 554 | 6DB364F21E82056A0017A67B /* Release */, 555 | ); 556 | defaultConfigurationIsVisible = 0; 557 | defaultConfigurationName = Release; 558 | }; 559 | 6DB364F31E82056A0017A67B /* Build configuration list for PBXNativeTarget "BluetoothDemoTests" */ = { 560 | isa = XCConfigurationList; 561 | buildConfigurations = ( 562 | 6DB364F41E82056A0017A67B /* Debug */, 563 | 6DB364F51E82056A0017A67B /* Release */, 564 | ); 565 | defaultConfigurationIsVisible = 0; 566 | defaultConfigurationName = Release; 567 | }; 568 | 6DB364F61E82056A0017A67B /* Build configuration list for PBXNativeTarget "BluetoothDemoUITests" */ = { 569 | isa = XCConfigurationList; 570 | buildConfigurations = ( 571 | 6DB364F71E82056A0017A67B /* Debug */, 572 | 6DB364F81E82056A0017A67B /* Release */, 573 | ); 574 | defaultConfigurationIsVisible = 0; 575 | defaultConfigurationName = Release; 576 | }; 577 | /* End XCConfigurationList section */ 578 | }; 579 | rootObject = 6DB364BB1E82056A0017A67B /* Project object */; 580 | } 581 | -------------------------------------------------------------------------------- /BluetoothDemo/SVProgressHUD/SVProgressHUD.m: -------------------------------------------------------------------------------- 1 | // 2 | // SVProgressHUD.h 3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD 4 | // 5 | // Copyright (c) 2011-2016 Sam Vermette and contributors. All rights reserved. 6 | // 7 | 8 | #if !__has_feature(objc_arc) 9 | #error SVProgressHUD is ARC only. Either turn on ARC for the project or use -fobjc-arc flag 10 | #endif 11 | 12 | #import "SVProgressHUD.h" 13 | #import "SVIndefiniteAnimatedView.h" 14 | #import "SVProgressAnimatedView.h" 15 | #import "SVRadialGradientLayer.h" 16 | 17 | NSString * const SVProgressHUDDidReceiveTouchEventNotification = @"SVProgressHUDDidReceiveTouchEventNotification"; 18 | NSString * const SVProgressHUDDidTouchDownInsideNotification = @"SVProgressHUDDidTouchDownInsideNotification"; 19 | NSString * const SVProgressHUDWillDisappearNotification = @"SVProgressHUDWillDisappearNotification"; 20 | NSString * const SVProgressHUDDidDisappearNotification = @"SVProgressHUDDidDisappearNotification"; 21 | NSString * const SVProgressHUDWillAppearNotification = @"SVProgressHUDWillAppearNotification"; 22 | NSString * const SVProgressHUDDidAppearNotification = @"SVProgressHUDDidAppearNotification"; 23 | 24 | NSString * const SVProgressHUDStatusUserInfoKey = @"SVProgressHUDStatusUserInfoKey"; 25 | 26 | static const CGFloat SVProgressHUDParallaxDepthPoints = 10; 27 | static const CGFloat SVProgressHUDUndefinedProgress = -1; 28 | static const CGFloat SVProgressHUDDefaultAnimationDuration = 0.15; 29 | 30 | @interface SVProgressHUD () 31 | 32 | @property (nonatomic, strong, readonly) NSTimer *fadeOutTimer; 33 | @property (nonatomic, readonly, getter = isClear) BOOL clear; 34 | 35 | @property (nonatomic, strong) UIControl *overlayView; 36 | @property (nonatomic, strong) UIView *hudView; 37 | 38 | @property (nonatomic, strong) UILabel *statusLabel; 39 | @property (nonatomic, strong) UIImageView *imageView; 40 | @property (nonatomic, strong) UIView *indefiniteAnimatedView; 41 | @property (nonatomic, strong) SVProgressAnimatedView *ringView; 42 | @property (nonatomic, strong) SVProgressAnimatedView *backgroundRingView; 43 | @property (nonatomic, strong) CALayer *backgroundLayer; 44 | 45 | @property (nonatomic, readwrite) CGFloat progress; 46 | @property (nonatomic, readwrite) NSUInteger activityCount; 47 | 48 | @property (nonatomic, readonly) CGFloat visibleKeyboardHeight; 49 | 50 | - (void)updateHUDFrame; 51 | - (void)updateMask; 52 | - (void)updateBlurBounds; 53 | #if TARGET_OS_IOS 54 | - (void)updateMotionEffectForOrientation:(UIInterfaceOrientation)orientation; 55 | #endif 56 | - (void)updateMotionEffectForXMotionEffectType:(UIInterpolatingMotionEffectType)xMotionEffectType yMotionEffectType:(UIInterpolatingMotionEffectType)yMotionEffectType; 57 | - (void)updateViewHierachy; 58 | 59 | - (void)setStatus:(NSString*)status; 60 | - (void)setFadeOutTimer:(NSTimer*)timer; 61 | 62 | - (void)registerNotifications; 63 | - (NSDictionary*)notificationUserInfo; 64 | 65 | - (void)positionHUD:(NSNotification*)notification; 66 | - (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle; 67 | 68 | - (void)overlayViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent*)event; 69 | 70 | - (void)showProgress:(float)progress status:(NSString*)status; 71 | - (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration; 72 | - (void)showStatus:(NSString*)status; 73 | 74 | - (void)dismiss; 75 | - (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion; 76 | 77 | - (UIView*)indefiniteAnimatedView; 78 | - (SVProgressAnimatedView*)ringView; 79 | - (SVProgressAnimatedView*)backgroundRingView; 80 | 81 | - (void)cancelRingLayerAnimation; 82 | - (void)cancelIndefiniteAnimatedViewAnimation; 83 | 84 | - (UIColor*)foregroundColorForStyle; 85 | - (UIColor*)backgroundColorForStyle; 86 | - (UIImage*)image:(UIImage*)image withTintColor:(UIColor*)color; 87 | 88 | @end 89 | 90 | 91 | @implementation SVProgressHUD { 92 | BOOL _isInitializing; 93 | } 94 | 95 | + (SVProgressHUD*)sharedView { 96 | static dispatch_once_t once; 97 | 98 | static SVProgressHUD *sharedView; 99 | #if !defined(SV_APP_EXTENSIONS) 100 | dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[[UIApplication sharedApplication] delegate] window].bounds]; }); 101 | #else 102 | dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; }); 103 | #endif 104 | return sharedView; 105 | } 106 | 107 | 108 | #pragma mark - Setters 109 | 110 | + (void)setStatus:(NSString*)status { 111 | [[self sharedView] setStatus:status]; 112 | } 113 | 114 | + (void)setDefaultStyle:(SVProgressHUDStyle)style { 115 | [self sharedView].defaultStyle = style; 116 | } 117 | 118 | + (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType { 119 | [self sharedView].defaultMaskType = maskType; 120 | } 121 | 122 | + (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type { 123 | [self sharedView].defaultAnimationType = type; 124 | } 125 | 126 | + (void)setMinimumSize:(CGSize)minimumSize { 127 | [self sharedView].minimumSize = minimumSize; 128 | } 129 | 130 | + (void)setRingThickness:(CGFloat)ringThickness { 131 | [self sharedView].ringThickness = ringThickness; 132 | } 133 | 134 | + (void)setRingRadius:(CGFloat)radius { 135 | [self sharedView].ringRadius = radius; 136 | } 137 | 138 | + (void)setRingNoTextRadius:(CGFloat)radius { 139 | [self sharedView].ringNoTextRadius = radius; 140 | } 141 | 142 | + (void)setCornerRadius:(CGFloat)cornerRadius { 143 | [self sharedView].cornerRadius = cornerRadius; 144 | } 145 | 146 | + (void)setFont:(UIFont*)font { 147 | [self sharedView].font = font; 148 | } 149 | 150 | + (void)setForegroundColor:(UIColor*)color { 151 | [self sharedView].foregroundColor = color; 152 | } 153 | 154 | + (void)setBackgroundColor:(UIColor*)color { 155 | [self sharedView].backgroundColor = color; 156 | } 157 | 158 | + (void)setBackgroundLayerColor:(UIColor*)color { 159 | [self sharedView].backgroundLayerColor = color; 160 | } 161 | 162 | + (void)setInfoImage:(UIImage*)image { 163 | [self sharedView].infoImage = image; 164 | } 165 | 166 | + (void)setSuccessImage:(UIImage*)image { 167 | [self sharedView].successImage = image; 168 | } 169 | 170 | + (void)setErrorImage:(UIImage*)image { 171 | [self sharedView].errorImage = image; 172 | } 173 | 174 | + (void)setViewForExtension:(UIView*)view { 175 | [self sharedView].viewForExtension = view; 176 | } 177 | 178 | + (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval { 179 | [self sharedView].minimumDismissTimeInterval = interval; 180 | } 181 | 182 | + (void)setFadeInAnimationDuration:(NSTimeInterval)duration { 183 | [self sharedView].fadeInAnimationDuration = duration; 184 | } 185 | 186 | + (void)setFadeOutAnimationDuration:(NSTimeInterval)duration { 187 | [self sharedView].fadeOutAnimationDuration = duration; 188 | } 189 | 190 | 191 | #pragma mark - Show Methods 192 | 193 | + (void)show { 194 | [self showWithStatus:nil]; 195 | } 196 | 197 | + (void)showWithMaskType:(SVProgressHUDMaskType)maskType { 198 | SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; 199 | [self setDefaultMaskType:maskType]; 200 | [self show]; 201 | [self setDefaultMaskType:existingMaskType]; 202 | } 203 | 204 | + (void)showWithStatus:(NSString*)status { 205 | [self sharedView]; 206 | [self showProgress:SVProgressHUDUndefinedProgress status:status]; 207 | } 208 | 209 | + (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { 210 | SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; 211 | [self setDefaultMaskType:maskType]; 212 | [self showWithStatus:status]; 213 | [self setDefaultMaskType:existingMaskType]; 214 | } 215 | 216 | + (void)showProgress:(float)progress { 217 | [self showProgress:progress status:nil]; 218 | } 219 | 220 | + (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType { 221 | SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; 222 | [self setDefaultMaskType:maskType]; 223 | [self showProgress:progress]; 224 | [self setDefaultMaskType:existingMaskType]; 225 | } 226 | 227 | + (void)showProgress:(float)progress status:(NSString*)status { 228 | [[self sharedView] showProgress:progress status:status]; 229 | } 230 | 231 | + (void)showProgress:(float)progress status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { 232 | SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; 233 | [self setDefaultMaskType:maskType]; 234 | [self showProgress:progress status:status]; 235 | [self setDefaultMaskType:existingMaskType]; 236 | } 237 | 238 | 239 | #pragma mark - Show, then automatically dismiss methods 240 | 241 | + (void)showInfoWithStatus:(NSString*)status { 242 | [self showImage:[self sharedView].infoImage status:status]; 243 | } 244 | 245 | + (void)showInfoWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { 246 | SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; 247 | [self setDefaultMaskType:maskType]; 248 | [self showInfoWithStatus:status]; 249 | [self setDefaultMaskType:existingMaskType]; 250 | } 251 | 252 | + (void)showSuccessWithStatus:(NSString*)status { 253 | [self showImage:[self sharedView].successImage status:status]; 254 | } 255 | 256 | + (void)showSuccessWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { 257 | SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; 258 | [self setDefaultMaskType:maskType]; 259 | [self showSuccessWithStatus:status]; 260 | [self setDefaultMaskType:existingMaskType]; 261 | } 262 | 263 | + (void)showErrorWithStatus:(NSString*)status { 264 | [self showImage:[self sharedView].errorImage status:status]; 265 | } 266 | 267 | + (void)showErrorWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { 268 | SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; 269 | [self setDefaultMaskType:maskType]; 270 | [self showErrorWithStatus:status]; 271 | [self setDefaultMaskType:existingMaskType]; 272 | } 273 | 274 | + (void)showImage:(UIImage*)image status:(NSString*)status { 275 | NSTimeInterval displayInterval = [self displayDurationForString:status]; 276 | [[self sharedView] showImage:image status:status duration:displayInterval]; 277 | } 278 | 279 | + (void)showImage:(UIImage*)image status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { 280 | SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; 281 | [self setDefaultMaskType:maskType]; 282 | [self showImage:image status:status]; 283 | [self setDefaultMaskType:existingMaskType]; 284 | } 285 | 286 | 287 | #pragma mark - Dismiss Methods 288 | 289 | + (void)popActivity { 290 | if([self sharedView].activityCount > 0) { 291 | [self sharedView].activityCount--; 292 | } 293 | if([self sharedView].activityCount == 0) { 294 | [[self sharedView] dismiss]; 295 | } 296 | } 297 | 298 | + (void)dismiss { 299 | [self dismissWithDelay:0.0 completion:nil]; 300 | } 301 | 302 | + (void)dismissWithDelay:(NSTimeInterval)delay { 303 | [self dismissWithDelay:delay completion:nil]; 304 | } 305 | 306 | + (void)dismissWithCompletion:(SVProgressHUDDismissCompletion)completion { 307 | [self dismissWithDelay:0.0 completion:completion]; 308 | } 309 | 310 | + (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion { 311 | [[self sharedView] dismissWithDelay:delay completion:completion]; 312 | } 313 | 314 | #pragma mark - Offset 315 | 316 | + (void)setOffsetFromCenter:(UIOffset)offset { 317 | [self sharedView].offsetFromCenter = offset; 318 | } 319 | 320 | + (void)resetOffsetFromCenter { 321 | [self setOffsetFromCenter:UIOffsetZero]; 322 | } 323 | 324 | 325 | #pragma mark - Instance Methods 326 | 327 | - (instancetype)initWithFrame:(CGRect)frame { 328 | if((self = [super initWithFrame:frame])) { 329 | _isInitializing = YES; 330 | 331 | self.userInteractionEnabled = NO; 332 | _backgroundColor = [UIColor clearColor]; 333 | _foregroundColor = [UIColor blackColor]; 334 | _backgroundLayerColor = [UIColor colorWithWhite:0 alpha:0.4]; 335 | 336 | self.alpha = 0.0f; 337 | self.activityCount = 0; 338 | 339 | // Set default values 340 | _defaultMaskType = SVProgressHUDMaskTypeNone; 341 | _defaultStyle = SVProgressHUDStyleDark; 342 | _defaultAnimationType = SVProgressHUDAnimationTypeNative; 343 | 344 | if ([UIFont respondsToSelector:@selector(preferredFontForTextStyle:)]) { 345 | _font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; 346 | } else { 347 | _font = [UIFont systemFontOfSize:14.0f]; 348 | } 349 | 350 | NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]]; 351 | NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"]; 352 | NSBundle *imageBundle = [NSBundle bundleWithURL:url]; 353 | 354 | UIImage* infoImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"info" ofType:@"png"]]; 355 | UIImage* successImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"success" ofType:@"png"]]; 356 | UIImage* errorImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"error" ofType:@"png"]]; 357 | 358 | if ([[UIImage class] instancesRespondToSelector:@selector(imageWithRenderingMode:)]) { 359 | _infoImage = [infoImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 360 | _successImage = [successImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 361 | _errorImage = [errorImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 362 | } else { 363 | _infoImage = infoImage; 364 | _successImage = successImage; 365 | _errorImage = errorImage; 366 | } 367 | 368 | _ringThickness = 2.0f; 369 | _ringRadius = 18.0f; 370 | _ringNoTextRadius = 24.0f; 371 | 372 | _cornerRadius = 14.0f; 373 | 374 | _minimumDismissTimeInterval = 5.0; 375 | 376 | _fadeInAnimationDuration = SVProgressHUDDefaultAnimationDuration; 377 | _fadeOutAnimationDuration = SVProgressHUDDefaultAnimationDuration; 378 | 379 | // Accessibility support 380 | self.accessibilityIdentifier = @"SVProgressHUD"; 381 | self.accessibilityLabel = @"SVProgressHUD"; 382 | self.isAccessibilityElement = YES; 383 | 384 | _isInitializing = NO; 385 | } 386 | return self; 387 | } 388 | 389 | - (void)updateHUDFrame { 390 | // For the beginning use default values, these 391 | // might get update if string is too large etc. 392 | CGFloat hudWidth = 100.0f; 393 | CGFloat hudHeight = 100.0f; 394 | CGFloat stringHeightBuffer = 20.0f; 395 | CGFloat stringAndContentHeightBuffer = 80.0f; 396 | CGRect labelRect = CGRectZero; 397 | 398 | // Check if an image or progress ring is displayed 399 | BOOL imageUsed = (self.imageView.image) && !(self.imageView.hidden); 400 | BOOL progressUsed = self.imageView.hidden; 401 | 402 | // Calculate size of string and update HUD size 403 | NSString *string = self.statusLabel.text; 404 | if(string) { 405 | CGSize constraintSize = CGSizeMake(200.0f, 300.0f); 406 | CGRect stringRect; 407 | if([string respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) { 408 | stringRect = [string boundingRectWithSize:constraintSize 409 | options:(NSStringDrawingOptions)(NSStringDrawingUsesFontLeading|NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin) 410 | attributes:@{NSFontAttributeName: self.statusLabel.font} 411 | context:NULL]; 412 | } else { 413 | CGSize stringSize; 414 | if([string respondsToSelector:@selector(sizeWithAttributes:)]) { 415 | stringSize = [string sizeWithAttributes:@{NSFontAttributeName:[UIFont fontWithName:self.statusLabel.font.fontName size:self.statusLabel.font.pointSize]}]; 416 | } else { 417 | #if TARGET_OS_IOS 418 | #pragma clang diagnostic push 419 | #pragma clang diagnostic ignored "-Wdeprecated" 420 | stringSize = [string sizeWithFont:self.statusLabel.font constrainedToSize:CGSizeMake(200.0f, 300.0f)]; 421 | #pragma clang diagnostic pop 422 | #endif 423 | } 424 | stringRect = CGRectMake(0.0f, 0.0f, stringSize.width, stringSize.height); 425 | } 426 | 427 | CGFloat stringWidth = stringRect.size.width; 428 | CGFloat stringHeight = ceilf(CGRectGetHeight(stringRect)); 429 | 430 | if(imageUsed || progressUsed) { 431 | hudHeight = stringAndContentHeightBuffer + stringHeight; 432 | } else { 433 | hudHeight = stringHeightBuffer + stringHeight; 434 | } 435 | if(stringWidth > hudWidth) { 436 | hudWidth = ceilf(stringWidth/2)*2; 437 | } 438 | CGFloat labelRectY = (imageUsed || progressUsed) ? 68.0f : 9.0f; 439 | if(hudHeight > 100.0f) { 440 | labelRect = CGRectMake(12.0f, labelRectY, hudWidth, stringHeight); 441 | hudWidth += 24.0f; 442 | } else { 443 | hudWidth += 24.0f; 444 | labelRect = CGRectMake(0.0f, labelRectY, hudWidth, stringHeight); 445 | } 446 | } 447 | 448 | // Update values on subviews 449 | self.hudView.bounds = CGRectMake(0.0f, 0.0f, MAX(self.minimumSize.width, hudWidth), MAX(self.minimumSize.height, hudHeight)); 450 | labelRect.size.width += MAX(0, self.minimumSize.width - hudWidth); 451 | [self updateBlurBounds]; 452 | 453 | if(string) { 454 | self.imageView.center = CGPointMake(CGRectGetWidth(self.hudView.bounds)/2, 36.0f); 455 | } else { 456 | self.imageView.center = CGPointMake(CGRectGetWidth(self.hudView.bounds)/2, CGRectGetHeight(self.hudView.bounds)/2); 457 | } 458 | 459 | self.statusLabel.hidden = NO; 460 | self.statusLabel.frame = labelRect; 461 | 462 | // Animate value update 463 | [CATransaction begin]; 464 | [CATransaction setDisableActions:YES]; 465 | 466 | if(string) { 467 | if(self.defaultAnimationType == SVProgressHUDAnimationTypeFlat) { 468 | SVIndefiniteAnimatedView *indefiniteAnimationView = (SVIndefiniteAnimatedView*)self.indefiniteAnimatedView; 469 | indefiniteAnimationView.radius = self.ringRadius; 470 | [indefiniteAnimationView sizeToFit]; 471 | } 472 | 473 | CGPoint center = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), 36.0f); 474 | self.indefiniteAnimatedView.center = center; 475 | 476 | if(self.progress != SVProgressHUDUndefinedProgress) { 477 | self.backgroundRingView.center = self.ringView.center = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), 36.0f); 478 | } 479 | } else { 480 | if(self.defaultAnimationType == SVProgressHUDAnimationTypeFlat) { 481 | SVIndefiniteAnimatedView *indefiniteAnimationView = (SVIndefiniteAnimatedView*)self.indefiniteAnimatedView; 482 | indefiniteAnimationView.radius = self.ringNoTextRadius; 483 | [indefiniteAnimationView sizeToFit]; 484 | } 485 | 486 | CGPoint center = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), CGRectGetHeight(self.hudView.bounds)/2); 487 | self.indefiniteAnimatedView.center = center; 488 | 489 | if(self.progress != SVProgressHUDUndefinedProgress) { 490 | self.backgroundRingView.center = self.ringView.center = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), CGRectGetHeight(self.hudView.bounds)/2); 491 | } 492 | } 493 | 494 | [CATransaction commit]; 495 | } 496 | 497 | - (void)updateMask { 498 | if(self.backgroundLayer) { 499 | [self.backgroundLayer removeFromSuperlayer]; 500 | self.backgroundLayer = nil; 501 | } 502 | switch (self.defaultMaskType) { 503 | case SVProgressHUDMaskTypeCustom: 504 | case SVProgressHUDMaskTypeBlack:{ 505 | 506 | self.backgroundLayer = [CALayer layer]; 507 | self.backgroundLayer.frame = self.bounds; 508 | self.backgroundLayer.backgroundColor = self.defaultMaskType == SVProgressHUDMaskTypeCustom ? self.backgroundLayerColor.CGColor : [UIColor colorWithWhite:0 alpha:0.4].CGColor; 509 | [self.backgroundLayer setNeedsDisplay]; 510 | 511 | [self.layer insertSublayer:self.backgroundLayer atIndex:0]; 512 | break; 513 | } 514 | 515 | case SVProgressHUDMaskTypeGradient:{ 516 | SVRadialGradientLayer *layer = [SVRadialGradientLayer layer]; 517 | self.backgroundLayer = layer; 518 | self.backgroundLayer.frame = self.bounds; 519 | CGPoint gradientCenter = self.center; 520 | gradientCenter.y = (self.bounds.size.height - self.visibleKeyboardHeight)/2; 521 | layer.gradientCenter = gradientCenter; 522 | [self.backgroundLayer setNeedsDisplay]; 523 | 524 | [self.layer insertSublayer:self.backgroundLayer atIndex:0]; 525 | break; 526 | } 527 | default: 528 | break; 529 | } 530 | } 531 | 532 | - (void)updateBlurBounds { 533 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 534 | if(NSClassFromString(@"UIBlurEffect") && self.defaultStyle != SVProgressHUDStyleCustom) { 535 | // Remove background color, else the effect would not work 536 | self.hudView.backgroundColor = [UIColor clearColor]; 537 | 538 | // Remove any old instances of UIVisualEffectViews 539 | for (UIView *subview in self.hudView.subviews) { 540 | if([subview isKindOfClass:[UIVisualEffectView class]]) { 541 | [subview removeFromSuperview]; 542 | } 543 | } 544 | 545 | if(self.backgroundColor != [UIColor clearColor]) { 546 | // Create blur effect 547 | UIBlurEffectStyle blurEffectStyle = self.defaultStyle == SVProgressHUDStyleDark ? UIBlurEffectStyleDark : UIBlurEffectStyleLight; 548 | UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:blurEffectStyle]; 549 | UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; 550 | blurEffectView.autoresizingMask = self.hudView.autoresizingMask; 551 | blurEffectView.frame = self.hudView.bounds; 552 | 553 | // Add vibrancy to the blur effect to make it more vivid 554 | UIVibrancyEffect *vibrancyEffect = [UIVibrancyEffect effectForBlurEffect:blurEffect]; 555 | UIVisualEffectView *vibrancyEffectView = [[UIVisualEffectView alloc] initWithEffect:vibrancyEffect]; 556 | vibrancyEffectView.autoresizingMask = blurEffectView.autoresizingMask; 557 | vibrancyEffectView.bounds = blurEffectView.bounds; 558 | [blurEffectView.contentView addSubview:vibrancyEffectView]; 559 | 560 | [self.hudView insertSubview:blurEffectView atIndex:0]; 561 | } 562 | } 563 | #endif 564 | } 565 | 566 | #if TARGET_OS_IOS 567 | - (void)updateMotionEffectForOrientation:(UIInterfaceOrientation)orientation { 568 | UIInterpolatingMotionEffectType xMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis : UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis; 569 | UIInterpolatingMotionEffectType yMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis : UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis; 570 | [self updateMotionEffectForXMotionEffectType:xMotionEffectType yMotionEffectType:yMotionEffectType]; 571 | } 572 | #endif 573 | 574 | - (void)updateMotionEffectForXMotionEffectType:(UIInterpolatingMotionEffectType)xMotionEffectType yMotionEffectType:(UIInterpolatingMotionEffectType)yMotionEffectType { 575 | if([self.hudView respondsToSelector:@selector(addMotionEffect:)]) { 576 | UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:xMotionEffectType]; 577 | effectX.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints); 578 | effectX.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints); 579 | 580 | UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:yMotionEffectType]; 581 | effectY.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints); 582 | effectY.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints); 583 | 584 | UIMotionEffectGroup *effectGroup = [[UIMotionEffectGroup alloc] init]; 585 | effectGroup.motionEffects = @[effectX, effectY]; 586 | 587 | // Clear old motion effect, then add new motion effects 588 | self.hudView.motionEffects = @[]; 589 | [self.hudView addMotionEffect:effectGroup]; 590 | } 591 | } 592 | 593 | - (void)updateViewHierachy { 594 | // Add the overlay (e.g. black, gradient) to the application window if necessary 595 | if(!self.overlayView.superview) { 596 | #if !defined(SV_APP_EXTENSIONS) 597 | // Default case: iterate over UIApplication windows 598 | NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator]; 599 | for (UIWindow *window in frontToBackWindows) { 600 | BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen; 601 | BOOL windowIsVisible = !window.hidden && window.alpha > 0; 602 | BOOL windowLevelNormal = window.windowLevel == UIWindowLevelNormal; 603 | 604 | if(windowOnMainScreen && windowIsVisible && windowLevelNormal) { 605 | [window addSubview:self.overlayView]; 606 | break; 607 | } 608 | } 609 | #else 610 | // If SVProgressHUD ist used inside an app extension add it to the given view 611 | if(self.viewForExtension) { 612 | [self.viewForExtension addSubview:self.overlayView]; 613 | } 614 | #endif 615 | } else { 616 | // The HUD is already on screen, but maybot not in front. Therefore 617 | // ensure that overlay will be on top of rootViewController (which may 618 | // be changed during runtime). 619 | [self.overlayView.superview bringSubviewToFront:self.overlayView]; 620 | } 621 | 622 | 623 | // Add self to the overlay view 624 | if(!self.superview){ 625 | [self.overlayView addSubview:self]; 626 | } 627 | if(!self.hudView.superview) { 628 | [self addSubview:self.hudView]; 629 | } 630 | } 631 | 632 | - (void)setStatus:(NSString*)status { 633 | self.statusLabel.text = status; 634 | [self updateHUDFrame]; 635 | } 636 | 637 | - (void)setFadeOutTimer:(NSTimer*)timer { 638 | if(_fadeOutTimer) { 639 | [_fadeOutTimer invalidate], _fadeOutTimer = nil; 640 | } 641 | if(timer) { 642 | _fadeOutTimer = timer; 643 | } 644 | } 645 | 646 | 647 | #pragma mark - Notifications and their handling 648 | 649 | - (void)registerNotifications { 650 | #if TARGET_OS_IOS 651 | [[NSNotificationCenter defaultCenter] addObserver:self 652 | selector:@selector(positionHUD:) 653 | name:UIApplicationDidChangeStatusBarOrientationNotification 654 | object:nil]; 655 | 656 | [[NSNotificationCenter defaultCenter] addObserver:self 657 | selector:@selector(positionHUD:) 658 | name:UIKeyboardWillHideNotification 659 | object:nil]; 660 | 661 | [[NSNotificationCenter defaultCenter] addObserver:self 662 | selector:@selector(positionHUD:) 663 | name:UIKeyboardDidHideNotification 664 | object:nil]; 665 | 666 | [[NSNotificationCenter defaultCenter] addObserver:self 667 | selector:@selector(positionHUD:) 668 | name:UIKeyboardWillShowNotification 669 | object:nil]; 670 | 671 | [[NSNotificationCenter defaultCenter] addObserver:self 672 | selector:@selector(positionHUD:) 673 | name:UIKeyboardDidShowNotification 674 | object:nil]; 675 | #endif 676 | [[NSNotificationCenter defaultCenter] addObserver:self 677 | selector:@selector(positionHUD:) 678 | name:UIApplicationDidBecomeActiveNotification 679 | object:nil]; 680 | } 681 | 682 | - (NSDictionary*)notificationUserInfo{ 683 | return (self.statusLabel.text ? @{SVProgressHUDStatusUserInfoKey : self.statusLabel.text} : nil); 684 | } 685 | 686 | - (void)positionHUD:(NSNotification*)notification { 687 | CGFloat keyboardHeight = 0.0f; 688 | double animationDuration = 0.0; 689 | 690 | #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS 691 | self.frame = [[[UIApplication sharedApplication] delegate] window].bounds; 692 | UIInterfaceOrientation orientation = UIApplication.sharedApplication.statusBarOrientation; 693 | #elif !defined(SV_APP_EXTENSIONS) 694 | self.frame = [UIApplication sharedApplication].keyWindow.bounds; 695 | #else 696 | if (self.viewForExtension) { 697 | self.frame = self.viewForExtension.frame; 698 | } else { 699 | self.frame = UIScreen.mainScreen.bounds; 700 | } 701 | UIInterfaceOrientation orientation = CGRectGetWidth(self.frame) > CGRectGetHeight(self.frame) ? UIInterfaceOrientationLandscapeLeft : UIInterfaceOrientationPortrait; 702 | #endif 703 | 704 | // no transforms applied to window in iOS 8, but only if compiled with iOS 8 sdk as base sdk, otherwise system supports old rotation logic. 705 | BOOL ignoreOrientation = NO; 706 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 707 | if([[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) { 708 | ignoreOrientation = YES; 709 | } 710 | #endif 711 | 712 | #if TARGET_OS_IOS 713 | // Get keyboardHeight in regards to current state 714 | if(notification) { 715 | NSDictionary* keyboardInfo = [notification userInfo]; 716 | CGRect keyboardFrame = [keyboardInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; 717 | animationDuration = [keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 718 | 719 | if(notification.name == UIKeyboardWillShowNotification || notification.name == UIKeyboardDidShowNotification) { 720 | keyboardHeight = CGRectGetWidth(keyboardFrame); 721 | 722 | if(ignoreOrientation || UIInterfaceOrientationIsPortrait(orientation)) { 723 | keyboardHeight = CGRectGetHeight(keyboardFrame); 724 | } 725 | } 726 | } else { 727 | keyboardHeight = self.visibleKeyboardHeight; 728 | } 729 | #endif 730 | 731 | // Get the currently active frame of the display (depends on orientation) 732 | CGRect orientationFrame = self.bounds; 733 | 734 | #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS 735 | CGRect statusBarFrame = UIApplication.sharedApplication.statusBarFrame; 736 | #else 737 | CGRect statusBarFrame = CGRectZero; 738 | #endif 739 | 740 | #if TARGET_OS_IOS 741 | if(!ignoreOrientation && UIInterfaceOrientationIsLandscape(orientation)) { 742 | float temp = CGRectGetWidth(orientationFrame); 743 | orientationFrame.size.width = CGRectGetHeight(orientationFrame); 744 | orientationFrame.size.height = temp; 745 | 746 | temp = CGRectGetWidth(statusBarFrame); 747 | statusBarFrame.size.width = CGRectGetHeight(statusBarFrame); 748 | statusBarFrame.size.height = temp; 749 | } 750 | 751 | // Update the motion effects in regards to orientation 752 | [self updateMotionEffectForOrientation:orientation]; 753 | #else 754 | [self updateMotionEffectForXMotionEffectType:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis yMotionEffectType:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; 755 | #endif 756 | 757 | // Calculate available height for display 758 | CGFloat activeHeight = CGRectGetHeight(orientationFrame); 759 | if(keyboardHeight > 0) { 760 | activeHeight += CGRectGetHeight(statusBarFrame)*2; 761 | } 762 | activeHeight -= keyboardHeight; 763 | 764 | CGFloat posX = CGRectGetWidth(orientationFrame)/2.0f; 765 | CGFloat posY = floorf(activeHeight*0.45f); 766 | 767 | CGFloat rotateAngle = 0.0; 768 | CGPoint newCenter = CGPointMake(posX, posY); 769 | 770 | // Update posX and posY in regards to orientation 771 | #if TARGET_OS_IOS 772 | if(!ignoreOrientation) { 773 | switch (orientation) { 774 | case UIInterfaceOrientationPortraitUpsideDown: 775 | rotateAngle = (CGFloat) M_PI; 776 | newCenter = CGPointMake(posX, CGRectGetHeight(orientationFrame)-posY); 777 | break; 778 | case UIInterfaceOrientationLandscapeLeft: 779 | rotateAngle = (CGFloat) (-M_PI/2.0f); 780 | newCenter = CGPointMake(posY, posX); 781 | break; 782 | case UIInterfaceOrientationLandscapeRight: 783 | rotateAngle = (CGFloat) (M_PI/2.0f); 784 | newCenter = CGPointMake(CGRectGetHeight(orientationFrame)-posY, posX); 785 | break; 786 | default: // Same as UIInterfaceOrientationPortrait 787 | rotateAngle = 0.0f; 788 | newCenter = CGPointMake(posX, posY); 789 | break; 790 | } 791 | } 792 | #endif 793 | 794 | if(notification) { 795 | // Animate update if notification was present 796 | __weak SVProgressHUD *weakSelf = self; 797 | [UIView animateWithDuration:animationDuration 798 | delay:0 799 | options:UIViewAnimationOptionAllowUserInteraction 800 | animations:^{ 801 | __strong SVProgressHUD *strongSelf = weakSelf; 802 | if(strongSelf) { 803 | [strongSelf moveToPoint:newCenter rotateAngle:rotateAngle]; 804 | [strongSelf.hudView setNeedsDisplay]; 805 | } 806 | } completion:NULL]; 807 | } else { 808 | [self moveToPoint:newCenter rotateAngle:rotateAngle]; 809 | [self.hudView setNeedsDisplay]; 810 | } 811 | 812 | [self updateMask]; 813 | } 814 | 815 | - (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle { 816 | self.hudView.transform = CGAffineTransformMakeRotation(angle); 817 | self.hudView.center = CGPointMake(newCenter.x + self.offsetFromCenter.horizontal, newCenter.y + self.offsetFromCenter.vertical); 818 | } 819 | 820 | 821 | #pragma mark - Event handling 822 | 823 | - (void)overlayViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent*)event { 824 | [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidReceiveTouchEventNotification 825 | object:self 826 | userInfo:[self notificationUserInfo]]; 827 | 828 | UITouch *touch = event.allTouches.anyObject; 829 | CGPoint touchLocation = [touch locationInView:self]; 830 | 831 | if(CGRectContainsPoint(self.hudView.frame, touchLocation)) { 832 | [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidTouchDownInsideNotification 833 | object:self 834 | userInfo:[self notificationUserInfo]]; 835 | } 836 | } 837 | 838 | 839 | #pragma mark - Master show/dismiss methods 840 | 841 | - (void)showProgress:(float)progress status:(NSString*)status { 842 | __weak SVProgressHUD *weakSelf = self; 843 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 844 | __strong SVProgressHUD *strongSelf = weakSelf; 845 | if(strongSelf){ 846 | // Update / Check view hierachy to ensure the HUD is visible 847 | [strongSelf updateViewHierachy]; 848 | 849 | // Reset imageView and fadeout timer if an image is currently displayed 850 | strongSelf.imageView.hidden = YES; 851 | strongSelf.imageView.image = nil; 852 | 853 | if(strongSelf.fadeOutTimer) { 854 | strongSelf.activityCount = 0; 855 | } 856 | strongSelf.fadeOutTimer = nil; 857 | 858 | // Update text and set progress to the given value 859 | strongSelf.statusLabel.text = status; 860 | strongSelf.progress = progress; 861 | 862 | // Choose the "right" indicator depending on the progress 863 | if(progress >= 0) { 864 | // Cancel the indefiniteAnimatedView, then show the ringLayer 865 | [strongSelf cancelIndefiniteAnimatedViewAnimation]; 866 | 867 | // Add ring to HUD and set progress 868 | [strongSelf.hudView addSubview:strongSelf.ringView]; 869 | [strongSelf.hudView addSubview:strongSelf.backgroundRingView]; 870 | strongSelf.ringView.strokeEnd = progress; 871 | 872 | // Updat the activity count 873 | if(progress == 0) { 874 | strongSelf.activityCount++; 875 | } 876 | } else { 877 | // Cancel the ringLayer animation, then show the indefiniteAnimatedView 878 | [strongSelf cancelRingLayerAnimation]; 879 | 880 | // Add indefiniteAnimatedView to HUD 881 | [strongSelf.hudView addSubview:strongSelf.indefiniteAnimatedView]; 882 | if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) { 883 | [(id)strongSelf.indefiniteAnimatedView startAnimating]; 884 | } 885 | 886 | // Update the activity count 887 | strongSelf.activityCount++; 888 | } 889 | 890 | // Show 891 | [strongSelf showStatus:status]; 892 | } 893 | }]; 894 | } 895 | 896 | - (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration { 897 | __weak SVProgressHUD *weakSelf = self; 898 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 899 | __strong SVProgressHUD *strongSelf = weakSelf; 900 | if(strongSelf){ 901 | // Update / Check view hierachy to ensure the HUD is visible 902 | [strongSelf updateViewHierachy]; 903 | 904 | // Reset progress and cancel any running animation 905 | strongSelf.progress = SVProgressHUDUndefinedProgress; 906 | [strongSelf cancelRingLayerAnimation]; 907 | [strongSelf cancelIndefiniteAnimatedViewAnimation]; 908 | 909 | // Update imageView 910 | UIColor *tintColor = strongSelf.foregroundColorForStyle; 911 | UIImage *tintedImage = image; 912 | if([strongSelf.imageView respondsToSelector:@selector(setTintColor:)]) { 913 | if (tintedImage.renderingMode != UIImageRenderingModeAlwaysTemplate) { 914 | tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 915 | } 916 | strongSelf.imageView.tintColor = tintColor; 917 | } else { 918 | tintedImage = [strongSelf image:image withTintColor:tintColor]; 919 | } 920 | strongSelf.imageView.image = tintedImage; 921 | strongSelf.imageView.hidden = NO; 922 | 923 | // Update text 924 | strongSelf.statusLabel.text = status; 925 | 926 | // Show 927 | [strongSelf showStatus:status]; 928 | 929 | // An image will dismissed automatically. Therefore we start a timer 930 | // which then will call dismiss after the predefined duration 931 | strongSelf.fadeOutTimer = [NSTimer timerWithTimeInterval:duration target:strongSelf selector:@selector(dismiss) userInfo:nil repeats:NO]; 932 | [[NSRunLoop mainRunLoop] addTimer:strongSelf.fadeOutTimer forMode:NSRunLoopCommonModes]; 933 | } 934 | }]; 935 | } 936 | 937 | - (void)showStatus:(NSString*)status { 938 | // Update the HUDs frame to the new content and position HUD 939 | [self updateHUDFrame]; 940 | [self positionHUD:nil]; 941 | 942 | // Update accesibilty as well as user interaction 943 | if(self.defaultMaskType != SVProgressHUDMaskTypeNone) { 944 | self.overlayView.userInteractionEnabled = YES; 945 | self.accessibilityLabel = status; 946 | self.isAccessibilityElement = YES; 947 | } else { 948 | self.overlayView.userInteractionEnabled = NO; 949 | self.hudView.accessibilityLabel = status; 950 | self.hudView.isAccessibilityElement = YES; 951 | } 952 | 953 | // Show overlay 954 | self.overlayView.backgroundColor = [UIColor clearColor]; 955 | 956 | // Show if not already visible (depending on alpha) 957 | if(self.alpha != 1.0f || self.hudView.alpha != 1.0f) { 958 | // Post notification to inform user 959 | [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification 960 | object:self 961 | userInfo:[self notificationUserInfo]]; 962 | 963 | // Zoom HUD a little to make a nice appear / pop up animation 964 | self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3); 965 | 966 | // Set initial values to handle iOS 7 (and above) UIToolbar which not answers well to hierarchy opacity change 967 | self.alpha = 0.0f; 968 | self.hudView.alpha = 0.0f; 969 | 970 | // Define blocks 971 | __weak SVProgressHUD *weakSelf = self; 972 | 973 | __block void (^animationsBlock)(void) = ^{ 974 | __strong SVProgressHUD *strongSelf = weakSelf; 975 | if(strongSelf) { 976 | // Shrink HUD to finish pop up animation 977 | strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f); 978 | strongSelf.alpha = 1.0f; 979 | strongSelf.hudView.alpha = 1.0f; 980 | } 981 | }; 982 | 983 | __block void (^completionBlock)(void) = ^{ 984 | __strong SVProgressHUD *strongSelf = weakSelf; 985 | if(strongSelf) { 986 | /// Register observer <=> we now have to handle orientation changes etc. 987 | [strongSelf registerNotifications]; 988 | 989 | // Post notification to inform user 990 | [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification 991 | object:strongSelf 992 | userInfo:[strongSelf notificationUserInfo]]; 993 | } 994 | 995 | // Update accesibilty 996 | UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); 997 | UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, status); 998 | }; 999 | 1000 | if (self.fadeInAnimationDuration > 0) { 1001 | // Animate appearance 1002 | [UIView animateWithDuration:self.fadeInAnimationDuration 1003 | delay:0 1004 | options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState) 1005 | animations:^{ 1006 | animationsBlock(); 1007 | } completion:^(BOOL finished) { 1008 | completionBlock(); 1009 | }]; 1010 | } else { 1011 | animationsBlock(); 1012 | completionBlock(); 1013 | } 1014 | 1015 | // Inform iOS to redraw the view hierachy 1016 | [self setNeedsDisplay]; 1017 | } 1018 | } 1019 | 1020 | - (void)dismiss { 1021 | [self dismissWithDelay:0.0 completion:nil]; 1022 | } 1023 | 1024 | - (void)dismissWithDelay:(NSTimeInterval)delay completion:(SVProgressHUDDismissCompletion)completion { 1025 | __weak SVProgressHUD *weakSelf = self; 1026 | [[NSOperationQueue mainQueue] addOperationWithBlock:^{ 1027 | __strong SVProgressHUD *strongSelf = weakSelf; 1028 | if(strongSelf){ 1029 | // Dismiss if visible (depending on alpha) 1030 | if(strongSelf.alpha != 0.0f || strongSelf.hudView.alpha != 0.0f){ 1031 | // Post notification to inform user 1032 | [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillDisappearNotification 1033 | object:nil 1034 | userInfo:[strongSelf notificationUserInfo]]; 1035 | 1036 | // Reset activitiy count 1037 | strongSelf.activityCount = 0; 1038 | 1039 | // Define blocks 1040 | __block void (^animationsBlock)(void) = ^{ 1041 | strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 0.8f, 0.8f); 1042 | strongSelf.alpha = 0.0f; 1043 | strongSelf.hudView.alpha = 0.0f; 1044 | }; 1045 | 1046 | __block void (^completionBlock)(void) = ^{ 1047 | // Clean up view hierachy (overlays) 1048 | [strongSelf.overlayView removeFromSuperview]; 1049 | [strongSelf.hudView removeFromSuperview]; 1050 | [strongSelf removeFromSuperview]; 1051 | 1052 | // Reset progress and cancel any running animation 1053 | strongSelf.progress = SVProgressHUDUndefinedProgress; 1054 | [strongSelf cancelRingLayerAnimation]; 1055 | [strongSelf cancelIndefiniteAnimatedViewAnimation]; 1056 | 1057 | // Remove observer <=> we do not have to handle orientation changes etc. 1058 | [[NSNotificationCenter defaultCenter] removeObserver:strongSelf]; 1059 | 1060 | // Post notification to inform user 1061 | [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification 1062 | object:strongSelf 1063 | userInfo:[strongSelf notificationUserInfo]]; 1064 | 1065 | // Tell the rootViewController to update the StatusBar appearance 1066 | #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS 1067 | UIViewController *rootController = [[UIApplication sharedApplication] keyWindow].rootViewController; 1068 | if([rootController respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) { 1069 | [rootController setNeedsStatusBarAppearanceUpdate]; 1070 | } 1071 | #endif 1072 | // Update accesibilty 1073 | UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); 1074 | 1075 | if (completion) { 1076 | completion(); 1077 | } 1078 | }; 1079 | 1080 | if (strongSelf.fadeOutAnimationDuration > 0) { 1081 | // Animate appearance 1082 | [UIView animateWithDuration:strongSelf.fadeOutAnimationDuration 1083 | delay:delay 1084 | options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) 1085 | animations:^{ 1086 | animationsBlock(); 1087 | } completion:^(BOOL finished) { 1088 | completionBlock(); 1089 | }]; 1090 | } else { 1091 | animationsBlock(); 1092 | completionBlock(); 1093 | } 1094 | 1095 | // Inform iOS to redraw the view hierachy 1096 | [strongSelf setNeedsDisplay]; 1097 | } 1098 | } else if (completion) { 1099 | completion(); 1100 | } 1101 | }]; 1102 | } 1103 | 1104 | 1105 | #pragma mark - Ring progress animation 1106 | 1107 | - (UIView*)indefiniteAnimatedView { 1108 | // Get the correct spinner for defaultAnimationType 1109 | if(self.defaultAnimationType == SVProgressHUDAnimationTypeFlat){ 1110 | // Check if spinner exists and is an object of different class 1111 | if(_indefiniteAnimatedView && ![_indefiniteAnimatedView isKindOfClass:[SVIndefiniteAnimatedView class]]){ 1112 | [_indefiniteAnimatedView removeFromSuperview]; 1113 | _indefiniteAnimatedView = nil; 1114 | } 1115 | 1116 | if(!_indefiniteAnimatedView){ 1117 | _indefiniteAnimatedView = [[SVIndefiniteAnimatedView alloc] initWithFrame:CGRectZero]; 1118 | } 1119 | 1120 | // Update styling 1121 | SVIndefiniteAnimatedView *indefiniteAnimatedView = (SVIndefiniteAnimatedView*)_indefiniteAnimatedView; 1122 | indefiniteAnimatedView.strokeColor = self.foregroundColorForStyle; 1123 | indefiniteAnimatedView.strokeThickness = self.ringThickness; 1124 | indefiniteAnimatedView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; 1125 | } else { 1126 | // Check if spinner exists and is an object of different class 1127 | if(_indefiniteAnimatedView && ![_indefiniteAnimatedView isKindOfClass:[UIActivityIndicatorView class]]){ 1128 | [_indefiniteAnimatedView removeFromSuperview]; 1129 | _indefiniteAnimatedView = nil; 1130 | } 1131 | 1132 | if(!_indefiniteAnimatedView){ 1133 | _indefiniteAnimatedView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; 1134 | } 1135 | 1136 | // Update styling 1137 | UIActivityIndicatorView *activityIndicatorView = (UIActivityIndicatorView*)_indefiniteAnimatedView; 1138 | activityIndicatorView.color = self.foregroundColorForStyle; 1139 | } 1140 | [_indefiniteAnimatedView sizeToFit]; 1141 | 1142 | return _indefiniteAnimatedView; 1143 | } 1144 | 1145 | - (SVProgressAnimatedView*)ringView { 1146 | if(!_ringView) { 1147 | _ringView = [[SVProgressAnimatedView alloc] initWithFrame:CGRectZero]; 1148 | } 1149 | 1150 | // Update styling 1151 | _ringView.strokeColor = self.foregroundColorForStyle; 1152 | _ringView.strokeThickness = self.ringThickness; 1153 | _ringView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; 1154 | 1155 | return _ringView; 1156 | } 1157 | 1158 | - (SVProgressAnimatedView*)backgroundRingView { 1159 | if(!_backgroundRingView) { 1160 | _backgroundRingView = [[SVProgressAnimatedView alloc] initWithFrame:CGRectZero]; 1161 | _backgroundRingView.strokeEnd = 1.0f; 1162 | } 1163 | 1164 | // Update styling 1165 | _backgroundRingView.strokeColor = [self.foregroundColorForStyle colorWithAlphaComponent:0.1f]; 1166 | _backgroundRingView.strokeThickness = self.ringThickness; 1167 | _backgroundRingView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; 1168 | 1169 | return _backgroundRingView; 1170 | } 1171 | 1172 | - (void)cancelRingLayerAnimation { 1173 | // Animate value update, stop animation 1174 | [CATransaction begin]; 1175 | [CATransaction setDisableActions:YES]; 1176 | 1177 | [self.hudView.layer removeAllAnimations]; 1178 | self.ringView.strokeEnd = 0.0f; 1179 | 1180 | [CATransaction commit]; 1181 | 1182 | // Remove from view 1183 | [self.ringView removeFromSuperview]; 1184 | [self.backgroundRingView removeFromSuperview]; 1185 | } 1186 | 1187 | - (void)cancelIndefiniteAnimatedViewAnimation { 1188 | // Stop animation 1189 | if([self.indefiniteAnimatedView respondsToSelector:@selector(stopAnimating)]) { 1190 | [(id)self.indefiniteAnimatedView stopAnimating]; 1191 | } 1192 | // Remove from view 1193 | [self.indefiniteAnimatedView removeFromSuperview]; 1194 | } 1195 | 1196 | 1197 | #pragma mark - Utilities 1198 | 1199 | + (BOOL)isVisible { 1200 | return ([self sharedView].alpha > 0); 1201 | } 1202 | 1203 | 1204 | #pragma mark - Getters 1205 | 1206 | + (NSTimeInterval)displayDurationForString:(NSString*)string { 1207 | return MAX((float)string.length * 0.06 + 0.5, [self sharedView].minimumDismissTimeInterval); 1208 | } 1209 | 1210 | - (UIColor*)foregroundColorForStyle { 1211 | if(self.defaultStyle == SVProgressHUDStyleLight) { 1212 | return [UIColor blackColor]; 1213 | } else if(self.defaultStyle == SVProgressHUDStyleDark) { 1214 | return [UIColor whiteColor]; 1215 | } else { 1216 | return self.foregroundColor; 1217 | } 1218 | } 1219 | 1220 | - (UIColor*)backgroundColorForStyle { 1221 | if(self.defaultStyle == SVProgressHUDStyleLight) { 1222 | return [UIColor whiteColor]; 1223 | } else if(self.defaultStyle == SVProgressHUDStyleDark) { 1224 | return [UIColor blackColor]; 1225 | } else { 1226 | return self.backgroundColor; 1227 | } 1228 | } 1229 | 1230 | - (UIImage*)image:(UIImage*)image withTintColor:(UIColor*)color { 1231 | CGRect rect = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height); 1232 | UIGraphicsBeginImageContextWithOptions(rect.size, NO, image.scale); 1233 | CGContextRef c = UIGraphicsGetCurrentContext(); 1234 | [image drawInRect:rect]; 1235 | CGContextSetFillColorWithColor(c, [color CGColor]); 1236 | CGContextSetBlendMode(c, kCGBlendModeSourceAtop); 1237 | CGContextFillRect(c, rect); 1238 | UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext(); 1239 | UIGraphicsEndImageContext(); 1240 | 1241 | return tintedImage; 1242 | } 1243 | 1244 | - (BOOL)isClear { // used for iOS 7 and above 1245 | return (self.defaultMaskType == SVProgressHUDMaskTypeClear || self.defaultMaskType == SVProgressHUDMaskTypeNone); 1246 | } 1247 | 1248 | - (UIControl*)overlayView { 1249 | if(!_overlayView) { 1250 | #if !defined(SV_APP_EXTENSIONS) 1251 | CGRect windowBounds = [[[UIApplication sharedApplication] delegate] window].bounds; 1252 | _overlayView = [[UIControl alloc] initWithFrame:windowBounds]; 1253 | #else 1254 | _overlayView = [[UIControl alloc] initWithFrame:[UIScreen mainScreen].bounds]; 1255 | #endif 1256 | _overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 1257 | _overlayView.backgroundColor = [UIColor clearColor]; 1258 | [_overlayView addTarget:self action:@selector(overlayViewDidReceiveTouchEvent:forEvent:) forControlEvents:UIControlEventTouchDown]; 1259 | } 1260 | return _overlayView; 1261 | } 1262 | 1263 | - (UIView*)hudView { 1264 | if(!_hudView) { 1265 | _hudView = [[UIView alloc] initWithFrame:CGRectZero]; 1266 | _hudView.layer.masksToBounds = YES; 1267 | _hudView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin; 1268 | } 1269 | 1270 | // Update styling 1271 | _hudView.layer.cornerRadius = self.cornerRadius; 1272 | _hudView.backgroundColor = self.backgroundColorForStyle; 1273 | 1274 | return _hudView; 1275 | } 1276 | 1277 | - (UILabel*)statusLabel { 1278 | if(!_statusLabel) { 1279 | _statusLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 1280 | _statusLabel.backgroundColor = [UIColor clearColor]; 1281 | _statusLabel.adjustsFontSizeToFitWidth = YES; 1282 | _statusLabel.textAlignment = NSTextAlignmentCenter; 1283 | _statusLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; 1284 | _statusLabel.numberOfLines = 0; 1285 | } 1286 | if(!_statusLabel.superview) { 1287 | [self.hudView addSubview:_statusLabel]; 1288 | } 1289 | 1290 | // Update styling 1291 | _statusLabel.textColor = self.foregroundColorForStyle; 1292 | _statusLabel.font = self.font; 1293 | 1294 | return _statusLabel; 1295 | } 1296 | 1297 | - (UIImageView*)imageView { 1298 | if(!_imageView) { 1299 | _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 28.0f, 28.0f)]; 1300 | } 1301 | if(!_imageView.superview) { 1302 | [self.hudView addSubview:_imageView]; 1303 | } 1304 | return _imageView; 1305 | } 1306 | 1307 | - (CGFloat)visibleKeyboardHeight { 1308 | #if !defined(SV_APP_EXTENSIONS) 1309 | UIWindow *keyboardWindow = nil; 1310 | for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) { 1311 | if(![[testWindow class] isEqual:[UIWindow class]]) { 1312 | keyboardWindow = testWindow; 1313 | break; 1314 | } 1315 | } 1316 | 1317 | for (__strong UIView *possibleKeyboard in [keyboardWindow subviews]) { 1318 | if([possibleKeyboard isKindOfClass:NSClassFromString(@"UIPeripheralHostView")] || [possibleKeyboard isKindOfClass:NSClassFromString(@"UIKeyboard")]) { 1319 | return CGRectGetHeight(possibleKeyboard.bounds); 1320 | } else if([possibleKeyboard isKindOfClass:NSClassFromString(@"UIInputSetContainerView")]) { 1321 | for (__strong UIView *possibleKeyboardSubview in [possibleKeyboard subviews]) { 1322 | if([possibleKeyboardSubview isKindOfClass:NSClassFromString(@"UIInputSetHostView")]) { 1323 | return CGRectGetHeight(possibleKeyboardSubview.bounds); 1324 | } 1325 | } 1326 | } 1327 | } 1328 | #endif 1329 | return 0; 1330 | } 1331 | 1332 | 1333 | #pragma mark - UIAppearance Setters 1334 | 1335 | - (void)setDefaultStyle:(SVProgressHUDStyle)style { 1336 | if (!_isInitializing) _defaultStyle = style; 1337 | } 1338 | 1339 | - (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType { 1340 | if (!_isInitializing) _defaultMaskType = maskType; 1341 | } 1342 | 1343 | - (void)setDefaultAnimationType:(SVProgressHUDAnimationType)animationType { 1344 | if (!_isInitializing) _defaultAnimationType = animationType; 1345 | } 1346 | 1347 | - (void)setMinimumSize:(CGSize)minimumSize { 1348 | if (!_isInitializing) _minimumSize = minimumSize; 1349 | } 1350 | 1351 | - (void)setRingThickness:(CGFloat)ringThickness { 1352 | if (!_isInitializing) _ringThickness = ringThickness; 1353 | } 1354 | 1355 | - (void)setRingRadius:(CGFloat)ringRadius { 1356 | if (!_isInitializing) _ringRadius = ringRadius; 1357 | } 1358 | 1359 | - (void)setRingNoTextRadius:(CGFloat)ringNoTextRadius { 1360 | if (!_isInitializing) _ringNoTextRadius = ringNoTextRadius; 1361 | } 1362 | 1363 | - (void)setCornerRadius:(CGFloat)cornerRadius { 1364 | if (!_isInitializing) _cornerRadius = cornerRadius; 1365 | } 1366 | 1367 | - (void)setFont:(UIFont*)font { 1368 | if (!_isInitializing) _font = font; 1369 | } 1370 | 1371 | - (void)setForegroundColor:(UIColor*)color { 1372 | if (!_isInitializing) _foregroundColor = color; 1373 | } 1374 | 1375 | - (void)setBackgroundColor:(UIColor*)color { 1376 | if (!_isInitializing) _backgroundColor = color; 1377 | } 1378 | 1379 | - (void)setBackgroundLayerColor:(UIColor*)color { 1380 | if (!_isInitializing) _backgroundLayerColor = color; 1381 | } 1382 | 1383 | - (void)setInfoImage:(UIImage*)image { 1384 | if (!_isInitializing) _infoImage = image; 1385 | } 1386 | 1387 | - (void)setSuccessImage:(UIImage*)image { 1388 | if (!_isInitializing) _successImage = image; 1389 | } 1390 | 1391 | - (void)setErrorImage:(UIImage*)image { 1392 | if (!_isInitializing) _errorImage = image; 1393 | } 1394 | 1395 | - (void)setViewForExtension:(UIView*)view { 1396 | if (!_isInitializing) _viewForExtension = view; 1397 | } 1398 | 1399 | - (void)setOffsetFromCenter:(UIOffset)offset { 1400 | if (!_isInitializing) _offsetFromCenter = offset; 1401 | } 1402 | 1403 | - (void)setMinimumDismissTimeInterval:(NSTimeInterval)minimumDismissTimeInterval { 1404 | if (!_isInitializing) _minimumDismissTimeInterval = minimumDismissTimeInterval; 1405 | } 1406 | 1407 | - (void)setFadeInAnimationDuration:(NSTimeInterval)duration { 1408 | if (!_isInitializing) _fadeInAnimationDuration = duration; 1409 | } 1410 | 1411 | - (void)setFadeOutAnimationDuration:(NSTimeInterval)duration { 1412 | if (!_isInitializing) _fadeOutAnimationDuration = duration; 1413 | } 1414 | 1415 | @end 1416 | 1417 | --------------------------------------------------------------------------------