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

How to create an IBDesignable custom UIView from a UIBezierPath?

我想塑造一个 UIView 并能够在 Interface Builder 中看到它的形状,当我将以下 class 设置为我的 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
    }
}

请加"super.layoutSubviews()"

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

这样,您的设计视图将是"up to date"。

一些观察:

  1. 您正在调用 setFill,然后调用 fill,但您只有在绘制到图形上下文中时才这样做(例如,在 draw(_:)). When using CAShapeLayer, you should instead set the fillColor 中15=].

  2. 您正在使用 CAShapeLayer 设置视图的遮罩。如果您的 UIView 没有可识别的 backgroundColor,您将看不到任何东西。

    如果您将视图的背景颜色设置为蓝色,如下所示,您的蒙版将在蒙版允许的任何位置(在您的路径的椭圆形中)显示蓝色背景。

  3. 您已实施 layoutSubviews。通常,只有当您在这里做的事情取决于视图的 bounds 时,您才会这样做。例如,这是一个再现,其中椭圆路径基于视图的 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. 正如 E. Coms 所说,如果您覆盖 layoutSubviews,您确实应该调用 super 实现。这并不重要,因为默认实现实际上什么都不做,但这是最佳实践。例如。如果您稍后将此 class 更改为 subclass 一些其他 UIView subclass,您不想重新访问所有这些覆盖。

  5. 如果您有可设计的视图,建议将其放在单独的目标中。这样,故事板中视图的呈现不依赖于主项目中可能正在进行的任何工作。只要designables target(通常是你的主目标名称加Kit后缀)能build,就能渲染designable view

例如,这是您的可设计视图的再现,在单独的框架目标中,并在故事板中使用,其中相关视图具有蓝色 backgroundColor:


就其价值而言,我认为必须屏蔽以显示椭圆内的背景颜色是非常令人困惑的。应用开发者必须设置 "background" 颜色才能设置椭圆内的内容,但不能设置背景。

我可能会移除 "mask" 逻辑,而是给可设计的视图一个可检查的 属性、fillColor,然后添加一个 CAShapeLayer 作为子层,使用fillColor:

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

这完成了同样的事情,但我认为填充颜色与背景颜色的区别更直观。但是您可能有其他原因使用遮罩方法,但只要确保如果您这样做,遮罩后您有一些东西可以显示(例如背景颜色或您正在渲染的其他东西)。