├── .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 |
4 |
5 |
6 | [](https://github.com/TimOliver/TOAlertViewController/actions)
7 | [](http://cocoadocs.org/docsets/TOAlertViewController)
8 | [](https://raw.githubusercontent.com/TimOliver/TOAlertViewController/master/LICENSE)
9 | [](http://cocoadocs.org/docsets/TOAlertViewController)
10 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=M4RKULAVKV7K8)
11 | [](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. 
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
--------------------------------------------------------------------------------