iOS textviews not expanding in scrollview in landscape - iphone

I had a view with some textviews in it that were working as I wanted. The expanded to a the same right-margin whether in landscape or portrait.
I recently have tried changing the normal view to a scrollview. I've had no luck getting these text views to expand as they once did, though. When in landscape mode everything stays huddled over on the left side with the same width as a portrait phone.
Here is some code.
- (void)viewDidLoad {
CGSize screen = [self handleScreenOrientation];
[(UIScrollView *)self.view setContentSize:CGSizeMake(screen.width, screen.height)];
}
- (CGSize)handleScreenOrientation {
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
CGRect screenRect = [[UIScreen mainScreen] bounds];
if (orientation == UIDeviceOrientationPortrait || orientation == UIDeviceOrientationPortraitUpsideDown ) {
return CGSizeMake(screenRect.size.width, screenRect.size.height);
}
else {
return CGSizeMake(screenRect.size.height, screenRect.size.width);
}
}
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
CGSize screen = [self handleScreenOrientation:toInterfaceOrientation];
UIScrollView *scrollView = (UIScrollView *)self.view;
scrollView.frame = CGRectMake(0, 0, screen.width, screen.height);
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width, scrollView.frame.size.height);
[scrollView setNeedsDisplay];
}
The method handleScreenOrientation with the passed orientation is the same as the one w/ no parameters, just it uses the passed orientation instead of the current orientation of the status bar.
I've checked and my scrollview is set to autoresize subviews.
Any ideas would be much appreciated. Thanks.
Update: I have added
self.view.autoresizesSubviews = true;
for (UIView *view in self.view.subviews) {
view.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleWidth;
}
to viewdidload and willrotatetointerfaceorientation. No change.

I think the problem is that you are overriding the autoresizingMasks by forcing a change to the scrollView's contentSize. Although you have correctly noted that the screenSize is always in portrait orientation, so you reverse its dimensions for landscape, you are determining which orientation to use before the device actually rotates. So you're forcing the scrollView to maintain portrait dimensions in landscape.
Keep your autoresizeMask routine and discard your willRotateToInterfaceOrientation routine. Does it work now?
If not, try putting the willRotate code into *did*RotateToInterfaceOrientation.
Another idea:
I notice that in viewDidLoad you cast the container view (self.view) to a UIScrollView. I wonder if it might be better to leave the container view as a generic UIView, add a UIScrollView as a subview, and then add the textviews to the scrollview.
I might be out in left field here, but I've never used a scrollview directly at the container-view level, and I wonder if that might be the problem.

Related

Rotate according to topmost/last subview?

I have a UITabController that is displayed by becoming a subview of the window (and then animated to slide in):
[self.window addSubview:tabController.view];
CGRect endFrame = tabController.view.frame;
CGRect startFrame = tabController.view.frame;
startFrame.origin.y += [[UIScreen mainScreen] bounds].size.height;
tabController.view.frame = startFrame;
[UIView animateWithDuration:.5 animations:^{
tabController.view.frame = endFrame;
}];
My problem is that I want this subview to not rotate, at all, and be portrait only. Despite shouldAutorotateToInterfaceOrientation and shouldAutoRotate returning false in my UITabController's subview, parts of it still rotate - like the status bar, and the UINavigationBar changes size.
Is there a way to fix this, or simply a better way of doing things? Maybe a way to force the statusbar rotation and UINavigationBar height?
Define the following in your UITabController:
- (UIInterfaceOrientation) supportedInterfaceOrientations
{ return UIInterfaceOrientationPortrain; }
From iOS documentation:
Declaring a Preferred Presentation Orientation
When a view controller
is presented full-screen to show its content, sometimes the content
appears best when viewed in a particular orientation in mind. If the
content can only be displayed in that orientation, then you simply
return that as the only orientation from your
supportedInterfaceOrientations method.

How to prevent size of UIScrollView after device orientation is changed?

