CALayer IOS上的自动布局约束


77

嗨,我正在开发iPhone应用程序,其中我试图为edittext设置一侧边框。我这样做的方式如下:

 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的宽度而不调整我为我的编辑文本设置的CALayer的宽度。因此,我认为我也必须对我的CALayer项目设置一些约束。但是我不知道该怎么做。有人知道吗?需要帮忙。谢谢。


1
不可能。约束条件仅适用于UIView
Max MacLeod'3

Answers:


122

整个自动调整大小业务是针对特定视图的。图层不会自动调整大小。

您要做的-在代码中-是自己调整图层的大小

例如

在viewController中,你会做

- (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

@ rmp251的文档和头文件都没有这么说,这似乎很奇怪。我的意思是我只会保留默认行为-自动布局(在ios51 +上)..也许就是我们想要的,但它并不总是正确的...所以没有AFAIK
Daij-Djan 2014年

21
如果通过自动布局约束调整拥有图层的视图的大小,则此解决方案不起作用。在这种情况下,边界和框架属性不能反映约束所确定的实际边界/框架。有什么建议么?
阿尔菲·汉森

3
@AlfieHanssen他们确实反映了实际的框架和界限。LayoutSubviews可以做到这一点。您也可以在ViewDidAppear之后触发。还调用layoutIfNeeded,InvalidateIntrinsic等...
Nick Turner

2
当您在具有一些子层的视图上使用layoutIfNeeded动画自动布局约束时,这也将不起作用
。– dvdblk

10

在中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已损坏。
有用的

6

根据答案,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;
}

5

我实现了自定义视图的layoutSubviews方法。在此方法中,我只是更新每个子图层的框架以匹配子视图图层的当前边界。

-(void)layoutSubviews{
      sublayer1.frame = self.layer.bounds;
}

最好的解决方案!
Mc.Lover

8
至少致电超级
玩家

4

创建带有虚线边框的解决方案

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
   }
}

像魅力一样工作。
user246392

1

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

添加观察者

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

观察价值

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

当我必须应用KVO时,我更喜欢用RxSwift来做(仅当我将它用于更多东西时,才不要为此添加该库。)

您也可以在该库中使用KVO,也可以在中使用KVO,viewDidLayoutSubviews但我的效果更好。

  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)

0

摆脱arango_86的答案–如果您要在自己的UIView子类中应用KVO修复程序,则更“快速”的方法是重写bounds并对其进行使用didSet,如下所示:

override var bounds: CGRect {
    didSet {
        layer.frame = bounds
    }
}

-1

我有一个类似的问题-使用带有视图的自动布局时(在代码中,不是IB),需要设置“ CALayer”的框架。

就我而言,我的层次结构有些复杂,在视图控制器中有一个视图控制器。我结束了这个SO问题,并探讨了使用的方法viewDidLayoutSubviews。那没用。万一您的情况与我的情况相似,这就是我发现的...

总览

我想设置帧为CAGradientLayerUIView,我是定位为一个内的子视图UIViewController使用自动布局约束。

调用子视图gradientView和视图控制器child_viewController

child_viewController是我将其设置为一种可重用组件的视图控制器。因此,viewofchild_viewController组成了一个父视图控制器-调用that parent_viewController

viewDidLayoutSubviewschild_viewController叫,在framegradientView尚未设定。

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

所以我继续尝试使用viewDidAppear。但是,由于嵌套性质,child_viewController我发现 viewDidAppear没有被调用。

(请参见以下SO问题:viewWillAppear,viewDidAppear不被调用,不触发)。

我目前的解决方案

我已经添加viewDidAppear进来,parent_viewController然后从那里打viewDidAppear进来child_viewController

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

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

最初设置框架后,CAGradientLayer如果布局更改,该框架可能会失效,例如旋转设备。

为了解决这个问题,我使用viewDidLayoutSubviewschild_viewController-保持的框架CAGradientLayergradientView,正确的。

它有效,但感觉不好。(有没有更好的办法?)


1
通常,您不应该手动调用视图外观/生命周期方法。Daij-Djan在此特定情况下具有正确的想法。
萨米(Sami Samhuri)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.