Wrong positioning after orientation change on iPad - objective-c

Having some problems when changing the orientation of my iPadd app.
I wrote a function that lays out my subviews so I can call it when the view appears or is rotated.
The function works perfectly when it is called by the "viewWillAppear" function. But when the "willAnimateRotationToInterfaceOrientation" function calls my layout function ("setPositionsForOrientation") I'm facing following issues:
When changing from portrait to landscape I have a 128px offset on the left side
When changing from landscape to portrait I have a negative offset on the left side
I have the impression, that somehow the new frame-properties are not handled correctly, as my UIScrollViews should also resize, but they don't do it.
That's the code I wrote:
- (void)viewWillAppear:(BOOL)animated{
[self setPositionsForOrientation:self.interfaceOrientation];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)newInterfaceOrientation duration:(NSTimeInterval)duration {
[self setPositionsForOrientation:newInterfaceOrientation];
}
- (void)setPositionsForOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Position the UI elements for landscape mode
if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationLandscapeRight) {
self.view.frame = CGRectMake(0, 0, 1024, 748);
self.menubarImage.frame = CGRectMake(0, 0, 1024, 121);
self.backgroundImage.frame = CGRectMake(0, 0, 1024, 748);
self.mainCategories.frame = CGRectMake(6, 6, 1012, 55);
self.subCategories.frame = CGRectMake(58, 79, 960, 30);
self.moviesContainer.frame = CGRectMake(6, 204, 1012, 456);
self.searchButton.frame = CGRectMake(935, 709, 80, 30);
}
// Position the UI elements for portrait mode
else {
self.view.frame = CGRectMake(0, 0, 768, 1004);
self.menubarImage.frame = CGRectMake(0, 256, 768, 121);
self.toolbarImage.frame = CGRectMake(0, 924, 768, 80);
self.backgroundImage.frame = CGRectMake(0, 256, 768, 748);
self.mainCategories.frame = CGRectMake(6, 262, 756, 55);
self.subCategories.frame = CGRectMake(58, 335, 704, 30);
self.moviesContainer.frame = CGRectMake(6, 460, 756, 456);
self.searchButton.frame = CGRectMake(680, 963, 80, 30);
}
}
And here are some pictures to illustrate my problem..
Layout in IB (All subviews have contentMode set to Top-Left)
http://www.abload.de/image.php?img=interfacebuilderl9ey.png
Portrait mode shown correctly / strange
http://www.abload.de/image.php?img=portrait_oklxf1.png
http://www.abload.de/image.php?img=portrait_failma8y.png
Landscape mode shown correctly / strange
http://www.abload.de/image.php?img=landscape_ok4z2l.png
http://www.abload.de/image.php?img=landscape_failvzuy.png
What have I done wrong and more important, how can I fix it?
I found this post which describes the usual way, but I don't get how I can override my views "layoutSubviews" method, as the view is just a property of my UIViewController..

If you want to create you own subclass of UIView and set it to UIViewController so you can implement layoutSubviews method, is very easy actually!.
UIViewController does not creates a view it expects the user to set the view but when using Interface BUilder there is a UIView already set for us.
To change the view do:
Create your subclass, here I will assume it is called MyView, and put there all your UI stuff (toolbars, buttons, etc).
Open your view controller file in IB (the *.xib)
Select the view (the view that we are changing)
Go to the Identity Inspector (or press Cmd+4)
Enter "MyView" in the class identity field
Since you put all your UI elements in the view as ivar you cannot access them from your UIViewController anymore right? So, make sure you have the correct accessors/properties and methods to handle your view (and its ivars) from UIViewController. ;)
Good Luck ;)
EDIT:2011/01/17
When you are in your controller and do [self.view xyz] you get the warning right?
That is because a UIViewController's view is declared to be an UIView object. And you subclassed UIView and set it in IB. So everytime you access the view you have to cast it or the compiler will think it is still the normal UIView which does not have the methods you have declared in MyView. Do like this:
[(MyView *)self.view xyz]; //and for that don't forget to #import "MyView.h"
This should work. But I don't understand why then got: "property 'view' type does not match super class 'UIViewController' property type" First try what I just said ;) Are you sure you Declared MyView as:
#interface MyView: UIView{
...
}
#property (...) XYZ *xyz;
...
#end

I added following code in the MyView class in the .m file and it helped me out of my misery:
- (void)awakeFromNib
{
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}

Related

iOS 8 Form Sheet Modal Dialog Resizing After Display

