使用自动布局时,如何调整CALayer的锚点?

jrt*_*ton 159 calayer ios autolayout

注意:自问这个问题以来,事情已经发生了变化; 请看这里有一个很好的近期概述.


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

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

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

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]];
Run Code Online (Sandbox Code Playgroud)

在这里我的目的是左边缘设置layerView,调整后的定位点来看,它的宽度加上20(我从上海华盈的左边缘要插入的距离)的一半.

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

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

mat*_*att 357

[编辑:警告:整个随后的讨论可能会过时或至少在iOS 8中大幅减轻,这可能不再是在应用视图转换时触发布局的错误.

Autolayout与视图转换

通过视图转换,Autolayout并不能很好地发挥作用.据我所知,原因是你不应该混淆具有变换的视图的帧(除了默认的身份变换) - 但这正是autolayout所做的.自动布局的工作方式是在layoutSubviews运行时中突破所有约束并相应地设置所有视图的帧.

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

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

v.transform = CGAffineTransformMakeScale(0.5,0.5);
Run Code Online (Sandbox Code Playgroud)

我希望看到视图的中心与之前相同,并且大小只有一半.但取决于它的限制,这可能不是我所看到的.

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

解决方案1:没有约束

一个当前的解决方案是,如果我要对视图应用半永久变换(并且不仅仅是以某种方式暂时摇摆它),则删除所有影响它的约束.不幸的是,这通常会导致视图从屏幕上消失,因为仍然会发生自动布局,现在没有任何约束来告诉我们放置视图的位置.因此,除了删除约束之外,我还将视图设置translatesAutoresizingMaskIntoConstraints为YES.视图现在以旧方式工作,实际上不受自动布局的影响.(这由自动布局的影响,很明显,但隐含的自动尺寸面具限制导致其行为是就像它是自动布局之前.)

解决方案2:仅使用适当的约束

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

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]];
Run Code Online (Sandbox Code Playgroud)

结果是,如果你没有影响视图框架的约束,autolayout将不会触及视图的框架 - 这正是你在涉及转换时所追求的.

解决方案3:使用子视图

上述解决方案的问题在于我们失去了约束的好处来定位我们的观点.所以这是解决这个问题的解决方案.从一个看不见的视图开始,其作用仅仅是充当主机,并使用约束来定位它.在其中,将真实视图作为子视图.使用约束将子视图定位在主机视图中,但将这些约束限制为在应用转换时不会反击的约束.

这是一个例子:

在此输入图像描述

白色视图是主视图; 你应该假装它是透明的,因此是看不见的.红色视图是其子视图,通过将其中心固定到主视图的中心来定位.现在我们可以毫不费力地围绕其中心缩放和旋转红色视图,实际上插图显示我们已经这样做了:

self.otherView.transform = CGAffineTransformScale(self.otherView.transform, 0.5, 0.5);
self.otherView.transform = CGAffineTransformRotate(self.otherView.transform, M_PI/8.0);
Run Code Online (Sandbox Code Playgroud)

同时,当我们旋转设备时,主机视图上的约束使其保持在正确的位置.

解决方案4:改为使用层转换

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

例如,这个简单的"悸动"视图动画可能会在autolayout下破坏:

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

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

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];
Run Code Online (Sandbox Code Playgroud)

  • 变换不是导致我问题的原因,只是设置新的锚点.我将去除约束并尝试自动调整掩码. (3认同)
  • 在iOS 8上,图层转换还会触发自动布局 (2认同)

sen*_*len 40

我有一个类似的Isuue,刚从Apple的Autolayout团队那里听到了回复.他们建议使用容器视图方法暗示,但他们创建一个UIView子类来覆盖layoutSubviews并在那里应用自定义布局代码 - 它就像一个魅力

头文件看起来像这样,以便您可以直接从Interface Builder链接您选择的子视图

#import <UIKit/UIKit.h>

@interface BugFixContainerView : UIView
@property(nonatomic,strong) IBOutlet UIImageView *knobImageView;
@end
Run Code Online (Sandbox Code Playgroud)

并且m文件应用特殊代码:

#import "BugFixContainerView.h"

@implementation BugFixContainerView
- (void)layoutSubviews
{
    static CGPoint fixCenter = {0};
    [super layoutSubviews];
    if (CGPointEqualToPoint(fixCenter, CGPointZero)) {
        fixCenter = [self.knobImageView center];
    } else {
        self.knobImageView.center = fixCenter;
    }
}
@end
Run Code Online (Sandbox Code Playgroud)

如您所见,它在第一次调用时抓取View的中心点,并在进一步调用中重用该Position,以便相应地放置View.从这个意义上说,它会覆盖Autolayout代码,它发生在[super layoutSubviews]之后; 其中包含自动布局代码.

就像那样,不再需要避免Autolayout,但是当默认行为不再合适时,您可以创建自己的自动布局.当然你可以应用比那个例子更复杂的东西,但这就是我需要的,因为我的App只能使用Portrait Mode.

  • 感谢您发布此内容.自从我写完答案以来,我一定明白了重写`layoutSubviews`的价值.不过,我想补充一点,那里的Apple只是在做*你*做我认为*他们应该做的事情(在他们的Autolayout的实施中). (5认同)
  • 自动布局团队自己建议创建自定义容器子类只是为了解决autolayout如何破坏视图系统中长期和直观的行为,这是令人遗憾和荒谬的. (4认同)

see*_*nte 7

我找到了一个简单的方法.它适用于iOS 8和iOS 9.

就像在使用基于帧的布局时调整anchorPoint一样:

let oldFrame = layerView.frame
layerView.layer.anchorPoint = newAnchorPoint
layerView.frame = oldFrame
Run Code Online (Sandbox Code Playgroud)

当您使用自动布局调整视图的锚点时,您会以约束方式执行相同的操作.当anchorPoint从(0.5,0.5)变为(1,0.5)时,layerView将向左移动一段距离视图宽度的一半,因此您需要对此进行补偿.

我不明白问题中的约束.因此,假设您使用常量添加相对于superView centerX的centerX约束:layerView.centerX = superView.centerX + constant

layerView.layer.anchorPoint = CGPoint(1, 0.5)
let centerXConstraint = .....
centerXConstraint.constant = centerXConstraint.constant + layerView.bounds.size.width/2
Run Code Online (Sandbox Code Playgroud)