RayChen
RayChen

Reputation: 1468

How to change UIAlertController button text colour in iOS9?

The question is similar to iOS 8 UIActivityViewController and UIAlertController button text color uses window's tintColor but in iOS 9.

I have a UIAlertController and the dismiss button keeps white colour even I have tried to set

[[UIView appearanceWhenContainedIn:[UIAlertController class], nil] setTintColor:[UIColor blackColor]];

UIAlertController *strongController = [UIAlertController alertControllerWithTitle:title
                                                             message:message
                                                      preferredStyle:preferredStyle];
strongController.view.tintColor = [UIColor black];

Upvotes: 38

Views: 40714

Answers (14)

user9855996
user9855996

Reputation:

After alot of research, I found out how to make this work:

let cancelButton = UIAlertAction(title: button, style: UIAlertAction.Style.cancel, handler: { (action) in alert.dismiss(animated: true, completion: nil)
    })

    cancelButton.setValue(UIColor.systemBlue, forKey: "titleTextColor")
    alert.addAction(cancelButton)

Just change the UIColor.systemBlue to what ever color you want, and it will make just that button a special color. I made this example (I created 3 UIAlertActions to make it.):
enter image description here

With just UIAlertAction.Style.whatever, it can only make it blue or red. If you change the UIColor, it will make it any color you want!

Upvotes: 17

BB9z
BB9z

Reputation: 2720

The most reasonable way is set the main window's tintColor. As a uniform appearance is what we usually need.

// in app delegate
window.tintColor = ...

Other solutions have defects

  • Use apperance

    UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = ...
    

    Not works on iOS 9, tests with iOS 11 SDK.

    [[UIView appearance] setTintColor:[UIColor black]];  
    

    Are you serious?

  • Set UIAlertController view's tintColor is unstable. The color may change when user press the button or after view layout.

  • Subclass UIAlertController and overwrite layout method is hack way which is unacceptable.

Upvotes: 3

MAhipal Singh
MAhipal Singh

Reputation: 4827

Objective-C

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Title text"  message:@"Message text"  preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* ok = [UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
//code here…
}];
UIAlertAction* cancel = [UIAlertAction actionWithTitle:@"Later" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
//code here….
}];
[ok setValue:[UIColor greenColor] forKey:@"titleTextColor"];
[cancel setValue:[UIColor redColor] forKey:@"titleTextColor"];
[alertController addAction:ok];
[alertController addAction:cancel];
[alertController.view setTintColor:[UIColor yellowColor]];
[self presentViewController:alertController animated:YES completion:nil];

Swift 3

let alertController = UIAlertController(title: "Title text", message: "Message text", preferredStyle: .alert)
let ok = UIAlertAction(title: "Yes" , style: .default) { (_ action) in
             //code here…
        }
let cancel = UIAlertAction(title: "Later" , style: .default) { (_ action) in
            //code here…
        }
ok.setValue(UIColor.green, forKey: "titleTextColor")
cancel.setValue(UIColor.red, forKey: "titleTextColor")
alertController.addAction(ok)
alertController.addAction(cancel)
alertController.view.tintColor = .yellow
self.present(alertController, animated: true, completion: nil)

Upvotes: 21

Mike M
Mike M

Reputation: 4436

swift3

Tried to use UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = MyColor but this prevents other items unrelated to the UIAlertController from tintColor configuration. I saw it while trying to change the color of navigation bar button items.

I switched to an extension (based on Mike Taverne's response above) and it works great.

extension UIAlertController {

override open func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    //set this to whatever color you like...
    self.view.tintColor = MyColor
}
}

Upvotes: 6

Badr Bujbara
Badr Bujbara

Reputation: 8671

I wanted to make the delete button to appear red, so I used .destructive style:

 alert.addAction(UIAlertAction(title: "Delete", style: .destructive, handler:{(UIAlertAction) in

Upvotes: 1

Ferran Maylinch
Ferran Maylinch

Reputation: 11519

You have 3 styles for the action buttons:

let style : UIAlertActionStyle = .default
// default, cancel (bold) or destructive (red)

let alertCtrl = UIAlertController(....)
alertCtrl.addAction( UIAlertAction(title: "click me", style: style, handler: {
    _ in doWhatever()
}))

Upvotes: 0

Neil Smith
Neil Smith

Reputation: 1051

In Swift 3.x:

I found the following to work effectively. I call this at app launch .

UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = UIColor.black

So this would change the tint color of all UIAlertViewController button labels in your app globally. The only button label color it doesn't change are those which have a UIAlertActionStyle of destructive.

Upvotes: 26

Dheeraj D
Dheeraj D

Reputation: 4451

You can change it using: Swift 3.x

    strongController.view.tintColor = UIColor.green

Upvotes: 5

Mohsin Qureshi
Mohsin Qureshi

Reputation: 1213

In Swift 2.2 you can use following code

 // LogOut or Cancel
    let logOutActionSheet: UIAlertController = UIAlertController(title: "Hello Mohsin!", message: "Are you sure you want to logout?", preferredStyle: .Alert)

    self.presentViewController(logOutActionSheet, animated: true, completion: nil)

    let cancelActionButton: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
        print("Cancel Tapped")
    }

    logOutActionSheet.addAction(cancelActionButton)

    let logOutActionButton: UIAlertAction = UIAlertAction(title: "Clear All", style: .Default)
    { action -> Void in
        //Clear All Method
        print("Logout Tapped")

    }

    logOutActionButton.setValue(UIColor.redColor(), forKey: "titleTextColor")

    logOutActionSheet.addAction(logOutActionButton)

Upvotes: 1

lin
lin

Reputation: 121

[[UIView appearance] setTintColor:[UIColor black]];  

this will change all the UIView tintColor as well as UIAlertController's view

Upvotes: 4

ObiDan
ObiDan

Reputation: 131

There is a problem with setting the tint color on the view after presenting; even if you do it in the completion block of presentViewController:animated:completion:, it causes a flicker effect on the color of the button titles. This is sloppy, unprofessional and completely unacceptable.

The one sure-fire way to solve this problem and to do it everywhere, is via adding a category to UIAlertController and swizzling the viewWillAppear.

The header:

//
//  UIAlertController+iOS9TintFix.h
//
//  Created by Flor, Daniel J on 11/2/15.
//

#import <UIKit/UIKit.h>

@interface UIAlertController (iOS9TintFix)

+ (void)tintFix;

- (void)swizzledViewWillAppear:(BOOL)animated;

@end

The implementation:

//
//  UIAlertController+iOS9TintFix.m
//
//  Created by Flor, Daniel J on 11/2/15.
//

#import "UIAlertController+iOS9TintFix.h"
#import <objc/runtime.h>

@implementation UIAlertController (iOS9TintFix)

+ (void)tintFix {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method method  = class_getInstanceMethod(self, @selector(viewWillAppear:));
        Method swizzle = class_getInstanceMethod(self, @selector(swizzledViewWillAppear:));
        method_exchangeImplementations(method, swizzle);});
}

