UISlider - IB_DESIGNABLE - runtime attribute warning - swift

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 { ... }

Related

Overriding isHighlighted still changes UIControlState - why?

In a UIControl, if I override isHighlighted to set a private _isHighlighted property, and then check the control's state to see if it contains .highlighted, the state still accurately reflects the change. See code below.
My question is, how is this possible? I never call super.isHighlighted or manipulate the state property. state is an OptionSet that must have the .highlighted property inserted into the set, which, from what I can determine, does not, or should not, happen if I override the property.
The only other explanation I can think of is that state is actually a computed property based off other properties (i.e. isSelected, isEnabled, etc.)
class MyControl: UIControl {
private var _isHighlighted: Bool = false
override var isHighlighted: Bool {
get { return self._isHighlighted }
set { self._isHighlighted = newValue }
}
}
let myControl = MyControl()
myControl.isHighlighted = true
myControl.state.contains(.highlighted) // returns true
The only other explanation I can think of is that state is actually a computed property based off other properties (i.e. isSelected, isEnabled, etc.)
Good explanation! Let's try logging (printing) in the getter to see if that's true:
class MyControl: UIControl {
private var _isHighlighted: Bool = false
override var isHighlighted: Bool {
get { print("getting"); return self._isHighlighted }
set { self._isHighlighted = newValue }
}
}
let myControl = MyControl()
myControl.isHighlighted = true
print("about to check state")
myControl.state.contains(.highlighted)
print("checked state")
And here's the log:
about to check state
getting
checked state
Quod erat demonstrandum.

How to fix "'#IBInspectable' attribute is meaningless on a property that cannot be represented in Objective-C" warning

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()
}
}
}

Is this officially the proper way of using get and set in Swift?

Let's say for example I want to make some kind of a radio button which keeps track of its active state and changes color when its state is changed. I want it to change color as I set the value. This is how I would implement it:
class TagButton: UIButton {
var _active: Bool = false
var active: Bool {
set(newVal){
_active = newVal
if(!newVal){
self.backgroundColor = UIColor.white //inactive
}
else {
self.backgroundColor = UIColor.red //active
}
}
get {
return _active
}
}
}
Now, I have seen some questions suggest a similar approach, but what bothers me, is whether or not this is actually intended use of Swift. I have a feeling I am inventing a bicycle here. And I could not find anything about this in official Swift documentation. Was anyone able to confirm this?
Your code looks like Objective-C. In Swift, there is no need to create the backing storage and you can use the property observer didSet to change the background color:
class TagButton: UIButton {
var active = false {
didSet {
backgroundColor = active ? .red : .white
}
}
}
Or you could use a Computed Property and not have storage for active at all:
class TagButton: UIButton {
var active: Bool {
set {
backgroundColor = newValue ? .red : .white
}
get {
return backgroundColor == .red
}
}
}
You can read more about Property Observers and Computed Properties here.

When to use didset or get set when using #IBInspectable

