├── .github ├── FUNDING.yml └── workflows │ └── CI.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TOAlertViewController.podspec ├── TOAlertViewController ├── Models │ ├── TOAlertAction.h │ ├── TOAlertAction.m │ ├── TOAlertViewTransitioning.h │ └── TOAlertViewTransitioning.m ├── Supporting │ └── TOAlertViewConstants.h ├── TOAlertViewController.h ├── TOAlertViewController.m └── Views │ ├── TOAlertDimmingView.h │ ├── TOAlertDimmingView.m │ ├── TOAlertView.h │ └── TOAlertView.m ├── TOAlertViewControllerExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── TOAlertViewControllerExample.xcscheme │ ├── TOAlertViewControllerFramework.xcscheme │ └── TOAlertViewControllerTests.xcscheme ├── TOAlertViewControllerExample ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── catalina-wallpaper.imageset │ │ ├── Contents.json │ │ └── catalina-wallpaper.jpg ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── TORoundedButton │ ├── TORoundedButton.h │ └── TORoundedButton.m ├── ViewController.h ├── ViewController.m └── main.m ├── TOAlertViewControllerFramework └── Info.plist ├── TOAlertViewControllerTests ├── Info.plist └── TOAlertViewControllerTests.m ├── buildkite ├── pipeline.release.yml └── pipeline.test.yml └── screenshot.jpg /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: timoliver 2 | custom: https://tim.dev/paypal 3 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: macos-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Run a one-line script 13 | run: '(curl -s -L http://tim.dev/install_lib | bash -s arg1 arg2) && bundle exec fastlane test' 14 | env: 15 | TEST_SCHEME: "TOAlertViewControllerTests" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | .DS_Store 65 | /RevealServer.framework 66 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 1.1.0 Release Notes (yyyy-MM-dd) 2 | ============================================================= 3 | 4 | ## Added 5 | 6 | * Proper support for handling an arbitrary combination of button types. ([#2](https://github.com/TimOliver/TOAlertViewController/pull/2)) 7 | 8 | 1.0.0 Release Notes (2019-06-26) 9 | ============================================================= 10 | 11 | * Initial Release! 🎉 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tim Oliver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TOAlertViewController 2 | 3 | TORoundedButton 4 | 5 | 6 | [![Actions Status](https://github.com/TimOliver/TOAlertViewController/workflows/CI/badge.svg)](https://github.com/TimOliver/TOAlertViewController/actions) 7 | [![Version](https://img.shields.io/cocoapods/v/TOAlertViewController.svg?style=flat)](http://cocoadocs.org/docsets/TOAlertViewController) 8 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/TimOliver/TOAlertViewController/master/LICENSE) 9 | [![Platform](https://img.shields.io/cocoapods/p/TOAlertViewController.svg?style=flat)](http://cocoadocs.org/docsets/TOAlertViewController) 10 | [![PayPal](https://img.shields.io/badge/paypal-donate-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=M4RKULAVKV7K8) 11 | [![Twitch](https://img.shields.io/badge/twitch-timXD-6441a5.svg)](http://twitch.tv/timXD) 12 | 13 | `TOAlertViewController` is a custom re-implementation of `UIAlertController` with a much more modern visual design. It features a large bold title 14 | and rounded action buttons in line with the more modern design language of iOS that started appearing since 2017. 15 | 16 | # Features 17 | 18 | * A much more modern look and field than the native `UIAlertController` class (As of iOS 13). 19 | * Includes theming for default, and destructive action buttons. 20 | * Provides an optional dark mode appearance. 21 | * Smooth presentation and dismissal animations. 22 | * Uses `UIVisualEffectView` to produce a subtle 'depth-of-field' effect when presented. 23 | 24 | # Examples 25 | 26 | `TOAlertViewController` features a complete default configuration useful for most app instances, but can be easily modified beyond that. 27 | 28 | ```objc 29 | 30 | TOAlertViewController *alertController = [[TOAlertViewController alloc] 31 | initWithTitle:@"Are you sure?" message:@"This action may take some time to complete. Are you sure you wish to perform this action?"]; 32 | 33 | alertController.defaultAction = [TOAlertAction alertActionWithTitle:@"Yes" action:^{ NSLog(@"Default Button Tapped!"); }]; 34 | alertController.cancelAction = [TOAlertAction alertActionWithTitle:@"Cancel" action:^{ NSLog(@"Cancel Button Tapped!"); }]; 35 | 36 | [self presentViewController:alertController animated:YES completion:nil]; 37 | 38 | ``` 39 | 40 | # Requirements 41 | 42 | `TOAlertViewController` will work with iOS 11 and above. While written in Objective-C, it will easily import into Swift. 43 | It also requires the [`TORoundedButton`](https://github.com/TimOliver/TORoundedButton) library to be installed in your app. 44 | 45 | ## Manual Installation 46 | 47 | Copy the contents of the `TOAlertViewController` folder to your app project. 48 | Download a copy of [`TORoundedButton`](https://github.com/TimOliver/TORoundedButton) and also be sure to install that into your project as well. 49 | 50 | ## CocoaPods 51 | 52 | CocoaPods automatically manages importing `TORoundedButton` itself. 53 | 54 | ``` 55 | pod 'TOAlertViewController' 56 | ``` 57 | 58 | ## Carthage 59 | 60 | ``` 61 | github "TimOliver/TORoundedButton" 62 | github "TimOliver/TOAlertViewController" 63 | ``` 64 | 65 | # Credits 66 | 67 | `TOAlertViewController` was created by [Tim Oliver](http://twitter.com/TimOliverAU) as a component of [iComics](http://icomics.co). 68 | 69 | The iOS device mockup art was also created by Tim Oliver and is [available on Dribbble](https://dribbble.com/shots/1129682-iPod-touch-5G-PSD-Template). 70 | 71 | # License 72 | 73 | `TOAlertViewController` is available under the MIT license. Please see the [LICENSE](LICENSE) file for more information. ![analytics](https://ga-beacon.appspot.com/UA-5643664-16/TOAlertViewController/README.md?pixel) 74 | -------------------------------------------------------------------------------- /TOAlertViewController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'TOAlertViewController' 3 | s.version = '1.0.0' 4 | s.license = { :type => 'MIT', :file => 'LICENSE' } 5 | s.summary = 'A modern looking modal popup UI component for iOS and iPadOS' 6 | s.homepage = 'https://github.com/TimOliver/TOAlertViewController' 7 | s.author = 'Tim Oliver' 8 | s.source = { :git => 'https://github.com/TimOliver/TOAlertViewController.git', :tag => s.version } 9 | s.platform = :ios, '11.0' 10 | s.source_files = 'TOAlertViewController/**/*.{h,m}' 11 | s.requires_arc = true 12 | s.dependency 'TORoundedButton' 13 | end 14 | -------------------------------------------------------------------------------- /TOAlertViewController/Models/TOAlertAction.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertAction.h 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | @interface TOAlertAction : NSObject 28 | 29 | /** The title text that will displayed for this action */ 30 | @property (nonatomic, copy) NSString *title; 31 | 32 | /** The action that will be executed if the user taps this button */ 33 | @property (nonatomic, copy, nullable) void (^action)(void); 34 | 35 | /** 36 | Initializes a new alert action object with the provided title and action block. 37 | @param title The title that will be displayed in the button 38 | @param action The block that will be triggered when the user taps on that button 39 | */ 40 | - (instancetype)initWithTitle:(NSString *)title action:(nullable void (^)(void))action; 41 | 42 | /** 43 | Creates a new alert action object with the provided title and action block. 44 | @param title The title that will be displayed in the button 45 | @param action The block that will be triggered when the user taps on that button 46 | */ 47 | + (instancetype)alertActionWithTitle:(NSString *)title action:(nullable void (^)(void))action; 48 | 49 | @end 50 | 51 | NS_ASSUME_NONNULL_END 52 | -------------------------------------------------------------------------------- /TOAlertViewController/Models/TOAlertAction.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertAction.m 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOAlertAction.h" 24 | 25 | @implementation TOAlertAction 26 | 27 | - (instancetype)initWithTitle:(NSString *)title action:(void (^)(void))action 28 | { 29 | if (self = [super init]) { 30 | _title = title; 31 | _action = action; 32 | } 33 | 34 | return self; 35 | } 36 | 37 | + (instancetype)alertActionWithTitle:(NSString *)title action:(void (^)(void))action 38 | { 39 | return [[[self class] alloc] initWithTitle:title action:action]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /TOAlertViewController/Models/TOAlertViewTransitioning.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertViewTransitioning.h 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import 25 | 26 | @class TOAlertView; 27 | @class TOAlertDimmingView; 28 | 29 | NS_ASSUME_NONNULL_BEGIN 30 | 31 | /// :nodoc: 32 | @interface TOAlertViewTransitioning : NSObject 33 | - (instancetype)initWithAlertView:(TOAlertView *)alertView dimmingView:(TOAlertDimmingView *)dimmingView reverse:(BOOL)reverse; 34 | @end 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /TOAlertViewController/Models/TOAlertViewTransitioning.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertViewTransitioning.m 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | 24 | #import "TOAlertViewTransitioning.h" 25 | 26 | #import "TOAlertView.h" 27 | #import "TOAlertDimmingView.h" 28 | 29 | @interface TOAlertViewTransitioning () 30 | 31 | @property (nonatomic, assign) BOOL isReverse; 32 | @property (nonatomic, weak) TOAlertView *alertView; 33 | @property (nonatomic, weak) TOAlertDimmingView *dimmingView; 34 | 35 | @end 36 | 37 | @implementation TOAlertViewTransitioning 38 | 39 | #pragma mark - Class Creation - 40 | 41 | - (instancetype)initWithAlertView:(TOAlertView *)alertView dimmingView:(TOAlertDimmingView *)dimmingView reverse:(BOOL)reverse 42 | { 43 | if (self = [super init]) { 44 | _isReverse = reverse; 45 | _alertView = alertView; 46 | _dimmingView = dimmingView; 47 | } 48 | 49 | return self; 50 | } 51 | 52 | #pragma mark - UIViewControllerAnimatedTransitioning Implementation - 53 | 54 | - (NSTimeInterval)transitionDuration:(nullable id )transitionContext 55 | { 56 | // Play the transition twice as fast when dismissing so we can give control back to the user ASAP 57 | return self.isReverse ? 0.25f : 0.5f; 58 | } 59 | 60 | - (void)animateTransition:(id )transitionContext 61 | { 62 | NSTimeInterval duration = [self transitionDuration:transitionContext]; 63 | 64 | // Get the target view controller 65 | UITransitionContextViewControllerKey key = _isReverse ? UITransitionContextFromViewControllerKey : UITransitionContextToViewControllerKey; 66 | UIViewController *controller = [transitionContext viewControllerForKey:key]; 67 | 68 | // Add it to the container 69 | [transitionContext.containerView addSubview:controller.view]; 70 | 71 | // Play the fade in animation for the background 72 | if (!self.isReverse) { 73 | [self.dimmingView playFadeInAnimationWithDuration:duration]; 74 | } 75 | else { 76 | [self.dimmingView playFadeOutAnimationWithDuration:duration]; 77 | } 78 | 79 | CGFloat zeroAlpha = 0.0f, fullAlpha = 1.0f; 80 | CGAffineTransform identity = CGAffineTransformIdentity; 81 | CGAffineTransform scaled = CGAffineTransformScale(CGAffineTransformIdentity, 0.85f, 0.85f); 82 | 83 | // Fade in the alert view 84 | if (!self.isReverse) { 85 | self.alertView.alpha = zeroAlpha; 86 | self.alertView.transform = scaled; 87 | } 88 | 89 | // Animate the alert view zooming in 90 | [UIView animateWithDuration:duration 91 | delay:0.0f 92 | usingSpringWithDamping:1.0f 93 | initialSpringVelocity:2.0f 94 | options:UIViewAnimationOptionAllowUserInteraction 95 | animations:^{ 96 | self.alertView.alpha = self.isReverse ? zeroAlpha : fullAlpha; 97 | self.alertView.transform = self.isReverse ? scaled : identity; 98 | } completion:^(BOOL finished) { 99 | [transitionContext completeTransition:finished]; 100 | }]; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /TOAlertViewController/Supporting/TOAlertViewConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertViewConstants.h 3 | // TOAlertViewControllerExample 4 | // 5 | // Created by Tim Oliver on 25/5/19. 6 | // Copyright © 2019 Tim Oliver. All rights reserved. 7 | // 8 | 9 | typedef NS_ENUM(NSInteger, TOAlertViewStyle) { 10 | TOAlertViewStyleDefault, 11 | TOAlertViewStyleDark 12 | }; 13 | -------------------------------------------------------------------------------- /TOAlertViewController/TOAlertViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertViewController.h 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | #import "TOAlertViewConstants.h" 26 | #import "TOAlertAction.h" 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | @interface TOAlertViewController : UIViewController 31 | 32 | /** The title text displayed along the top of the alert. */ 33 | @property(nullable, nonatomic, copy) NSString *title; 34 | 35 | /** A message displayed under the title, typically to advise the user on what choices they have. */ 36 | @property (nullable, nonatomic, copy) NSString *message; 37 | 38 | /** The maximum width this controller may expand to on larger screens. (Default is 375.0f) */ 39 | @property (nonatomic, assign) CGFloat maximumWidth; 40 | 41 | /** The corner radius amount for the corners of the alert view (Default is 30) */ 42 | @property (nonatomic, assign) CGFloat cornerRadius; 43 | 44 | /** The corner radius of the buttons (default is 15.0f) */ 45 | @property (nonatomic, assign) CGFloat buttonCornerRadius; 46 | 47 | /** The vertical spacing between the title and message labels (Default is 7) */ 48 | @property (nonatomic, assign) CGFloat verticalTextSpacing; 49 | 50 | /** The spacing between horizontally and vertically aligned buttons (Default is 8 both ways) */ 51 | @property (nonatomic, assign) CGSize buttonSpacing; 52 | 53 | /** The height of the buttons (Default is 50) */ 54 | @property (nonatomic, assign) CGFloat buttonHeight; 55 | 56 | /** The insets of the content from the edge of the alert view. (Default is {23, 25, 17, 25}) */ 57 | @property (nonatomic, assign) UIEdgeInsets contentInsets; 58 | 59 | /** The insets of the region containing the buttons (Default is {18, 17, 0, 17.}) */ 60 | @property (nonatomic, assign) UIEdgeInsets buttonInsets; 61 | 62 | /** The list of regular actions added to this controller */ 63 | @property (nonatomic, readonly) NSArray *actions; 64 | 65 | /** A default action, which will use the same tint color as the app. Pressing 'Return' on a keyboard will trigger this action. */ 66 | @property (nonatomic, strong) TOAlertAction *defaultAction; 67 | 68 | /** A cancel action, which by default will always close the dialog when tapped. Pressing Command-. on a keyboard will trigger it. */ 69 | @property (nonatomic, strong) TOAlertAction *cancelAction; 70 | 71 | /** A destructive action, colored red indicatinf this operation will perform something irreversible. */ 72 | @property (nonatomic, strong) TOAlertAction *destructiveAction; 73 | 74 | /** The visual style of the alert view; light or dark. Setting this will configure all views to default settings. */ 75 | @property (nonatomic, assign) TOAlertViewStyle style UI_APPEARANCE_SELECTOR; 76 | 77 | /** The color of the title text (Default is black in light mode, white in dark mode) */ 78 | @property (nonatomic, strong, null_resettable) UIColor *titleColor UI_APPEARANCE_SELECTOR; 79 | 80 | /** The color of the message text (Default is black in light mode, white in dark mode) */ 81 | @property (nonatomic, strong, null_resettable) UIColor *messageColor UI_APPEARANCE_SELECTOR; 82 | 83 | /** The background color of the default action button */ 84 | @property (nonatomic, strong, null_resettable) UIColor *actionButtonColor UI_APPEARANCE_SELECTOR; 85 | 86 | /** The color of the default action button text */ 87 | @property (nonatomic, strong, null_resettable) UIColor *actionTextColor UI_APPEARANCE_SELECTOR; 88 | 89 | /** The background color of the return action button */ 90 | @property (nonatomic, strong, null_resettable) UIColor *defaultActionButtonColor UI_APPEARANCE_SELECTOR; 91 | 92 | /** The color of the default return button text */ 93 | @property (nonatomic, strong, null_resettable) UIColor *defaultActionTextColor UI_APPEARANCE_SELECTOR; 94 | 95 | /** The background color of the destructive action button */ 96 | @property (nonatomic, strong, null_resettable) UIColor *destructiveActionButtonColor UI_APPEARANCE_SELECTOR; 97 | 98 | /** The color of the default destructive button text */ 99 | @property (nonatomic, strong, null_resettable) UIColor *destructiveActionTextColor UI_APPEARANCE_SELECTOR; 100 | 101 | /** 102 | Create a new instance of alert view with the supplied title and message. 103 | 104 | @param title The title text that will be displayed along the top 105 | @param message The message text displayed under the title 106 | @return A new instance of TOAlertView 107 | */ 108 | - (instancetype)initWithTitle:(NSString *)title message:(NSString *)message; 109 | 110 | /** Adds a new regular action to the new alert view controller */ 111 | - (void)addAction:(TOAlertAction *)action; 112 | 113 | /** Removes a specific action from the alert controller */ 114 | - (void)removeAction:(TOAlertAction *)action; 115 | 116 | /** Removes a regular action at a specific index */ 117 | - (void)removeActionAtIndex:(NSUInteger)index; 118 | 119 | /** Sets a default action that will be tinted to the app tint color. 120 | Pressing return on an attacked keyboard will also trigger it. 121 | */ 122 | - (void)setDefaultAction:(nullable TOAlertAction *)action; 123 | 124 | /** Sets a cancel action that by default will close the dialog. 125 | Pressing Command-. on an attached keyboard will also trigger it. 126 | */ 127 | - (void)setCancelAction:(nullable TOAlertAction *)action; 128 | 129 | /** Sets a red colored button denoting an irreversible operation will occur. 130 | */ 131 | - (void)setDestructiveAction:(nullable TOAlertAction *)action; 132 | 133 | @end 134 | 135 | NS_ASSUME_NONNULL_END 136 | 137 | //! Project version number for TOAlertViewControllerFramework. 138 | FOUNDATION_EXPORT double TOAlertViewControllerVersionNumber; 139 | 140 | //! Project version string for TOAlertViewControllerFramework. 141 | FOUNDATION_EXPORT const unsigned char TOAlertViewControllerVersionString[]; 142 | -------------------------------------------------------------------------------- /TOAlertViewController/TOAlertViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertViewController.m 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOAlertViewController.h" 24 | #import "TOAlertView.h" 25 | #import "TOAlertDimmingView.h" 26 | #import "TOAlertViewTransitioning.h" 27 | 28 | @interface TOAlertViewController () 29 | 30 | // State to track when we're dismissing so to disable any implicit layout 31 | @property (nonatomic, assign) BOOL isDismissing; 32 | 33 | // Managed views 34 | @property (nonatomic, strong) TOAlertDimmingView *dimmingView; 35 | @property (nonatomic, strong) TOAlertView *alertView; 36 | 37 | @end 38 | 39 | @implementation TOAlertViewController 40 | 41 | // Defining title in the header is only for documentation convenience. 42 | // The internal implementation will actually be used. 43 | @dynamic title; 44 | 45 | #pragma mark - View Controller Creation - 46 | 47 | - (instancetype)initWithTitle:(NSString *)title message:(NSString *)message 48 | { 49 | if (self = [super init]) { 50 | super.title = title; 51 | _message = [message copy]; 52 | 53 | [self commonInit]; 54 | } 55 | 56 | return self; 57 | } 58 | 59 | - (instancetype)init 60 | { 61 | if (self = [super init]) { 62 | [self commonInit]; 63 | } 64 | 65 | return self; 66 | } 67 | 68 | - (void)commonInit 69 | { 70 | self.modalPresentationStyle = UIModalPresentationOverFullScreen; 71 | } 72 | 73 | - (void)viewDidLoad { 74 | [super viewDidLoad]; 75 | 76 | // Set us as the manager for our own presentation controller 77 | self.transitioningDelegate = self; 78 | 79 | // Add the subiews 80 | [self.view addSubview:self.dimmingView]; 81 | [self.view addSubview:self.alertView]; 82 | 83 | // Set the block handler for all buttons to dismiss 84 | __weak typeof(self) weakSelf = self; 85 | self.alertView.buttonTappedHandler = ^(void (^buttonAction)(void)) { 86 | [weakSelf.presentingViewController dismissViewControllerAnimated:YES completion:buttonAction]; 87 | }; 88 | } 89 | 90 | #pragma mark - View Controller Configuration - 91 | 92 | - (UIStatusBarStyle)preferredStatusBarStyle 93 | { 94 | return UIStatusBarStyleLightContent; 95 | } 96 | 97 | #pragma mark - View Layout - 98 | 99 | - (void)viewDidLayoutSubviews 100 | { 101 | [super viewDidLayoutSubviews]; 102 | 103 | if (self.isDismissing) { return; } 104 | 105 | UIEdgeInsets layoutMargins = self.view.layoutMargins; 106 | CGSize contentSize = self.view.bounds.size; 107 | contentSize.width -= (layoutMargins.left + layoutMargins.right); 108 | contentSize.height -= (layoutMargins.top + layoutMargins.bottom); 109 | [self.alertView sizeToFitInBoundSize:contentSize]; 110 | self.alertView.center = self.view.center; 111 | } 112 | 113 | #pragma mark - Presentation Handling - 114 | 115 | - (nullable id )animationControllerForPresentedController:(UIViewController *)presented 116 | presentingController:(UIViewController *)presenting 117 | sourceController:(UIViewController *)source 118 | { 119 | return [[TOAlertViewTransitioning alloc] initWithAlertView:self.alertView dimmingView:self.dimmingView reverse:NO]; 120 | } 121 | 122 | - (nullable id )animationControllerForDismissedController:(UIViewController *)dismissed 123 | { 124 | self.isDismissing = YES; 125 | return [[TOAlertViewTransitioning alloc] initWithAlertView:self.alertView dimmingView:self.dimmingView reverse:YES]; 126 | } 127 | 128 | #pragma mark - Lazy View Accessors - 129 | 130 | - (TOAlertDimmingView *)dimmingView 131 | { 132 | if (_dimmingView) { return _dimmingView; } 133 | _dimmingView = [[TOAlertDimmingView alloc] initWithFrame:self.view.bounds]; 134 | return _dimmingView; 135 | } 136 | 137 | - (TOAlertView *)alertView 138 | { 139 | if (_alertView) { return _alertView; } 140 | _alertView = [[TOAlertView alloc] initWithTitle:self.title message:self.message]; 141 | return _alertView; 142 | } 143 | 144 | #pragma mark - Regular Accessor Management - 145 | 146 | - (void)addAction:(TOAlertAction *)action 147 | { 148 | [self.alertView addAction:action]; 149 | } 150 | 151 | - (void)removeAction:(TOAlertAction *)action 152 | { 153 | [self.alertView removeAction:action]; 154 | } 155 | 156 | - (void)removeActionAtIndex:(NSUInteger)index 157 | { 158 | [self.alertView removeActionAtIndex:index]; 159 | } 160 | 161 | #pragma mark - Layout Accessors - 162 | 163 | - (void)setMaximumWidth:(CGFloat)maximumWidth { self.alertView.maximumWidth = maximumWidth; } 164 | - (CGFloat)maximumWidth { return self.alertView.maximumWidth; } 165 | 166 | - (void)setCornerRadius:(CGFloat)cornerRadius { self.alertView.cornerRadius = cornerRadius; } 167 | - (CGFloat)cornerRadius { return self.alertView.cornerRadius; } 168 | 169 | - (void)setButtonCornerRadius:(CGFloat)buttonCornerRadius { self.alertView.buttonCornerRadius = buttonCornerRadius; } 170 | - (CGFloat)buttonCornerRadius { return self.alertView.buttonCornerRadius; } 171 | 172 | - (void)setButtonSpacing:(CGSize)buttonSpacing { self.alertView.buttonSpacing = buttonSpacing; } 173 | - (CGSize)buttonSpacing { return self.alertView.buttonSpacing; } 174 | 175 | - (void)setButtonHeight:(CGFloat)buttonHeight { self.alertView.buttonHeight = buttonHeight; } 176 | - (CGFloat)buttonHeight { return self.alertView.buttonHeight; } 177 | 178 | - (void)setContentInsets:(UIEdgeInsets)contentInsets { self.alertView.contentInsets = contentInsets; } 179 | - (UIEdgeInsets)contentInsets { return self.alertView.contentInsets; } 180 | 181 | - (void)setButtonInsets:(UIEdgeInsets)buttonInsets { self.alertView.buttonInsets = buttonInsets; } 182 | - (UIEdgeInsets)buttonInsets { return self.alertView.buttonInsets; } 183 | 184 | - (void)setVerticalTextSpacing:(CGFloat)verticalTextSpacing { self.alertView.verticalTextSpacing = verticalTextSpacing; } 185 | - (CGFloat)verticalTextSpacing { return self.alertView.verticalTextSpacing; } 186 | 187 | #pragma mark - Theme Accessors - 188 | 189 | // Global dialog style 190 | - (void)setStyle:(TOAlertViewStyle)style { self.alertView.style = style; } 191 | - (TOAlertViewStyle)style { return self.alertView.style; } 192 | 193 | // Title label color 194 | - (void)setTitleColor:(UIColor *)titleColor { self.alertView.titleColor = titleColor; } 195 | - (UIColor *)titleColor { return self.alertView.titleColor; } 196 | 197 | // Message label color 198 | - (void)setMessageColor:(UIColor *)messageColor {self.alertView.messageColor = messageColor; } 199 | - (UIColor *)messageColor { return self.alertView.messageColor; } 200 | 201 | // Color of default action button background 202 | - (void)setActionButtonColor:(UIColor *)actionButtonColor { self.alertView.actionButtonColor = actionButtonColor; } 203 | - (UIColor *)actionButtonColor { return self.alertView.actionButtonColor; } 204 | 205 | // Color of default action button text 206 | - (void)setActionTextColor:(UIColor *)actionTextColor { self.alertView.actionTextColor = actionTextColor; } 207 | - (UIColor *)actionTextColor { return self.alertView.actionTextColor; } 208 | 209 | // Color of the default action button background 210 | - (void)setDefaultActionButtonColor:(UIColor *)defaultActionButtonColor { self.alertView.defaultActionButtonColor = defaultActionButtonColor; } 211 | - (UIColor *)defaultActionButtonColor { return self.alertView.defaultActionButtonColor; } 212 | 213 | // Color of the default action button text 214 | - (void)setDefaultActionTextColor:(UIColor *)defaultActionTextColor { self.alertView.defaultActionTextColor = defaultActionTextColor; } 215 | - (UIColor *)defaultActionTextColor { return self.alertView.defaultActionTextColor; } 216 | 217 | // Color of the destruction action button background 218 | - (void)setDestructiveActionButtonColor:(UIColor *)destructiveActionButtonColor { self.alertView.destructiveActionButtonColor = destructiveActionButtonColor; } 219 | - (UIColor *)destructiveActionButtonColor { return self.alertView.destructiveActionButtonColor; } 220 | 221 | // Color of the destructive action button text 222 | - (void)setDestructiveActionTextColor:(UIColor *)destructiveActionTextColor { self.alertView.destructiveActionTextColor = destructiveActionTextColor; } 223 | - (UIColor *)destructiveActionTextColor { return self.alertView.destructiveActionTextColor; } 224 | 225 | #pragma mark - Action Accessors - 226 | 227 | // The default button action 228 | - (void)setDefaultAction:(TOAlertAction *)action { self.alertView.defaultAction = action; } 229 | - (TOAlertAction *)defaultAction { return self.alertView.defaultAction; } 230 | 231 | // The cancel button action 232 | - (void)setCancelAction:(TOAlertAction *)action {self.alertView.cancelAction = action; } 233 | - (TOAlertAction *)cancelAction { return self.alertView.cancelAction; } 234 | 235 | // The destructive button 236 | - (void)setDestructiveAction:(TOAlertAction *)action { self.alertView.destructiveAction = action; } 237 | - (TOAlertAction *)destructiveAction { return self.alertView.destructiveAction; } 238 | 239 | @end 240 | -------------------------------------------------------------------------------- /TOAlertViewController/Views/TOAlertDimmingView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertDimmingView.h 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | /// :nodoc: 28 | @interface TOAlertDimmingView : UIView 29 | 30 | - (void)playFadeInAnimationWithDuration:(NSTimeInterval)duration; 31 | - (void)playFadeOutAnimationWithDuration:(NSTimeInterval)duration; 32 | 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /TOAlertViewController/Views/TOAlertDimmingView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertDimmingView.m 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOAlertDimmingView.h" 24 | 25 | @interface TOAlertDimmingView() 26 | 27 | @property (nonatomic, strong) UIVisualEffectView *blurView; 28 | @property (nonatomic, strong) UIViewPropertyAnimator *animator; 29 | 30 | @end 31 | 32 | @implementation TOAlertDimmingView 33 | 34 | - (instancetype)initWithFrame:(CGRect)frame 35 | { 36 | if (self = [super initWithFrame:frame]) { 37 | [self commonInit]; 38 | } 39 | 40 | return self; 41 | } 42 | 43 | - (void)commonInit 44 | { 45 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 46 | self.backgroundColor = [UIColor clearColor]; 47 | 48 | self.blurView = [[UIVisualEffectView alloc] initWithEffect:nil]; 49 | self.blurView.frame = self.bounds; 50 | self.blurView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 51 | [self addSubview:self.blurView]; 52 | } 53 | 54 | - (void)playFadeInAnimationWithDuration:(NSTimeInterval)duration 55 | { 56 | // Animate the dimming view 57 | [UIView animateWithDuration:duration animations:^{ 58 | self.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.15f]; 59 | }]; 60 | 61 | // Reset if need be 62 | if (self.animator) { 63 | [self.animator stopAnimation:YES]; 64 | self.animator = nil; 65 | self.blurView.effect = nil; 66 | } 67 | 68 | // Animate the blur view 69 | self.animator = [[UIViewPropertyAnimator alloc] initWithDuration:(duration/0.15f)*0.5f curve:UIViewAnimationCurveEaseOut animations:^{ 70 | self.blurView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; 71 | }]; 72 | [self.animator startAnimation]; 73 | 74 | // Pause the animation after the duration 75 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration*0.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 76 | [self.animator pauseAnimation]; 77 | }); 78 | } 79 | 80 | - (void)playFadeOutAnimationWithDuration:(NSTimeInterval)duration 81 | { 82 | // Animate the dimming view 83 | [UIView animateWithDuration:duration animations:^{ 84 | self.backgroundColor = [UIColor clearColor]; 85 | }]; 86 | 87 | // Get the fraction of the animation, and flip it so we can reverse 88 | CGFloat fraction = 1.0f - self.animator.fractionComplete; 89 | [self.animator stopAnimation:YES]; 90 | self.animator = nil; 91 | 92 | // Flip the animation 93 | self.blurView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; 94 | self.animator = [[UIViewPropertyAnimator alloc] initWithDuration:duration curve:UIViewAnimationCurveLinear animations:^{ 95 | self.blurView.effect = nil; 96 | }]; 97 | 98 | self.animator.fractionComplete = fraction; 99 | [self.animator startAnimation]; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /TOAlertViewController/Views/TOAlertView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertView.h 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import "TOAlertViewConstants.h" 25 | 26 | @class TOAlertAction; 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | /// :nodoc: 31 | @interface TOAlertView : UIView 32 | 33 | @property (nonatomic, copy) NSString *title; 34 | @property (nonatomic, copy) NSString *message; 35 | 36 | @property (nonatomic, assign) CGFloat cornerRadius; 37 | @property (nonatomic, assign) CGFloat buttonCornerRadius; 38 | @property (nonatomic, assign) CGSize buttonSpacing; 39 | @property (nonatomic, assign) CGFloat buttonHeight; 40 | @property (nonatomic, assign) UIEdgeInsets buttonInsets; 41 | @property (nonatomic, assign) CGFloat maximumWidth; 42 | @property (nonatomic, assign) UIEdgeInsets contentInsets; 43 | @property (nonatomic, assign) CGFloat verticalTextSpacing; 44 | 45 | @property (nonatomic, assign) TOAlertViewStyle style; 46 | 47 | @property (nonatomic, strong, null_resettable) UIColor *titleColor; 48 | @property (nonatomic, strong, null_resettable) UIColor *messageColor; 49 | 50 | @property (nonatomic, strong, null_resettable) UIColor *actionButtonColor; 51 | @property (nonatomic, strong, null_resettable) UIColor *actionTextColor; 52 | 53 | @property (nonatomic, strong, null_resettable) UIColor *defaultActionButtonColor; 54 | @property (nonatomic, strong, null_resettable) UIColor *defaultActionTextColor; 55 | 56 | @property (nonatomic, strong, null_resettable) UIColor *destructiveActionButtonColor; 57 | @property (nonatomic, strong, null_resettable) UIColor *destructiveActionTextColor; 58 | 59 | @property (nonatomic, readonly) NSArray *actions; 60 | @property (nonatomic, strong, nullable) TOAlertAction *defaultAction; 61 | @property (nonatomic, strong, nullable) TOAlertAction *destructiveAction; 62 | @property (nonatomic, strong, nullable) TOAlertAction *cancelAction; 63 | 64 | @property (nonatomic, copy) void (^buttonTappedHandler)(void (^)(void)); 65 | 66 | - (instancetype)initWithTitle:(NSString *)title message:(NSString *)message; 67 | 68 | - (void)sizeToFitInBoundSize:(CGSize)size; 69 | 70 | - (void)addAction:(TOAlertAction *)action; 71 | - (void)removeAction:(TOAlertAction *)action; 72 | - (void)removeActionAtIndex:(NSUInteger)index; 73 | 74 | @end 75 | 76 | NS_ASSUME_NONNULL_END 77 | -------------------------------------------------------------------------------- /TOAlertViewController/Views/TOAlertView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertView.m 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOAlertView.h" 24 | #import "TORoundedButton.h" 25 | #import "TOAlertAction.h" 26 | #import "TOAlertViewConstants.h" 27 | 28 | // ------------------------------------------- 29 | 30 | @interface TOAlertView () 31 | 32 | @property (nonatomic, strong, readwrite) NSMutableArray *actions; 33 | 34 | // All of the components of the alert view 35 | @property (nonatomic, strong) UIView *backgroundView; 36 | @property (nonatomic, strong) UIScrollView *scrollView; 37 | @property (nonatomic, strong) UILabel *titleLabel; 38 | @property (nonatomic, strong) UILabel *messageLabel; 39 | 40 | // All of the button views we can display 41 | @property (nonatomic, strong) NSMutableArray *buttons; 42 | @property (nonatomic, strong) TORoundedButton *defaultButton; 43 | @property (nonatomic, strong) TORoundedButton *cancelButton; 44 | @property (nonatomic, strong) TORoundedButton *destructiveButton; 45 | 46 | // A dynamic list of the buttons to display, in the correct order 47 | @property (nonatomic, readonly) NSArray *displayButtons; 48 | 49 | // State Tracking 50 | @property (nonatomic, assign) BOOL isDirty; 51 | @property (nonatomic, readonly) BOOL isDarkMode; 52 | 53 | @end 54 | 55 | @implementation TOAlertView 56 | 57 | #pragma mark - Class Creation - 58 | 59 | - (instancetype)initWithTitle:(NSString *)title message:(NSString *)message 60 | { 61 | if (self = [super initWithFrame:CGRectZero]) { 62 | _title = [title copy]; 63 | _message = [message copy]; 64 | [self alertViewCommonInit]; 65 | } 66 | 67 | return self; 68 | } 69 | 70 | - (instancetype)initWithFrame:(CGRect)frame 71 | { 72 | if (self = [super initWithFrame:CGRectZero]) { 73 | [self alertViewCommonInit]; 74 | } 75 | 76 | return self; 77 | } 78 | 79 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 80 | { 81 | if (self = [super initWithCoder:aDecoder]) { 82 | [self alertViewCommonInit]; 83 | } 84 | 85 | return self; 86 | } 87 | 88 | - (void)alertViewCommonInit 89 | { 90 | _buttons = [NSMutableArray array]; 91 | _cornerRadius = 30.0f; 92 | _buttonCornerRadius = 15.0f; 93 | _buttonSpacing = (CGSize){12.0f, 15.0f}; 94 | _buttonHeight = 54.0f; 95 | _contentInsets = (UIEdgeInsets){23.0f, 25.0f, 17.0f, 25.0f}; 96 | _maximumWidth = 375.0f; 97 | _verticalTextSpacing = 7.0f; 98 | _buttonInsets = (UIEdgeInsets){18.0f, 17.0f, 0.0f, 17.0f}; 99 | 100 | [self configureColorsForTheme:_style]; 101 | [self setUpSubviews]; 102 | } 103 | 104 | - (void)setUpSubviews 105 | { 106 | // Make sure the container itself is clear 107 | [super setBackgroundColor:[UIColor clearColor]]; 108 | 109 | // Create the actual background view placed in the container view 110 | _backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; 111 | 112 | if (@available(iOS 13.0, *)) { 113 | #ifdef __IPHONE_13_0 114 | _backgroundView.layer.cornerCurve = kCACornerCurveContinuous; 115 | #endif 116 | } 117 | _backgroundView.layer.cornerRadius = _cornerRadius; 118 | _backgroundView.backgroundColor = [UIColor whiteColor]; 119 | _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 120 | _backgroundView.layer.shadowRadius = 35.0f; 121 | _backgroundView.layer.shadowOpacity = 0.15f; 122 | [self addSubview:_backgroundView]; 123 | 124 | // Create the title label, shown at the top of the container 125 | UIFontMetrics *titleMetrics = [UIFontMetrics metricsForTextStyle:UIFontTextStyleTitle1]; 126 | _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 127 | _titleLabel.backgroundColor = _backgroundView.backgroundColor; 128 | _titleLabel.font = [titleMetrics scaledFontForFont:[UIFont systemFontOfSize:29.0f weight:UIFontWeightBold]]; 129 | _titleLabel.textColor = [UIColor blackColor]; 130 | _titleLabel.textAlignment = NSTextAlignmentCenter; 131 | _titleLabel.adjustsFontForContentSizeCategory = YES; 132 | _titleLabel.text = _title; 133 | [self addSubview:_titleLabel]; 134 | 135 | // Create the message label show below the title 136 | _messageLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 137 | _messageLabel.textColor = [UIColor blackColor]; 138 | _messageLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; 139 | _messageLabel.adjustsFontForContentSizeCategory = YES; 140 | _messageLabel.textAlignment = NSTextAlignmentCenter; 141 | _messageLabel.numberOfLines = 0; 142 | _messageLabel.text = _message; 143 | _messageLabel.backgroundColor = _backgroundView.backgroundColor; 144 | [self addSubview:_messageLabel]; 145 | } 146 | 147 | - (TORoundedButton *)makeButtonWithAction:(TOAlertAction *)action 148 | textColor:(UIColor *)textColor 149 | backgroundColor:(UIColor *)backgroundColor 150 | boldText:(BOOL)boldText 151 | { 152 | UIFontWeight fontWeight = boldText ? UIFontWeightBold : UIFontWeightMedium; 153 | UIFontMetrics *buttonTitleMetrics = [UIFontMetrics metricsForTextStyle:UIFontTextStyleTitle3]; 154 | UIFont *buttonFont = [buttonTitleMetrics scaledFontForFont:[UIFont systemFontOfSize:19.0f weight:fontWeight]]; 155 | 156 | __weak typeof(self) weakSelf = self; 157 | TORoundedButton *button = [[TORoundedButton alloc] initWithText:action.title]; 158 | button.tintColor = backgroundColor; 159 | button.cornerRadius = _buttonCornerRadius; 160 | button.textColor = textColor; 161 | button.textFont = buttonFont; 162 | button.backgroundColor = [UIColor clearColor]; 163 | button.tappedHandler = ^{ [weakSelf buttonTappedWithAction:action.action]; }; 164 | return button; 165 | } 166 | 167 | - (void)configureColorsForTheme:(TOAlertViewStyle)style 168 | { 169 | BOOL isDarkMode = (style == TOAlertViewStyleDark); 170 | 171 | // Set text colors 172 | UIColor *defaultColor = isDarkMode ? [UIColor whiteColor] : [UIColor blackColor]; 173 | _titleColor = defaultColor; 174 | _messageColor = defaultColor; 175 | 176 | // Set background color of alert view 177 | UIColor *backgroundColor = isDarkMode ? [UIColor colorWithWhite:0.116 alpha:1.0f] : [UIColor whiteColor]; 178 | self.backgroundColor = backgroundColor; 179 | 180 | // Set background colors of all button types 181 | CGFloat white = isDarkMode ? 0.35f : 0.9f; 182 | _actionButtonColor = [UIColor colorWithWhite:white alpha:1.0f]; 183 | _defaultActionButtonColor = self.tintColor; 184 | _destructiveActionButtonColor = [UIColor redColor]; 185 | 186 | // Set text colors for all button types 187 | _actionTextColor = defaultColor; 188 | _defaultActionTextColor = [UIColor whiteColor]; 189 | _destructiveActionTextColor = [UIColor whiteColor]; 190 | 191 | // Mark as dirty so we can bulk update the button views 192 | self.isDirty = YES; 193 | [self setNeedsLayout]; 194 | } 195 | 196 | - (void)configureViewsForCurrentTheme 197 | { 198 | // Title label 199 | self.titleLabel.backgroundColor = self.backgroundColor; 200 | self.titleLabel.textColor = self.titleColor; 201 | 202 | // Message label 203 | self.messageLabel.backgroundColor = self.backgroundColor; 204 | self.messageLabel.textColor = self.messageColor; 205 | 206 | // Destructive button 207 | if (self.destructiveButton) { 208 | self.destructiveButton.textColor = self.destructiveActionTextColor; 209 | self.destructiveButton.tintColor = self.destructiveActionButtonColor; 210 | } 211 | 212 | // Default button 213 | if (self.defaultButton) { 214 | self.defaultButton.tintColor = self.tintColor; 215 | self.defaultButton.textColor = self.defaultActionTextColor; 216 | } 217 | 218 | // Cancel button 219 | if (self.cancelButton) { 220 | self.cancelButton.textColor = self.actionTextColor; 221 | self.cancelButton.tintColor = self.actionButtonColor; 222 | } 223 | 224 | // Other buttons 225 | for (TORoundedButton *button in self.buttons) { 226 | button.textColor = self.actionTextColor; 227 | button.tintColor = self.actionButtonColor; 228 | } 229 | } 230 | 231 | #pragma mark - Presentation Configuration - 232 | 233 | - (void)sizeToFitInBoundSize:(CGSize)size 234 | { 235 | CGRect frame = CGRectZero; 236 | 237 | // Width is either the maximum width, or the available size we have 238 | frame.size.width = MIN(_maximumWidth, size.width); 239 | 240 | // For sizing text, work out the usable width we have 241 | CGFloat contentWidth = frame.size.width - (_contentInsets.left + _contentInsets.right); 242 | CGSize contentSize = (CGSize){contentWidth, CGFLOAT_MAX}; 243 | 244 | // Work out the height we need to fit every element 245 | 246 | // Top and bottom insets 247 | frame.size.height += _contentInsets.top + _contentInsets.bottom; 248 | 249 | // Title label size 250 | frame.size.height += [self.titleLabel sizeThatFits:contentSize].height + _verticalTextSpacing; 251 | 252 | // Message label size 253 | frame.size.height += [self.messageLabel sizeThatFits:contentSize].height + _buttonInsets.top; 254 | 255 | // Work out the number of rows for buttons 256 | CGFloat buttonWidth = size.width - (_buttonInsets.left + _buttonInsets.right); 257 | NSInteger numberOfRows = [self numberOfButtonRowsForWidth:buttonWidth]; 258 | 259 | // Add button height 260 | frame.size.height += numberOfRows *_buttonHeight; 261 | 262 | // Add button padding 263 | frame.size.height += (numberOfRows - 1) * _buttonSpacing.height; 264 | 265 | self.frame = frame; 266 | } 267 | 268 | - (NSInteger)numberOfButtonRowsForWidth:(CGFloat)width 269 | { 270 | // Return none if absolutely no actions are set 271 | if (!self.defaultAction && 272 | !self.cancelAction && 273 | !self.destructiveAction && 274 | self.actions.count == 0) { return 0; } 275 | 276 | // With padding, the maximum size a button may be 277 | CGFloat maxWidth = floorf(width - (self.buttonSpacing.width * 0.5f)); 278 | 279 | // As long as the labels are small enough, line up the two bottom 280 | // ones side by side 281 | NSArray *buttons = self.displayButtons; 282 | NSInteger numberOfRows = self.displayButtons.count; 283 | 284 | // If only one button is there, it cannot be split 285 | if (numberOfRows <= 1) { return 1; } 286 | 287 | // Check if the final two buttons can be split and displayed side by side 288 | TORoundedButton *lastButton = buttons.lastObject; 289 | TORoundedButton *secondLastButton = [buttons objectAtIndex:numberOfRows-2]; 290 | if (lastButton.minimumWidth < maxWidth && 291 | secondLastButton.minimumWidth < maxWidth) 292 | { 293 | numberOfRows--; 294 | } 295 | 296 | return numberOfRows; 297 | } 298 | 299 | - (NSArray *)displayButtons 300 | { 301 | NSMutableArray *buttons = [NSMutableArray array]; 302 | 303 | // Destructive button always comes first, either on the left, or top 304 | if (self.destructiveButton) { [buttons addObject:self.destructiveButton]; } 305 | 306 | // Add all regular buttons 307 | [buttons addObjectsFromArray:self.buttons]; 308 | 309 | // Cancel comes after destructive, but before default 310 | if (self.cancelButton) { [buttons addObject:self.cancelButton]; } 311 | 312 | // Add default button (Should be right by default) 313 | if (self.defaultButton) { [buttons addObject:self.defaultButton]; } 314 | 315 | return buttons; 316 | } 317 | 318 | #pragma mark - Layout - 319 | 320 | - (void)layoutSubviews 321 | { 322 | [super layoutSubviews]; 323 | 324 | // If necessary, set the new color theme 325 | if (self.isDirty) { 326 | [self configureViewsForCurrentTheme]; 327 | self.isDirty = NO; 328 | } 329 | 330 | // Layout the background 331 | self.backgroundView.frame = self.bounds; 332 | 333 | // For sizing text, work out the usable width we have 334 | CGFloat contentWidth = self.bounds.size.width - (_contentInsets.left + _contentInsets.right); 335 | CGSize contentSize = (CGSize){contentWidth, CGFLOAT_MAX}; 336 | 337 | // Track the vertical layout height 338 | CGFloat y = 0.0f; 339 | 340 | // Lay out the title view 341 | CGRect frame = self.titleLabel.frame; 342 | frame.size = [self.titleLabel sizeThatFits:contentSize]; 343 | frame.size.width = contentWidth; 344 | frame.origin.x = _contentInsets.left; 345 | frame.origin.y = _contentInsets.top; 346 | self.titleLabel.frame = frame; 347 | 348 | y = CGRectGetMaxY(frame) + self.verticalTextSpacing; 349 | 350 | // Lay out the message label 351 | frame = self.messageLabel.frame; 352 | frame.size = [self.messageLabel sizeThatFits:contentSize]; 353 | frame.origin.x =_contentInsets.left; 354 | frame.size.width = contentWidth; 355 | frame.origin.y = y; 356 | self.messageLabel.frame = frame; 357 | 358 | y = CGRectGetMaxY(frame) + self.buttonInsets.top; 359 | 360 | // Add any regular buttons 361 | NSInteger i = 0; 362 | CGFloat buttonWidth = self.bounds.size.width - (_buttonInsets.left + _buttonInsets.right); 363 | CGFloat midWidth = floorf((buttonWidth - _buttonSpacing.width) * 0.5f); 364 | 365 | NSArray *displayButtons = self.displayButtons; 366 | for (TORoundedButton *button in displayButtons) { 367 | frame = CGRectZero; 368 | frame.size.width = buttonWidth; 369 | frame.size.height = _buttonHeight; 370 | frame.origin.x = _buttonInsets.left; 371 | frame.origin.y = y; 372 | 373 | // For the second last button, change its width to half if both support it 374 | if (i == displayButtons.count - 2) { 375 | if (button.minimumWidth < midWidth && displayButtons[i+1].minimumWidth < midWidth) { 376 | frame.size.width = midWidth; 377 | } 378 | } 379 | else if (i == displayButtons.count - 1) { 380 | if (button.minimumWidth < midWidth && displayButtons[i-1].minimumWidth < midWidth) { 381 | frame.origin.y = displayButtons[i-1].frame.origin.y; 382 | frame.size.width = midWidth; 383 | frame.origin.x = self.bounds.size.width - (_buttonInsets.left + midWidth); 384 | } 385 | } 386 | 387 | y += _buttonSpacing.height + _buttonHeight; 388 | 389 | button.frame = CGRectIntegral(frame); 390 | 391 | i++; 392 | } 393 | 394 | // Update the shadow path shape 395 | _backgroundView.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:_backgroundView.bounds cornerRadius:_cornerRadius].CGPath; 396 | } 397 | 398 | - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection 399 | { 400 | [super traitCollectionDidChange:previousTraitCollection]; 401 | [self setNeedsLayout]; 402 | } 403 | 404 | #pragma mark - Interaction - 405 | 406 | - (void)buttonTappedWithAction:(void (^)(void))action 407 | { 408 | if (self.buttonTappedHandler) { 409 | self.buttonTappedHandler(action); 410 | } 411 | } 412 | 413 | #pragma mark - Private Accessors - 414 | 415 | - (BOOL)isDarkMode 416 | { 417 | return (self.style == TOAlertViewStyleDark); 418 | } 419 | 420 | #pragma mark - Action Creation/Deletion - 421 | 422 | - (void)setDefaultAction:(TOAlertAction *)defaultAction 423 | { 424 | if (defaultAction == _defaultAction) { return; } 425 | _defaultAction = defaultAction; 426 | 427 | self.isDirty = YES; 428 | [self setNeedsLayout]; 429 | 430 | // If we set it to null, remove the button 431 | if (_defaultAction == nil) { 432 | [_defaultButton removeFromSuperview]; 433 | _defaultButton = nil; 434 | return; 435 | } 436 | 437 | _defaultButton = [self makeButtonWithAction:defaultAction 438 | textColor:self.defaultActionTextColor 439 | backgroundColor:self.tintColor 440 | boldText:YES]; 441 | [self addSubview:_defaultButton]; 442 | } 443 | 444 | - (void)setDestructiveAction:(TOAlertAction *)destructiveAction 445 | { 446 | if (destructiveAction == _destructiveAction) { return; } 447 | _destructiveAction = destructiveAction; 448 | 449 | self.isDirty = YES; 450 | [self setNeedsLayout]; 451 | 452 | // If we set it to null, remove the button 453 | if (_destructiveAction == nil) { 454 | [_destructiveButton removeFromSuperview]; 455 | _destructiveButton = nil; 456 | return; 457 | } 458 | 459 | _destructiveButton = [self makeButtonWithAction:destructiveAction 460 | textColor:self.destructiveActionTextColor 461 | backgroundColor:_destructiveActionButtonColor 462 | boldText:NO]; 463 | [self addSubview:_destructiveButton]; 464 | } 465 | 466 | - (void)setCancelAction:(TOAlertAction *)cancelAction 467 | { 468 | if (cancelAction == _cancelAction) { return; } 469 | _cancelAction = cancelAction; 470 | 471 | self.isDirty = YES; 472 | [self setNeedsLayout]; 473 | 474 | // If we set it to null, remove the button 475 | if (_cancelAction == nil) { 476 | [_cancelButton removeFromSuperview]; 477 | _cancelButton = nil; 478 | return; 479 | } 480 | 481 | _cancelButton = [self makeButtonWithAction:cancelAction 482 | textColor:self.actionTextColor 483 | backgroundColor:_actionButtonColor 484 | boldText:NO]; 485 | [self addSubview:_cancelButton]; 486 | } 487 | 488 | - (void)addAction:(TOAlertAction *)action 489 | { 490 | // Create data stores if needed 491 | if (!self.actions) { self.actions = [NSMutableArray array]; } 492 | if (!self.buttons) { self.buttons = [NSMutableArray array]; } 493 | 494 | NSMutableArray *actions = (NSMutableArray *)self.actions; 495 | 496 | // Add action to array 497 | [actions addObject:action]; 498 | 499 | // Create button for it 500 | TORoundedButton *button = [self makeButtonWithAction:action 501 | textColor:self.actionTextColor 502 | backgroundColor:self.actionButtonColor 503 | boldText:NO]; 504 | [self.buttons addObject:button]; 505 | [self addSubview:button]; 506 | } 507 | 508 | - (void)removeAction:(TOAlertAction *)action 509 | { 510 | NSUInteger index = [self.actions indexOfObject:action]; 511 | [self removeActionAtIndex:index]; 512 | } 513 | 514 | - (void)removeActionAtIndex:(NSUInteger)index 515 | { 516 | if (index == NSNotFound || index >= self.actions.count) { return; } 517 | 518 | TORoundedButton *button = self.buttons[index]; 519 | [button removeFromSuperview]; 520 | [self.buttons removeObjectAtIndex:index]; 521 | 522 | [(NSMutableArray *)self.actions removeObjectAtIndex:index]; 523 | } 524 | 525 | 526 | 527 | #pragma mark - Color/Theme Accessors - 528 | 529 | - (void)setBackgroundColor:(UIColor *)backgroundColor 530 | { 531 | self.backgroundView.backgroundColor = backgroundColor; 532 | } 533 | - (UIColor *)backgroundColor { return self.backgroundView.backgroundColor; } 534 | 535 | - (void)setStyle:(TOAlertViewStyle)style 536 | { 537 | _style = style; 538 | [self configureColorsForTheme:_style]; 539 | } 540 | 541 | - (void)setTitleColor:(nullable UIColor *)titleColor 542 | { 543 | if (!titleColor) { 544 | _titleColor = self.isDarkMode ? [UIColor whiteColor] : [UIColor blackColor]; 545 | return; 546 | } 547 | 548 | if (titleColor == _titleColor) { return; } 549 | _titleColor = titleColor; 550 | _isDirty = YES; 551 | [self setNeedsLayout]; 552 | } 553 | 554 | - (void)setMessageColor:(UIColor *)messageColor 555 | { 556 | if (!messageColor) { 557 | _messageColor = self.isDarkMode ? [UIColor whiteColor] : [UIColor blackColor]; 558 | return; 559 | } 560 | 561 | if (messageColor == _messageColor) { return; } 562 | _messageColor = messageColor; 563 | _isDirty = YES; 564 | [self setNeedsLayout]; 565 | } 566 | 567 | - (void)setDefaultActionButtonColor:(UIColor *)defaultActionButtonColor 568 | { 569 | if (!defaultActionButtonColor) { 570 | CGFloat white = self.isDarkMode ? 0.3f : 0.7f; 571 | _defaultActionButtonColor = [UIColor colorWithWhite:white alpha:1.0f]; 572 | return; 573 | } 574 | 575 | if (defaultActionButtonColor == _defaultActionButtonColor) { return; } 576 | _defaultActionButtonColor = defaultActionButtonColor; 577 | _isDirty = YES; 578 | [self setNeedsLayout]; 579 | } 580 | 581 | - (void)setDefaultActionTextColor:(UIColor *)defaultActionTextColor 582 | { 583 | if (!defaultActionTextColor) { 584 | _defaultActionTextColor = self.isDarkMode ? [UIColor whiteColor] : [UIColor blackColor]; 585 | return; 586 | } 587 | 588 | if (defaultActionTextColor == _defaultActionTextColor) { return; } 589 | _defaultActionTextColor = defaultActionTextColor; 590 | _isDirty = YES; 591 | [self setNeedsLayout]; 592 | } 593 | 594 | - (void)setActionButtonColor:(UIColor *)actionButtonColor 595 | { 596 | if (!actionButtonColor) { 597 | _actionButtonColor = self.tintColor; 598 | return; 599 | } 600 | 601 | if (actionButtonColor == _actionButtonColor) { return; } 602 | _actionButtonColor = actionButtonColor; 603 | _isDirty = YES; 604 | [self setNeedsLayout]; 605 | } 606 | 607 | - (void)setActionTextColor:(UIColor *)actionTextColor 608 | { 609 | if (!actionTextColor) { 610 | _actionTextColor = [UIColor whiteColor]; 611 | return; 612 | } 613 | 614 | if (actionTextColor == _actionTextColor) { return; } 615 | _actionTextColor = actionTextColor; 616 | _isDirty = YES; 617 | [self setNeedsLayout]; 618 | } 619 | 620 | - (void)setDestructiveActionButtonColor:(UIColor *)destructiveActionButtonColor 621 | { 622 | if (!destructiveActionButtonColor) { 623 | _destructiveActionButtonColor = [UIColor redColor]; 624 | return; 625 | } 626 | 627 | if (destructiveActionButtonColor == _destructiveActionButtonColor) { return; } 628 | _destructiveActionButtonColor = destructiveActionButtonColor; 629 | _isDirty = YES; 630 | [self setNeedsLayout]; 631 | } 632 | 633 | - (void)setDestructiveActionTextColor:(UIColor *)destructiveActionTextColor 634 | { 635 | if (!destructiveActionTextColor) { 636 | _destructiveActionTextColor = [UIColor whiteColor]; 637 | return; 638 | } 639 | 640 | if (destructiveActionTextColor == _destructiveActionTextColor) { return; } 641 | _destructiveActionTextColor = destructiveActionTextColor; 642 | _isDirty = YES; 643 | [self setNeedsLayout]; 644 | } 645 | 646 | @end 647 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 223309C822BE6D6500B437F3 /* TOAlertViewTransitioning.m in Sources */ = {isa = PBXBuildFile; fileRef = 223309C722BE6D6500B437F3 /* TOAlertViewTransitioning.m */; }; 11 | 227AB91A22970A0D009ECE33 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 227AB91922970A0D009ECE33 /* AppDelegate.m */; }; 12 | 227AB91D22970A0D009ECE33 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 227AB91C22970A0D009ECE33 /* ViewController.m */; }; 13 | 227AB92022970A0D009ECE33 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 227AB91E22970A0D009ECE33 /* Main.storyboard */; }; 14 | 227AB92222970A0F009ECE33 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 227AB92122970A0F009ECE33 /* Assets.xcassets */; }; 15 | 227AB92522970A0F009ECE33 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 227AB92322970A0F009ECE33 /* LaunchScreen.storyboard */; }; 16 | 227AB92822970A0F009ECE33 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 227AB92722970A0F009ECE33 /* main.m */; }; 17 | 227AB93F22970A51009ECE33 /* TOAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = 227AB93D22970A51009ECE33 /* TOAlertView.m */; }; 18 | 227AB94322970A6D009ECE33 /* TORoundedButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 227AB94122970A6D009ECE33 /* TORoundedButton.m */; }; 19 | 227AB94622984F93009ECE33 /* TOAlertAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 227AB94522984F93009ECE33 /* TOAlertAction.m */; }; 20 | 227AB9492298506A009ECE33 /* TOAlertViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 227AB9482298506A009ECE33 /* TOAlertViewController.m */; }; 21 | 22BBDC6A22C2669700783BEA /* TOAlertViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 227AB9482298506A009ECE33 /* TOAlertViewController.m */; }; 22 | 22BBDC6B22C2669700783BEA /* TOAlertAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 227AB94522984F93009ECE33 /* TOAlertAction.m */; }; 23 | 22BBDC6C22C2669700783BEA /* TOAlertViewTransitioning.m in Sources */ = {isa = PBXBuildFile; fileRef = 223309C722BE6D6500B437F3 /* TOAlertViewTransitioning.m */; }; 24 | 22BBDC6D22C2669700783BEA /* TOAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = 227AB93D22970A51009ECE33 /* TOAlertView.m */; }; 25 | 22BBDC6E22C2669700783BEA /* TOAlertDimmingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 22F09B6222A0290E001DFB9A /* TOAlertDimmingView.m */; }; 26 | 22BBDC6F22C266A900783BEA /* TOAlertViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 227AB9472298506A009ECE33 /* TOAlertViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27 | 22BBDC7022C266A900783BEA /* TOAlertAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 227AB94422984F93009ECE33 /* TOAlertAction.h */; settings = {ATTRIBUTES = (Public, ); }; }; 28 | 22BBDC7122C266C500783BEA /* TOAlertViewTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = 223309C622BE6D6500B437F3 /* TOAlertViewTransitioning.h */; }; 29 | 22BBDC7222C266C500783BEA /* TOAlertView.h in Headers */ = {isa = PBXBuildFile; fileRef = 227AB93E22970A51009ECE33 /* TOAlertView.h */; }; 30 | 22BBDC7322C266C500783BEA /* TOAlertDimmingView.h in Headers */ = {isa = PBXBuildFile; fileRef = 22F09B6122A0290E001DFB9A /* TOAlertDimmingView.h */; }; 31 | 22BBDC7422C266C500783BEA /* TOAlertViewConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 227AB94A229853F8009ECE33 /* TOAlertViewConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32 | 22BBDC9022C26B5A00783BEA /* TOAlertViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 22BBDC8F22C26B5A00783BEA /* TOAlertViewControllerTests.m */; }; 33 | 22F09B6322A0290E001DFB9A /* TOAlertDimmingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 22F09B6222A0290E001DFB9A /* TOAlertDimmingView.m */; }; 34 | /* End PBXBuildFile section */ 35 | 36 | /* Begin PBXContainerItemProxy section */ 37 | 22BBDC9222C26B5A00783BEA /* PBXContainerItemProxy */ = { 38 | isa = PBXContainerItemProxy; 39 | containerPortal = 227AB90D22970A0D009ECE33 /* Project object */; 40 | proxyType = 1; 41 | remoteGlobalIDString = 227AB91422970A0D009ECE33; 42 | remoteInfo = TOAlertViewControllerExample; 43 | }; 44 | /* End PBXContainerItemProxy section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | 223309C622BE6D6500B437F3 /* TOAlertViewTransitioning.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOAlertViewTransitioning.h; sourceTree = ""; }; 48 | 223309C722BE6D6500B437F3 /* TOAlertViewTransitioning.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOAlertViewTransitioning.m; sourceTree = ""; }; 49 | 227AB91522970A0D009ECE33 /* TOAlertViewControllerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TOAlertViewControllerExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 227AB91822970A0D009ECE33 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 51 | 227AB91922970A0D009ECE33 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 52 | 227AB91B22970A0D009ECE33 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 53 | 227AB91C22970A0D009ECE33 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 54 | 227AB91F22970A0D009ECE33 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 227AB92122970A0F009ECE33 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | 227AB92422970A0F009ECE33 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | 227AB92622970A0F009ECE33 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | 227AB92722970A0F009ECE33 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 59 | 227AB93D22970A51009ECE33 /* TOAlertView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TOAlertView.m; sourceTree = ""; }; 60 | 227AB93E22970A51009ECE33 /* TOAlertView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TOAlertView.h; sourceTree = ""; }; 61 | 227AB94122970A6D009ECE33 /* TORoundedButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TORoundedButton.m; sourceTree = ""; }; 62 | 227AB94222970A6D009ECE33 /* TORoundedButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TORoundedButton.h; sourceTree = ""; }; 63 | 227AB94422984F93009ECE33 /* TOAlertAction.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOAlertAction.h; sourceTree = ""; }; 64 | 227AB94522984F93009ECE33 /* TOAlertAction.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOAlertAction.m; sourceTree = ""; }; 65 | 227AB9472298506A009ECE33 /* TOAlertViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOAlertViewController.h; sourceTree = ""; }; 66 | 227AB9482298506A009ECE33 /* TOAlertViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOAlertViewController.m; sourceTree = ""; }; 67 | 227AB94A229853F8009ECE33 /* TOAlertViewConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOAlertViewConstants.h; sourceTree = ""; }; 68 | 22BBDC5D22C265CE00783BEA /* TOAlertViewController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TOAlertViewController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | 22BBDC6022C265CE00783BEA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 70 | 22BBDC8D22C26B5A00783BEA /* TOAlertViewControllerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TOAlertViewControllerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | 22BBDC8F22C26B5A00783BEA /* TOAlertViewControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOAlertViewControllerTests.m; sourceTree = ""; }; 72 | 22BBDC9122C26B5A00783BEA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | 22F09B6122A0290E001DFB9A /* TOAlertDimmingView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TOAlertDimmingView.h; sourceTree = ""; }; 74 | 22F09B6222A0290E001DFB9A /* TOAlertDimmingView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOAlertDimmingView.m; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 227AB91222970A0D009ECE33 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | 22BBDC5A22C265CE00783BEA /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | 22BBDC8A22C26B5A00783BEA /* Frameworks */ = { 93 | isa = PBXFrameworksBuildPhase; 94 | buildActionMask = 2147483647; 95 | files = ( 96 | ); 97 | runOnlyForDeploymentPostprocessing = 0; 98 | }; 99 | /* End PBXFrameworksBuildPhase section */ 100 | 101 | /* Begin PBXGroup section */ 102 | 227AB90C22970A0D009ECE33 = { 103 | isa = PBXGroup; 104 | children = ( 105 | 227AB93C22970A51009ECE33 /* TOAlertViewController */, 106 | 22BBDC5E22C265CE00783BEA /* TOAlertViewControllerFramework */, 107 | 227AB91722970A0D009ECE33 /* TOAlertViewControllerExample */, 108 | 22BBDC8E22C26B5A00783BEA /* TOAlertViewControllerTests */, 109 | 227AB91622970A0D009ECE33 /* Products */, 110 | ); 111 | sourceTree = ""; 112 | }; 113 | 227AB91622970A0D009ECE33 /* Products */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 227AB91522970A0D009ECE33 /* TOAlertViewControllerExample.app */, 117 | 22BBDC5D22C265CE00783BEA /* TOAlertViewController.framework */, 118 | 22BBDC8D22C26B5A00783BEA /* TOAlertViewControllerTests.xctest */, 119 | ); 120 | name = Products; 121 | sourceTree = ""; 122 | }; 123 | 227AB91722970A0D009ECE33 /* TOAlertViewControllerExample */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 227AB94022970A6D009ECE33 /* TORoundedButton */, 127 | 227AB91822970A0D009ECE33 /* AppDelegate.h */, 128 | 227AB91922970A0D009ECE33 /* AppDelegate.m */, 129 | 227AB91B22970A0D009ECE33 /* ViewController.h */, 130 | 227AB91C22970A0D009ECE33 /* ViewController.m */, 131 | 227AB91E22970A0D009ECE33 /* Main.storyboard */, 132 | 227AB92122970A0F009ECE33 /* Assets.xcassets */, 133 | 227AB92322970A0F009ECE33 /* LaunchScreen.storyboard */, 134 | 227AB92622970A0F009ECE33 /* Info.plist */, 135 | 227AB92722970A0F009ECE33 /* main.m */, 136 | ); 137 | path = TOAlertViewControllerExample; 138 | sourceTree = ""; 139 | }; 140 | 227AB93C22970A51009ECE33 /* TOAlertViewController */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 227AB9472298506A009ECE33 /* TOAlertViewController.h */, 144 | 227AB9482298506A009ECE33 /* TOAlertViewController.m */, 145 | 22F09B6422A02919001DFB9A /* Models */, 146 | 22F09B6522A0292B001DFB9A /* Views */, 147 | 22F09B6622A02939001DFB9A /* Supporting */, 148 | ); 149 | path = TOAlertViewController; 150 | sourceTree = ""; 151 | }; 152 | 227AB94022970A6D009ECE33 /* TORoundedButton */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 227AB94222970A6D009ECE33 /* TORoundedButton.h */, 156 | 227AB94122970A6D009ECE33 /* TORoundedButton.m */, 157 | ); 158 | path = TORoundedButton; 159 | sourceTree = ""; 160 | }; 161 | 22BBDC5E22C265CE00783BEA /* TOAlertViewControllerFramework */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 22BBDC6022C265CE00783BEA /* Info.plist */, 165 | ); 166 | path = TOAlertViewControllerFramework; 167 | sourceTree = ""; 168 | }; 169 | 22BBDC8E22C26B5A00783BEA /* TOAlertViewControllerTests */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 22BBDC8F22C26B5A00783BEA /* TOAlertViewControllerTests.m */, 173 | 22BBDC9122C26B5A00783BEA /* Info.plist */, 174 | ); 175 | path = TOAlertViewControllerTests; 176 | sourceTree = ""; 177 | }; 178 | 22F09B6422A02919001DFB9A /* Models */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 227AB94422984F93009ECE33 /* TOAlertAction.h */, 182 | 227AB94522984F93009ECE33 /* TOAlertAction.m */, 183 | 223309C622BE6D6500B437F3 /* TOAlertViewTransitioning.h */, 184 | 223309C722BE6D6500B437F3 /* TOAlertViewTransitioning.m */, 185 | ); 186 | path = Models; 187 | sourceTree = ""; 188 | }; 189 | 22F09B6522A0292B001DFB9A /* Views */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | 227AB93E22970A51009ECE33 /* TOAlertView.h */, 193 | 227AB93D22970A51009ECE33 /* TOAlertView.m */, 194 | 22F09B6122A0290E001DFB9A /* TOAlertDimmingView.h */, 195 | 22F09B6222A0290E001DFB9A /* TOAlertDimmingView.m */, 196 | ); 197 | path = Views; 198 | sourceTree = ""; 199 | }; 200 | 22F09B6622A02939001DFB9A /* Supporting */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | 227AB94A229853F8009ECE33 /* TOAlertViewConstants.h */, 204 | ); 205 | path = Supporting; 206 | sourceTree = ""; 207 | }; 208 | /* End PBXGroup section */ 209 | 210 | /* Begin PBXHeadersBuildPhase section */ 211 | 22BBDC5822C265CE00783BEA /* Headers */ = { 212 | isa = PBXHeadersBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 22BBDC6F22C266A900783BEA /* TOAlertViewController.h in Headers */, 216 | 22BBDC7022C266A900783BEA /* TOAlertAction.h in Headers */, 217 | 22BBDC7422C266C500783BEA /* TOAlertViewConstants.h in Headers */, 218 | 22BBDC7122C266C500783BEA /* TOAlertViewTransitioning.h in Headers */, 219 | 22BBDC7222C266C500783BEA /* TOAlertView.h in Headers */, 220 | 22BBDC7322C266C500783BEA /* TOAlertDimmingView.h in Headers */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | /* End PBXHeadersBuildPhase section */ 225 | 226 | /* Begin PBXNativeTarget section */ 227 | 227AB91422970A0D009ECE33 /* TOAlertViewControllerExample */ = { 228 | isa = PBXNativeTarget; 229 | buildConfigurationList = 227AB93622970A0F009ECE33 /* Build configuration list for PBXNativeTarget "TOAlertViewControllerExample" */; 230 | buildPhases = ( 231 | 227AB91122970A0D009ECE33 /* Sources */, 232 | 227AB91222970A0D009ECE33 /* Frameworks */, 233 | 225DA4B122B1DD7300958F84 /* Integrate Reveal */, 234 | 227AB91322970A0D009ECE33 /* Resources */, 235 | ); 236 | buildRules = ( 237 | ); 238 | dependencies = ( 239 | ); 240 | name = TOAlertViewControllerExample; 241 | productName = TOAlertViewControllerExample; 242 | productReference = 227AB91522970A0D009ECE33 /* TOAlertViewControllerExample.app */; 243 | productType = "com.apple.product-type.application"; 244 | }; 245 | 22BBDC5C22C265CE00783BEA /* TOAlertViewController */ = { 246 | isa = PBXNativeTarget; 247 | buildConfigurationList = 22BBDC6922C265CE00783BEA /* Build configuration list for PBXNativeTarget "TOAlertViewController" */; 248 | buildPhases = ( 249 | 22BBDC5822C265CE00783BEA /* Headers */, 250 | 22BBDC5922C265CE00783BEA /* Sources */, 251 | 22BBDC5A22C265CE00783BEA /* Frameworks */, 252 | 22BBDC5B22C265CE00783BEA /* Resources */, 253 | ); 254 | buildRules = ( 255 | ); 256 | dependencies = ( 257 | ); 258 | name = TOAlertViewController; 259 | productName = TOAlertViewControllerFramework; 260 | productReference = 22BBDC5D22C265CE00783BEA /* TOAlertViewController.framework */; 261 | productType = "com.apple.product-type.framework"; 262 | }; 263 | 22BBDC8C22C26B5A00783BEA /* TOAlertViewControllerTests */ = { 264 | isa = PBXNativeTarget; 265 | buildConfigurationList = 22BBDC9422C26B5A00783BEA /* Build configuration list for PBXNativeTarget "TOAlertViewControllerTests" */; 266 | buildPhases = ( 267 | 22BBDC8922C26B5A00783BEA /* Sources */, 268 | 22BBDC8A22C26B5A00783BEA /* Frameworks */, 269 | 22BBDC8B22C26B5A00783BEA /* Resources */, 270 | ); 271 | buildRules = ( 272 | ); 273 | dependencies = ( 274 | 22BBDC9322C26B5A00783BEA /* PBXTargetDependency */, 275 | ); 276 | name = TOAlertViewControllerTests; 277 | productName = TOAlertViewControllerTests; 278 | productReference = 22BBDC8D22C26B5A00783BEA /* TOAlertViewControllerTests.xctest */; 279 | productType = "com.apple.product-type.bundle.unit-test"; 280 | }; 281 | /* End PBXNativeTarget section */ 282 | 283 | /* Begin PBXProject section */ 284 | 227AB90D22970A0D009ECE33 /* Project object */ = { 285 | isa = PBXProject; 286 | attributes = { 287 | LastUpgradeCheck = 1020; 288 | ORGANIZATIONNAME = "Tim Oliver"; 289 | TargetAttributes = { 290 | 227AB91422970A0D009ECE33 = { 291 | CreatedOnToolsVersion = 10.2.1; 292 | }; 293 | 22BBDC5C22C265CE00783BEA = { 294 | CreatedOnToolsVersion = 11.0; 295 | }; 296 | 22BBDC8C22C26B5A00783BEA = { 297 | CreatedOnToolsVersion = 11.0; 298 | TestTargetID = 227AB91422970A0D009ECE33; 299 | }; 300 | }; 301 | }; 302 | buildConfigurationList = 227AB91022970A0D009ECE33 /* Build configuration list for PBXProject "TOAlertViewControllerExample" */; 303 | compatibilityVersion = "Xcode 9.3"; 304 | developmentRegion = en; 305 | hasScannedForEncodings = 0; 306 | knownRegions = ( 307 | en, 308 | Base, 309 | ); 310 | mainGroup = 227AB90C22970A0D009ECE33; 311 | productRefGroup = 227AB91622970A0D009ECE33 /* Products */; 312 | projectDirPath = ""; 313 | projectRoot = ""; 314 | targets = ( 315 | 227AB91422970A0D009ECE33 /* TOAlertViewControllerExample */, 316 | 22BBDC5C22C265CE00783BEA /* TOAlertViewController */, 317 | 22BBDC8C22C26B5A00783BEA /* TOAlertViewControllerTests */, 318 | ); 319 | }; 320 | /* End PBXProject section */ 321 | 322 | /* Begin PBXResourcesBuildPhase section */ 323 | 227AB91322970A0D009ECE33 /* Resources */ = { 324 | isa = PBXResourcesBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | 227AB92522970A0F009ECE33 /* LaunchScreen.storyboard in Resources */, 328 | 227AB92222970A0F009ECE33 /* Assets.xcassets in Resources */, 329 | 227AB92022970A0D009ECE33 /* Main.storyboard in Resources */, 330 | ); 331 | runOnlyForDeploymentPostprocessing = 0; 332 | }; 333 | 22BBDC5B22C265CE00783BEA /* Resources */ = { 334 | isa = PBXResourcesBuildPhase; 335 | buildActionMask = 2147483647; 336 | files = ( 337 | ); 338 | runOnlyForDeploymentPostprocessing = 0; 339 | }; 340 | 22BBDC8B22C26B5A00783BEA /* Resources */ = { 341 | isa = PBXResourcesBuildPhase; 342 | buildActionMask = 2147483647; 343 | files = ( 344 | ); 345 | runOnlyForDeploymentPostprocessing = 0; 346 | }; 347 | /* End PBXResourcesBuildPhase section */ 348 | 349 | /* Begin PBXShellScriptBuildPhase section */ 350 | 225DA4B122B1DD7300958F84 /* Integrate Reveal */ = { 351 | isa = PBXShellScriptBuildPhase; 352 | buildActionMask = 2147483647; 353 | files = ( 354 | ); 355 | inputFileListPaths = ( 356 | ); 357 | inputPaths = ( 358 | ); 359 | name = "Integrate Reveal"; 360 | outputFileListPaths = ( 361 | ); 362 | outputPaths = ( 363 | ); 364 | runOnlyForDeploymentPostprocessing = 0; 365 | shellPath = /bin/sh; 366 | shellScript = "export REVEAL_SERVER_FILENAME=\"RevealServer.framework\"\n\n# Update this path to point to the location of RevealServer.framework in your project.\nexport REVEAL_SERVER_PATH=\"${SRCROOT}/${REVEAL_SERVER_FILENAME}\"\n\n# If configuration is not Debug, skip this script.\n[ \"${CONFIGURATION}\" != \"Debug\" ] && exit 0\n\n# If RevealServer.framework exists at the specified path, run code signing script.\nif [ -d \"${REVEAL_SERVER_PATH}\" ]; then\n\"${REVEAL_SERVER_PATH}/Scripts/copy_and_codesign_revealserver.sh\"\nelse\necho \"Reveal Server not loaded: RevealServer.framework could not be found.\"\nfi\n"; 367 | }; 368 | /* End PBXShellScriptBuildPhase section */ 369 | 370 | /* Begin PBXSourcesBuildPhase section */ 371 | 227AB91122970A0D009ECE33 /* Sources */ = { 372 | isa = PBXSourcesBuildPhase; 373 | buildActionMask = 2147483647; 374 | files = ( 375 | 227AB91D22970A0D009ECE33 /* ViewController.m in Sources */, 376 | 227AB92822970A0F009ECE33 /* main.m in Sources */, 377 | 22F09B6322A0290E001DFB9A /* TOAlertDimmingView.m in Sources */, 378 | 227AB94322970A6D009ECE33 /* TORoundedButton.m in Sources */, 379 | 223309C822BE6D6500B437F3 /* TOAlertViewTransitioning.m in Sources */, 380 | 227AB93F22970A51009ECE33 /* TOAlertView.m in Sources */, 381 | 227AB91A22970A0D009ECE33 /* AppDelegate.m in Sources */, 382 | 227AB94622984F93009ECE33 /* TOAlertAction.m in Sources */, 383 | 227AB9492298506A009ECE33 /* TOAlertViewController.m in Sources */, 384 | ); 385 | runOnlyForDeploymentPostprocessing = 0; 386 | }; 387 | 22BBDC5922C265CE00783BEA /* Sources */ = { 388 | isa = PBXSourcesBuildPhase; 389 | buildActionMask = 2147483647; 390 | files = ( 391 | 22BBDC6A22C2669700783BEA /* TOAlertViewController.m in Sources */, 392 | 22BBDC6B22C2669700783BEA /* TOAlertAction.m in Sources */, 393 | 22BBDC6C22C2669700783BEA /* TOAlertViewTransitioning.m in Sources */, 394 | 22BBDC6D22C2669700783BEA /* TOAlertView.m in Sources */, 395 | 22BBDC6E22C2669700783BEA /* TOAlertDimmingView.m in Sources */, 396 | ); 397 | runOnlyForDeploymentPostprocessing = 0; 398 | }; 399 | 22BBDC8922C26B5A00783BEA /* Sources */ = { 400 | isa = PBXSourcesBuildPhase; 401 | buildActionMask = 2147483647; 402 | files = ( 403 | 22BBDC9022C26B5A00783BEA /* TOAlertViewControllerTests.m in Sources */, 404 | ); 405 | runOnlyForDeploymentPostprocessing = 0; 406 | }; 407 | /* End PBXSourcesBuildPhase section */ 408 | 409 | /* Begin PBXTargetDependency section */ 410 | 22BBDC9322C26B5A00783BEA /* PBXTargetDependency */ = { 411 | isa = PBXTargetDependency; 412 | target = 227AB91422970A0D009ECE33 /* TOAlertViewControllerExample */; 413 | targetProxy = 22BBDC9222C26B5A00783BEA /* PBXContainerItemProxy */; 414 | }; 415 | /* End PBXTargetDependency section */ 416 | 417 | /* Begin PBXVariantGroup section */ 418 | 227AB91E22970A0D009ECE33 /* Main.storyboard */ = { 419 | isa = PBXVariantGroup; 420 | children = ( 421 | 227AB91F22970A0D009ECE33 /* Base */, 422 | ); 423 | name = Main.storyboard; 424 | sourceTree = ""; 425 | }; 426 | 227AB92322970A0F009ECE33 /* LaunchScreen.storyboard */ = { 427 | isa = PBXVariantGroup; 428 | children = ( 429 | 227AB92422970A0F009ECE33 /* Base */, 430 | ); 431 | name = LaunchScreen.storyboard; 432 | sourceTree = ""; 433 | }; 434 | /* End PBXVariantGroup section */ 435 | 436 | /* Begin XCBuildConfiguration section */ 437 | 227AB93422970A0F009ECE33 /* Debug */ = { 438 | isa = XCBuildConfiguration; 439 | buildSettings = { 440 | ALWAYS_SEARCH_USER_PATHS = NO; 441 | CLANG_ANALYZER_NONNULL = YES; 442 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 443 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 444 | CLANG_CXX_LIBRARY = "libc++"; 445 | CLANG_ENABLE_MODULES = YES; 446 | CLANG_ENABLE_OBJC_ARC = YES; 447 | CLANG_ENABLE_OBJC_WEAK = YES; 448 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 449 | CLANG_WARN_BOOL_CONVERSION = YES; 450 | CLANG_WARN_COMMA = YES; 451 | CLANG_WARN_CONSTANT_CONVERSION = YES; 452 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 453 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 454 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 455 | CLANG_WARN_EMPTY_BODY = YES; 456 | CLANG_WARN_ENUM_CONVERSION = YES; 457 | CLANG_WARN_INFINITE_RECURSION = YES; 458 | CLANG_WARN_INT_CONVERSION = YES; 459 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 460 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 461 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 462 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 463 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 464 | CLANG_WARN_STRICT_PROTOTYPES = YES; 465 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 466 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 467 | CLANG_WARN_UNREACHABLE_CODE = YES; 468 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 469 | CODE_SIGN_IDENTITY = "iPhone Developer"; 470 | COPY_PHASE_STRIP = NO; 471 | DEBUG_INFORMATION_FORMAT = dwarf; 472 | ENABLE_STRICT_OBJC_MSGSEND = YES; 473 | ENABLE_TESTABILITY = YES; 474 | GCC_C_LANGUAGE_STANDARD = gnu11; 475 | GCC_DYNAMIC_NO_PIC = NO; 476 | GCC_NO_COMMON_BLOCKS = YES; 477 | GCC_OPTIMIZATION_LEVEL = 0; 478 | GCC_PREPROCESSOR_DEFINITIONS = ( 479 | "DEBUG=1", 480 | "$(inherited)", 481 | ); 482 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 483 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 484 | GCC_WARN_UNDECLARED_SELECTOR = YES; 485 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 486 | GCC_WARN_UNUSED_FUNCTION = YES; 487 | GCC_WARN_UNUSED_VARIABLE = YES; 488 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 489 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 490 | MTL_FAST_MATH = YES; 491 | ONLY_ACTIVE_ARCH = YES; 492 | SDKROOT = iphoneos; 493 | }; 494 | name = Debug; 495 | }; 496 | 227AB93522970A0F009ECE33 /* Release */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | ALWAYS_SEARCH_USER_PATHS = NO; 500 | CLANG_ANALYZER_NONNULL = YES; 501 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 502 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 503 | CLANG_CXX_LIBRARY = "libc++"; 504 | CLANG_ENABLE_MODULES = YES; 505 | CLANG_ENABLE_OBJC_ARC = YES; 506 | CLANG_ENABLE_OBJC_WEAK = YES; 507 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 508 | CLANG_WARN_BOOL_CONVERSION = YES; 509 | CLANG_WARN_COMMA = YES; 510 | CLANG_WARN_CONSTANT_CONVERSION = YES; 511 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 512 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 513 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 514 | CLANG_WARN_EMPTY_BODY = YES; 515 | CLANG_WARN_ENUM_CONVERSION = YES; 516 | CLANG_WARN_INFINITE_RECURSION = YES; 517 | CLANG_WARN_INT_CONVERSION = YES; 518 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 519 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 520 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 521 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 522 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 523 | CLANG_WARN_STRICT_PROTOTYPES = YES; 524 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 525 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 526 | CLANG_WARN_UNREACHABLE_CODE = YES; 527 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 528 | CODE_SIGN_IDENTITY = "iPhone Developer"; 529 | COPY_PHASE_STRIP = NO; 530 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 531 | ENABLE_NS_ASSERTIONS = NO; 532 | ENABLE_STRICT_OBJC_MSGSEND = YES; 533 | GCC_C_LANGUAGE_STANDARD = gnu11; 534 | GCC_NO_COMMON_BLOCKS = YES; 535 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 536 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 537 | GCC_WARN_UNDECLARED_SELECTOR = YES; 538 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 539 | GCC_WARN_UNUSED_FUNCTION = YES; 540 | GCC_WARN_UNUSED_VARIABLE = YES; 541 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 542 | MTL_ENABLE_DEBUG_INFO = NO; 543 | MTL_FAST_MATH = YES; 544 | SDKROOT = iphoneos; 545 | VALIDATE_PRODUCT = YES; 546 | }; 547 | name = Release; 548 | }; 549 | 227AB93722970A0F009ECE33 /* Debug */ = { 550 | isa = XCBuildConfiguration; 551 | buildSettings = { 552 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 553 | CODE_SIGN_STYLE = Automatic; 554 | DEVELOPMENT_TEAM = 6LF3GMKZAB; 555 | FRAMEWORK_SEARCH_PATHS = ( 556 | "$(inherited)", 557 | "$(PROJECT_DIR)/TOAlertViewControllerExample", 558 | "$(inherited)", 559 | "$(SRCROOT)/**", 560 | ); 561 | INFOPLIST_FILE = TOAlertViewControllerExample/Info.plist; 562 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 563 | LD_RUNPATH_SEARCH_PATHS = ( 564 | "$(inherited)", 565 | "@executable_path/Frameworks", 566 | ); 567 | PRODUCT_BUNDLE_IDENTIFIER = dev.tim.TOAlertViewControllerExample; 568 | PRODUCT_NAME = "$(TARGET_NAME)"; 569 | TARGETED_DEVICE_FAMILY = "1,2"; 570 | }; 571 | name = Debug; 572 | }; 573 | 227AB93822970A0F009ECE33 /* Release */ = { 574 | isa = XCBuildConfiguration; 575 | buildSettings = { 576 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 577 | CODE_SIGN_STYLE = Automatic; 578 | DEVELOPMENT_TEAM = 6LF3GMKZAB; 579 | FRAMEWORK_SEARCH_PATHS = ( 580 | "$(inherited)", 581 | "$(PROJECT_DIR)/TOAlertViewControllerExample", 582 | "$(inherited)", 583 | "$(SRCROOT)/**", 584 | ); 585 | INFOPLIST_FILE = TOAlertViewControllerExample/Info.plist; 586 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 587 | LD_RUNPATH_SEARCH_PATHS = ( 588 | "$(inherited)", 589 | "@executable_path/Frameworks", 590 | ); 591 | PRODUCT_BUNDLE_IDENTIFIER = dev.tim.TOAlertViewControllerExample; 592 | PRODUCT_NAME = "$(TARGET_NAME)"; 593 | TARGETED_DEVICE_FAMILY = "1,2"; 594 | }; 595 | name = Release; 596 | }; 597 | 22BBDC6722C265CE00783BEA /* Debug */ = { 598 | isa = XCBuildConfiguration; 599 | buildSettings = { 600 | CODE_SIGN_STYLE = Automatic; 601 | CURRENT_PROJECT_VERSION = 1; 602 | DEFINES_MODULE = YES; 603 | DEVELOPMENT_TEAM = 6LF3GMKZAB; 604 | DYLIB_COMPATIBILITY_VERSION = 1; 605 | DYLIB_CURRENT_VERSION = 1; 606 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 607 | INFOPLIST_FILE = TOAlertViewControllerFramework/Info.plist; 608 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 609 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 610 | LD_RUNPATH_SEARCH_PATHS = ( 611 | "$(inherited)", 612 | "@executable_path/Frameworks", 613 | "@loader_path/Frameworks", 614 | ); 615 | MACH_O_TYPE = staticlib; 616 | PRODUCT_BUNDLE_IDENTIFIER = dev.tim.TOAlertViewController; 617 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 618 | SKIP_INSTALL = YES; 619 | TARGETED_DEVICE_FAMILY = "1,2"; 620 | VERSIONING_SYSTEM = "apple-generic"; 621 | VERSION_INFO_PREFIX = ""; 622 | }; 623 | name = Debug; 624 | }; 625 | 22BBDC6822C265CE00783BEA /* Release */ = { 626 | isa = XCBuildConfiguration; 627 | buildSettings = { 628 | CODE_SIGN_STYLE = Automatic; 629 | CURRENT_PROJECT_VERSION = 1; 630 | DEFINES_MODULE = YES; 631 | DEVELOPMENT_TEAM = 6LF3GMKZAB; 632 | DYLIB_COMPATIBILITY_VERSION = 1; 633 | DYLIB_CURRENT_VERSION = 1; 634 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 635 | INFOPLIST_FILE = TOAlertViewControllerFramework/Info.plist; 636 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 637 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 638 | LD_RUNPATH_SEARCH_PATHS = ( 639 | "$(inherited)", 640 | "@executable_path/Frameworks", 641 | "@loader_path/Frameworks", 642 | ); 643 | MACH_O_TYPE = staticlib; 644 | PRODUCT_BUNDLE_IDENTIFIER = dev.tim.TOAlertViewController; 645 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 646 | SKIP_INSTALL = YES; 647 | TARGETED_DEVICE_FAMILY = "1,2"; 648 | VERSIONING_SYSTEM = "apple-generic"; 649 | VERSION_INFO_PREFIX = ""; 650 | }; 651 | name = Release; 652 | }; 653 | 22BBDC9522C26B5A00783BEA /* Debug */ = { 654 | isa = XCBuildConfiguration; 655 | buildSettings = { 656 | BUNDLE_LOADER = "$(TEST_HOST)"; 657 | CODE_SIGN_STYLE = Automatic; 658 | DEVELOPMENT_TEAM = 6LF3GMKZAB; 659 | INFOPLIST_FILE = TOAlertViewControllerTests/Info.plist; 660 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 661 | LD_RUNPATH_SEARCH_PATHS = ( 662 | "$(inherited)", 663 | "@executable_path/Frameworks", 664 | "@loader_path/Frameworks", 665 | ); 666 | PRODUCT_BUNDLE_IDENTIFIER = dev.tim.TOAlertViewControllerTests; 667 | PRODUCT_NAME = "$(TARGET_NAME)"; 668 | TARGETED_DEVICE_FAMILY = "1,2"; 669 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TOAlertViewControllerExample.app/TOAlertViewControllerExample"; 670 | }; 671 | name = Debug; 672 | }; 673 | 22BBDC9622C26B5A00783BEA /* Release */ = { 674 | isa = XCBuildConfiguration; 675 | buildSettings = { 676 | BUNDLE_LOADER = "$(TEST_HOST)"; 677 | CODE_SIGN_STYLE = Automatic; 678 | DEVELOPMENT_TEAM = 6LF3GMKZAB; 679 | INFOPLIST_FILE = TOAlertViewControllerTests/Info.plist; 680 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 681 | LD_RUNPATH_SEARCH_PATHS = ( 682 | "$(inherited)", 683 | "@executable_path/Frameworks", 684 | "@loader_path/Frameworks", 685 | ); 686 | PRODUCT_BUNDLE_IDENTIFIER = dev.tim.TOAlertViewControllerTests; 687 | PRODUCT_NAME = "$(TARGET_NAME)"; 688 | TARGETED_DEVICE_FAMILY = "1,2"; 689 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TOAlertViewControllerExample.app/TOAlertViewControllerExample"; 690 | }; 691 | name = Release; 692 | }; 693 | /* End XCBuildConfiguration section */ 694 | 695 | /* Begin XCConfigurationList section */ 696 | 227AB91022970A0D009ECE33 /* Build configuration list for PBXProject "TOAlertViewControllerExample" */ = { 697 | isa = XCConfigurationList; 698 | buildConfigurations = ( 699 | 227AB93422970A0F009ECE33 /* Debug */, 700 | 227AB93522970A0F009ECE33 /* Release */, 701 | ); 702 | defaultConfigurationIsVisible = 0; 703 | defaultConfigurationName = Release; 704 | }; 705 | 227AB93622970A0F009ECE33 /* Build configuration list for PBXNativeTarget "TOAlertViewControllerExample" */ = { 706 | isa = XCConfigurationList; 707 | buildConfigurations = ( 708 | 227AB93722970A0F009ECE33 /* Debug */, 709 | 227AB93822970A0F009ECE33 /* Release */, 710 | ); 711 | defaultConfigurationIsVisible = 0; 712 | defaultConfigurationName = Release; 713 | }; 714 | 22BBDC6922C265CE00783BEA /* Build configuration list for PBXNativeTarget "TOAlertViewController" */ = { 715 | isa = XCConfigurationList; 716 | buildConfigurations = ( 717 | 22BBDC6722C265CE00783BEA /* Debug */, 718 | 22BBDC6822C265CE00783BEA /* Release */, 719 | ); 720 | defaultConfigurationIsVisible = 0; 721 | defaultConfigurationName = Release; 722 | }; 723 | 22BBDC9422C26B5A00783BEA /* Build configuration list for PBXNativeTarget "TOAlertViewControllerTests" */ = { 724 | isa = XCConfigurationList; 725 | buildConfigurations = ( 726 | 22BBDC9522C26B5A00783BEA /* Debug */, 727 | 22BBDC9622C26B5A00783BEA /* Release */, 728 | ); 729 | defaultConfigurationIsVisible = 0; 730 | defaultConfigurationName = Release; 731 | }; 732 | /* End XCConfigurationList section */ 733 | }; 734 | rootObject = 227AB90D22970A0D009ECE33 /* Project object */; 735 | } 736 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample.xcodeproj/xcshareddata/xcschemes/TOAlertViewControllerExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample.xcodeproj/xcshareddata/xcschemes/TOAlertViewControllerFramework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample.xcodeproj/xcshareddata/xcschemes/TOAlertViewControllerTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 67 | 69 | 75 | 76 | 77 | 78 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TOAlertViewControllerExample 4 | // 5 | // Created by Tim Oliver on 24/5/19. 6 | // Copyright © 2019 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TOAlertViewControllerExample 4 | // 5 | // Created by Tim Oliver on 24/5/19. 6 | // Copyright © 2019 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /TOAlertViewControllerExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TOAlertViewControllerExample/Assets.xcassets/catalina-wallpaper.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "catalina-wallpaper.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TOAlertViewControllerExample/Assets.xcassets/catalina-wallpaper.imageset/catalina-wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOAlertViewController/bfbc95699cb79297397c4884684d3e5276d29ab2/TOAlertViewControllerExample/Assets.xcassets/catalina-wallpaper.imageset/catalina-wallpaper.jpg -------------------------------------------------------------------------------- /TOAlertViewControllerExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample/TORoundedButton/TORoundedButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // TORoundedButton.h 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | FOUNDATION_EXPORT double TORoundedButtonVersionNumber; 26 | FOUNDATION_EXPORT const unsigned char TORoundedButtonVersionString[]; 27 | 28 | NS_ASSUME_NONNULL_BEGIN 29 | 30 | NS_SWIFT_NAME(RoundedButton) 31 | IB_DESIGNABLE @interface TORoundedButton : UIControl 32 | 33 | /** The text that is displayed in center of the button (Default is "Button") */ 34 | @property (nonatomic, copy) IBInspectable NSString *text; 35 | 36 | /** The attributed string used in the label of this button. See `UILabel.attributedText` documentation for full details (Default is nil) */ 37 | @property (nonatomic, copy, nullable) NSAttributedString *attributedText; 38 | 39 | /** The radius of the corners of this button (Default is 12.0f) */ 40 | @property (nonatomic, assign) IBInspectable CGFloat cornerRadius; 41 | 42 | /** The color of the text in this button (Default is white) */ 43 | @property (nonatomic, strong) IBInspectable UIColor *textColor; 44 | 45 | /** When tapped, the level of transparency that the text label animates to. (Defaults to off with 1.0f) */ 46 | @property (nonatomic, assign) IBInspectable CGFloat tappedTextAlpha; 47 | 48 | /** The font of the text in the button (Default is size UIFontTextStyleBody with bold) */ 49 | @property (nonatomic, strong) UIFont *textFont; 50 | 51 | /** Because IB cannot handle fonts, this can alternatively be used to set the font size. (Default is off with 0.0) */ 52 | @property (nonatomic, assign) IBInspectable CGFloat textPointSize; 53 | 54 | /** Taking the default button background color apply a brightness offset for the tapped color (Default is -0.1f. Set 0.0 for off) */ 55 | @property (nonatomic, assign) IBInspectable CGFloat tappedTintColorBrightnessOffset; 56 | 57 | /** If desired, explicity set the background color of the button when tapped (Default is nil). */ 58 | @property (nonatomic, strong, nullable) IBInspectable UIColor *tappedTintColor; 59 | 60 | /** When tapped, the scale by which the button shrinks during the animation (Default is 0.97f) */ 61 | @property (nonatomic, assign) IBInspectable CGFloat tappedButtonScale; 62 | 63 | /** The duration of the tapping cross-fade animation (Default is 0.4f) */ 64 | @property (nonatomic, assign) CGFloat tapAnimationDuration; 65 | 66 | /** Given the current size of the text label, the smallest horizontal width in which this button can scale. */ 67 | @property (nonatomic, readonly) CGFloat minimumWidth; 68 | 69 | /** A callback handler triggered each time the button is tapped. */ 70 | @property (nonatomic, copy) void (^tappedHandler)(void); 71 | 72 | /** Create a new instance with the supplied button text. The size will be 288 points wide, and 50 tall. */ 73 | - (instancetype)initWithText:(NSString *)text; 74 | 75 | @end 76 | 77 | NS_ASSUME_NONNULL_END 78 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample/TORoundedButton/TORoundedButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // TORoundedButton.m 3 | // 4 | // Copyright 2019 Timothy Oliver. All rights reserved. 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 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell 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 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR 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 LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TORoundedButton.h" 24 | 25 | @interface TORoundedButton () 26 | 27 | /** Hold on to a global state for whether we are tapped or not because the state can change before blocks complete */ 28 | @property (nonatomic, assign) BOOL isTapped; 29 | 30 | /** A container view that holds all of the content view and performs the clipping */ 31 | @property (nonatomic, strong) UIView *containerView; 32 | 33 | /** The title label displaying the text in the center of the button */ 34 | @property (nonatomic, strong) UILabel *titleLabel; 35 | 36 | /** An image view that displays the rounded box behind the button text */ 37 | @property (nonatomic, strong) UIView *backgroundView; 38 | 39 | @end 40 | 41 | // -------------------------------------------------------------------- 42 | 43 | static inline BOOL TO_ROUNDED_BUTTON_FLOAT_IS_ZERO(CGFloat value) { 44 | return (value > -FLT_EPSILON) && (value < FLT_EPSILON); 45 | } 46 | 47 | static inline BOOL TO_ROUNDED_BUTTON_FLOATS_MATCH(CGFloat firstValue, CGFloat secondValue) { 48 | return fabs(firstValue - secondValue) > FLT_EPSILON; 49 | } 50 | 51 | // -------------------------------------------------------------------- 52 | 53 | @implementation TORoundedButton 54 | 55 | #pragma mark - View Creation - 56 | 57 | - (instancetype)initWithText:(NSString *)text 58 | { 59 | if (self = [super initWithFrame:(CGRect){0,0, 288.0f, 50.0f}]) { 60 | [self roundedButtonCommonInit]; 61 | _titleLabel.text = text; 62 | [_titleLabel sizeToFit]; 63 | } 64 | 65 | return self; 66 | } 67 | 68 | - (instancetype)initWithFrame:(CGRect)frame 69 | { 70 | if (self = [super initWithFrame:frame]) { 71 | [self roundedButtonCommonInit]; 72 | } 73 | 74 | return self; 75 | } 76 | 77 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 78 | { 79 | if (self = [super initWithCoder:aDecoder]) { 80 | [self roundedButtonCommonInit]; 81 | } 82 | 83 | return self; 84 | } 85 | 86 | - (void)roundedButtonCommonInit 87 | { 88 | // Default properties (Make sure they're not overriding IB) 89 | _cornerRadius = (_cornerRadius > FLT_EPSILON) ?: 12.0f; 90 | _tappedTextAlpha = (_tappedTextAlpha > FLT_EPSILON) ?: 1.0f; 91 | _tapAnimationDuration = (_tapAnimationDuration > FLT_EPSILON) ?: 0.4f; 92 | _tappedButtonScale = (_tappedButtonScale > FLT_EPSILON) ?: 0.97f; 93 | _tappedTintColorBrightnessOffset = !TO_ROUNDED_BUTTON_FLOAT_IS_ZERO(_tappedTintColorBrightnessOffset) ?: -0.1f; 94 | 95 | // Set the tapped tint color if we've set to dynamically calculate it 96 | [self updateTappedTintColorForTintColor]; 97 | 98 | // Create the container view that manages the image view and text 99 | self.containerView = [[UIView alloc] initWithFrame:self.bounds]; 100 | self.containerView.backgroundColor = [UIColor clearColor]; 101 | self.containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 102 | self.containerView.userInteractionEnabled = NO; 103 | self.containerView.clipsToBounds = YES; 104 | [self addSubview:self.containerView]; 105 | 106 | // Create the image view which will show the button background 107 | self.backgroundView = [[UIView alloc] initWithFrame:self.bounds]; 108 | self.backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 109 | self.backgroundView.backgroundColor = self.tintColor; 110 | self.backgroundView.layer.cornerRadius = _cornerRadius; 111 | #ifdef __IPHONE_13_0 112 | if (@available(iOS 13.0, *)) { self.backgroundView.layer.cornerCurve = kCACornerCurveContinuous; } 113 | #endif 114 | [self.containerView addSubview:self.backgroundView]; 115 | 116 | // Create the title label that will display the button text 117 | UIFont *buttonFont = [UIFont systemFontOfSize:17.0f weight:UIFontWeightBold]; 118 | if (@available(iOS 11.0, *)) { 119 | // Apply resizable button metrics to font 120 | UIFontMetrics *metrics = [[UIFontMetrics alloc] initForTextStyle:UIFontTextStyleBody]; 121 | buttonFont = [metrics scaledFontForFont:buttonFont]; 122 | } 123 | 124 | self.titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 125 | self.titleLabel.textAlignment = NSTextAlignmentCenter; 126 | self.titleLabel.textColor = [UIColor whiteColor]; 127 | self.titleLabel.font = buttonFont; 128 | self.titleLabel.adjustsFontForContentSizeCategory = YES; 129 | self.titleLabel.backgroundColor = self.tintColor; 130 | self.titleLabel.text = @"Button"; 131 | [self.containerView addSubview:self.titleLabel]; 132 | 133 | // Create action events for all possible interactions with this control 134 | [self addTarget:self action:@selector(didTouchDownInside) forControlEvents:UIControlEventTouchDown|UIControlEventTouchDownRepeat]; 135 | [self addTarget:self action:@selector(didTouchUpInside) forControlEvents:UIControlEventTouchUpInside]; 136 | [self addTarget:self action:@selector(didDragOutside) forControlEvents:UIControlEventTouchDragExit|UIControlEventTouchCancel]; 137 | [self addTarget:self action:@selector(didDragInside) forControlEvents:UIControlEventTouchDragEnter]; 138 | } 139 | 140 | #pragma mark - View Displaying - 141 | 142 | - (void)layoutSubviews 143 | { 144 | [super layoutSubviews]; 145 | 146 | // Configure the button text 147 | [self.titleLabel sizeToFit]; 148 | self.titleLabel.center = self.containerView.center; 149 | self.titleLabel.frame = CGRectIntegral(self.titleLabel.frame); 150 | } 151 | 152 | - (void)tintColorDidChange 153 | { 154 | [super tintColorDidChange]; 155 | self.titleLabel.backgroundColor = self.isTapped ? [UIColor clearColor] : self.tintColor; 156 | self.backgroundView.backgroundColor = self.tintColor; 157 | [self setNeedsLayout]; 158 | } 159 | 160 | - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection 161 | { 162 | [super traitCollectionDidChange:previousTraitCollection]; 163 | [self setNeedsLayout]; 164 | } 165 | 166 | - (void)updateTappedTintColorForTintColor 167 | { 168 | if (TO_ROUNDED_BUTTON_FLOAT_IS_ZERO(_tappedTintColorBrightnessOffset)) { 169 | return; 170 | } 171 | 172 | _tappedTintColor = [[self class] brightnessAdjustedColorWithColor:self.tintColor 173 | amount:_tappedTintColorBrightnessOffset]; 174 | } 175 | 176 | #pragma mark - Interaction - 177 | 178 | - (void)didTouchDownInside 179 | { 180 | self.isTapped = YES; 181 | 182 | // The user touched their finger down into the button bounds 183 | [self setLabelAlphaTappedAnimated:NO]; 184 | [self setBackgroundColorTappedAnimated:NO]; 185 | [self setButtonScaledTappedAnimated:YES]; 186 | } 187 | 188 | - (void)didTouchUpInside 189 | { 190 | self.isTapped = NO; 191 | 192 | // The user lifted their finger up from inside the button bounds 193 | [self setLabelAlphaTappedAnimated:YES]; 194 | [self setBackgroundColorTappedAnimated:YES]; 195 | [self setButtonScaledTappedAnimated:YES]; 196 | 197 | // Send the semantic button action for apps relying on this action 198 | [self sendActionsForControlEvents:UIControlEventPrimaryActionTriggered]; 199 | 200 | // Trigger the block if it has been set 201 | if (self.tappedHandler) { self.tappedHandler(); } 202 | } 203 | 204 | - (void)didDragOutside 205 | { 206 | self.isTapped = NO; 207 | 208 | // After tapping down, without releasing, the user dragged their finger outside the bounds 209 | [self setLabelAlphaTappedAnimated:YES]; 210 | [self setBackgroundColorTappedAnimated:YES]; 211 | [self setButtonScaledTappedAnimated:YES]; 212 | } 213 | 214 | - (void)didDragInside 215 | { 216 | self.isTapped = YES; 217 | 218 | // After dragging out, without releasing, they dragged back in 219 | [self setLabelAlphaTappedAnimated:YES]; 220 | [self setBackgroundColorTappedAnimated:YES]; 221 | [self setButtonScaledTappedAnimated:YES]; 222 | } 223 | 224 | #pragma mark - Animation - 225 | 226 | - (void)setBackgroundColorTappedAnimated:(BOOL)animated 227 | { 228 | if (!self.tappedTintColor) { return; } 229 | 230 | // Toggle the background color of the title label 231 | void (^updateTitleOpacity)(void) = ^{ 232 | self.titleLabel.backgroundColor = self.isTapped ? [UIColor clearColor] : self.tintColor; 233 | }; 234 | 235 | // ----------------------------------------------------- 236 | 237 | void (^animationBlock)(void) = ^{ 238 | self.backgroundView.backgroundColor = self.isTapped ? self.tappedTintColor : self.tintColor; 239 | }; 240 | 241 | void (^completionBlock)(BOOL) = ^(BOOL completed){ 242 | if (completed == NO) { return; } 243 | updateTitleOpacity(); 244 | }; 245 | 246 | if (!animated) { 247 | animationBlock(); 248 | completionBlock(YES); 249 | } 250 | else { 251 | self.titleLabel.backgroundColor = [UIColor clearColor]; 252 | [UIView animateWithDuration:self.tapAnimationDuration 253 | delay:0.0f 254 | options:UIViewAnimationOptionBeginFromCurrentState 255 | animations:animationBlock 256 | completion:completionBlock]; 257 | } 258 | 259 | } 260 | 261 | - (void)setLabelAlphaTappedAnimated:(BOOL)animated 262 | { 263 | if (self.tappedTextAlpha > 1.0f - FLT_EPSILON) { return; } 264 | 265 | CGFloat alpha = self.isTapped ? self.tappedTextAlpha : 1.0f; 266 | 267 | // Animate the alpha value of the label 268 | void (^animationBlock)(void) = ^{ 269 | self.titleLabel.alpha = alpha; 270 | }; 271 | 272 | // If we're not animating, just call the blocks manually 273 | if (!animated) { 274 | // Remove any animations in progress 275 | [self.titleLabel.layer removeAnimationForKey:@"opacity"]; 276 | animationBlock(); 277 | return; 278 | } 279 | 280 | // Set the title label to clear beforehand 281 | self.titleLabel.backgroundColor = [UIColor clearColor]; 282 | 283 | // Animate the button alpha 284 | [UIView animateWithDuration:self.tapAnimationDuration 285 | delay:0.0f 286 | options:UIViewAnimationOptionBeginFromCurrentState 287 | animations:animationBlock 288 | completion:nil]; 289 | } 290 | 291 | - (void)setButtonScaledTappedAnimated:(BOOL)animated 292 | { 293 | if (self.tappedButtonScale < FLT_EPSILON) { return; } 294 | 295 | CGFloat scale = self.isTapped ? self.tappedButtonScale : 1.0f; 296 | 297 | // Animate the alpha value of the label 298 | void (^animationBlock)(void) = ^{ 299 | self.containerView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 300 | scale, 301 | scale); 302 | }; 303 | 304 | // If we're not animating, just call the blocks manually 305 | if (!animated) { 306 | animationBlock(); 307 | return; 308 | } 309 | 310 | // Animate the button alpha 311 | [UIView animateWithDuration:self.tapAnimationDuration 312 | delay:0.0f 313 | usingSpringWithDamping:1.0f 314 | initialSpringVelocity:0.5f 315 | options:UIViewAnimationOptionBeginFromCurrentState 316 | animations:animationBlock 317 | completion:nil]; 318 | } 319 | 320 | #pragma mark - Public Accessors - 321 | 322 | - (void)setAttributedText:(NSAttributedString *)attributedText 323 | { 324 | self.titleLabel.attributedText = attributedText; 325 | [self.titleLabel sizeToFit]; 326 | [self setNeedsLayout]; 327 | } 328 | 329 | - (NSAttributedString *)attributedText 330 | { 331 | return self.titleLabel.attributedText; 332 | } 333 | 334 | - (void)setText:(NSString *)text 335 | { 336 | self.titleLabel.text = text; 337 | [self.titleLabel sizeToFit]; 338 | [self setNeedsLayout]; 339 | } 340 | - (NSString *)text { return self.titleLabel.text; } 341 | 342 | - (void)setTextFont:(UIFont *)textFont 343 | { 344 | self.titleLabel.font = textFont; 345 | self.textPointSize = 0.0f; // Reset the IB text point size back to disabled 346 | } 347 | - (UIFont *)textFont { return self.titleLabel.font; } 348 | 349 | - (void)setTextColor:(UIColor *)textColor 350 | { 351 | self.titleLabel.textColor = textColor; 352 | } 353 | - (UIColor *)textColor { return self.titleLabel.textColor; } 354 | 355 | - (void)setTextPointSize:(CGFloat)textPointSize 356 | { 357 | if (_textPointSize == textPointSize) { return; } 358 | _textPointSize = textPointSize; 359 | self.titleLabel.font = [UIFont boldSystemFontOfSize:textPointSize]; 360 | [self setNeedsLayout]; 361 | } 362 | 363 | - (void)setTintColor:(UIColor *)tintColor 364 | { 365 | [super setTintColor:tintColor]; 366 | [self updateTappedTintColorForTintColor]; 367 | self.backgroundView.backgroundColor = tintColor; 368 | self.titleLabel.backgroundColor = tintColor; 369 | [self setNeedsLayout]; 370 | } 371 | 372 | - (void)setTappedTintColor:(UIColor *)tappedTintColor 373 | { 374 | if (_tappedTintColor == tappedTintColor) { return; } 375 | _tappedTintColor = tappedTintColor; 376 | _tappedTintColorBrightnessOffset = 0.0f; 377 | [self setNeedsLayout]; 378 | } 379 | 380 | - (void)setTappedTintColorBrightnessOffset:(CGFloat)tappedTintColorBrightnessOffset 381 | { 382 | if (TO_ROUNDED_BUTTON_FLOATS_MATCH(_tappedTintColorBrightnessOffset, 383 | tappedTintColorBrightnessOffset)) 384 | { 385 | return; 386 | } 387 | 388 | _tappedTintColorBrightnessOffset = tappedTintColorBrightnessOffset; 389 | [self updateTappedTintColorForTintColor]; 390 | [self setNeedsLayout]; 391 | } 392 | 393 | - (void)setCornerRadius:(CGFloat)cornerRadius 394 | { 395 | // Make sure the corner radius doesn't match 396 | if (fabs(cornerRadius - _cornerRadius) < FLT_EPSILON) { 397 | return; 398 | } 399 | 400 | _cornerRadius = cornerRadius; 401 | self.backgroundView.layer.cornerRadius = _cornerRadius; 402 | [self setNeedsLayout]; 403 | } 404 | 405 | - (void)setEnabled:(BOOL)enabled 406 | { 407 | [super setEnabled:enabled]; 408 | self.containerView.alpha = enabled ? 1 : 0.4; 409 | } 410 | 411 | - (CGFloat)minimumWidth 412 | { 413 | return self.titleLabel.frame.size.width; 414 | } 415 | 416 | #pragma mark - Graphics Handling - 417 | 418 | + (UIColor *)brightnessAdjustedColorWithColor:(UIColor *)color amount:(CGFloat)amount 419 | { 420 | if (!color) { return nil; } 421 | 422 | CGFloat h, s, b, a; 423 | if (![color getHue:&h saturation:&s brightness:&b alpha:&a]) { return nil; } 424 | b += amount; // Add the adjust amount 425 | b = MAX(b, 0.0f); b = MIN(b, 1.0f); 426 | return [UIColor colorWithHue:h saturation:s brightness:b alpha:a]; 427 | } 428 | 429 | @end 430 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TOAlertViewControllerExample 4 | // 5 | // Created by Tim Oliver on 24/5/19. 6 | // Copyright © 2019 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TOAlertViewControllerExample 4 | // 5 | // Created by Tim Oliver on 24/5/19. 6 | // Copyright © 2019 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "TOAlertViewController.h" 11 | 12 | @interface ViewController () 13 | 14 | @end 15 | 16 | @implementation ViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | // Do any additional setup after loading the view. 21 | } 22 | 23 | - (UIStatusBarStyle)preferredStatusBarStyle { return UIStatusBarStyleLightContent; } 24 | 25 | - (IBAction)didTap:(id)sender 26 | { 27 | TOAlertViewController *alertController = [[TOAlertViewController alloc] 28 | initWithTitle:@"Are you sure?" message:@"This action may take some time to complete. Are you sure you wish to perform this action?"]; 29 | 30 | alertController.defaultAction = [TOAlertAction alertActionWithTitle:@"Yes" action:^{ NSLog(@"Default Button Tapped!"); }]; 31 | alertController.cancelAction = [TOAlertAction alertActionWithTitle:@"Cancel" action:^{ NSLog(@"Cancel Button Tapped!"); }]; 32 | // alertController.destructiveAction = [TOAlertAction alertActionWithTitle:@"Delete" action:^{ NSLog(@"Delete Button Tapped!"); }]; 33 | // [alertController addAction:[TOAlertAction alertActionWithTitle:@"More Info" action:^{ NSLog(@"More info Button Tapped!"); }]]; 34 | 35 | alertController.style = TOAlertViewStyleDark; 36 | 37 | [self presentViewController:alertController animated:YES completion:nil]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /TOAlertViewControllerExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TOAlertViewControllerExample 4 | // 5 | // Created by Tim Oliver on 24/5/19. 6 | // Copyright © 2019 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TOAlertViewControllerFramework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /TOAlertViewControllerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TOAlertViewControllerTests/TOAlertViewControllerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOAlertViewControllerTests.m 3 | // TOAlertViewControllerTests 4 | // 5 | // Created by Tim Oliver on 25/6/19. 6 | // Copyright © 2019 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TOAlertViewControllerTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TOAlertViewControllerTests 16 | 17 | - (void)setUp { 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | } 20 | 21 | - (void)tearDown { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | - (void)testExample { 26 | // This is an example of a functional test case. 27 | // Use XCTAssert and related functions to verify your tests produce the correct results. 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /buildkite/pipeline.release.yml: -------------------------------------------------------------------------------- 1 | env: 2 | LC_ALL: "en_US.UTF-8" 3 | REPO_PATH: "TimOliver/TOAlertViewController" 4 | PODSPEC_PATH: "TOAlertViewController.podspec" 5 | FRAMEWORK_PLIST_PATH: "TOAlertViewControllerFramework/Info.plist" 6 | BUILDKITE_CLEAN_CHECKOUT: true 7 | 8 | steps: 9 | 10 | - label: ':fastlane: Cut New Release' 11 | command: '(curl -s -L http://tim.dev/install_lib | bash -s arg1 arg2) && bundle exec fastlane release' 12 | -------------------------------------------------------------------------------- /buildkite/pipeline.test.yml: -------------------------------------------------------------------------------- 1 | env: 2 | TEST_SCHEME: "TOAlertViewControllerExample" 3 | 4 | steps: 5 | 6 | - label: ':fastlane: Run Tests' 7 | command: '(curl -s -L http://tim.dev/install_lib | bash -s arg1 arg2) && bundle exec fastlane test' 8 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOAlertViewController/bfbc95699cb79297397c4884684d3e5276d29ab2/screenshot.jpg --------------------------------------------------------------------------------