I am currently spinning a complex web of UIAppearance modifiers*, and have come across a problem.
My usage of FlatUIKit's custom UIBarButton appearance protocol is causing MFMailComposerViewController to complain and stop working.
Therefore, instead of using UIAppearance's whenContainedIn method to specify classes that cause modification to occur, is there a way to exclude certain classes, i.e. a "when not contained in"?
*I am talking about the UIAppearance protocol that is used to predefine object appearance settings in the app's delegate.
You can use appearanceWhenContainedIn: to specify nil modification, which will give the default appearance:
[[UIBarButton appearance] setBarTintColor:[UIColor redColor]];
[[UIBarButton appearanceWhenContainedIn:[MFMailComposerViewController class], nil] setBarTintColor:nil];
As of iOS 9 SDK, there is also
[[UIBarButton appearance] setBarTintColor:[UIColor redColor]];
[[UIBarButton appearanceWhenContainedInInstancesOfClasses:#[[MFMailComposerViewController class]] setBarTintColor:nil];
Which can be used Swift-2 like so:
UIBarButton.appearance().barTintColor = UIColor.redColor()
UIBarButton.appearanceWhenContainedInInstancesOfClasses([MFMailComposerViewController.self]).barTintColor = nil
For me I used this one which works in iOS 10,
[[UITextField appearanceWhenContainedInInstancesOfClasses:#[[UISearchBar class]]] setTextColor:[UIColor darkGrayColor]];
My solution is to create a custom subclass of all the container view controllers that are being used in the app as the main UIWindow subviews (window's root view controller or presented view controllers), such as UINavigationController, UITabBarController or UISplitViewController.
Let's say the app is only using UINavigationController. Create a subclass:
class CustomizedNavigationController: UINavigationController {}
Then use the CustomizedNavigationController instead of plain UINavigationController everywhere in the app.
Also, instead of specifying the appearance for every UIBarButton, specify the appearance only when contained in the subclass:
UIBarButtonItem.appearance(whenContainedInInstancesOf:
[CustomizedNavigationController.self])
Because MFMailComposerViewController is not using the subclass, it won't get customized.
Related
Xcode or iOS update has made the following code below function differently because now the navigation bar button background has a background color, unlike previously. Any fix? I want all buttons to have the same global color, but now it effects the navigation bar buttons which I dont want. I would like it transparent like before.
[[UIButton appearance] setBackgroundColor:[Helper getColor:self.application.color]];
As this particular UIButton is inside a UINavigationBar you could try applying a second specific appearance to 'override' the first general appearance set.
For example calling specifically,
[[UIButton appearanceWhenContainedIn:[UINavigationBar class], nil]
setBackgroundColor:[UIColor clearColor] forState:state barMetrics:metrics];
after you call,
[[UIButton appearance] setBackgroundColor:[Helper getColor:self.application.color]];
To override the general with the specific.
Docs Link: https://developer.apple.com/documentation/uikit/uiappearance
Is there a way to disable UINavigationBar Translucency for an entire application?
I'm aware that using [self.navigationController.navigationBar setTranslucent:NO] can fix this issue for a single controller, but I have a lot of UINavigationBars in my application and this is a pretty tedious solution.
I've tried [[UINavigationBar appearance] setTranslucent:NO], but that functionality is surprisingly not supported. Doing that results in Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** Illegal property type, c for appearance setter, _installAppearanceSwizzlesForSetter:'
If I HAVE to, I can go through my entire app setting UINavigationBars to disable translucency one by one, but there must be some more elegant solution to this issue...
if you set the translucence of the first navigation bar in the stack to false [self.navigationController.navigationBar setTranslucent:NO], it will reflect in all the following NavigationViewController that are pushed to that stack.
Here is a Swift solution if you want to apply this Styling to the whole app.
in the AppDelegate class add this to the didFinishLaunchingWithOptions:
For Swift 2:
UINavigationBar.appearance().translucent = false
For Swift 3+:
UINavigationBar.appearance().isTranslucent = false
It seems very simple with this code in appDelegate didFinishLaunchingWithOptions (works fine with iOS 8 and above versions)
[[UINavigationBar appearance] setTranslucent:NO];
I think you are right about no appearance proxy being available for this property. Are you using UINavigationControllers or UINavigationBar objects? If you are using UINavigationBars you could subclass it and create a non-translucent nav bar.
Header file:
#import <UIKit/UIKit.h>
#interface ABCNonTranslucentNavBar : UINavigationBar
#end
Implementation file:
#import "ABCNonTranslucentNavBar.h"
#implementation ABCNonTranslucentNavBar
- (void)drawRect:(CGRect)rect
{
[self setTranslucent:NO];
}
Then just replace the UINavigationBars with your subclass. You could also do something similar with a subclassed UINavigationController.
Adding this in case anyones still battling this.
You can fool it though by specifying a non exist image, which will make the nav bar INCLUDING it's tool bar go opaque
[[UIToolbar appearance] setBackgroundColor:[UIColor colorWithRed:219.0/255.0 green:67.0/255.0 blue:67.0/255.0 alpha:1.0]];
[[UIToolbar appearance] setBackgroundImage:[[UIImage alloc] init] forToolbarPosition:UIBarPositionAny barMetrics:UIBarMetricsDefault];
I know this is old, but this might come in handy for someone;
You can use a category, and within it* set the property [translucent][1]
#implementation UINavigationBar (MakeTranslucent)
-(void)willMoveToWindow:(UIWindow *)newWindow {
[super willMoveToWindow:newWindow];
self.translucent = NO;
}
#end
I used willMoveToWindow, I do not know whether this is a good idea so UAYOR.
See the excerpt from UIKit code documentation:
/*
New behavior on iOS 7.
Default is YES.
You may force an opaque background by setting the property to NO.
If the navigation bar has a custom background image, the default is inferred
from the alpha values of the image—YES if it has any pixel with alpha < 1.0
If you send setTranslucent:YES to a bar with an opaque custom background image
it will apply a system opacity less than 1.0 to the image.
If you send setTranslucent:NO to a bar with a translucent custom background image
it will provide an opaque background for the image using the bar's barTintColor if defined, or black
for UIBarStyleBlack or white for UIBarStyleDefault if barTintColor is nil.
*/
Correct Swift 4 solution is
UINavigationBar.appearance().isTranslucent = false
UINavigationBar.appearance().backgroundColor = .white
I think appearance api does not support translucent property of navigation bar .
But you can do this for whole App like this , please have a look at this code --
here Menu Screen is a root view controller .
MenuScreen *ms = [[MenuScreen alloc]initWithNibName:#"MenuScreen" bundle:nil];
UINavigationController *nv = [[UINavigationController alloc]initWithRootViewController:ms];
//This will set property for whole App.
[nv.navigationBar setTranslucent:NO];
self.window.rootViewController = nv ;
If you don't use storyboard, but IB, set the navigation bar style of your MainWindows.xib to NOT translucent and set as color not the clear color.
I have an appearance proxy that sets the barTintColor property to green on UINavigationBar
[[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:54./255 green:165./255 blue:53./255 alpha:1]];
As needed I override this using appearanceWhenContainedIn:
[[UINavigationBar appearanceWhenContainedIn:[INFSearchViewController class], nil] setBarTintColor:[UIColor colorWithWhite:0.80 alpha:1]];
This works fine.
However when I present an MFMessageComposeViewController it adheres to the UINavigationBar proxy and looks like the following.
Which obviously looks terrible, I would prefer MFMessageComposeViewController to not adhere to the proxy but attempting to do
[[UINavigationBar appearanceWhenContainedIn:[MFMessageComposeViewController class], nil] setBarTintColor:[UIColor whiteColor]];
has no affect.
What course of action should I take here?
The hacky way: set the appearance back to the default white, present the modal, set the appearance to styled when the modal returns.
Or, reverse your thinking. Leave the global appearance as the default. Then you can selectively apply the styled nav bar where appropriate.
If "where appropriate" ends up being 90% of the app, just set up a thin subclass of UIViewController (or whatever view controller you use a lot) and that use that where you want the appearance.
[[UINavigationBar appearanceWhenContainedIn:[MyStyledViewController class], nil]
setBarTintColor:[UIColor colorWithRed:54./255 green:165./255 blue:53./255 alpha:1]];
And in each .h file, set your view controller superclass to MyStyledViewController rather than plain old UIViewController.
After digging around and trying a few different suggestions I arrived at a nice, non-hacky solution using a UINavigationController subclass.
This allows me to style all wanted nav bars once using the appearance proxy, with the exception of the MFMessageComposeViewController and MFMailComposeViewController which I'd prefer to look standard in order to communicate to the user that they are using core iOS functionality.
1 - Create a UINavigationController subclass.
2 - Style your nav bar using the appearance proxy as you were, but now using appearanceWhenContainedIn:
[[UINavigationBar appearanceWhenContainedIn:[KCStyledNavController class], nil] setBarTintColor:[UIColor redColor]];
[[UINavigationBar appearanceWhenContainedIn:[KCStyledNavController class], nil] setTintColor:[UIColor whiteColor]];
3 - Go into your storyboard, select all the the UINavigationControllers you want styled and change their custom class to your styled one.
I am using a cocoa iOS component from cocoacontrols.com, which is NOT using storyboards. This class creates a new modal view and the modal view has a NavigationBar at the top. I am trying to get the color of the navigationBar to change. In all my other views, which are storyboard based, I use a base class of STBaseViewController which sets the navigationBar as white.
I have changed this cocoacontrol class to be a STBaseViewController instead of a UIViewController, but it still is not changing my background to white.
#interface BZPasscodeFieldController : STBaseViewController
In the BZPasscodeFieldController, which they don't make any reference to a navigationbar, so I am not sure how its even showing up (again it has no storyboard's only xibs and they don't have navigationbars)?
Anyway, does anyone know how to make the navigationbar background color white programatically?
Further, in all my storyboard viewControllers that have a UINavigationBar, I have added a UIButton over the area where the title goes, so that I can easily change the font/style and also make it clickable. I need to add this same UIButton to the uinavigationBar of this BZPasscodeFieldController created programatically. How would I go about doing that?
To Set Navigationbar Background Color:
[[UINavigationBar appearance] setBarTintColor:[UIColor orangeColor]];
To set Navigationbar Tint Color:
[[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];
For your question about the button on the place of title of navigationBar. Each navigationBar has a titleView property, you can assign any view to it. For example take a look at this method, you can call it in your BZPasscodeFieldController:
- (void) setNavigationBarTitleButton
{
UIButton* centralButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 40, 44)];
[centralButton setImage:navigationCentralButtonImage forState:UIControlStateNormal];
[centralButton setShowsTouchWhenHighlighted:TRUE];
[centralButton addTarget:self action:#selector(goHigher) forControlEvents:UIControlEventTouchDown];
self.navigationItem.titleView = centralButton;
}
For your previous question the answers provided already are all correct, calling them in the right place(for example in AppDelegate's application:didFinishLaunchingWithOptions: should make it work well, unless something is messed up in your custom view controller classes.
[[UINavigationBar appearance] setTintColor:[UIColor blueColor]];
[self.navigationController.navigationBar setTintColor:[UIColor whiteColor]];
Is there a way to set same background image for all views?
Setting same background in all viewDidLoad: methods. It's not cool.
You can create a custom class, let's call it a TemplateView, which is subclass of UIView.
Then using xib/storyboards select the controller view and identity inspector change the Class property to 'TemplateView'.
Subsequently using an UIAppearance change the background color of the template view to the desired one.
[[TemplateView appearance]setBackgroundColor:[UIColor blueColor]];
That will change the background color of each of the template views in your project.
I think it's better solution than
[[UIView appearance] setBackgroundColor:[UIColor redColor]];
because we don't change the background of everything, just our custom class.
Hope it will help.
Yes, using UIAppearance in iOS5+:
[[UIView appearance] setBackgroundColor:[UIColor redColor]];
NOTE: UIView conforms to <UIAppearance, UIAppearanceContainer> protocols but does not mark any properties as UI_APPEARANCE_SELECTOR for some reason.
I don't think this is a good idea, but if you really want to do this, you could do something like this:
- (void)setBGColor:(UIColor *)color forAllSubviewsOf:(UIView *)view
{
[view setBackgroundColor:color];
for (UIView *sub in view.subviews)
[self setBGColor:color forAllSubviewsOf:sub];
}
I guess you can use Method Swizzling kind some feature to change all views colour or image setting.
Check following
https://wiredcraft.com/blog/method-swizzling-ios/