After looking at different tutorial. i don't know when to use didset or get set to update the variable.
Could anyone explain a little bit more detail for when to use didset or get set?
#IBInspectable var circleColor: UIColor = UIColor.redColor() {
didSet { //after properties are set in storyboard, update here
circleLayer.strokeColor = circleColor.CGColor
self.toggleButon()
}
}
/**
Radius of RadioButton circle.
*/
#IBInspectable var circleRadius: CGFloat = 5.0
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
for circle radius, it doesn't have to use didset to update its value. i can't get it.
Here i am giving you one example and try to explain you how to use, hope it will helpful for you.
I am using this class for UIView here for did set and get set with Story Board my class name here "MyCustomView"
import Foundation
import UIKit
import QuartzCore
/// Computed properties, based on the backing CALayer property, that are visible in Interface Builder.
#IBDesignable public class MyCustomView: UIView {
/// When positive, the background of the layer will be drawn with rounded corners. Also effects the mask generated by the `masksToBounds' property. Defaults to zero. Animatable.
#IBInspectable var cornerRadius: Double {
get {
return Double(self.layer.cornerRadius)
}
set {
self.layer.cornerRadius = CGFloat(newValue)
}
}
/// The width of the layer's border, inset from the layer bounds. The border is composited above the layer's content and sublayers and includes the effects of the `cornerRadius' property. Defaults to zero. Animatable.
#IBInspectable var borderWidth: Double {
get {
return Double(self.layer.borderWidth)
}
set {
self.layer.borderWidth = CGFloat(newValue)
}
}
/// The color of the layer's border. Defaults to opaque black. Colors created from tiled patterns are supported. Animatable.
#IBInspectable var borderColor: UIColor? {
get {
return UIColor(CGColor: self.layer.borderColor!)
}
set {
self.layer.borderColor = newValue?.CGColor
}
}
/// The color of the shadow. Defaults to opaque black. Colors created from patterns are currently NOT supported. Animatable.
#IBInspectable var shadowColor: UIColor? {
get {
return UIColor(CGColor: self.layer.shadowColor!)
}
set {
self.layer.shadowColor = newValue?.CGColor
}
}
/// The opacity of the shadow. Defaults to 0. Specifying a value outside the [0,1] range will give undefined results. Animatable.
#IBInspectable var shadowOpacity: Float {
get {
return self.layer.shadowOpacity
}
set {
self.layer.shadowOpacity = newValue
}
}
/// The shadow offset. Defaults to (0, -3). Animatable.
#IBInspectable var shadowOffset: CGSize {
get {
return self.layer.shadowOffset
}
set {
self.layer.shadowOffset = newValue
}
}
/// The blur radius used to create the shadow. Defaults to 3. Animatable.
#IBInspectable var shadowRadius: Double {
get {
return Double(self.layer.shadowRadius)
}
set {
self.layer.shadowRadius = CGFloat(newValue)
}
}
}
And you can use this with story board import this class with your "UIView"
after you will see some
and you directly set here view cornet radius, shadow and Shadow
and result you can see inside your storyboard directly without running code
Output here from this code
This answer explains the difference in usage of set vs didSet quite clearly.
IN SUMMARY
willSet should be used for doing something before the value is set. (the value is not updated at this point).
set is to update the value
didSet if you want to do anything after the value is set (the value has been updated at this point)
ALSO
if you implement set, you will also need to implement get
but didSet can also be used without having to implement any other method
#IBInspectable will work with both property kinds:
Use didSet{} for stored properties:
didSet is a property observer.
Use set{} get{} for computed properties.
in the following example: firstName And lastName are stored properties.
fullName is a Computed property:
struct Person{
var firstName:String
var lastName:String{
didSet{
//property observer
print("It was set")
}
}
var fullName:String{
get{
return "\(firstName)-\(lastName)"
}
set{
let split = newValue.split(separator: "-")
firstName = String(split.first ?? "")
lastName = String(split.last ?? "")
}
}
}
var p = Person(firstName: "Moshe", lastName: "Doe")
print(p.firstName)
print(p.lastName)
print(p.fullName)
p.fullName = "Jhon-Doe"
print(p.firstName)
print(p.lastName)
print(p.fullName)
Also look into the language guide: (Properties)
https://docs.swift.org/swift-book/LanguageGuide/Properties.html
One final note about properties and #IBInspectable:
Validation of a value may be achieved using a combo of a computed property with a stored property (Backing property): Here is an example:
//Bound between 0 and 5
var boundRating:Int = 3{
didSet{
renderRating()
}
}
#IBInspectable
var rating:Int{
set{
if newValue <= 5 && newValue >= 0{
boundRating = newValue
renderRating()
}
}
get{
return boundRating
}
}

Is it possible use non read-only computed property in extension?

Is it possible for a computed property in extension to have both getter and setter? Apple's guide does not mention it and the only example I have seen only shows read-only computed property in extension.
Is it possible computed property in extension that has getter and setter?
Yes.
Probably one of the most common uses of computed properties in extensions in my experience is providing a wrapper to make easier access to particular properties.
For example, when we want to modify the border layer, border color, or corner radius of anything out of UIKit, we're stuck going through the layer property.
But we can extend UIView with a property with both a setter & getter to provide a much more convenient means of changing the properties of its layer:
extension UIView {
var borderColor: UIColor? {
get {
guard let color = self.layer.borderColor else {
return nil
}
return UIColor(CGColor: color)
}
set {
self.layer.borderColor = newValue?.CGColor
}
}
}
Moreover, if we really want to, we can leverage the Objective-C run time to emulate stored properties in extensions (which of course mean setting & getting). Take part of this Stack Overflow answer for example:
private var kAssociationKeyNextField: UInt8 = 0
extension UITextField {
#IBOutlet var nextField: UITextField? {
get {
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
}
set(newField) {
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
}
}
}
This serves as just one example of a property in an extension with a setter & getter.
This works:
extension Bool
{
public var integerValue: Int
{
get
{
return true ? 1 : 0
}
set
{
self = (newValue > 0) ? true : false
}
}
}
So yes.