68
votes

I am using the UIActivityViewController to share items in iOS7. When I tap on the Mail option, it pops up the mail composer, but the Cancel and Send buttons on the navigation bar and the navigation bar itself are blue, making it very difficult to read, so I want to change their color. It's working in iOS6 but not in iOS7.

I tried

[[UIBarButtonItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor redColor], UITextAttributeTextColor, [UIColor clearColor], UITextAttributeTextShadowColor, nil] forState:UIControlStateNormal];

which works in iOS6, and I tried

[[UIBarButtonItem appearance] setTintColor:[UIColor redColor]];
[[UINavigationBar appearance] setBarTintColor:[UIColor redColor]];

which causes the color to flash red the first time the app is run before immediately switching back to the blue color.

20
I'm having the same problem. Hopefully someone knows what's up.Kevin_TA
Also having the same issue here... Have you found a solution / workaround yet?rgomesbr
I don't know if you still watch this, but I gave an answer that is the solution to your problem. iOS 7 button colours are set using the navigation bar tint color.Kevin van Mierlo

20 Answers

76
votes

Managed to change the text color of the Send and Cancel buttons, which are on the UINavigationBar in the MFMailComposerViewController (both Send and Cancel) and MFMessageComposeViewController (only Cancel), when presented from UIActivityViewController.

Using an UIActivityViewController, tap on Messageor Mail:

Using an UIActivityViewController

You'll notice that the default text color of the Send and Cancel buttons is blue:

Default blue colors

In order to change that, in the AppDelegate.m class, in the didFinishLaunchingWithOptions method, insert the following line:

[[UIBarButtonItem appearanceWhenContainedIn:[UINavigationBar class], nil] setTintColor:[UIColor whiteColor]];

This results in:

Changed to white

You can also use other colors, for example:

[UIColor purpleColor];

Change to purple

[UIColor greenColor];

Changed to green

How did I test this? I noticed this solution works for the following:

  • with Xcode 5.1, in the iOS 7.1 simulator, building as base iOS SDK 7.1 (can be chosen from selecting the project file -> Build Settings -> Base SDK. Also, selected from General -> Deployment Target -> 7.1)
  • with Xcode 5.1, on an iPhone 4, building as base iOS SDK 7.0 (can be chosen from selecting the project file -> Build Settings -> Base SDK. Also, selected from General -> Deployment Target -> 7.0)
  • with Xcode 5.1, on an iPhone 4, building as base iOS SDK 7.1 (can be chosen from selecting the project file -> Build Settings -> Base SDK. Also, selected from General -> Deployment Target -> 7.1)

It didn't work when testing with:

  • with Xcode 5.1, in the iOS 7.0 simulator, building as base iOS SDK 7.0 (can be chosen from selecting the project file -> Build Settings -> Base SDK. Also, selected from General -> Deployment Target -> 7.0)

Therefore it should be safe to use, as I believe the behavior on the actual device matters more than the behavior in the iOS simulator. If anyone knows why it doesn't work in the iOS 7.0 simulator, I would like to know. :)

25
votes

Bar tint color and status bar color in UIActivityViewController. Swift 3 solution:

extension MFMailComposeViewController {
    override open func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        UIApplication.shared.statusBarStyle = UIStatusBarStyle.lightContent
    }

    open override func viewDidLoad() {
        super.viewDidLoad()
        navigationBar.isTranslucent = false
        navigationBar.isOpaque = false
        navigationBar.barTintColor = UIColor.white
        navigationBar.tintColor = UIColor.white
    }
}
6
votes

Here is what works on iOS 7.1 as of today.

Subclass the UIActivityViewController and override the following method:

- (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
{
    viewControllerToPresent.view.tintColor = [UIColor whiteColor];

    [super presentViewController:viewControllerToPresent animated:flag completion:^{
        [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

        if (completion) {
            completion();
        }
    }];
}

This will make the buttons white and the status bar white.

4
votes

For Swift:

self.navigationController?.presentViewController(activityViewController, animated: true, completion: { () in
   UIBarButtonItem.appearance().tintColor = UIColor.whiteColor()
   UINavigationBar.appearance().barTintColor = UIColor.whiteColor() // optional to change bar backgroundColor           
}

This will change Send & Cancel button color to White (tested on iOS 7,8) but am still not able to make status bar text color white.( Although I have not tried that Subclass UIActivityViewController solution to change statusbar text color )

4
votes

I fixed my issue by extending the UIActivityViewController and overriding the viewWillAppear and viewWilldisapper methods:

extension UIActivityViewController {

    override open func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        UINavigationBar.appearance().barTintColor = .white
    }
        
    open override func viewDidLoad() {
        super.viewDidLoad()
        navigationController?.navigationBar.isTranslucent = false
        navigationController?.navigationBar.isOpaque = false
        navigationController?.navigationBar.barTintColor = UIColor(red: (247/255), green: (247/255), blue: (247/255), alpha: 1)
        //navigationBar.tintColor = UIColor.white
    }

    open override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(true)
        UINavigationBar.appearance().barTintColor = mycustomColor
    }

}
3
votes

This appears to be a bug with iOS 7. I've seen other reports of this online. It also doesn't appear to be fixed in iOS 7.1.

To be specific, no matter what you do, you cannot set tint colors on navigation bars for dialogs shown from the UIActivityViewController.

3
votes

I had the same problem with my app where the tintColor property of UINavigationBar is white everywhere thanks to the appearance proxy. The resulting effect is that UIBarButtonItem from the mail composer view controller navigationBar were not visible (white buttons on a white navigation bar).

I have this call in my application:didFinishLaunchingWithOptions: method:

[[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];

As it is impossible (for now ?) to access the UINavigationBar of the mail composer view controller in the UIActivityViewController, I did the following workaround which is inspired from Alex's answer:

UIColor *normalColor = [[UINavigationBar appearance] tintColor];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:dataToShare applicationActivities:nil];
            [activityViewController setCompletionHandler:^(NSString *activityType, BOOL completed) {
                // back to normal color
                [[UINavigationBar appearance] setTintColor:normalColor];
            }];
            [self presentViewController:activityViewController animated:YES completion:^{
                // change color temporary
                [[UINavigationBar appearance] setTintColor:[UIColor colorWithRed:232.0f/255.0f green:51.0f/255.0f blue:72.0f/255.0f alpha:1.0f]];
            }];

PS: this code is intented for iOS 7, but you can use [[UIBarButtonItem appearance] setTintColor:] in iOS 6 (cf Kevin van Mierlo's answer)

1
votes

Well there are reasons for why we cannot change the way that the UI in apple's code is the way it is. Mostly because it is apple's. They do not allow you to edit the way the UI in MFMailComposerViewController looks in any way. If there is a way, then I have no clue about it, but I have never seen any way to do it. MFMailComposeViewController doesn't support the appearance attribute as it was created in iOS 3.0, and appearance didn't become a thing until iOS 5.0

Here is a link to the MFMailComposeViewController apple documentation: MFMailComposeViewController

Hope this helps!

1
votes

If you want to set the color of the cancel and send buttons in iOS 7 you should use this:

// Change the colours of the buttons in iOS 7
[[UINavigationBar appearance] setTintColor:[UIColor redColor]];

In iOS 6 it is indeed these and you should also leave this in your code:

// Change the colours of the buttons in iOS 6
[[UIBarButtonItem appearance] setTintColor:[UIColor redColor]];

// Change the color of the the navigation bar in iOS 6 and 7
[[UINavigationBar appearance] setBarTintColor:[UIColor redColor]];
1
votes

Try this code for may be it will help you

[[mailComposer navigationBar] setTintColor:[UIColor blackColor]];
1
votes

I couldn't get Alex's solution to work, however I have managed to get a variation of Paillou's answer to work although I had to set both the barTintColor and the titleTextAttributes in my situation:

UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:applicationActivities];

activityViewController.excludedActivityTypes = @[UIActivityTypePrint, UIActivityTypeCopyToPasteboard, UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll, UIActivityTypeAddToReadingList, UIActivityTypePostToVimeo, UIActivityTypePostToFlickr, UIActivityTypeAirDrop];

[activityViewController setCompletionHandler:^(NSString *activityType, BOOL completed) {
    // back to normal color
    [[UINavigationBar appearance] setBarTintColor:AAColorInputBorder];
    [[UINavigationBar appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
                                                         [UIFont fontWithName:@"Avenir-Medium" size:18], NSFontAttributeName,
                                                         [UIColor whiteColor], NSForegroundColorAttributeName,
                                                         nil]];
}];

[self presentViewController:activityViewController animated:YES completion:^{
// change color temporary
[[UINavigationBar appearance] setBarTintColor:[UIColor whiteColor]];
[[UINavigationBar appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
                                                     [UIFont fontWithName:@"Avenir-Medium" size:18], NSFontAttributeName,
                                                     AAColorInputBorder, NSForegroundColorAttributeName,
                                                     nil]];

Thanks Paillou!

1
votes

This has worked for me: in AppDelegate.m in the function:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

I've entered the following code:

//mail composer
[[UINavigationBar appearanceWhenContainedIn:[MFMailComposeViewController class], nil] setBarTintColor:myBackgroundColor];
[[UINavigationBar appearanceWhenContainedIn:[MFMailComposeViewController class], nil] setTintColor:myBarItemsColor];

It works just fine on iOS7 + iOS8, did not try on newer versions

1
votes

You can set your appearance before presenting UIActivityViewController. Add resetting of appearance to completionWithItemsHandler of your activity VC:

setNavBarAppearance()

activityVC.completionWithItemsHandler = { [weak self] _, _, _, _ in
    self?.resetNavBarAppearance()
}

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

The only problem, that if activity is like mail sending, it is full screen. Your appearance will not be applied to current visible views. A little hacks to solve it:

setNavBarAppearance()

activityVC.completionWithItemsHandler = { [weak self] _, _, _, _ in
    self?.resetNavBarAppearance()

    // Hacks(choose one of them):
    // 1)
    self?.navigationController?.isNavigationBarHidden = true
    self?.navigationController?.isNavigationBarHidden = false
    // 2)
    let redrawTriggerVC = UIViewController()
    redrawTriggerVC.modalPresentationStyle = .popover
    self.present(redrawTriggerVC, animated: false, completion: nil)
    redrawTriggerVC.dismiss(animated: false, completion: nil)
}

present(activityVC, animated: true, completion: nil)
0
votes

For ios7 i think that you should go through this code

[[UINavigationBar appearance] setTintColor:[UIColor redColor]];

If it is also not working then try Mail Compose View Controller apple documentation available on the internet.

0
votes

Before presenting the mail composer insert this line like this:

[mailComposer.navigationBar setTintColor:[UIColor whiteColor]];
[self presentViewController:mailComposer animated:YES completion:nil];

Even though I set the status bar style in application did finish launching I also needed to set it again in the completion block like this:

[self presentViewController:mailComposer animated:YES completion:^{[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];}];
0
votes

I had enormous trouble with this, especially when MFMailComposeViewController/MFMessageViewController are themselves displayed by UIActivityViewController.

I resorted to using method swizzling on viewDidAppear/viewDidDisappear to undo and then redo my app's customisation of colors and fonts, with some help from https://github.com/rentzsch/jrswizzle:

SwizzledComposeViewControllers.h

#import <MessageUI/MessageUI.h>

@interface MFMailComposeViewController (GMSwizzling)
@end

@interface MFMessageComposeViewController (GMSwizzling)
@end

SwizzledComposeViewControllers.m

#import "SwizzledComposeViewControllers.h"
#import "AppDelegate.h"
#import "JRSwizzle.h"

@implementation MFMailComposeViewController (GMSwizzling)

+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    [self jr_swizzleMethod:@selector(init) withMethod:@selector(gmswizzled_init) error:nil];
    [self jr_swizzleMethod:@selector(viewWillAppear:) withMethod:@selector(gmswizzled_viewWillAppear:) error:nil];
    [self jr_swizzleMethod:@selector(viewWillDisappear:) withMethod:@selector(gmswizzled_viewWillDisappear:) error:nil];
  });
}

