I have a problem to change the background image of a UINavigationBar for IOS version < 5. I read already about one good solution, which is based on method swizzling, but the problem of this solution is when I add the image it covers everything include the buttons on a navigation bar.
I found a solution which partially worked for me it is base on a following code:
#interface UINavigationBar (UINavigationBarCategory)
-(void)setBackgroundImage:(UIImage*)image withTag:(NSInteger)bgTag;
-(void)resetBackground:(NSInteger)bgTag;
#end
#implementation UINavigationBar (UINavigationBarCategory)
-(void)setBackgroundImage:(UIImage*)image withTag:(NSInteger)bgTag{
if(image == NULL){ //might be called with NULL argument
return;
}
UIImageView *aTabBarBackground = [[UIImageView alloc]initWithImage:image];
aTabBarBackground.frame = CGRectMake(0,0,self.frame.size.width,self.frame.size.height);
aTabBarBackground.tag = bgTag;
[self addSubview:aTabBarBackground];
//[self sendSubviewToBack:aTabBarBackground];
[aTabBarBackground release];
}
-(void)setRightButton:(UIButton*)button withTag:(NSInteger)bgTag{
if(button == NULL){ //might be called with NULL argument
return;
}
[self addSubview:button];
}
/* input: The tag you chose to identify the view */
-(void)resetBackground:(NSInteger)bgTag {
[self sendSubviewToBack:[self viewWithTag:bgTag]];
}
#end
I used this Category in my ViewWillAppear methods like this:
-(void) viewWillAppear:(BOOL)animated {
UIImage *backgroundImage = [UIImage imageNamed:#"background_confernce_import_logo"];
if ([self.navigationController.navigationBar respondsToSelector:#selector(setBackgroundImage:forBarMetrics:)])
{
[self.navigationController.navigationBar setBackgroundImage:backgroundImage forBarMetrics:UIBarMetricsDefault];
}
else{
[[self.navigationController navigationBar] setBackgroundImage:backgroundImage withTag:8675309];
}
}
In else clause I call setBackgroundImage. It is ok, but the problem is that if I have a right button on navigation bar of page 1 for example and go to page 2 after come back to page 1 the button is disappear. I should change the background image of navigation bar in every page in my application like this in viewWillAppear method where I put the new image.
Any help will be appreciated. Under IOS 5 there are no such problem, but it should work on both versions.
I hate to say it, but your approach (adding a subview to hold the background) will not work exactly for the reason you mention. Each time the navigation bar is redrawn, the subview will not keep its z-order (and thus it will cover other UI elements). This behavior is described by other sources (see this, e.g.)
If you don't want to use swizzling, you could override drawRect in a category, so that the background is always drawn correctly. (this last option has the drawback that any navigation bar in your app will be drawn with the same background). This is a sample code I use:
#implementation UINavigationBar (CustomBackground)
- (void)drawRect:(CGRect)rect {
UIImage *image = [UIImage imageNamed: #"back.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
A different approach could be:
subclassing UINavigationBar;
overriding drawRect;
in Interface Builder, set the class of your navigation bar object to your UINavigationBar subclass.
I haven't tried it, but it should work.
As per request, here comes my slightly naughty and not really polished hack. This is really just for making the OP happy. The right answer was given by sergio.
UINavigationBar+CustomDraw.m
NSString *gNavbarBackgroundImageName = #"default_navbar_background.png";
#implementation UINavigationBar (CustomBackground)
- (void)drawRect:(CGRect)rect
{
if (gNavbarBackgroundImageName != nil)
{
UIImage *image = [UIImage imageNamed:gNavbarBackgroundImageName];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
}
#end
UINavigationBar+CustomDraw.h
extern NSString *gNavbarBackgroundImageName;
And here comes the example usage in two view controllers...
FooViewController.m
#import "UINavigationBar+CustomDraw.h"
#implementation FooViewController
- (void)viewWillAppear:(BOOL)animated
{
gNavbarBackgroundImageName = #"foo_navbar_background.png";
[self.navigationController.navigationBar setNeedsDisplay];
}
#end
BarViewController.m
#import "UINavigationBar+CustomDraw.h"
#implementation BarViewController
- (void)viewWillAppear:(BOOL)animated
{
gNavbarBackgroundImageName = #"bar_navbar_background.png";
[self.navigationController.navigationBar setNeedsDisplay];
}
#end
Now let us assume you want to show a non-styled navigation bar, for example when displaying a Facebook login page (as provided by their SDK).
Use this to prevent any custom drawing:
gNavbarBackgroundImageName = nil;
Note Some of Apple's components use the UINavigationBar at places you might not have thought of. For example, the MPMoviePlayerController uses a custom navigation bar for displaying the upper part of its UI - so that would be another case where you want to prevent custom drawing.
Related
When you create a UINavigationController, you can reveal its default hidden UIToolbar via setToolbarHidden:animated: (or by checking Shows Toolbar in Interface Builder). This causes a toolbar to appear at the bottom of the screen, and this toolbar persists between pushing and popping of view controllers on the navigation stack. That is exactly what I need, except I need the toolbar to be located at the top of the screen. It appears that's exactly what Apple has done with the iTunes app:
How can one move UINavigationController's toolbar to the top to lie underneath the navigation bar instead of at the bottom?
I've tried to implement the UIToolbarDelegate, override positionForBar:, and return UIBarPosition.TopAttached or UIBarPosition.Top after setting the delegate of self.navigationController?.toolbar to self, but this did not even call the delegate method therefore it didn't change the bar position.
Note that I need the toolbar to be preserved between navigation, so I can't simply add a toolbar to a view controller and position it under the nav bar.
The solution for this problem is a two (and a half) step process:
First you have to add an observer to the toolbars 'center' member.
Second, inside your observeValueForKeyPath:ofObject:change:context:, relocate the toolbar to your target position every time it is moved by somebody (e.g. the navigation controller itself for example, when the device rotates).
I did this in my UINavigationController subclass.
To avoid recursion, I've installed an local flag member 'inToolbarFrameChange'.
The last (half) step was a bit tricky to find out... you've to access the toolbars 'frame' member, to get the observer to be called at all... I guess, the reason for this might be, that 'frame' is implemented as an method inside UIToolbar and the base 'frame' value in UIView is only updated when the UIToolbar method is called ?!?
I did implement this 'frame' access in my overloaded setToolbarHidden:animated: method, which does nothing but to forward the call and to access the toolbars 'frame' value.
#interface MMMasterNavigationController ()
#property (assign, nonatomic) BOOL inToolbarFrameChange;
#end
#implementation MMMasterNavigationController
/*
awakeFromNib
*/
- (void)awakeFromNib {
[super awakeFromNib];
// ... other inits
self.inToolbarFrameChange = NO;
}
/*
viewDidLoad
*/
- (void)viewDidLoad {
[super viewDidLoad];
// 'center' instead of 'frame' from: http://stackoverflow.com/a/17977278/2778898
[self.toolbar addObserver:self
forKeyPath:#"center"
options:NSKeyValueObservingOptionNew
context:0];
}
/*
observeValueForKeyPath:ofObject:change:context:
*/
- (void)observeValueForKeyPath:(NSString *)pKeyPath
ofObject:(id)pObject
change:(NSDictionary<NSString *,id> *)pChange
context:(void *)pContext {
if ([pKeyPath isEqualToString:#"center"]) {
if (!self.inToolbarFrameChange) {
//NSLog(#"%s (0): %#", __PRETTY_FUNCTION__, pChange);
self.inToolbarFrameChange = YES;
CGRect tbFrame = self.toolbar.frame;
// maybe some other values are needed here for you
tbFrame = CGRectMake(0, 0, CGRectGetWidth(tbFrame), CGRectGetHeight(tbFrame));
self.toolbar.frame = tbFrame;
self.inToolbarFrameChange = NO;
}
} else {
[super observeValueForKeyPath:pKeyPath ofObject:pObject change:pChange context:pContext];
}
}
/*
setToolbarHidden:animated:
*/
- (void)setToolbarHidden:(BOOL)pHidden
animated:(BOOL)pAnimated {
FLog;
[super setToolbarHidden:pHidden animated:NO];
// Access the 'frame' member to let to observer fire
CGRect rectTB = self.toolbar.frame;
rectTB = CGRectZero;
}
You may create not UITableViewController but UIViewController. In view of UIViewController place UIToolBar below NavigationBar and UITableView. Delegate all necessary list of UITableView to UIViewController and thats all.
Why should you use UIViewController instead UITableViewController? Because tableView will not have statical positions elements. You should have something that not contains ScrollView. In this situation it is only UIView. Also you may do some hack with UIScrollView of tableView but I think described method is easer.
I need to change the color of my navigation bar on only some specific views.
There are many discussions about modifying the color of navigation bar, like https://stackoverflow.com/a/18870519/938380, but they all change the color of navigation bar on every views under the same navigation hierarchy.
I want to change the color on specific views and keep other views the same color. How do I achieve this?
For Swift 4 use
override func willMove(toParentViewController parent: UIViewController?) {
self.navigationController?.navigationBar.barTintColor = .red
}
It gives a cleaner animation
if you use standart navigation bar, you can't do it. But you can use some cheat ;) For example, you can add this code(or something like this) to your controller:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
oldColor = self.navigationController.navigationBar.backgroundColor;//probably barTintColor instead of backgroundColor
self.navigationController.navigationBar.backgroundColor = [UIColor yellowColor];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
self.navigationController.navigationBar.backgroundColor = oldColor;
}
You Can Set Bar Tint Property OF navigationController
I hope this one help you
[self.navigationController.navigationBar setBarTintColor:[UIColor redColor]];
I handle this kind of thing through an extension to UIViewController. In my case I want to make selected navigation bars transparent. You could extend this to deal with colors. This saves pasting the same code in multiple controllers. Code below.
From viewWillAppear you call [self makeNavigationBarTransparent]. From viewWillDisappear you call [self restoreNavigationBar]
For your case you would simply extend this to add makeNavigationBarColored.
An option is to also sub class UINavigationController to create a class which has a particular color. In its implementation use this extension to set the color. In your storyboard make any controllers you want this color be of your new class type. Then you are doing all this from storyboard.
UIViewController+TransparentNavigationBar.h
#import <UIKit/UIKit.h>
#interface UIViewController (TransparentNavigationBar)
/** Makes the current navigation bar transparent and returns a context holding
* the original settings.
*/
- (void) makeNavigationBarTransparent;
/**
* Restores the current navigation bar to its original settings.
*/
- (void) restoreNavigationBar;
#end
UIViewController+TransparentNavigationController.m
#import "UIViewController+TransparentNavigationBar.h"
#import <objc/runtime.h>
#interface VSSNavigationBarContext:NSObject
/** Backup of nav bar image used to restore on push. */
#property UIImage *originalNavBarBackgroundImage;
/** Backup of nav bar shadow image used to restore on push. */
#property UIImage *originalNavBarShadowImage;
/** Backup of nav bar color used to restore on push. */
#property UIColor *originalNavBarColour;
#end
#implementation VSSNavigationBarContext
- (id) init {
self=[super init];
if (self){
self.originalNavBarBackgroundImage=nil;
self.originalNavBarShadowImage=nil;
self.originalNavBarColour=nil;
}
return self;
}
#end
static char const * const ObjectTagKey = "NavBarContextTag";
#implementation UIViewController (TransparentNavigationBar)
- (VSSNavigationBarContext *) getContext
{
VSSNavigationBarContext *context=(VSSNavigationBarContext *)objc_getAssociatedObject(self, &ObjectTagKey);
return context;
}
- (void) makeNavigationBarTransparent{
VSSNavigationBarContext *context=(VSSNavigationBarContext *)objc_getAssociatedObject(self, &ObjectTagKey);
if (context == nil){
context=[[VSSNavigationBarContext alloc] init];
context.originalNavBarBackgroundImage=[self.navigationController.navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault];
context.originalNavBarShadowImage=self.navigationController.navigationBar.shadowImage;
context.originalNavBarColour=self.navigationController.view.backgroundColor;
// Store the original settings
objc_setAssociatedObject(self, &ObjectTagKey, context, OBJC_ASSOCIATION_RETAIN);
}
//
// Make transparent
//
[self.navigationController.navigationBar setBackgroundImage:[UIImage new]
forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.shadowImage = [UIImage new];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f){
self.navigationController.navigationBar.translucent = YES;
}
else{
self.navigationController.navigationBar.translucent = NO;
}
self.navigationController.view.backgroundColor = [UIColor clearColor];
}
- (void) restoreNavigationBar
{
VSSNavigationBarContext *context=(VSSNavigationBarContext *)objc_getAssociatedObject(self, &ObjectTagKey);
if (context != nil){
// Restore original
[self.navigationController.navigationBar setBackgroundImage:context.originalNavBarBackgroundImage forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.shadowImage = context.originalNavBarShadowImage;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f){
self.navigationController.navigationBar.translucent = YES;
}
else{
self.navigationController.navigationBar.translucent = NO;
}
self.navigationController.view.backgroundColor = context.originalNavBarColour;
}
}
#end
I have a scrollViewController, in viewDidLoad, I add an UIImageView to it:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.scrollView addSubview:self.imageView];
}
Then I set my image:
- (void)setImage:(UIImage *)image
{
self.scrollView.zoomScale = 1.0;
self.imageView.image = image;
self.imageView.frame = CGRectMake(0,0,image.size.width,image.size.height);
self.scrollView.contentSize = self.image ? self.image.size : CGSizeZero;
}
On iPhone, this works fine, but on iPad (SplitViewController Detail), it doesn't show anything.
I think the problem is, that on iPhone ViewDidLoad is called when there is already an image set, on iPad when the app launches, the detail is always on screen.
I tried to put the addSubview to setImage, this works, but when the user clicks another item, the two imageViewControllers overlay each other.
Could anyone help me? Thanks! :-)
The basics:
viewDidLoad is only called once the controller is loaded into memory. So in your SplitViewController the viewDidLoad method is invoked immediately after launching the app.
My suggestion:
Add a UIImageView from InterfaceBuilder and connect it to your controller using a IBOutlet. This is the easiest way to reach the goal. I assume you're using a storyboard?!
right click drag the outlet to your header:
enter a name for your property and click connect
This way you can access your UIImageView in your implementation and you're good to go. No need to add it programmatically.
For further informations have a look at this tutorial: http://klanguedoc.hubpages.com/hub/IOS-5-A-Beginners-Guide-to-Storyboard-Connection
Before adding second imageViewController, you should remove first imageViewController
Try this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//_imageView variable is created by outlet, or you can do it by programmatically
_imageView.image = [UIImage imageNamed:#"1"];
}
- (void) setNewImage
{
[_imageView removeFromSuperview];
_imageView.image = [UIImage imageNamed:#"2"];
}
Basically what I'm trying to achieve is to have my scope bar to never disappear.
Environment : IOS 7, storyboard, inside a view controller I have a "search bar and search display controller" and a separate tableview (the searchbar is not inside the table)
Inside the view controller.h
#property (nonatomic, strong) IBOutlet UISearchBar *candySearchBar;
Inside the view controller.m
#synthesize candySearchBar;
What I tried : inside a custom search bar class
- (void) setShowsScopeBar:(BOOL) showsScopeBar
{
if ([self showsScopeBar] != showsScopeBar) {
[super invalidateIntrinsicContentSize];
}
[super setShowsScopeBar:showsScopeBar];
[super setShowsScopeBar: YES]; // always show!
NSLog(#"setShowsScopeBar searchbar");
NSLog(#"%hhd", showsScopeBar);
}
and
searchBarDidEndEditing
Same thing in the view controller, but then
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[candySearchBar setShowsScopeBar:YES];
[candySearchBar sizeToFit];
}
I hope my question is clear, I tried many solutions posted all over the internet, most of them talk about the setshowsscopebar, but it doesn't seem to work. The output of the log in setshowscopebar is 1, but the scopebar is still not shown.
I still consider myself to be new to the code, the fault can still be a newbie mistake.
edit : another piece of code in the view controller, as you can see i'm searching blind:
-(void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller{
self.searchDisplayController.searchBar.showsCancelButton = YES;
self.searchDisplayController.searchBar.showsScopeBar = YES;
controller.searchBar.showsScopeBar = TRUE;
controller.searchBar.frame = CGRectMake(0, 149, 768, 88);
UIButton *cancelButton;
UIView *topView = self.searchDisplayController.searchBar.subviews[0];
for (UIView *subView in topView.subviews) {
if ([subView isKindOfClass:NSClassFromString(#"UINavigationButton")]) {
cancelButton = (UIButton*)subView;
}
}
if (cancelButton) {
//Set the new title of the cancel button
[cancelButton setTitle:#"Cancel" forState:UIControlStateNormal];
[cancelButton setEnabled:YES];
controller.searchBar.showsScopeBar = YES;
//candySearchBar.scopeButtonTitles = [NSArray arrayWithObjects:#"Flags", #"Listeners", #"Stations", nil];
}
NSLog(#"%#",NSStringFromCGRect(controller.searchBar.frame));
NSLog(#"%#",NSStringFromCGRect(controller.searchBar.bounds));
NSLog(#"%hhd#",controller.searchBar.hidden);
}
The code you tried will not work in iOS7 onward because apple has changed it behavior of UISearchBar to hide the scope when return to normal view. Add this method to your custom searchBar class.
-(void)layoutSubviews
{
[super layoutSubviews];
if([[UIDevice currentDevice].systemVersion floatValue]>=7.0) {
//Get search bar with scope bar to reappear after search keyboard is dismissed
[[[[self.subviews objectAtIndex:0] subviews] objectAtIndex:0] setHidden:NO];
[self setShowsScopeBar:YES];
}
}
Directly accessing object at index may crash the app in iOS6 because of difference in view hierarchy between iOS6 and iOS7, to avoid this, add this inside if condition only when its iOS7.
In addition this is also required in the custom search bar class
-(void) setShowsScopeBar:(BOOL)showsScopeBar {
[super setShowsScopeBar:YES]; //Initially make search bar appear with scope bar
}
I have the same issue. Perhaps it is something that has changed in iOS7 since showing the scope bar is supposed to be the default behaviour. You can verify this in the section "Creating an Optional Scope Bar to Filter Results" of the following tutorial:
http://www.raywenderlich.com/16873/how-to-add-search-into-a-table-view
Hopefully someone has a solution for this; otherwise we will have to look for a workaround.
initialize set scope bar NO
[self.searchBar setShowsScopeBar:NO];
[self.searchBar sizeToFit];
//default scope bar selection
self.searchBar.selectedScopeButtonIndex=3;
unselect/remove tick from scopeBar checkbox
It's possible (but hacky) to do this without a custom searchBar, in a pretty similar way to what CoolMonster suggests.
In your TableViewController, this will show the ScopeBar after a search ends:
- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller
{
//Show the scopeBars
controller.searchBar.showsScopeBar = YES;
//Resize the searchBar to show ScopeBar
controller.searchBar.frame = CGRectMake(0, 0, 320, 88);
if([[UIDevice currentDevice].systemVersion floatValue]>=7.0) {
[[[[controller.searchBar.subviews objectAtIndex:0] subviews] objectAtIndex:0] setHidden:NO];
}
}
Then, since you probably want it to appear before you search, add this line to the TableViewController's viewDidLoad:
[self searchDisplayControllerDidEndSearch:self.searchDisplayController];
For the record, after getting this to work, I ended up using a separate segmented control instead of the approach above for several reasons, not least of which was that touching the ScopeBar of a SearchBar, once you get it to display, launches the search display tableView, which makes of sense if you're using it the recommended way. However, since I wanted the ScopeBar to work without launching the search tableview, for me it made more sense just to use my own segmented control and add it to my tableHeaderView under the searchBar.
I'm trying to get a custom tab bar for iPhone (iOS 6) and I've got to manage a central button that raises over the bar (based on code, https://github.com/tciuro/CustomTabBar) but now I have to face another feature: buttons must blink when clicked and glossy effect has to be removed. Any suggestion about the best way to get that? I'm still relatively new programming with iOS and its animations.
Thank you very much
What I already have so far:
In MBCenteredButtonVC (main entry in storyboard)
#import <UIKit/UIKit.h>
#interface MBCenteredButtonViewController : UITabBarController <UITabBarDelegate>
#property(nonatomic, weak) IBOutlet UIButton *centerButton;
#end
And its implementation:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tabBar setSelectedImageTintColor:[UIColor colorWithRed:171/225.0 green:233/255.0 blue:8/255.0 alpha:1]];
[self.tabBar setBackgroundImage:[UIImage imageNamed:#"bar-back.png"]];
// Do any additional setup after loading the view.
[self addCenterButtonWithImage:[UIImage imageNamed:#"button-rocketBg.png"] highlightImage:[UIImage imageNamed:#"button-rocketBg-active.png"] target:self action:#selector(buttonPressed:)];
}
Images for each item is defined within views properties using XCode. So, this way, I get a central button raised over the rest and I have changed the color for selected items but I need that they blink while content is being loading (it supposed that could take some time).
I feel that I have to implement this functionality when buttons are pressed:
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
NSLog(#"Selected tab bar item: %i", item.tag);
}
}
but not sure if it the right way and how to do it exactly.
The easiest way to get started with animations on iOS is probably to use the UIView animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations method.
Documentation here: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW111
Any values that are animatable that you change in that block will be animated.
Core Animation is extensive, and this barely scratches the surface of what can be done, but it might be enough to get you started.