Lazy UILabel property of custom control - ios

I want to be able to set the title of my custom control in interface builder and implement the title as a UILabel. I have created my properties thus:
#IBInspectable var titleText: String? {
get {
return title.text
}
set(newTitleText) {
title.text = newTitleText
}
}
#IBInspectable lazy var title: UILabel = UILabel.init()
Is this a good/recommended way to do this? I also want the control to layout the UILabel if the title text has been set and plan to do this by overriding layoutSubviews - again is this recommended or would you suggest a different pattern?
My ultimate aim is to be able to enable a designer to configure my control from IB and exposing the UILabel as a property that can be configured in IB would be my ideal.

#IBInspectable can be used only with the following types:
Int
CGFloat
Double
String
Bool
CGPoint
CGSize
CGRect
UIColor
UIImage
So, no, you can not "expose a UILabel" for design-time manipulation.

First, I don't think that you need to make your variable lazy here, also, if you want to create a new instance of UILabel, it's better to user the constructor UILabel().
Now, I am not sure if I understand correctly your question, but if I did, then you can expose your custom view properties (a string that represents your title label for example) to the Attributes Inspector.
You would do it like so:
#IBDesignable
class CustomView: UIView {
#IBOutlet weak var titleLabel: UILabel!
#IBInspectable var title: String? {
didSet { self.titleLabel.text = title }
}
}
To break up the code above :
First you make the class (your custom view) #IBDesignable so that it supports live preview in the Interface Builder.
Then, inside your custom view class, you create a title property (which is a String) and make sure to make it #IBInspectable so that you can change this property later via the Attributes Inspector.
Finally you add a property observer ( didSet ) so that the text property of your custom label is updated whenever you change the title property from the Attributes Inspector.

Related

Human readable tag/id for a View in iOS

Let's say I have created a UI with some views using an interface builder (not in code, so not programmatically).
How can I assign human readable ids / tags to these views, so that I could reference them in code?
I know that I can assign an integer tag to a view using attribute inspector and then make a dictionary (or enum) to store the mapping of tags to the views. However, this is an error-prone method which also scales really badly (imaging assigning integer tags to hundred of views in a complex app...).
Is there a better solution for this problem? Is there a way to directly assign a human readable tag / id to a view, like "resumeButton"?
UPDATE:
Here is an example scenario of what I want to achieve:
UI with five different buttons; the buttons have image and no title
all five buttons are connected to the same IBAction in code
in IBAction I have a switch statement, so that depending on which button is clicked, different versions of code are executed
UPDATE 2:
SOLUTION
I ended up implementing a simple custom view:
#IBDesignable
class CustomButton: UIButton {
#IBInspectable var stringTag: String = defaultID
}
This way I can see an additional property stringTag in Interface Builder and can simply add a value to it directly in Interface Builder.
You could create an extension property on UIView to store an identifier string. If you made that extension property IBInspectable, you could set and view it from the storyboard directly.
More detail on setting up such a property in this answer:
https://stackoverflow.com/a/37166043/1830999
The built-in tag property for UIView is just an integer, so it isn't very descriptive for humans who read it.
From what you're describing sounds to me that you are looking for an: Outlets
You are right about tags. Every connection with Interface Builder should be handled using IBAction and IBOutlet.
Since you say that every button has a different action, the simplest solution is to create a separate IBAction for each of them:
#IBAction private func onResumeButtonTapped() {
...
}
#IBAction private func onPauseButtonTapped() {
...
}
If you, for some reason, want to keep them connected to one function, you can use outlets:
#IBOutlet private var resumeButton: UIButton!
#IBOutlet private var pauseButton: UIButton!
#IBAction private func onButtonPressed(_ sender: UIButton) {
switch sender {
case resumeButton:
...
case pauseButton:
...
default:
break
}
}

Round button on custom class with UIAppearance

I am trying to apply styles on a custom class using UIAppearance()
class MainStyleButton: UIButton {}
with a code:
let buttonView = MainStyleButton.appearance()
buttonView.backgroundColor = Style.buttonColor
buttonView.layer.cornerRadius = 5
buttonView.layer.borderWidth = 5
buttonView.layer.borderColor = Style.buttonColor.cgColor
It works with color, but unfortunately doesn't make my button round. I would appreciate any tips.
Tested on simulator iPhone X, 8 with iOS 11.2.
I tried replicating your approach and set up a button. I tried to change the button's appearance in a UIViewController during viewDidLoad and also in the AppDelegate during applicationDidFinishLaunching using your code. I additionally tested changing the button type to .custom from the default type .system. None of this seemed to work, I could not override the same attributes that you couldn't.
From Apple's docs I understand that the button type defines its appearance and also which appearance attributes can be overridden:
A button’s type defines its basic appearance and behavior. You specify the type of a button at creation time using the init(type:) method or in your storyboard file. After creating a button, you cannot change its type.
I do not know why the attributes of interest to you are not changeable at this point
However I would like to offer a different approach that I personally use and allows you to change the buttons appearance. Since you already defined your custom class it is much simpler to define corner radius and other attributes that you would like, like so (or you could write a style function with parameters that you can call at any time, to be able to change the appearance based on where the button is used):
class MainStyleButton: UIButton {
override func awakeFromNib() {
layer.borderColor = Style.buttonColor.cgColor
layer.borderWidth = 5
layer.cornerRadius = 5
}
}
Or you can instantiate/use an IBOutlet for a system button and do this:
class MyViewController: UIViewController {
#IBOutlet weak var myButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// not necessary to do this is viewDidLoad, that's just my example
myButton.layer.borderColor = Style.buttonColor.cgColor
myButton.layer.cornerRadius = 5
myButton.layer.borderWidth = 5
}