- (instancetype)gmswizzled_init {
  [(AppDelegate*)UIApplication.sharedApplication.delegate uncustomiseAppearance];
  return [self gmswizzled_init];
}

- (void)gmswizzled_viewWillAppear:(BOOL)animated {
  [(AppDelegate*)UIApplication.sharedApplication.delegate uncustomiseAppearance];
  [self gmswizzled_viewWillAppear:animated];
}

- (void)gmswizzled_viewWillDisappear:(BOOL)animated {
  [(AppDelegate*)UIApplication.sharedApplication.delegate customiseAppearance];
  [self gmswizzled_viewWillDisappear:animated];
}

@end


@implementation MFMessageComposeViewController (GMSwizzling)

+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    [self jr_swizzleMethod:@selector(init) withMethod:@selector(gmswizzled_init) error:nil];
    [self jr_swizzleMethod:@selector(viewWillAppear:) withMethod:@selector(gmswizzled_viewWillAppear:) error:nil];
    [self jr_swizzleMethod:@selector(viewWillDisappear:) withMethod:@selector(gmswizzled_viewWillDisappear:) error:nil];
  });
}

- (instancetype)gmswizzled_init {
  [(AppDelegate*)UIApplication.sharedApplication.delegate uncustomiseAppearance];
  return [self gmswizzled_init];
}

