How to change the background color of the UIAlertController?
Asked Answered
P

10

36

Due to strange behavior of UIActionSheet in iOS 8, I have implemented UIAlertController with UIAction as buttons in it. I would like to change the entire background of the UIAlertController. But I can't find any ways to do it.

Tried even with,

actionController.view.backgroundColor = [UIColor blackColor];

But didn't help me out. Any inputs on this regard will be appreciable.

Thanks in advance.

Petronille answered 11/11, 2014 at 11:8 Comment(1)
hello Dear, from your question, i created a demo and it's working fine... I tried with yellow color. And it's running.Castoff
I
45

You have to step some views deeper:

let subview = actionController.view.subviews.first! as UIView
let alertContentView = subview.subviews.first! as UIView
alertContentView.backgroundColor = UIColor.blackColor()

And maybe you want to keep original corner radius:

 alertContentView.layer.cornerRadius = 5;

Sorry for the "Swifting" but i'm not familiar with Objective-C. I hope that's similar.

Of course it's also important to change the title color of the actions. Unfortunately I don't know, how to set the color of actions separately. But this is, how you change all button text colors:

actionController.view.tintColor = UIColor.whiteColor();

EDIT:

The corner radius of the UIAlertController has changed since this answer's been posted! Replace this:

 alertContentView.layer.cornerRadius = 5;

to this:

 alertContentView.layer.cornerRadius = 15
Ibadan answered 18/3, 2015 at 13:4 Comment(9)
Here's the Objective C version:` UIView * firstView = alertController.view.subviews.firstObject; UIView * nextView = firstView.subviews.firstObject; nextView.backgroundColor = [UIColor greenColor];`Fibre
This is awesome. It works like a charm and solved my issue. It should be accepted!Warpath
If you're Swifting, what's with the semi-colons?! :)Bluegrass
@ Levinson: I had some first and Phoenix removed them. They are not necessary ;)Ibadan
i dont understand why dont u updated the answer with Objective CCitrin
what version of iOS is this? It does not appear to work in 11.3Markel
Cancel button background color is still whiteSidman
iOS 12 : Apple sez: The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.Tersanctus
In iOS 13+ we can do: alertContentView.overrideUserInterfaceStyle = .darkFrederiksberg
I
21

maybe you like the use the blur effect in the dark mode. Here is a very easy way to get this:

UIVisualEffectView.appearance(whenContainedInInstancesOf: [UIAlertController.classForCoder() as! UIAppearanceContainer.Type]).effect = UIBlurEffect(style: .dark)
Insubordinate answered 7/11, 2016 at 15:44 Comment(4)
I originally marked this as working for me, but it does not. When I do this actionSheets work fine (which is what I was trying to solve), but alerts look different than actionSheets.Figurehead
You're my hero.Langevin
Works really well. In swift 4.2 it can be done even more concisely: UIVisualEffectView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).effect = UIBlurEffect(style: .dark)Shotton
This not work for the cancel button, still white color.Somnambulation
E
18

I have found a hack-ish way of doing it. First you need an extension to allow you to search for the UIVisualEffectView inside the UIAlertController:

extension UIView
{
    func searchVisualEffectsSubview() -> UIVisualEffectView?
    {
        if let visualEffectView = self as? UIVisualEffectView
        {
            return visualEffectView
        }
        else
        {
            for subview in subviews
            {
                if let found = subview.searchVisualEffectsSubview()
                {
                    return found
                }
            }
        }

        return nil
    }
}

Important: You have to call this function after calling presentViewController, because only after loading the view controller that the visual effects view is inserted into place. Then you can change the effect associated with it to a dark blur effect:

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

if let visualEffectView = actionController.view.searchVisualEffectsSubview()
{
    visualEffectView.effect = UIBlurEffect(style: .Dark)
}

And this is the final result:

demo picture

I am honestly surprised myself how well it works! I think this is probably something Apple forgot to add. Also, I haven't yet passed an App through approval with this "hack" (it isn't a hack because we're only using public APIs), but I'm confident there won't be a problem.

Emplace answered 25/5, 2016 at 0:56 Comment(2)
This almost works! Unfortunately in Landscape mode on a horizontally regular environment where the buttons are laid out side by side, the Cancel button is still left white as it appears to have no visual effect.Elroy
Suggestion in objective c?Chatelaine
N
12

for Swift 3/ Swift 4

