使用自动布局时,如何调整 CALayer 的锚点? [英] How do I adjust the anchor point of a CALayer, when Auto Layout is being used?

查看:37
本文介绍了使用自动布局时,如何调整 CALayer 的锚点?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:自从提出这个问题以来,事情已经发生了变化;请参阅此处了解近期的概览.

Note: Things have moved on since this question was asked; see here for a good recent overview.

在自动布局之前,您可以通过存储框架、设置锚点和恢复框架来更改视图层的锚点,而无需移动视图.

Before auto layout, you could change the anchor point of a view's layer without moving the view by storing the frame, setting the anchor point, and restoring the frame.

在自动布局的世界中,我们不再设置框架,但约束似乎无法将视图的位置调整回我们想要的位置.你可以破解约束来重新定位你的视图,但是在旋转或其他调整大小的事件中,这些再次变得无效.

In an auto layout world, we don't set frames any more, but constraints don't seem up to the task of adjusting the position of a view back to where we want it to. You can hack the constraints to reposition your view, but on rotation or other resizing events, these become invalid again.

以下聪明的想法不起作用,因为它创建了无效的布局属性配对(左和宽度)":

The following bright idea doesn't work as it creates an "Invalid pairing of layout attributes (left and width)":

layerView.layer.anchorPoint = CGPointMake(1.0, 0.5);
// Some other size-related constraints here which all work fine...
[self.view addConstraint:
    [NSLayoutConstraint constraintWithItem:layerView
                                 attribute:NSLayoutAttributeLeft
                                 relatedBy:NSLayoutRelationEqual 
                                    toItem:layerView 
                                 attribute:NSLayoutAttributeWidth 
                                multiplier:0.5 
                                  constant:20.0]];

我的目的是将 layerView 的左边缘(带有调整锚点的视图)设置为其宽度的一半加上 20(我想要从超级视图的左边缘插入的距离).

My intention here was to set the left edge of layerView, the view with the adjusted anchor point, to half of its width plus 20 (the distance I want inset from the left edge of the superview).

在使用自动布局布局的视图中,是否可以在不更改视图位置的情况下更改锚点?我是否需要使用硬编码值并在每次旋转时编辑约束?我希望不会.

Is it possible to change the anchor point, without changing the location of a view, in a view that is laid out with auto layout? Do I need to use hardcoded values and edit the constraint on every rotation? I do hope not.

我需要更改锚点,以便在对视图应用变换时获得正确的视觉效果.

I need to change the anchor point so that when I apply a transform to the view, I get the correct visual effect.

推荐答案

自动布局在视图转换中根本无法发挥作用.据我所知,原因是您不应该弄乱具有转换(默认标识转换除外)的视图的框架 - 但这正是自动布局所做的.自动布局的工作方式是在 layoutSubviews 中,运行时通过所有约束并相应地设置所有视图的框架.

Autolayout does not play at all well with view transforms. The reason, as far as I can discern, is that you're not supposed to mess with the frame of a view that has a transform (other than the default identity transform) - but that is exactly what autolayout does. The way autolayout works is that in layoutSubviews the runtime comes dashing through all the constraints and setting the frames of all the views accordingly.

换句话说,约束不是魔术;它们只是一个待办事项清单.layoutSubviews 是完成待办事项列表的地方.它通过设置框架来实现.

In other words, the constraints are not magic; they are just a to-do list. layoutSubviews is where the to-do list gets done. And it does it by setting frames.

我不禁认为这是一个错误.如果我将此转换应用于视图:

I can't help regarding this as a bug. If I apply this transform to a view:

v.transform = CGAffineTransformMakeScale(0.5,0.5);

我希望看到视图的中心与以前相同,并且尺寸只有原来的一半.但根据它的限制,这可能完全不是我所看到的.

I expect to see the view appear with its center in the same place as before and at half the size. But depending on its constraints, that may not be what I see at all.

[实际上,这里还有第二个惊喜:对视图应用变换会立即触发布局.这在我看来是另一个错误.或者它可能是第一个错误的核心.我期望的是能够至少在布局时间之前进行转换,例如设备被旋转 - 就像我可以在布局时间之前摆脱帧动画一样.但实际上布局时间是即时的,这似乎是错误的.]

[Actually, there's a second surprise here: applying a transform to a view triggers layout immediately. This seems to me be another bug. Or perhaps it's the heart of the first bug. What I would expect is to be able to get away with a transform at least until layout time, e.g. the device is rotated - just as I can get away with a frame animation until layout time. But in fact layout time is immediate, which seems just wrong.]

目前的一个解决方案是,如果我要对视图应用半永久性变换(而不仅仅是以某种方式暂时摆动它),则移除所有影响它的约束.不幸的是,这通常会导致视图从屏幕上消失,因为自动布局仍然发生,现在没有限制告诉我们把视图放在哪里.因此,除了移除约束之外,我还将视图的 translatesAutoresizingMaskIntoConstraints 设置为 YES.该视图现在以旧方式工作,有效地不受自动布局的影响.(显然,它受到自动布局的影响,但隐式自动调整大小掩码约束导致其行为与自动布局之前一样.)