Override UI element .enabled property in Swift

I want to override property of UIView class to do extra lines of code each time property value of .enabled is changed. How do I do that?
For example:
There is MyUIView class myClass
There is myClass.enabled property
I want to override this to add next things
Edit subview of type UIView and set background to red colour if enabled/green if disabled.
Edit subview of type UIButton and make it disabled if disabled/enabled if enabled.
The code will look something like this -
class SubClass: UIView {
override var userInteractionEnabled: Bool {
didSet {
// Do the color change and other stuff.
// Use oldValue to access old value
}
}
}
There is much more to explore about this setter and getter.

Failed to set (cornerRadius) user defined inspected property on (UIView)

Details
I was watching the video of Session 411 on WWDC 2014 referring to "What's new on Interface Builder", and I was trying out how to create Frameworks in order to make #IBDesignable classes to preview my changes in Storyboard without having to run the application.
#IBInspectable properties are showing correctly when I add my class to a specific view and are rendering the view correctly with the below code:
Code
#IBDesignable
class MyView: UIView {
#IBInspectable var borderColor: UIColor = UIColor.clearColor() {
didSet {
layer.borderColor = borderColor.CGColor
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
}
}
#IBInspectable var masksToBounds: Bool = false {
didSet {
layer.masksToBounds = masksToBounds
}
}
}
Log
I noticed that these attributes were being added on User Defined Runtime Attributes (Under Identity Inspector). Now what I am expecting from this is when I run the code to keep these changed I made with a specific view.
I run the application and the view doesn't load the User Defined Runtime Attributes, and gives this error on output (not crash):
Unknown class MyClass in Interface Builder file
Failed to set (cornerRadius) user defined inspected property on (UIView)
Question
What is causing the application not to load User Defined Runtime Attributes that I added under Identity Inspector?
The code is correct.
When you declare a # IBDesignable all the #IBInspectable properties are exposed to the Interface Builder as User Defined Runtime Attributes.
The problem -
Unknown class MyClass in Interface Builder file
Is means that Interface Builder couldn't find some class. You have set wrong class that doesn't exist in your app.
Your customView class is MyView but in the Interface Builder you have MyClass
Solution to fix-
Set correct class in interface Builder, in your case MyView
Check Designable status. It should be Up to date , if it's not than something is wrong.
Also If you decided to remove customer Designable you should
Remove custom class
Remove User Defined Runtime Attributes
Based on the question title, a few people may come here with a slightly different problem (like I did). If you
added an #IBInspectible
and then deleted it in code
Then you may also get an error similar to
Failed to set (xxx) user defined inspected property on [Your Custom
View] ...: this class is not key value coding-compliant for the key
[xxx].
The solution is to delete the the old property.
Open the Identity inspector for your class, select the property name under User Defined Runtime Attributes, and press the minus button (-).
Again, this is not the answer to the OP's question, but it might be the answer to someone else's problem who comes here.
in Indentity inspector add in user Defined runtime attributes
layer.cornerRadius - type string
after go to Attibutes inspector and select "Clip Subviews"

Live Render IBOutlet Connected Subviews Via IBInspectable Properties

I'm using storyboards for the first time in iOS 8 and so far have been loving the live rendering aspect of things on the storyboard. However, I seem to have hit a snag in getting my views to render properly on the storyboard.
I have a container UIView that contains a connection to a UILabel on the storyboard, I am attempting to set the label's text based on an IBInspectable attribute on the label's parent container view.
#IBDesignable class ContainerView : UIView {
#IBOutlet weak var : titleLabel : UILabel!
#IBInspectable var title : String = "" {
didSet {
titleLabel?.text = title
}
}
/* Init functions */
prepareForInterfaceBuilder() {
self.titleLabel?.text = title
}
}
If I set the attribute in the storyboard it renders as expected while the program is executing but fails to render in the storyboard as I would expect. I've checked my connections and everything appears to be hooked up properly.
My question is: Is it possible to affect the contents of an IBOutlet connected view via IBInspectable attributes and have them live render on the storyboard, and if so, what am I missing or doing wrong?
Unfortunately you can't see IBOutlet objects in interface builder for your custom views which are marked as IBDesignable. If you want to see your outlets in interface builder, you have to use regular variables instead IBOutlet and you have to create your objects programmatically.
Also please note that, if you need to change something from interface builder for your objects, you have to define your properties as IBInspectable. Currently following variables types are valid for IBInspectable:
Bool, CGFloat, CGPoint, CGRect, CGSize, NSInteger, NSString, UIColor, UIImage
I hope this answer is adequately clear for you.
Edit: I found following article which is describing a way how to do what you need:
http://justabeech.com/2014/07/27/xcode-6-live-rendering-from-nib/
2nd Edit: I tried the article and it works. Now I can see my outlets on interface builder

Resources