let subview =(alert.view.subviews.first?.subviews.first?.subviews.first!)! as UIView

            subview.backgroundColor = UIColor(red: (145/255.0), green: (200/255.0), blue: (0/255.0), alpha: 1.0)

            alert.view.tintColor = UIColor.black

enter image description here.

Neela answered 31/3, 2017 at 10:35 Comment(0)
S
9

Here is a UIAlertController extension that works on both iPad and iPhone. Cancel button will change from a dark colour to white automatically depending on what blurStyle is selected:

extension UIAlertController {

    private struct AssociatedKeys {
        static var blurStyleKey = "UIAlertController.blurStyleKey"
    }

    public var blurStyle: UIBlurEffectStyle {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.blurStyleKey) as? UIBlurEffectStyle ?? .extraLight
        } set (style) {
            objc_setAssociatedObject(self, &AssociatedKeys.blurStyleKey, style, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

            view.setNeedsLayout()
            view.layoutIfNeeded()
        }
    }

    public var cancelButtonColor: UIColor? {
        return blurStyle == .dark ? UIColor(red: 28.0/255.0, green: 28.0/255.0, blue: 28.0/255.0, alpha: 1.0) : nil
    }

    private var visualEffectView: UIVisualEffectView? {
        if let presentationController = presentationController, presentationController.responds(to: Selector(("popoverView"))), let view = presentationController.value(forKey: "popoverView") as? UIView // We're on an iPad and visual effect view is in a different place.
        {
            return view.recursiveSubviews.flatMap({$0 as? UIVisualEffectView}).first
        }

        return view.recursiveSubviews.flatMap({$0 as? UIVisualEffectView}).first
    }

    private var cancelActionView: UIView? {
        return view.recursiveSubviews.flatMap({
            $0 as? UILabel}
        ).first(where: {
            $0.text == actions.first(where: { $0.style == .cancel })?.title
        })?.superview?.superview
    }

    public convenience init(title: String?, message: String?, preferredStyle: UIAlertControllerStyle, blurStyle: UIBlurEffectStyle) {
        self.init(title: title, message: message, preferredStyle: preferredStyle)
        self.blurStyle = blurStyle
    }

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

        visualEffectView?.effect = UIBlurEffect(style: blurStyle)
        cancelActionView?.backgroundColor = cancelButtonColor
    }
}

The following UIView extension is also needed:

extension UIView {

    var recursiveSubviews: [UIView] {
        var subviews = self.subviews.flatMap({$0})
        subviews.forEach { subviews.append(contentsOf: $0.recursiveSubviews) }
        return subviews
    }
}

Example:

let controller = UIAlertController(title: "Dark Alert Controller", message: nil, preferredStyle: .actionSheet, blurStyle: .dark)

// Setup controller actions etc...

present(controller, animated: true, completion: nil)

iPhone:

enter image description here

iPad:

enter image description here

Subsidize answered 21/1, 2017 at 13:36 Comment(2)
Have you had any trouble getting by App Store submission?Dail
Haven't tried but it violates none of Apples rules. All we are doing is searching through subviews using only public classesSubsidize
T
5

Swift3

Step into one more layer compare with swift2

    let subview1 = alert.view.subviews.first! as UIView
    let subview2 = subview1.subviews.first! as UIView
    let view = subview2.subviews.first! as UIView

    subview.backgroundColor = backgroundColor
    view.backgroundColor = backgroundColor
    view.layer.cornerRadius = 10.0
    
    // set color to UILabel font
    setSubviewLabelsToTextColor(textColor, view: view)
    
    // set font to alert via KVC, otherwise it'll get overwritten
    let titleAttributed = NSMutableAttributedString(
        string: alert.title!,
        attributes: [NSFontAttributeName:UIFont.boldSystemFont(ofSize: 17)])
    alert.setValue(titleAttributed, forKey: "attributedTitle")
    
    let messageAttributed = NSMutableAttributedString(
        string: alert.message!,
        attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: 13)])
    alert.setValue(messageAttributed, forKey: "attributedMessage")

    // set the buttons to non-blue, if we have buttons
    if let buttonColor = buttonColor {
        alert.view.tintColor = buttonColor
    }
