iOS中CALayer的自动布局约束

83

你好,我正在开发iPhone应用程序,尝试为编辑文本设置单侧边框。我是按照以下方式实现的:

 int borderWidth = 1;
CALayer *leftBorder = [CALayer layer];

leftBorder.borderColor = [UIColor whiteColor].CGColor;
leftBorder.borderWidth = borderWidth;

leftBorder.frame = CGRectMake(0, textField.frame.size.height - borderWidth, textField
                              .frame.size.width, borderWidth);
[textField.layer addSublayer:leftBorder];

我在IB中对我的EditText设置了一些限制,这样当我旋转设备时,它会根据设备的宽度调整文本框的宽度。我的问题是,它只调整了EditText的宽度,而没有调整我为EditText设置的CALayer的宽度。所以我认为我也必须为我的CALayer项设置一些约束。但是我不知道如何做到这一点。有人知道吗?需要帮助。谢谢。


1
仅对UIView有效的限制。 - Max MacLeod
10个回答

131

整个自动调整大小的业务与视图有关。层不会自动调整大小。

您需要在代码中手动调整图层大小。

例如,在视图控制器中,您将执行以下操作:

- (void) viewDidLayoutSubviews {
  [super viewDidLayoutSubviews]; //if you want superclass's behaviour... 
  // resize your layers based on the view's new frame
  self.editViewBorderLayer.frame = self.editView.bounds;
}

或者在自定义的UIView中,您可以使用

- (void)layoutSubviews {
  [super layoutSubviews]; //if you want superclass's behaviour...  (and lay outing of children)
  // resize your layers based on the view's new frame
  layer.frame = self.bounds;
}

1
如果您正在使用layoutSubviews,应该在super上调用它。 - rmp251
22
如果拥有图层的视图是通过自动布局约束进行大小调整,则此解决方案无法使用。在这种情况下,边界和框架属性不反映由约束确定的实际边界/框架。有什么建议吗? - Alfie Hanssen
3
它们确实反映了实际的框架和边界。LayoutSubviews实现这一点。您还可以在ViewDidAppear之后调用。还可以调用layoutIfNeeded、InvalidateIntrinsic等。 - Nick Turner
2
当您在使用layoutIfNeeded对一个视图进行自动布局约束的动画处理时,如果该视图具有一些子层应该反映该视图的框架,则此方法也无法正常工作。 - David
1
一切正常,谢谢! - user21331610
显示剩余3条评论

14

Swift 5 中,你可以尝试以下解决方案:

使用 KVO,监听路径为 "YourView.bounds",代码如下。

self.addObserver(self, forKeyPath: "YourView.bounds", options: .new, context: nil)

然后按照下面的方式处理。

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "YourView.bounds" {
            YourLayer.frame = YourView.bounds
            return
        }
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }

更多关于此内容的信息在这里


1
这个方法非常有效,我认为这是处理“自动布局”时采取的最佳方法,而不是在 *layoutsubviews 中完成。 - Salil Junior
答案中的URL已经失效。 - ThE uSeFuL
1
URL仍然无法访问。 - user21331610
不确定问题出在哪里。对我来说,引用的URL仍然有效。https://www.marcosantadev.com/calayer-auto-layout-swift/ - arango_86

9
根据这个答案,layoutSubviews并不会在所有情况下都被调用。我发现使用这个代理方法会更有效:

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if(self != nil) {
        [self.layer addSublayer:self.mySublayer];
    }
    return self;
}

- (void)layoutSublayersOfLayer:(CALayer*)layer
{
    self.mySublayer.frame = self.bounds;
}

7

当我使用虚线边框创建时的解决方案

class DashedBorderView: UIView {

   let dashedBorder = CAShapeLayer()

   override init(frame: CGRect) {
       super.init(frame: frame)
       commonInit()
   }

   required init?(coder aDecoder: NSCoder) {
       super.init(coder: aDecoder)
       commonInit()
   }

   private func commonInit() {
       //custom initialization
       dashedBorder.strokeColor = UIColor.red.cgColor
       dashedBorder.lineDashPattern = [2, 2]
       dashedBorder.frame = self.bounds
       dashedBorder.fillColor = nil
       dashedBorder.cornerRadius = 2
       dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
       self.layer.addSublayer(dashedBorder)
   }

