UIView addsubview after orientation change: Tell view to resize - iphone

I have a UIView as a XIB in Portrait mode.
This view is added programmatically to the viewcontroller like this:
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"InputView" owner:self options:nil];
InputView *inputView = (InputView*)[nibObjects objectAtIndex:0];
[self.view addSubview:inputView];
This view has autoresizing masks set up properly and rotates fine when the orientation changes from Portrait to landscape.
However, if the orientation is already landscape and I create the view after the orientation change, it has its initial portrait orientation.
Is there a way to tell the view to initialize or resize itself to portrait by using its masks?
Thanks in advance for any reply!
EDIT:
Using the suggestions of occulus and Inder Kumar Rathore (thanks guys!), I altered the code to this:
InputView *inputView = (InputView*)[nibObjects objectAtIndex:0];
[self.view addSubview:inputView];
[self.view setNeedsLayout];
[self.view layoutSubviews];
[self.view layoutIfNeeded];
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
[self shouldAutorotateToInterfaceOrientation:orientation];
Unfortunately, there is no change at all.
I think I found someone asking the same question:
When adding a sub view the view does not resize if the app is in landscape mode
The answer identifies the problem correctly, but is not very encouraging...
Sure, I could create two nibs or resize the frame, but this seems so contrary to the idea of auto-resizing.
I find it hard to believe that there is no way to tell a nib after awakening and adding it to a view to use its autoresize features...something that works flawless when the device rotates.
EDIT 2:
The solution of idz works:
InputView *inputView = (InputView*)[nibObjects objectAtIndex:0];
[self.view addSubview:inputView];
inputView.frame = self.view.bounds;
[inputView show];
Thanks!

Often a NIB/XIB file contains a UIViewController that takes care of all of this. In this case, since their is no view controller (in the NIB/XIB) you need to take over its post-load duties.
Calling layoutSubviews directly, or indirectly via setNeedsLayout or layoutIfNeeded won't do you much good because the default implementation does nothing.
Assuming you want input view to fill the bounds of self.view you do the following:
InputView *inputView = (InputView*)[nibObjects objectAtIndex:0];
[self.view addSubview:inputView];
inputView.frame = self.view.bounds;
[inputView show];
All the resize masks of the sub-views must be correctly set for this to work and, obviously, if you don't want to fill the full bounds you may want to adjust the frame.

[self.view addSubview:viewSpinner];
viewSpinner.frame = self.view.frame;
[viewSpinner setNeedsLayout];
This works for me (Y)

I don't know if you still have this issue.
Let's say you have the following architecture:
window
subviewcontroller
(you implemented shouldautorotate correct to answering the wanted orientations)
Into this subviewcontroller you want to add the views of new UIViewControllers by just calling the addSubview function.
Instead of implementing the bounds manipulation in shouldautorotate, you should implement it in
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
self.newUIViewController.view.frame = self.view.bounds;
}
didRotateFromInterface... is called after shouldRotate. In this function the bounds are already setup correctly.
This way you don't need so much manipulation by code.

See this related question:
When do autoresizing masks take effect in iOS?
So after loading your new view from the nib, and adding as a subview to self.view, try calling setNeedsLayout.

Related

What exactly is done to a UIView on interface rotation?

could someone explain what iOS does on rotation of the interface. I´ve got a layout problem with one View that is gone after rotating the iPhone. Seems that the View got set a new frame, bounds or whatever, don´t know. Anyhow after the interface was rotated once the layoutproblem is gone forever. So something must be set to the view at the time the interface rotates.
I´m loading the View from a NIB file and show it with a navigationcontroller:
BirthdayReminderWidgetConfigViewController *vc = [self.storyboard
instantiateViewControllerWithIdentifier:#"BirthdayConfigController"];
self.navigationController pushViewController:vc animated:true];
Maybe there is some setting I have to do in order to show up the view correct without rotating the interface.
I´ve got a layout problem with a view that is loaded from a nib file as follows:
The project contains a MainStoryboard. Within that, I load a view from a nib file.
NSArray *xib = [[NSBundle mainBundle] loadNibNamed:#"View" owner:self options:nil];
self.configViewController = [xib objectAtIndex:0];
The storyboard has a navigation controller and the loaded view is shown like this:
if (currentWidgetConfigViewController != nil)
{
[self.navigationController pushViewController:currentWidgetConfigViewController animated:true];
}
So in my opinion nothing wrong? (First question)
But now the problem.
What I designed is that:
What iOS does is the following
The controls are not arranged well.
And besides that with a button on the new view I open up a PeoplePicker with that code:
[self presentViewController:self.picker animated:YES completion:nil];
After closing the People picker with [self.picker dismissViewControllerAnimated:true completion:nil]; I get this result:
So what is going wrong here?
Override -setFrame: and -setBounds: in your view to see what happens:
- (void) setFrame: (CGRect) newFrame
{
NSLog(#"New frame: %#", NSStringFromCGRect(newFrame));
[super setFrame:newFrame];
}
Also, the transform usually changes during a rotation. Orientation Zoo might help, it’s a sample Xcode project showing what happens in various rotation use cases. Didn’t touch it for a long time, though, so I don’t know if it still works.

CATransform3D weird UIScrollView contentOffset

I'm doing a cool CA3DTransform while a UIScrollview is scrolling in the scrollViewDidScroll delegate method. It works perfectly when you use your finger to scroll, so when scrolling manually everything is perfect.
But, when I set scrollview contentoffset programmatically like:
[scrollView setContentOffSet:CGPointMake(0,460) animated:YES];
It still calls the delegate method scrollviewdidscroll, so the same animation methods are called, so I still see the correct animation, BUT, somehow parts of the view are missing during and after the animation! I've tried to set the layer.zPosition on all things and it doesn't seem to help. It should be fine since manually scrolling does work without parts of views going missing... Somehow calling this method programmatically differs and I have no idea why!
Post your question in this way is not very clear, considering that with the delegate scrollViewDidScroll, I can print me the code, either manually or programmatically.
...
UIScrollView *sv = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 10, 320, 200)];
[sv setContentSize:CGSizeMake(640, 200)];
sv.delegate = self;
[sv setContentOffset:CGPointMake(320, 0) animated:YES];
[self.view addSubview:sv];
...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(#"---%#---", NSStringFromCGPoint(scrollView.contentOffset));
}
In this simple example, you can see that the delegate actually works.
Excuse the simplicity of the answer, but the only question you ask is hard to give you help.
I had a similar problem (in the context of using a scrollview for a slideshow) but solved it by using scrollRectToVisible:NO (NO for no animations), and wrapped that inside a UIView animation block where I also put my animation code.

Two NIBs, same objects (iPhone)

So I am setting up my application rotation, and I have been setting up all of the button translations manually using the following method:
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)x
duration:(NSTimeInterval)duration{
CGRect bounds = [[self view] bounds];
if (UIInterfaceOrientationIsPortrait(x)) {
[anObject setFrame: CGRectMake(0, 0, bounds.size.width, 194)];
}
else {
[anObject setFrame: CGRectMake(0, 0, bounds.size.width, 110)];
}
}
I'm setting up each object's position in both orientations. It seems like a horrible way of accomplishing my goal..
So now what I'd like to do is make a copy of my main nib, and set up all of the buttons' positions in landscape on the new nib. Then when the device rotates, call the corresponding nib. Is that possible/feasible/an alright way to do it?
How would I call the nib for rotation? Can I use the exact same objects with the same IBconnections?
Is there a better way?
I've used a couple of different approaches depending on the behavior I needed. See which works best for you.
Find a set of rules which allow you to support both orientations using only autoresizingMask and autoresizesSubviews.
Find a set of rules for the parent view's -layoutSubviews such that the subviews can be rearranged relative to the parent view's bounds. That's more work than autoresizing but better than two hardcoded positions for every subview. This often requires the creation of a deeper view hierarchy to nest several views with different -layoutSubviews behaviors to get appropriate positioning of and spacing around the visible subviews.
Have two subviews, one for each orientation. Hide one when you rotate. This allows custom transitions between the subviews which you won't get if you load a new nib and replace the root view. If you load each subview from it's own nib you can then unload whichever one is no longer visible to reduce memory use. You then need to make sure both views display the same state, trigger the same IBActions, and have extra IBOutlet references (or carefully capture state from your current IBOutlets before rebinding them by loading a new subview from a nib) so you can set attributes of equivalent views in each rotation.
Combine options 2 and 3 above to load different views with different layout behaviors for each rotation and shuffle subviews back and forth between them.
This is a much easier way - two NIBs, using Apple's naming convention. I suspect that in iOS 6 or 7, Apple might add this as a "feature". I'm using it in my apps and it works perfectly:
triggers on WILL rotate (waits until the rotate anim is about to start)
uses the Apple naming convention for landscape/portrait files (Default.png is Default-landscape.png if you want Apple to auto-load a landscape version)
reloads the new NIB
which resets the self.view - this will AUTOMATICALLY update the display
and then it calls viewDidLoad (Apple will NOT call this for you, if you manually reload a NIB)
(NB stackoverflow.com requires this sentence here - there's a bug in the code formatter)
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if( UIInterfaceOrientationIsLandscape(toInterfaceOrientation) )
{
[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"%#-landscape", NSStringFromClass([self class])] owner:self options:nil];
[self viewDidLoad];
}
else
{
[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"%#", NSStringFromClass([self class])] owner:self options:nil];
[self viewDidLoad];
}
}

