├── Example ├── Example │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── ViewController.h │ ├── AppDelegate.h │ ├── main.m │ ├── Example-Prefix.pch │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── Example-Info.plist │ ├── AppDelegate.m │ ├── Launch Screen.storyboard │ ├── ViewController.m │ └── Base.lproj │ │ └── Main.storyboard ├── ExampleTests │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── ExampleTests-Info.plist │ └── ExampleTests.m └── Example.xcodeproj │ ├── project.xcworkspace │ └── contents.xcworkspacedata │ └── project.pbxproj ├── .gitignore ├── ASProgressPopUpView.xcworkspace └── contents.xcworkspacedata ├── LICENSE ├── ASProgressPopUpView.podspec ├── ASProgressPopUpView ├── ASPopUpView.h ├── ASProgressPopUpView.h ├── ASProgressPopUpView.m └── ASPopUpView.m └── README.md /Example/Example/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/ExampleTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Example 4 | // 5 | // Created by Alan Skipp on 16/04/2014. 6 | // Copyright (c) 2014 Alan Skipp. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa -------------------------------------------------------------------------------- /Example/Example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Example 4 | // 5 | // Created by Alan Skipp on 16/04/2014. 6 | // Copyright (c) 2014 Alan Skipp. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/Example/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Example 4 | // 5 | // Created by Alan Skipp on 16/04/2014. 6 | // Copyright (c) 2014 Alan Skipp. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/Example/Example-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Example/Example/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /ASProgressPopUpView.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 10 | 12 | 13 | 15 | 16 | 18 | 19 | 20 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Example/ExampleTests/ExampleTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/ExampleTests/ExampleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleTests.m 3 | // ExampleTests 4 | // 5 | // Created by Alan Skipp on 16/04/2014. 6 | // Copyright (c) 2014 Alan Skipp. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ExampleTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation ExampleTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Alan Skipp 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /ASProgressPopUpView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ASProgressPopUpView" 3 | s.version = "0.9" 4 | s.summary = "A progress view showing percentage complete in an animated popUpView" 5 | s.description = <<-DESC 6 | * Customize: font, font color, background color, corner radius 7 | * Option to animate background color and progress bar color as value changes 8 | * Optional dataSource protocol to fully customize label text 9 | DESC 10 | s.homepage = "https://github.com/alskipp/ASProgressPopUpView" 11 | s.screenshots = "http://alskipp.github.io/ASProgressPopUpView/img/screenshot1.gif" 12 | s.license = { :type => 'MIT', :file => 'LICENSE' } 13 | s.author = { "Al Skipp" => "al_skipp@fastmail.fm" } 14 | s.social_media_url = 'https://twitter.com/al_skipp' 15 | 16 | s.platform = :ios, '8.0' 17 | s.source = { :git => "https://github.com/alskipp/ASProgressPopUpView.git", :tag => "0.9" } 18 | s.source_files = 'ASProgressPopUpView' 19 | s.requires_arc = true 20 | end 21 | -------------------------------------------------------------------------------- /Example/Example/Example-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | Launch Screen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ASProgressPopUpView/ASPopUpView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ASPopUpView.h 3 | // ASProgressPopUpView 4 | // 5 | // Created by Alan Skipp on 16/04/2013. 6 | // Copyright (c) 2014 Alan Skipp. All rights reserved. 7 | // 8 | 9 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | // This UIView subclass is used internally by ASProgressPopUpView 11 | // The public API is declared in ASProgressPopUpView.h 12 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 13 | 14 | #import 15 | 16 | @protocol ASPopUpViewDelegate 17 | - (CGFloat)currentValueOffset; //expects value in the range 0.0 - 1.0 18 | //- (void)colorDidUpdate:(UIColor *)opaqueColor; 19 | @end 20 | 21 | @interface ASPopUpView : UIView 22 | 23 | @property (weak, nonatomic) id delegate; 24 | @property (nonatomic) CGFloat cornerRadius; 25 | @property (nonatomic) CGFloat arrowLength; 26 | @property (nonatomic) CGFloat widthPaddingFactor; 27 | @property (nonatomic) CGFloat heightPaddingFactor; 28 | 29 | - (UIColor *)color; 30 | - (void)setColor:(UIColor *)color; 31 | - (UIColor *)opaqueColor; 32 | 33 | - (void)setTextColor:(UIColor *)textColor; 34 | - (void)setFont:(UIFont *)font; 35 | - (void)setText:(NSString *)text; 36 | 37 | - (void)setAnimatedColors:(NSArray *)animatedColors withKeyTimes:(NSArray *)keyTimes; 38 | 39 | //- (void)animateColorToOffset:(CGFloat)animOffset returnColor:(void (^)(UIColor *opaqueReturnColor))block; 40 | 41 | - (void)setFrame:(CGRect)frame arrowOffset:(CGFloat)arrowOffset colorOffset:(CGFloat)colorOffset text:(NSString *)text; 42 | 43 | - (void)animateBlock:(void (^)(CFTimeInterval duration))block; 44 | 45 | - (CGSize)popUpSizeForString:(NSString *)string; 46 | 47 | - (void)showAnimated:(BOOL)animated; 48 | - (void)hideAnimated:(BOOL)animated completionBlock:(void (^)())block; 49 | 50 | @end -------------------------------------------------------------------------------- /Example/Example/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Example 4 | // 5 | // Created by Alan Skipp on 16/04/2014. 6 | // Copyright (c) 2014 Alan Skipp. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // 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. 22 | // 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. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // 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. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /ASProgressPopUpView/ASProgressPopUpView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ASProgressPopUpView.m 3 | // ASProgressPopUpView 4 | // 5 | // Created by Alan Skipp on 27/03/2014. 6 | // Copyright (c) 2014 Alan Skipp. All rights reserved. 7 | // 8 | 9 | #import 10 | @protocol ASProgressPopUpViewDelegate; 11 | @protocol ASProgressPopUpViewDataSource; 12 | 13 | @interface ASProgressPopUpView : UIView 14 | 15 | - (void)showPopUpViewAnimated:(BOOL)animated; 16 | - (void)hidePopUpViewAnimated:(BOOL)animated; 17 | 18 | @property(nonatomic) float progress; // 0.0 .. 1.0, default is 0.0. values outside are pinned. 19 | //@property(strong, nonatomic) UIColor* progressTintColor; 20 | @property(strong, nonatomic) UIColor* trackTintColor; 21 | 22 | - (void)setProgress:(float)progress animated:(BOOL)animated; 23 | 24 | @property (strong, nonatomic) UIColor *textColor; 25 | 26 | // font can not be nil, it must be a valid UIFont 27 | // default is ‘boldSystemFontOfSize:20.0’ 28 | @property (strong, nonatomic) UIFont *font; 29 | 30 | // setting the value of 'popUpViewColor' overrides 'popUpViewAnimatedColors' and vice versa 31 | // the return value of 'popUpViewColor' is the currently displayed value 32 | // this will vary if 'popUpViewAnimatedColors' is set (see below) 33 | @property (strong, nonatomic) UIColor *popUpViewColor; 34 | 35 | // pass an array of 2 or more UIColors to animate the color change as the progress updates 36 | @property (strong, nonatomic) NSArray *popUpViewAnimatedColors; 37 | 38 | // the above @property distributes the colors evenly across the progress view 39 | // to specify the exact position of colors, pass an NSArray of NSNumbers (in the range 0.0 - 1.0) 40 | - (void)setPopUpViewAnimatedColors:(NSArray *)popUpViewAnimatedColors withPositions:(NSArray *)positions; 41 | 42 | // radius of the popUpView, default is 4.0 43 | @property (nonatomic) CGFloat popUpViewCornerRadius; 44 | 45 | 46 | // by default the popUpView size will be static and calculated to fit the largest display value 47 | // to have the size continuously adjust set the property to YES 48 | // if you set a dataSource the property will automatically be set to YES 49 | @property (nonatomic) BOOL continuouslyAdjustPopUpViewSize; // (default is NO) 50 | 51 | // by default the popUpView displays progress from 0% - 100% 52 | // to display custom text instead, implement the datasource protocol - see below 53 | // setting a dataSource automatically sets continuouslyAdjustPopUpViewSize to YES. 54 | @property (weak, nonatomic) id dataSource; 55 | 56 | // delegate is only needed when used with a TableView or CollectionView - see below 57 | @property (weak, nonatomic) id delegate; 58 | @end 59 | 60 | 61 | // to supply custom text to the popUpView label, implement 62 | // the dataSource will be messaged each time the progress changes 63 | @protocol ASProgressPopUpViewDataSource 64 | - (NSString *)progressView:(ASProgressPopUpView *)progressView stringForProgress:(float)progress; 65 | 66 | // required to calculate the default size for the popUpView 67 | // must return an Array of all the custom strings which will be displayed 68 | - (NSArray *)allStringsForProgressView:(ASProgressPopUpView *)progressView; 69 | @end 70 | 71 | 72 | // when embedding an ASProgressPopUpView inside a TableView or CollectionView 73 | // you need to ensure that the cell it resides in is brought to the front of the view hierarchy 74 | // to prevent the popUpView from being obscured 75 | @protocol ASProgressPopUpViewDelegate 76 | - (void)progressViewWillDisplayPopUpView:(ASProgressPopUpView *)progressView; 77 | 78 | @optional 79 | - (void)progressViewDidHidePopUpView:(ASProgressPopUpView *)progressView; 80 | @end 81 | 82 | /* 83 | // the recommended technique for use with a tableView is to create a UITableViewCell subclass ↓ 84 | 85 | @interface ProgressCell : UITableViewCell 86 | @property (weak, nonatomic) IBOutlet ASProgressPopUpView *progressView; 87 | @end 88 | 89 | @implementation ProgressCell 90 | - (void)awakeFromNib 91 | { 92 | self.progressView.delegate = self; 93 | } 94 | 95 | - (void)progressViewWillDisplayPopUpView:(ASProgressPopUpView *)progressView; 96 | { 97 | [self.superview bringSubviewToFront:self]; 98 | } 99 | @end 100 | */ 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ASProgressPopUpView 2 | ======== 3 | 4 | ###What is it? 5 | 6 | 7 | A UIProgressView subclass that displays the percentage complete in an easy to customize popUpView. 8 | 9 | ![screenshot] (http://alskipp.github.io/ASProgressPopUpView/img/screenshot1.gif) 10 | 11 | If you'd like similar functionality for UISlider, then take a look at [ASValueTrackingSlider](https://github.com/alskipp/ASValueTrackingSlider). 12 | 13 | 14 | Features 15 | --- 16 | 17 | * Live updating of progress 18 | * Customizable properties: 19 | * textColor 20 | * font 21 | * popUpViewColor 22 | * popUpViewAnimatedColors - popUpView and progress bar color animate as value changes 23 | * popUpViewCornerRadius 24 | * Optional dataSource - supply your own custom text to the popUpView label 25 | * Wholesome springy animation 26 | 27 | 28 | Which files are needed? 29 | --- 30 | 31 | For [CocoaPods](http://beta.cocoapods.org) users, simply add `pod 'ASProgressPopUpView'` to your podfile. Don't forget, CocoaPods includes the `try` command, type `$ pod try ASProgressPopUpView` in the terminal, CocoaPods will download the demo project into a temp folder and open it in Xcode. Magic. 32 | 33 | If you don't use CocoaPods, just include these files in your project: 34 | 35 | * ASProgressPopUpView (.h .m) 36 | * ASPopUpView (.h .m) 37 | 38 | 39 | How to use it 40 | --- 41 | 42 | It’s very simple. Drag a `UIView` into your Storyboard/nib and set the height and width to the size you need, 43 | now set its class to `ASProgressPopUpView` – that's it. 44 | The example below demonstrates how to customize the appearance. 45 | 46 | ```objective-c 47 | self.progressView.font = [UIFont fontWithName:@"Futura-CondensedExtraBold" size:26]; 48 | self.progressView.popUpViewAnimatedColors = @[[UIColor redColor], [UIColor orangeColor], [UIColor greenColor]]; 49 | self.progressView.popUpViewCornerRadius = 16.0; 50 | ``` 51 | 52 | To show the popUpView, just call: 53 | 54 | ```objective-c 55 | [self.progressView showPopUpViewAnimated:YES]; 56 | ``` 57 | 58 | And to hide: 59 | 60 | ```objective-c 61 | [self.progressView hidePopUpViewAnimated:YES]; 62 | ``` 63 | 64 | You update the value exactly as you would normally use a UIProgressView, just update the `progress` property `self.progressView.progress = 0.31;`. 65 | 66 | ![screenshot] (http://alskipp.github.io/ASProgressPopUpView/img/screenshot2.png) 67 | 68 | With `version 0.7.1` and above, the animated progress method is supported `- (void)setProgress:(float)progress animated:(BOOL)animated`. When updating the progress in increments `> 0.05` the results will be much smoother using the animated form. 69 | 70 | 71 | ###How to use custom strings in popUpView label 72 | 73 | Set your controller as the `dataSource` to `ASProgressPopUpView`, then return NSStrings for any progress values you want to customize. 74 | 75 | ```objective-c 76 | - (NSString *)progressView:(ASProgressPopUpView *)progressView stringForProgress:(float)progress 77 | { 78 | NSString *s; 79 | if (progress < 0.2) { 80 | s = @"Just starting"; 81 | } else if (progress > 0.4 && progress < 0.6) { 82 | s = @"About halfway"; 83 | } else if (progress > 0.75 && progress < 1.0) { 84 | s = @"Nearly there"; 85 | } else if (progress >= 1.0) { 86 | s = @"Complete"; 87 | } 88 | return s; 89 | } 90 | ``` 91 | 92 | ![screenshot] (http://alskipp.github.io/ASProgressPopUpView/img/screenshot3.png) 93 | 94 | 95 | ###How to use with UITableView 96 | 97 | To use effectively inside a UITableView you need to implement the `` protocol. If you just embed an ASProgressPopUpView inside a UITableViewCell the popUpView will probably be obscured by the cell above. The delegate method notifies you before the popUpView appears so that you can ensure that your UITableViewCell is rendered above the others. 98 | 99 | The recommended technique for use with UITableView is to create a UITableViewCell subclass that implements the delegate method. 100 | 101 | 102 | ```objective-c 103 | @interface ProgressCell : UITableViewCell 104 | @property (weak, nonatomic) IBOutlet ASProgressPopUpView *progressView; 105 | @end 106 | 107 | @implementation ProgressCell 108 | - (void)awakeFromNib 109 | { 110 | self.progressView.delegate = self; 111 | } 112 | 113 | - (void)progressViewWillDisplayPopUpView:(ASProgressPopUpView *)progressView; 114 | { 115 | [self.superview bringSubviewToFront:self]; 116 | } 117 | @end 118 | ``` 119 | 120 | -------------------------------------------------------------------------------- /Example/Example/Launch Screen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Example/Example/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Example 4 | // 5 | // Created by Alan Skipp on 16/04/2014. 6 | // Copyright (c) 2014 Alan Skipp. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "ASProgressPopUpView.h" 11 | 12 | @interface ViewController () 13 | @property (weak, nonatomic) IBOutlet ASProgressPopUpView *progressView1; 14 | @property (weak, nonatomic) IBOutlet ASProgressPopUpView *progressView2; 15 | 16 | @property (weak, nonatomic) IBOutlet UIButton *progressButton; 17 | @property (weak, nonatomic) IBOutlet UIButton *continuousButton; 18 | @end 19 | 20 | @implementation ViewController 21 | { 22 | NSTimer *_timer; 23 | } 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | self.progressView1.font = [UIFont fontWithName:@"Futura-CondensedExtraBold" size:16]; 29 | self.progressView1.popUpViewAnimatedColors = @[[UIColor redColor], [UIColor orangeColor], [UIColor greenColor]]; 30 | self.progressView1.dataSource = self; 31 | 32 | self.progressView2.popUpViewCornerRadius = 12.0; 33 | self.progressView2.font = [UIFont fontWithName:@"Futura-CondensedExtraBold" size:28]; 34 | self.progressView2.popUpViewAnimatedColors = @[[UIColor colorWithHue:0.4 saturation:0.4 brightness:0.3 alpha:1.0], 35 | [UIColor colorWithHue:0.5 saturation:0.9 brightness:0.8 alpha:1.0]]; 36 | 37 | [self.progressView1 showPopUpViewAnimated:YES]; 38 | [self.progressView2 showPopUpViewAnimated:YES]; 39 | } 40 | 41 | #pragma mark - IBActions 42 | 43 | - (IBAction)toggleShowHide:(UISwitch *)sender 44 | { 45 | if (sender.on) { 46 | [self.progressView1 showPopUpViewAnimated:YES]; 47 | [self.progressView2 showPopUpViewAnimated:YES]; 48 | 49 | } else { 50 | [self.progressView1 hidePopUpViewAnimated:YES]; 51 | [self.progressView2 hidePopUpViewAnimated:YES]; 52 | } 53 | } 54 | 55 | - (IBAction)toggleContinuousProgress:(UIButton *)sender 56 | { 57 | sender.selected = !sender.selected; 58 | } 59 | 60 | - (IBAction)reset:(id)sender 61 | { 62 | self.progressButton.selected = NO; 63 | self.progressButton.enabled = YES; 64 | 65 | self.progressView1.progress = 0.0; 66 | self.progressView2.progress = 0.0; 67 | } 68 | 69 | - (IBAction)startProgress:(UIButton *)sender 70 | { 71 | sender.selected = !sender.selected; 72 | [self progress]; 73 | } 74 | 75 | #pragma mark - Timer 76 | 77 | - (void)progress 78 | { 79 | if (self.progressView1.progress >= 1.0) { 80 | self.progressButton.selected = NO; 81 | self.progressButton.enabled = NO; 82 | } 83 | 84 | float progress = self.progressView1.progress; 85 | if (self.progressButton.selected && progress < 1.0) { 86 | 87 | progress += _continuousButton.selected ? 0.005 : 0.1; 88 | 89 | [self.progressView1 setProgress:progress animated:!_continuousButton.selected]; 90 | [self.progressView2 setProgress:progress animated:!_continuousButton.selected]; 91 | 92 | [NSTimer scheduledTimerWithTimeInterval:_continuousButton.selected ? 0.05 : 0.5 93 | target:self 94 | selector:@selector(progress) 95 | userInfo:nil 96 | repeats:NO]; 97 | } 98 | } 99 | 100 | #pragma mark - ASProgressPopUpView dataSource 101 | 102 | // is entirely optional 103 | // it allows you to supply custom NSStrings to ASProgressPopUpView 104 | - (NSString *)progressView:(ASProgressPopUpView *)progressView stringForProgress:(float)progress 105 | { 106 | NSString *s; 107 | if (progress < 0.2) { 108 | s = @"Just starting"; 109 | } else if (progress > 0.4 && progress < 0.6) { 110 | s = @"About halfway"; 111 | } else if (progress > 0.75 && progress < 1.0) { 112 | s = @"Nearly there"; 113 | } else if (progress >= 1.0) { 114 | s = @"Complete"; 115 | } 116 | return s; 117 | } 118 | 119 | // required to calculate the default size for the popUpView 120 | // simply list all the custom strings that will be displayed 121 | - (NSArray *)allStringsForProgressView:(ASProgressPopUpView *)progressView; 122 | { 123 | return @[@"Just starting", @"About halfway", @"Nearly there", @"Complete"]; 124 | } 125 | 126 | #pragma mark - Cleanup 127 | 128 | - (void)didReceiveMemoryWarning 129 | { 130 | [super didReceiveMemoryWarning]; 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /ASProgressPopUpView/ASProgressPopUpView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ASProgressPopUpView.h 3 | // ASProgressPopUpView 4 | // 5 | // Created by Alan Skipp on 19/10/2013. 6 | // Copyright (c) 2013 Alan Skipp. All rights reserved. 7 | // 8 | 9 | #import "ASPopUpView.h" 10 | #import "ASProgressPopUpView.h" 11 | 12 | @interface ASProgressPopUpView() 13 | @property (strong, nonatomic) NSNumberFormatter *numberFormatter; 14 | @property (strong, nonatomic) ASPopUpView *popUpView; 15 | @end 16 | 17 | @implementation ASProgressPopUpView 18 | { 19 | UIColor *_popUpViewColor; 20 | NSArray *_keyTimes; 21 | BOOL _shouldAnimate; 22 | CALayer *_progressLayer; 23 | CAGradientLayer *_gradientLayer; 24 | } 25 | 26 | #pragma mark - initialization 27 | 28 | - (id)initWithFrame:(CGRect)frame 29 | { 30 | self = [super initWithFrame:frame]; 31 | if (self) { 32 | [self setup]; 33 | } 34 | return self; 35 | } 36 | 37 | - (id)initWithCoder:(NSCoder *)coder 38 | { 39 | self = [super initWithCoder:coder]; 40 | if (self) { 41 | [self setup]; 42 | } 43 | return self; 44 | } 45 | 46 | #pragma mark - public 47 | 48 | - (void)showPopUpViewAnimated:(BOOL)animated 49 | { 50 | if (self.popUpView.alpha == 1.0) return; 51 | 52 | [self.delegate progressViewWillDisplayPopUpView:self]; 53 | [self.popUpView showAnimated:animated]; 54 | } 55 | 56 | - (void)hidePopUpViewAnimated:(BOOL)animated 57 | { 58 | if (self.popUpView.alpha == 0.0) return; 59 | 60 | [self.popUpView hideAnimated:animated completionBlock:^{ 61 | if ([self.delegate respondsToSelector:@selector(progressViewDidHidePopUpView:)]) { 62 | [self.delegate progressViewDidHidePopUpView:self]; 63 | } 64 | }]; 65 | } 66 | 67 | - (void)setTextColor:(UIColor *)color 68 | { 69 | _textColor = color; 70 | [self.popUpView setTextColor:color]; 71 | } 72 | 73 | - (void)setFont:(UIFont *)font 74 | { 75 | NSAssert(font, @"font can not be nil, it must be a valid UIFont"); 76 | _font = font; 77 | [self.popUpView setFont:font]; 78 | } 79 | 80 | - (void)setTrackTintColor:(UIColor *)color 81 | { 82 | self.backgroundColor = color; 83 | } 84 | 85 | - (UIColor *)trackTintColor 86 | { 87 | return self.backgroundColor; 88 | } 89 | 90 | // return the currently displayed color if possible, otherwise return _popUpViewColor 91 | // if animated colors are set, the color will change each time the progress view updates 92 | - (UIColor *)popUpViewColor 93 | { 94 | return [self.popUpView color] ?: _popUpViewColor; 95 | } 96 | 97 | - (void)setPopUpViewColor:(UIColor *)color 98 | { 99 | _popUpViewColor = color; 100 | _popUpViewAnimatedColors = nil; // animated colors should be discarded 101 | [self.popUpView setColor:color]; 102 | [self setGradientColors:@[color, color] withPositions:nil]; 103 | } 104 | 105 | - (void)setPopUpViewAnimatedColors:(NSArray *)colors 106 | { 107 | [self setPopUpViewAnimatedColors:colors withPositions:nil]; 108 | } 109 | 110 | // if 2 or more colors are present, set animated colors 111 | // if only 1 color is present then call 'setPopUpViewColor:' 112 | // if arg is nil then restore previous _popUpViewColor 113 | - (void)setPopUpViewAnimatedColors:(NSArray *)colors withPositions:(NSArray *)positions 114 | { 115 | if (positions) { 116 | NSAssert([colors count] == [positions count], @"popUpViewAnimatedColors and locations should contain the same number of items"); 117 | } 118 | 119 | _popUpViewAnimatedColors = colors; 120 | _keyTimes = positions; 121 | 122 | if ([colors count] >= 2) { 123 | [self.popUpView setAnimatedColors:colors withKeyTimes:_keyTimes]; 124 | [self setGradientColors:colors withPositions:positions]; 125 | } else { 126 | [self setGradientColors:colors withPositions:positions]; 127 | // [self setPopUpViewColor:[colors lastObject] ?: _popUpViewColor]; 128 | } 129 | } 130 | 131 | - (void)setPopUpViewCornerRadius:(CGFloat)radius 132 | { 133 | self.popUpView.cornerRadius = radius; 134 | } 135 | 136 | - (CGFloat)popUpViewCornerRadius 137 | { 138 | return self.popUpView.cornerRadius; 139 | } 140 | 141 | - (void)setDataSource:(id)dataSource 142 | { 143 | _dataSource = dataSource; 144 | self.continuouslyAdjustPopUpViewSize = YES; 145 | } 146 | 147 | // returns the current progress in the range 0.0 – 1.0 148 | - (CGFloat)currentValueOffset 149 | { 150 | return self.progress; 151 | } 152 | 153 | #pragma mark - private 154 | 155 | - (void)setup 156 | { 157 | _progressLayer = [CALayer layer]; 158 | _progressLayer.masksToBounds = YES; 159 | _progressLayer.anchorPoint = CGPointMake(0, 0); 160 | [self.layer addSublayer:_progressLayer]; 161 | 162 | _gradientLayer = [CAGradientLayer layer]; 163 | _gradientLayer.startPoint = CGPointZero; 164 | _gradientLayer.endPoint = CGPointMake(1, 0); 165 | [_progressLayer addSublayer:_gradientLayer]; 166 | 167 | self.progress = 0; 168 | 169 | _continuouslyAdjustPopUpViewSize = NO; 170 | 171 | NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; 172 | [formatter setNumberStyle:NSNumberFormatterPercentStyle]; 173 | _numberFormatter = formatter; 174 | 175 | self.popUpView = [[ASPopUpView alloc] initWithFrame:CGRectZero]; 176 | self.popUpViewColor = [UIColor colorWithHue:0.6 saturation:0.6 brightness:0.5 alpha:0.8]; 177 | 178 | self.popUpView.alpha = 0.0; 179 | self.popUpView.delegate = self; 180 | [self addSubview:self.popUpView]; 181 | 182 | self.textColor = [UIColor whiteColor]; 183 | self.font = [UIFont boldSystemFontOfSize:20.0f]; 184 | } 185 | 186 | // ensure animation restarts if app is closed then becomes active again 187 | - (void)didBecomeActiveNotification:(NSNotification *)note 188 | { 189 | if (self.popUpViewAnimatedColors) { 190 | [self.popUpView setAnimatedColors:_popUpViewAnimatedColors withKeyTimes:_keyTimes]; 191 | } 192 | } 193 | 194 | - (void)updatePopUpView 195 | { 196 | NSString *progressString; // ask dataSource for string, if nil get string from _numberFormatter 197 | progressString = [self.dataSource progressView:self stringForProgress:self.progress] ?: [_numberFormatter stringFromNumber:@(self.progress)]; 198 | if (progressString.length == 0) progressString = @"???"; // replacement for blank string 199 | 200 | CGSize popUpViewSize = (self.continuouslyAdjustPopUpViewSize == YES) 201 | ? [self.popUpView popUpSizeForString:progressString] 202 | : [self calculatePopUpViewSize]; 203 | 204 | // calculate the popUpView frame 205 | CGRect bounds = self.bounds; 206 | CGFloat xPos = (CGRectGetWidth(bounds) * self.progress) - popUpViewSize.width/2; 207 | 208 | CGRect popUpRect = CGRectMake(xPos, CGRectGetMinY(bounds)-popUpViewSize.height, 209 | popUpViewSize.width, popUpViewSize.height); 210 | 211 | // determine if popUpRect extends beyond the frame of the progress view 212 | // if so adjust frame and set the center offset of the PopUpView's arrow 213 | CGFloat minOffsetX = CGRectGetMinX(popUpRect); 214 | CGFloat maxOffsetX = CGRectGetMaxX(popUpRect) - CGRectGetWidth(bounds); 215 | 216 | CGFloat offset = minOffsetX < 0.0 ? minOffsetX : (maxOffsetX > 0.0 ? maxOffsetX : 0.0); 217 | popUpRect.origin.x -= offset; 218 | 219 | [self.popUpView setFrame:popUpRect arrowOffset:offset colorOffset:self.progress text:progressString]; 220 | 221 | } 222 | 223 | - (CGSize)calculatePopUpViewSize 224 | { 225 | NSString *defaultString = (self.dataSource) 226 | ? [self.dataSource progressView:self stringForProgress:1.0] 227 | : [_numberFormatter stringFromNumber:@1.0]; 228 | 229 | CGSize defaultPopUpViewSize = [self.popUpView popUpSizeForString:defaultString]; 230 | if (!self.dataSource) return defaultPopUpViewSize; 231 | 232 | // calculate the largest popUpView size needed to keep the size consistent 233 | // ask the dataSource for 'allStringsForProgressView' 234 | // set size to the largest width and height returned from the dataSource 235 | 236 | CGFloat width = 0.0, height = 0.0; 237 | for (NSString *string in [self.dataSource allStringsForProgressView:self]) { 238 | CGSize size = [self.popUpView popUpSizeForString:string]; 239 | if (size.width > width) width = size.width; 240 | if (size.height > height) height = size.height; 241 | } 242 | 243 | return (width > defaultPopUpViewSize.width) ? CGSizeMake(width, height) : defaultPopUpViewSize; 244 | } 245 | 246 | #pragma mark - subclassed 247 | 248 | - (void)layoutSubviews 249 | { 250 | [super layoutSubviews]; 251 | 252 | [self updateProgressLayer]; 253 | [self updatePopUpView]; 254 | } 255 | 256 | - (void)updateProgressLayer 257 | { 258 | _gradientLayer.frame = self.bounds; 259 | _progressLayer.frame = CGRectMake(0, 0, self.bounds.size.width * self.progress, self.bounds.size.height); 260 | } 261 | 262 | 263 | - (void)didMoveToWindow 264 | { 265 | if (!self.window) { // removed from window - cancel observers and notifications 266 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 267 | } 268 | else { // added to window - register observers, notifications and reset animated colors if needed 269 | [[NSNotificationCenter defaultCenter] addObserver:self 270 | selector:@selector(didBecomeActiveNotification:) 271 | name:UIApplicationDidBecomeActiveNotification 272 | object:nil]; 273 | 274 | if (self.popUpViewAnimatedColors) { 275 | [self.popUpView setAnimatedColors:_popUpViewAnimatedColors withKeyTimes:_keyTimes]; 276 | } 277 | } 278 | } 279 | 280 | //- (void)setProgressTintColor:(UIColor *)color 281 | //{ 282 | // self.progressLayer.backgroundColor = color.CGColor; 283 | //} 284 | // 285 | //- (UIColor *)progressTintColor 286 | //{ 287 | // return [UIColor colorWithCGColor:self.progressLayer.backgroundColor]; 288 | //} 289 | 290 | - (void)setProgress:(float)progress 291 | { 292 | _progress = MAX(0.0, MIN(progress, 1.0)); 293 | [self updateProgressLayer]; 294 | } 295 | 296 | - (void)setProgress:(float)progress animated:(BOOL)animated 297 | { 298 | _shouldAnimate = animated; 299 | 300 | if (animated) { 301 | [self.popUpView animateBlock:^(CFTimeInterval duration) { 302 | CABasicAnimation *anim = [CABasicAnimation animation]; 303 | anim.duration = duration; 304 | anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 305 | anim.fromValue = [_progressLayer.presentationLayer valueForKey:@"bounds"]; 306 | _progressLayer.actions = @{@"bounds" : anim}; 307 | 308 | [UIView animateWithDuration:duration animations:^{ 309 | self.progress = progress; 310 | [self layoutIfNeeded]; 311 | }]; 312 | }]; 313 | } else { 314 | _progressLayer.actions = @{@"bounds" : [NSNull null]}; 315 | self.progress = progress; 316 | } 317 | } 318 | 319 | - (void)setGradientColors:(NSArray *)gradientColors withPositions:(NSArray *)positions 320 | { 321 | NSMutableArray *cgColors = [NSMutableArray array]; 322 | for (UIColor *col in gradientColors) { 323 | [cgColors addObject:(id)col.CGColor]; 324 | } 325 | 326 | _gradientLayer.colors = cgColors; 327 | _gradientLayer.locations = positions; 328 | } 329 | 330 | @end 331 | -------------------------------------------------------------------------------- /Example/Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 48 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /ASProgressPopUpView/ASPopUpView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ASPopUpView.m 3 | // ASProgressPopUpView 4 | // 5 | // Created by Alan Skipp on 27/03/2014. 6 | // Copyright (c) 2014 Alan Skipp. All rights reserved. 7 | // 8 | 9 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 | // This UIView subclass is used internally by ASProgressPopUpView 11 | // The public API is declared in ASProgressPopUpView.h 12 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 13 | 14 | #import "ASPopUpView.h" 15 | 16 | @implementation CALayer (ASAnimationAdditions) 17 | 18 | - (void)animateKey:(NSString *)animationName fromValue:(id)fromValue toValue:(id)toValue 19 | customize:(void (^)(CABasicAnimation *animation))block 20 | { 21 | [self setValue:toValue forKey:animationName]; 22 | CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:animationName]; 23 | anim.fromValue = fromValue ?: [self.presentationLayer valueForKey:animationName]; 24 | anim.toValue = toValue; 25 | if (block) block(anim); 26 | [self addAnimation:anim forKey:animationName]; 27 | } 28 | @end 29 | 30 | NSString *const FillColorAnimation = @"fillColor"; 31 | 32 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 33 | @interface ASPopUpView () 34 | @end 35 | #endif 36 | 37 | 38 | @implementation ASPopUpView 39 | { 40 | BOOL _shouldAnimate; 41 | CFTimeInterval _animDuration; 42 | 43 | NSMutableAttributedString *_attributedString; 44 | CAShapeLayer *_pathLayer; 45 | 46 | CATextLayer *_textLayer; 47 | CGFloat _arrowCenterOffset; 48 | 49 | // never actually visible, its purpose is to interpolate color values for the popUpView color animation 50 | // using shape layer because it has a 'fillColor' property which is consistent with _backgroundLayer 51 | CAShapeLayer *_colorAnimLayer; 52 | } 53 | 54 | + (Class)layerClass { 55 | return [CAShapeLayer class]; 56 | } 57 | 58 | // if ivar _shouldAnimate) is YES then return an animation 59 | // otherwise return NSNull (no animation) 60 | - (id )actionForLayer:(CALayer *)layer forKey:(NSString *)key 61 | { 62 | if (_shouldAnimate) { 63 | CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:key]; 64 | anim.beginTime = CACurrentMediaTime(); 65 | anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 66 | anim.fromValue = [layer.presentationLayer valueForKey:key]; 67 | anim.duration = _animDuration; 68 | return anim; 69 | } else return (id )[NSNull null]; 70 | } 71 | 72 | #pragma mark - public 73 | 74 | - (id)initWithFrame:(CGRect)frame 75 | { 76 | self = [super initWithFrame:frame]; 77 | if (self) { 78 | _shouldAnimate = NO; 79 | self.layer.anchorPoint = CGPointMake(0.5, 1); 80 | 81 | self.userInteractionEnabled = NO; 82 | _pathLayer = (CAShapeLayer *)self.layer; // ivar can now be accessed without casting to CAShapeLayer every time 83 | 84 | _cornerRadius = 4.0; 85 | _arrowLength = 8.0; 86 | _widthPaddingFactor = 1.15; 87 | _heightPaddingFactor = 1.1; 88 | 89 | _textLayer = [CATextLayer layer]; 90 | _textLayer.alignmentMode = kCAAlignmentCenter; 91 | _textLayer.anchorPoint = CGPointMake(0, 0); 92 | _textLayer.contentsScale = [UIScreen mainScreen].scale; 93 | _textLayer.actions = @{@"contents" : [NSNull null]}; 94 | 95 | _colorAnimLayer = [CAShapeLayer layer]; 96 | 97 | [self.layer addSublayer:_colorAnimLayer]; 98 | [self.layer addSublayer:_textLayer]; 99 | 100 | _attributedString = [[NSMutableAttributedString alloc] initWithString:@" " attributes:nil]; 101 | } 102 | return self; 103 | } 104 | 105 | - (void)setCornerRadius:(CGFloat)radius 106 | { 107 | if (_cornerRadius == radius) return; 108 | _cornerRadius = radius; 109 | _pathLayer.path = [self pathForRect:self.bounds withArrowOffset:_arrowCenterOffset].CGPath; 110 | } 111 | 112 | - (UIColor *)color 113 | { 114 | return [UIColor colorWithCGColor:[_pathLayer.presentationLayer fillColor]]; 115 | } 116 | 117 | - (void)setColor:(UIColor *)color 118 | { 119 | _pathLayer.fillColor = color.CGColor; 120 | [_colorAnimLayer removeAnimationForKey:FillColorAnimation]; // single color, no animation required 121 | } 122 | 123 | - (UIColor *)opaqueColor 124 | { 125 | return opaqueUIColorFromCGColor([_colorAnimLayer.presentationLayer fillColor] ?: _pathLayer.fillColor); 126 | } 127 | 128 | - (void)setTextColor:(UIColor *)color 129 | { 130 | _textLayer.foregroundColor = color.CGColor; 131 | } 132 | 133 | - (void)setFont:(UIFont *)font 134 | { 135 | [_attributedString addAttribute:NSFontAttributeName 136 | value:font 137 | range:NSMakeRange(0, [_attributedString length])]; 138 | 139 | _textLayer.font = (__bridge CFTypeRef)(font.fontName); 140 | _textLayer.fontSize = font.pointSize; 141 | } 142 | 143 | - (void)setText:(NSString *)string 144 | { 145 | [[_attributedString mutableString] setString:string]; 146 | _textLayer.string = string; 147 | } 148 | 149 | // set up an animation, but prevent it from running automatically 150 | // the animation progress will be adjusted manually 151 | - (void)setAnimatedColors:(NSArray *)animatedColors withKeyTimes:(NSArray *)keyTimes 152 | { 153 | NSMutableArray *cgColors = [NSMutableArray array]; 154 | for (UIColor *col in animatedColors) { 155 | [cgColors addObject:(id)col.CGColor]; 156 | } 157 | 158 | CAKeyframeAnimation *colorAnim = [CAKeyframeAnimation animationWithKeyPath:FillColorAnimation]; 159 | colorAnim.keyTimes = keyTimes; 160 | colorAnim.values = cgColors; 161 | colorAnim.fillMode = kCAFillModeBoth; 162 | colorAnim.duration = 1.0; 163 | colorAnim.delegate = self; 164 | 165 | // As the interpolated color values from the presentationLayer are needed immediately 166 | // the animation must be allowed to start to initialize _colorAnimLayer's presentationLayer 167 | // hence the speed is set to min value - then set to zero in 'animationDidStart:' delegate method 168 | _colorAnimLayer.speed = FLT_MIN; 169 | _colorAnimLayer.timeOffset = 0.0; 170 | 171 | [_colorAnimLayer addAnimation:colorAnim forKey:FillColorAnimation]; 172 | } 173 | 174 | - (void)setFrame:(CGRect)frame arrowOffset:(CGFloat)arrowOffset colorOffset:(CGFloat)colorOffset text:(NSString *)text 175 | { 176 | // only redraw path if either the arrowOffset or popUpView size has changed 177 | if (arrowOffset != _arrowCenterOffset || !CGSizeEqualToSize(frame.size, self.frame.size)) { 178 | _pathLayer.path = [self pathForRect:frame withArrowOffset:arrowOffset].CGPath; 179 | } 180 | _arrowCenterOffset = arrowOffset; 181 | 182 | CGFloat anchorX = 0.5+(arrowOffset/CGRectGetWidth(frame)); 183 | self.layer.anchorPoint = CGPointMake(anchorX, 1); 184 | self.layer.position = CGPointMake(CGRectGetMinX(frame) + CGRectGetWidth(frame)*anchorX, 0); 185 | self.layer.bounds = (CGRect){CGPointZero, frame.size}; 186 | 187 | [self setText:text]; 188 | 189 | if ([_colorAnimLayer animationForKey:FillColorAnimation]) { 190 | _colorAnimLayer.timeOffset = colorOffset; 191 | _pathLayer.fillColor = [_colorAnimLayer.presentationLayer fillColor]; 192 | } 193 | } 194 | 195 | // _shouldAnimate = YES; causes 'actionForLayer:' to return an animation for layer property changes 196 | // call the supplied block, then set _shouldAnimate back to NO 197 | - (void)animateBlock:(void (^)(CFTimeInterval duration))block 198 | { 199 | _shouldAnimate = YES; 200 | _animDuration = 0.5; 201 | 202 | CAAnimation *anim = [self.layer animationForKey:@"position"]; 203 | if ((anim)) { // if previous animation hasn't finished reduce the time of new animation 204 | CFTimeInterval elapsedTime = MIN(CACurrentMediaTime() - anim.beginTime, anim.duration); 205 | _animDuration = _animDuration * elapsedTime / anim.duration; 206 | } 207 | 208 | block(_animDuration); 209 | _shouldAnimate = NO; 210 | } 211 | 212 | - (CGSize)popUpSizeForString:(NSString *)string 213 | { 214 | [[_attributedString mutableString] setString:string]; 215 | CGFloat w, h; 216 | w = ceilf([_attributedString size].width * _widthPaddingFactor); 217 | h = ceilf(([_attributedString size].height * _heightPaddingFactor) + _arrowLength); 218 | return CGSizeMake(w, h); 219 | } 220 | 221 | - (void)showAnimated:(BOOL)animated 222 | { 223 | if (!animated) { 224 | self.layer.opacity = 1.0; 225 | return; 226 | } 227 | 228 | [CATransaction begin]; { 229 | // start the transform animation from scale 0.5, or its current value if it's already running 230 | NSValue *fromValue = [self.layer animationForKey:@"transform"] ? [self.layer.presentationLayer valueForKey:@"transform"] : [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 1)]; 231 | 232 | [self.layer animateKey:@"transform" fromValue:fromValue toValue:[NSValue valueWithCATransform3D:CATransform3DIdentity] 233 | customize:^(CABasicAnimation *animation) { 234 | animation.duration = 0.6; 235 | animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.8 :2.5 :0.35 :0.5]; 236 | }]; 237 | 238 | [self.layer animateKey:@"opacity" fromValue:nil toValue:@1.0 customize:^(CABasicAnimation *animation) { 239 | animation.duration = 0.1; 240 | }]; 241 | 242 | } [CATransaction commit]; 243 | } 244 | 245 | - (void)hideAnimated:(BOOL)animated completionBlock:(void (^)())block 246 | { 247 | [CATransaction begin]; { 248 | [CATransaction setCompletionBlock:^{ 249 | block(); 250 | self.layer.transform = CATransform3DIdentity; 251 | }]; 252 | if (animated) { 253 | [self.layer animateKey:@"transform" fromValue:nil 254 | toValue:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.5, 0.5, 1)] 255 | customize:^(CABasicAnimation *animation) { 256 | animation.duration = 0.55; 257 | animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.1 :-2 :0.3 :3]; 258 | }]; 259 | 260 | [self.layer animateKey:@"opacity" fromValue:nil toValue:@0.0 customize:^(CABasicAnimation *animation) { 261 | animation.duration = 0.75; 262 | }]; 263 | } else { // not animated - just set opacity to 0.0 264 | self.layer.opacity = 0.0; 265 | } 266 | } [CATransaction commit]; 267 | } 268 | 269 | #pragma mark - CAAnimation delegate 270 | 271 | // set the speed to zero to freeze the animation and set the offset to the correct value 272 | // the animation can now be updated manually by explicity setting its 'timeOffset' 273 | - (void)animationDidStart:(CAAnimation *)animation 274 | { 275 | _colorAnimLayer.speed = 0.0; 276 | _colorAnimLayer.timeOffset = [self.delegate currentValueOffset]; 277 | 278 | _pathLayer.fillColor = [_colorAnimLayer.presentationLayer fillColor]; 279 | // [self.delegate colorDidUpdate:[self opaqueColor]]; 280 | } 281 | 282 | #pragma mark - private 283 | 284 | - (UIBezierPath *)pathForRect:(CGRect)rect withArrowOffset:(CGFloat)arrowOffset; 285 | { 286 | if (CGRectEqualToRect(rect, CGRectZero)) return nil; 287 | 288 | rect = (CGRect){CGPointZero, rect.size}; // ensure origin is CGPointZero 289 | 290 | // Create rounded rect 291 | CGRect roundedRect = rect; 292 | roundedRect.size.height -= _arrowLength; 293 | UIBezierPath *popUpPath = [UIBezierPath bezierPathWithRoundedRect:roundedRect cornerRadius:_cornerRadius]; 294 | 295 | // Create arrow path 296 | CGFloat maxX = CGRectGetMaxX(roundedRect); // prevent arrow from extending beyond this point 297 | CGFloat arrowTipX = CGRectGetMidX(rect) + arrowOffset; 298 | CGPoint tip = CGPointMake(arrowTipX, CGRectGetMaxY(rect)); 299 | 300 | CGFloat arrowLength = CGRectGetHeight(roundedRect)/2.0; 301 | CGFloat x = arrowLength * tan(45.0 * M_PI/180); // x = half the length of the base of the arrow 302 | 303 | UIBezierPath *arrowPath = [UIBezierPath bezierPath]; 304 | [arrowPath moveToPoint:tip]; 305 | [arrowPath addLineToPoint:CGPointMake(MAX(arrowTipX - x, 0), CGRectGetMaxY(roundedRect) - arrowLength)]; 306 | [arrowPath addLineToPoint:CGPointMake(MIN(arrowTipX + x, maxX), CGRectGetMaxY(roundedRect) - arrowLength)]; 307 | [arrowPath closePath]; 308 | 309 | [popUpPath appendPath:arrowPath]; 310 | 311 | return popUpPath; 312 | } 313 | 314 | - (void)layoutSubviews 315 | { 316 | [super layoutSubviews]; 317 | 318 | CGFloat textHeight = [_attributedString size].height; 319 | CGRect textRect = CGRectMake(self.bounds.origin.x, 320 | (self.bounds.size.height-_arrowLength-textHeight)/2, 321 | self.bounds.size.width, textHeight); 322 | _textLayer.frame = CGRectIntegral(textRect); 323 | } 324 | 325 | static UIColor* opaqueUIColorFromCGColor(CGColorRef col) 326 | { 327 | if (col == NULL) return nil; 328 | 329 | const CGFloat *components = CGColorGetComponents(col); 330 | UIColor *color; 331 | if (CGColorGetNumberOfComponents(col) == 2) { 332 | color = [UIColor colorWithWhite:components[0] alpha:1.0]; 333 | } else { 334 | color = [UIColor colorWithRed:components[0] green:components[1] blue:components[2] alpha:1.0]; 335 | } 336 | return color; 337 | } 338 | 339 | @end 340 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 47; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E71B0485190FF28400394A2A /* ASPopUpView.m in Sources */ = {isa = PBXBuildFile; fileRef = E71B0482190FF28400394A2A /* ASPopUpView.m */; }; 11 | E71B0486190FF28400394A2A /* ASProgressPopUpView.m in Sources */ = {isa = PBXBuildFile; fileRef = E71B0484190FF28400394A2A /* ASProgressPopUpView.m */; }; 12 | E72C351C18FEC3A800D814BA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E72C351B18FEC3A800D814BA /* Foundation.framework */; }; 13 | E72C351E18FEC3A800D814BA /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E72C351D18FEC3A800D814BA /* CoreGraphics.framework */; }; 14 | E72C352018FEC3A800D814BA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E72C351F18FEC3A800D814BA /* UIKit.framework */; }; 15 | E72C352618FEC3A800D814BA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E72C352418FEC3A800D814BA /* InfoPlist.strings */; }; 16 | E72C352818FEC3A800D814BA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E72C352718FEC3A800D814BA /* main.m */; }; 17 | E72C352C18FEC3A800D814BA /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E72C352B18FEC3A800D814BA /* AppDelegate.m */; }; 18 | E72C352F18FEC3A800D814BA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E72C352D18FEC3A800D814BA /* Main.storyboard */; }; 19 | E72C353218FEC3A800D814BA /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E72C353118FEC3A800D814BA /* ViewController.m */; }; 20 | E72C353418FEC3A800D814BA /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E72C353318FEC3A800D814BA /* Images.xcassets */; }; 21 | E72C353B18FEC3A800D814BA /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E72C353A18FEC3A800D814BA /* XCTest.framework */; }; 22 | E72C353C18FEC3A800D814BA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E72C351B18FEC3A800D814BA /* Foundation.framework */; }; 23 | E72C353D18FEC3A800D814BA /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E72C351F18FEC3A800D814BA /* UIKit.framework */; }; 24 | E72C354518FEC3A800D814BA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E72C354318FEC3A800D814BA /* InfoPlist.strings */; }; 25 | E72C354718FEC3A800D814BA /* ExampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E72C354618FEC3A800D814BA /* ExampleTests.m */; }; 26 | E748EDC21D9021D200AAFD13 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E748EDC11D9021D200AAFD13 /* Launch Screen.storyboard */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | E72C353E18FEC3A800D814BA /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = E72C351018FEC3A800D814BA /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = E72C351718FEC3A800D814BA; 35 | remoteInfo = Example; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | E71B0481190FF28400394A2A /* ASPopUpView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASPopUpView.h; path = ../ASProgressPopUpView/ASPopUpView.h; sourceTree = ""; }; 41 | E71B0482190FF28400394A2A /* ASPopUpView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASPopUpView.m; path = ../ASProgressPopUpView/ASPopUpView.m; sourceTree = ""; }; 42 | E71B0483190FF28400394A2A /* ASProgressPopUpView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASProgressPopUpView.h; path = ../ASProgressPopUpView/ASProgressPopUpView.h; sourceTree = ""; }; 43 | E71B0484190FF28400394A2A /* ASProgressPopUpView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASProgressPopUpView.m; path = ../ASProgressPopUpView/ASProgressPopUpView.m; sourceTree = ""; }; 44 | E72C351818FEC3A800D814BA /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | E72C351B18FEC3A800D814BA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 46 | E72C351D18FEC3A800D814BA /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 47 | E72C351F18FEC3A800D814BA /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 48 | E72C352318FEC3A800D814BA /* Example-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Example-Info.plist"; sourceTree = ""; }; 49 | E72C352518FEC3A800D814BA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 50 | E72C352718FEC3A800D814BA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 51 | E72C352918FEC3A800D814BA /* Example-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Example-Prefix.pch"; sourceTree = ""; }; 52 | E72C352A18FEC3A800D814BA /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 53 | E72C352B18FEC3A800D814BA /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 54 | E72C352E18FEC3A800D814BA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | E72C353018FEC3A800D814BA /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 56 | E72C353118FEC3A800D814BA /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 57 | E72C353318FEC3A800D814BA /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 58 | E72C353918FEC3A800D814BA /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | E72C353A18FEC3A800D814BA /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 60 | E72C354218FEC3A800D814BA /* ExampleTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ExampleTests-Info.plist"; sourceTree = ""; }; 61 | E72C354418FEC3A800D814BA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 62 | E72C354618FEC3A800D814BA /* ExampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExampleTests.m; sourceTree = ""; }; 63 | E748EDC11D9021D200AAFD13 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | E72C351518FEC3A800D814BA /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | E72C351E18FEC3A800D814BA /* CoreGraphics.framework in Frameworks */, 72 | E72C352018FEC3A800D814BA /* UIKit.framework in Frameworks */, 73 | E72C351C18FEC3A800D814BA /* Foundation.framework in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | E72C353618FEC3A800D814BA /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | E72C353B18FEC3A800D814BA /* XCTest.framework in Frameworks */, 82 | E72C353D18FEC3A800D814BA /* UIKit.framework in Frameworks */, 83 | E72C353C18FEC3A800D814BA /* Foundation.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | E72C350F18FEC3A800D814BA = { 91 | isa = PBXGroup; 92 | children = ( 93 | E72C352118FEC3A800D814BA /* Example */, 94 | E72C354018FEC3A800D814BA /* ExampleTests */, 95 | E72C355718FEDC1300D814BA /* Vendor */, 96 | E72C351A18FEC3A800D814BA /* Frameworks */, 97 | E72C351918FEC3A800D814BA /* Products */, 98 | ); 99 | sourceTree = ""; 100 | }; 101 | E72C351918FEC3A800D814BA /* Products */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | E72C351818FEC3A800D814BA /* Example.app */, 105 | E72C353918FEC3A800D814BA /* ExampleTests.xctest */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | E72C351A18FEC3A800D814BA /* Frameworks */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | E72C351B18FEC3A800D814BA /* Foundation.framework */, 114 | E72C351D18FEC3A800D814BA /* CoreGraphics.framework */, 115 | E72C351F18FEC3A800D814BA /* UIKit.framework */, 116 | E72C353A18FEC3A800D814BA /* XCTest.framework */, 117 | ); 118 | name = Frameworks; 119 | sourceTree = ""; 120 | }; 121 | E72C352118FEC3A800D814BA /* Example */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | E72C352A18FEC3A800D814BA /* AppDelegate.h */, 125 | E72C352B18FEC3A800D814BA /* AppDelegate.m */, 126 | E72C352D18FEC3A800D814BA /* Main.storyboard */, 127 | E748EDC11D9021D200AAFD13 /* Launch Screen.storyboard */, 128 | E72C353018FEC3A800D814BA /* ViewController.h */, 129 | E72C353118FEC3A800D814BA /* ViewController.m */, 130 | E72C353318FEC3A800D814BA /* Images.xcassets */, 131 | E72C352218FEC3A800D814BA /* Supporting Files */, 132 | ); 133 | path = Example; 134 | sourceTree = ""; 135 | }; 136 | E72C352218FEC3A800D814BA /* Supporting Files */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | E72C352318FEC3A800D814BA /* Example-Info.plist */, 140 | E72C352418FEC3A800D814BA /* InfoPlist.strings */, 141 | E72C352718FEC3A800D814BA /* main.m */, 142 | E72C352918FEC3A800D814BA /* Example-Prefix.pch */, 143 | ); 144 | name = "Supporting Files"; 145 | sourceTree = ""; 146 | }; 147 | E72C354018FEC3A800D814BA /* ExampleTests */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | E72C354618FEC3A800D814BA /* ExampleTests.m */, 151 | E72C354118FEC3A800D814BA /* Supporting Files */, 152 | ); 153 | path = ExampleTests; 154 | sourceTree = ""; 155 | }; 156 | E72C354118FEC3A800D814BA /* Supporting Files */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | E72C354218FEC3A800D814BA /* ExampleTests-Info.plist */, 160 | E72C354318FEC3A800D814BA /* InfoPlist.strings */, 161 | ); 162 | name = "Supporting Files"; 163 | sourceTree = ""; 164 | }; 165 | E72C355718FEDC1300D814BA /* Vendor */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | E71B0481190FF28400394A2A /* ASPopUpView.h */, 169 | E71B0482190FF28400394A2A /* ASPopUpView.m */, 170 | E71B0483190FF28400394A2A /* ASProgressPopUpView.h */, 171 | E71B0484190FF28400394A2A /* ASProgressPopUpView.m */, 172 | ); 173 | name = Vendor; 174 | sourceTree = ""; 175 | }; 176 | /* End PBXGroup section */ 177 | 178 | /* Begin PBXNativeTarget section */ 179 | E72C351718FEC3A800D814BA /* Example */ = { 180 | isa = PBXNativeTarget; 181 | buildConfigurationList = E72C354A18FEC3A800D814BA /* Build configuration list for PBXNativeTarget "Example" */; 182 | buildPhases = ( 183 | E72C351418FEC3A800D814BA /* Sources */, 184 | E72C351518FEC3A800D814BA /* Frameworks */, 185 | E72C351618FEC3A800D814BA /* Resources */, 186 | ); 187 | buildRules = ( 188 | ); 189 | dependencies = ( 190 | ); 191 | name = Example; 192 | productName = Example; 193 | productReference = E72C351818FEC3A800D814BA /* Example.app */; 194 | productType = "com.apple.product-type.application"; 195 | }; 196 | E72C353818FEC3A800D814BA /* ExampleTests */ = { 197 | isa = PBXNativeTarget; 198 | buildConfigurationList = E72C354D18FEC3A800D814BA /* Build configuration list for PBXNativeTarget "ExampleTests" */; 199 | buildPhases = ( 200 | E72C353518FEC3A800D814BA /* Sources */, 201 | E72C353618FEC3A800D814BA /* Frameworks */, 202 | E72C353718FEC3A800D814BA /* Resources */, 203 | ); 204 | buildRules = ( 205 | ); 206 | dependencies = ( 207 | E72C353F18FEC3A800D814BA /* PBXTargetDependency */, 208 | ); 209 | name = ExampleTests; 210 | productName = ExampleTests; 211 | productReference = E72C353918FEC3A800D814BA /* ExampleTests.xctest */; 212 | productType = "com.apple.product-type.bundle.unit-test"; 213 | }; 214 | /* End PBXNativeTarget section */ 215 | 216 | /* Begin PBXProject section */ 217 | E72C351018FEC3A800D814BA /* Project object */ = { 218 | isa = PBXProject; 219 | attributes = { 220 | LastUpgradeCheck = 0800; 221 | ORGANIZATIONNAME = "Alan Skipp"; 222 | TargetAttributes = { 223 | E72C351718FEC3A800D814BA = { 224 | DevelopmentTeam = 9JZTGH7N9V; 225 | }; 226 | E72C353818FEC3A800D814BA = { 227 | TestTargetID = E72C351718FEC3A800D814BA; 228 | }; 229 | }; 230 | }; 231 | buildConfigurationList = E72C351318FEC3A800D814BA /* Build configuration list for PBXProject "Example" */; 232 | compatibilityVersion = "Xcode 6.3"; 233 | developmentRegion = English; 234 | hasScannedForEncodings = 0; 235 | knownRegions = ( 236 | en, 237 | Base, 238 | ); 239 | mainGroup = E72C350F18FEC3A800D814BA; 240 | productRefGroup = E72C351918FEC3A800D814BA /* Products */; 241 | projectDirPath = ""; 242 | projectRoot = ""; 243 | targets = ( 244 | E72C351718FEC3A800D814BA /* Example */, 245 | E72C353818FEC3A800D814BA /* ExampleTests */, 246 | ); 247 | }; 248 | /* End PBXProject section */ 249 | 250 | /* Begin PBXResourcesBuildPhase section */ 251 | E72C351618FEC3A800D814BA /* Resources */ = { 252 | isa = PBXResourcesBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | E72C353418FEC3A800D814BA /* Images.xcassets in Resources */, 256 | E72C352618FEC3A800D814BA /* InfoPlist.strings in Resources */, 257 | E748EDC21D9021D200AAFD13 /* Launch Screen.storyboard in Resources */, 258 | E72C352F18FEC3A800D814BA /* Main.storyboard in Resources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | E72C353718FEC3A800D814BA /* Resources */ = { 263 | isa = PBXResourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | E72C354518FEC3A800D814BA /* InfoPlist.strings in Resources */, 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | }; 270 | /* End PBXResourcesBuildPhase section */ 271 | 272 | /* Begin PBXSourcesBuildPhase section */ 273 | E72C351418FEC3A800D814BA /* Sources */ = { 274 | isa = PBXSourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | E72C353218FEC3A800D814BA /* ViewController.m in Sources */, 278 | E72C352C18FEC3A800D814BA /* AppDelegate.m in Sources */, 279 | E71B0486190FF28400394A2A /* ASProgressPopUpView.m in Sources */, 280 | E72C352818FEC3A800D814BA /* main.m in Sources */, 281 | E71B0485190FF28400394A2A /* ASPopUpView.m in Sources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | E72C353518FEC3A800D814BA /* Sources */ = { 286 | isa = PBXSourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | E72C354718FEC3A800D814BA /* ExampleTests.m in Sources */, 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | /* End PBXSourcesBuildPhase section */ 294 | 295 | /* Begin PBXTargetDependency section */ 296 | E72C353F18FEC3A800D814BA /* PBXTargetDependency */ = { 297 | isa = PBXTargetDependency; 298 | target = E72C351718FEC3A800D814BA /* Example */; 299 | targetProxy = E72C353E18FEC3A800D814BA /* PBXContainerItemProxy */; 300 | }; 301 | /* End PBXTargetDependency section */ 302 | 303 | /* Begin PBXVariantGroup section */ 304 | E72C352418FEC3A800D814BA /* InfoPlist.strings */ = { 305 | isa = PBXVariantGroup; 306 | children = ( 307 | E72C352518FEC3A800D814BA /* en */, 308 | ); 309 | name = InfoPlist.strings; 310 | sourceTree = ""; 311 | }; 312 | E72C352D18FEC3A800D814BA /* Main.storyboard */ = { 313 | isa = PBXVariantGroup; 314 | children = ( 315 | E72C352E18FEC3A800D814BA /* Base */, 316 | ); 317 | name = Main.storyboard; 318 | sourceTree = ""; 319 | }; 320 | E72C354318FEC3A800D814BA /* InfoPlist.strings */ = { 321 | isa = PBXVariantGroup; 322 | children = ( 323 | E72C354418FEC3A800D814BA /* en */, 324 | ); 325 | name = InfoPlist.strings; 326 | sourceTree = ""; 327 | }; 328 | /* End PBXVariantGroup section */ 329 | 330 | /* Begin XCBuildConfiguration section */ 331 | E72C354818FEC3A800D814BA /* Debug */ = { 332 | isa = XCBuildConfiguration; 333 | buildSettings = { 334 | ALWAYS_SEARCH_USER_PATHS = NO; 335 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 336 | CLANG_CXX_LIBRARY = "libc++"; 337 | CLANG_ENABLE_MODULES = YES; 338 | CLANG_ENABLE_OBJC_ARC = YES; 339 | CLANG_WARN_BOOL_CONVERSION = YES; 340 | CLANG_WARN_CONSTANT_CONVERSION = YES; 341 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 342 | CLANG_WARN_EMPTY_BODY = YES; 343 | CLANG_WARN_ENUM_CONVERSION = YES; 344 | CLANG_WARN_INFINITE_RECURSION = YES; 345 | CLANG_WARN_INT_CONVERSION = YES; 346 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 347 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 348 | CLANG_WARN_UNREACHABLE_CODE = YES; 349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 350 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 351 | COPY_PHASE_STRIP = NO; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | ENABLE_TESTABILITY = YES; 354 | GCC_C_LANGUAGE_STANDARD = gnu99; 355 | GCC_DYNAMIC_NO_PIC = NO; 356 | GCC_NO_COMMON_BLOCKS = YES; 357 | GCC_OPTIMIZATION_LEVEL = 0; 358 | GCC_PREPROCESSOR_DEFINITIONS = ( 359 | "DEBUG=1", 360 | "$(inherited)", 361 | ); 362 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 370 | ONLY_ACTIVE_ARCH = YES; 371 | SDKROOT = iphoneos; 372 | }; 373 | name = Debug; 374 | }; 375 | E72C354918FEC3A800D814BA /* Release */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | ALWAYS_SEARCH_USER_PATHS = NO; 379 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 380 | CLANG_CXX_LIBRARY = "libc++"; 381 | CLANG_ENABLE_MODULES = YES; 382 | CLANG_ENABLE_OBJC_ARC = YES; 383 | CLANG_WARN_BOOL_CONVERSION = YES; 384 | CLANG_WARN_CONSTANT_CONVERSION = YES; 385 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 386 | CLANG_WARN_EMPTY_BODY = YES; 387 | CLANG_WARN_ENUM_CONVERSION = YES; 388 | CLANG_WARN_INFINITE_RECURSION = YES; 389 | CLANG_WARN_INT_CONVERSION = YES; 390 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 391 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 392 | CLANG_WARN_UNREACHABLE_CODE = YES; 393 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 394 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 395 | COPY_PHASE_STRIP = YES; 396 | ENABLE_NS_ASSERTIONS = NO; 397 | ENABLE_STRICT_OBJC_MSGSEND = YES; 398 | GCC_C_LANGUAGE_STANDARD = gnu99; 399 | GCC_NO_COMMON_BLOCKS = YES; 400 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 401 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 402 | GCC_WARN_UNDECLARED_SELECTOR = YES; 403 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 404 | GCC_WARN_UNUSED_FUNCTION = YES; 405 | GCC_WARN_UNUSED_VARIABLE = YES; 406 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 407 | SDKROOT = iphoneos; 408 | VALIDATE_PRODUCT = YES; 409 | }; 410 | name = Release; 411 | }; 412 | E72C354B18FEC3A800D814BA /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 416 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 417 | DEVELOPMENT_TEAM = 9JZTGH7N9V; 418 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 419 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 420 | INFOPLIST_FILE = "Example/Example-Info.plist"; 421 | PRODUCT_BUNDLE_IDENTIFIER = "alan-skipp.${PRODUCT_NAME:rfc1034identifier}"; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | WRAPPER_EXTENSION = app; 424 | }; 425 | name = Debug; 426 | }; 427 | E72C354C18FEC3A800D814BA /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 431 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 432 | DEVELOPMENT_TEAM = 9JZTGH7N9V; 433 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 434 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 435 | INFOPLIST_FILE = "Example/Example-Info.plist"; 436 | PRODUCT_BUNDLE_IDENTIFIER = "alan-skipp.${PRODUCT_NAME:rfc1034identifier}"; 437 | PRODUCT_NAME = "$(TARGET_NAME)"; 438 | WRAPPER_EXTENSION = app; 439 | }; 440 | name = Release; 441 | }; 442 | E72C354E18FEC3A800D814BA /* Debug */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 446 | FRAMEWORK_SEARCH_PATHS = ( 447 | "$(SDKROOT)/Developer/Library/Frameworks", 448 | "$(inherited)", 449 | "$(DEVELOPER_FRAMEWORKS_DIR)", 450 | ); 451 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 452 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 453 | GCC_PREPROCESSOR_DEFINITIONS = ( 454 | "DEBUG=1", 455 | "$(inherited)", 456 | ); 457 | INFOPLIST_FILE = "ExampleTests/ExampleTests-Info.plist"; 458 | PRODUCT_BUNDLE_IDENTIFIER = "alan-skipp.${PRODUCT_NAME:rfc1034identifier}"; 459 | PRODUCT_NAME = "$(TARGET_NAME)"; 460 | TEST_HOST = "$(BUNDLE_LOADER)"; 461 | WRAPPER_EXTENSION = xctest; 462 | }; 463 | name = Debug; 464 | }; 465 | E72C354F18FEC3A800D814BA /* Release */ = { 466 | isa = XCBuildConfiguration; 467 | buildSettings = { 468 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 469 | FRAMEWORK_SEARCH_PATHS = ( 470 | "$(SDKROOT)/Developer/Library/Frameworks", 471 | "$(inherited)", 472 | "$(DEVELOPER_FRAMEWORKS_DIR)", 473 | ); 474 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 475 | GCC_PREFIX_HEADER = "Example/Example-Prefix.pch"; 476 | INFOPLIST_FILE = "ExampleTests/ExampleTests-Info.plist"; 477 | PRODUCT_BUNDLE_IDENTIFIER = "alan-skipp.${PRODUCT_NAME:rfc1034identifier}"; 478 | PRODUCT_NAME = "$(TARGET_NAME)"; 479 | TEST_HOST = "$(BUNDLE_LOADER)"; 480 | WRAPPER_EXTENSION = xctest; 481 | }; 482 | name = Release; 483 | }; 484 | /* End XCBuildConfiguration section */ 485 | 486 | /* Begin XCConfigurationList section */ 487 | E72C351318FEC3A800D814BA /* Build configuration list for PBXProject "Example" */ = { 488 | isa = XCConfigurationList; 489 | buildConfigurations = ( 490 | E72C354818FEC3A800D814BA /* Debug */, 491 | E72C354918FEC3A800D814BA /* Release */, 492 | ); 493 | defaultConfigurationIsVisible = 0; 494 | defaultConfigurationName = Release; 495 | }; 496 | E72C354A18FEC3A800D814BA /* Build configuration list for PBXNativeTarget "Example" */ = { 497 | isa = XCConfigurationList; 498 | buildConfigurations = ( 499 | E72C354B18FEC3A800D814BA /* Debug */, 500 | E72C354C18FEC3A800D814BA /* Release */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | E72C354D18FEC3A800D814BA /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { 506 | isa = XCConfigurationList; 507 | buildConfigurations = ( 508 | E72C354E18FEC3A800D814BA /* Debug */, 509 | E72C354F18FEC3A800D814BA /* Release */, 510 | ); 511 | defaultConfigurationIsVisible = 0; 512 | defaultConfigurationName = Release; 513 | }; 514 | /* End XCConfigurationList section */ 515 | }; 516 | rootObject = E72C351018FEC3A800D814BA /* Project object */; 517 | } 518 | --------------------------------------------------------------------------------