Telling answered 28/9, 2016 at 9:22 Comment(0)
P
4
func Alert(View: ViewController, Title: String, TitleColor: UIColor, Message: String, MessageColor: UIColor, BackgroundColor: UIColor, BorderColor: UIColor, ButtonColor: UIColor) {

    let TitleString = NSAttributedString(string: Title, attributes: [NSFontAttributeName : UIFont.systemFontOfSize(15), NSForegroundColorAttributeName : TitleColor])
    let MessageString = NSAttributedString(string: Message, attributes: [NSFontAttributeName : UIFont.systemFontOfSize(15), NSForegroundColorAttributeName : MessageColor])

    let alertController = UIAlertController(title: Title, message: Message, preferredStyle: .Alert)

    alertController.setValue(TitleString, forKey: "attributedTitle")
    alertController.setValue(MessageString, forKey: "attributedMessage")

    let okAction = UIAlertAction(title: "OK", style: .Default) { (action) in

    }

    let cancelAction = UIAlertAction(title: "Cancel", style: .Default, handler: nil)

    alertController.addAction(okAction)
    alertController.addAction(cancelAction)


    let subview = alertController.view.subviews.first! as UIView
    let alertContentView = subview.subviews.first! as UIView
    alertContentView.backgroundColor = BackgroundColor
    alertContentView.layer.cornerRadius = 10
    alertContentView.alpha = 1
    alertContentView.layer.borderWidth = 1
    alertContentView.layer.borderColor = BorderColor.CGColor


    //alertContentView.tintColor = UIColor.whiteColor()
    alertController.view.tintColor = ButtonColor

    View.presentViewController(alertController, animated: true) {
        // ...
    }
}
Plenish answered 2/1, 2016 at 15:26 Comment(0)
J
2

For Objective - C Code May be Like.

UIAlertController * alert=[UIAlertController alertControllerWithTitle:@"Title"
                                                              message:@"Message"
                                                       preferredStyle:UIAlertControllerStyleAlert];
UIView *firstSubview = alert.view.subviews.firstObject;
UIView *alertContentView = firstSubview.subviews.firstObject;
for (UIView *subSubView in alertContentView.subviews) {
    subSubView.backgroundColor = [UIColor colorWithRed:255/255.0f green:255/255.0f blue:255/255.0f alpha:1.0f];
}
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action){
   //Close Action
}];
[alert addAction:cancelAction];
[self presentViewController:alert animated:YES completion:nil];
Jest answered 13/10, 2017 at 12:21 Comment(0)
L
1

You can use the appearance proxy.

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

This seems to apply for all but the cancel action when presenting as an action sheet.

Loney answered 27/11, 2014 at 9:38 Comment(2)
Adding 'yourAlertController.view.backgroundColor = [UIColor clearColor];' after your code makes all the actions black. Cancel action too.Kaleena
You lose the blur effect, and I see artifacts around the buttons. Not usable.Schuck
C
0

The best decision that i found (without white spots on the sides)

Link to original answer by Vadim Akhmerov

Answer:

It is easier to subclass UIAlertController.

The idea is to traverse through view hierarchy each time viewDidLayoutSubviews gets called, remove effect for UIVisualEffectView's and update their backgroundColor:

class AlertController: UIAlertController {

    /// Buttons background color.
    var buttonBackgroundColor: UIColor = .darkGray {
        didSet {
            // Invalidate current colors on change.
            view.setNeedsLayout()
        }
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        // Traverse view hierarchy.
        view.allViews.forEach {
            // If there was any non-clear background color, update to custom background.
            if let color = $0.backgroundColor, color != .clear {
                $0.backgroundColor = buttonBackgroundColor
            }
            // If view is UIVisualEffectView, remove it's effect and customise color.
            if let visualEffectView = $0 as? UIVisualEffectView {
                visualEffectView.effect = nil
                visualEffectView.backgroundColor = buttonBackgroundColor
            }
        }

        // Update background color of popoverPresentationController (for iPads).
        popoverPresentationController?.backgroundColor = buttonBackgroundColor
    }

}


extension UIView {

    /// All child subviews in view hierarchy plus self.
    fileprivate var allViews: [UIView] {
        var views = [self]
        subviews.forEach {
            views.append(contentsOf: $0.allViews)
        }

        return views
    }

}

Usage:

  1. Create alert controller(use now AlertController instead UIAlertController)

let testAlertController = AlertController(title: nil, message: nil, preferredStyle: .actionSheet)

  1. Set background color on custom class "AlertController":

var buttonBackgroundColor: UIColor = .darkGray

Compost answered 25/2, 2019 at 8:16 Comment(1)
This is what I have been looking for hours! Do you have any idea on how to keep the little line between options?Bluestocking

© 2022 - 2024 — McMap. All rights reserved.