Automatically Sizing UIView after Adding to Window

11,778

Solution 1

This works, but it seems a little hacky:

- (void)switchToViewController:(UIViewController *)viewController {
    if (viewController != currentViewController) {
        UIInterfaceOrientation orientation = currentViewController.interfaceOrientation;
        [currentViewController.view removeFromSuperview];

        currentViewController = viewController;
        UIView *view = viewController.view;

        // Set appropriate view frame (it won't be autosized by addSubview:)
        CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
        if (UIInterfaceOrientationIsLandscape(orientation)) {
            // Need to flip the X-Y coordinates for landscape
            view.frame = CGRectMake(appFrame.origin.y, appFrame.origin.x, appFrame.size.height, appFrame.size.width);
        }
        else {
            view.frame = appFrame;
        }

        [window addSubview:view];
    }
}

Solution 2

This is absolutely possible! :-)

You can check out my repo here: https://github.com/hfossli/AGWindowView

It will automatically deal with any rotation and framechanges so you won't have to worry about that.

If you like to worry about that then you can just cut and paste the most important parts

# 1 Add view to window

[[UIApplication sharedApplication] keyWindow] addSubview:aView];

# 2 Add listener and update view

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameOrOrientationChanged:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarFrameOrOrientationChanged:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];

Remember to remove notification listening

[[NSNotificationCenter defaultCenter] removeObserver:self];

# 3 Do the math

- (void)statusBarFrameOrOrientationChanged:(NSNotification *)notification
{
    /*
     This notification is most likely triggered inside an animation block,
     therefore no animation is needed to perform this nice transition.
     */
    [self rotateAccordingToStatusBarOrientationAndSupportedOrientations];
}

- (void)rotateAccordingToStatusBarOrientationAndSupportedOrientations
{
    UIInterfaceOrientation statusBarOrientation = [UIApplication sharedApplication].statusBarOrientation;
    CGFloat angle = UIInterfaceOrientationAngleOfOrientation(statusBarOrientation);
    CGFloat statusBarHeight = [[self class] getStatusBarHeight];

    CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
    CGRect frame = [[self class] rectInWindowBounds:self.window.bounds statusBarOrientation:statusBarOrientation statusBarHeight:statusBarHeight];

    [self setIfNotEqualTransform:transform frame:frame];
}

- (void)setIfNotEqualTransform:(CGAffineTransform)transform frame:(CGRect)frame
{
    if(!CGAffineTransformEqualToTransform(self.transform, transform))
    {
        self.transform = transform;
    }
    if(!CGRectEqualToRect(self.frame, frame))
    {
        self.frame = frame;
    }
}

+ (CGFloat)getStatusBarHeight
{
    UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
    if(UIInterfaceOrientationIsLandscape(orientation))
    {
        return [UIApplication sharedApplication].statusBarFrame.size.width;
    }
    else
    {
        return [UIApplication sharedApplication].statusBarFrame.size.height;
    }
}

+ (CGRect)rectInWindowBounds:(CGRect)windowBounds statusBarOrientation:(UIInterfaceOrientation)statusBarOrientation statusBarHeight:(CGFloat)statusBarHeight
{    
    CGRect frame = windowBounds;
    frame.origin.x += statusBarOrientation == UIInterfaceOrientationLandscapeLeft ? statusBarHeight : 0;
    frame.origin.y += statusBarOrientation == UIInterfaceOrientationPortrait ? statusBarHeight : 0;
    frame.size.width -= UIInterfaceOrientationIsLandscape(statusBarOrientation) ? statusBarHeight : 0;
    frame.size.height -= UIInterfaceOrientationIsPortrait(statusBarOrientation) ? statusBarHeight : 0;
    return frame;
}

CGFloat UIInterfaceOrientationAngleOfOrientation(UIInterfaceOrientation orientation)
{
    CGFloat angle;

    switch (orientation)
    {
        case UIInterfaceOrientationPortraitUpsideDown:
            angle = M_PI;
            break;
        case UIInterfaceOrientationLandscapeLeft:
            angle = -M_PI_2;
            break;
        case UIInterfaceOrientationLandscapeRight:
            angle = M_PI_2;
            break;
        default:
            angle = 0.0;
            break;
    }

    return angle;
}

UIInterfaceOrientationMask UIInterfaceOrientationMaskFromOrientation(UIInterfaceOrientation orientation)
{
    return 1 << orientation;
}

Good luck!

Solution 3

The window may include other UI elements besides your view. The 20 pixel difference in your example is the height of the status bar.

[[UIApplication sharedApplication] statusBarFrame].height;

Neither the window nor screen rotate. Getting their frames and using them for a rotated view will only work if you have switched the height and width.

If you are using a UIViewController, try returning YES from this method:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation; // Override to allow rotation. Default returns YES only for UIDeviceOrientationPortrait

Solution 4

I got the same problem, but i fixed it with this lines of code:

- (void)changeRow:(NSNotification *)notification {
[window addSubview:new.view];
[old.view removeFromSuperview];
[new.view removeFromSuperview];
[window addSubview:new.view];

}

