我对IBDesignables
和IBInspectable
来说还比较陌生,我注意到许多教程都以这种方式使用IBInspectable
。
@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
}
}
有人可以解释一下:在每种情况下发生了什么以及如何选择使用哪种情况?
答案 0 :(得分:1)
首先,您要问的与@IBInspectable
或@IBDesignable
无关。这些只是XCode在创建自己的View / ViewControllers时与Interface Builder一起使用的指令。任何带有@IBInspectable
的属性也将出现在“界面生成器”的属性检查器中。 @IBDesignable
用于在“界面”构建器中显示定制视图。现在进入didSet
和get/set
didSet
这就是所谓的属性观察器。您可以为存储的属性定义属性观察器,以监视属性中的更改。可以定义两种监视willSet
和didSet
的更改的方式。因此,您可以定义观察者以执行一些代码块,其中该属性发生更改。如果定义willSet
,则将在设置属性之前调用该代码。同样,didSet
是在设置属性后运行的块。因此,根据您需要执行的操作,可以实现任何一个观察者。
get/set
除了存储的属性外,您还可以定义称为计算属性。顾名思义,计算属性本身不会创建和存储任何值。这些值在需要时进行计算。因此,这些属性需要get
和set
代码来在需要时计算属性。如果只有get
,则表示它是只读属性。
希望这会有所帮助。阅读Swift书,并在iTunesU上浏览CS193p的前几讲
答案 1 :(得分:1)
我看到两个问题。第一个是“在每种情况下都发生了什么”,最好通过阅读the “Properties” chapter of The Swift Programming Language来回答。还已经发布了其他三个答案来解决第一个问题,但是没有一个答案可以回答第二个问题,而且更有趣。
第二个问题是“如何选择要使用的那个”。
您的shadowOpacity
示例(它是计算属性)比您的buttonBorderWidth
示例(它是带有观察者的存储属性)具有以下优点:
所有与shadowOpacity
相关的代码都放在一个地方,因此更容易理解它的工作方式。 buttonBorderWidth
代码分布在didSet
和updateViews
之间。在实际程序中,这些功能更可能相距更远,正如您所说,“通常,每个IBInspectable都在此处有更多条目”。这使得查找和理解实现buttonBorderWidth
所涉及的所有代码变得更加困难。
由于视图的shadowOpacity
属性获取和设置器仅转发到图层的属性,因此视图的属性不会在视图的内存布局中占用任何额外的空间。视图的buttonBorderWidth
是一种存储属性,它确实在视图的内存布局中占用了额外的空间。
此处单独的updateViews
有一个优点,但这很微妙。请注意,buttonBorderWidth
的默认值为1.0。这与layer.borderWidth
的默认值0不同。默认值是layer.borderWidth
从不初始化,因此在初始化视图时我们需要获取buttonBorderWidth
来与buttonBorderWidth
匹配。改性。由于设置layer.borderWidth
的代码位于updateViews
中,因此我们可以确保在显示视图之前的某个时刻调用updateViews
(例如,在init
或{{ 1}}或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
}
的默认值设为0(与buttonBorderWidth
的默认值相同),则不必使这两个属性同步,因为它们永远不会超出-同步。因此,我将像这样实现layer.borderWidth
:
buttonBorderWidth
因此,您何时想与观察者一起使用存储的属性?一个特别适用于@IBInspectable var buttonBorderWidth: CGFloat {
get { return layer.borderWidth }
set { layer.borderWidth = newValue }
}
的条件是可检查属性没有平凡地映射到现有图层属性上。 / p>
例如,在iOS 11和macOS 10.13及更高版本中,IBInspectable
具有CALayer
属性,该属性控制用maskedCorners
舍入哪些角。假设我们要同时公开cornerRadius
和cornerRadius
作为可检查的属性。我们不妨使用计算属性公开maskedCorners
:
cornerRadius
但是@IBInspectable var cornerRadius: CGFloat {
get { return layer.cornerRadius }
set { layer.cornerRadius = newValue }
}
本质上是将四个不同的布尔属性组合为一个。因此,我们应该将其公开为四个单独的可检查属性。如果我们使用计算属性,则如下所示:
maskedCorners
那是一堆重复的代码。如果您使用复制和粘贴方式写东西,很容易错过。 (我不保证我做对了!)现在让我们来看一下使用带有观察者的存储属性的样子:
@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
}
并使边数可检查。给定边数,我们需要代码来创建路径,所以这里是:
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
。
答案 2 :(得分:0)
didSet
的意思是“设置变量后执行以下操作”。对于您而言,如果您更改buttonBorderWidth
,则将调用函数updateView()
。
get
和set
是当您要求变量本身时实际得到的。如果我设置了shadowOpacity
,它将传递给set
代码。如果我得到shadowOpacity
,实际上就会得到我layer.shadowOpacity
。
答案 3 :(得分:0)
-O3
在该示例中,@IBInspectable var buttonBorderWidth: CGFloat = 1.0
是视图的实际属性。属性检查器可以对其进行写入和直接读取。 buttonBorderWidth
观察者只是为了响应我们更改该属性而发生某些事情。
与其他示例完全不同:
didSet
在该示例中,目标是使层的 @IBInspectable
var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
可检查。但是您不能这样做,因为它不是视图的属性。因此,我们将façade放置在layer属性的前面,以视图的“属性”形式出现;属性检查器看不到shadowOpacity
,但是可以看到视图的 layer.shadowOpacity
,这鲜为人知,它只是访问层的shadowOpacity
的一种方式