添加到 Window 后自动调整 UIView 大小

6
注意:这可能是Subview Doesnt AutoSize When Added to Root View Controller的重复。
我有一个iPad应用程序,可以在其主窗口中切换不同的视图。 视图切换代码如下所示:
- (void)switchToViewController:(UIViewController*)viewController {
    if (currentViewController != viewController) {
        [currentViewController.view removeFromSuperview];
        currentViewController = viewController;
        [window addSubview:viewController.view];
    }
}

问题在于当新视图(一个UISplitView)出现在横向方向时,它的大小不足以填满整个窗口。右侧有一个大的空黑色空间。看起来视图只有768个像素宽,而不是横向窗口的1024像素宽。
如果我将设备旋转到纵向,然后再旋转到横向,视图会正确地调整大小。
如果设备处于纵向方向,一切正常。如果它是我显示的第一个视图,UISplitView也会被正确地调整大小。问题只发生在我在横向显示另一个视图之后切换到它时。
所以,有没有办法强制iPhone OS在将视图添加到窗口后重新调整视图大小?
我已经尝试过调用sizeToFit和setNeedsLayout。我还尝试将视图的bounds设置为窗口的bounds,并尝试将frame设置为与先前视图的frame匹配。
6个回答

9
这是完全可以实现的!:-)
你可以在这里查看我的存储库: https://github.com/hfossli/AGWindowView 它会自动处理任何旋转和框架更改,所以您不必担心。
如果您想要关注这个问题,那么您可以只剪切和粘贴最重要的部分。
# 1 将视图添加到窗口。
[[UIApplication sharedApplication] keyWindow] addSubview:aView];

# 2 添加监听器和更新视图

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

请记得移除通知监听

[[NSNotificationCenter defaultCenter] removeObserver:self];

# 3 做数学

- (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!


3

以下方法也可行,但可能有些不正规:

- (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];
    }
}

你不会在这里遇到问题吗?因为你没有计算导航栏的20像素。 - basti

1

我遇到了同样的问题,但是我用这几行代码解决了它:

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

}

你必须添加新视图,然后删除旧视图和新视图,再添加新视图。我不知道为什么,但这样做有效。


1

窗口可能包括与您的视图不同的其他UI元素。在您的示例中,20像素的差异是状态栏的高度。

[[UIApplication sharedApplication] statusBarFrame].height;

窗口和屏幕都不旋转。如果你交换了高度和宽度,获取它们的框架并将其用于旋转视图会起作用。

如果你正在使用UIViewController,请尝试从这个方法中返回YES:

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

事情不只是偏移20像素;它们偏离了320像素。我从shouldAutorotateToInterfaceOrientation:返回YES,一切都以正确的方向显示。只是视图大小有误。 - Kristopher Johnson
我所说的20像素是在1004与1024以及748与768之间。你通过硬编码将值20写入宽度或高度,为状态栏留出了空间。 - drawnonward

1
Fossli的回答对于iPad是正确的。然而,我有一个需要支持的通用应用程序。因此需要进行一些调整。
请在AppDelegate.h中添加以下内容。
@property (strong, nonatomic) UIImageView *imageView;

请在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;
}

请在Utility.h中添加以下内容:
+ (NSString *)getBackgroundImageNameWithOrientation:(UIDeviceOrientation)interfaceOrientation;

请在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;
}

还要注意,如果您不为应用程序显示状态栏(您可能应该这样做),则可能不需要使用状态栏高度进行某些调整。 - Christopher

0

iOS7的窗口与iOS8/9的窗口有不同的行为。

iOS7的键盘窗口和iOS8/9的所有窗口始终具有正确的方向和大小。因此,您可以观察大小更改事件并更新视图的框架。

但是,iOS7的其他窗口始终保持纵向方向和大小。旋转后,您需要更新视图的变换。

您需要观察UIApplicationWillChangeStatusBarOrientationNotification并像这样更新UIView的大小:

@interface MyView : UIView

@end

@implementation MyView

- (instancetype)init
{
    if (self = [super init]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeOrientationHandler:) name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillChangeStatusBarOrientationNotification object:nil];
}

- (void)updateTransformWithOrientation:(UIInterfaceOrientation)orientation
{
    CGFloat width = CGRectGetWidth(self.window.bounds);
    CGFloat height = CGRectGetHeight(self.window.bounds);
    if (width > height) {
        CGFloat temp = width;
        width = height;
        height = temp;
    }
    CGFloat offset = (height - width) / 2;
    CGAffineTransform transform;
    switch (orientation) {
        case UIInterfaceOrientationLandscapeLeft:
            transform = CGAffineTransformMakeTranslation(-offset, offset);
            transform = CGAffineTransformRotate(transform, -M_PI_2);
            break;
        case UIInterfaceOrientationLandscapeRight:
            transform = CGAffineTransformMakeTranslation(-offset, offset);
            transform = CGAffineTransformRotate(transform, M_PI_2);
            break;
        case UIInterfaceOrientationPortraitUpsideDown:
            transform = CGAffineTransformMakeRotation(-M_PI);
            break;
        default:
            transform = CGAffineTransformIdentity;
            break;
    }
    self.transform = transform;
    self.frame = CGRectMake(0, 0, width, height);
}

- (void)updateFrameWithOrientation:(UIInterfaceOrientation)orientation
{
    CGFloat width = CGRectGetWidth(self.window.bounds);
    CGFloat height = CGRectGetHeight(self.window.bounds);
    if (width > height) {
        CGFloat temp = width;
        width = height;
        height = temp;
    }
    switch (orientation) {
        case UIInterfaceOrientationLandscapeLeft:
        case UIInterfaceOrientationLandscapeRight:
            self.frame = CGRectMake(0, 0, height, width);
            break;
        default:
            self.frame = CGRectMake(0, 0, width, height);
            break;
    }
}

- (void)updateWithOrientation:(UIInterfaceOrientation)orientation
{
    BOOL isIos7 = [[UIDevice currentDevice].systemVersion floatValue] < 8.0;
    BOOL isKeyboardWindow = [self.window isKindOfClass:NSClassFromString(@"UITextEffectsWindow")];
    if (isIos7 == YES && isKeyboardWindow == NO) {
        [self updateTransformWithOrientation:orientation];
    } else {
        [self updateFrameWithOrientation:orientation];
    }
}

- (void)changeOrientationHandler:(NSNotification *)notification
{
    [UIView animateWithDuration:0.25 animations:^{
        UIInterfaceOrientation orientation = (UIInterfaceOrientation)[notification.userInfo[UIApplicationStatusBarOrientationUserInfoKey] integerValue];
        [self updateWithOrientation:orientation];
    }];
}

@end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接