Changing UINavigationBar when interface orientation changes - iphone

I have a NavigationController with customized UINavigationBar:
#implementation UINavigationBar (CustomImage)
- (void)drawRect:(CGRect)rect
{
UIImage *image = [UIImage imageNamed: #"banner.png"];
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
#end
I'm allowing rotation using shouldAutorotateToInterfaceOrientation.
When i'm rotating, i would like to change the banner.png with banner_port.png
How can i do it?

[UIDevice currentDevice].orientation will tell you which way the device thinks it is...
you can do an if or switch at that point on the value it gives you.

You can make your navigation bar object to listen global UIDeviceOrientationDidChangeNotification, which has property 'orientation', and make necessary changes, when event occurs.

See also Custom UINavigationBar Background
I agree wit with Dave DeLong - overriding drawRect isn't wise, it may break some day. Plus, your category's drawRect replaces the original drawRect -- you don't have the option of calling super to get default functionality.
Note that if you listen for changes to UIDeviceOrientationDidChangeNotification, you need to be aware that there are six device orientations (the usual four, plus face up and face down).
Poorly written code is often seen which doesn't acknowledge the possibility of face up and face down device orientations.
N.B. you can also access the user interface's current status bar orientation - which isn't necessarily the same as the UIDeviceOrientation!

Related

Issues setting up a back ground image and with UIImageView

On my iPhone app, I simply want to set a particular background image, which depends on whether it's an iPhone 5 or not.
So, I tried two approaches:
A) Using
self.view.backgroundColor = [UIColor colorWithPatternImage:backGroundimage];
B) Creating an UIImageView and setting up the image there. Code:
UIImageView *backgroundImageView = [[UIImageView alloc]initWithFrame:screenBounds];
[backgroundImageView setImage:[UIImage imageNamed:backGroundImage]];
[self.view addSubview:backgroundImageView];
But I am having issues with both of them:
Issues with Step A:
When I set the image through that way, I have to deal with the image scaling issues for different sizes of the screen. I use the following code to do the scalling:
UIGraphicsBeginImageContext(screenBounds.size);
[[UIImage imageNamed:backGroundImage] drawInRect:screenBounds];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.view.backgroundColor = [UIColor colorWithPatternImage:image];
Another issue from Step A is that the image appears quite blurry. It doesn't have the same sharpness to it.
Issues with Step B:
With this, the image looks really crisp and sharp - just the way it should look.
But when I switch to another view using the following code, strangely enough the UIImageView backgroundImageView still appears on the second one. The code I use to switch views is:
[self presentViewController:secondViewController animated:YES completion:nil];
I even tried [backgroundImageView removeFromSuperview], but that doesn't solve anything either.
So what am I doing wrong? And how can I set up a picture as my background which is dependent on the size of the iphone?
Plan B is a good plan. Presenting another view controller should and will definitely hide your image view. If it isn't happening, then it's a problem with the creation of secondViewController, unrelated to the background image on the presenting VC.
Before presenting secondViewController, log it:
NSLog(#"presenting %#", secondViewController);
I'll bet a dollar that it's nil. If I'm right, let's have a look at how you initialize secondViewController. That's the problem, unrelated to the background image view.
Okay, I finally fixed this issue, although the cause of this issue is still puzzling to me.
To fix this, I had to create an IBOutlet property for UIImageView and hook it up on the XIB file.
The reason I was programmatically creating the UIImageView is because the size of the UIImageView depends on what size iPhone they are using. But for the IBOutlet (let's call it as UIImageViewOutlet, I simply used [self.UIImageViewOutlet setFrame:] to get the size and location that I wanted.
I also discovered that one of the buttons that I was programmatically creating, was still visible in the secondViewController. I ended up creating an Outlet on the XIB file for that one as well and used setFrame on it to position it properly.
If anyone who knows the reason of this problem, I will be very grateful.

CATiledLayer to CALayer

I have a view with a CATiledLayer backing. I want to take the visible tiles from this CATiledLayer UIView and add it to another view as its CALayer backing, thus recreating the visible image in another UIView that doesnt use CATiledLayer.
The reason I want to do this is I will use this second UIView to mask the effect of updating the CATiledLayer backed UIView - this currently produces a flicker as all tiles are re-loaded.
The problem is, I'm not totally sure how i would do this. Any ideas?
CATiledLayer is a subclass of CALayer providing a way to
asynchronously provide tiles of the layer's content, potentially
cached at multiple levels of detail.
You can render the visible things in the layer into a CGContextRef with:
- (void)renderInContext:(CGContextRef)ctx
And then use this to update your other layer by settings its delegate and implementing the
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
ss shown here http://www.raywenderlich.com/2502/introduction-to-calayers-tutorial
But honestly I don't think this is efficient.
Your real problem here is the flickering. I had a similar problem on a view with a custom CALayer backing it, this was part of the solution:
Create a custom subclass of your CALayer and implement the following method:
- (void) display {
self.contents = nil;
[super display];
}
This fixed a set of problems for me, but may not fix it for you. The alternative for you may be to disable transactions while you update:
From CATransaction Class Reference
setDisableActions: Sets whether actions triggered as a result of
property changes made within this transaction group are suppressed.
So to use this:
[CATransaction begin];
[CATransaction setDisableActions:YES];
// do updating/flickering stuff
[self doFlickeringThing];
[CATransaction commit];

UINavigationBar change tint color in Category

I know I can change the tint color of navigation bar in xib file easily. But the problem is that if i have to change in a lot of places.
Hence i tried using the (fancy) category and overriding the draw rect method.
#import "UINavigationBar+customTint.h"
#implementation UINavigationBar (customTint)
-(void)drawRect:(CGRect)rect{
self.tintColor = [UIColor redColor];
}
#end
I have imported it in the corresponding view controller as well. What am i doing wrong?
Whats the right way of doing it?
THanks in advance
That is one way of doing it. It would probably just be easier in you ViewController where ever you need to change it add the line:
self.navigationController.navigationBar.tintColor = [UIColor redColor];
Using a category for just one line is probably overkill.
EDIT:
If you do want to use a category, you may need to call setNeedsDisplay on the navigation bar. or override another method and call it.
Something like ,
[self.navigationController.navigationBar setNeedsDisplay];
Also according to Apple's documentation
In iOS 5, the UINavigationBar, UIToolbar, and UITabBar implementations have changed so that the drawRect: method is not called unless it is implemented in a subclass. Apps that have re-implemented drawRect: in a category on any of these classes will find that the drawRect: method isn't called. UIKit does link-checking to keep the method from being called in apps linked before iOS 5 but does not support this design on iOS 5 or later.
Apps can either:
Use the customization API for bars in iOS 5 and later, which is the preferred way.
Subclass UINavigationBar (or the other bar classes) and override drawRect: in the subclass.
The best way to therefore go about this would be to place this in your ApplicationDidFinishLaunching
NSString *reqSysVer = #"5.0";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
if ([currSysVer compare:reqSysVer options:NSNumericSearch] != NSOrderedAscending)
[[UINavigationBar appearance] setTintColor:myColor];
And also leave your DrawRect, so that it will work on iOS less than 5
If you're using iOS5 then the correct way to do it is to use the UIAppearance proxy.
[[UINavigationBar appearance] setTintColor:myColor];
Details here: https://developer.apple.com/library/ios/#documentation/uikit/reference/UIAppearance_Protocol/Reference/Reference.html

Customized UINavigationbar in Universal App

I have a Universal App in which I customize my UINavigationBar.
In my iPhone AppDelegate I use this to achieve it:
#implementation UINavigationBar (CustomImage)
static NSMutableDictionary *navigationBarImages = NULL;
- (void)initImageDictionary
{
if(navigationBarImages==NULL){
navigationBarImages=[[NSMutableDictionary alloc] init];
}
}
- (void)drawRect:(CGRect)rect
{
NSLog(#"drawing navbar2");
UIImage *imageName=[navigationBarImages objectForKey:[NSValue valueWithNonretainedObject: self]];
if (imageName==nil) {
imageName=[UIImage imageNamed:#"bg_titleBar.png"];
UIImage *image = imageName;
[image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}
}
- (void)setMyImage:(UIImage*)image
{
[navigationBarImages setObject:image forKey:[NSValue valueWithNonretainedObject: self]];
[self setNeedsDisplay];
}
#end
Now my questions: why does this code get called, although I start the iPad simulator?
And more important it seems to corrupt the UIPopoverController because it looks like this:
http://awesome-apps.com/pic/ok.png
While it should look somehow like this:
http://awesome-apps.com/pic/nok.png
Besides it corrupts more in my App, but this should be it for starters :)
Can anyone help me with this? Have you ever had a similar experience?
So ignore the fact that it gets called when you run in the iPad simulator, because as you'll see in a minute even if you used two different categories (one for iPhone, one for iPad) you'd still have this problem.
Here's why:
You are using a category to override the UINavigationBar behaviour. I assume you know what that means - crucially any and all navigation bars in your app will use your supplied methods in the category.
This can cause problems if you're using standard apple elements that use UINavigationBars - the exact thing you're seeing in the popover controller. What's happening is the UIPopoverController uses a UINavigationBar. But because you've defined a category, the app assumes you want the popover navbar to use that category as well.
So that's why you're seeing your weird behaviour in your pop-over controller.
As long as you use categories you'll have this problem, because you can't selectively tell the system which bars should use your category.
I'd suggest you tell us exactly you're trying to customise in the navbar, because there are other ways to achieve customisation outside of categories.

Quartz -- sometimes it starts from a blank view and sometimes it doesn't

I have in front of me two Quartz iPhone apps. In each of them, calls to setNeedsDisplay cause a view to redraw itself. But there is an important difference. In one case (the "Quartz Fun" app from the Mark/Lamarche book "Beginning iPhone development"), the view starts out blank each time. In the other case (the app I am working on), the view starts with whatever was there before, and new graphics are added on top of it.
I can't figure out why this is. Can anyone clue me in?
Thanks.
EDIT #2: I still don't understand what is going on here. But I have figured out that I can clear my view by calling
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, self.frame);
EDIT #3: showing shortened code:
As a suggested by a commenter, I have edited my app so that the issue occurs with very little code. [The form of the issue is a bit different now, as explained below.]
App delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
DiceView *dv = [[DiceView alloc]initWithFrame: window.frame];
[window addSubview:dv];
[dv release];
[window makeKeyAndVisible];
return YES;
}
DiceView:
- (void)drawRect:(CGRect)rect {
static int nDrawrectCalls = 0;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColorWithColor(context, (nDrawrectCalls%5==0?[UIColor redColor]:[UIColor greenColor]).CGColor);
CGContextMoveToPoint(context, 10, 30+10*nDrawrectCalls);
CGContextAddLineToPoint(context, 300, 30+10*nDrawrectCalls);
CGContextStrokePath(context);
nDrawrectCalls++;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self setNeedsDisplay];
}
Everything else is just default stub methods.
Now for the difference. It now appears to start drawing with whatever was on the screen two touches prior. In other words, after touch #2, I see the initial line, plus the line from touch #2 -- but not the line from touch #1. After touch #3, I see the lines from touches #1 and #3. After touch #4, I see the initial line and the lines from touches #2 and #4. And so on.
UIView has a clearsContextBeforeDrawing boolean property that switches between the different behaviors you describe. The default is YES which means the view empties the context before calling drawRect:
Check if that property is set somewhere in the example.
Apparently a view doesn't clear it's context correctly, when no backgroundColor is set on the view. Please set backgroundColor to something other than nil.
I think this is a bug and have filed rdar://8165730.
EDIT
It's not a bug. You have to set the opaque property to NO or the backgroundColor. The behavior is described in the UIView Documentation.
Property clearsContextBeforeDrawing:
The default value of this property is
YES. When set to YES, the current
graphics context buffer in the
drawRect: method is automatically
cleared to transparent black before
drawRect: is invoked. If the view’s
opaque property is also set to YES,
the backgroundColor property of the
view must not be nil or drawing errors
may occur.
If the value of this property is NO,
it is the view’s responsibility to
completely fill its content. Drawing
performance can be improved if this
property is NO—for example, when
scrolling.
Property opaque:
YES if it is opaque; otherwise, NO. If
opaque, the drawing operation assumes
that the view fills its bounds and can
draw more efficiently. The results are
unpredictable if opaque and the view
doesn’t fill its bounds. Set this
property to NO if the view is fully or
partially transparent. The default
value is YES.