iOS Designable always failing, no warning - ios

So I created this simple subclass to have design-time data for cells:
#IBDesignable
class UIPrototypeLabel: UILabel {
#IBInspectable var designTimeText: String? {
didSet {
#if TARGET_INTERFACE_BUILDER
self.text = designTimeText
#endif
}
}
}
I haven't even set any label to this class yet, and the IB build is already failing with no warnings.
After I set a UILabel with it's class as UIPrototypeLabel and set the Design Time Text attribute in Attribute Inspector, it creates the User Defined Runtime Attributes with the right key and value, but I get ignoring user defined runtime attribute for key path designTimeText on instance UILabel
Any ideas?

Related

At which order Interface Builder evaluates IBInspectables?

I have custom UI element designed with a help of IBInspectables, so that I can use and modify it in the Interface Builder. There are 2 inspectables which effectively change the same property of the element but in a slightly different manner.
At the screenshot there is title that sets the title of the element, and locTitleKey that gets NSLocalizedString by the key and sets it as the title.
As I can see from passing different values to these inspectables:
If locTitleKey is specified, and title is left empty, the localised string is used
If both are specified, title is used and localized one is ignored
The question - is that behaviour predictable?
In other words, can I safely assume that the order of evaluation of my IBInspectables always be the same? (title and then locTitleKey)
To provide more context here are the snippets of code used.
title is defined in the scope of the class itself:
#IBDesignable
class StandardInputField: UIView, NibLoadable {
//...
#IBInspectable
public var title: String? {
didSet {
titleLabel.text = title
}
}
//...
}
And locTitleKey is defined as extension:
extension StandardInputField {
#IBInspectable var locTitleKey: String? {
get {
return nil
}
set(key) {
title = key?.localized
}
}
}

Failed to set () user defined inspected property on (UIButton)

