首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Swift IBInspectable didSet与get/set

Swift IBInspectable didSet与get/set
EN

Stack Overflow用户
提问于 2018-07-21 05:28:04
回答 4查看 2.3K关注 0票数 2
代码语言:javascript
复制
@IBInspectable var buttonBorderWidth: CGFloat = 1.0 {
    didSet {
        updateView()
    }
}

func updateView() {
    // Usually there are more entries here for each IBInspectable
    self.layer.borderWidth = buttonBorderWidth
}

但在某些情况下,它们使用get和set,例如

代码语言:javascript
复制
@IBInspectable
var shadowOpacity: Float {
    get {
        return layer.shadowOpacity
    }
    set {
        layer.shadowOpacity = newValue
    }
}

有人能解释一下:在这些情况下发生了什么,以及如何选择使用哪种情况?

EN

回答 4

Stack Overflow用户

发布于 2018-07-21 15:02:34

我看到两个问题。第一个问题是“在这些情况下发生了什么”,最好的答案是阅读the “Properties” chapter of 。已经发布了另外三个答案,解决了第一个问题,但没有一个回答第二个,也是更有趣的问题。

第二个问题是“如何选择使用哪一个”。

buttonBorderWidth示例(具有观察者的存储属性)相比,shadowOpacity示例(它是一个计算属性)具有以下优点:

  • 所有的shadowOpacity-related代码都在一个地方,因此更容易理解它是如何工作的。在实际的程序中,这些函数更有可能相距较远,正如您所说,“通常每个IBInspectable都有更多的条目”。这使得查找和理解实现buttonBorderWidth.
  • Since视图的shadowOpacity属性getter和setter所涉及的所有代码变得更加困难,视图的属性不会占用视图的内存布局中的任何额外空间。视图的buttonBorderWidth是一个存储属性,它确实在视图的内存布局中占用了额外的空间。

单独的updateViews在这里有一个优势,但它是微妙的。请注意,buttonBorderWidth的默认值为1.0。这与layer.borderWidth的默认值0不同。在初始化视图时,我们需要让layer.borderWidthbuttonBorderWidth匹配,即使buttonBorderWidth从未修改过。由于设置layer.borderWidth的代码在updateViews中,因此我们可以确保在视图显示之前的某个时刻调用updateViews (例如,在init中、在layoutSubviews中或在willMove(toWindow:)中)。

如果我们想让buttonBorderWidth成为一个计算属性,我们要么必须在某个地方强制将buttonBorderWidth设置为它的现有值,要么在某个地方复制设置layer.borderWidth的代码。

代码语言:javascript
复制
init(frame: CGRect) {
    ...

    super.init(frame: frame)

    // This is cumbersome because:
    // - init won't call buttonBorderWidth.didSet by default.
    // - You can't assign a property to itself, e.g. `a = a` is banned.
    // - Without the semicolon, the closure is treated as a trailing
    //   closure on the above call to super.init().
    ;{ buttonBorderWidth = { buttonBorderWidth }() }()
}

或者我们必须这样做:

代码语言:javascript
复制
init(frame: CGRect) {
    ...

    super.init(frame: frame)

    // This is the same code as in buttonBorderWidth.didSet:
    layer.borderWidth = buttonBorderWidth
}

如果我们有一堆这样的属性,这些属性覆盖了图层属性,但有不同的默认值,我们必须对每个属性进行强制设置或复制。

我对此的解决方案通常是,我的可检查属性的默认值不能与它所覆盖的属性的默认值不同。如果我们只让buttonBorderWidth的默认值为0(与layer.borderWidth的默认值相同),那么我们就不必同步这两个属性,因为它们永远不会不同步。所以我会像这样实现buttonBorderWidth

代码语言:javascript
复制
@IBInspectable var buttonBorderWidth: CGFloat {
    get { return layer.borderWidth }
    set { layer.borderWidth = newValue }
}

一个特别适用于IBInspectable的条件是,当可检查的属性不能平凡地映射到现有的图层属性时。

例如,在iOS 11和macOS 10.13及更高版本中,CALayer具有maskedCorners特性,该特性控制cornerRadius对哪些角点进行舍入。

代码语言:javascript
复制
@IBInspectable var cornerRadius: CGFloat {
    get { return layer.cornerRadius }
    set { layer.cornerRadius = newValue }
}

代码语言:javascript
复制
@IBInspectable var isTopLeftCornerRounded: Bool {
    get { return layer.maskedCorners.contains(.layerMinXMinYCorner) }
    set {
        if newValue { layer.maskedCorners.insert(.layerMinXMinYCorner) }
        else { layer.maskedCorners.remove(.layerMinXMinYCorner) }
    }
}

@IBInspectable var isBottomLeftCornerRounded: Bool {
    get { return layer.maskedCorners.contains(.layerMinXMaxYCorner) }
    set {
        if newValue { layer.maskedCorners.insert(.layerMinXMaxYCorner) }
        else { layer.maskedCorners.remove(.layerMinXMaxYCorner) }
    }
}

@IBInspectable var isTopRightCornerRounded: Bool {
    get { return layer.maskedCorners.contains(.layerMaxXMinYCorner) }
    set {
        if newValue { layer.maskedCorners.insert(.layerMaxXMinYCorner) }
        else { layer.maskedCorners.remove(.layerMaxXMinYCorner) }
    }
}

