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

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

Related

Swift set UIButton setBorderColor in Storyboard

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.

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

UISlider - IB_DESIGNABLE - runtime attribute warning

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

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.

Setting backgroundColor of custom NSView

What is the process of drawing to NSView using storyboards for osx? I have added a NSView to the NSViewController. Then, I added a few constraints and an outlet.
Next, I added some code to change the color:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var box: NSView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear() {
box.layer?.backgroundColor = NSColor.blueColor().CGColor
//box.layer?.setNeedsDisplay()
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
I would like to do custom drawing and changing colors of the NSView. I have
performed sophisticated drawing on iOS in the past, but am totally stuck here.
Does anyone see what I'm doing wrong?
The correct way is
class ViewController: NSViewController {
#IBOutlet var box: NSView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.wantsLayer = true
}
override func viewWillAppear() {
box.layer?.backgroundColor = NSColor.blue.cgColor
//box.layer?.setNeedsDisplay()
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}}
Swift via property
extension NSView {
var backgroundColor: NSColor? {
get {
if let colorRef = self.layer?.backgroundColor {
return NSColor(CGColor: colorRef)
} else {
return nil
}
}
set {
self.wantsLayer = true
self.layer?.backgroundColor = newValue?.CGColor
}
}
}
Usage:
yourView.backgroundColor = NSColor.greenColor()
Where yourView is NSView or any of its subclasses
Updated for Swift 3
extension NSView {
var backgroundColor: NSColor? {
get {
if let colorRef = self.layer?.backgroundColor {
return NSColor(cgColor: colorRef)
} else {
return nil
}
}
set {
self.wantsLayer = true
self.layer?.backgroundColor = newValue?.cgColor
}
}
}
edit/update:
Another option is to design your own colored view:
import Cocoa
#IBDesignable class ColoredView: NSView {
#IBInspectable var backgroundColor: NSColor = .clear
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
backgroundColor.set()
dirtyRect.fill()
}
}
Then you just need to add a Custom View NSView and set the custom class in the inspector:
Original Answer
Swift 3.0 or later
extension NSView {
var backgroundColor: NSColor? {
get {
guard let color = layer?.backgroundColor else { return nil }
return NSColor(cgColor: color)
}
set {
wantsLayer = true
layer?.backgroundColor = newValue?.cgColor
}
}
}
let myView = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
myView.backgroundColor = .red
This works a lot better:
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.blueColor().setFill()
NSRectFill(dirtyRect);
}
Best way to set a NSView background colour in MacOS 10.14 with dark mode support :
1/ Create your colour in Assets.xcassets
2/ Subclass your NSView and add this :
class myView: NSView {
override func updateLayer() {
self.layer?.backgroundColor = NSColor(named: "customControlColor")?.cgColor
}
}
Very simple and dark mode supported with the colour of your choice !
Full guide : https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface
Just one line of code is enough to change the background of any NSView object:
myView.setValue(NSColor.blue, forKey: "backgroundColor")
Instead of this, you can also add an user defined attribute in the interface designer of type Color with keyPath backgroundColor.
Update to Swift 3 solution by #CryingHippo (It showed colors not on every run in my case). I've added DispatchQueue.main.async and now it shows colors on every run of the app.
extension NSView {
var backgroundColor: NSColor? {
get {
if let colorRef = self.layer?.backgroundColor {
return NSColor(cgColor: colorRef)
} else {
return nil
}
}
set {
DispatchQueue.main.async {
self.wantsLayer = true
self.layer?.backgroundColor = newValue?.cgColor
}
}
}
}
None of the solutions is using pure power of Cocoa framework.
The correct solution is to use NSBox instead of NSView. It has always supported fillColor, borderColor etc.
Set titlePosition to None
Set boxType to Custom
Set borderType to None
Set your desired fillColor from asset catalogue (IMPORTANT for dark mode)
Set borderWidth and borderRadius to 0
Bonus:
it dynamically reacts to sudden change of appearance (light to dark)
it supports animations ( no need for dynamic + no need to override animation(forKey key:NSAnimatablePropertyKey) ->)
future macOS support automatically
WARNING:
Using NSBox + system colors in dark mode will apply tinting corectly. If you do not want tinting use catalogue color.
Alternative is to provide subclass of NSView and do the drawing updates in updateLayer
import Cocoa
#IBDesignable
class CustomView: NSView {
#IBInspectable var backgroundColor : NSColor? {
didSet { needsDisplay = true }
}
override var wantsUpdateLayer: Bool {
return true
}
override func updateLayer() {
guard let layer = layer else { return }
layer.backgroundColor = backgroundColor?.cgColor
}
}
Tinting:
Since macOS 10.13 (High Sierra) NSView responds to selector backgroundColor although it is not documented!