I work on an application where it shows a modal popup by performing a segue using the form sheet modal style and then a horizontal flip animation. In iOS 7 made the form take up the correct screen size by calling:
self.view.superview.bounds = CGRectMake(0,0,300,300);
for example and this set the modal dialogs dimensions to the correct size. There is also a button in this modal dialog that allows the user to see 'more detail' and for this the view grows to show some additional content. We did this again by animating the view and make a call to set the bounds to something a little bigger on the superview.
For Example:
self.view.superview.bounds = CGRectMake(0,0,600,600);
In iOS 7 this worked just great. In iOS 8 however it has stopped working. Instead of modifying the super view's bounds we now have to call:
self.preferredContentSize = CGSizeMake(300, 300);
This resolves the forms appearance after opening but i can't find a way to manipulate the size of the view there after. Setting the super views bounds has no effect.
I can see in iOS8 Apple has made a change to form sheets to have a 3 view hierarchy (your view -> shadow view -> transition view -> window) where as before in iOS 7 it was just a 2 view hierarchy (your view -> shadow view -> window).
I've tried manipulating the super view's super view but that did not have the desired effect.
Is anyone else seeing this / found a way around it? Here is an example project:
https://github.com/ingybing/FormSheet
Run it in iOS 7 Simulator and you will see the desired behaviour. That is what I'm struggling to get working in iOS 8
View Controller Example:
#import "ModalWindowViewController.h"
#interface ModalWindowViewController ()
#property bool minimised;
#property bool isInitialViewLoadLayout;
#end
#define is_iOS8 ([[[UIDevice currentDevice] systemVersion]floatValue] >= 8)
#implementation ModalWindowViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.minimised = YES;
self.isInitialViewLoadLayout = YES;
if(is_iOS8)
{
// Set initial size in iOS 8 etc.
self.preferredContentSize = CGSizeMake(300, 300);
}
}
- (void) viewWillLayoutSubviews
{
if (self.isInitialViewLoadLayout)
{
self.isInitialViewLoadLayout = NO;
if(!is_iOS8)
{
// Set initial size in iOS 6,7 etc.
self.view.superview.bounds = CGRectMake(0, 0, 300, 300);
}
}
}
- (IBAction)minMaxTouched:(id)sender
{
CGRect newSize = CGRectMake(0, 0, 300, 300);
if (self.minimised == YES)
{
newSize = CGRectMake(0, 0, 600, 600);
}
if(is_iOS8)
{
// Doesn't Work
// self.view.superview.bounds = newSize;
// Doesn't seem to do anything either.
self.preferredContentSize = newSize.size;
}
else
{
// Works a treat on ios7
self.view.superview.bounds = newSize;
}
self.minimised = !self.minimised;
}
- (IBAction)closeTouched:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
After much messing around.. if you update the view.superview's bounds to something and then call layoutIfNeeded on the superview. This will cause the willLayoutSubviews method to be invoked. If you set the superview bounds in there, it will update the modal dialog size but not if you do it anywhere else.
Example view controller solution:
#import "ModalWindowViewController.h"
#interface ModalWindowViewController ()
#property bool minimised;
#property CGRect windowBounds;
#end
#define is_iOS8 ([[[UIDevice currentDevice] systemVersion]floatValue] >= 8)
#implementation ModalWindowViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Start with a minimised window size.
self.minimised = YES;
self.windowBounds = CGRectMake(0, 0, 300, 300);
if(is_iOS8)
{
// Set initial size in iOS 8 etc.
self.preferredContentSize = self.windowBounds.size;
}
}
- (void) viewWillLayoutSubviews
{
// Resize the window based on it's current required size.
// The supeview bounds only seems to actually changed on screen
// if changed inside this method.
self.view.superview.bounds = self.windowBounds;
}
- (IBAction)minMaxTouched:(id)sender
{
// Toggle the dimensions of the window bases on minimised / maximised state.
if (self.minimised == YES)
{
self.windowBounds = CGRectMake(0, 0, 600, 600);
}
else
{
self.windowBounds = CGRectMake(0, 0, 300, 300);
}
// Set some value that will get overridden in viewWillLayoutSubviews
// You need to change the view bounds or it won't actually invoke a layout.
self.view.superview.bounds = CGRectMake(0, 0, 100, 100);
// Toggle minimised state before we layout.
self.minimised = !self.minimised;
// Manually request a layout. Since the superview bounds have been changed
// to a temporary value it should cause a layout where we set the real
// desired size.
[self.view.superview layoutIfNeeded];
}
- (IBAction)closeTouched:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
#end

