自动布局、约束和旋转

Posted

技术标签:

【中文标题】自动布局、约束和旋转【英文标题】:AutoLayout, constraints, and rotation 【发布时间】:2013-03-13 12:09:33 【问题描述】:

我在自动布局和约束方面遇到问题,需要一些帮助。

我在 iPad 上运行此应用程序。我有一个包含两个视图的窗口,一个 UIWebView 和一个 MKMapView。这两个视图都是在 IB 中设置的,并且 Auto Layout 是打开的。 UIWebView 位于窗口顶部,MKMapView 位于底部。每个视图几乎占据了窗口的一半。 UIWebView 在 IB 中设置了以下约束:NSLayoutAttributeTop 到 Superview 等于 0,Leading Edge 到 Superview 等于 0,Trailing Edge 到 Superview 等于 0,NSLayoutAttributeBottom 到 Superview 等于 480。 MKMapView 设置了以下约束在 IB 中:NSLayoutAttributeTop 到 Superview 等于 480,Leading Edge 到 Superview 等于 0,Trailing Edge 到 Superview 等于 0,NSLayoutAttributeBottom 到 Superview 等于 0。

当窗口被加载时,MKMapView 实际上被移除了,因为我希望 UIWebView 占据整个屏幕,因为在地图视图中没有数据可以显示。这是在我的 updateDetailViews 函数中完成的:

- (void)updateDetailViews

displayHeight = self.maximumUsableFrame.size.height;
viewDistance=displayHeight/2+centerMapButton.frame.size.height/2+16;

[detailMapView setTranslatesAutoresizingMaskIntoConstraints:NO];
[directoryWebView setTranslatesAutoresizingMaskIntoConstraints:NO];

if (mapViewVisible==true) 
    webViewDistFromBottomDefault=viewDistance;
    webViewDistFromBottom.constant=viewDistance;
    mapViewDistFromTopDefault=viewDistance;
    mapViewDistFromTop.constant=viewDistance;


else 
    [detailMapView removeFromSuperview];
    webViewDistFromBottomDefault=0;
    webViewDistFromBottom.constant=0;
    mapViewDistFromTopDefault=viewDistance;
    mapViewDistFromTop.constant=viewDistance;


[detailMapView setNeedsUpdateConstraints];
[UIView animateWithDuration:0.25f animations:^
    [self.detailMapView layoutIfNeeded];
];


移除MKMapView后,UIWebview的NSLayoutAttributeBottom属性设置为0,并填满整个屏幕。一旦地图中显示了实际数据,然后添加 MKMapView,并在我的 displayMapView 函数中重新定位 UIWebView 以及必要的约束:

- (void)displayMapView

double dblLatitude;
double dblLongitude;


[detailMapView setTranslatesAutoresizingMaskIntoConstraints:NO];
if ([self isMapViewDisplayed]==FALSE) 

    [detailView addSubview:detailMapView];

    NSLayoutConstraint *myConstraint =[NSLayoutConstraint
                                       constraintWithItem:detailMapView
                                       attribute:NSLayoutAttributeTop
                                       relatedBy:NSLayoutRelationEqual
                                       toItem:detailView
                                       attribute:NSLayoutAttributeTop
                                       multiplier:1.0
                                       constant:mapViewDistFromTopDefault];

    [detailView addConstraint:myConstraint];

    //mapViewDistFromTop.constant = mapViewDistFromTopDefault;

    myConstraint =[NSLayoutConstraint
                   constraintWithItem:detailMapView
                   attribute:NSLayoutAttributeBottom
                   relatedBy:NSLayoutRelationEqual
                   toItem:detailView
                   attribute:NSLayoutAttributeBottom
                   multiplier:1.0
                   constant:0];

    [detailView addConstraint:myConstraint];

    myConstraint =[NSLayoutConstraint
                   constraintWithItem:detailMapView
                   attribute:NSLayoutAttributeLeading
                   relatedBy:NSLayoutRelationEqual
                   toItem:detailView
                   attribute:NSLayoutAttributeLeading
                   multiplier:1.0
                   constant:0];

    [detailView addConstraint:myConstraint];

    myConstraint =[NSLayoutConstraint
                   constraintWithItem:detailMapView
                   attribute:NSLayoutAttributeTrailing
                   relatedBy:NSLayoutRelationEqual
                   toItem:detailView
                   attribute:NSLayoutAttributeTrailing
                   multiplier:1.0
                   constant:0];

    [detailView addConstraint:myConstraint];
    [detailMapView setNeedsUpdateConstraints];


