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
Related
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?
I notice when I'm writing in Swift, if I declare a label in the ViewController as either var label: UILabel! or var label = UILabel(), and then initialize them in the viewDidLoad method, they both seem to, functionally, work identically. So what is the difference between these two?
On a related note, when I declare var label: UILabel! I have to write label = UILabel(frame:CGRectMake(...)), but if I use var label = UILabel() I have to write label.frame = CGRectMake(...). Is this because var label = UILabel() is both declaring and instantiating a class object whereas var label: UILabel! is simply declaring a variable of a type? I'm confused about what's actually going on here.
Thanks in advance!
var label: UILabel!
is declaring that you have a UILabel variable that may be empty (Optional), but you can access it pretending as if it will not - usually that means it would be an IBOutlet that would be set by the interface storyboard on load, before you tried to use the label.
var label = UILabel()
is declaring that you have a label that will always hold a label, and can never be nil. It is ALSO creating an instance of a UILabel when a class instance is created, even though you say you assign a different label to the variable in viewDidLoad()
There's no difference in access, but it does mean no matter when you use that variable it will hold a real value and not be nil.
They both are variables of type UILabel.
I'm not sure why you could not not set the variable to a new UILabel using the CGRect constructor, that should be possible. Perhaps at one point you had said let label = UILabel(), which would mean it was a fixed variable that could not hold a new value - so all you could do was alter the frame of the variable that exists already.
The best approach for something like what you are doing is to declare the variable an Optional:
var label : UILabel?
Which makes it an true Optional, and makes sure that you do not accidentally write code that accesses the variable badly if it's never been set, or sets a value to your first label before you assign the "real" UILabel in your viewDidLoad method. Accessing is a little different, but not much harder - to set text you just use the "?" syntax to optionally call the property if the instance has been set:
label?.text = "fred"
You are correct. UILabel() calls the UILabel no-arg constructor and assigns that object reference to the label variable. So that's a declaration and an initialization.
In the case of the constructor, label is not an optional and is inferred to be the straightforward UILabel type, but in the case of your declaration label is an Optional type. The ! just means it's an optional with a force-unwrapping, so that when you use it as a value, you don't have to type the !; however if you declare it as force-unwrapped and you use it when it's nil-valued, your program crashes.
Note also that you're not allowed to declare:
var label: UILabel
as a class member unless you initialize it in all of your constructors, before the (possibly implicit) call to super.init() because it will complain that there is no initial value, so it might be referenced with no known value -- something the Swift compiler prevents.
When I write a simple function such as this:
#IBAction func buttonTapped(theButton:UIButton) {
println(theButton.titleLabel.text);
}
It gives me an error: UILabel doesn't have a label called text.
However, when I change it to this:
#IBAction func buttonTapped(theButton:UIButton) {
println(theButton.titleLabel?.text);
}
It works fine, but it prints out something like this:
Optional("1");
What I am doing wrong? I am expecting a value of 1. But it is printing out Optional("1") and secondly, it is working fine when println(theButton.titleLabel?.text);
You can get directly from
let title = theButton.currentTitle!
Optional chaining makes the result optional, so you are printing optional value: https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/OptionalChaining.html
With optional binding you can print the value only if it exits.
if let text = theButton.titleLabel?.text {
println(text)
} else {
// text doesn't have value
}
#Kirsteins's answer shows how to obtain the button label text in a safe manner.
Remember that:
UIButton has a titleLabel, which is an optional UILabel.
UILabel has a text property, which is an optional String
so there are 2 optionals in the chain. You can use optional binding as in #Kirsteins's answer, or use forced unwrapping:
let text = theButton.titleLabel!.text!
which however I discourage using, because if any of the 2 is nil you'll have a runtime exception. But for completeness it's worth mentioning.
The buttons titleLabel property returns an optional UILabel, that means it's possible that the button doesn't have a titleLabel.
var titleLabel: UILabel? { get }
If you don't set a title to the button, then the button doesn't have a titleLabel property, the iOS framework adds the titleLabel only if the button has a title, I think this happens to reduce memory.
This is why you have to put the "?" (is called optional chaining you can read about it here http://bit.ly/1vrSOi1) in that line, but this usually get auto completed by Xcode itself.
Kirsteins answers it correctly but misses one small detail
if your object can be nil (optional) you need to check first if it exists to then access its value, like this:
if let text = theButton.titleLabel?.text {
println(text)
}
but you can also ignore the if and just call it like this:
let text : String = theButton.titleLabel?.text
// If theButton.titleLabel don't exists then text will be nil
this happen if the IBOutlet was declared with ? but if you declare with ! that means you know that it could be nil, but you never want it to be nil, for a IBOutlet i prefer this approach since if the IBOutlet is not connected then maybe something is worn with my code.
#IBOutlet var theButton : UIButton!
// and get text value as
theButton.titleLabel!.text
this will ensure theButton.titleLabel could be nil, but in this part of code it is required, hope this helps to understand the difference between optional (?) and optional required (!)
I came across a strange behaviour in Swift while programming a Master-Detail application.
Here's the scenario:
It's a simple Task Manager application. I have two text controls (TaskName, TaskDescription) on the TaskDetailView and two string variables with the same name but in lowerCamelCase (taskName, taskDescription) declared in the TaskDetailViewController.
#IBOutlet var TaskName:UITextField! //UpperCamelCase
#IBOutlet var TaskDescription:UITextView! //UpperCamelCase
var taskName:String? //lowerCamelCase
var taskDescription:String? //lowerCamelCase
I am setting the values of Text controls on ViewDidLoad() as usual:
override func viewDidLoad() {
super.viewDidLoad()
TaskName.text = taskName
TaskDescription.text = taskDescription
}
And I am passing the data in prepareForSegue (from TaskListViewController) as usual:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if(segue.identifier == "TaskListSegue"){
let detailViewController = segue.destinationViewController as ToDoTaskViewController
let (task, desc) = m_ToDoListManager.GetTask(TaskListView.indexPathForSelectedRow().row)
println("selected \(task) \(desc)")
detailViewController.taskName = task
detailViewController.taskDescription = desc
}
}
The way everything is implemented is correct.
But now when you run the application, the values of text controls are not set.
In fact, the values of the variables also are not set.
What must be happening here?
I have already investigated this problem and also came up with a solution (see my answer below). Please also see Martin R's answer below for a detailed explanation. I just wanted to share this with everyone. I am not sure if anyone has come across this issue.
Update:
Here's the actual code:https://github.com/Abbyjeet/Swift-ToDoList
Here is an explanation:
Your Swift class is (ultimately) a subclass of NSObject.
Therefore the properties are Objective-C properties with getter and setter method.
The name of the setter method for a property is built by capitalizing the first
letter of the property name, e.g. property "foo" has the setter method setFoo:
As a consequence, the setter method for both properties TaskName and taskName is called setTaskName:.
In an Objective-C file, you would get a compiler error
synthesized properties 'taskName' and 'TaskName' both claim setter 'setTaskName:' - use of this setter will cause unexpected behavior
but the Swift compiler does not notice the conflict.
A small demo of the problem:
class MyClass : NSObject {
var prop : String?
var Prop : String?
}
let mc = MyClass()
mc.prop = "foo"
mc.Prop = "bar"
println(mc.prop) // bar
println(mc.Prop) // nil
In your case
TaskName.text = ...
sets the "taskName" property, not the "TaskName". The properties have different type,
so that the behavior is undefined.
Note that the problem does only occur for "Objective-C compatible" properties. If you remove the
NSObject superclass in above example, the output is as expected.
Conclusion: You cannot have two Objective-C properties that differ only in the
case of the first letter. The Swift compiler should fail with an error here (as the
Objective-C compiler does).
The problem you were facing with was not connected to the swift language. Method prepareForSegue is called before loadView. That mean UITextField and UITextView are not initialized yet. That's why fields were not initialized.
You also asked: Why compiler doesn't show any error? That's because any selector performed on nil object doesn't throw an exception. So for example (sorry for obj-c):
UITextField *tf = nil;
[tf setText:#"NewText"];
Will not show any error.
As you said on your own answer to solve your problem you need to add additional fields to your destination controller (copy-paste):
var tAskName:String? //cUstomCamelCase
var tAskDescription:String? //cUstomCamelCase
Why is it happening?
I believe that internally Swift is using lowerCamelCase for text controls names which are not yet initialized and thus failing to set the values. But it is also strange that I didn't get any kind of error.
How did I solve it?
I know that the Swift is case-sensitive. So that was not the issue. So I just changed the case of one letter and named the variables as (tAskName, tAskDescription) and the values were set as expected.
#IBOutlet var TaskName:UITextField! //UpperCamelCase
#IBOutlet var TaskDescription:UITextView! //UpperCamelCase
var tAskName:String? //cUstomCamelCase
var tAskDescription:String? //cUstomCamelCase
So the conclusion is that if I have a control named TaskName, I cannot have a variable named as taskName
I have a custom control template that contains a Slider control.
I name that as a part in the class that implements the custom control:
[TemplatePart(Name = MapZoomSliderName, Type = typeof(Slider))]
In the OnApplyTemplate() override, I get the Slider:
MapZoomSlider = (Slider) GetTemplateChild("MapZoomSlider");
if (null != MapZoomSlider)
{
MapZoomSlider.ValueChanged +=new RoutedPropertyChangedEventHandler<double>(MapZoomSlider_ValueChanged);
MapZoomSlider.Value = InitSliderValue; // crash
_lastSliderValue = MapZoomSlider.Value;
}
When I try to set the Slider's Value property, the app crashes with "Object reference not set to an instance of an object."
Getting the slider's value works as expected.
What do I need to do to set the Slider's value at run time?
Thanks for any tips...
What is "InitSliderValue"? Maybe its the wrong value type? (Must be a double) Also, zero or negative may not be a valid value.
It appears the problem was in setting the ValueChanged handler before changing the Value property. The ValueChanged handler tries to manipulate other parts of app, parts that might not be ready yet.
If I set the value, then add the handler, it works as desired.
MapZoomSlider.Value = InitSliderValue; // all good
MapZoomSlider.ValueChanged +=new RoutedPropertyChangedEventHandler<double>(MapZoomSlider_ValueChanged);