UIAlertController change background color of Cancel button for action sheet

烈酒焚心 提交于 2019-11-29 12:30:06

You cannot change color of a default cancel style button. You need to create a custom view controller for the cancel button and set it as a content view controller of a cancel alert action. This way keeps the cancel button separately

let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)

alertController.addAction(UIAlertAction(title: "Option 1", style: .default, handler: nil))
alertController.addAction(UIAlertAction(title: "Option 2", style: .default, handler: nil))
alertController.addAction(UIAlertAction(title: "Option 3", style: .default, handler: nil))

alertController.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: nil))

if let firstSubview = alertController.view.subviews.first, let alertContentView = firstSubview.subviews.first {
    for view in alertContentView.subviews {
        view.backgroundColor = .darkGray
    }
}

alertController.view.tintColor = .white

let cancelButtonViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "CancelButtonViewController")
let cancelAction = UIAlertAction(title: "", style: .cancel, handler: nil)
cancelAction.setValue(cancelButtonViewController, forKey: "contentViewController")

alertController.addAction(cancelAction)

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

There is a very very dirty solution but it works. We are going to use UIAppearance for this purpose.

First of all we need to prepare a special private extension for UIView to be able to change it's subviews background color.

fileprivate extension UIView {
    private struct AssociatedKey {
        static var subviewsBackgroundColor = "subviewsBackgroundColor"
    }

    @objc dynamic var subviewsBackgroundColor: UIColor? {
        get { 
          return objc_getAssociatedObject(self, &AssociatedKey.subviewsBackgroundColor) as? UIColor 
        }

        set {
          objc_setAssociatedObject(self,
                                   &AssociatedKey.subviewsBackgroundColor,
                                   newValue,
                                   .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
          subviews.forEach { $0.backgroundColor = newValue }
        }
    }
}

Each time we set subiewsBackgroundColor value UIView will iterate it's subviews and set a new background color for each.

As you can see in the answer below there is a special UIView called _UIAlertControlleriOSActionSheetCancelBackgroundView in UIAlertController's hierarchy which contains a subview with white color (for the cancel button)

Let's try to get it's appearance and use our property to change it's subview color. I guess it's kind of a private api.

if let cancelBackgroundViewType = NSClassFromString("_UIAlertControlleriOSActionSheetCancelBackgroundView") as? UIView.Type {
    cancelBackgroundViewType.appearance().subviewsBackgroundColor = .red
}

Place this code in the AppDelegate's didFinishLaunching or a dedicated class. That's it. Now you can see the cancel button on a red background. Tested on iOS 11.

If you want a separate Cancel Button(UIAlertActionStyleCancel) you can't change the background color of the cancel button. If it's your priority then you have to make your own custom View. Or else you can simply add a default action with the title "Cancel".

[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:nil]]

(But it won't give you a separate button).

I have debugged the view hierarchy and found this.

I used the answer by mbryzinski in my mixed Objective-C / Swift project and was able to modify the Cancel button background color from Objective-C like this:

((UIView *)[NSClassFromString(@"_UIAlertControlleriOSActionSheetCancelBackgroundView")
 appearance]).subviewsBackgroundColor = [UIColor yourBackgroundColor];

I included the UIView extension as a .swift file in the project and I had to remove the fileprivate keyword:

extension UIView {
    private struct AssociatedKey {
        static var subviewsBackgroundColor = "subviewsBackgroundColor"
    }
    @objc dynamic var subviewsBackgroundColor: UIColor? {
    ...

Also, the bridging header needs to be imported in the file where the extension is used:

#import "{Your Project's name}-Swift.h"

Modifying the background and text colors of an UIAlertController is a quick (and honestly dirty) way for implementing a dark theme. There are some slight artifacts around the rounded corners, that become more visible, the darker the background color gets. Especially on the iPad, the artifacts are quite visible.

The correct solution would probably be to use a custom Alert Controller. (Haven't found one yet that looks mostly like the stock one.)

I got the solution now. I created sample project and worked out for your question and I got it.

ViewController.h

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end

ViewController.m

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];

    [alert addAction:[UIAlertAction actionWithTitle:@"Option 1" style:UIAlertActionStyleDefault handler:nil]];
    [alert addAction:[UIAlertAction actionWithTitle:@"Option 2" style:UIAlertActionStyleDefault handler:nil]];
    [alert addAction:[UIAlertAction actionWithTitle:@"Option 3" style:UIAlertActionStyleDefault handler:nil]];
    [alert addAction:[UIAlertAction actionWithTitle:@"Delete" style:UIAlertActionStyleDestructive handler:nil]];

    [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];

    UIView *subView = alert.view.subviews.lastObject; //firstObject
    UIView *alertContentView = subView.subviews.lastObject; //firstObject
    [alertContentView setBackgroundColor:[UIColor darkGrayColor]];
    alertContentView.layer.cornerRadius = 5;
    [self presentViewController:alert animated:YES completion:nil];
    alert.view.tintColor = [UIColor darkGrayColor];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


@end

See the output

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!