- (void)gmswizzled_viewWillAppear:(BOOL)animated {
  [(AppDelegate*)UIApplication.sharedApplication.delegate uncustomiseAppearance];
  [self gmswizzled_viewWillAppear:animated];
}

- (void)gmswizzled_viewWillDisappear:(BOOL)animated {
  [(AppDelegate*)UIApplication.sharedApplication.delegate customiseAppearance];
  [self gmswizzled_viewWillDisappear:animated];
}

@end

(I have to admit I can't recall why I uncustomised appearances both in init and viewWillAppear, but I'm fairly sure there was a reason ...).

0
votes

In Swift I made an extension for UIViewController:

extension UIViewController {

    func presentActivityViewController(viewControllerToPresent: UIViewController) {
        self.presentViewController(viewControllerToPresent, animated: true) { _ in
            UIBarButtonItem.appearance().tintColor = UIColor.whiteColor()
            UINavigationBar.appearance().barTintColor = Config.primaryColor
        }
    }
}

When I need to present an UIActivityViewController I call:

    let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: [])
    presentActivityViewController(activityViewController)
0
votes

In Swift, on iOS9, setting

UINavigationBar.appearance().barTintColor = UIColor.greenColor() // eg
UINavigationBar.appearance().translucent = false

before presenting the activity view controller did the trick for me.

0
votes

I tried many different methods in iOS 9 and 10, but this is the only one that worked. Note that I have a background image behind the navigationBar as well:

[UIApplication.sharedApplication setStatusBarStyle:UIStatusBarStyleLightContent animated:YES];
NSDictionary *attribs = @{NSForegroundColorAttributeName:UIColor.whiteColor};
UINavigationBar.appearance.titleTextAttributes = attribs;
UINavigationBar.appearance.tintColor = UIColor.whiteColor;
[UINavigationBar.appearance setBackgroundImage:[UIImage imageNamed:@"IOSNavigationBar"] forBarMetrics:UIBarMetricsDefault];
UIBarButtonItem.appearance.tintColor = UIColor.whiteColor;
0
votes

I haven't found a mechanism I liked, so for what it's worth here's mine. Part of the trouble is later versions of iOS add the capability for apps to add system-wide Share and Action Extensions. These third-party items seem to be coded all sorts of ways. Some inherit the app's nav bar style, some use their own, and some seem to assume a white nav bar (but actually inherits from the app).

This is tested on iOS 12.2.

I create an UIActivityItemSource, to which I have:

- (nullable id)activityViewController:(nonnull UIActivityViewController *)activityViewController itemForActivityType:(nullable UIActivityType)activityType {
    if (activityType == UIActivityTypePrint || [activityType.lowercaseString containsString:@"extension"] || [activityType containsString:@"AssignToContact"]) {
        //What a hack, but the best I can do.  Seems some extensions inherit nav style from parent, others don't.
        //ActionExtension is bottom row; all those I tested need this.  The string comparison catches most non-OS extensions (the type is set by developer).
        [[UINavigationBar appearance] setBarTintColor:[UIColor kNavigationBarBackgroundColor]]; //kNavigationBarBackgroundColor is my app's custom nav bar background color
    } else {
        [[UINavigationBar appearance] setBarTintColor:[UIColor whiteColor]];
    }
    return self.pdfData; //In my case I'm sharing a PDF as NSData - modify as needed for your shared item
}

Then in my UIActivityViewController's completionWithItemsHandler I include:

[[UINavigationBar appearance] setBarTintColor:[UIColor kNavigationBarBackgroundColor]]; //Again, this is my app's custom nav bar background color

Unrelated to the specific issue, but if you don't currently have a UIActivityItemSource you need to do something like this:

NSArray *activities=@[self]; //And set self to be a UIActivityItemSource
UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:activities applicationActivities:nil];

I'm sure this isn't 100% reliable, but worked with all extensions I tried.