You must add the new view, then remove the old and the new and then add the new view. I don't know why, but that works.

Solution 5

Fossli's answer is correct for iPad. However, I have a universal app that I needed to support. Therefore some adjustments are necessary.

Add the following to AppDelegate.h

@property (strong, nonatomic) UIImageView *imageView;

Add the following to AppDelegate.m

@synthesize imageView;

- (void)orientationChanged:(NSNotification *)notification
{
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;   
    if (! (UIInterfaceOrientationIsLandscape(deviceOrientation) ||
           UIInterfaceOrientationIsPortrait(deviceOrientation)))
    {
        // May be "UIInterfaceOrientationUnknown" which does not appear to be a defined value anywhere.
        return;
    }

    [imageView setImage:[UIImage imageNamed:[Utility getBackgroundImageNameWithOrientation:deviceOrientation]]];

    /*
     iOS Image Sizes

     iPhone/iPod    Portrait 320 x 480 (640 x 960 @2x)
     iPad           Portrait 768 x 1004 (1536 x 2008 @2x)
                    Landscape 1024 x 748 (2048 x 1496 @2x)

     iPad window bounds in both orientations 768 x 1024  (needs manual swap in landscape)
     iPhone window bounds in both orientations 320 x 480 (needs manual swap in landscape)

     Note the size variations between the required default launch image sizes and
     the size of the window bounds.
     iPhone/iPod only requires rotations.
     iPad needs origin or size adjustments depending on orientation.
     */

    CGFloat angle = 0.0;
    CGRect newFrame = [[self window] bounds];

    // How to get size of status bar
    // Size of status bar gets all wonky on rotations so just set it manually
    // CGSize statusBarSize = [[UIApplication sharedApplication] statusBarFrame].size;
    CGSize statusBarSize = CGSizeMake(20.0, 20.0);

    if (deviceOrientation == UIInterfaceOrientationPortraitUpsideDown)
    {
        angle = M_PI; 
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
        {
            newFrame.size.height -= statusBarSize.height;
        }
    }
    else if (deviceOrientation == UIInterfaceOrientationLandscapeLeft)
    {
        angle = - M_PI / 2.0f;        
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
        {
            newFrame.origin.x += statusBarSize.height;
            newFrame.size.width += statusBarSize.height;
        }
    }
    else if (deviceOrientation == UIInterfaceOrientationLandscapeRight)
    {
        angle = M_PI / 2.0f;
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
        {
            newFrame.size.width -= statusBarSize.height;
        }
    }
    else
    {
        angle = 0.0;
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
        {
            newFrame.origin.y += statusBarSize.height;
            newFrame.size.height -= statusBarSize.height;
        }
    } 

    imageView.transform = CGAffineTransformMakeRotation(angle);
    imageView.frame = newFrame;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{   
    // Add background image to window with orientation changes so that it is visible in all views.
    // A listener is added since subviews do not receive orientation changes.
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object: nil];

    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;      
    imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[Utility getBackgroundImageNameWithOrientation:deviceOrientation]]];
    [[self window] addSubview:imageView];

    return YES;
}

Add the following to Utility.h

+ (NSString *)getBackgroundImageNameWithOrientation:(UIDeviceOrientation)interfaceOrientation;

Add the following to Utility.m

+ (NSString *)getBackgroundImageNameWithOrientation:(UIDeviceOrientation)interfaceOrientation
{
    NSString *imageName = nil;

    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
    {
        if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
        {
            imageName = @"Default-Landscape~ipad.png";
        }
        else
        {
            imageName = @"Default-Portrait~ipad.png";
        }
    }
    else
    {
        if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
        {
            imageName = @"Default-Landscape~iphone.png";
        }
        else
        {
            imageName = @"Default.png";
        }
    }

    return imageName;
}
Share:
11,778
kevinthompson
Author by

kevinthompson

Senior developer who knows a little bit about a lot of things. Twitter: @OldManKris Prefers spaces to tabs.

Updated on June 17, 2022

Comments

  • kevinthompson
    kevinthompson almost 2 years

    Note: This may be a duplicate of Subview Doesnt AutoSize When Added to Root View Controller


    I have an iPad app that switches between different views in its main window. The view-switching code looks like this:

    - (void)switchToViewController:(UIViewController*)viewController {
        if (currentViewController != viewController) {
            [currentViewController.view removeFromSuperview];
            currentViewController = viewController;
            [window addSubview:viewController.view];
        }
    }
    

    The problem is that when the new view (a UISplitView) appears in landscape orientation, it is not sized to fill the entire window. There is a large empty black space on the right. It looks like the view is only 768 pixels wide, rather than the 1024-pixel width of the landscape window.

    If I rotate the device to portrait and then back to landscape, the view sizes itself properly.

    If the device is in portrait orientation, everything works fine. The UISplitView also gets sized properly if it is the first view I show. The problem only occurs if I switch to it after another view has been shown, in landscape.

    So, is there some way to force iPhone OS to resize the view after it has been added to the window?

    I've tried calling sizeToFit, and setNeedsLayout. I've also tried setting the view's bounds to the window's bounds, and I've tried setting the frame to match the previous view's frame.