How to make NSView not clip its bounding area?

I created an empty Cocoa app on Xcode for OS X, and added:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.view = [[NSView alloc] initWithFrame:NSMakeRect(100, 100, 200, 200)];
self.view.wantsLayer = YES;
self.view.layer = [CALayer layer];
self.view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
self.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
self.view.layer.transform = CATransform3DMakeRotation(30 * M_PI / 180, 1, 1, 1);
[self.window.contentView addSubview:self.view];
}
But the rotated layer's background is clipped by the view's bounding area:
I thought since some version of OS X and iOS, the view won't clip the content of its subviews and will show everything inside and outside? On iOS, I do see that behavior, but I wonder why it shows up like that and how to make everything show? (I am already using the most current Xcode 4.4.1 on Mountain Lion).
(note: if you try the code above, you will need to link to Quartz Core, and possibly import the quartz core header, although I wonder why I didn't import the header and it still compiles perfectly)
It turns out that if the line:
((NSView *)self.window.contentView).wantsLayer = YES;
is added to the very beginning, then it works as expected:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
((NSView *)self.window.contentView).wantsLayer = YES;
self.view = [[NSView alloc] initWithFrame:NSMakeRect(200, 200, 200, 200)];
self.view.wantsLayer = YES;
self.view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
[self.window.contentView addSubview:self.view];
self.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
self.view.layer.transform = CATransform3DMakeRotation(30 * M_PI / 180, 0, 0, 1);
}
So it looks like if all the views are made to be layer backed, then it works the same as it does on iOS. (If there is a quick way to make all views layer backed automatically, that'd be good).
the anchorPoint line cannot be moved before addSubview line, or else it is incorrect, although I wonder why that would make any difference.
The line self.view.layer = [CALayer layer]; can be removed if window.contentView is layer backed. Both the contentView and self.view don't need to set the layer, and I wonder why too.
The transform line cannot be before the addSubview line, or else it won't rotate, and I wonder why too.
The third thing is that, I thought if I go to Interface Builder and make the contentView a class of ContentView (subclassing NSView), and in its init method, do a self.wantsLayer = YES;, then it would work too, but it does not.
But anyway, the code above works, and I will update the reasons above why when I find out more.

UIView has portrait dimensions while in landscape?

I have a storyboard with 1 UIViewController, holding 1 UIView that contains a number of nested UIViews. I subclassed the View Controller to implement this method:
- (BOOL)shouldAutorotateToInterfaceOrientation: UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationLandscapeRight || interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}
I also added
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIInterfaceOrientation</key>
<string>UIInterfaceOrientationLandscapeLeft</string>
to the Info.plist.
In the viewDidLoad of the main UIView I'm doing this:
PASectionView* sectionView = [[PASectionView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 180)];
[self addSubview:sectionView];
The problem is the control is only 756px wide instead of the expected 1024. See the screenshot below for details. I've been searching all over the web but I can't find a solution to this frustrating problem anywhere. I'm using Xcode 4.5 with iOS5.1 set as base SDK.
EDIT
It's working by replacing frame with bounds. However I don't understand what's happening so it isn't working with the frame size.
The frame rectangle, which describes the view’s location and size in
its superview’s coordinate system.
#property(nonatomic) CGRect frame
and
The bounds rectangle, which describes the view’s location and size in
its own coordinate system.
#property(nonatomic) CGRect bounds
Use bounds, not frame.
Set the autoresizingMask to whatever views you want to autoresize on rotate. Like this:
[myView setAutoresizingMask:UIViewAutoresizingMaskFlexibleWidth | UIViewAutoresizingMaskFlexibleRightMargin];
The problem is because before loading into Landscape mode your lines of code is calling the method loading
in which method are you calling this [PASectionView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 180)];
[self addSubview:sectionView]; if it is view controller class then it should be [PASectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 180)];
[self addSubview:sectionView]; if it is subclass of UIView then where did you get the ViewDidLoad method.
Now to solve your problem
in .h class:
write this
PASectionView* sectionView;
in .m class implement this method
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
if (toInterfaceOrientation == UIInterfaceOrientationPortrait)
{
}
else // OrientationLandscape
{
sectionView.frame = CGRect(sectionView.frame.origin.x,sectionView.frame.origin.y,self.view.frame.size.width,180);
}
You have to set the Auto resizing mask for your subview,
Add this line of code before adding the subView to the superview, if your subView is added by code.
yourSubView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
else if your subView added in nib file, select the border magnets and resizing masks in the properties of your subView in the nib file.

