I've been searching but can't find an answer to this issue I'm having. It seems fairly fundamental, so hopefully someone can explain, or point me towards a previous post.
When adding a viewcontroller's view as a subview of another viewcontroller, I find that the subview's height property goes to zero upon rotation. The width tends to increase as well.
For example, with NSDChildViewController's view set to 50x100 in the xib file...
#implementation ParentViewController
-(void)viewDidLoad
{
[super viewDidLoad];
mChild = [[ChildViewController alloc] initWithNibName:nil
bundle:nil];
[self.view addSubview:mChild.view];
}
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[mChild printFrame];
}
#end
#implementation ChildViewController
-(void)printFrame
{
NSLog(#"%s %#",__FUNCTION__, NSStringFromCGRect(self.view.frame));
}
#end
The logging for respective orientations portrait->landscape->portrait is as follows:
-[ChildViewController printFrame] {{0, 0}, {50, 100}}
-[ChildViewController printFrame] {{0, 0}, {298, 0}}
-[ChildViewController printFrame] {{0, 0}, {50, 248}}
Why does this happen and how can I prevent it? Presently it's causing me trouble whilst trying to programmatically layout viewcontrollers as subviews. The only solution I've found so far is to force the size property back to 50x100 using something like this...
-(void)forceSize
{
CGRect frame = self.view.frame;
frame.size.width=50;
frame.size.height=100;
self.view.frame=frame;
}
but the above seems ludicrous. Any help appreciated.
Your parent view is automatically resizing its child views when its frame changes. In this case, the parent view's frame is changing when the device switches between portrait and landscape mode.
You appear to be testing on a 4-inch iPhone device (or simulator), so the dimensions we're playing with are: 320 x 568. The parent view uses up the full width and height of the screen.
Here's what's happening:
Before rotation from portrait to landscape:
widthDifference = parentViewWidth - originalChildViewWidth // 270 (320 - 50)
heightDifference = parentViewHeight - originalChildViewHeight // 468 (568 - 100)
After rotation from portrait to landscape:
childLandscapeViewWidth = parentViewHeight - widthDifference // 298 (568 - 270)
childLandscapeViewHeight = parentViewWidth - heightDifference // -148 (320 - 468)
// (but height cannot be < 0, so this is automatically set to 0)
So in landscape mode we end up with the child view's width = 298 and height = 0
Before rotation from landscape to portrait:
widthDifference = parentViewWidth - childLandscapeViewWidth // 270 (568 - 298)
heightDifference = parentViewHeight - childLandscapeViewHeight // 320 (320 - 0)
After rotation from landscape to portrait:
childPortraitViewWidth = parentViewWidth - widthDifference // 50 (320 - 270)
childPortraitViewHeight = parentViewHeight - heightDifference // 248 (568 - 320)
And ultimately we end up with the child view's width = 50 and height = 248
So, to put a stop to this craziness set autoresizesSubviews on the parent view (which is YES by default) to NO like so:
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.autoresizesSubviews = NO;
...
}
Related
Since I am very new to ios programming I have more of a general-design question.
I have a ViewController which contains a GraphView (UIScrollView + UIView) which works fine. When I am rotating to landscape I want the GraphView to resize its height to the display height (so it fills the whole screen) but only 300pts when in portrait.
What I did so far is implementing viewWillLayoutSubviews in the ViewController and resetting the constraints:
- (void)viewWillLayoutSubviews{
_graphViewHeightConstraint.constant = ([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortrait) ? 300:[[UIScreen mainScreen] bounds].size.height-self.navigationController.navigationBar.frame.size.height - 2*_distanceToTopView.constant;
}
and in GraphView.m:
- (void)layoutSubviews{
kGraphHeight = self.frame.size.height;
[self setNeedsDisplay];
}
(because I need the variable kGraphHeight in the code to draw the Graph). This does not seem like a very elegant solution so I wanted to ask what the better way would be? Many thanks for your inputs :)
In GraphView.m
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
kViewWidth = <GET_SCREEN_WIDTH_HERE>;
kViewHeight = <GET_SCREEN_HEIGHT_HERE>;
[self updateViewDimensions];
}
and updateViewDimensions method will set the frame of UIScrollView and UIView
- (void)updateViewDimensions
{
scrollView.frame = self.view.frame;
yourView.frame = CGRectMake(kViewXStartsFrom, kViewYStartsFrom, kViewWidth, kViewHeight);
}
after rotating view to Landscape viewDidLayoutSubviews will be called.
It's working for me.
In Xcode 5.1 I have created a very simple single view app for iPhone and put the source code at GitHub:
I have disabled Autolayout and put the following views in each other: scrollView -> contentView -> imageView (here fullscreen):
For the contentView and imageView I've disabled Autoresizing and set their frames to {0, 0, 1000, 1000} - both in the Storyboard and in the viewDidLoad method.
I have enabled double tap and pinch gestures for zooming.
For double tap the image is zoomed at 100% or 50% width.
This works initially, but after device rotation it breaks:
The zoom doesn't work properly and the image is offset - you can't scroll to its top left corner:
Here is my very short code in ViewController.m, please advice how to fix it:
- (void)viewDidLoad
{
[super viewDidLoad];
_imageView.frame = CGRectMake(0, 0, 1000, 1000);
_contentView.frame = CGRectMake(0, 0, 1000, 1000);
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
float scale = _scrollView.frame.size.width / 1000;
_scrollView.minimumZoomScale = scale;
_scrollView.maximumZoomScale = 2 * scale;
_scrollView.zoomScale = 2 * scale;
_scrollView.contentSize = CGSizeMake(1000, 1000);
}
- (UIView*)viewForZoomingInScrollView:(UIScrollView*)scrollView
{
return _contentView;
}
- (IBAction)scrollViewDoubleTapped:(UITapGestureRecognizer*)sender
{
if (_scrollView.zoomScale < _scrollView.maximumZoomScale)
[_scrollView setZoomScale:_scrollView.maximumZoomScale animated:YES];
else
[_scrollView setZoomScale:_scrollView.minimumZoomScale animated:YES];
}
UPDATE: I've tried using Reveal app (here fullscreen), but couldn't find anything useful for me:
My source code seems to be okay, but in Interface Builder I had to disable "Autoresize Subviews" for the scrollView:
I've searched a lot about how can I determine the actual view size dynamically in the viewDidLoad method.
Here is my approach which seems to be work on both iOS6 and iOS7 with both Landscape and Portrait mode.
Is there a better solution for that?
Here is my code:
- (CGSize)actualSize {
CGFloat widht;
CGFloat height;
// Determin height and widht
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (orientation == UIDeviceOrientationLandscapeRight || orientation == UIDeviceOrientationLandscapeLeft) {
widht = kHeightSelfView;
height = kWidthSelfView;
} else {
widht = kWidthSelfView;
height = kHeightSelfView;
}
// Determin navigationbar height
CGFloat navigationBarHeight = self.navigationController.navigationBarHidden ? .0f : (MIN(self.navigationController.navigationBar.frame.size.height, self.navigationController.navigationBar.frame.size.width));
// Determin navigationbar height
CGFloat statusbarHeight = [DeviceCompatibility isIOS7] ? (MIN(SharedApplication.statusBarFrame.size.width, SharedApplication.statusBarFrame.size.height)) : .0f;
return CGSizeMake(widht, height - navigationBarHeight - statusbarHeight);
}
Typically you should only perform view-based geometry in viewWillAppear. Your view's frame is not yet set in viewDidLoad, but it will be set correctly before viewWillAppear is called.
Instead of picking height & width, you can pick the frame of view like this:
// Adjusts the frame of the child view.
CGRect frame = self.view.frame;
CGRect linkedFrame = scene.view.frame;
linkedFrame.origin.x -= frame.origin.x;
linkedFrame.origin.y -= frame.origin.y;
and then you can make them flexible so that they will resize properly :
// The scene's main view must be made flexible so it will resize properly
// in the container.
scene.view.autoresizingMask = (UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight);
Let me know if this is not what you need :)
Here is the desired outcome. The blue area is the UIView of interest. The UIView is not a UIImageView.
I've tried all sorts of arrangements with auto-resizing masks to no avail
This can only be done programmatically. One option is what #user2223761 suggests with subclassing. If you don't want to subclass UIView, then you need to set the frames on orientation changes and set yourView.center to be the center of the center.
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
if (UIInterfaceOrientationIsLandscape(interfaceOrientation)) {
// Make sure that the frame is centered in the screen
NSInteger paddingLeftSide = (self.view.bounds.size.width - 480) / 2;
self.view.frame = CGRectMake(paddingLeftSide, 0, 480, 320);
} else {
self.view.frame = CGRectMake(0, 0, 320, 320);
}
}
Dealing with different screen sizes can be tricky. In your case it is not :)
since you want to center the view in the screen what ever size it is, all you need to do is set the center of the view to be the center of the screen.
CGRect screenBounds = [[UIScreen mainScreen] bounds];
view.center = CGPointMake(screenBounds.size.width/2,screenBounds.size.height/2);
This code assumes the view's superView's bounds is the same size as the screenBounds..
First: Subclass UIView (create a MYUIView).
Second: override the method
- (void)layoutSubviews {
[super layoutSubviews];
// .. put your code...
}
and perform the frame update manually inside that method by reading the screen size.
auto-resize mask must be set to UIViewAutoresizingNone.
I am trying to create a facebook component (subclass of UIViewController) to share picture. A demonstration of its state now can be found here :
http://jeremy.gabriele.free.fr/SO/rotatesubvc/
Integrated inside a ViewController, I would like to use it this way :
_shareSheet = [[FBSharePictureViewController alloc] initWithImage:self.currentImage.image andDelegate:self]; // Create the instance
[_shareSheet showWithinViewController:self]; // Actually display the sheet with animation
[_shareSheet hide]; // When needed, hide with animation
The view architecture :
- [UIView] view
-- [UIView] overlay, a full-screen black view (when showWithinViewController: is called, slightly fade in to alpha 0.8)
-- [UIView] _sheetViewContainer, contains the sheet
---- [UIView] topView, contains the cancel and post buttons + a centered label
---- [UIView] bottomView, contains the picture we will share + an UITextView the user can enter text in.
-- spinner, an UIActivityIndicatorView
The showWithinViewController function :
- (void)showWithinViewController:(UIViewController *)parentViewController {
[parentViewController addChildViewController:self]; // So the parent will foward actions like rotation,...
// I want it to match the full size of my screen, so if we have a transparent
// status bar the overlay will be visible under the status bar too
CGRect frameBefore = self.view.frame;
[parentViewController.view addSubview:self.view]; // view frame = {{0, 0}, {320, 460}}
self.view.frame = frameBefore; // view frame = {{0, -20}, {320, 480}}
self.view.hidden = NO;
[UIView animateWithDuration:0.4 animations:^{
// _sheetViewContainer.frame is originally centered horizontally and y is offscreen
_sheetViewContainer.frame = [self getSheetViewFinalPos]; // I want the sheet to be centered between the UINavigationBar and the UIKeyboard
self.overlay.alpha = 0.8f;
} completion:^(BOOL finished) {
self.cancelButton.enabled = YES;
self.postButton.enabled = YES;
}];
}
The getSheetViewFinalPos function :
- (CGRect)getSheetViewFinalPos {
CGFloat keyboardHeight = UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) ? 216 : 162;
CGFloat navBarHeight = UIInterfaceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) ? 44 : 28;
CGRect f = _sheetViewContainer.frame;
f.origin.y = navBarHeight + 20 + (self.view.frame.size.height-20-navBarHeight-_sheetViewContainer.frame.size.height-keyboardHeight)/2;
return f;
}
Now my problem : (You could have a preview if you visited the link above :))
I'd like to find the best way for my component to resize its frame automatically depending on the orientation.
I've tried UIAutoresizingMask but it had really strange behaviors and I couldn't succeed in making it work :/.
I've tried to use viewWillLayoutSubviews method but this doesn't work if my view is hidden (when I rotate and the view is not shown).
I've tried to use didRotateFromInterfaceOrientation but when the parent ViewController is initially on landscape mode and I show my component, it's displayed in portrait mode.
How would you do to achieve this ?