@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,例如
@IBInspectable
var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}有人能解释一下:在这些情况下发生了什么,以及如何选择使用哪种情况?
发布于 2018-07-21 15:02:34
我看到两个问题。第一个问题是“在这些情况下发生了什么”,最好的答案是阅读the “Properties” chapter of 。已经发布了另外三个答案,解决了第一个问题,但没有一个回答第二个,也是更有趣的问题。
第二个问题是“如何选择使用哪一个”。
与buttonBorderWidth示例(具有观察者的存储属性)相比,shadowOpacity示例(它是一个计算属性)具有以下优点:
shadowOpacity-related代码都在一个地方,因此更容易理解它是如何工作的。在实际的程序中,这些函数更有可能相距较远,正如您所说,“通常每个IBInspectable都有更多的条目”。这使得查找和理解实现buttonBorderWidth.shadowOpacity属性getter和setter所涉及的所有代码变得更加困难,视图的属性不会占用视图的内存布局中的任何额外空间。视图的buttonBorderWidth是一个存储属性,它确实在视图的内存布局中占用了额外的空间。单独的updateViews在这里有一个优势,但它是微妙的。请注意,buttonBorderWidth的默认值为1.0。这与layer.borderWidth的默认值0不同。在初始化视图时,我们需要让layer.borderWidth与buttonBorderWidth匹配,即使buttonBorderWidth从未修改过。由于设置layer.borderWidth的代码在updateViews中,因此我们可以确保在视图显示之前的某个时刻调用updateViews (例如,在init中、在layoutSubviews中或在willMove(toWindow:)中)。
如果我们想让buttonBorderWidth成为一个计算属性,我们要么必须在某个地方强制将buttonBorderWidth设置为它的现有值,要么在某个地方复制设置layer.borderWidth的代码。
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 }() }()
}或者我们必须这样做:
init(frame: CGRect) {
...
super.init(frame: frame)
// This is the same code as in buttonBorderWidth.didSet:
layer.borderWidth = buttonBorderWidth
}如果我们有一堆这样的属性,这些属性覆盖了图层属性,但有不同的默认值,我们必须对每个属性进行强制设置或复制。
我对此的解决方案通常是,我的可检查属性的默认值不能与它所覆盖的属性的默认值不同。如果我们只让buttonBorderWidth的默认值为0(与layer.borderWidth的默认值相同),那么我们就不必同步这两个属性,因为它们永远不会不同步。所以我会像这样实现buttonBorderWidth:
@IBInspectable var buttonBorderWidth: CGFloat {
get { return layer.borderWidth }
set { layer.borderWidth = newValue }
}一个特别适用于IBInspectable的条件是,当可检查的属性不能平凡地映射到现有的图层属性时。
例如,在iOS 11和macOS 10.13及更高版本中,CALayer具有maskedCorners特性,该特性控制cornerRadius对哪些角点进行舍入。
@IBInspectable var cornerRadius: CGFloat {
get { return layer.cornerRadius }
set { layer.cornerRadius = newValue }
}@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) }
}
}那是一堆重复的代码。
@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.
这是我使用存储属性的另一个例子:假设你想做一个PolygonView,并使边数是可检查的。在给定边数的情况下,我们需要代码来创建路径,因此如下所示:
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并计算其绘制的线段数量的代码,但直接存储边的数量会更简单。因此,在这种情况下,将存储的属性与触发层路径更新的观察器一起使用是有意义的:
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。
发布于 2018-07-21 11:17:14
首先,您询问的内容与@IBInspectable或@IBDesignable无关。这些只是XCode在创建自己的视图/视图控件时与接口生成器一起使用的指令。任何具有@IBInspectable的属性也会显示在界面生成器的属性检查器中。@IBDesignable用于在界面生成器中显示自定义视图。现在我们来看看didSet和get/set
didSet这就是你所说的属性观察器。您可以为存储属性定义属性观察器,以监视属性中的更改。可以定义两种风格来监控更改willSet和didSet。因此,您可以定义观察器,以便在该属性发生更改时执行某些代码块。如果定义了willSet,则在设置属性之前将调用该代码。同样,didSet是在设置属性之后运行的块。因此,根据您需要做的事情,您可以实现这两个观察者中的任何一个。get/set除了存储属性之外,您还可以定义一些称为计算属性的东西。顾名思义,计算属性本身并不创建和存储任何值。这些值是在需要时计算的。因此,当需要时,这些属性需要get和set代码来计算属性。如果只有一个get,这意味着它是一个只读属性。希望这能有所帮助。阅读Swift这本书,并在iTunesU上浏览CS193p的前几课
发布于 2018-07-21 06:36:20
didSet的意思是“在设置变量时执行以下操作”。在本例中,如果更改buttonBorderWidth,则将调用函数updateView()。
当您请求变量本身时,实际得到的是get和set。如果我设置了shadowOpacity,它将把它传递给set代码。如果我得到shadowOpacity,它实际上会让我得到layer.shadowOpacity。
https://stackoverflow.com/questions/51450585
复制相似问题