[UIView animateWithDuration:.5
                 animations:^
                     webViewDistFromBottom.constant=webViewDistFromBottomDefault;
                     mapViewDistFromTop.constant=mapViewDistFromTopDefault;
                     [self.directoryWebView layoutIfNeeded];
                     [self.detailMapView layoutIfNeeded];
                 ];

[self updateDetailViews];

...

if ((dblLatitude != 0) && (dblLongitude != 0)) 
    zoomLocation.latitude = dblLatitude;
    zoomLocation.longitude = dblLongitude;

    MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, METERS_PER_MILE, METERS_PER_MILE);
    MKCoordinateRegion adjustedRegion = [detailMapView regionThatFits:viewRegion];
    [detailMapView setRegion:adjustedRegion animated:YES];

CLLocationCoordinate2D coordinate;
coordinate.latitude = dblLatitude;
coordinate.longitude = dblLongitude;

...

[detailMapView addAnnotation:annotation];

所有这些都按照我的意图进行。旋转设备时会出现此问题。如果我从 iPad 以纵向模式开始,webViewDistFromBottom 和 mapViewDistFromTop 约束设置为 490,因为上面的 updateDetailViews 函数具有以下计算:

displayHeight = self.maximumUsableFrame.size.height;
viewDistance=displayHeight/2+centerMapButton.frame.size.height/2+16;

如果 iPad 旋转到横向,将调用 willAnimateRotationToInterfaceOrientation 函数,然后调用 updateDetailViews,将 viewDistance 设置为 367(以及相应的 webViewDistFromBottom.constant 和 mapViewDistFromTop.constant)。顶部的 UIWebView 看起来应该如此,但是底部的 MKMapView 却没有。 mapViewDistFromTop 约束设置为 367(如果我将值输出到日志),但它似乎仍设置为 490。我的 updateDetailViews 函数调用 [self.view layoutIfNeeded](我也尝试过 [detailMapView layoutIfNeeded], [detailMapView setNeedsLayout]),但该视图未正确显示。与顶部的距离太大。如果我将 iPad 转回纵向,它看起来还不错。

如果 iPad 以横向模式启动,然后旋转为纵向模式,我也会遇到同样的问题。在横向模式下,mapViewDistFromTop 和 webViewDistFromBottom 的值为 367,一旦旋转为纵向,则设置为 490。但是,底部的MKMapView看起来和顶部的距离仍然是367,覆盖了太多的显示。

知道我做错了什么吗?提前感谢您的帮助!!!

【问题讨论】:

【参考方案1】:

如果我正确理解了这个问题,那么在纵向模式下,您需要一个底部高度为 480 点的地图视图;如果在横向模式下,您希望网络视图占据整个屏幕。另一种方法是只修改地图视图的高度(纵向为 480,横向为 0)。不要删除地图视图,只需将其高度设置为 0。让现有的约束处理其他所有事情。然后没有添加约束、视图等的修改。您需要做的就是在旋转时调整一个常数。这样做有用吗?

为了说明这一点,在这种情况下,我建议您设置约束(我将只关注垂直约束),以便它们等效于

V:|[webView][mapView(480)]|

(我不建议您使用 VFL 来指定约束,但这只是表达我使用的一系列约束的最简洁的方式。)注意,请确保您没有任何无关的约束(例如Web 视图底部约束到超级视图等)。我建议在垂直维度上仅使用这些约束(Web 视图顶部到超级视图、Web 视图底部到地图视图顶部、地图视图高度和地图视图底部到超级视图)。

然后,为mapView 的高度约束定义并链接出口:

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *mapViewHeightConstraint;

最后,在旋转时,只需更改该约束的常量:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration

    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];

    if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation))
        self.mapViewHeightConstraint.constant = 480.0;
    else
        self.mapViewHeightConstraint.constant = 0.0;

如果我了解您所追求的 UI,我认为这就是您所需要的,消除您问题中的所有其他代码。我刚刚测试了它,它似乎工作正常。

【讨论】:

好的,我确实让事情变得比我应该做的更难......谢谢你的帮助!!!

以上是关于自动布局、约束和旋转的主要内容,如果未能解决你的问题,请参考以下文章

任何自动布局大师能够启发我约束和旋转?

ScrollView 自动布局约束打破设备旋转

在屏幕旋转之前,自动布局约束不正确? [复制]

设备旋转时是不是有动画自动布局(约束)?

自动布局 [Snapkit] 更改旋转约束

使用自动布局约束旋转后对齐 UILabel 文本