简体   繁体   English

如何从 UIBezierPath 创建一个 IBDesignable 自定义 UIView?

[英]How to create an IBDesignable custom UIView from a UIBezierPath?

I want to shape a UIView and be able to see its shape in the Interface Builder, when I set the following class to my UIView it fails to build, I'm not sure where's the error.我想塑造一个UIView并能够在界面生成器中看到它的形状,当我将以下类设置为我的UIView它无法构建,我不确定错误在哪里。

@IBDesignable
class CircleExampleView: UIView {

    override func layoutSubviews() {
        setupMask()
    }

    func setupMask() {

        let path = makePath()

        // mask the whole view to that shape
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask
    }

    private func makePath() -> UIBezierPath {

        //// Oval Drawing
        let ovalPath = UIBezierPath(ovalIn: CGRect(x: 11, y: 12, width: 30, height: 30))
        UIColor.gray.setFill()
        ovalPath.fill()

        return ovalPath
    }
}

A couple of observations:几个观察:

  1. You are calling setFill followed by fill , but you only do that if drawing into a graphics context (eg, in draw(_:) ).您正在调用setFill后跟fill ,但只有在绘制到图形上下文中时才这样做(例如,在draw(_:) )。 When using CAShapeLayer , you should instead set the fillColor of the CAShapeLayer .使用CAShapeLayer ,您应该设置CAShapeLayerfillColor

  2. You are using the CAShapeLayer to set the mask of your view.您正在使用CAShapeLayer来设置视图的遮罩。 If your UIView doesn't have a discernible backgroundColor , you won't see anything.如果您的UIView没有可辨别的backgroundColor ,您将看不到任何内容。

    If you set the background color of the view to, say, blue, as shown below, your mask will reveal that blue background wherever the mask allows it to (in the oval of your path).如果您将视图的背景颜色设置为蓝色,如下所示,您的蒙版将在蒙版允许的任何地方(在路径的椭圆中)显示蓝色背景。

  3. You have implemented layoutSubviews .您已经实现了layoutSubviews You generally would do that only if you were doing something here that was contingent upon the bounds of the view.通常只有当你在这里做一些取决于视图bounds的事情时,你才会这样做。 For example, here's a rendition where the oval path is based upon the bounds of the view:例如,这里的椭圆路径基于视图的bounds的再现:

     @IBDesignable class CircleView: UIView { override func layoutSubviews() { super.layoutSubviews() setupMask() } private func setupMask() { let mask = CAShapeLayer() mask.path = path.cgPath mask.fillColor = UIColor.gray.cgColor layer.mask = mask } private var path: UIBezierPath { return UIBezierPath(ovalIn: bounds) } }
  4. As E. Coms said, if you override layoutSubviews , you really should call the super implementation.正如 E. Coms 所说,如果你覆盖layoutSubviews ,你真的应该调用super实现。 This isn't critical, as the default implementation actually does nothing, but it's best practice.这并不重要,因为默认实现实际上什么都不做,但这是最佳实践。 Eg if you later changed this class to subclass some other UIView subclass, you don't want to have to go to revisit all these overrides.例如,如果您稍后将此类更改为其他UIView子类的子类,则您不需要重新访问所有这些覆盖。

  5. If you have a designable view, it's advisable to put that in a separate target.如果您有可设计的视图,建议将其放在单独的目标中。 That way, the rendering of the view in the storyboard is not dependent upon any work that may be underway in the main project.这样,故事板中视图的渲染不依赖于主项目中可能正在进行的任何工作。 As long as the designables target (often the name of your main target with Kit suffix) can build, the designable view can be rendered.只要可设计目标(通常是带有Kit后缀的主要目标的名称)可以构建,就可以渲染可设计视图。

For example, here is a rendition of your designable view, in a separate framework target, and used in a storyboard where the view in question has a blue backgroundColor :例如,这里是您的可设计视图的再现,在单独的框架目标中,并在故事板中使用,其中所讨论的视图具有蓝色backgroundColor

在此处输入图片说明


For what it's worth, I think it's exceedingly confusing to have to mask to reveal the background color inside the oval.就其价值而言,我认为必须屏蔽以显示椭圆内的背景颜色非常令人困惑。 An app developer has to set "background" color in order to set what's inside the oval, but not the background.应用程序开发人员必须设置“背景”颜色才能设置椭圆内的内容,而不是背景。

I might instead remove the "mask" logic and instead give the designable view an inspectable property, fillColor , and just add a CAShapeLayer as a sublayer, using that fillColor :我可能会删除“掩码”逻辑,而是为可设计视图提供一个可检查的属性fillColor ,然后使用该fillColor添加一个CAShapeLayer作为子图层:

@IBDesignable
class CircleView: UIView {

    private var shapeLayer = CAShapeLayer()

    @IBInspectable var fillColor: UIColor = .blue {
        didSet {
            shapeLayer.fillColor = fillColor.cgColor
        }
    }

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)

        configure()
    }

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

        configure()
    }

    private func configure() {
        shapeLayer.fillColor = fillColor.cgColor
        layer.addSublayer(shapeLayer)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        shapeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
    }
}

This accomplishes the same thing, but I think the distinction of fill colors vs background colors is more intuitive.这完成了同样的事情,但我认为填充颜色与背景颜色的区别更直观。 But you may have had other reasons for using the masking approach, but just make sure if you do that, that you have something to reveal after it's masked (eg a background color or something else you're rendering).但是您可能有其他原因使用遮罩方法,但只要确保如果您这样做,在遮罩后您有一些东西要显示(例如背景颜色或您正在渲染的其他东西)。

Please add "super.layoutSubviews()"请添加“super.layoutSubviews()”

override func layoutSubviews() {
    setupMask()
    super.layoutSubviews()
}

By this way, your design view will be "up to date" .通过这种方式,您的设计视图将是“最新的”。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM