转换子视图时调用的 UIViewController viewDidLayoutSubviews

Posted

技术标签:

【中文标题】转换子视图时调用的 UIViewController viewDidLayoutSubviews【英文标题】:UIViewController viewDidLayoutSubviews called when transforming a subview 【发布时间】:2014-08-22 20:25:24 【问题描述】:

我有以下用例:

一个视图控制器 (RotationVC),其中包含一些逻辑,其中包括旋转其中一个子视图(通过将其转换设置为 CGAffineTransformMakeRotation) 此 RotationVC 覆盖 viewDidLayoutSubviews 方法以将其子视图设置为与父视图 (self.view) 一样大 (创建视图控制器及其视图都是以编程方式完成的,它必须保持这种方式;我尝试在子视图上使用具有灵活宽度和灵活高度的 autoresizingMask,但它不起作用) 另一个视图控制器 (ParentVC) 将 RotationVC 添加为其子视图,将其视图添加为其子视图,并将其框架在某个点设置为方形 - 想法是,当设置框架时,将调用并设置 RotationVC viewDidLayoutSubviews子视图以正确大小(实际上工作正常),然后一切正常 (RotationVC 也将在其他视图控制器中使用,并且具有不同的大小,这就是它被提取到“子”视图控制器的原因,并且它的大小应该是灵活的)

嗯,它不能正常工作。问题是每次旋转后,都会调用 RotationVC viewDidLayoutSubviews 方法,该方法设置旋转后的视图框架。这是错误的,根据 Apple 的文档,当视图的转换不是身份时(在这种情况下),使用 frame 属性是错误的。结果,旋转的视图是“倾斜的”。这是委托的源代码,您应该能够将其粘贴到单个文件并运行。如果你这样做了,我想要实现的是让旋转的子视图保持它的形状(在这个例子中它是一个矩形):

#import "AppDelegate.h"

@interface RotationVC : UIViewController

@property (nonatomic, weak) UIView *background;
@property (nonatomic, weak) UIView *foreground;
@property (nonatomic) int degrees;

@end

static const double DURATION = 0.5;

@implementation RotationVC

- (void)viewDidLoad 
    [super viewDidLoad];

    UIView *tmp = [UIView new];
    self.background = tmp;
    self.background.layer.borderWidth = 2;
    self.background.layer.borderColor = [UIColor redColor].CGColor;
    [self.view addSubview:self.background];

    tmp = [UIView new];
    self.foreground = tmp;
    self.foreground.layer.borderWidth = 2;
    self.foreground.layer.borderColor = [UIColor blueColor].CGColor;
    [self.view addSubview:self.foreground];


- (void)viewDidLayoutSubviews 
    [super viewDidLayoutSubviews];

    self.background.frame = self.view.bounds;
    CGAffineTransform tmp = self.foreground.transform;
    self.foreground.transform = CGAffineTransformIdentity;
    self.foreground.frame = self.view.bounds;
    self.foreground.transform = tmp;


- (void)viewWillAppear:(BOOL)animated 
    [super viewWillAppear:animated];

    [self performSelector:@selector(rotate) withObject:self];


- (void)rotate 
    self.degrees = (self.degrees + 15) % 360;
    [UIView animateWithDuration:DURATION
                          delay:0
                        options:UIViewAnimationOptionCurveLinear|UIViewAnimationOptionBeginFromCurrentState
                     animations:^
                         self.foreground.transform = CGAffineTransformMakeRotation(self.degrees * M_PI/180);
                     
                     completion:^(BOOL finished) 
                         [self performSelector:_cmd withObject:self afterDelay:0];
                     ];


@end



@interface ParentVC : UIViewController

@property (nonatomic, weak) RotationVC *rotationVC;

@end

@implementation ParentVC

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];

    if (self) 
        RotationVC *tmp = [RotationVC new];
        self.rotationVC = tmp;
        [self addChildViewController:self.rotationVC];
        [self.rotationVC didMoveToParentViewController:self];
    

    return self;


- (void)viewDidLoad 
    [super viewDidLoad];

    [self.view addSubview:self.rotationVC.view];


- (void)viewWillAppear:(BOOL)animated 
    [super viewWillAppear:animated];

    self.rotationVC.view.bounds = CGRectMake(0, 0, 100, 100);
    self.rotationVC.view.center = self.view.center;


@end



@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    UINavigationController *navi = [[UINavigationController alloc] initWithRootViewController:[ParentVC new]];
    navi.navigationBarHidden = YES;
    self.window.rootViewController = navi;
    [self.window makeKeyAndVisible];
    return YES;


@end

有一个有效的技巧 - 在 vi​​ewDidLayoutSubviews 方法中,将旋转视图的变换存储到临时变量,将视图的变换设置为标识,设置框架,然后再次将其设置为旋转 - 这太丑了,我打赌必须有更好的方法。我还能够设计出其他一些解决方法,但每一个都比前一个更丑……

在这种情况下,有没有人知道该怎么做?

【问题讨论】:

【参考方案1】:

我能够解决这个问题。只需使用以下代码:

self.foreground.bounds = self.view.bounds;
self.foreground.center = self.background.center;

而不是设置框架。

这在https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/WindowsandViews/WindowsandViews.html#//apple_ref/doc/uid/TP40009503-CH2-SW7中有解释:

重要提示:如果视图的 transform 属性不是恒等变换,则该视图的 frame 属性的值是未定义的,必须被忽略。将变换应用于视图时,必须使用视图的边界和中心属性来获取视图的大小和位置。任何子视图的框架矩形仍然有效,因为它们是相对于视图边界的。

我一直以为设置frame就是同时设置bounds和center,但显然这很不正确。

【讨论】:

以上是关于转换子视图时调用的 UIViewController viewDidLayoutSubviews的主要内容,如果未能解决你的问题,请参考以下文章

推送通知进入时调用的方法序列

UIAlertView 隐藏/删除时调用的通知或委托

显示和隐藏 uisearchcontroller 时调用的函数

在视图动画时调用 setVisibility

当我的 UIViewController 被弹出时调用的方法?

iOS 生命周期 -initviewDidLoadviewWillAppearviewDidAppearviewWillDisappearviewDidDisappear 区别和用途