Custom UINavigationBar is offset by 1 pixel at the top - iphone

I have a subclassed UINavigationBar where I'm overriding drawRect to provide a png with transparency as a background. Every thing works as expected, save for a 1 pixel space at the top of the bar (I can see the underlying map moving in the space).
screen shot
Only thing I was able to find is this question which sounds like my problem but I don't know what to make of the explanation: Empty space of 1 pixel above UINavigationBar
I have verified that the PNG file does not have 1 pixel of transparency at the top of the image.
Overriding in subclassed UINavigationBar:
- (void)drawRect:(CGRect)rect {
[_bg drawInRect:CGRectMake(0, 0, _bg.size.width, _bg.size.height)];
// showing correct bounds - drawRect: 0.000000, 0.000000, 320.000000, 85.000000
NSLog(#"drawRect: %f, %f, %f, %f", rect.origin.x,
rect.origin.y,
rect.size.width,
rect.size.height);
}
- (CGSize)sizeThatFits:(CGSize)size {
CGRect frame = [[UIScreen mainScreen] applicationFrame];
CGSize sz = CGSizeMake(frame.size.width, _bg.size.height);
NSLog(#"sizefits");
return sz;
}
Thanks for any help!

I had the same problem with my custom nav bar, although I was using UIAppearance proxies to set a custom background image instead of overriding drawRect:. This was my quick fix, in viewWillAppear: on the root view controller:
// Make sure nav bar is flush with status bar (iOS 5 iPhone portrait somehow gives status bar height 20 and nav bar y 20.5, so we miss a pixel).
CGRect navBarFrame = self.navigationController.navigationBar.frame;
navBarFrame.origin.y = [UIApplication sharedApplication].statusBarFrame.size.height;
self.navigationController.navigationBar.frame = navBarFrame;

Related

Scrolling, zooming UIScrollView and interface orientation rotation. How to use autoresize and more

This should be a pretty common thing to do, but I haven't been able to get it to work exactly right.
I have rectangular content. It normally fits in 320x361: portrait mode minus status bar minus ad minus tab bar.
I have put that content in a UIScrollView and enabled zooming. I also want interface rotation to work. The content will always be a tall rectangle, but when zoomed users might want to see more width at a time and less height.
What do I need to do in Interface Builder and code to get this done? How should I set my autoresizing on the different views? How do I set my contentSize and contentInsets?
I have tried a ton of different ways and nothing works exactly right. In various of my solutions, I've had problems with after some combination of zooming, interface rotation, and maybe scrolling, it's no longer possible to scroll to the entire content on the screen. Before you can see the edge of the content, the scroll view springs you back.
The way I'm doing it now is about 80% right. That is, out of 10 things it should do, it does 8 of them. The two things it does wrong are:
When zoomed in portrait mode, you can scroll past the edge of the content, and see a black background. That's not too much to complain about. At least you can see all the content. In landscape mode, zoomed or not, seeing the black background past the edge is normal, since the content doesn't have enough width to fill the screen at 1:1 zoom level (the minimum).
I am still getting content stuck off the edge when it runs on a test device running iOS 3.0, but it works on mine running 4.x. -- Actually that was with the previous solution. My tester hasn't tried the latest solution.
Here is the solution I'm currently using. To summarize, I have made the scroll view as wide and tall as it needs to be for either orientation, since I've found resizing it either manually or automatically adds complexity and is fragile.
View hierarchy:
view
scrollView
scrollableArea
content
ad
view is 320x411 and has all the autoresizing options on, so conforms to screen shape
scrollView is 480 x 361, starts at origin -80,0, and locks to top only and disables stretching
scrollableArea is 480 x 361 and locks to left and top. Since scrollView disables stretching, the autoresizing masks for its subviews don't matter, but I tell you anyway.
content is 320x361, starts at origin 80,0, and locks to top
I am setting scrollView.contentSize to 480x361.
shouldAutorotateToInterfaceOrientation supports all orientations except portrait upside down.
In didRotateFromInterfaceOrientation, I am setting a bottom content inset of 160 if the orientation is landscape, resetting to 0 if not. I am setting left and right indicator insets of 80 each if the orientation is portrait, resetting if not.
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 2.0
viewForZoomingInScrollView returns scrollableArea
// in IB it would be all options activated
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
scrollView.contentSize = content.frame.size; // or bounds, try both
what do you mean with scrollableArea?
your minZoomScale is set to 1.0 thats fine for portrait mode but not for landscape. Because in landscape your height is smaller than in portrait you need to have a value smaller than 1.0. For me I use this implementation and call it every time, the frame of the scrollView did change:
- (void)setMaxMinZoomScalesForCurrentBounds {
CGSize boundsSize = self.bounds.size; // self is a UIScrollView here
CGSize contentSize = content.bounds.size;
CGFloat xScale = boundsSize.width / contentSize.width;
CGFloat yScale = boundsSize.height / contentSize.height;
CGFloat minScale = MIN(xScale, yScale);
if (self.zoomScale < minScale) {
[self setZoomScale:minScale animated:NO];
}
if (minScale<self.maximumZoomScale) self.minimumZoomScale = minScale;
//[self setZoomScale:minScale animated:YES];
}
- (void)setFrame:(CGRect)rect { // again, this class is a UIScrollView
[super setFrame:rect];
[self setMaxMinZoomScalesForCurrentBounds];
}
I don't think I understood the entire problem from your post, but here's an answer for what I did understand.
As far as I know (and worked with UIScrollView), the content inside a UIScrollView is not automatically autoresized along with the UIScrollView.
Consider the UIScrollView as a window/portal to another universe where your content is. When autoresizing the UIScrollView, you are only changing the shape/size of the viewing window... not the size of the content in the other universe.
However, if needed you can intercept the rotation event and manually change your content too (with animation so that it looks good).
For a correct autoresize, you should change the contentSize for the scrollView (so that it knows the size of your universe) but also change the size of UIView. I think this is why you were able to scroll and get that black content. Maybe you just updated the contentSize, but now the actuall content views.
Personally, I haven't encountered any case that required to resize the content along with the UIScrollView, but I hope this will get you started in the right direction.
If I understand correctly is that you want a scrollview with an image on it. It needs to be fullscreen to start with and you need to be able to zoom in. On top of that you want it to be able to rotate according to orientation.
Well I've been prototyping with this in the past and if all of the above is correct the following code should work for you.
I left a bit of a white area for the bars/custombars.
- (void)viewDidLoad
{
[super viewDidLoad];
//first inits and allocs
scrollView2 = [[UIScrollView alloc] initWithFrame:self.view.frame];
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"someImageName"]];
[scrollView2 addSubview:imageView];
[self drawContent]; //refreshing the content
[self.view addSubview:scrollView2];
}
-(void)drawContent
{
//this refreshes the screen to the right sizes and zoomscales.
[scrollView2 setBackgroundColor:[UIColor blackColor]];
[scrollView2 setCanCancelContentTouches:NO];
scrollView2.clipsToBounds = YES;
[scrollView2 setDelegate:self];
scrollView2.indicatorStyle = UIScrollViewIndicatorStyleWhite;
[scrollView2 setContentSize:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)];
[scrollView2 setScrollEnabled:YES];
float minZoomScale;
float zoomHeight = imageView.frame.size.height / scrollView2.frame.size.height;
float zoomWidth = imageView.frame.size.width / scrollView2.frame.size.width;
if(zoomWidth > zoomHeight)
{
minZoomScale = 1.0 / zoomWidth;
}
else
{
minZoomScale = 1.0 / zoomHeight;
}
[scrollView2 setMinimumZoomScale:minZoomScale];
[scrollView2 setMaximumZoomScale:7.5];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
if (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
// Portrait
//the 88pxls is the white area that is left for the navbar etc.
self.scrollView2.frame = CGRectMake(0, 88, [UIScreen mainScreen].bounds.size.width, self.view.frame.size.height - 88);
[self drawContent];
}
else {
// Landscape
//the 88pxls is the white area that is left for the navbar etc.
self.scrollView2.frame = CGRectMake(0, 88, [UIScreen mainScreen].bounds.size.height, self.view.frame.size.width);
[self drawContent];
}
return YES;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
I hope this will fix your troubles. If not leave a comment.
When you want to put a content (a UIView instance, let's call it theViewInstance ) in a UIScrollView and then scroll / zoom on theViewInstance , the way to do it is :
theViewInstance should be added as the subview of the UIScrollView
set a delegate to the UIScrollView instance and implement the selector to return the view that should be used for zooming / scrolling:
-(UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return theViewInstance;
}
Set the contentSize of the UIScrollView to the frame of the theViewInstance by default:
scrollView.contentSize=theViewInstance.frame.size;
(Additionally, the accepted zoom levels can be set in the UIScrollView :)
scrollView.minimumZoomScale=1.0;
scrollView.maximumZoomScale=3.0;
This is the way a pinch to zoom is achieved on a UIImage : a UIImageView is added to a UIScrollView and in the UIScrollViewDelegate implementation, the UIImageView is returned (as described here for instance).
For the rotation support, this is done in the UIViewController whose UIView contains the UIScrollView we just talked about.

Autosizing doesn't work when loading from XIB after rotating

In my app I add a view loaded from XIB with UIViewAutosizingMaskFlexibleWidth and have the desired result only in one of two cases:
Adding subview on portrait, and rotating to landscape, resizes the subview correctly;
Adding subview on landscape do not cause the subview to resize its width the way it does when rotating;
Here is the code:
viewMenuList = [[MenuListController alloc] initWithNibName:#"MenuListController" bundle:[NSBundle mainBundle]];
viewMenuList.view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.view.autoresizesSubviews = YES;
[self.view addSubview:viewMenuList.view];
[self.view sendSubviewToBack:viewMenuList.view];
[viewMenuList.view setNeedsLayout];
[viewMenuList.view setNeedsDisplay];
NSLog(#"frame do menu links view %f, %f, %f, %f", self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
NSLog(#"frame do menu list view %f, %f, %f, %f", viewMenuList.view.frame.origin.x, viewMenuList.view.frame.origin.y, viewMenuList.view.frame.size.width, viewMenuList.view.frame.size.height);
The output in the console is:
2010-12-07 16:34:22.552 SlideShow_iPad[19352:207] frame do menu links view 0.000000, 0.000000, 1030.000000, 111.000000
2010-12-07 16:34:22.596 SlideShow_iPad[19352:207] frame do menu list view 0.000000, 0.000000, 768.000000, 109.000000
The last line should display an width of 1030, shouldnt it? It does resize to 1030 after rotating to portrait and going back to landscape.
The strangest thing is that I do use the same technic in the superview and worked.
You shouldn't mix both Interface Builder and Objective-C code to handle resizing. Remove autoresizingMask and autoresizesSubviews from code and do both only in Interface Builder. You might have conflicting defines now.
autoresizingMark: command-3 to view size properties. Check "Autosizing" part, the animation shows pretty nicely how your resizing will look.
autoresizesSubviews: command-1 to view drawing properties. There's a checkbox for this.

From willAnimateRotationToInterfaceOrientation how can I get the size the view will be after rotation?

I am trying to resize the objects in a UIView when the device is rotated without hard coding the width and height. In this code how would I get the newWidth and newHeight?
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
child.frame = CGRectMake(10, 10, newWidth - 20, newHeight - 20);
}
This would be about right.
- (void)willAnimateRotationToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration
{
// we grab the screen frame first off; these are always
// in portrait mode
CGRect bounds = [[UIScreen mainScreen] applicationFrame];
CGSize size = bounds.size;
// let's figure out if width/height must be swapped
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
// we're going to landscape, which means we gotta swap them
size.width = bounds.size.height;
size.height = bounds.size.width;
}
// size is now the width and height that we will have after the rotation
NSLog(#"size: w:%f h:%f", size.width, size.height);
}
If possible, you're better either:
Subclassing UIView and doing the layout you need inside -(void)layoutSubviews, or;
Making use of autoresizingMask to automatically layout your views.
It might be best to create a separate view altogether and call that view in the did rotate method.
You can set the Autosizing properties of the view from the interface builder. That will resize your view when rotation happens. But there may be problem that some of the views might not fit properly.

Resizing iPhone keywindow's main view

Edit 2: When I start the app without the status bar on top everything behaves as planned. With the status bar I couldn't get the views to act as I wanted. It looks as if the UINavigationController keeps resizing the content view by subtracting the 20 pixels of the status bar. I don't know.
I created a simple UINavigationController-based application. The root view in this navigation controller is a UITableView. At a certain time I want to slide in a 80 pixel high view from the bottom. The whole view on the top (the one that is controlled by the UINavigationController) should resize and get 80 pixel smaller to make room for the new bottom view.
This is basically the code I use to repositioning the views:
-(void)showTeaser {
float adHeight = 80;
[adView setFrame:CGRectMake(0.0,self.navigationController.view.bounds.size.height, 320.0, 80.0)];
[[[UIApplication sharedApplication] keyWindow] addSubview:adView];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[adView setAlpha:1.0];
[adView setFrame:CGRectMake(0.0,self.navigationController.view.bounds.size.height-adHeight, 320.0, 80.0)];
[self.navigationController.view setFrame:CGRectMake(0.0,0.0, 320.0, self.navigationController.view.bounds.size.height-adHeight)];
[self.view setFrame:CGRectMake(0.0, 0, 320.0, self.view.bounds.size.height-adHeight)];
[UIView commitAnimations]; }
I lowered the Navigationbar's alpha, set the UITableviewController's view to red. The new view is purple.
This is what happens. First screenshot initial state. Everything is looking normal. Second screenshot shows state after changing the frames. The view of the UITableviewController is always pushed 20 pixel under the Navigationbar. Also, if I try to add more views to the keywindow, they always end up 20 pixel higher than I expect. It almost looks like the keywindow (minus the navigation bar) is pushed up 20 pixel.
Edit 1: No matter to what size I resize the view, it's always 20 pixel.
Do I make a mistake by adding views to the keywindow at all? Shouldn't I do this?
alt text http://www.hans-schneider.de/iphone-seo/1.png alt text http://www.hans-schneider.de/iphone-seo/2.png
To solve this, I made the view of the UINavigationController a subview of a UIView, and manually set the bounds of the view for the `UINavigationController'.
//outerView is a UIView defined in the interface
outerView = [[UIView alloc] initWithFrame:CGRectMake( 0.0, 0.0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height);];
//mainNavigationController is a UINavigationController defined in the interface
//rootViewController is a UIViewController (or inherited class) defined in the interface and instanced before this code
mainNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
//set the frame for the view of the navigation controller - 20 is due to the status bar
mainNavigationController.view.frame = CGRectMake( 0.0, 20.0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 20);
Then later, when I go to resize, I resize the parent 'UIView' rather than the 'UINavigationController' view.
//change the outer view's frame to resize everything
//adHeight is a float defined earlier
outerView.frame = CGRectMake( 0.0, 20.0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 20 - adHeight);
This works well in an animation sequence.
Edit 2011-05-12: I updated the outerView frame to fill the screen. This must be set to allow for touch events.
Have you tried using the transform property of your tableview instead of manually changing it's frame? It may work out better, since the frame depends on the origin, and you only want to change it's size.

Problem when set NavigationBar to "Black Translucent" style

I'm developing an app in objective-c and in that app set the navigationBar to translucent(through IB). But the problem is that the view displayed behind the navigation bar.
Anybody else tried working with translucent navigation bars?
regards
Jayaraj
The [navigationController view] automatically resizes to "underlap" translucent navigation bars as of OS 3.0
You can simply add 44 pixels to the y value of the origin property to overcome this.
You can use 44 if you know you're in portrait (not landscape, in which the height of the navBar is less than 44)
You can also do:
// applicationFrame subtracts the height of the statusBar if it's visible.
// bounds doesn't take into account the statusBar.
CGRect navFrame = [[UIScreen mainScreen] applicationFrame];
NSLog(#"navFrame: %f x %f", navFrame.size.width, navFrame.size.height);
navFrame.size.height -= self.navigationController.navigationBar.frame.size.height;
NSLog(#"navFrame: %f x %f", navFrame.size.width, navFrame.size.height);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:navFrame];
NSLog(#"imageView: %#", imageView);
I learned this from reading three20's source code. You can find it on github.com
Matt