iPad uiview frame changes right after initWithFrame - iphone

Okay so i have a very basic app with a view in it with one button. i have the controller set to only allow landscape. My problem is that after it is initialized, and then i click my button (which only has a log statement) , is different than the log statements i have at the end of my init.
I start the app in landscape mode on my simulator (same results on device though). Its like once i assign it , it just switches back. I tried this statement self.frame = CGRectMake(0, 0, 1024, 768);in my buttonClicked method, but that just distorted and shifted it.
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
//BUTTONS
attributesButton = [UIButton buttonWithType:UIButtonTypeCustom];
attributesButton.frame = CGRectMake(self.frame.size.width - buttonPadding - 35, self.frame.size.height/2 -22 -150, 35, 35);
[attributesButton setBackgroundImage:[UIImage imageNamed:#"loadIcon.png"] forState:UIControlStateNormal];
[attributesButton addTarget:self action:#selector(attributesButtonClicked)
forControlEvents:UIControlEventTouchUpInside];
[attributesButton setTitle:#"Attr" forState:UIControlStateNormal];
[self addSubview:attributesButton];
NSLog(#"Width: %f",self.frame.size.width);
NSLog(#"Height: %f",self.frame.size.height);
}
return self;
}
-(void)attributesButtonClicked
{
NSLog(#"Width: %f",self.frame.size.width);
NSLog(#"Height: %f",self.frame.size.height);
}
So that is my init. Sorry it looks so terrible im not sure why. My view controller:
- (void)loadView
{
NSLog(#"myViewController: loadView");
myView = [[myView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
self.view = myView;
}
Now this is the part that gets me, the log statements.
2010-08-27 15:16:55.242 tester[8703:40b] myViewController: loadView
2010-08-27 15:16:55.262 tester[8703:40b] Width: 1024.000000
2010-08-27 15:16:55.262 tester[8703:40b] Height: 768.000000
CLICK MY BUTTON HERE
2010-08-27 15:17:05.689 tester[8703:40b] Width: 748.000000
2010-08-27 15:17:05.689 tester[8703:40b] Height: 1024.000000

From what I've experienced, it seems like even though you are in landscape mode, if you're using autoresizing masks, you need to use a "portrait" frame when initializing your view using initWithFrame:
Try:
myView = [[myView alloc] initWithFrame:CGRectMake(0, 0, 768, 1024)];

If your view is tied to one of the standard view controllers (UINavigationController, UITabBarController, ...), the controller will update the frame size at runtime, no matter what you specify in initWithFrame.
That's actually a good thing, because you don't have to worry about orientation, toolbars taking up space, etc.

that's probably because the view is being autoresized by the parent view when you add it as a subview.
If you don't want that to happen (though you'd usually do for a fullscreen view) you can set the autoresizingMask to UIViewAutoresizingNone:
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
...
self.autoresizingMask = UIViewAutoresizingNone;
}
return self;
}
EDIT: ok, sorry, I just remembered that UIViewAutoresizingNone is actually the default value for autoresizingMask, so setting it to that value during initialization won't actually change anything. But the point is that the frame is changed by the superview when you add your view as a subview.

Related

UIBarButtonItem with separate portrait and landscape images - layoutSubviews not called when popping a view controller from UINavigationController

I want to show completely custom buttons in UINavigationController's UIToolbar, and support portrait and landscape. Currently I have implemented a RotatingButton (a UIView subclass) class, which contains one UIButton that fills the whole RotatingButton frame. A RotatingButton also contains two images, for portrait and landscape orientations, and the heights of these images differ. Then this RotatingButton gets wrapped into UIBarButtonItem as a custom view.
Currently, in RotatingButton's layoutSubviews, I am setting the whole view's bounds, and setting the button the appropriate image for the current orientation. This works well and handles rotations as desired.
- (void) createLayout {
[self addButtonIfNeeded];
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
if(UIInterfaceOrientationIsLandscape(currentOrientation)) {
[self.button setImage:self.landscapeImage forState:UIControlStateNormal];
self.button.frame = CGRectMake(0.0, 0.0, self.landscapeImage.size.width / 2, self.landscapeImage.size.height / 2);
self.bounds = CGRectMake(0.0, 0.0, self.landscapeImage.size.width / 2, self.landscapeImage.size.height / 2);
} else {
[self.button setImage:self.portraitImage forState:UIControlStateNormal];
self.button.frame = CGRectMake(0.0, 0.0, self.portraitImage.size.width / 2, self.portraitImage.size.height / 2);
self.bounds = CGRectMake(0.0, 0.0, self.portraitImage.size.width / 2, self.portraitImage.size.height / 2);
}
}
- (void) layoutSubviews {
[super layoutSubviews];
[self createLayout];
}
However, this problem remains:
Starting view at portrait orientation
Push a view controller onto stack
Rotate the device to landscape (the current view reacts appropriately)
Pop the last view controller: the previous view reacts otherwise well, but the RotatingButtons' layoutSubviews don't get called, and the buttons remain larger than they should.
So, currently after popping a view controller, the previous UIBarButtonItems don't have their layoutSubviews called, and they remain too large (or too small, if we start from landscape and rotate to portrait in another view). How to solve this problem?
This is a really tricky question. You should try overriding viewWillAppear: to call [self.view setNeedsLayout] to force a layout update whenever the view is about to appear.
I didn't find a completely satisfactory solution, but my buttons happened to be of suitable size, and this kind of a solution worked very well for me:
UIBarButtonItem* b = [[UIBarButtonItem alloc] initWithTitle:#"" style:UIBarButtonItemStylePlain target:target action:selector];
UIImage *barButton = [portraitImage resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
UIImage *barButton_land = [landscapeImage resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)];
[b setBackgroundImage:barButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[b setBackgroundImage:barButton_land forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
And then obviously adding the created button as rightBarButtonItem/leftBarButtonItem, or as you may want to use it.
The problem with this is that if your buttons are not wide enough, the buttons may look completely wrong (since the middle content of the image is tiled in this solution).

Need UIView to autoresize

I have made a custom UIView which is shown when the user hits a button in the navigationbar. I make my view's in code. In my loadview I set the autoresizing masks and the view loads correct on screen. However the UIView which is shown when the user taps the button does not resize even when I have set the autoresizing masks.
UIView *blackView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 416.0)];
blackView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Do I need to use self.view.frame.size.width and self.view.frame.size.height instead? And if I do why? Does not resizing masks work outside of loadView?
Thank you for your time:)
the autoresizingMask affects how a view will behave when its superviews frame changes. if all you are doing is showing theblackViewwhen you tap a button, thenblackView` will have whatever frame you initially set for it.
If this isn't enough info, please post some more code around how you are configuring and displaying blackView and it's superview and explain more about what situations you are expecting blackView to resize in. Rotation is one of them, if that's what you're concerned with.
First things first, I hope you've done this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
Let's say the view that needs resizing is: view2
The view that has view2 as a subview is: view1
While creating view1 you would declare it as:
view1 = [[UIView alloc] init];
[view1 setNeedsLayout];
Now in view1's .m file you need to overload the layoutSubviews method as shown:
- (void)layoutSubviews
{
CGRect frame = view2.frame;
// apply changes to frame
view2.frame = frame;
}
In case view1 is a view controller's view, you need to do that same thing as above in the willRotate method as shown
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
CGRect frame = view2.frame;
// apply changes to frame
view2.frame = frame;
}
This is a tried and tested method that I use to handle orientation changes.

iOS willRotateToInterfaceOrientation proper usage

I have a very simply UIViewController, and I'm trying to figure out how to use willRotateToInterfaceOrientation. my UIViewController has a very simple viewDidLoad method:
-(void)viewDidLoad {
[super viewDidLoad];
theBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 48.0f)];
theBar.tintColor = [UIColor orangeColor];
UINavigationItem *item = [[UINavigationItem alloc] initWithTitle:#"The Title"];
item.hidesBackButton = YES;
[theBar pushNavigationItem:item animated:YES];
[item release];
[self.view addSubview:theBar];
}
So basically, I just have a UINavigationBar at the top of my controller. That's it. I implemented some methods for rotation, based on what I found online:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
-(void)willRotateToInterfaceOrientation: (UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {
if ((orientation == UIInterfaceOrientationLandscapeLeft) || (orientation == UIInterfaceOrientationLandscapeRight)) {
theBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, 640, 48)];
}
}
So, I launch the app in portrait mode, and then I twist in in landscape mode. And basically, theBar still stays it's normal size, and doesn't get resized. I'm sure this is a silly question, but what is the proper way to use the rotation capability? I want to make it so that it also works if the app is launched in landscape mode. What is the best way to initialize my components when the UIViewController first launches, keeping in mind that I want support for both orientations, and also keeping in mind that I want to be able to change the size of everything based on orientation changes throughout the duration of the life of the UIViewController? Thanks!
What you want to do is change the frame of your existing theBar object, and not instantiate a new one. You can do that with something like this:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation
duration:(NSTimeInterval)duration {
CGRect f = CGRectMake(0,
0,
CGRectGetWidth(self.view.frame),
CGRectGetHeight(theBar.frame);
theBar.frame = f;
}
Note that the value of self.view.frame is used, which contains values post rotation. Also note that the function I'm using here is different than yours. I haven't tested it with the function you're using, so I can't say if that'll work or not. Finally, you can avoid this altogether by just setting the autoresizingmask on theBar in viewDidLoad instead:
[theBar setAutoresizingMask:UIViewAutoresizingFlexibleRightMargin];

iOS: navigation bar's titleView doesn't resize correctly when phone rotates

I'm writing an iPhone app that (like most apps) supports auto-rotation: You rotate your phone, and its views rotate and resize appropriately.
But I am assigning a custom view to navigationItem.titleView (the title area of the navigation bar), and I can't get that view to resize correctly when the phone rotates.
I know what you're thinking, "Just set its autoresizingMask to UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight," but it's not that simple. Of course, if I don't set my view's autoresizingMask, then my view doesn't resize; and I want it to resize.
The problem is, if I do set its autoresizingMask, then it resizes correctly as long as that view is visible; but the titleView's size gets messed up in this scenario:
Run the app, with the phone held in portrait mode. Everything looks good.
Do something that causes the app to push another view onto the navigation stack. E.g. click a table row or button that causes a call to [self.navigationController pushViewController:someOtherViewController animated:YES].
While viewing the child controller, rotate the phone to landscape.
Click the "Back" button to return to the top-level view. At this point, the title view is messed up: Although you are holding the phone in landscape mode, the title view is still sized as if you were holding it in portrait mode.
Finally, rotate the phone back to portrait mode. Now things get even worse: The title view shrinks in size (since the navigation bar got smaller), but since it was already too small, now it is much too small.
If you want to reproduce this yourself, follow these steps (this is a bit of work):
Make an app using Xcode's "Navigation-based Application" wizard.
Set it up so that the top-level table view has rows that, when you click them, push a detail view onto the navigation stack.
Include this code in both the top-level view controller and the detail view controller:
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
Include this code in only the top-level view controller:
- (void)viewDidLoad {
[super viewDidLoad];
// Create "Back" button
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Master"
style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem = backButton;
[backButton release];
// Create title view
UILabel* titleView = [[[UILabel alloc] initWithFrame:CGRectMake(0,0,500,38)] autorelease];
titleView.textAlignment = UITextAlignmentCenter;
titleView.text = #"Watch this title view";
// If I leave the following line turned on, then resizing of the title view
// messes up if I:
//
// 1. Start at the master view (which uses this title view) in portrait
// 2. Navigate to the detail view
// 3. Rotate the phone to landscape
// 4. Navigate back to the master view
// 5. Rotate the phone back to portrait
//
// On the other hand, if I remove the following line, then I get a different
// problem: The title view doesn't resize as I want it to when I:
//
// 1. Start at the master view (which uses this title view) in portrait
// 2. Rotate the phone to landscape
titleView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.navigationItem.titleView = titleView;
}
Finally, follow my repro steps.
So ... am I doing something wrong? Is there a way to make my titleView always resize correctly?
You should also set the contentMode of the UIImageView to get the titleView properly displayed in landscape and/or portrait mode :
imgView.contentMode=UIViewContentModeScaleAspectFit;
The whole sequence: (self is a UIViewController instance)
UIImageView* imgView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"myCustomTitle.png"]];
imgView.autoresizingMask=UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
imgView.contentMode=UIViewContentModeScaleAspectFit;
self.navigationItem.titleView = imgView;
[imgView release];
I had something similar - but it was returning (popping) to root view controller. Ultimately, I went with the following for popping:
[[self navigationController] setNavigationBarHidden:YES animated:NO];
[[self navigationController] popViewControllerAnimated:YES];
[[self navigationController] setNavigationBarHidden:NO animated:NO];
And it worked. There may have been a better way but - after all the hours I'd already spent on this issue - this was good enough for me.
I dealt with this same issue by keeping track of the customView's initial frame, then toggling between that and a scaled CGRect of the initial frame in a -setLandscape method on a UIButton subclass. I used the UIButton subclass as navigationItem.titleView and navigationItem.rightBarButtonItem.
In UIButton subclass -
- (void)setLandscape:(BOOL)value
{
isLandscape = value;
CGFloat navbarPortraitHeight = 44;
CGFloat navbarLandscapeHeight = 32;
CGRect initialFrame = // your initial frame
CGFloat scaleFactor = floorf((navbarLandscapeHeight/navbarPortraitHeight) * 100) / 100;
if (isLandscape) {
self.frame = CGRectApplyAffineTransform(initialFrame, CGAffineTransformMakeScale(scaleFactor, scaleFactor));
} else {
self.frame = initialFrame;
}
}
Then in the InterfaceOrientation delegates I invoked the -setLandscape method on the customViews to change their sizes.
In UIViewController -
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self updateNavbarButtonsToDeviceOrientation];;
}
- (void)updateNavbarButtonsToDeviceOrientation
{
ResizeButton *rightButton = (ResizeButton *)self.navigationItem.rightBarButtonItem.customView;
ResizeButton *titleView = (ResizeButton *)self.navigationItem.titleView;
if (self.interfaceOrientation == UIDeviceOrientationPortrait || self.interfaceOrientation == UIDeviceOrientationPortraitUpsideDown) {
[rightButton setLandscape:NO];
[titleView setLandscape:NO];
} else {
[rightButton setLandscape:YES];
[titleView setLandscape:YES];
}
}
(Answering my own question)
I got this working by manually keeping track of the titleView's margins (its distance from the edges of the navigtion bar) -- saving when the view disappears, and restoring when the view reappears.
The idea is, we aren't restoring the titleView to the exact size it had previously; rather, we are restoring it so that it has the same margins it had previously. That way, if the phone has rotated, the titleView will have a new, appropriate size.
Here is my code:
In my view controller's .h file:
#interface MyViewController ...
{
CGRect titleSuperviewBounds;
UIEdgeInsets titleViewMargins;
}
In my view controller's .m file:
/**
* Helper function: Given a parent view's bounds and a child view's frame,
* calculate the margins of the child view.
*/
- (UIEdgeInsets) calcMarginsFromParentBounds:(CGRect)parentBounds
childFrame:(CGRect)childFrame {
UIEdgeInsets margins;
margins.left = childFrame.origin.x;
margins.top = childFrame.origin.y;
margins.right = parentBounds.size.width -
(childFrame.origin.x + childFrame.size.width);
margins.bottom = parentBounds.size.height -
(childFrame.origin.y + childFrame.size.height);
return margins;
}
- (void)viewDidUnload {
[super viewDidUnload];
titleSuperviewBounds = CGRectZero;
titleViewMargins = UIEdgeInsetsZero;
}
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// Keep track of bounds information, so that if the user changes the
// phone's orientation while we are in a different view, then when we
// return to this view, we can fix the titleView's size.
titleSuperviewBounds = self.navigationItem.titleView.superview.bounds;
CGRect titleViewFrame = self.navigationItem.titleView.frame;
titleViewMargins = [self calcMarginsFromParentBounds:titleSuperviewBounds
childFrame:titleViewFrame];
}
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Check for the case where the user went into a different view, then
// changed the phone's orientation, then returned to this view. In that
// case, our titleView probably has the wrong size, and we need to fix it.
if (titleSuperviewBounds.size.width > 0) {
CGRect newSuperviewBounds =
self.navigationItem.titleView.superview.bounds;
if (newSuperviewBounds.size.width > 0 &&
!CGRectEqualToRect(titleSuperviewBounds, newSuperviewBounds))
{
CGRect newFrame = UIEdgeInsetsInsetRect(newSuperviewBounds,
titleViewMargins);
newFrame.size.height =
self.navigationItem.titleView.frame.size.height;
newFrame.origin.y = floor((newSuperviewBounds.size.height -
self.navigationItem.titleView.frame.size.height) / 2);
self.navigationItem.titleView.frame = newFrame;
}
}
}
For IOS5 onwards, as this is an old question...This is how I accomplished the same issue with the title text not aligning properly.
[[UINavigationBar appearance] setTitleVerticalPositionAdjustment:2 forBarMetrics:UIBarMetricsLandscapePhone];
Tested on ios5/6 sims works fine.
This is what I did:
self.viewTitle.frame = self.navigationController.navigationBar.frame;
self.navigationItem.titleView = self.viewTitle;
The viewTitle is a view created in the xib, it takes the size of the navigationBar and after it has been added the titleView adjust the size to leave room to the back button. Rotations seem to work fine.
I had had same problem, but I seem to get workaround with following code.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
UIView *urlField = self.navigationItem.leftBarButtonItem.customView;
CGRect frame = urlField.frame;
frame.size.width = 1000;
urlField.frame = frame;
}
In my case, the custom view is a UITextField, but I hope this will help you.

Why is my view controllers view not quadratic?

I created an UIViewController subclass, and figured out that the default implementation of -loadView in UIViewController will ignore my frame size settings in a strange way.
To simplify it and to make sure it's really not the fault of my code, I did a clean test with a plain instance of UIViewController directly, rather than making a subclass. The result is the same. I try to make an exactly quadratic view of 320 x 320, but the view appears like 320 x 200.
iPhone OS 3.0, please check this out:
UIViewController *ts = [[UIViewController alloc] initWithNibName:nil bundle:nil];
ts.view.frame = CGRectMake(0.0f, 0.0f, 320.0f, 320.0f);
ts.view.backgroundColor = [UIColor cyanColor];
[self.view addSubview:ts.view];
like you can see, I do this:
1) Create a UIViewController instance
2) Set the frame of the view to a quadratic dimension of 320 x 320
3) Give it a color, so I can see it
4) Added it as a subview.
Now the part, that's even more strange: When I make my own implementation of -loadView, i.e. if I put this code in there like this:
- (void)loadView {
UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 320.0f)];
v.backgroundColor = [UIColor cyanColor];
self.view = v;
[v release];
}
then it looks right.
Now lets think about that: In the first example, I do pretty much exactly the same, just that I let UIViewController create the view on it's own, and then take it over in order to change it's frame. Right?
So why do I get this strange error? Right now I see no other way of messing around like that to correct this wrong behavior. I did not activate anything like clipsToBounds and there's no other code touching this.
The dimensions of the view of a view controller should not be changed. It should be autoresized to fit the size of the window or the parent controller.
If you really need a square view, make a subview.
// Note: better do this in -loadView or -viewDidLoad.
UIView* container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 320)];
[ts.view addSubview:container];
[container release];
// add stuff into the container view.
// ...