In a simple ViewController, I added one UIButton.
I would like to use "User Defined Runtime Attributes". For now I added the default Bool attribute.
The button is an #IBOutlet:
#IBOutlet var button:UIButton!
The link in the storyboard is done.
I have nothing else in my app.
I got this error:
2017-03-26 20:31:44.935319+0200 ISAMG[357:47616] Failed to set (keyPath) user defined inspected property on (UIButton):
[<UIButton 0x15e3a780> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key keyPath.
I don't understand what I'm missing.
EDIT 1
Following the advices #timaktimak, I created a custom UIButton class:
#IBDesignable
class ChoiceButton: UIButton {
#IBInspectable var keyPath: Bool! {
didSet {
print("didSet viewLabel, viewLabel = \(self.keyPath)")
}
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
}
The error is the same:
2017-03-26 22:32:27.389126+0200 ISAMG[429:71565] Failed to set (keyPath) user defined inspected property on (ISAMG.ChoiceButton):
[<ISAMG.ChoiceButton 0x14e8f040> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key keyPath.
Well, your button doesn't have a property called keyPath to be setting it to something.
User Defined Runtime Attributes are used to simply set a property value in the Interface Builder.
You can use a standard property that every UIButton has, for example backgroundColor:
Or, you can create a custom UIButton subclass, add a property to it, then set the button's class to the created custom subclass and set the property value in the User Defined Runtime Attributes section.
You can check out, for example, this answer, it contains an example of a custom class with User Defined Runtime Attributes: https://stackoverflow.com/a/24433125/3445458
Edit: make sure you are not using optional (or implicitly unwrapped optional) for the type, it doesn't work with User Defined Runtime Attributes (I guess because the Interface Builder doesn't know whether the value can be set to nil, and so show it in the options or not).
So you can't do
var keyPath: Bool!
instead you can only do
var keyPath: Bool = false
or don't use a default value and set it in the constuctor instead. For some reason it works with an optional String (and in the example they use an optional String), but it doesn't with optional Bool. To conclude, don't use or worry about User Defined Runtime Attributes too much, it is clearer to set the default values in code!
Hope this helps! Good luck!
Since this thread came up about 5 times in my search for a solution, I wanted to post my fix here for future frustrated souls. The reason I kept getting this error is because Interface Builder doesn't clean up old IBInspectable properties if you're experimenting with them.
In my case, I started with an Int property called type and set a few custom buttons on my screen to use a value of 3 as a test. Later, I changed it to a more meaningful property name and made it a string, then set a few of those. But when I ran the app, I was getting this message.
I didn't discover the error until I went to the Identity Inspector and noticed that I still had an old keypath and value in use for some of the buttons:
Removing the old keypath fixed the problem.
In User Defined Runtime Attributes use layer.cornerRadius instead of cornerRadius.
Looks like failed to set keyPath bool value to true of false.
setKeyPath to true or false!
Compare #IBInspectable(in sub class) and Key Path(identity inspector). Remove key value which is not in #IBInspectable.
Another way to set the set a property value in the Interface Builder is that create an IBOutlet of your view on your view controller.
#IBOutlet weak var yourView: UIView!
yourView.layer.shadowOffset = CGSize(width: 0, height: 1)
yourView.layer.shadowColor = UIColor.lightGray.cgColor
yourView.layer.shadowOpacity = 1
yourView.layer.shadowRadius = 5
yourView.layer.masksToBounds = false
yourView.layer.cornerRadius = 20

Declaring a Property with Type Constraint in Swift

I need to create a custom fields framework in my app. I defined a protocol for the fields called FieldType and extended with it UITextField and UIButton to be different types of fields.
Now I want to create a container view for the fields so I want the container to be able to refer to its field elements as both UIViews and FieldTypes, and I'm wondering if there's a concise way to define the type of elements it receives to be a specific UIView that implements the FieldType protocol?
I can have FieldContainerView accept UIViews or FieldTypes and check manually that it also matches the other with a guard statement, but it feels a bit cumbersome.
I tried 2 approaches:
1) Define a Custom Intermediary FieldViewType
The idea is to have FieldViewType extend UIView directly with FieldType so it might be useful as a general case for UITextField: FieldType and UIButton: FieldType. But as this code sample clearly shows, this does not work.
protocol FieldType {
var showError: Bool { get set }
var isEmpty: Bool { get set }
}
class CustomTextField: UITextField, FieldType {}
class CustomButtonField: UIButton, FieldType {}
let textField = CustomTextField()
textField is UIView // True
textField is FieldType // True
let buttonField = CustomButtonField()
buttonField is UIView // True
buttonField is FieldType // True
class FieldView: UIView, FieldProtocol {}
let field = FieldView()
field is UIView // True
field is FieldProtocol // True
textField is FieldView // False
buttonField is FieldView // False
2) Use Generics
I can define a generic type that matches the requirements like so <FieldViewType: UIView where FieldViewType: FieldType>, but I don't see where to use to best solve my problem. If I define it at the class level
class FieldContainerView<FieldViewType: UIView where FieldViewType: FieldType>: UIView {
var fields = [FieldViewType]()
func addField(FieldViewType: field) {
fields.append(field)
}
}
I need to declare the container class once for each field type I'll want to use and won't be able to use 2 field types in the same container.
The other option is to define type constraint at the function level with addField
class FieldContainerView: UIView {
var fields = [UIView]()
func addField<FieldViewType: UIView where FieldViewType: FieldType>(FieldViewType: field) {
fields.append(field)
}
}
and then cast each element in fields to FieldType when necessary and I'll know the cast will always work because addField is the only way to add elements to the container. But this also feels too cumbersome.
It feels like the best way around this would have been to be able to define FieldViewType with a typealias, but this doesn't seem to be supported. Or have UIView be defined with a protocol so it could be mixed better, but UIKit isn't constructed in this manner.
So it seems that at the moment there's no way to create a type constraint in property declarations. I don't know why, but I don't know anything about language implementations.
I went for a workaround where FieldType also has a view: UIView property with a default implementation.
The new FieldType declaration:
protocol FieldType {
var showError: Bool { get set }
var isEmpty: Bool { get set }
var view: UIView { get }
}
extension FieldType where Self: UIView {
var view: UIView {
return self
}
}
This way it doesn't matter from which class in the UIKit hierarchy you inherited before conforming to the FieldType protocol, as long as you have UIView somewhere as your super class, you'll have an accessibly view property.
This feels like a workaround, but at least it saves dual-declarations for collections that need both the FieldType and the UIView properties of an object.
Can you change this line:
class FieldView: UIView, FieldProtocol {}
To this:
class FieldView: UIView, FieldType {}

