【问题标题】:Auto Layout constraint on CALayer IOSCALayer IOS上的自动布局约束
【发布时间】:2014-08-09 19:05:31
【问题描述】:

您好,我正在开发 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 项目设置一些限制。但我不知道该怎么做。任何人都知道这件事吗?需要帮忙。谢谢。

【问题讨论】:

  • 不可能。约束仅适用于 UIView

标签: ios autolayout calayer


【解决方案1】:

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

你必须做的——在代码中——是自己调整层的大小

例如

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

【讨论】:

  • 如果你使用 layoutSubviews 你应该在 super 上调用它
  • @rmp251 文档和头文件都没有这么说,这看起来很奇怪。我的意思是我只会保留默认行为 - 自动布局(在 ios51+ 上).. 可能是一个人想要的,但它并不总是正确的......所以没有 AFAIK
  • 如果拥有该层的视图通过自动布局约束调整大小,则此解决方案不起作用。在这种情况下,边界和框架属性不反映由约束确定的实际边界/框架。有什么建议吗?
  • @AlfieHanssen 他们确实反映了实际的框架和边界。 LayoutSubviews 就是这样做的。您也可以在 ViewDidAppear 之后触发。还调用 layoutIfNeeded、InvalidateIntrinsic 等...
  • 当您在一个视图上使用 layoutIfNeeded 动画自动布局约束时,这也不起作用,该视图具有一些应该反映视图框架的子层..
【解决方案2】:

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

更多信息here

【讨论】:

  • 这非常有效,我认为这是使用“自动布局”而不是在 *layoutsubviews 中执行的最佳方法
  • 答案中的 URL 已损坏。
【解决方案3】:

根据this 的回答,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;
}

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 最佳解决方案!
    • 至少叫 super
    【解决方案5】:

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

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

    【讨论】:

    • 工作就像一个魅力。
    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      当我必须应用 KVO 时,我更喜欢使用 RxSwift(仅当我将它用于更多内容时,不要为此添加此库。)

      您也可以将 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)
      

      【讨论】:

        【解决方案8】:

        复制arango_86's answer - 如果您在自己的 UIView 子类中应用 KVO 修复,更“快速”的方法是覆盖 bounds 并在其上使用 didSet,如下所示:

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

        【讨论】:

          【解决方案9】:

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

          在我的例子中,我有一个稍微复杂的层次结构,在视图控制器中有一个视图控制器。我最终解决了这个 SO 问题,并研究了使用 viewDidLayoutSubviews 的方法。那没有用。以防万一您的情况与我的情况相似,这就是我发现的...

          概述

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

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

          child_viewController 是一个视图控制器,我将其设置为一种可重用的组件。所以,child_viewControllerview 被组合成一个父视图控制器 - 称之为 parent_viewController

          当调用child_viewControllerviewDidLayoutSubviews 时,尚未设置gradientViewframe

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

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

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

          我目前的解决方案

          我在parent_viewController 中添加了viewDidAppear,然后我在child_viewController 中调用viewDidAppear

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

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

          在初始设置 CAGradientLayer 的框架后 - 如果布局更改,该框架可能会变得无效 - 例如旋转设备。

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

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

          【讨论】:

          • 一般不应该手动调用视图外观/生命周期方法。 Daij-Djan 在这种特殊情况下的想法是正确的。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-02-04
          • 1970-01-01
          • 1970-01-01
          • 2013-08-23
          相关资源
          最近更新 更多