UIScrollView, paging and rotation: Second view is not aligned properly after rotation

I am using a UIScrollView with Paging enabled and the following code to add subviews (core plot charts) to it.
The horizontal scrolling between the views works properly.
However, when showing the second view and then rotating from landscape to portrait mode, the second view is shifted partly to the right and a portion of the first view's right hand side is shown on the left side, hence "destroying" the paging mode.
Could you help me with these issue please? I tried many alternatives, but can't find my bug. Thank you so much!
This is how my iPad screen looks after rotating to portrait mode with the second view:
:
This is my viewDidLoad method:
- (void)viewDidLoad {
self.scrollView.contentSize = CGSizeMake(768 * 2, 400);
chart1 = [[CPTGraphHostingView alloc]initWithFrame:CGRectMake(0, 0, 768, 400)];
chart2 = [[CPTGraphHostingView alloc]initWithFrame:CGRectMake(768, 0, 768, 400)];
self.scrollView.autoresizesSubviews = NO;
chart1.autoresizesSubviews = YES;
chart2.autoresizesSubviews = YES;
self.scrollView.contentOffset = CGPointZero;
self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth ;
chart1.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth ;
chart2.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth ;
[self.scrollView addSubview:chart1];
[self.scrollView addSubview:chart2];
}
This is how I have implemented rotation:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
if (UIDeviceOrientationIsPortrait(fromInterfaceOrientation)) {
self.scrollView.contentSize = CGSizeMake(704 * 2, 400);
chart1.frame = CGRectMake(0, 0, 704, 400);
chart2.frame = CGRectMake(704, 0, 704, 400);
}
else {
self.scrollView.contentSize = CGSizeMake(768 * 2, 400);
chart1.frame = CGRectMake(0, 0, 768, 400);
chart2.frame = CGRectMake(768, 0, 768, 400);
}
}
I think you should change the contentOffset of the scrollview when rotation is taking place.
You should have a way to know which page is currently displayed before rotation (maybe put this information in a variable). Then in your didRotate.. method set the contentOffset of the scrollview after resizing it, like this:
CGFloat offset = self.scrollView.frame.size.width * currentPageIndex;
[self.scrollView setContentOffset: offset];
As an alternative to laying out your subviews in your view controller, have you considered subclassing your UIScrollView and overriding it's layoutSubviews method? You might also consider defining your dimensions as percentages rather than fixed points - because the point values will shift according to rotation and presence of other UI elements such as navigation and toolbars. You may run into trouble as you're manually resizing UI elements in your rotation method, at the same time that the UI is going to be attempting to automatically resize elements according to your resizing masks. Just my thought...

Center UIPageControl after orientation change

I'm trying to centralize a UIPageControl in portrait and landscape modes, but it isn't working, the x changes after device rotation.
#interface DetailViewController : UIViewController<UIScrollViewDelegate>
{
UIPageControl *pageControl;
}
- (void)viewDidLoad
{
pageControl = [[UIPageControl alloc] init] ;
[self renderMyView];
[self.view addSubview:pageControl];
}
- (void)renderMyView
{
if(isPortrait)
{
pageControl.frame = CGRectMake(200, 976, 0, 0);
} else {
pageControl.frame = CGRectMake(200, 720, 0, 0);
}
}
The renderMyView is executed on didLoad and didRotate.
At first time viewDidLoad works well in portrait and landscape, but if I rotate the device the pageControl appears in a different x != 200.
I've also tried pageControl.center, but it didn't work.
How can I keep it centralized?
1) In viewDidLoad call renderMyView.
2) After device has rotated don't call method renderMyView
3) Replace your renderMyView with this one:
- (void)renderMyView
{
if(isPortrait)
{
pageControl.frame = CGRectMake(200, 976, 0, 0);
} else {
pageControl.frame = CGRectMake(200, 720, 0, 0);
}
pageControl.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin;
}
So, don't call my renderMyView more then one time. AutoresizingMask will do all you need.
Tell me, please, if it works for you.
PS: For more information about the autoresizingMask, read the UIView Documentation.
probably a very old question, but I'll share my way to center the UIPageControl. Just set the width of the UIPageControl to be the same width as the view's bounds size width. So, In portrait mode it will be 768 and in the landscape it will be 1024. It works for me.
Set the UIView autoresizingMask in code:
pageControl.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin;
Or you can also do this in interface builder.