   override func layoutSublayers(of layer: CALayer) {
       super.layoutSublayers(of: layer)
       dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
       dashedBorder.frame = self.bounds
   }
}

7
我实现了自定义视图的layoutSubviews方法;在这个方法内,我只是更新每个子层的框架以匹配我的子视图层的当前边界。
-(void)layoutSubviews{
      sublayer1.frame = self.layer.bounds;
}

最佳解决方案! - iOS.Lover
10
请至少调用super方法。 - Daij-Djan

1

Swift 3.x KVO 解决方案 (更新 @arango_86 的答案)

添加观察者

self.addObserver(
    self, 
    forKeyPath: "<YOUR_VIEW>.bounds", 
    options: .new, 
    context: nil
)

Observe Values

override open func observeValue(
    forKeyPath keyPath: String?, 
    of object: Any?, 
    change: [NSKeyValueChangeKey : Any]?, 
    context: UnsafeMutableRawPointer?
) {
    if (keyPath == "<YOUR_VIEW.bounds") {
      updateDashedShapeLayerFrame()
      return
    }

    super.observeValue(
        forKeyPath: keyPath, 
        of: object, 
        change: change, 
        context: context
    )
}

1
借鉴arango_86的答案,如果您正在自己的UIView子类中应用KVO修复程序,则更“Swifty”的方法是覆盖bounds并在其上使用didSet,如下所示:
override var bounds: CGRect {
    didSet {
        layer.frame = bounds
    }
}

1

当我需要应用KVO时,我更喜欢使用RxSwift(仅在我使用它进行更多工作时,不要仅为此添加此库。)

您也可以使用此库或在viewDidLayoutSubviews中应用KVO,但我使用此方法取得了更好的结果。

  view.rx.observe(CGRect.self, #keyPath(UIView.bounds))
        .subscribe(onNext: { [weak self] in
            guard let bounds = $0 else { return }
            self?.YourLayer.frame = bounds
        })
        .disposed(by: disposeBag)

1

只需在您的自定义视图中实现此方法即可。

override func layoutSubviews() {
        super.layoutSubviews()
        
        sublayer1.frame = self.layer.bounds
        
    }

-1

我遇到了相似的问题 - 在使用自动布局与视图(用代码编写,而非 IB)时需要设置 “CALayer” 的框架。

在我的情况下,我有一个稍微复杂的层次结构,即在一个视图控制器中嵌套另一个视图控制器。我最终找到了这个 Stack Overflow 的问题,并研究了使用“viewDidLayoutSubviews”的方法。但是它没有起作用。以防您的情况类似于我自己的情况,以下是我发现的内容...

概述

我想为我正在定位为 UIViewController 中的子视图的 UIViewCAGradientLayer 设置框架,使用自动布局约束。

将子视图命名为 gradientView,视图控制器命名为 child_viewController

child_viewController 是我设置为可重复使用组件的视图控制器。因此,“child_viewController”的“view”正在组合到父视图控制器中 - 称为 parent_viewController

当调用child_viewControllerviewDidLayoutSubviews时,gradientViewframe尚未设置。

(在这一点上,我建议您散布一些NSLog语句,以了解视图层次结构中视图创建的顺序等)

因此,我尝试使用viewDidAppear。 然而,由于child_viewController的嵌套性质,我发现viewDidAppear没有被调用。

(请参阅此SO问题:viewWillAppear, viewDidAppear not being called, not firing)。

我的当前解决方案

我已经将viewDidAppear添加到了parent_viewController中,然后从那里调用child_viewController中的viewDidAppear

对于初始加载, 我需要 viewDidAppear,因为直到在 child_viewController 中调用它时,所有的子视图才有了它们的框架设置。然后我可以为 CAGradientLayer 设置框架...

我说这是我的 当前解决方案,因为我对此并不特别满意。

在最初设置 CAGradientLayer 的框架之后,如果布局发生更改(例如旋转设备),那么该框架可能会变得无效。

为了处理这个问题,我在 child_viewController 中使用 viewDidLayoutSubviews 来确保 CAGradientLayer 的框架保持在 gradientView 内。

它能工作,但感觉不太好。 (有更好的方法吗?)


2
通常情况下,您不应手动调用视图的外观/生命周期方法。在这种特定情况下,Daij-Djan 的想法是正确的。 - Sami Samhuri

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