I have UIScrollView on xib-file on portrait mode. When I run the application and rotate device to landscape mode I see the new bounds of the UIScrollView. Half of the controls inside UIScrollView at the bottom are inaccessible. There is no any scroller visible.
How to prevent change of the vertical size of UIScrollView after device orientation is changed?
Thanks a lot for the help!
Change the UIScrollView's frame in
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
According to the device rotation.
shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if(interfaceOrientation == UIInterfaceOrientationPortrait)
{
//set frame for scroll in portrait mode
}
else if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
//set frame for landscape
}
}
select your UIScrollView and then from properties click on size inspector and change autoresize property for your view.
EDIT : select your UIScrollView and change autoresize property for your scroll view like this.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
{
//change sub view of scrollview accordingly to orientation
if(UIInterfaceOrientationIsPortrait(interfaceOrientation))
yourScrollView.frame = CGRectMake(0,0,320,460);
yourScrollView.contentSize = CGSizeMake(320,460);//change accordingly
else
yourScrollView.frame = CGRectMake(0,0,480,300);
yourScrollView.contentSize = CGSizeMake(480,460); //change accordingly
return YES;
}
Setup Reference Outlet.
Use the code:
scrollView.contentSize = CGSizeMake(width, height);

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.

touchesBegan after didRotateFromInterfaceOrientation

I noticed that after an orientation change from portrait to landscape, I'm not getting touchesBegan events for some parts of my view any longer. I suppose that this is because I'm not informing my UIView about the dimension change of my window's frame after the device rotation.
I'm setting up everything programmatically (UIWindow, UIViewController, UIView) like this:
myViewController = [[myUIViewController alloc] init];
myWindow = [[UIWindow alloc] initWithFrame: rect];
myView = [[myUIView alloc] initWithFrame: [myWindow bounds]];
[myViewController setView:myView];
[myWindow addSubview:myView];
[myWindow setFrame:rect];
[myWindow makeKeyAndVisible];
When I get the didRotateFromInterfaceOrientation notification, I'm updating the window frame like this:
[[[self view] window] setFrame:rect];
But after that, my UIView does no longer get touchesXXX events for all areas. It seems that only the areas of the previous frame are still reporting events. So my question: Is there anything else I need to do in didRotateFromInterfaceOrientation to inform my UIView about the dimension change?
Thanks for help!
EDIT: Do I have to reposition the UIView on didRotateFromInterfaceOrientation() or is this done automatically? I noticed that the "transform" property of my UIView is set to a transformation matrix when the orientation changes. However, this makes it very hard to reposition my view. The docs say that the "frame" property can't be used when a transformation is active, so I tried to modify the "center" property to reposition my view, but this also doesn't work correctly. I want to move the view to the top-left corner, so I set "center" to (screenwidth/2,screenheight/2) but it doesn't position the view correctly :( Any idea or info what must be done to get the events right in orientation mode?
I have had this same problem, and I believe it is a frame issue.
I had to manually set the rects depending on orientation, as well as set the userInteractionEnabled on the main view, like:
if (appOrientation == 0)
appOrientation = [UIApplication sharedApplication].statusBarOrientation;
if (appOrientation == UIInterfaceOrientationLandscapeLeft || appOrientation == UIInterfaceOrientationLandscapeRight) {
[[UIApplication sharedApplication] setStatusBarHidden:YES];
myView.frame = CGRectMake(0, 0, 1024, 768);
} else {
[[UIApplication sharedApplication] setStatusBarHidden:YES];
myView.frame = CGRectMake(0, 0, 768, 1024);
}
myView.userInteractionEnabled = YES;
OK I know this is an old question but this is how I fixed this issue.
In my situation I had a storyboard with a view that would be displayed either in portrait or forced to landscape mode depending on a user setting.
My app displays the statusBar at all times except for when I'm showing this view.
To make this all work, the transformations had to be applied in the viewWillAppear method for one. I had the following code in the viewDidAppear method at first and that messed with the bounds for the touchesBegan event I guess.
Here's the viewWillAppear code:
- (void)viewWillAppear:(BOOL)animated
{
// Hide the status bar
[[UIApplication sharedApplication] setStatusBarHidden:YES];
// This is to offset the frame so that the view will take the fullscreen once the status bar is hidden
self.navigationController.navigationBar.frame = CGRectOffset(self.navigationController.navigationBar.frame, 0.0, -20.0);
// If view is to be displayed in landscape mode
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"orientation"])
{
// Change status bar orientation
[[UIApplication sharedApplication] setStatusBarOrientation:UIDeviceOrientationLandscapeLeft];
// Turn the view 90 degrees
[self.navigationController.view setTransform:CGAffineTransformMakeRotation(M_PI/2)];
// Set the frame and the bounds for the view
// Please Note: Frame size has to be reversed for some reason.
[self.navigationController.view setFrame: CGRectMake(0,0,320,480)];
[self.navigationController.view setBounds: CGRectMake(0,0,480,320)];
// Make sure user can interact with view
[self.navigationController.view setUserInteractionEnabled:YES];
}
}
Any other thing that had to happen layout wise, have to happen in the viewDidAppear method. For instance I had an image that covered the whole view and the frame had to be set depending on the orientation. Setting the frame in the viewWillAppear gave weird results but the same code worked perfectly in viewDidAppear.
Hope this helps someone as I banged my head for 6 hours on this thing.

