Repository: 976431yang/YQInAppPurchaseTool Branch: master Commit: 33ca03b6d0f3 Files: 33 Total size: 143.8 KB Directory structure: gitextract_cek43rwn/ ├── DEMO/ │ ├── SVProgressHUD/ │ │ ├── SVIndefiniteAnimatedView.h │ │ ├── SVIndefiniteAnimatedView.m │ │ ├── SVProgressAnimatedView.h │ │ ├── SVProgressAnimatedView.m │ │ ├── SVProgressHUD-Prefix.pch │ │ ├── SVProgressHUD.h │ │ ├── SVProgressHUD.m │ │ ├── SVRadialGradientLayer.h │ │ └── SVRadialGradientLayer.m │ ├── YQIAPTest/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets/ │ │ │ └── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── ViewController.h │ │ ├── ViewController.m │ │ ├── YQInAppPurchaseTool/ │ │ │ ├── YQInAppPurchaseTool.h │ │ │ └── YQInAppPurchaseTool.m │ │ └── main.m │ ├── YQIAPTest.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcuserdata/ │ │ │ └── ProblemChild.xcuserdatad/ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata/ │ │ └── ProblemChild.xcuserdatad/ │ │ └── xcschemes/ │ │ ├── YQIAPTest.xcscheme │ │ └── xcschememanagement.plist │ ├── YQIAPTestTests/ │ │ ├── Info.plist │ │ └── YQIAPTestTests.m │ └── YQIAPTestUITests/ │ ├── Info.plist │ └── YQIAPTestUITests.m ├── LICENSE ├── README.md └── YQInAppPurchaseTool/ ├── YQInAppPurchaseTool.h └── YQInAppPurchaseTool.m ================================================ FILE CONTENTS ================================================ ================================================ FILE: DEMO/SVProgressHUD/SVIndefiniteAnimatedView.h ================================================ // // SVIndefiniteAnimatedView.h // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD // // Copyright (c) 2014-2016 Guillaume Campagna. All rights reserved. // #import @interface SVIndefiniteAnimatedView : UIView @property (nonatomic, assign) CGFloat strokeThickness; @property (nonatomic, assign) CGFloat radius; @property (nonatomic, strong) UIColor *strokeColor; @end ================================================ FILE: DEMO/SVProgressHUD/SVIndefiniteAnimatedView.m ================================================ // // SVIndefiniteAnimatedView.m // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD // // Copyright (c) 2014-2016 Guillaume Campagna. All rights reserved. // #import "SVIndefiniteAnimatedView.h" #import "SVProgressHUD.h" @interface SVIndefiniteAnimatedView () @property (nonatomic, strong) CAShapeLayer *indefiniteAnimatedLayer; @end @implementation SVIndefiniteAnimatedView - (void)willMoveToSuperview:(UIView*)newSuperview { if (newSuperview) { [self layoutAnimatedLayer]; } else { [_indefiniteAnimatedLayer removeFromSuperlayer]; _indefiniteAnimatedLayer = nil; } } - (void)layoutAnimatedLayer { CALayer *layer = self.indefiniteAnimatedLayer; [self.layer addSublayer:layer]; CGFloat widthDiff = CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds); CGFloat heightDiff = CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds); layer.position = CGPointMake(CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds) / 2 - widthDiff / 2, CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds) / 2 - heightDiff / 2); } - (CAShapeLayer*)indefiniteAnimatedLayer { if(!_indefiniteAnimatedLayer) { CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5); UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat) (M_PI*3/2) endAngle:(CGFloat) (M_PI/2+M_PI*5) clockwise:YES]; _indefiniteAnimatedLayer = [CAShapeLayer layer]; _indefiniteAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale]; _indefiniteAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2); _indefiniteAnimatedLayer.fillColor = [UIColor clearColor].CGColor; _indefiniteAnimatedLayer.strokeColor = self.strokeColor.CGColor; _indefiniteAnimatedLayer.lineWidth = self.strokeThickness; _indefiniteAnimatedLayer.lineCap = kCALineCapRound; _indefiniteAnimatedLayer.lineJoin = kCALineJoinBevel; _indefiniteAnimatedLayer.path = smoothedPath.CGPath; CALayer *maskLayer = [CALayer layer]; NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]]; NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"]; NSBundle *imageBundle = [NSBundle bundleWithURL:url]; NSString *path = [imageBundle pathForResource:@"angle-mask" ofType:@"png"]; maskLayer.contents = (__bridge id)[[UIImage imageWithContentsOfFile:path] CGImage]; maskLayer.frame = _indefiniteAnimatedLayer.bounds; _indefiniteAnimatedLayer.mask = maskLayer; NSTimeInterval animationDuration = 1; CAMediaTimingFunction *linearCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; animation.fromValue = (id) 0; animation.toValue = @(M_PI*2); animation.duration = animationDuration; animation.timingFunction = linearCurve; animation.removedOnCompletion = NO; animation.repeatCount = INFINITY; animation.fillMode = kCAFillModeForwards; animation.autoreverses = NO; [_indefiniteAnimatedLayer.mask addAnimation:animation forKey:@"rotate"]; CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; animationGroup.duration = animationDuration; animationGroup.repeatCount = INFINITY; animationGroup.removedOnCompletion = NO; animationGroup.timingFunction = linearCurve; CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; strokeStartAnimation.fromValue = @0.015; strokeStartAnimation.toValue = @0.515; CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; strokeEndAnimation.fromValue = @0.485; strokeEndAnimation.toValue = @0.985; animationGroup.animations = @[strokeStartAnimation, strokeEndAnimation]; [_indefiniteAnimatedLayer addAnimation:animationGroup forKey:@"progress"]; } return _indefiniteAnimatedLayer; } - (void)setFrame:(CGRect)frame { if(!CGRectEqualToRect(frame, super.frame)) { [super setFrame:frame]; if(self.superview) { [self layoutAnimatedLayer]; } } } - (void)setRadius:(CGFloat)radius { if(radius != _radius) { _radius = radius; [_indefiniteAnimatedLayer removeFromSuperlayer]; _indefiniteAnimatedLayer = nil; if(self.superview) { [self layoutAnimatedLayer]; } } } - (void)setStrokeColor:(UIColor*)strokeColor { _strokeColor = strokeColor; _indefiniteAnimatedLayer.strokeColor = strokeColor.CGColor; } - (void)setStrokeThickness:(CGFloat)strokeThickness { _strokeThickness = strokeThickness; _indefiniteAnimatedLayer.lineWidth = _strokeThickness; } - (CGSize)sizeThatFits:(CGSize)size { return CGSizeMake((self.radius+self.strokeThickness/2+5)*2, (self.radius+self.strokeThickness/2+5)*2); } @end ================================================ FILE: DEMO/SVProgressHUD/SVProgressAnimatedView.h ================================================ // // SVProgressAnimatedView.h // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD // // Copyright (c) 2016 Tobias Tiemerding. All rights reserved. // #import @interface SVProgressAnimatedView : UIView @property (nonatomic, assign) CGFloat radius; @property (nonatomic, assign) CGFloat strokeThickness; @property (nonatomic, strong) UIColor *strokeColor; @property (nonatomic, assign) CGFloat strokeEnd; @end ================================================ FILE: DEMO/SVProgressHUD/SVProgressAnimatedView.m ================================================ // // SVProgressAnimatedView.m // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD // // Copyright (c) 2016 Tobias Tiemerding. All rights reserved. // #import "SVProgressAnimatedView.h" @interface SVProgressAnimatedView () @property (nonatomic, strong) CAShapeLayer *ringAnimatedLayer; @end @implementation SVProgressAnimatedView - (void)willMoveToSuperview:(UIView*)newSuperview { if (newSuperview) { [self layoutAnimatedLayer]; } else { [_ringAnimatedLayer removeFromSuperlayer]; _ringAnimatedLayer = nil; } } - (void)layoutAnimatedLayer { CALayer *layer = self.ringAnimatedLayer; [self.layer addSublayer:layer]; CGFloat widthDiff = CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds); CGFloat heightDiff = CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds); layer.position = CGPointMake(CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds) / 2 - widthDiff / 2, CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds) / 2 - heightDiff / 2); } - (CAShapeLayer*)ringAnimatedLayer { if(!_ringAnimatedLayer) { CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5); UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat)-M_PI_2 endAngle:(CGFloat) (M_PI + M_PI_2) clockwise:YES]; _ringAnimatedLayer = [CAShapeLayer layer]; _ringAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale]; _ringAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2); _ringAnimatedLayer.fillColor = [UIColor clearColor].CGColor; _ringAnimatedLayer.strokeColor = self.strokeColor.CGColor; _ringAnimatedLayer.lineWidth = self.strokeThickness; _ringAnimatedLayer.lineCap = kCALineCapRound; _ringAnimatedLayer.lineJoin = kCALineJoinBevel; _ringAnimatedLayer.path = smoothedPath.CGPath; } return _ringAnimatedLayer; } - (void)setFrame:(CGRect)frame { if(!CGRectEqualToRect(frame, super.frame)) { [super setFrame:frame]; if(self.superview) { [self layoutAnimatedLayer]; } } } - (void)setRadius:(CGFloat)radius { if(radius != _radius) { _radius = radius; [_ringAnimatedLayer removeFromSuperlayer]; _ringAnimatedLayer = nil; if(self.superview) { [self layoutAnimatedLayer]; } } } - (void)setStrokeColor:(UIColor*)strokeColor { _strokeColor = strokeColor; _ringAnimatedLayer.strokeColor = strokeColor.CGColor; } - (void)setStrokeThickness:(CGFloat)strokeThickness { _strokeThickness = strokeThickness; _ringAnimatedLayer.lineWidth = _strokeThickness; } - (void)setStrokeEnd:(CGFloat)strokeEnd { _strokeEnd = strokeEnd; _ringAnimatedLayer.strokeEnd = _strokeEnd; } - (CGSize)sizeThatFits:(CGSize)size { return CGSizeMake((self.radius+self.strokeThickness/2+5)*2, (self.radius+self.strokeThickness/2+5)*2); } @end ================================================ FILE: DEMO/SVProgressHUD/SVProgressHUD-Prefix.pch ================================================ // // Prefix header for all source files of the 'SVProgressHUD' target in the 'SVProgressHUD' project // #ifdef __OBJC__ #import #endif ================================================ FILE: DEMO/SVProgressHUD/SVProgressHUD.h ================================================ // // SVProgressHUD.h // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD // // Copyright (c) 2011-2016 Sam Vermette and contributors. All rights reserved. // #import #import #if __IPHONE_OS_VERSION_MAX_ALLOWED < 70000 #define UI_APPEARANCE_SELECTOR #endif extern NSString * const SVProgressHUDDidReceiveTouchEventNotification; extern NSString * const SVProgressHUDDidTouchDownInsideNotification; extern NSString * const SVProgressHUDWillDisappearNotification; extern NSString * const SVProgressHUDDidDisappearNotification; extern NSString * const SVProgressHUDWillAppearNotification; extern NSString * const SVProgressHUDDidAppearNotification; extern NSString * const SVProgressHUDStatusUserInfoKey; typedef NS_ENUM(NSInteger, SVProgressHUDStyle) { SVProgressHUDStyleLight, // default style, white HUD with black text, HUD background will be blurred on iOS 8 and above SVProgressHUDStyleDark, // black HUD and white text, HUD background will be blurred on iOS 8 and above SVProgressHUDStyleCustom // uses the fore- and background color properties }; typedef NS_ENUM(NSUInteger, SVProgressHUDMaskType) { SVProgressHUDMaskTypeNone = 1, // default mask type, allow user interactions while HUD is displayed SVProgressHUDMaskTypeClear, // don't allow user interactions SVProgressHUDMaskTypeBlack, // don't allow user interactions and dim the UI in the back of the HUD, as on iOS 7 and above SVProgressHUDMaskTypeGradient, // don't allow user interactions and dim the UI with a a-la UIAlertView background gradient, as on iOS 6 SVProgressHUDMaskTypeCustom // don't allow user interactions and dim the UI in the back of the HUD with a custom color }; typedef NS_ENUM(NSUInteger, SVProgressHUDAnimationType) { SVProgressHUDAnimationTypeFlat, // default animation type, custom flat animation (indefinite animated ring) SVProgressHUDAnimationTypeNative // iOS native UIActivityIndicatorView }; @interface SVProgressHUD : UIView #pragma mark - Customization @property (assign, nonatomic) SVProgressHUDStyle defaultStyle UI_APPEARANCE_SELECTOR; // default is SVProgressHUDStyleLight @property (assign, nonatomic) SVProgressHUDMaskType defaultMaskType UI_APPEARANCE_SELECTOR; // default is SVProgressHUDMaskTypeNone @property (assign, nonatomic) SVProgressHUDAnimationType defaultAnimationType UI_APPEARANCE_SELECTOR; // default is SVProgressHUDAnimationTypeFlat @property (assign, nonatomic) CGSize minimumSize UI_APPEARANCE_SELECTOR; // default is CGSizeZero, can be used to avoid resizing for a larger message @property (assign, nonatomic) CGFloat ringThickness UI_APPEARANCE_SELECTOR; // default is 2 pt @property (assign, nonatomic) CGFloat ringRadius UI_APPEARANCE_SELECTOR; // default is 18 pt @property (assign, nonatomic) CGFloat ringNoTextRadius UI_APPEARANCE_SELECTOR; // default is 24 pt @property (assign, nonatomic) CGFloat cornerRadius UI_APPEARANCE_SELECTOR; // default is 14 pt @property (strong, nonatomic) UIFont *font UI_APPEARANCE_SELECTOR; // default is [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] @property (strong, nonatomic) UIColor *backgroundColor UI_APPEARANCE_SELECTOR; // default is [UIColor whiteColor] @property (strong, nonatomic) UIColor *foregroundColor UI_APPEARANCE_SELECTOR; // default is [UIColor blackColor] @property (strong, nonatomic) UIColor *backgroundLayerColor UI_APPEARANCE_SELECTOR; // default is [UIColor colorWithWhite:0 alpha:0.4] @property (strong, nonatomic) UIImage *infoImage UI_APPEARANCE_SELECTOR; // default is the bundled info image provided by Freepik @property (strong, nonatomic) UIImage *successImage UI_APPEARANCE_SELECTOR; // default is the bundled success image provided by Freepik @property (strong, nonatomic) UIImage *errorImage UI_APPEARANCE_SELECTOR; // default is the bundled error image provided by Freepik @property (strong, nonatomic) UIView *viewForExtension UI_APPEARANCE_SELECTOR; // default is nil, only used if #define SV_APP_EXTENSIONS is set @property (assign, nonatomic) NSTimeInterval minimumDismissTimeInterval; // default is 5.0 seconds @property (assign, nonatomic) UIOffset offsetFromCenter UI_APPEARANCE_SELECTOR; // default is 0, 0 @property (assign, nonatomic) NSTimeInterval fadeInAnimationDuration; // default is 0.15 @property (assign, nonatomic) NSTimeInterval fadeOutAnimationDuration; // default is 0.15 + (void)setDefaultStyle:(SVProgressHUDStyle)style; // default is SVProgressHUDStyleLight + (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType; // default is SVProgressHUDMaskTypeNone + (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type; // default is SVProgressHUDAnimationTypeFlat + (void)setMinimumSize:(CGSize)minimumSize; // default is CGSizeZero, can be used to avoid resizing for a larger message + (void)setRingThickness:(CGFloat)ringThickness; // default is 2 pt + (void)setRingRadius:(CGFloat)radius; // default is 18 pt + (void)setRingNoTextRadius:(CGFloat)radius; // default is 24 pt + (void)setCornerRadius:(CGFloat)cornerRadius; // default is 14 pt + (void)setFont:(UIFont*)font; // default is [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] + (void)setForegroundColor:(UIColor*)color; // default is [UIColor blackColor], only used for SVProgressHUDStyleCustom + (void)setBackgroundColor:(UIColor*)color; // default is [UIColor whiteColor], only used for SVProgressHUDStyleCustom + (void)setBackgroundLayerColor:(UIColor*)color; // default is [UIColor colorWithWhite:0 alpha:0.5], only used for SVProgressHUDMaskTypeBlack + (void)setInfoImage:(UIImage*)image; // default is the bundled info image provided by Freepik + (void)setSuccessImage:(UIImage*)image; // default is the bundled success image provided by Freepik + (void)setErrorImage:(UIImage*)image; // default is the bundled error image provided by Freepik + (void)setViewForExtension:(UIView*)view; // default is nil, only used if #define SV_APP_EXTENSIONS is set + (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval; // default is 5.0 seconds + (void)setFadeInAnimationDuration:(NSTimeInterval)duration; // default is 0.15 seconds + (void)setFadeOutAnimationDuration:(NSTimeInterval)duration; // default is 0.15 seconds #pragma mark - Show Methods + (void)show; + (void)showWithMaskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use show and setDefaultMaskType: instead."))); + (void)showWithStatus:(NSString*)status; + (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showWithStatus: and setDefaultMaskType: instead."))); + (void)showProgress:(float)progress; + (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showProgress: and setDefaultMaskType: instead."))); + (void)showProgress:(float)progress status:(NSString*)status; + (void)showProgress:(float)progress status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showProgress:status: and setDefaultMaskType: instead."))); + (void)setStatus:(NSString*)status; // change the HUD loading status while it's showing // stops the activity indicator, shows a glyph + status, and dismisses the HUD a little bit later + (void)showInfoWithStatus:(NSString*)status; + (void)showInfoWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showInfoWithStatus: and setDefaultMaskType: instead."))); + (void)showSuccessWithStatus:(NSString*)status; + (void)showSuccessWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showSuccessWithStatus: and setDefaultMaskType: instead."))); + (void)showErrorWithStatus:(NSString*)status; + (void)showErrorWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showErrorWithStatus: and setDefaultMaskType: instead."))); // shows a image + status, use 28x28 white PNGs + (void)showImage:(UIImage*)image status:(NSString*)status; + (void)showImage:(UIImage*)image status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType __attribute__((deprecated("Use showImage:status: and setDefaultMaskType: instead."))); + (void)setOffsetFromCenter:(UIOffset)offset; + (void)resetOffsetFromCenter; + (void)popActivity; // decrease activity count, if activity count == 0 the HUD is dismissed + (void)dismiss; + (void)dismissWithDelay:(NSTimeInterval)delay; + (BOOL)isVisible; + (NSTimeInterval)displayDurationForString:(NSString*)string; @end ================================================ FILE: DEMO/SVProgressHUD/SVProgressHUD.m ================================================ // // SVProgressHUD.h // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD // // Copyright (c) 2011-2016 Sam Vermette and contributors. All rights reserved. // #if !__has_feature(objc_arc) #error SVProgressHUD is ARC only. Either turn on ARC for the project or use -fobjc-arc flag #endif #import "SVProgressHUD.h" #import "SVIndefiniteAnimatedView.h" #import "SVProgressAnimatedView.h" #import "SVRadialGradientLayer.h" NSString * const SVProgressHUDDidReceiveTouchEventNotification = @"SVProgressHUDDidReceiveTouchEventNotification"; NSString * const SVProgressHUDDidTouchDownInsideNotification = @"SVProgressHUDDidTouchDownInsideNotification"; NSString * const SVProgressHUDWillDisappearNotification = @"SVProgressHUDWillDisappearNotification"; NSString * const SVProgressHUDDidDisappearNotification = @"SVProgressHUDDidDisappearNotification"; NSString * const SVProgressHUDWillAppearNotification = @"SVProgressHUDWillAppearNotification"; NSString * const SVProgressHUDDidAppearNotification = @"SVProgressHUDDidAppearNotification"; NSString * const SVProgressHUDStatusUserInfoKey = @"SVProgressHUDStatusUserInfoKey"; static const CGFloat SVProgressHUDParallaxDepthPoints = 10; static const CGFloat SVProgressHUDUndefinedProgress = -1; static const CGFloat SVProgressHUDDefaultAnimationDuration = 0.15; @interface SVProgressHUD () @property (nonatomic, strong, readonly) NSTimer *fadeOutTimer; @property (nonatomic, readonly, getter = isClear) BOOL clear; @property (nonatomic, strong) UIControl *overlayView; @property (nonatomic, strong) UIView *hudView; @property (nonatomic, strong) UILabel *statusLabel; @property (nonatomic, strong) UIImageView *imageView; @property (nonatomic, strong) UIView *indefiniteAnimatedView; @property (nonatomic, strong) SVProgressAnimatedView *ringView; @property (nonatomic, strong) SVProgressAnimatedView *backgroundRingView; @property (nonatomic, strong) CALayer *backgroundLayer; @property (nonatomic, readwrite) CGFloat progress; @property (nonatomic, readwrite) NSUInteger activityCount; @property (nonatomic, readonly) CGFloat visibleKeyboardHeight; - (void)updateHUDFrame; - (void)updateMask; - (void)updateBlurBounds; #if TARGET_OS_IOS - (void)updateMotionEffectForOrientation:(UIInterfaceOrientation)orientation; #endif - (void)updateMotionEffectForXMotionEffectType:(UIInterpolatingMotionEffectType)xMotionEffectType yMotionEffectType:(UIInterpolatingMotionEffectType)yMotionEffectType; - (void)updateViewHierachy; - (void)setStatus:(NSString*)status; - (void)setFadeOutTimer:(NSTimer*)timer; - (void)registerNotifications; - (NSDictionary*)notificationUserInfo; - (void)positionHUD:(NSNotification*)notification; - (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle; - (void)overlayViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent*)event; - (void)showProgress:(float)progress status:(NSString*)status; - (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration; - (void)showStatus:(NSString*)status; - (void)dismiss; - (void)dismissWithDelay:(NSTimeInterval)delay; - (UIView*)indefiniteAnimatedView; - (SVProgressAnimatedView*)ringView; - (SVProgressAnimatedView*)backgroundRingView; - (void)cancelRingLayerAnimation; - (void)cancelIndefiniteAnimatedViewAnimation; - (UIColor*)foregroundColorForStyle; - (UIColor*)backgroundColorForStyle; - (UIImage*)image:(UIImage*)image withTintColor:(UIColor*)color; @end @implementation SVProgressHUD { BOOL _isInitializing; } + (SVProgressHUD*)sharedView { static dispatch_once_t once; static SVProgressHUD *sharedView; #if !defined(SV_APP_EXTENSIONS) dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[[UIApplication sharedApplication] delegate] window].bounds]; }); #else dispatch_once(&once, ^{ sharedView = [[self alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; }); #endif return sharedView; } #pragma mark - Setters + (void)setStatus:(NSString*)status { [[self sharedView] setStatus:status]; } + (void)setDefaultStyle:(SVProgressHUDStyle)style { [self sharedView].defaultStyle = style; } + (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType { [self sharedView].defaultMaskType = maskType; } + (void)setDefaultAnimationType:(SVProgressHUDAnimationType)type { [self sharedView].defaultAnimationType = type; } + (void)setMinimumSize:(CGSize)minimumSize { [self sharedView].minimumSize = minimumSize; } + (void)setRingThickness:(CGFloat)ringThickness { [self sharedView].ringThickness = ringThickness; } + (void)setRingRadius:(CGFloat)radius { [self sharedView].ringRadius = radius; } + (void)setRingNoTextRadius:(CGFloat)radius { [self sharedView].ringNoTextRadius = radius; } + (void)setCornerRadius:(CGFloat)cornerRadius { [self sharedView].cornerRadius = cornerRadius; } + (void)setFont:(UIFont*)font { [self sharedView].font = font; } + (void)setForegroundColor:(UIColor*)color { [self sharedView].foregroundColor = color; } + (void)setBackgroundColor:(UIColor*)color { [self sharedView].backgroundColor = color; } + (void)setBackgroundLayerColor:(UIColor*)color { [self sharedView].backgroundLayerColor = color; } + (void)setInfoImage:(UIImage*)image { [self sharedView].infoImage = image; } + (void)setSuccessImage:(UIImage*)image { [self sharedView].successImage = image; } + (void)setErrorImage:(UIImage*)image { [self sharedView].errorImage = image; } + (void)setViewForExtension:(UIView*)view { [self sharedView].viewForExtension = view; } + (void)setMinimumDismissTimeInterval:(NSTimeInterval)interval { [self sharedView].minimumDismissTimeInterval = interval; } + (void)setFadeInAnimationDuration:(NSTimeInterval)duration { [self sharedView].fadeInAnimationDuration = duration; } + (void)setFadeOutAnimationDuration:(NSTimeInterval)duration { [self sharedView].fadeOutAnimationDuration = duration; } #pragma mark - Show Methods + (void)show { [self showWithStatus:nil]; } + (void)showWithMaskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self show]; [self setDefaultMaskType:existingMaskType]; } + (void)showWithStatus:(NSString*)status { [self sharedView]; [self showProgress:SVProgressHUDUndefinedProgress status:status]; } + (void)showWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showWithStatus:status]; [self setDefaultMaskType:existingMaskType]; } + (void)showProgress:(float)progress { [self showProgress:progress status:nil]; } + (void)showProgress:(float)progress maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showProgress:progress]; [self setDefaultMaskType:existingMaskType]; } + (void)showProgress:(float)progress status:(NSString*)status { [[self sharedView] showProgress:progress status:status]; } + (void)showProgress:(float)progress status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showProgress:progress status:status]; [self setDefaultMaskType:existingMaskType]; } #pragma mark - Show, then automatically dismiss methods + (void)showInfoWithStatus:(NSString*)status { [self showImage:[self sharedView].infoImage status:status]; } + (void)showInfoWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showInfoWithStatus:status]; [self setDefaultMaskType:existingMaskType]; } + (void)showSuccessWithStatus:(NSString*)status { [self showImage:[self sharedView].successImage status:status]; } + (void)showSuccessWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showSuccessWithStatus:status]; [self setDefaultMaskType:existingMaskType]; } + (void)showErrorWithStatus:(NSString*)status { [self showImage:[self sharedView].errorImage status:status]; } + (void)showErrorWithStatus:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showErrorWithStatus:status]; [self setDefaultMaskType:existingMaskType]; } + (void)showImage:(UIImage*)image status:(NSString*)status { NSTimeInterval displayInterval = [self displayDurationForString:status]; [[self sharedView] showImage:image status:status duration:displayInterval]; } + (void)showImage:(UIImage*)image status:(NSString*)status maskType:(SVProgressHUDMaskType)maskType { SVProgressHUDMaskType existingMaskType = [self sharedView].defaultMaskType; [self setDefaultMaskType:maskType]; [self showImage:image status:status]; [self setDefaultMaskType:existingMaskType]; } #pragma mark - Dismiss Methods + (void)popActivity { if([self sharedView].activityCount > 0) { [self sharedView].activityCount--; } if([self sharedView].activityCount == 0) { [[self sharedView] dismiss]; } } + (void)dismiss { [self dismissWithDelay:0.0]; } + (void)dismissWithDelay:(NSTimeInterval)delay { [[self sharedView] dismissWithDelay:delay]; } #pragma mark - Offset + (void)setOffsetFromCenter:(UIOffset)offset { [self sharedView].offsetFromCenter = offset; } + (void)resetOffsetFromCenter { [self setOffsetFromCenter:UIOffsetZero]; } #pragma mark - Instance Methods - (instancetype)initWithFrame:(CGRect)frame { if((self = [super initWithFrame:frame])) { _isInitializing = YES; self.userInteractionEnabled = NO; _backgroundColor = [UIColor clearColor]; _foregroundColor = [UIColor blackColor]; _backgroundLayerColor = [UIColor colorWithWhite:0 alpha:0.4]; self.alpha = 0.0f; self.activityCount = 0; // Set default values _defaultMaskType = SVProgressHUDMaskTypeNone; _defaultStyle = SVProgressHUDStyleLight; _defaultAnimationType = SVProgressHUDAnimationTypeFlat; if ([UIFont respondsToSelector:@selector(preferredFontForTextStyle:)]) { _font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; } else { _font = [UIFont systemFontOfSize:14.0f]; } NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]]; NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"]; NSBundle *imageBundle = [NSBundle bundleWithURL:url]; UIImage* infoImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"info" ofType:@"png"]]; UIImage* successImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"success" ofType:@"png"]]; UIImage* errorImage = [UIImage imageWithContentsOfFile:[imageBundle pathForResource:@"error" ofType:@"png"]]; if ([[UIImage class] instancesRespondToSelector:@selector(imageWithRenderingMode:)]) { _infoImage = [infoImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; _successImage = [successImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; _errorImage = [errorImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; } else { _infoImage = infoImage; _successImage = successImage; _errorImage = errorImage; } _ringThickness = 2.0f; _ringRadius = 18.0f; _ringNoTextRadius = 24.0f; _cornerRadius = 14.0f; _minimumDismissTimeInterval = 5.0; _fadeInAnimationDuration = SVProgressHUDDefaultAnimationDuration; _fadeOutAnimationDuration = SVProgressHUDDefaultAnimationDuration; // Accessibility support self.accessibilityIdentifier = @"SVProgressHUD"; self.accessibilityLabel = @"SVProgressHUD"; self.isAccessibilityElement = YES; _isInitializing = NO; } return self; } - (void)updateHUDFrame { // For the beginning use default values, these // might get update if string is too large etc. CGFloat hudWidth = 100.0f; CGFloat hudHeight = 100.0f; CGFloat stringHeightBuffer = 20.0f; CGFloat stringAndContentHeightBuffer = 80.0f; CGRect labelRect = CGRectZero; // Check if an image or progress ring is displayed BOOL imageUsed = (self.imageView.image) && !(self.imageView.hidden); BOOL progressUsed = self.imageView.hidden; // Calculate size of string and update HUD size NSString *string = self.statusLabel.text; if(string) { CGSize constraintSize = CGSizeMake(200.0f, 300.0f); CGRect stringRect; if([string respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) { stringRect = [string boundingRectWithSize:constraintSize options:(NSStringDrawingOptions)(NSStringDrawingUsesFontLeading|NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin) attributes:@{NSFontAttributeName: self.statusLabel.font} context:NULL]; } else { CGSize stringSize; if([string respondsToSelector:@selector(sizeWithAttributes:)]) { stringSize = [string sizeWithAttributes:@{NSFontAttributeName:[UIFont fontWithName:self.statusLabel.font.fontName size:self.statusLabel.font.pointSize]}]; } else { #if TARGET_OS_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated" stringSize = [string sizeWithFont:self.statusLabel.font constrainedToSize:CGSizeMake(200.0f, 300.0f)]; #pragma clang diagnostic pop #endif } stringRect = CGRectMake(0.0f, 0.0f, stringSize.width, stringSize.height); } CGFloat stringWidth = stringRect.size.width; CGFloat stringHeight = ceilf(CGRectGetHeight(stringRect)); if(imageUsed || progressUsed) { hudHeight = stringAndContentHeightBuffer + stringHeight; } else { hudHeight = stringHeightBuffer + stringHeight; } if(stringWidth > hudWidth) { hudWidth = ceilf(stringWidth/2)*2; } CGFloat labelRectY = (imageUsed || progressUsed) ? 68.0f : 9.0f; if(hudHeight > 100.0f) { labelRect = CGRectMake(12.0f, labelRectY, hudWidth, stringHeight); hudWidth += 24.0f; } else { hudWidth += 24.0f; labelRect = CGRectMake(0.0f, labelRectY, hudWidth, stringHeight); } } // Update values on subviews self.hudView.bounds = CGRectMake(0.0f, 0.0f, MAX(self.minimumSize.width, hudWidth), MAX(self.minimumSize.height, hudHeight)); labelRect.size.width += MAX(0, self.minimumSize.width - hudWidth); [self updateBlurBounds]; if(string) { self.imageView.center = CGPointMake(CGRectGetWidth(self.hudView.bounds)/2, 36.0f); } else { self.imageView.center = CGPointMake(CGRectGetWidth(self.hudView.bounds)/2, CGRectGetHeight(self.hudView.bounds)/2); } self.statusLabel.hidden = NO; self.statusLabel.frame = labelRect; // Animate value update [CATransaction begin]; [CATransaction setDisableActions:YES]; if(string) { if(self.defaultAnimationType == SVProgressHUDAnimationTypeFlat) { SVIndefiniteAnimatedView *indefiniteAnimationView = (SVIndefiniteAnimatedView*)self.indefiniteAnimatedView; indefiniteAnimationView.radius = self.ringRadius; [indefiniteAnimationView sizeToFit]; } CGPoint center = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), 36.0f); self.indefiniteAnimatedView.center = center; if(self.progress != SVProgressHUDUndefinedProgress) { self.backgroundRingView.center = self.ringView.center = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), 36.0f); } } else { if(self.defaultAnimationType == SVProgressHUDAnimationTypeFlat) { SVIndefiniteAnimatedView *indefiniteAnimationView = (SVIndefiniteAnimatedView*)self.indefiniteAnimatedView; indefiniteAnimationView.radius = self.ringNoTextRadius; [indefiniteAnimationView sizeToFit]; } CGPoint center = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), CGRectGetHeight(self.hudView.bounds)/2); self.indefiniteAnimatedView.center = center; if(self.progress != SVProgressHUDUndefinedProgress) { self.backgroundRingView.center = self.ringView.center = CGPointMake((CGRectGetWidth(self.hudView.bounds)/2), CGRectGetHeight(self.hudView.bounds)/2); } } [CATransaction commit]; } - (void)updateMask { if(self.backgroundLayer) { [self.backgroundLayer removeFromSuperlayer]; self.backgroundLayer = nil; } switch (self.defaultMaskType) { case SVProgressHUDMaskTypeCustom: case SVProgressHUDMaskTypeBlack:{ self.backgroundLayer = [CALayer layer]; self.backgroundLayer.frame = self.bounds; self.backgroundLayer.backgroundColor = self.defaultMaskType == SVProgressHUDMaskTypeCustom ? self.backgroundLayerColor.CGColor : [UIColor colorWithWhite:0 alpha:0.4].CGColor; [self.backgroundLayer setNeedsDisplay]; [self.layer insertSublayer:self.backgroundLayer atIndex:0]; break; } case SVProgressHUDMaskTypeGradient:{ SVRadialGradientLayer *layer = [SVRadialGradientLayer layer]; self.backgroundLayer = layer; self.backgroundLayer.frame = self.bounds; CGPoint gradientCenter = self.center; gradientCenter.y = (self.bounds.size.height - self.visibleKeyboardHeight)/2; layer.gradientCenter = gradientCenter; [self.backgroundLayer setNeedsDisplay]; [self.layer insertSublayer:self.backgroundLayer atIndex:0]; break; } default: break; } } - (void)updateBlurBounds { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 if(NSClassFromString(@"UIBlurEffect") && self.defaultStyle != SVProgressHUDStyleCustom) { // Remove background color, else the effect would not work self.hudView.backgroundColor = [UIColor clearColor]; // Remove any old instances of UIVisualEffectViews for (UIView *subview in self.hudView.subviews) { if([subview isKindOfClass:[UIVisualEffectView class]]) { [subview removeFromSuperview]; } } if(self.backgroundColor != [UIColor clearColor]) { // Create blur effect UIBlurEffectStyle blurEffectStyle = self.defaultStyle == SVProgressHUDStyleDark ? UIBlurEffectStyleDark : UIBlurEffectStyleLight; UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:blurEffectStyle]; UIVisualEffectView *blurEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; blurEffectView.autoresizingMask = self.hudView.autoresizingMask; blurEffectView.frame = self.hudView.bounds; // Add vibrancy to the blur effect to make it more vivid UIVibrancyEffect *vibrancyEffect = [UIVibrancyEffect effectForBlurEffect:blurEffect]; UIVisualEffectView *vibrancyEffectView = [[UIVisualEffectView alloc] initWithEffect:vibrancyEffect]; vibrancyEffectView.autoresizingMask = blurEffectView.autoresizingMask; vibrancyEffectView.bounds = blurEffectView.bounds; [blurEffectView.contentView addSubview:vibrancyEffectView]; [self.hudView insertSubview:blurEffectView atIndex:0]; } } #endif } #if TARGET_OS_IOS - (void)updateMotionEffectForOrientation:(UIInterfaceOrientation)orientation { UIInterpolatingMotionEffectType xMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis : UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis; UIInterpolatingMotionEffectType yMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis : UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis; [self updateMotionEffectForXMotionEffectType:xMotionEffectType yMotionEffectType:yMotionEffectType]; } #endif - (void)updateMotionEffectForXMotionEffectType:(UIInterpolatingMotionEffectType)xMotionEffectType yMotionEffectType:(UIInterpolatingMotionEffectType)yMotionEffectType { if([self.hudView respondsToSelector:@selector(addMotionEffect:)]) { UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:xMotionEffectType]; effectX.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints); effectX.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints); UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:yMotionEffectType]; effectY.minimumRelativeValue = @(-SVProgressHUDParallaxDepthPoints); effectY.maximumRelativeValue = @(SVProgressHUDParallaxDepthPoints); UIMotionEffectGroup *effectGroup = [[UIMotionEffectGroup alloc] init]; effectGroup.motionEffects = @[effectX, effectY]; // Clear old motion effect, then add new motion effects self.hudView.motionEffects = @[]; [self.hudView addMotionEffect:effectGroup]; } } - (void)updateViewHierachy { // Add the overlay (e.g. black, gradient) to the application window if necessary if(!self.overlayView.superview) { #if !defined(SV_APP_EXTENSIONS) // Default case: iterate over UIApplication windows NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator]; for (UIWindow *window in frontToBackWindows) { BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen; BOOL windowIsVisible = !window.hidden && window.alpha > 0; BOOL windowLevelNormal = window.windowLevel == UIWindowLevelNormal; if(windowOnMainScreen && windowIsVisible && windowLevelNormal) { [window addSubview:self.overlayView]; break; } } #else // If SVProgressHUD ist used inside an app extension add it to the given view if(self.viewForExtension) { [self.viewForExtension addSubview:self.overlayView]; } #endif } else { // The HUD is already on screen, but maybot not in front. Therefore // ensure that overlay will be on top of rootViewController (which may // be changed during runtime). [self.overlayView.superview bringSubviewToFront:self.overlayView]; } // Add self to the overlay view if(!self.superview){ [self.overlayView addSubview:self]; } if(!self.hudView.superview) { [self addSubview:self.hudView]; } } - (void)setStatus:(NSString*)status { self.statusLabel.text = status; [self updateHUDFrame]; } - (void)setFadeOutTimer:(NSTimer*)timer { if(_fadeOutTimer) { [_fadeOutTimer invalidate], _fadeOutTimer = nil; } if(timer) { _fadeOutTimer = timer; } } #pragma mark - Notifications and their handling - (void)registerNotifications { #if TARGET_OS_IOS [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardWillHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardDidHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardWillShowNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIKeyboardDidShowNotification object:nil]; #endif [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(positionHUD:) name:UIApplicationDidBecomeActiveNotification object:nil]; } - (NSDictionary*)notificationUserInfo{ return (self.statusLabel.text ? @{SVProgressHUDStatusUserInfoKey : self.statusLabel.text} : nil); } - (void)positionHUD:(NSNotification*)notification { CGFloat keyboardHeight = 0.0f; double animationDuration = 0.0; #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS self.frame = [[[UIApplication sharedApplication] delegate] window].bounds; UIInterfaceOrientation orientation = UIApplication.sharedApplication.statusBarOrientation; #elif !defined(SV_APP_EXTENSIONS) self.frame = [UIApplication sharedApplication].keyWindow.bounds; #else if (self.viewForExtension) { self.frame = self.viewForExtension.frame; } else { self.frame = UIScreen.mainScreen.bounds; } UIInterfaceOrientation orientation = CGRectGetWidth(self.frame) > CGRectGetHeight(self.frame) ? UIInterfaceOrientationLandscapeLeft : UIInterfaceOrientationPortrait; #endif // 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. BOOL ignoreOrientation = NO; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 if([[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) { ignoreOrientation = YES; } #endif #if TARGET_OS_IOS // Get keyboardHeight in regards to current state if(notification) { NSDictionary* keyboardInfo = [notification userInfo]; CGRect keyboardFrame = [keyboardInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue]; animationDuration = [keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; if(notification.name == UIKeyboardWillShowNotification || notification.name == UIKeyboardDidShowNotification) { keyboardHeight = CGRectGetWidth(keyboardFrame); if(ignoreOrientation || UIInterfaceOrientationIsPortrait(orientation)) { keyboardHeight = CGRectGetHeight(keyboardFrame); } } } else { keyboardHeight = self.visibleKeyboardHeight; } #endif // Get the currently active frame of the display (depends on orientation) CGRect orientationFrame = self.bounds; #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS CGRect statusBarFrame = UIApplication.sharedApplication.statusBarFrame; #else CGRect statusBarFrame = CGRectZero; #endif #if TARGET_OS_IOS if(!ignoreOrientation && UIInterfaceOrientationIsLandscape(orientation)) { float temp = CGRectGetWidth(orientationFrame); orientationFrame.size.width = CGRectGetHeight(orientationFrame); orientationFrame.size.height = temp; temp = CGRectGetWidth(statusBarFrame); statusBarFrame.size.width = CGRectGetHeight(statusBarFrame); statusBarFrame.size.height = temp; } // Update the motion effects in regards to orientation [self updateMotionEffectForOrientation:orientation]; #else [self updateMotionEffectForXMotionEffectType:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis yMotionEffectType:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; #endif // Calculate available height for display CGFloat activeHeight = CGRectGetHeight(orientationFrame); if(keyboardHeight > 0) { activeHeight += CGRectGetHeight(statusBarFrame)*2; } activeHeight -= keyboardHeight; CGFloat posX = CGRectGetWidth(orientationFrame)/2.0f; CGFloat posY = floorf(activeHeight*0.45f); CGFloat rotateAngle = 0.0; CGPoint newCenter = CGPointMake(posX, posY); // Update posX and posY in regards to orientation #if TARGET_OS_IOS if(!ignoreOrientation) { switch (orientation) { case UIInterfaceOrientationPortraitUpsideDown: rotateAngle = (CGFloat) M_PI; newCenter = CGPointMake(posX, CGRectGetHeight(orientationFrame)-posY); break; case UIInterfaceOrientationLandscapeLeft: rotateAngle = (CGFloat) (-M_PI/2.0f); newCenter = CGPointMake(posY, posX); break; case UIInterfaceOrientationLandscapeRight: rotateAngle = (CGFloat) (M_PI/2.0f); newCenter = CGPointMake(CGRectGetHeight(orientationFrame)-posY, posX); break; default: // Same as UIInterfaceOrientationPortrait rotateAngle = 0.0f; newCenter = CGPointMake(posX, posY); break; } } #endif if(notification) { // Animate update if notification was present __weak SVProgressHUD *weakSelf = self; [UIView animateWithDuration:animationDuration delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{ __strong SVProgressHUD *strongSelf = weakSelf; if(strongSelf) { [strongSelf moveToPoint:newCenter rotateAngle:rotateAngle]; [strongSelf.hudView setNeedsDisplay]; } } completion:NULL]; } else { [self moveToPoint:newCenter rotateAngle:rotateAngle]; [self.hudView setNeedsDisplay]; } [self updateMask]; } - (void)moveToPoint:(CGPoint)newCenter rotateAngle:(CGFloat)angle { self.hudView.transform = CGAffineTransformMakeRotation(angle); self.hudView.center = CGPointMake(newCenter.x + self.offsetFromCenter.horizontal, newCenter.y + self.offsetFromCenter.vertical); } #pragma mark - Event handling - (void)overlayViewDidReceiveTouchEvent:(id)sender forEvent:(UIEvent*)event { [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidReceiveTouchEventNotification object:self userInfo:[self notificationUserInfo]]; UITouch *touch = event.allTouches.anyObject; CGPoint touchLocation = [touch locationInView:self]; if(CGRectContainsPoint(self.hudView.frame, touchLocation)) { [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidTouchDownInsideNotification object:self userInfo:[self notificationUserInfo]]; } } #pragma mark - Master show/dismiss methods - (void)showProgress:(float)progress status:(NSString*)status { __weak SVProgressHUD *weakSelf = self; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ __strong SVProgressHUD *strongSelf = weakSelf; if(strongSelf){ // Update / Check view hierachy to ensure the HUD is visible [strongSelf updateViewHierachy]; // Reset imageView and fadeout timer if an image is currently displayed strongSelf.imageView.hidden = YES; strongSelf.imageView.image = nil; if(strongSelf.fadeOutTimer) { strongSelf.activityCount = 0; } strongSelf.fadeOutTimer = nil; // Update text and set progress to the given value strongSelf.statusLabel.text = status; strongSelf.progress = progress; // Choose the "right" indicator depending on the progress if(progress >= 0) { // Cancel the indefiniteAnimatedView, then show the ringLayer [strongSelf cancelIndefiniteAnimatedViewAnimation]; // Add ring to HUD and set progress [strongSelf.hudView addSubview:strongSelf.ringView]; [strongSelf.hudView addSubview:strongSelf.backgroundRingView]; strongSelf.ringView.strokeEnd = progress; // Updat the activity count if(progress == 0) { strongSelf.activityCount++; } } else { // Cancel the ringLayer animation, then show the indefiniteAnimatedView [strongSelf cancelRingLayerAnimation]; // Add indefiniteAnimatedView to HUD [strongSelf.hudView addSubview:strongSelf.indefiniteAnimatedView]; if([strongSelf.indefiniteAnimatedView respondsToSelector:@selector(startAnimating)]) { [(id)strongSelf.indefiniteAnimatedView startAnimating]; } // Update the activity count strongSelf.activityCount++; } // Show [strongSelf showStatus:status]; } }]; } - (void)showImage:(UIImage*)image status:(NSString*)status duration:(NSTimeInterval)duration { __weak SVProgressHUD *weakSelf = self; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ __strong SVProgressHUD *strongSelf = weakSelf; if(strongSelf){ // Update / Check view hierachy to ensure the HUD is visible [strongSelf updateViewHierachy]; // Reset progress and cancel any running animation strongSelf.progress = SVProgressHUDUndefinedProgress; [strongSelf cancelRingLayerAnimation]; [strongSelf cancelIndefiniteAnimatedViewAnimation]; // Update imageView UIColor *tintColor = strongSelf.foregroundColorForStyle; UIImage *tintedImage = image; if([strongSelf.imageView respondsToSelector:@selector(setTintColor:)]) { if (tintedImage.renderingMode != UIImageRenderingModeAlwaysTemplate) { tintedImage = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; } strongSelf.imageView.tintColor = tintColor; } else { tintedImage = [strongSelf image:image withTintColor:tintColor]; } strongSelf.imageView.image = tintedImage; strongSelf.imageView.hidden = NO; // Update text strongSelf.statusLabel.text = status; // Show [strongSelf showStatus:status]; // An image will dismissed automatically. Therefore we start a timer // which then will call dismiss after the predefined duration strongSelf.fadeOutTimer = [NSTimer timerWithTimeInterval:duration target:strongSelf selector:@selector(dismiss) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:strongSelf.fadeOutTimer forMode:NSRunLoopCommonModes]; } }]; } - (void)showStatus:(NSString*)status { // Update the HUDs frame to the new content and position HUD [self updateHUDFrame]; [self positionHUD:nil]; // Update accesibilty as well as user interaction if(self.defaultMaskType != SVProgressHUDMaskTypeNone) { self.overlayView.userInteractionEnabled = YES; self.accessibilityLabel = status; self.isAccessibilityElement = YES; } else { self.overlayView.userInteractionEnabled = NO; self.hudView.accessibilityLabel = status; self.hudView.isAccessibilityElement = YES; } // Show overlay self.overlayView.backgroundColor = [UIColor clearColor]; // Show if not already visible (depending on alpha) if(self.alpha != 1.0f || self.hudView.alpha != 1.0f) { // Post notification to inform user [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillAppearNotification object:self userInfo:[self notificationUserInfo]]; // Zoom HUD a little to make a nice appear / pop up animation self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 1.3, 1.3); // Set initial values to handle iOS 7 (and above) UIToolbar which not answers well to hierarchy opacity change self.alpha = 0.0f; self.hudView.alpha = 0.0f; // Define blocks __weak SVProgressHUD *weakSelf = self; __block void (^animationsBlock)(void) = ^{ __strong SVProgressHUD *strongSelf = weakSelf; if(strongSelf) { // Shrink HUD to finish pop up animation strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 1/1.3f, 1/1.3f); strongSelf.alpha = 1.0f; strongSelf.hudView.alpha = 1.0f; } }; __block void (^completionBlock)(void) = ^{ __strong SVProgressHUD *strongSelf = weakSelf; if(strongSelf) { /// Register observer <=> we now have to handle orientation changes etc. [strongSelf registerNotifications]; // Post notification to inform user [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidAppearNotification object:strongSelf userInfo:[strongSelf notificationUserInfo]]; } // Update accesibilty UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, status); }; if (self.fadeInAnimationDuration > 0) { // Animate appearance [UIView animateWithDuration:self.fadeInAnimationDuration delay:0 options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState) animations:^{ animationsBlock(); } completion:^(BOOL finished) { completionBlock(); }]; } else { animationsBlock(); completionBlock(); } // Inform iOS to redraw the view hierachy [self setNeedsDisplay]; } } - (void)dismiss { [self dismissWithDelay:0]; } - (void)dismissWithDelay:(NSTimeInterval)delay { __weak SVProgressHUD *weakSelf = self; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ __strong SVProgressHUD *strongSelf = weakSelf; if(strongSelf){ // Dismiss if visible (depending on alpha) if(strongSelf.alpha != 0.0f || strongSelf.hudView.alpha != 0.0f){ // Post notification to inform user [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillDisappearNotification object:nil userInfo:[strongSelf notificationUserInfo]]; // Reset activitiy count strongSelf.activityCount = 0; // Define blocks __block void (^animationsBlock)(void) = ^{ strongSelf.hudView.transform = CGAffineTransformScale(strongSelf.hudView.transform, 0.8f, 0.8f); strongSelf.alpha = 0.0f; strongSelf.hudView.alpha = 0.0f; }; __block void (^completionBlock)(void) = ^{ // Clean up view hierachy (overlays) [strongSelf.overlayView removeFromSuperview]; [strongSelf.hudView removeFromSuperview]; [strongSelf removeFromSuperview]; // Reset progress and cancel any running animation strongSelf.progress = SVProgressHUDUndefinedProgress; [strongSelf cancelRingLayerAnimation]; [strongSelf cancelIndefiniteAnimatedViewAnimation]; // Remove observer <=> we do not have to handle orientation changes etc. [[NSNotificationCenter defaultCenter] removeObserver:strongSelf]; // Post notification to inform user [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification object:strongSelf userInfo:[strongSelf notificationUserInfo]]; // Tell the rootViewController to update the StatusBar appearance #if !defined(SV_APP_EXTENSIONS) && TARGET_OS_IOS UIViewController *rootController = [[UIApplication sharedApplication] keyWindow].rootViewController; if([rootController respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) { [rootController setNeedsStatusBarAppearanceUpdate]; } #endif // Update accesibilty UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); }; if (strongSelf.fadeOutAnimationDuration > 0) { // Animate appearance [UIView animateWithDuration:strongSelf.fadeOutAnimationDuration delay:delay options:(UIViewAnimationOptions) (UIViewAnimationOptionAllowUserInteraction | UIViewAnimationCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState) animations:^{ animationsBlock(); } completion:^(BOOL finished) { completionBlock(); }]; } else { animationsBlock(); completionBlock(); } // Inform iOS to redraw the view hierachy [strongSelf setNeedsDisplay]; } } }]; } #pragma mark - Ring progress animation - (UIView*)indefiniteAnimatedView { // Get the correct spinner for defaultAnimationType if(self.defaultAnimationType == SVProgressHUDAnimationTypeFlat){ // Check if spinner exists and is an object of different class if(_indefiniteAnimatedView && ![_indefiniteAnimatedView isKindOfClass:[SVIndefiniteAnimatedView class]]){ [_indefiniteAnimatedView removeFromSuperview]; _indefiniteAnimatedView = nil; } if(!_indefiniteAnimatedView){ _indefiniteAnimatedView = [[SVIndefiniteAnimatedView alloc] initWithFrame:CGRectZero]; } // Update styling SVIndefiniteAnimatedView *indefiniteAnimatedView = (SVIndefiniteAnimatedView*)_indefiniteAnimatedView; indefiniteAnimatedView.strokeColor = self.foregroundColorForStyle; indefiniteAnimatedView.strokeThickness = self.ringThickness; indefiniteAnimatedView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; } else { // Check if spinner exists and is an object of different class if(_indefiniteAnimatedView && ![_indefiniteAnimatedView isKindOfClass:[UIActivityIndicatorView class]]){ [_indefiniteAnimatedView removeFromSuperview]; _indefiniteAnimatedView = nil; } if(!_indefiniteAnimatedView){ _indefiniteAnimatedView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; } // Update styling UIActivityIndicatorView *activityIndicatorView = (UIActivityIndicatorView*)_indefiniteAnimatedView; activityIndicatorView.color = self.foregroundColorForStyle; } [_indefiniteAnimatedView sizeToFit]; return _indefiniteAnimatedView; } - (SVProgressAnimatedView*)ringView { if(!_ringView) { _ringView = [[SVProgressAnimatedView alloc] initWithFrame:CGRectZero]; } // Update styling _ringView.strokeColor = self.foregroundColorForStyle; _ringView.strokeThickness = self.ringThickness; _ringView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; return _ringView; } - (SVProgressAnimatedView*)backgroundRingView { if(!_backgroundRingView) { _backgroundRingView = [[SVProgressAnimatedView alloc] initWithFrame:CGRectZero]; _backgroundRingView.strokeEnd = 1.0f; } // Update styling _backgroundRingView.strokeColor = [self.foregroundColorForStyle colorWithAlphaComponent:0.1f]; _backgroundRingView.strokeThickness = self.ringThickness; _backgroundRingView.radius = self.statusLabel.text ? self.ringRadius : self.ringNoTextRadius; return _backgroundRingView; } - (void)cancelRingLayerAnimation { // Animate value update, stop animation [CATransaction begin]; [CATransaction setDisableActions:YES]; [self.hudView.layer removeAllAnimations]; self.ringView.strokeEnd = 0.0f; [CATransaction commit]; // Remove from view [self.ringView removeFromSuperview]; [self.backgroundRingView removeFromSuperview]; } - (void)cancelIndefiniteAnimatedViewAnimation { // Stop animation if([self.indefiniteAnimatedView respondsToSelector:@selector(stopAnimating)]) { [(id)self.indefiniteAnimatedView stopAnimating]; } // Remove from view [self.indefiniteAnimatedView removeFromSuperview]; } #pragma mark - Utilities + (BOOL)isVisible { return ([self sharedView].alpha > 0); } #pragma mark - Getters + (NSTimeInterval)displayDurationForString:(NSString*)string { return MAX((float)string.length * 0.06 + 0.5, [self sharedView].minimumDismissTimeInterval); } - (UIColor*)foregroundColorForStyle { if(self.defaultStyle == SVProgressHUDStyleLight) { return [UIColor blackColor]; } else if(self.defaultStyle == SVProgressHUDStyleDark) { return [UIColor whiteColor]; } else { return self.foregroundColor; } } - (UIColor*)backgroundColorForStyle { if(self.defaultStyle == SVProgressHUDStyleLight) { return [UIColor whiteColor]; } else if(self.defaultStyle == SVProgressHUDStyleDark) { return [UIColor blackColor]; } else { return self.backgroundColor; } } - (UIImage*)image:(UIImage*)image withTintColor:(UIColor*)color { CGRect rect = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height); UIGraphicsBeginImageContextWithOptions(rect.size, NO, image.scale); CGContextRef c = UIGraphicsGetCurrentContext(); [image drawInRect:rect]; CGContextSetFillColorWithColor(c, [color CGColor]); CGContextSetBlendMode(c, kCGBlendModeSourceAtop); CGContextFillRect(c, rect); UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return tintedImage; } - (BOOL)isClear { // used for iOS 7 and above return (self.defaultMaskType == SVProgressHUDMaskTypeClear || self.defaultMaskType == SVProgressHUDMaskTypeNone); } - (UIControl*)overlayView { if(!_overlayView) { #if !defined(SV_APP_EXTENSIONS) CGRect windowBounds = [[[UIApplication sharedApplication] delegate] window].bounds; _overlayView = [[UIControl alloc] initWithFrame:windowBounds]; #else _overlayView = [[UIControl alloc] initWithFrame:[UIScreen mainScreen].bounds]; #endif _overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; _overlayView.backgroundColor = [UIColor clearColor]; [_overlayView addTarget:self action:@selector(overlayViewDidReceiveTouchEvent:forEvent:) forControlEvents:UIControlEventTouchDown]; } return _overlayView; } - (UIView*)hudView { if(!_hudView) { _hudView = [[UIView alloc] initWithFrame:CGRectZero]; _hudView.layer.masksToBounds = YES; _hudView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin; } // Update styling _hudView.layer.cornerRadius = self.cornerRadius; _hudView.backgroundColor = self.backgroundColorForStyle; return _hudView; } - (UILabel*)statusLabel { if(!_statusLabel) { _statusLabel = [[UILabel alloc] initWithFrame:CGRectZero]; _statusLabel.backgroundColor = [UIColor clearColor]; _statusLabel.adjustsFontSizeToFitWidth = YES; _statusLabel.textAlignment = NSTextAlignmentCenter; _statusLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters; _statusLabel.numberOfLines = 0; } if(!_statusLabel.superview) { [self.hudView addSubview:_statusLabel]; } // Update styling _statusLabel.textColor = self.foregroundColorForStyle; _statusLabel.font = self.font; return _statusLabel; } - (UIImageView*)imageView { if(!_imageView) { _imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 28.0f, 28.0f)]; } if(!_imageView.superview) { [self.hudView addSubview:_imageView]; } return _imageView; } - (CGFloat)visibleKeyboardHeight { #if !defined(SV_APP_EXTENSIONS) UIWindow *keyboardWindow = nil; for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) { if(![[testWindow class] isEqual:[UIWindow class]]) { keyboardWindow = testWindow; break; } } for (__strong UIView *possibleKeyboard in [keyboardWindow subviews]) { if([possibleKeyboard isKindOfClass:NSClassFromString(@"UIPeripheralHostView")] || [possibleKeyboard isKindOfClass:NSClassFromString(@"UIKeyboard")]) { return CGRectGetHeight(possibleKeyboard.bounds); } else if([possibleKeyboard isKindOfClass:NSClassFromString(@"UIInputSetContainerView")]) { for (__strong UIView *possibleKeyboardSubview in [possibleKeyboard subviews]) { if([possibleKeyboardSubview isKindOfClass:NSClassFromString(@"UIInputSetHostView")]) { return CGRectGetHeight(possibleKeyboardSubview.bounds); } } } } #endif return 0; } #pragma mark - UIAppearance Setters - (void)setDefaultStyle:(SVProgressHUDStyle)style { if (!_isInitializing) _defaultStyle = style; } - (void)setDefaultMaskType:(SVProgressHUDMaskType)maskType { if (!_isInitializing) _defaultMaskType = maskType; } - (void)setDefaultAnimationType:(SVProgressHUDAnimationType)animationType { if (!_isInitializing) _defaultAnimationType = animationType; } - (void)setMinimumSize:(CGSize)minimumSize { if (!_isInitializing) _minimumSize = minimumSize; } - (void)setRingThickness:(CGFloat)ringThickness { if (!_isInitializing) _ringThickness = ringThickness; } - (void)setRingRadius:(CGFloat)ringRadius { if (!_isInitializing) _ringRadius = ringRadius; } - (void)setRingNoTextRadius:(CGFloat)ringNoTextRadius { if (!_isInitializing) _ringNoTextRadius = ringNoTextRadius; } - (void)setCornerRadius:(CGFloat)cornerRadius { if (!_isInitializing) _cornerRadius = cornerRadius; } - (void)setFont:(UIFont*)font { if (!_isInitializing) _font = font; } - (void)setForegroundColor:(UIColor*)color { if (!_isInitializing) _foregroundColor = color; } - (void)setBackgroundColor:(UIColor*)color { if (!_isInitializing) _backgroundColor = color; } - (void)setBackgroundLayerColor:(UIColor*)color { if (!_isInitializing) _backgroundLayerColor = color; } - (void)setInfoImage:(UIImage*)image { if (!_isInitializing) _infoImage = image; } - (void)setSuccessImage:(UIImage*)image { if (!_isInitializing) _successImage = image; } - (void)setErrorImage:(UIImage*)image { if (!_isInitializing) _errorImage = image; } - (void)setViewForExtension:(UIView*)view { if (!_isInitializing) _viewForExtension = view; } - (void)setOffsetFromCenter:(UIOffset)offset { if (!_isInitializing) _offsetFromCenter = offset; } - (void)setMinimumDismissTimeInterval:(NSTimeInterval)minimumDismissTimeInterval { if (!_isInitializing) _minimumDismissTimeInterval = minimumDismissTimeInterval; } - (void)setFadeInAnimationDuration:(NSTimeInterval)duration { if (!_isInitializing) _fadeInAnimationDuration = duration; } - (void)setFadeOutAnimationDuration:(NSTimeInterval)duration { if (!_isInitializing) _fadeOutAnimationDuration = duration; } @end ================================================ FILE: DEMO/SVProgressHUD/SVRadialGradientLayer.h ================================================ // // SVRadialGradientLayer.h // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD // // Copyright (c) 2014-2016 Tobias Tiemerding. All rights reserved. // #import @interface SVRadialGradientLayer : CALayer @property (nonatomic) CGPoint gradientCenter; @end ================================================ FILE: DEMO/SVProgressHUD/SVRadialGradientLayer.m ================================================ // // SVRadialGradientLayer.m // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD // // Copyright (c) 2014-2016 Tobias Tiemerding. All rights reserved. // #import "SVRadialGradientLayer.h" @implementation SVRadialGradientLayer - (void)drawInContext:(CGContextRef)context { size_t locationsCount = 2; CGFloat locations[2] = {0.0f, 1.0f}; CGFloat colors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f}; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, locationsCount); CGColorSpaceRelease(colorSpace); float radius = MIN(self.bounds.size.width , self.bounds.size.height); CGContextDrawRadialGradient (context, gradient, self.gradientCenter, 0, self.gradientCenter, radius, kCGGradientDrawsAfterEndLocation); CGGradientRelease(gradient); } @end ================================================ FILE: DEMO/YQIAPTest/AppDelegate.h ================================================ // // AppDelegate.h // YQIAPTest // // Created by problemchild on 16/8/25. // Copyright © 2016年 ProblenChild. All rights reserved. // #import @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: DEMO/YQIAPTest/AppDelegate.m ================================================ // // AppDelegate.m // YQIAPTest // // Created by problemchild on 16/8/25. // Copyright © 2016年 ProblenChild. All rights reserved. // #import "AppDelegate.h" #import "ViewController.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { _window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds]; _window.backgroundColor = [UIColor grayColor]; _window.rootViewController = [[UINavigationController alloc]initWithRootViewController:[ViewController new]]; [_window makeKeyAndVisible]; return YES; } - (void)applicationWillResignActive:(UIApplication *)application { // 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. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - (void)applicationDidEnterBackground:(UIApplication *)application { // 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. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - (void)applicationDidBecomeActive:(UIApplication *)application { // 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. } - (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } @end ================================================ FILE: DEMO/YQIAPTest/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "29x29", "scale" : "3x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "3x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "2x" }, { "idiom" : "iphone", "size" : "60x60", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: DEMO/YQIAPTest/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: DEMO/YQIAPTest/Base.lproj/Main.storyboard ================================================ ================================================ FILE: DEMO/YQIAPTest/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: DEMO/YQIAPTest/ViewController.h ================================================ // // ViewController.h // YQIAPTest // // Created by problemchild on 16/8/25. // Copyright © 2016年 ProblenChild. All rights reserved. // #import @interface ViewController : UIViewController @end ================================================ FILE: DEMO/YQIAPTest/ViewController.m ================================================ // // ViewController.m // YQIAPTest // // Created by problemchild on 16/8/25. // Copyright © 2016年 ProblenChild. All rights reserved. // #import "ViewController.h" #import "YQInAppPurchaseTool.h" #import "SVProgressHUD.h" @interface ViewController () @property (nonatomic,strong) UITableView *tabV; @property (nonatomic,strong) NSMutableArray *productArray; @end @implementation ViewController -(NSMutableArray *)productArray{ if(!_productArray){ _productArray = [NSMutableArray array]; } return _productArray; } - (void)viewDidLoad { [super viewDidLoad]; [self setupViews]; //获取单例 YQInAppPurchaseTool *IAPTool = [YQInAppPurchaseTool defaultTool]; //设置代理 IAPTool.delegate = self; //购买后,向苹果服务器验证一下购买结果。默认为YES。不建议关闭 //IAPTool.CheckAfterPay = NO; [SVProgressHUD showWithStatus:@"向苹果询问哪些商品能够购买"]; //向苹果询问哪些商品能够购买 [IAPTool requestProductsWithProductArray:@[@"com.problenchild.YQIAPTest.product1", @"com.problenchild.YQIAPTest.product2", @"com.problenchild.YQIAPTest.product3"]]; } #pragma mark --------YQInAppPurchaseToolDelegate //IAP工具已获得可购买的商品 -(void)IAPToolGotProducts:(NSMutableArray *)products { NSLog(@"GotProducts:%@",products); // for (SKProduct *product in products){ // NSLog(@"localizedDescription:%@\nlocalizedTitle:%@\nprice:%@\npriceLocale:%@\nproductID:%@", // product.localizedDescription, // product.localizedTitle, // product.price, // product.priceLocale, // product.productIdentifier); // NSLog(@"--------------------------"); // } self.productArray = products; [self.tabV reloadData]; [SVProgressHUD showSuccessWithStatus:@"成功获取到可购买的商品"]; } //支付失败/取消 -(void)IAPToolCanceldWithProductID:(NSString *)productID { NSLog(@"canceld:%@",productID); [SVProgressHUD showInfoWithStatus:@"购买失败"]; } //支付成功了,并开始向苹果服务器进行验证(若CheckAfterPay为NO,则不会经过此步骤) -(void)IAPToolBeginCheckingdWithProductID:(NSString *)productID { NSLog(@"BeginChecking:%@",productID); [SVProgressHUD showWithStatus:@"购买成功,正在验证购买"]; } //商品被重复验证了 -(void)IAPToolCheckRedundantWithProductID:(NSString *)productID { NSLog(@"CheckRedundant:%@",productID); [SVProgressHUD showInfoWithStatus:@"重复验证了"]; } //商品完全购买成功且验证成功了。(若CheckAfterPay为NO,则会在购买成功后直接触发此方法) -(void)IAPToolBoughtProductSuccessedWithProductID:(NSString *)productID andInfo:(NSDictionary *)infoDic { NSLog(@"BoughtSuccessed:%@",productID); NSLog(@"successedInfo:%@",infoDic); [SVProgressHUD showSuccessWithStatus:@"购买成功!(相关信息已打印)"]; } //商品购买成功了,但向苹果服务器验证失败了 //2种可能: //1,设备越狱了,使用了插件,在虚假购买。 //2,验证的时候网络突然中断了。(一般极少出现,因为购买的时候是需要网络的) -(void)IAPToolCheckFailedWithProductID:(NSString *)productID andInfo:(NSData *)infoData { NSLog(@"CheckFailed:%@",productID); [SVProgressHUD showErrorWithStatus:@"验证失败了"]; } //恢复了已购买的商品(仅限永久有效商品) -(void)IAPToolRestoredProductID:(NSString *)productID { NSLog(@"Restored:%@",productID); [SVProgressHUD showSuccessWithStatus:@"成功恢复了商品(已打印)"]; } //内购系统错误了 -(void)IAPToolSysWrong { NSLog(@"SysWrong"); [SVProgressHUD showErrorWithStatus:@"内购系统出错"]; } #pragma mark --------Functions //初始化界面显示 -(void)setupViews{ self.tabV = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; self.tabV.delegate = self; self.tabV.dataSource = self; [self.view addSubview:self.tabV]; //注册重用单元格 [self.tabV registerClass:[UITableViewCell class] forCellReuseIdentifier:@"MyCell"]; self.navigationItem.rightBarButtonItem =({ UIBarButtonItem *BTN = [[UIBarButtonItem alloc]initWithTitle:@"说明" style:UIBarButtonItemStylePlain target:self action:@selector(ShowInfo)]; BTN; }); self.navigationItem.leftBarButtonItem =({ UIBarButtonItem *BTN = [[UIBarButtonItem alloc]initWithTitle:@"恢复已购买商品" style:UIBarButtonItemStylePlain target:self action:@selector(restoreProduct)]; BTN; }); [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeBlack]; } //显示说明 -(void)ShowInfo{ UIAlertView *alertDialog; alertDialog = [[UIAlertView alloc] initWithTitle:@"说明!" message:@"请使用真机进行测试\n\n请使用以下账号进行购买:\n账号:2966435424@qq.com\n密码:Mima123456" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil]; [alertDialog show]; } //恢复已购买的商品 -(void)restoreProduct{ [SVProgressHUD showWithStatus:@"正在恢复商品"]; //直接调用 [[YQInAppPurchaseTool defaultTool]restorePurchase]; } //购买商品 -(void)BuyProduct:(SKProduct *)product{ [SVProgressHUD showWithStatus:@"正在购买商品"]; [[YQInAppPurchaseTool defaultTool]buyProduct:product.productIdentifier]; } #pragma mark --------UITableViewDataSource,UITableViewDelegate -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return self.productArray.count; } -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return 1; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ return 220; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ //自动从重用队列中取得名称是MyCell的注册对象,如果没有,就会生成一个 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell" forIndexPath:indexPath]; //清除cell上的原有view NSArray *subviews = [[NSArray alloc] initWithArray:cell.contentView.subviews]; for (UIView *subview in subviews) { [subview removeFromSuperview]; } SKProduct *product = self.productArray[indexPath.section]; //cell的设置 cell.textLabel.text = [NSString stringWithFormat:@"本地化商品描述:%@\n\n本地化商品标题:%@\n\n价格:%@\n\n商品ID:%@", product.localizedDescription, product.localizedTitle, product.price, product.productIdentifier]; cell.textLabel.numberOfLines = 0; cell.textLabel.textAlignment = NSTextAlignmentCenter; return cell; } -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ [self.tabV deselectRowAtIndexPath:indexPath animated:YES]; [self BuyProduct:self.productArray[indexPath.section]]; } @end ================================================ FILE: DEMO/YQIAPTest/YQInAppPurchaseTool/YQInAppPurchaseTool.h ================================================ // // YQInAppPurchaseTool.h // YQStoreToolDemo // // Created by problemchild on 16/8/24. // Copyright © 2016年 ProblenChild. All rights reserved. // #import #import #pragma mark --------YQInAppPurchaseToolDelegate--内购代理 /** * 内购工具的代理 */ @protocol YQInAppPurchaseToolDelegate /** * 代理:系统错误 */ -(void)IAPToolSysWrong; /** * 代理:已刷新可购买商品 * * @param products 商品数组 */ -(void)IAPToolGotProducts:(NSMutableArray *)products; /** * 代理:购买成功 * * @param productID 购买成功的商品ID */ -(void)IAPToolBoughtProductSuccessedWithProductID:(NSString *)productID andInfo:(NSDictionary *)infoDic;; /** * 代理:取消购买 * * @param productID 商品ID */ -(void)IAPToolCanceldWithProductID:(NSString *)productID; /** * 代理:购买成功,开始验证购买 * * @param productID 商品ID */ -(void)IAPToolBeginCheckingdWithProductID:(NSString *)productID; /** * 代理:重复验证 * * @param productID 商品ID */ -(void)IAPToolCheckRedundantWithProductID:(NSString *)productID; /** * 代理:验证失败 * * @param productID 商品ID */ -(void)IAPToolCheckFailedWithProductID:(NSString *)productID andInfo:(NSData *)infoData; /** * 恢复了已购买的商品(永久性商品) * * @param productID 商品ID */ -(void)IAPToolRestoredProductID:(NSString *)productID; @end #pragma mark --------YQInAppPurchaseTool--内购工具 /** * 内购工具 */ @interface YQInAppPurchaseTool : NSObject typedef void(^BoolBlock)(BOOL successed,BOOL result); typedef void(^DicBlock)(BOOL successed,NSDictionary *result); /** * 代理 */ @property(nonatomic,weak) id delegate; /** * 购买完后是否在iOS端向服务器验证一次,默认为YES */ @property(nonatomic)BOOL CheckAfterPay; /** * 单例 * * @return YQInAppPurchaseTool */ +(YQInAppPurchaseTool *)defaultTool; /** * 询问苹果的服务器能够销售哪些商品 * * @param products 商品ID的数组 */ - (void)requestProductsWithProductArray:(NSArray *)products; /** * 用户决定购买商品 * * @param productID 商品ID */ - (void)buyProduct:(NSString *)productID; /** * 恢复商品(仅限永久有效商品) */ - (void)restorePurchase; @end ================================================ FILE: DEMO/YQIAPTest/YQInAppPurchaseTool/YQInAppPurchaseTool.m ================================================ // // YQInAppPurchaseTool.m // YQStoreToolDemo // // Created by problemchild on 16/8/24. // Copyright © 2016年 ProblenChild. All rights reserved. // #ifdef DEBUG #define checkURL @"https://sandbox.itunes.apple.com/verifyReceipt" #else #define checkURL @"https://buy.itunes.apple.com/verifyReceipt" #endif #import "YQInAppPurchaseTool.h" @interface YQInAppPurchaseTool () /** * 商品字典 */ @property(nonatomic,strong)NSMutableDictionary *productDict; @end @implementation YQInAppPurchaseTool //单例 static YQInAppPurchaseTool *storeTool; //单例 +(YQInAppPurchaseTool *)defaultTool{ if(!storeTool){ storeTool = [YQInAppPurchaseTool new]; [storeTool setup]; } return storeTool; } #pragma mark 初始化 /** * 初始化 */ -(void)setup{ self.CheckAfterPay = YES; // 设置购买队列的监听器 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } #pragma mark 询问苹果的服务器能够销售哪些商品 /** * 询问苹果的服务器能够销售哪些商品 */ - (void)requestProductsWithProductArray:(NSArray *)products { NSLog(@"开始请求可销售商品"); // 能够销售的商品 NSSet *set = [[NSSet alloc] initWithArray:products]; // "异步"询问苹果能否销售 SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set]; request.delegate = self; // 启动请求 [request start]; } #pragma mark 获取询问结果,成功采取操作把商品加入可售商品字典里 /** * 获取询问结果,成功采取操作把商品加入可售商品字典里 * * @param request 请求内容 * @param response 返回的结果 */ - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { if (self.productDict == nil) { self.productDict = [NSMutableDictionary dictionaryWithCapacity:response.products.count]; } NSMutableArray *productArray = [NSMutableArray array]; for (SKProduct *product in response.products) { //NSLog(@"%@", product.productIdentifier); // 填充商品字典 [self.productDict setObject:product forKey:product.productIdentifier]; [productArray addObject:product]; } //通知代理 [self.delegate IAPToolGotProducts:productArray]; } #pragma mark - 用户决定购买商品 /** * 用户决定购买商品 * * @param productID 商品ID */ - (void)buyProduct:(NSString *)productID { SKProduct *product = self.productDict[productID]; // 要购买产品(店员给用户开了个小票) SKPayment *payment = [SKPayment paymentWithProduct:product]; // 去收银台排队,准备购买(异步网络) [[SKPaymentQueue defaultQueue] addPayment:payment]; } #pragma mark - SKPaymentTransaction Observer #pragma mark 购买队列状态变化,,判断购买状态是否成功 /** * 监测购买队列的变化 * * @param queue 队列 * @param transactions 交易 */ - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { // 处理结果 for (SKPaymentTransaction *transaction in transactions) { NSLog(@"队列状态变化 %@", transaction); // 如果小票状态是购买完成 if (SKPaymentTransactionStatePurchased == transaction.transactionState) { //NSLog(@"购买完成 %@", transaction.payment.productIdentifier); if(self.CheckAfterPay){ //需要向苹果服务器验证一下 //通知代理 [self.delegate IAPToolBeginCheckingdWithProductID:transaction.payment.productIdentifier]; // 验证购买凭据 [self verifyPruchaseWithID:transaction.payment.productIdentifier]; }else{ //不需要向苹果服务器验证 //通知代理 [self.delegate IAPToolBoughtProductSuccessedWithProductID:transaction.payment.productIdentifier andInfo:nil]; } // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } else if (SKPaymentTransactionStateRestored == transaction.transactionState) { //NSLog(@"恢复成功 :%@", transaction.payment.productIdentifier); // 通知代理 [self.delegate IAPToolRestoredProductID:transaction.payment.productIdentifier]; // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } else if (SKPaymentTransactionStateFailed == transaction.transactionState){ // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; //NSLog(@"交易失败"); [self.delegate IAPToolCanceldWithProductID:transaction.payment.productIdentifier]; }else if(SKPaymentTransactionStatePurchasing == transaction.transactionState){ NSLog(@"正在购买"); }else{ NSLog(@"state:%ld",(long)transaction.transactionState); NSLog(@"已经购买"); // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } } } #pragma mark - 恢复商品 /** * 恢复商品 */ - (void)restorePurchase { // 恢复已经完成的所有交易.(仅限永久有效商品) [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; } #pragma mark 验证购买凭据 /** * 验证购买凭据 * * @param ProductID 商品ID */ - (void)verifyPruchaseWithID:(NSString *)ProductID { // 验证凭据,获取到苹果返回的交易凭据 // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 从沙盒中获取到购买凭据 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; // 发送网络POST请求,对购买凭据进行验证 //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt //In the real environment, use https://buy.itunes.apple.com/verifyReceipt // Create a POST request with the receipt data. NSURL *url = [NSURL URLWithString:checkURL]; NSLog(@"checkURL:%@",checkURL); // 国内访问苹果服务器比较慢,timeoutInterval需要长一点 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0f]; request.HTTPMethod = @"POST"; // 在网络中传输数据,大多情况下是传输的字符串而不是二进制数据 // 传输的是BASE64编码的字符串 /** BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性 BASE64是可以编码和解码的 */ NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr]; NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; request.HTTPBody = payloadData; // 提交验证请求,并获得官方的验证JSON结果 NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]; // 官方验证结果为空 if (result == nil) { //NSLog(@"验证失败"); //验证失败,通知代理 [self.delegate IAPToolCheckFailedWithProductID:ProductID andInfo:result]; } NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingAllowFragments error:nil]; //NSLog(@"RecivedVerifyPruchaseDict:%@", dict); if (dict != nil) { // 验证成功,通知代理 // bundle_id&application_version&product_id&transaction_id [self.delegate IAPToolBoughtProductSuccessedWithProductID:ProductID andInfo:dict]; }else{ //验证失败,通知代理 [self.delegate IAPToolCheckFailedWithProductID:ProductID andInfo:result]; } } @end ================================================ FILE: DEMO/YQIAPTest/main.m ================================================ // // main.m // YQIAPTest // // Created by problemchild on 16/8/25. // Copyright © 2016年 ProblenChild. All rights reserved. // #import #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: DEMO/YQIAPTest.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 95BCBBF31D703E790035827C /* SVIndefiniteAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 95BCBBEA1D703E790035827C /* SVIndefiniteAnimatedView.m */; }; 95BCBBF41D703E790035827C /* SVProgressAnimatedView.m in Sources */ = {isa = PBXBuildFile; fileRef = 95BCBBEC1D703E790035827C /* SVProgressAnimatedView.m */; }; 95BCBBF51D703E790035827C /* SVProgressHUD.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 95BCBBEE1D703E790035827C /* SVProgressHUD.bundle */; }; 95BCBBF61D703E790035827C /* SVProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 95BCBBF01D703E790035827C /* SVProgressHUD.m */; }; 95BCBBF71D703E790035827C /* SVRadialGradientLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 95BCBBF21D703E790035827C /* SVRadialGradientLayer.m */; }; 95F43B9A1D6EA09A00B88872 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 95F43B991D6EA09A00B88872 /* main.m */; }; 95F43B9D1D6EA09A00B88872 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 95F43B9C1D6EA09A00B88872 /* AppDelegate.m */; }; 95F43BA01D6EA09A00B88872 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 95F43B9F1D6EA09A00B88872 /* ViewController.m */; }; 95F43BA31D6EA09A00B88872 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 95F43BA11D6EA09A00B88872 /* Main.storyboard */; }; 95F43BA51D6EA09A00B88872 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 95F43BA41D6EA09A00B88872 /* Assets.xcassets */; }; 95F43BA81D6EA09A00B88872 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 95F43BA61D6EA09A00B88872 /* LaunchScreen.storyboard */; }; 95F43BB31D6EA09A00B88872 /* YQIAPTestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 95F43BB21D6EA09A00B88872 /* YQIAPTestTests.m */; }; 95F43BBE1D6EA09A00B88872 /* YQIAPTestUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 95F43BBD1D6EA09A00B88872 /* YQIAPTestUITests.m */; }; 95F43BCC1D6EA0BE00B88872 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 95F43BCB1D6EA0BE00B88872 /* StoreKit.framework */; }; 95F43BD01D6EA0DE00B88872 /* YQInAppPurchaseTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 95F43BCF1D6EA0DE00B88872 /* YQInAppPurchaseTool.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 95F43BAF1D6EA09A00B88872 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 95F43B8D1D6EA09A00B88872 /* Project object */; proxyType = 1; remoteGlobalIDString = 95F43B941D6EA09A00B88872; remoteInfo = YQIAPTest; }; 95F43BBA1D6EA09A00B88872 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 95F43B8D1D6EA09A00B88872 /* Project object */; proxyType = 1; remoteGlobalIDString = 95F43B941D6EA09A00B88872; remoteInfo = YQIAPTest; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 95BCBBE91D703E790035827C /* SVIndefiniteAnimatedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVIndefiniteAnimatedView.h; sourceTree = ""; }; 95BCBBEA1D703E790035827C /* SVIndefiniteAnimatedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVIndefiniteAnimatedView.m; sourceTree = ""; }; 95BCBBEB1D703E790035827C /* SVProgressAnimatedView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressAnimatedView.h; sourceTree = ""; }; 95BCBBEC1D703E790035827C /* SVProgressAnimatedView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressAnimatedView.m; sourceTree = ""; }; 95BCBBED1D703E790035827C /* SVProgressHUD-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SVProgressHUD-Prefix.pch"; sourceTree = ""; }; 95BCBBEE1D703E790035827C /* SVProgressHUD.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = SVProgressHUD.bundle; sourceTree = ""; }; 95BCBBEF1D703E790035827C /* SVProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVProgressHUD.h; sourceTree = ""; }; 95BCBBF01D703E790035827C /* SVProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVProgressHUD.m; sourceTree = ""; }; 95BCBBF11D703E790035827C /* SVRadialGradientLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVRadialGradientLayer.h; sourceTree = ""; }; 95BCBBF21D703E790035827C /* SVRadialGradientLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SVRadialGradientLayer.m; sourceTree = ""; }; 95F43B951D6EA09A00B88872 /* YQIAPTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YQIAPTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 95F43B991D6EA09A00B88872 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 95F43B9B1D6EA09A00B88872 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 95F43B9C1D6EA09A00B88872 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 95F43B9E1D6EA09A00B88872 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 95F43B9F1D6EA09A00B88872 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 95F43BA21D6EA09A00B88872 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 95F43BA41D6EA09A00B88872 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 95F43BA71D6EA09A00B88872 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 95F43BA91D6EA09A00B88872 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 95F43BAE1D6EA09A00B88872 /* YQIAPTestTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YQIAPTestTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 95F43BB21D6EA09A00B88872 /* YQIAPTestTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YQIAPTestTests.m; sourceTree = ""; }; 95F43BB41D6EA09A00B88872 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 95F43BB91D6EA09A00B88872 /* YQIAPTestUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YQIAPTestUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 95F43BBD1D6EA09A00B88872 /* YQIAPTestUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YQIAPTestUITests.m; sourceTree = ""; }; 95F43BBF1D6EA09A00B88872 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 95F43BCB1D6EA0BE00B88872 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; 95F43BCE1D6EA0DE00B88872 /* YQInAppPurchaseTool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YQInAppPurchaseTool.h; sourceTree = ""; }; 95F43BCF1D6EA0DE00B88872 /* YQInAppPurchaseTool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YQInAppPurchaseTool.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 95F43B921D6EA09A00B88872 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 95F43BCC1D6EA0BE00B88872 /* StoreKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 95F43BAB1D6EA09A00B88872 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 95F43BB61D6EA09A00B88872 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 95BCBBE81D703E790035827C /* SVProgressHUD */ = { isa = PBXGroup; children = ( 95BCBBE91D703E790035827C /* SVIndefiniteAnimatedView.h */, 95BCBBEA1D703E790035827C /* SVIndefiniteAnimatedView.m */, 95BCBBEB1D703E790035827C /* SVProgressAnimatedView.h */, 95BCBBEC1D703E790035827C /* SVProgressAnimatedView.m */, 95BCBBED1D703E790035827C /* SVProgressHUD-Prefix.pch */, 95BCBBEE1D703E790035827C /* SVProgressHUD.bundle */, 95BCBBEF1D703E790035827C /* SVProgressHUD.h */, 95BCBBF01D703E790035827C /* SVProgressHUD.m */, 95BCBBF11D703E790035827C /* SVRadialGradientLayer.h */, 95BCBBF21D703E790035827C /* SVRadialGradientLayer.m */, ); path = SVProgressHUD; sourceTree = ""; }; 95F43B8C1D6EA09A00B88872 = { isa = PBXGroup; children = ( 95BCBBE81D703E790035827C /* SVProgressHUD */, 95F43BCB1D6EA0BE00B88872 /* StoreKit.framework */, 95F43BCD1D6EA0DE00B88872 /* YQInAppPurchaseTool */, 95F43B971D6EA09A00B88872 /* YQIAPTest */, 95F43BB11D6EA09A00B88872 /* YQIAPTestTests */, 95F43BBC1D6EA09A00B88872 /* YQIAPTestUITests */, 95F43B961D6EA09A00B88872 /* Products */, ); sourceTree = ""; }; 95F43B961D6EA09A00B88872 /* Products */ = { isa = PBXGroup; children = ( 95F43B951D6EA09A00B88872 /* YQIAPTest.app */, 95F43BAE1D6EA09A00B88872 /* YQIAPTestTests.xctest */, 95F43BB91D6EA09A00B88872 /* YQIAPTestUITests.xctest */, ); name = Products; sourceTree = ""; }; 95F43B971D6EA09A00B88872 /* YQIAPTest */ = { isa = PBXGroup; children = ( 95F43B9B1D6EA09A00B88872 /* AppDelegate.h */, 95F43B9C1D6EA09A00B88872 /* AppDelegate.m */, 95F43B9E1D6EA09A00B88872 /* ViewController.h */, 95F43B9F1D6EA09A00B88872 /* ViewController.m */, 95F43BA11D6EA09A00B88872 /* Main.storyboard */, 95F43BA41D6EA09A00B88872 /* Assets.xcassets */, 95F43BA61D6EA09A00B88872 /* LaunchScreen.storyboard */, 95F43BA91D6EA09A00B88872 /* Info.plist */, 95F43B981D6EA09A00B88872 /* Supporting Files */, ); path = YQIAPTest; sourceTree = ""; }; 95F43B981D6EA09A00B88872 /* Supporting Files */ = { isa = PBXGroup; children = ( 95F43B991D6EA09A00B88872 /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; 95F43BB11D6EA09A00B88872 /* YQIAPTestTests */ = { isa = PBXGroup; children = ( 95F43BB21D6EA09A00B88872 /* YQIAPTestTests.m */, 95F43BB41D6EA09A00B88872 /* Info.plist */, ); path = YQIAPTestTests; sourceTree = ""; }; 95F43BBC1D6EA09A00B88872 /* YQIAPTestUITests */ = { isa = PBXGroup; children = ( 95F43BBD1D6EA09A00B88872 /* YQIAPTestUITests.m */, 95F43BBF1D6EA09A00B88872 /* Info.plist */, ); path = YQIAPTestUITests; sourceTree = ""; }; 95F43BCD1D6EA0DE00B88872 /* YQInAppPurchaseTool */ = { isa = PBXGroup; children = ( 95F43BCE1D6EA0DE00B88872 /* YQInAppPurchaseTool.h */, 95F43BCF1D6EA0DE00B88872 /* YQInAppPurchaseTool.m */, ); name = YQInAppPurchaseTool; path = YQIAPTest/YQInAppPurchaseTool; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 95F43B941D6EA09A00B88872 /* YQIAPTest */ = { isa = PBXNativeTarget; buildConfigurationList = 95F43BC21D6EA09A00B88872 /* Build configuration list for PBXNativeTarget "YQIAPTest" */; buildPhases = ( 95F43B911D6EA09A00B88872 /* Sources */, 95F43B921D6EA09A00B88872 /* Frameworks */, 95F43B931D6EA09A00B88872 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = YQIAPTest; productName = YQIAPTest; productReference = 95F43B951D6EA09A00B88872 /* YQIAPTest.app */; productType = "com.apple.product-type.application"; }; 95F43BAD1D6EA09A00B88872 /* YQIAPTestTests */ = { isa = PBXNativeTarget; buildConfigurationList = 95F43BC51D6EA09A00B88872 /* Build configuration list for PBXNativeTarget "YQIAPTestTests" */; buildPhases = ( 95F43BAA1D6EA09A00B88872 /* Sources */, 95F43BAB1D6EA09A00B88872 /* Frameworks */, 95F43BAC1D6EA09A00B88872 /* Resources */, ); buildRules = ( ); dependencies = ( 95F43BB01D6EA09A00B88872 /* PBXTargetDependency */, ); name = YQIAPTestTests; productName = YQIAPTestTests; productReference = 95F43BAE1D6EA09A00B88872 /* YQIAPTestTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 95F43BB81D6EA09A00B88872 /* YQIAPTestUITests */ = { isa = PBXNativeTarget; buildConfigurationList = 95F43BC81D6EA09A00B88872 /* Build configuration list for PBXNativeTarget "YQIAPTestUITests" */; buildPhases = ( 95F43BB51D6EA09A00B88872 /* Sources */, 95F43BB61D6EA09A00B88872 /* Frameworks */, 95F43BB71D6EA09A00B88872 /* Resources */, ); buildRules = ( ); dependencies = ( 95F43BBB1D6EA09A00B88872 /* PBXTargetDependency */, ); name = YQIAPTestUITests; productName = YQIAPTestUITests; productReference = 95F43BB91D6EA09A00B88872 /* YQIAPTestUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 95F43B8D1D6EA09A00B88872 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0730; ORGANIZATIONNAME = ProblenChild; TargetAttributes = { 95F43B941D6EA09A00B88872 = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = CKAC5QB95T; SystemCapabilities = { com.apple.InAppPurchase = { enabled = 1; }; }; }; 95F43BAD1D6EA09A00B88872 = { CreatedOnToolsVersion = 7.3.1; TestTargetID = 95F43B941D6EA09A00B88872; }; 95F43BB81D6EA09A00B88872 = { CreatedOnToolsVersion = 7.3.1; TestTargetID = 95F43B941D6EA09A00B88872; }; }; }; buildConfigurationList = 95F43B901D6EA09A00B88872 /* Build configuration list for PBXProject "YQIAPTest" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 95F43B8C1D6EA09A00B88872; productRefGroup = 95F43B961D6EA09A00B88872 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 95F43B941D6EA09A00B88872 /* YQIAPTest */, 95F43BAD1D6EA09A00B88872 /* YQIAPTestTests */, 95F43BB81D6EA09A00B88872 /* YQIAPTestUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 95F43B931D6EA09A00B88872 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 95F43BA81D6EA09A00B88872 /* LaunchScreen.storyboard in Resources */, 95F43BA51D6EA09A00B88872 /* Assets.xcassets in Resources */, 95F43BA31D6EA09A00B88872 /* Main.storyboard in Resources */, 95BCBBF51D703E790035827C /* SVProgressHUD.bundle in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 95F43BAC1D6EA09A00B88872 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 95F43BB71D6EA09A00B88872 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 95F43B911D6EA09A00B88872 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 95BCBBF31D703E790035827C /* SVIndefiniteAnimatedView.m in Sources */, 95F43BA01D6EA09A00B88872 /* ViewController.m in Sources */, 95F43B9D1D6EA09A00B88872 /* AppDelegate.m in Sources */, 95BCBBF71D703E790035827C /* SVRadialGradientLayer.m in Sources */, 95BCBBF61D703E790035827C /* SVProgressHUD.m in Sources */, 95F43B9A1D6EA09A00B88872 /* main.m in Sources */, 95F43BD01D6EA0DE00B88872 /* YQInAppPurchaseTool.m in Sources */, 95BCBBF41D703E790035827C /* SVProgressAnimatedView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 95F43BAA1D6EA09A00B88872 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 95F43BB31D6EA09A00B88872 /* YQIAPTestTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 95F43BB51D6EA09A00B88872 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 95F43BBE1D6EA09A00B88872 /* YQIAPTestUITests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 95F43BB01D6EA09A00B88872 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 95F43B941D6EA09A00B88872 /* YQIAPTest */; targetProxy = 95F43BAF1D6EA09A00B88872 /* PBXContainerItemProxy */; }; 95F43BBB1D6EA09A00B88872 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 95F43B941D6EA09A00B88872 /* YQIAPTest */; targetProxy = 95F43BBA1D6EA09A00B88872 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 95F43BA11D6EA09A00B88872 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 95F43BA21D6EA09A00B88872 /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 95F43BA61D6EA09A00B88872 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 95F43BA71D6EA09A00B88872 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 95F43BC01D6EA09A00B88872 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.3; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; }; 95F43BC11D6EA09A00B88872 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 95F43BC31D6EA09A00B88872 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = YQIAPTest/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.problenchild.YQIAPTest; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 95F43BC41D6EA09A00B88872 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = YQIAPTest/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.problenchild.YQIAPTest; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 95F43BC61D6EA09A00B88872 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = YQIAPTestTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.problenchild.YQIAPTestTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YQIAPTest.app/YQIAPTest"; }; name = Debug; }; 95F43BC71D6EA09A00B88872 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = YQIAPTestTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.problenchild.YQIAPTestTests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/YQIAPTest.app/YQIAPTest"; }; name = Release; }; 95F43BC91D6EA09A00B88872 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = YQIAPTestUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.problenchild.YQIAPTestUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = YQIAPTest; }; name = Debug; }; 95F43BCA1D6EA09A00B88872 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = YQIAPTestUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.problenchild.YQIAPTestUITests; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_TARGET_NAME = YQIAPTest; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 95F43B901D6EA09A00B88872 /* Build configuration list for PBXProject "YQIAPTest" */ = { isa = XCConfigurationList; buildConfigurations = ( 95F43BC01D6EA09A00B88872 /* Debug */, 95F43BC11D6EA09A00B88872 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 95F43BC21D6EA09A00B88872 /* Build configuration list for PBXNativeTarget "YQIAPTest" */ = { isa = XCConfigurationList; buildConfigurations = ( 95F43BC31D6EA09A00B88872 /* Debug */, 95F43BC41D6EA09A00B88872 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 95F43BC51D6EA09A00B88872 /* Build configuration list for PBXNativeTarget "YQIAPTestTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 95F43BC61D6EA09A00B88872 /* Debug */, 95F43BC71D6EA09A00B88872 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 95F43BC81D6EA09A00B88872 /* Build configuration list for PBXNativeTarget "YQIAPTestUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( 95F43BC91D6EA09A00B88872 /* Debug */, 95F43BCA1D6EA09A00B88872 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 95F43B8D1D6EA09A00B88872 /* Project object */; } ================================================ FILE: DEMO/YQIAPTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: DEMO/YQIAPTest.xcodeproj/xcuserdata/ProblemChild.xcuserdatad/xcschemes/YQIAPTest.xcscheme ================================================ ================================================ FILE: DEMO/YQIAPTest.xcodeproj/xcuserdata/ProblemChild.xcuserdatad/xcschemes/xcschememanagement.plist ================================================ SchemeUserState YQIAPTest.xcscheme orderHint 0 SuppressBuildableAutocreation 95F43B941D6EA09A00B88872 primary 95F43BAD1D6EA09A00B88872 primary 95F43BB81D6EA09A00B88872 primary ================================================ FILE: DEMO/YQIAPTestTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: DEMO/YQIAPTestTests/YQIAPTestTests.m ================================================ // // YQIAPTestTests.m // YQIAPTestTests // // Created by problemchild on 16/8/25. // Copyright © 2016年 ProblenChild. All rights reserved. // #import @interface YQIAPTestTests : XCTestCase @end @implementation YQIAPTestTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } - (void)testPerformanceExample { // This is an example of a performance test case. [self measureBlock:^{ // Put the code you want to measure the time of here. }]; } @end ================================================ FILE: DEMO/YQIAPTestUITests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1 ================================================ FILE: DEMO/YQIAPTestUITests/YQIAPTestUITests.m ================================================ // // YQIAPTestUITests.m // YQIAPTestUITests // // Created by problemchild on 16/8/25. // Copyright © 2016年 ProblenChild. All rights reserved. // #import @interface YQIAPTestUITests : XCTestCase @end @implementation YQIAPTestUITests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. self.continueAfterFailure = NO; // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. [[[XCUIApplication alloc] init] launch]; // 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. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } @end ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016 FreakyYang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # YQInAppPurchaseTool #### 微博:畸形滴小男孩 ### iOS苹果内购集成工具 #### 使用方法: ##### 1把文件拖到XCodeg工程中,并开启工程的IAP: ![image](https://github.com/976431yang/YQInAppPurchaseTool/blob/master/DEMO/ScreenShot/ScrrenShot01.png) ##### 2引入头文件 ```Objective-C #import "YQInAppPurchaseTool.h" ``` ##### 3添加代理 ```Objective-C ``` ##### 4遵循、实现代理 ```Objective-C //-----------获取单例 YQInAppPurchaseTool *IAPTool = [YQInAppPurchaseTool defaultTool]; //-----------设置代理 IAPTool.delegate = self; //-----------实现代理 //IAP工具已获得可购买的商品 -(void)IAPToolGotProducts:(NSMutableArray *)products { NSLog(@"GotProducts:%@",products); } //支付失败/取消 -(void)IAPToolCanceldWithProductID:(NSString *)productID { NSLog(@"canceld:%@",productID); } //支付成功了,并开始向苹果服务器进行验证(若CheckAfterPay为NO,则不会经过此步骤) -(void)IAPToolBeginCheckingdWithProductID:(NSString *)productID { NSLog(@"BeginChecking:%@",productID); } //商品被重复验证了 -(void)IAPToolCheckRedundantWithProductID:(NSString *)productID { NSLog(@"CheckRedundant:%@",productID); } //商品完全购买成功且验证成功了。(若CheckAfterPay为NO,则会在购买成功后直接触发此方法) -(void)IAPToolBoughtProductSuccessedWithProductID:(NSString *)productID andInfo:(NSDictionary *)infoDic { NSLog(@"BoughtSuccessed:%@",productID); NSLog(@"successedInfo:%@",infoDic); } //商品购买成功了,但向苹果服务器验证失败了 //2种可能: //1,设备越狱了,使用了插件,在虚假购买。 //2,验证的时候网络突然中断了。(一般极少出现,因为购买的时候是需要网络的) -(void)IAPToolCheckFailedWithProductID:(NSString *)productID andInfo:(NSData *)infoData { NSLog(@"CheckFailed:%@",productID); } //恢复了已购买的商品(仅限永久有效商品) -(void)IAPToolRestoredProductID:(NSString *)productID { NSLog(@"Restored:%@",productID); } //内购系统错误了 -(void)IAPToolSysWrong { NSLog(@"SysWrong"); } ``` ##### 5使用 ```Objective-C //向苹果询问哪些商品能够购买 [IAPTool requestProductsWithProductArray:@[@"productID1", @"productID2", @"productID3"]]; //请求购买商品 [[YQInAppPurchaseTool defaultTool]buyProduct:product.productIdentifier]; //请求恢复商品(永久性商品)(也可直接再次购买,系统会提示不用扣费) [[YQInAppPurchaseTool defaultTool]restorePurchase]; ``` ##### 6购买流程 1.向苹果询问哪些商品可以购买
2.请求购买商品
3.代理会自动响应购买中的各个状态
---3.1.购买成功,开始向苹果服务器验证购买凭证(可设置跳过)
------3.1.1.验证成功(购买过程结束)
------3.1.2.验证失败(可能用户越狱了在虚假购买)
---3.2.购买失败
-更详细使用方法可下载DEMO工程查看 ================================================ FILE: YQInAppPurchaseTool/YQInAppPurchaseTool.h ================================================ // // YQInAppPurchaseTool.h // YQStoreToolDemo // // Created by problemchild on 16/8/24. // Copyright © 2016年 ProblenChild. All rights reserved. // #import #import #pragma mark --------YQInAppPurchaseToolDelegate--内购代理 /** * 内购工具的代理 */ @protocol YQInAppPurchaseToolDelegate /** * 代理:系统错误 */ -(void)IAPToolSysWrong; /** * 代理:已刷新可购买商品 * * @param products 商品数组 */ -(void)IAPToolGotProducts:(NSMutableArray *)products; /** * 代理:购买成功 * * @param productID 购买成功的商品ID */ -(void)IAPToolBoughtProductSuccessedWithProductID:(NSString *)productID andInfo:(NSDictionary *)infoDic;; /** * 代理:取消购买 * * @param productID 商品ID */ -(void)IAPToolCanceldWithProductID:(NSString *)productID; /** * 代理:购买成功,开始验证购买 * * @param productID 商品ID */ -(void)IAPToolBeginCheckingdWithProductID:(NSString *)productID; /** * 代理:重复验证 * * @param productID 商品ID */ -(void)IAPToolCheckRedundantWithProductID:(NSString *)productID; /** * 代理:验证失败 * * @param productID 商品ID */ -(void)IAPToolCheckFailedWithProductID:(NSString *)productID andInfo:(NSData *)infoData; /** * 恢复了已购买的商品(永久性商品) * * @param productID 商品ID */ -(void)IAPToolRestoredProductID:(NSString *)productID; @end #pragma mark --------YQInAppPurchaseTool--内购工具 /** * 内购工具 */ @interface YQInAppPurchaseTool : NSObject typedef void(^BoolBlock)(BOOL successed,BOOL result); typedef void(^DicBlock)(BOOL successed,NSDictionary *result); /** * 代理 */ @property(nonatomic,weak) id delegate; /** * 购买完后是否在iOS端向服务器验证一次,默认为YES */ @property(nonatomic)BOOL CheckAfterPay; /** * 单例 * * @return YQInAppPurchaseTool */ +(YQInAppPurchaseTool *)defaultTool; /** * 询问苹果的服务器能够销售哪些商品 * * @param products 商品ID的数组 */ - (void)requestProductsWithProductArray:(NSArray *)products; /** * 用户决定购买商品 * * @param productID 商品ID */ - (void)buyProduct:(NSString *)productID; /** * 恢复商品(仅限永久有效商品) */ - (void)restorePurchase; @end ================================================ FILE: YQInAppPurchaseTool/YQInAppPurchaseTool.m ================================================ // // YQInAppPurchaseTool.m // YQStoreToolDemo // // Created by problemchild on 16/8/24. // Copyright © 2016年 ProblenChild. All rights reserved. // #ifdef DEBUG #define checkURL @"https://sandbox.itunes.apple.com/verifyReceipt" #else #define checkURL @"https://buy.itunes.apple.com/verifyReceipt" #endif #import "YQInAppPurchaseTool.h" @interface YQInAppPurchaseTool () /** * 商品字典 */ @property(nonatomic,strong)NSMutableDictionary *productDict; @end @implementation YQInAppPurchaseTool //单例 static YQInAppPurchaseTool *storeTool; //单例 +(YQInAppPurchaseTool *)defaultTool{ if(!storeTool){ storeTool = [YQInAppPurchaseTool new]; [storeTool setup]; } return storeTool; } #pragma mark 初始化 /** * 初始化 */ -(void)setup{ self.CheckAfterPay = YES; // 设置购买队列的监听器 [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; } #pragma mark 询问苹果的服务器能够销售哪些商品 /** * 询问苹果的服务器能够销售哪些商品 */ - (void)requestProductsWithProductArray:(NSArray *)products { NSLog(@"开始请求可销售商品"); // 能够销售的商品 NSSet *set = [[NSSet alloc] initWithArray:products]; // "异步"询问苹果能否销售 SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:set]; request.delegate = self; // 启动请求 [request start]; } #pragma mark 获取询问结果,成功采取操作把商品加入可售商品字典里 /** * 获取询问结果,成功采取操作把商品加入可售商品字典里 * * @param request 请求内容 * @param response 返回的结果 */ - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { if (self.productDict == nil) { self.productDict = [NSMutableDictionary dictionaryWithCapacity:response.products.count]; } NSMutableArray *productArray = [NSMutableArray array]; for (SKProduct *product in response.products) { //NSLog(@"%@", product.productIdentifier); // 填充商品字典 [self.productDict setObject:product forKey:product.productIdentifier]; [productArray addObject:product]; } //通知代理 [self.delegate IAPToolGotProducts:productArray]; } #pragma mark - 用户决定购买商品 /** * 用户决定购买商品 * * @param productID 商品ID */ - (void)buyProduct:(NSString *)productID { SKProduct *product = self.productDict[productID]; // 要购买产品(店员给用户开了个小票) SKPayment *payment = [SKPayment paymentWithProduct:product]; // 去收银台排队,准备购买(异步网络) [[SKPaymentQueue defaultQueue] addPayment:payment]; } #pragma mark - SKPaymentTransaction Observer #pragma mark 购买队列状态变化,,判断购买状态是否成功 /** * 监测购买队列的变化 * * @param queue 队列 * @param transactions 交易 */ - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { // 处理结果 for (SKPaymentTransaction *transaction in transactions) { NSLog(@"队列状态变化 %@", transaction); // 如果小票状态是购买完成 if (SKPaymentTransactionStatePurchased == transaction.transactionState) { //NSLog(@"购买完成 %@", transaction.payment.productIdentifier); if(self.CheckAfterPay){ //需要向苹果服务器验证一下 //通知代理 [self.delegate IAPToolBeginCheckingdWithProductID:transaction.payment.productIdentifier]; // 验证购买凭据 [self verifyPruchaseWithID:transaction.payment.productIdentifier]; }else{ //不需要向苹果服务器验证 //通知代理 [self.delegate IAPToolBoughtProductSuccessedWithProductID:transaction.payment.productIdentifier andInfo:nil]; } // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } else if (SKPaymentTransactionStateRestored == transaction.transactionState) { //NSLog(@"恢复成功 :%@", transaction.payment.productIdentifier); // 通知代理 [self.delegate IAPToolRestoredProductID:transaction.payment.productIdentifier]; // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } else if (SKPaymentTransactionStateFailed == transaction.transactionState){ // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; //NSLog(@"交易失败"); [self.delegate IAPToolCanceldWithProductID:transaction.payment.productIdentifier]; }else if(SKPaymentTransactionStatePurchasing == transaction.transactionState){ NSLog(@"正在购买"); }else{ NSLog(@"state:%ld",(long)transaction.transactionState); NSLog(@"已经购买"); // 将交易从交易队列中删除 [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } } } #pragma mark - 恢复商品 /** * 恢复商品 */ - (void)restorePurchase { // 恢复已经完成的所有交易.(仅限永久有效商品) [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; } #pragma mark 验证购买凭据 /** * 验证购买凭据 * * @param ProductID 商品ID */ - (void)verifyPruchaseWithID:(NSString *)ProductID { // 验证凭据,获取到苹果返回的交易凭据 // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 从沙盒中获取到购买凭据 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; // 发送网络POST请求,对购买凭据进行验证 //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt //In the real environment, use https://buy.itunes.apple.com/verifyReceipt // Create a POST request with the receipt data. NSURL *url = [NSURL URLWithString:checkURL]; NSLog(@"checkURL:%@",checkURL); // 国内访问苹果服务器比较慢,timeoutInterval需要长一点 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0f]; request.HTTPMethod = @"POST"; // 在网络中传输数据,大多情况下是传输的字符串而不是二进制数据 // 传输的是BASE64编码的字符串 /** BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性 BASE64是可以编码和解码的 */ NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr]; NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; request.HTTPBody = payloadData; // 提交验证请求,并获得官方的验证JSON结果 NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]; // 官方验证结果为空 if (result == nil) { //NSLog(@"验证失败"); //验证失败,通知代理 [self.delegate IAPToolCheckFailedWithProductID:ProductID andInfo:result]; } NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingAllowFragments error:nil]; //NSLog(@"RecivedVerifyPruchaseDict:%@", dict); if (dict != nil) { // 验证成功,通知代理 // bundle_id&application_version&product_id&transaction_id [self.delegate IAPToolBoughtProductSuccessedWithProductID:ProductID andInfo:dict]; }else{ //验证失败,通知代理 [self.delegate IAPToolCheckFailedWithProductID:ProductID andInfo:result]; } } @end