@IBInspectable var isBottomRightCornerRounded: Bool {
    get { return layer.maskedCorners.contains(.layerMaxXMaxYCorner) }
    set {
        if newValue { layer.maskedCorners.insert(.layerMaxXMaxYCorner) }
        else { layer.maskedCorners.remove(.layerMaxXMaxYCorner) }
    }
}

那是一堆重复的代码。

代码语言:javascript
复制
@IBInspectable var isTopLeftCornerRounded = true {
    didSet { updateMaskedCorners() }
}

@IBInspectable var isBottomLeftCornerRounded = true {
    didSet { updateMaskedCorners() }
}

@IBInspectable var isTopRightCornerRounded = true {
    didSet { updateMaskedCorners() }
}

@IBInspectable var isBottomRightCornerRounded = true {
    didSet { updateMaskedCorners() }
}

private func updateMaskedCorners() {
    var mask: CACornerMask = []
    if isTopLeftCornerRounded { mask.insert(.layerMinXMinYCorner) }
    if isBottomLeftCornerRounded { mask.insert(.layerMinXMaxYCorner) }
    if isTopRightCornerRounded { mask.insert(.layerMaxXMinYCorner) }
    if isBottomRightCornerRounded { mask.insert(.layerMaxXMaxYCorner) }
    layer.maskedCorners = mask
}

掩码重复的部分shorter.

  • Each

  • 选项只被提到一次,因此更容易确保所有选项都是正确的。

  • 所有实际计算掩码的代码都在一个地方。

  • 掩码每次都是完全从头开始构建的,因此您不必知道掩码的先验值就可以理解它的新值。

这是我使用存储属性的另一个例子:假设你想做一个PolygonView,并使边数是可检查的。在给定边数的情况下,我们需要代码来创建路径,因此如下所示:

代码语言:javascript
复制
extension CGPath {
    static func polygon(in rect: CGRect, withSideCount sideCount: Int) -> CGPath {
        let path = CGMutablePath()
        guard sideCount >= 3 else {
            return path
        }

        // It's easiest to compute the vertices of a polygon inscribed in the unit circle.
        // So I'll do that, and use this transform to inscribe the polygon in `rect` instead.
        let transform = CGAffineTransform.identity
            .translatedBy(x: rect.minX, y: rect.minY) // translate to the rect's origin
            .scaledBy(x: rect.width, y: rect.height) // scale up to the rect's size
            .scaledBy(x: 0.5, y: 0.5) // unit circle fills a 2x2 box but we want a 1x1 box
            .translatedBy(x: 1, y: 1) // lower left of unit circle's box is at (-1, -1) but we want it at (0, 0)

        path.move(to: CGPoint(x: 1, y: 0), transform: transform)
        for i in 1 ..< sideCount {
            let angle = CGFloat(i) / CGFloat(sideCount) * 2 * CGFloat.pi
            print("\(i) \(angle)")
            path.addLine(to: CGPoint(x: cos(angle), y: sin(angle)), transform: transform)
        }
        path.closeSubpath()

        print("rect=\(rect) path=\(path.boundingBox)")
        return path
    }
}

我们可以编写接受CGPath并计算其绘制的线段数量的代码,但直接存储边的数量会更简单。因此,在这种情况下,将存储的属性与触发层路径更新的观察器一起使用是有意义的:

代码语言:javascript
复制
class PolygonView: UIView {

    override class var layerClass: AnyClass { return CAShapeLayer.self }

    @IBInspectable var sideCount: Int = 3 {
        didSet {
            setNeedsLayout()
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        (layer as! CAShapeLayer).path = CGPath.polygon(in: bounds, withSideCount: sideCount)
    }

}

我更新了layoutSubviews中的路径,因为如果视图的大小发生变化,我也需要更新路径,而大小的变化也会触发layoutSubviews

票数 4
EN

Stack Overflow用户

发布于 2018-07-21 11:17:14

首先,您询问的内容与@IBInspectable@IBDesignable无关。这些只是XCode在创建自己的视图/视图控件时与接口生成器一起使用的指令。任何具有@IBInspectable的属性也会显示在界面生成器的属性检查器中。@IBDesignable用于在界面生成器中显示自定义视图。现在我们来看看didSetget/set

  • didSet这就是你所说的属性观察器。您可以为存储属性定义属性观察器,以监视属性中的更改。可以定义两种风格来监控更改willSetdidSet。因此,您可以定义观察器,以便在该属性发生更改时执行某些代码块。如果定义了willSet,则在设置属性之前将调用该代码。同样,didSet是在设置属性之后运行的块。因此,根据您需要做的事情,您可以实现这两个观察者中的任何一个。
  • get/set除了存储属性之外,您还可以定义一些称为计算属性的东西。顾名思义,计算属性本身并不创建和存储任何值。这些值是在需要时计算的。因此,当需要时,这些属性需要getset代码来计算属性。如果只有一个get,这意味着它是一个只读属性。

希望这能有所帮助。阅读Swift这本书,并在iTunesU上浏览CS193p的前几课

票数 2
EN

Stack Overflow用户

发布于 2018-07-21 06:36:20

didSet的意思是“在设置变量时执行以下操作”。在本例中,如果更改buttonBorderWidth,则将调用函数updateView()

当您请求变量本身时,实际得到的是getset。如果我设置了shadowOpacity,它将把它传递给set代码。如果我得到shadowOpacity,它实际上会让我得到layer.shadowOpacity

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51450585

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档