UIView autoresizingmask not working for me

I have my app set up so that auto-rotate works. Things like tableviewcontrollers and tabbarcontrollers automatically resize them selves without the need for me to write any code. However I need my webview etc. to resize when the device is turned to landscape. I set:
webView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
but when I rotate the device it does not resize. I am implementing this incorrectly?
I think I finally figured this out. It seems that the root view of a NIB file does not allow you to specify flexible width and flexible height for the autoresizing mask in interface builder (at least not as of 3.2). Take a look. Putting the following code in your viewDidLoad seems to fix the problem:
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
I did it after the super call.
I implement the following in my view controller:
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self.webView setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
}
Seems to work well.

iPhone OS: Tap status bar to scroll to top doesn't work after remove/add back

Using this method to hide the status bar:
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:YES];
When setting "hidden" back to NO, the tap-to-scroll-to-top (in UIWebView, UITableView, whatever) doesn't work any more, and requires a restart of the app to get the functionality back.
Is this a bug (I filed a rdar anyhow) or have I missed a step? Should I perhaps expect this behavior since the statusBar "loses touch" somehow with the respective view?
You could try setting the ScrollsToTop property to true again after re-showing it:
[currentView setScrollsToTop:YES];
If that's not working, are you definitely only showing one view? If there is more than one scrolling view a scrollViewDidScrollToTop message is ignored...
In iOS 5.0 you can access the scrollview property of the UIWebView
webView.scrollView.scrollsToTop = YES;
The following fix by Alex worked for me. Thanks!
((UIScrollView *)[[webView subviews] objectAtIndex:0]).scrollsToTop = NO;
Being in a hurry this fix worked great, however given more time I might've subclassed the UIWebView and accessed the protected UIScrollView member directly.
The worry I have with Alex' method is that it assumes that UIScrollView is at index zero of the subviews (encapsulation allows private members to change). Which suggests another solution still:
for (UIView* v in [webView subviews])
{
if ([v isKindOfClass:[UIScrollView class]])
{
(UIScrollView *)v.scrollsToTop = NO;
}
}
I was having a similar problem where the scroll-to-top functionality was lost. Turns out this will only work when you have only one active view at a time (within the same scroll view). In my case I had a table view and another view which would fade in/out. Adding a removeFromSuperview at the end of the animation did the trick.
The answer was in the UIScrollView.h file comments:
/*
this is for the scroll to top gesture. by default, a single scroll visible scroll view with this flag set will get the call. if there is more than one visible with this
flag set or the delegeat method returns NO, the view isn't scrolled
*/
#property(nonatomic) BOOL scrollsToTop; // default is YES. if set, special gesture will scroll to top of view after consulting delegate
You can use the following code to have the UIWebView ignore scrollToTop without the extra UIScrollView:
((UIScrollView *)[[webView valueForKey:#"_internal"] valueForKey:#"scroller"]).scrollsToTop = NO;
I had a similar problem after playing a Youtube video within my app. scrollsToTop was still set to YES but tapping the status bar had no effect.
I finally realised that my app window was no longer the key window. After adding the following line to a UIWindow subclass (which I already had for other reasons) everything worked as it should again:
if (![self isKeyWindow]) [self makeKeyWindow];
I just ran across a similar behavior in the app I'm currently working on. In its case, if you load a YouTube video from within a UIWebView, scroll to top stops working for the rest of the application's life cycle. I kind of assume this might happen after loading the movie player as well, but haven't confirmed. That functionality has been around a lot longer and probably has fewer bugs.
When there are multiple scrollview, you can also set scrollUpToTop to NO for the others scrollview. cf:
setScrollsToTop with multiple UIScrollView classes and/or subclasses(UITableView)
I want to add my case, I add an UIWebView on an UIScrollView, as h4xxr had answered on the top:
If there is more than one scrolling view a scrollViewDidScrollToTop message is ignored
So, I get a simply way to make it work on webView: just set the scrollView·s scrollsToTop property false.
And when tap the status bar, it won`t got intercepted by the scrollView, and the webView scrolls to the top!
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.frame = self.view.bounds;
scrollView.scrollsToTop = false; //igore scrollView`s scrollsToTop
[self.view addSubview:scrollView];
UIWebView *webView = [[UIWebView alloc] init];
webView.frame = scrollView.bounds;
[scrollView addSubview:webView];