- (void)swizzledViewWillAppear:(BOOL)animated {
    [self swizzledViewWillAppear:animated];
    for (UIView *view in self.view.subviews) {
        if (view.tintColor == self.view.tintColor) {
            //only do those that match the main view, so we don't strip the red-tint from destructive buttons.
            self.view.tintColor = [UIColor colorWithRed:0.0 green:122.0/255.0 blue:1.0 alpha:1.0];
            [view setNeedsDisplay];
        }
    }
}

@end

Add a .pch (precompiled header) to your project and include the category:

#import "UIAlertController+iOS9TintFix.h"

Make sure you register your pch in the project properly, and it will include the category methods in every class that uses the UIAlertController.

Then, in your app delegates didFinishLaunchingWithOptions method, import your category and call

[UIAlertController tintFix];

and it will automatically propagate to every single instance of UIAlertController within your app, whether launched by your code or anyone else's.

This solution works for both iOS 8.X and iOS 9.X and lacks the flicker of the tint change post-presentation approach.

Mad props to Brandon above for starting this journey, unfortunately my reputation was not sufficient enough to comment on his post, or else I would have left it there!

Upvotes: 4

Mike Taverne
Mike Taverne

Reputation: 9352

I was able to solve this by subclassing UIAlertController:

class MyUIAlertController: UIAlertController {

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        //set this to whatever color you like...
        self.view.tintColor = UIColor.blackColor()
    }
}

This survives a device rotation while the alert is showing.

You also don't need to set the tintColor after presenting the alert when using this subclass.

Though it isn't necessary on iOS 8.4, this code does work on iOS 8.4 as well.

Objective-C implementation should be something like this:

@interface MyUIAlertController : UIAlertController
@end

@implementation MyUIAlertController
-(void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    //set this to whatever color you like...
    self.view.tintColor = [UIColor blackColor];
}
@end

Upvotes: 14

Brandon Schlenker
Brandon Schlenker

Reputation: 5088

I found a solution to this. Not an elegant solution, but a solution.

I swizzled viewWillAppear: on UIAlertController, then looped through the views and modified the tint color. In my case I had a tintColor set on the entire window and despite setting the tintColor via appearance the UIAlertController maintained the color on the window. I check if the color is equal to that of the window and if so apply a new one. Blindly applying the tintColor to all views will result in the red tint on destructive actions to be reset.

+ (void)load  
{  
    static dispatch_once_t onceToken;  
    dispatch_once(&onceToken, ^{  
        Method swizzleMethod = class_getInstanceMethod(self, @selector(viewWillAppear:));  
        Method method = class_getInstanceMethod(self, @selector(alertSwizzle_viewWillAppear:));  
        method_exchangeImplementations(method, swizzleMethod);  
    });  
}  

- (void)alertSwizzle_viewWillAppear:(BOOL)animated  
{  
    [self alertSwizzle_viewWillAppear:animated];  
    [self applyTintToView:self.view];  
}  

- (void)applyTintToView:(UIView *)view  
{  
    UIWindow *mainWindow = [UIApplication sharedApplication].keyWindow;  

    for (UIView *v in view.subviews) {  
        if ([v.tintColor isEqual:mainWindow.tintColor]) {  
            v.tintColor = [UIColor greenColor];  
        }  
        [self applyTintToView:v];  
    }  
}  

However this doesn't work on iOS 8, so you'll still need to set the apperance tint color.

[[UIView appearanceWhenContainedIn:[UIAlertController class], nil] setTintColor:[UIColor greenColor]];  

Upvotes: 2

dbart
dbart

Reputation: 5566

I've run into something similar in the past and the issue seems to stem from the fact that the alert controller's view isn't ready to accept tintColor changes before it's presented. Alternatively, try setting the tint color AFTER you present your alert controller:

[self presentViewController:strongController animated:YES completion:nil];
strongController.view.tintColor = [UIColor black];

Upvotes: 62

Related Questions