UIScreen applicationFrame returning incorrect rectangle on landscape application launch (iPhone/iPad)

Having trouble getting the correct bounds for my iPad application when launching it in landscape mode. I have the proper keys set in my Info.plist file, and my view controllers launch properly in landscape (and portrait, natch).
In my applicationDidFinishLaunching: method I'm calling a selector after a 3 second delay, and that method makes a call to [[UIScreen mainScreen] applicationFrame], but it's returning me a portrait frame (ie height > width).
Does anyone know how to fix this? It smells like a bug to me (if so I'll file a radar), but if it's intended behaviour, where is it documented?
I never rely on [[UIScreen mainScreen] applicationFrame], especially during app launch.
When creating views in code, use the superview to set your frame.
If you're using xibs with "simulated interface elements" they will be correctly sized and everything will work great.
UINavigationController based apps
In the case of a UINavigationController based app, grab the frame directly from self.navigationController.view, don't try to use [self loadView] and self.view.superview. UINavigationController uses "hidden" subviews to do it's job--so the direct superview will not work.
UINavigationController is special because during app launch, the navigation controller resizes your views after loadView gets called. When autoresizing kicks in you end up with a small margin at the bottom of the screen.
Why not UIScreen
[[UIScreen mainScreen] applicationFrame] doesn't work reliably (especially during app launch in landscape). My experience is that the viewcontroller's interfaceOrientation property will not match the applicationFrame orientation.
CGRect bounds = [[UIScreen mainScreen] bounds]; // portrait bounds
if (UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation])) {
bounds.size = CGSizeMake(bounds.size.height, bounds.size.width);
}
When you are holding the iPad in landscape orientation and launch an app, the view controller initially sees bounds for a portrait view (even though orientation reports landscape). The view controller will then get a message to rotate to landscape orientation before it appears.
This is the way I get the correct CGRect when the view controller is on landscape:
CGRect landscapeBounds;
if (self.view.frame.size.width < self.view.frame.size.height)
landscapeBounds = CGRectMake(self.view.frame.origin.y, self.view.frame.origin.x, self.view.frame.size.height, self.view.frame.size.width);
else
landscapeBounds = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.width, self.view.frame.size.height);
This is as designed. You should query the size of your superview and adjust as necessary.
[[UIScreen mainScreen] applicationFrame] will always return the portrait rectangle even if the app is in landscape mode. This is related to the fact that UIWindow never actually rotates, but just changes the transform of rootViewController.view instead.
To make sure, you can print the root view object in portrait and landscape modes, and you'll see something like this:
Portrait:
<UIView: 0x96290e0; frame = (0 20; 768 1004); ...
Landscape:
<UIView: 0x96290e0; frame = (0 20; 768 1004); transform = [0, 1, -1, 0, 0, 0]; ...
So, add a launch image and give it the suffix -568h, according to Apple's guides.
I don't understand why anyone with a sound mind would make a system setting depend on a graphic; but I just tested and it worked.
Here is the spot that taught me after a quick search I didn't see this answer above, figured it'd be useful to someone.
S
I got into same problem when dismissing view with dismissViewControllerAnimated in Cordova plugin.
I modified singingAtom code for viewWillAppear method in MainViewController, which got resized after dismissing modal view:
- (void)viewWillAppear:(BOOL)animated
{
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
UIDeviceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
if (UIDeviceOrientationLandscapeLeft == orientation ||
UIDeviceOrientationLandscapeRight == orientation)
{
if (appFrame.size.width < appFrame.size.height)
{
appFrame = CGRectMake(appFrame.origin.y, appFrame.origin.x, appFrame.size.height, appFrame.size.width);
}
}
self.view.frame = appFrame;
[super viewWillAppear:animated];
}