Is there a way for swift code to check if this variable changes and than change the shape of an object?
var Shape = 0.4
let a = (self.size.width + self.size.height) * Shape
Using property observers you can achieve what you are looking for. didSet will be called after varibale value changed.
var variable1 = 0 {
didSet {
onValueChange()
}
}
func onValueChange() {
print("Change \(variable1)")
}
I am relatively new to IBDesignables and IBInspectable's and I noticed that a lot of tutorial use IBInspectable in this fashion.
#IBInspectable var buttonBorderWidth: CGFloat = 1.0 {
didSet {
updateView()
}
}
func updateView() {
// Usually there are more entries here for each IBInspectable
self.layer.borderWidth = buttonBorderWidth
}
But in some instances they use get and set like this for example
#IBInspectable
var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
Can someone please explain: What is happening in each of these cases and how to choose which one to use?
I see two questions. The first one is “What is happening in each of these cases”, and is best answered by reading the “Properties” chapter of The Swift Programming Language. There are also already three other answers posted which address the first question, but none of them answer the second, and more interesting, question.
The second question is “how to choose which one to use”.
Your shadowOpacity example (which is a computed property) has the following advantages over your buttonBorderWidth example (which is a stored property with an observer):
All of the shadowOpacity-related code is in one place, so it's easier to understand how it works. The buttonBorderWidth code is spread between didSet and updateViews. In a real program these functions are more likely to be farther apart, and as you said, “Usually there are more entries here for each IBInspectable”. This makes it harder to find and understand all the code involved in implementing buttonBorderWidth.
Since the view's shadowOpacity property getter and setter just forward to the layer's property, the view's property doesn't take any additional space in the view's memory layout. The view's buttonBorderWidth, being a stored property, does take additional space in the view's memory layout.
There is an advantage to the separate updateViews here, but it is subtle. Notice that buttonBorderWidth has a default value of 1.0. This is different than the default value of layer.borderWidth, which is 0. Somehow we need to get layer.borderWidth to match buttonBorderWidth when the view is initialized, even if buttonBorderWidth is never modified. Since the code that sets layer.borderWidth is in updateViews, we can just make sure we call updateViews at some point before the view is displayed (e.g. in init or in layoutSubviews or in willMove(toWindow:)).
If we want to make buttonBorderWidth be a computed property instead, we either have to force-set the buttonBorderWidth to its existing value somewhere, or duplicate the code that sets layer.borderWidth somewhere. That is, we either have to do something like this:
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 }() }()
}
Or we have to do something like this:
init(frame: CGRect) {
...
super.init(frame: frame)
// This is the same code as in buttonBorderWidth.didSet:
layer.borderWidth = buttonBorderWidth
}
And if we have a bunch of these properties that cover layer properties but have different default values, we have to do this force-setting or duplicating for each of them.
My solution to this is generally to not have a different default value for my inspectable property than for the property it covers. If we just let the default value of buttonBorderWidth be 0 (same as the default for layer.borderWidth), then we don't have to get the two properties in sync because they're never out-of-sync. So I would just implement buttonBorderWidth like this:
#IBInspectable var buttonBorderWidth: CGFloat {
get { return layer.borderWidth }
set { layer.borderWidth = newValue }
}
So, when would you want to use a stored property with an observer? One condition especially applicable to IBInspectable is when the inspectable properties do not map trivially onto existing layer properties.
For example, in iOS 11 and macOS 10.13 and later, CALayer has a maskedCorners property that controls which corners are rounded by cornerRadius. Suppose we want to expose both cornerRadius and maskedCorners as inspectable properties. We might as well just expose cornerRadius using a computed property:
#IBInspectable var cornerRadius: CGFloat {
get { return layer.cornerRadius }
set { layer.cornerRadius = newValue }
}
But maskedCorners is essentially four different boolean properties combined into one. So we should expose it as four separate inspectable properties. If we use computed properties, it looks like this:
#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) }
}
}
That's a bunch of repetitive code. It's easy to miss something if you write it using copy and paste. (I don't guarantee that I got it correct!) Now let's see what it looks like using stored properties with observers:
#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
}
I think this version with stored properties has several advantages over the version with computed properties:
The parts of the code that are repeated are much shorter.
Each mask option is only mentioned once, so it's easier to make sure the options are all correct.
All the code that actually computes the mask is in one place.
The mask is constructed entirely from scratch each time, so you don't have to know the mask's prior value to understand what its new value will be.
Here's another example where I'd use a stored property: suppose you want to make a PolygonView and make the number of sides be inspectable. We need code to create the path given the number of sides, so here it is:
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
}
}
We could write code that takes a CGPath and counts the number of segments it draws, but it is simpler to just store the number of sides directly. So in this case, it makes sense to use a stored property with an observer that triggers an update to the layer path:
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)
}
}
I update the path in layoutSubviews because I also need to update the path if the view's size changes, and a size change also triggers layoutSubviews.
First of all, what you are asking about is nothing to do with #IBInspectable or #IBDesignable. Those are just directives for XCode to use with the Interface Builder when you create your own View/ViewControllers. Any property with #IBInspectable also appears in the attributes inspector in the Interface Builder. And #IBDesignable is for displaying the custom view in Interface builder. Now to get to the didSet and get/set
didSet
This is what you call a Property Observer. You can define property observers for a stored property to monitor the changes in a property. There are 2 flavors to monitor the change willSet and didSetthat can be defined. So you define the observers to perform some block of code where there is a change to that property. If you define willSet that code will be called before the property is set. Likewise didSet is the block run after the property has been set. So depending on what you need to do you can implement either of the observers.
get/set
Besides stored properties you can define something called Computed properties. As the name implies computed properties do not create and store any values themselves. These values are computed when needed. So these properties need get and set code to compute the property when required. If there is only a get that means it’s a read only property.
Hope this helps. Read the Swift book and go through the first few lectures of CS193p on iTunesU
didSet means "do the following when the variable is set". In your case, if you change buttonBorderWidth, the function updateView() will be called.
get and set are what you actually get when you ask for the variable itself. If I set shadowOpacity, it will pass it on to the set code. If I get shadowOpacity, it will actually get me layer.shadowOpacity.
#IBInspectable var buttonBorderWidth: CGFloat = 1.0
In that example, buttonBorderWidth is an actual property of the view. The attributes inspector can write to it and read it directly. The didSet observer is just so that something happens in response to our changing that property.
That's totally different from the other example:
#IBInspectable
var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set {
layer.shadowOpacity = newValue
}
}
In that example, the goal is to make the layer's shadowOpacity inspectable. But you can't do that, because it's not a property of the view. Therefore we put a façade in front of the layer property, in the form of a computed "property" of the view; the attributes inspector can't see layer.shadowOpacity, but it can see the view's shadowOpacity which, unbeknownst to it, is just a way of accessing the layer's shadowOpacity.
In Xcode 9 and Swift 4 I always get this warning for some IBInspectable properties:
#IBDesignable public class CircularIndicator: UIView {
// this has a warning
#IBInspectable var backgroundIndicatorLineWidth: CGFloat? { // <-- warning here
didSet {
backgroundIndicator.lineWidth = backgroundIndicatorLineWidth!
}
}
// this doesn't have a warning
#IBInspectable var topIndicatorFillColor: UIColor? {
didSet {
topIndicator.fillColor = topIndicatorFillColor?.cgColor
}
}
}
Is there a way to get rid of it ?
Maybe.
The exact error (not warning) I got when doing a copy/paste of class CircularIndicator: UIView is:
Property cannot be marked #IBInspectable because its type cannot be
represented in Objective-C
I resolved it by making this change:
#IBInspectable var backgroundIndicatorLineWidth: CGFloat? { // <-- warning here
didSet {
backgroundIndicator.lineWidth = backgroundIndicatorLineWidth!
}
}
To:
#IBInspectable var backgroundIndicatorLineWidth: CGFloat = 0.0 {
didSet {
backgroundIndicator.lineWidth = backgroundIndicatorLineWidth
}
}
Of course, backgroundIndicator is undefined in my project.
But if you are coding against didSet, it looks like you just need to define a default value instead of making backgroundIndicatorLineWidth optional.
Below two points might helps you
As there is no concept of optional in objective c, So optional IBInspectable produces this error. I removed the optional and provided a default value.
If you are using some enumerations types, then write #objc before that enum to remove this error.
Swift - 5
//Change this with below
#IBInspectable public var shadowPathRect: CGRect!{
didSet {
if shadowPathRect != oldValue {
setNeedsDisplay()
}
}
}
To
#IBInspectable public var shadowPathRect: CGRect = CGRect(x:0, y:0, width:0, height:0) {
didSet {
if shadowPathRect != oldValue {
setNeedsDisplay()
}
}
}
I'm trying to set UIButton Border color on Storyboard:
But the button is still Black. When I try to use layer.borderColor, it doesn't show the border at all.
How to se the color?
Thank you
// UPDATE
I've changed the way to set up based on #dfd to:
import Foundation
import UIKit
#IBDesignable
public class OnboardingButton: UIButton {
#IBInspectable public var borderColor:UIColor? {
didSet {
layer.borderColor = borderColor?.cgColor
}
}
#IBInspectable public var borderWidth:CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable public var cornerRadius:CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
After set up in Storyboard Custom Class to *OnboardingButton app started build but Faield. There is no error in the Log. Where can I find the error and how to fix it?
Here's my UIButton subclass:
#IBDesignable
public class Button: UIButton {
#IBInspectable public var borderColor:UIColor? {
didSet {
layer.borderColor = borderColor?.cgColor
}
}
#IBInspectable public var borderWidth:CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable public var cornerRadius:CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
EDIT:
I created a simple project and added the above IBDesignable subclass. Below are three screen shots of the result. Note that in the Identity inspector I've set the Class to be Button, not UIButton and that it reports that the Designables are "Up to date". Note that in the Attributes inspector these appear as properties at the very top.
You can change some properties of UIButton form interface builder but not color of border. Another way would be subclassing it like in answer suggested by dfd.
I am trying to render a vertical slider in interface builder however, when I try to set the value interface builder gives me a warning.
What am I doing wrong here?
#IBDesignable
class VerticalSlider: UISlider {
#IBInspectable var setOrientation: Bool! {
didSet {
if setOrientation == true{
self.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI_2))
}else {
self.transform = CGAffineTransformIdentity
}
}
}
}
Your #IBInspectable property setOrientation shouldn't be an implicitly unwrapped optional, just a non-optional boolean property.
If you change the declaration of setOrientation to a regular (non-optional) boolean with a default (initial) value, say false, you should no longer be prompted with warnings regarding your #IBInspectable.
#IBInspectable var setOrientation: Bool = false { ... }