One current solution is, if I'm going to apply a semipermanent transform to a view (and not merely waggle it temporarily somehow), to remove all constraints affecting it. Unfortunately this typically causes the view to vanish from the screen, since autolayout still takes place, and now there are no constraints to tell us where to put the view. So in addition to removing the constraints, I set the view's translatesAutoresizingMaskIntoConstraints to YES. The view now works in the old way, effectively unaffected by autolayout. (It is affected by autolayout, obviously, but the implicit autoresizing mask constraints cause its behavior to be just like it was before autolayout.)

如果这看起来有点激烈,另一种解决方案是设置约束以在预期的转换中正常工作.例如,如果视图的大小完全由其内部固定的宽度和高度确定,并且完全由其中心定位,那么我的缩放变换将按我预期的那样工作.在此代码中,我删除了子视图 (otherView) 上的现有约束,并将它们替换为这四个约束,赋予它固定的宽度和高度,并仅将其固定在其中心.之后,我的比例转换工作:

If that seems a bit drastic, another solution is to set the constraints to work correctly with an intended transform. If a view is sized purely by its internal fixed width and height, and positioned purely by its center, for example, my scale transform will work as I expect. In this code, I remove the existing constraints on a subview (otherView) and replace them with those four constraints, giving it a fixed width and height and pinning it purely by its center. After that, my scale transform works:

NSMutableArray* cons = [NSMutableArray array];
for (NSLayoutConstraint* con in self.view.constraints)
    if (con.firstItem == self.otherView || con.secondItem == self.otherView)
        [cons addObject:con];

[self.view removeConstraints:cons];
[self.otherView removeConstraints:self.otherView.constraints];
[self.view addConstraint:
 [NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterX relatedBy:0 toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:self.otherView.center.x]];
[self.view addConstraint:
 [NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeCenterY relatedBy:0 toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:self.otherView.center.y]];
[self.otherView addConstraint:
 [NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeWidth relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.width]];
[self.otherView addConstraint:
 [NSLayoutConstraint constraintWithItem:self.otherView attribute:NSLayoutAttributeHeight relatedBy:0 toItem:nil attribute:0 multiplier:1 constant:self.otherView.bounds.size.height]];

结果是,如果您没有影响视图框架的约束,则自动布局不会触及视图的框架 - 这正是涉及转换时您所追求的.

The upshot is that if you have no constraints that affect a view's frame, autolayout won't touch the view's frame - which is just what you're after when a transform is involved.

上述两种解决方案的问题在于,我们失去了约束定位视图的好处.所以这是解决这个问题的解决方案.从一个不可见的视图开始,它的工作只是作为一个宿主,并使用约束来定位它.在里面,将真实视图作为子视图.使用约束将子视图定位在宿主视图中,但将这些约束限制为在我们应用转换时不会反击的约束.

The problem with both the above solutions is that we lose the benefits of constraints to position our view. So here's a solution that solves that. Start with an invisible view whose job is solely to act as a host, and use constraints to position it. Inside that, put the real view as a subview. Use constraints to position the subview within the host view, but limit those constraints to constraints that won't fight back when we apply a transform.

这是一个说明:

白色视图为宿主视图;你应该假装它是透明的,因此是不可见的.红色视图是它的子视图,通过将其中心固定到宿主视图的中心来定位.现在我们可以毫无问题地围绕它的中心缩放和旋转红色视图,而且插图确实表明我们已经这样做了:

The white view is host view; you are supposed to pretend that it is transparent and hence invisible. The red view is its subview, positioned by pinning its center to the host view's center. Now we can scale and rotate the red view around its center without any problem, and indeed the illustration shows that we have done so:

self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);

与此同时,当我们旋转设备时,宿主视图的约束将其保持在正确的位置.

And meanwhile the constraints on the host view keep it in the right place as we rotate the device.

不要使用视图变换,而是使用图层变换,它不会触发布局,因此不会立即与约束发生冲突.

Instead of view transforms, use layer transforms, which do not trigger layout and thus do not cause immediate conflict with constraints.

例如,这个简单的throb"视图动画在自动布局下很可能会中断:

For example, this simple "throb" view animation may well break under autolayout:

[UIView animateWithDuration:0.3 delay:0
                    options:UIViewAnimationOptionAutoreverse
                 animations:^{
    v.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
    v.transform = CGAffineTransformIdentity;
}];

即使最后视图的大小没有变化,仅仅设置它的 transform 会导致布局发生,并且约束可以使视图跳转.(这感觉像是一个错误还是什么?)但是如果我们对 Core Animation 做同样的事情(使用 CABasicAnimation 并将动画应用到视图的层),布局不会发生,并且它工作正常:

Even though in the end there was no change in the view's size, merely setting its transform causes layout to happen, and constraints can make the view jump around. (Does this feel like a bug or what?) But if we do the same thing with Core Animation (using a CABasicAnimation and applying the animation to the view's layer), layout doesn't happen, and it works fine:

CABasicAnimation* ba = [CABasicAnimation animationWithKeyPath:@"transform"];
ba.autoreverses = YES;
ba.duration = 0.3;
ba.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
[v.layer addAnimation:ba forKey:nil];

这篇关于使用自动布局时,如何调整 CALayer 的锚点?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