How to specify nested custom view class?

Available
nested classes SuperView and NestedView.
class SuperView : UIImageView {
class NestedView : UIImageView {
var text : String = "Nested View"
}
var text : String = "Super View"
var nested : NestedView?
}
I would like to set for a UIImageView the property named "Custom Class Name" to value "NestedView" inside the inspector of the storyboard scene. But the Interface Builder couldn't find "NestedView" class.
At this time, I think that Interface Builder only recognizes the names of Objective-C classes. You can still make Interface Builder find a nested class with the #objc keyword:
class SuperView: UIView {
#objc(SVNestedView) class NestedView: UIImageView {
}
}
Then, in Interface Builder, specify that th view is of class SVNestedView. Since Objective-C isn't namespaced, you still need to pick unique names for each nested class, but at least the Swift side is properly namespaced.
In Swift, an instance of an inner class is independent of any instance of the outer class. It is as if all inner classes in Swift are declared using Java's static.
I don't think this class design suits your requirement. What you need to do is, you need to create a new class outside your view, and create some sort of a composition. This will surely work out for you.
Here is some modification to your code:
class SuperView : UIImageView {
var text : String = "Super View"
var nested : NestedView = NestedView()
}
class NestedView : UIImageView {
var text : String = "Nested View"
}
I think it impossible to specify an inner class in a storyboard like SuperView.NestedView so far. So I makes a class extended from an inner class, then specifies it in a storyboard like SuperView_NestedView. It works for me.
final class SuperView_NestedView: SuperView.NestedView {} // Specifies this class in the storyboard
class SuperView : UIImageView {
class NestedView : UIImageView {
var text : String = "Nested View"
}
var text : String = "Super View"
var nested : NestedView?
}
This view can be referred as SuperView.NestedView from a ViewController because SuperView_NestedView is extended from SuperView.NestedView.
class TheViewController: UIViewController {
#IBOutlet var nestedView: SuperView.NestedView!

Swift: 'var' declaration without getter/setter method not allowed here

I've tried to declare IBOutlet property on extension of class. But it give error as
'var' declaration without getter/setter method not allowed here
class ExampleView : UIView
{
}
extension ExampleView
{
#IBOutlet var btn1, btn2 : UIButton // here I got error.
}
Please any one suggest me correct way to do it?
From Extensions -> Computed Properties in The Swift Programming Language
NOTE
Extensions can add new computed properties, but they cannot add stored
properties, or add property observers to existing properties.
Addition in response to twlkyao's comment: Here is my implementation of the absoluteValue property of a Double
extension Double {
var absoluteValue: Double {
if self >= 0 {
return self
} else {
return -self
}
}
}
// Simple test -> BOTH println() should get called.
var a = -10.0
if (a < 0) {
println("Smaller than Zero")
}
if (a.absoluteValue > 5) {
println("Absolute is > 5")
}
From The Swift Programming Language:
Extensions in Swift can:
Add computed properties and computed static properties
Define instance methods and type methods
Provide new initializers
Define subscripts
Define and use new nested types
Which means you can't add IBOutlets and other stored properties.
If you really want to cheat, you can create global vars or a bookkeeping object which would allow you to query these vars or the object in order to add those properties (and have them be computed properties).
But it seems like it would go against the best practices. I would only do it if there's absolutely no other way.

Resources