UIAppearance, see result in Interface Builder? - ios

I'm using UIAppearance a lot for my navigation controllers and other UI objects, and I was wondering if it's possible to have my related objects styled in Interface Builder (maybe with some magic voodoo of #IBDesignable?)

matt's comments on the question are correct. You're able to successfully use UIAppearance proxies in the prepareForInterfaceBuilder method.
Example:
#IBDesignable
class MyCustomView: UIView {
override func prepareForInterfaceBuilder() {
MyCustomView.appearance().backgroundColor = UIColor.redColor()
}
}
This will result in all MyCustomView instances to be rendered red in IB.

Related

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
}

Add custom functionality to UITextField, UILabel, UIView custom classes

In my app I have some interface elements such as :CustomLabel, CustomView, CustomTextField (all are custom classes inherit from their base class: UIView, UILabel, UITextField.
At this moment I'm calling from each element DrawRect callback to a function that draws underline on itself and basically I have chunks of duplicated code on each subclass.
How can I make this three subclasses have the same DrawRect content without creating an extension of UIView because I don't want each View or subclass of UIView to have this behaviour.
For this you could use a protocol and a protocol extension to add your common methods to your subclasses. Then you'd only have to make your classes conform to that protocol and override drawRect to call the methods from your protocol. Adding the new behaviour by just conforming to the protocol and not doing any other changes is not possible, unfortunately. You can't override methods in protocol extensions, and you can't add methods that use the Objective-C runtime there either which would allow method swizzling.
In code this would look something like this:
protocol CustomDrawing {}
extension CustomDrawing where Self: UIView {
func myDrawingCode() {
// Whatever
}
}
You use it like this then:
class CustomLabel: UILabel, CustomDrawing {
override func draw(_ rect: CGRect) {
super.draw(rect)
myDrawingCode()
}
}
If you need to access some common property or method in myDrawingCode() you will have to declare them inside the CustomDrawing protocol. UIView methods are available since the protocol extension is constrained to UIView and subtypes.

XCode - Change Default Global Font in Storyboard

Very much like how we can set the Global Tint in Storyboard, Is it possible to set the global font-family as something else?
For example, I want to change the Global/Default Font from System to Source Sans Pro 16pt. However, what I have to do (to my knowledge) is one of the following:
Change font of each label, button, textField, etc. in Storyboard.
Set it via Swift ViewDidLoad Code (like this question) or through extensions as explained in this question
My Problem with (2) is that I do not get Visual Feedbacks like in (1) using storyboards. On the other hand, it is also not very eloquent as I have to manually set it anyway.
So, is there a way to change/set the default Storyboard font?
You can use the Appearance API, to ensure that all controls have the same font (at runtime)
Look at this question as to how to set the font for UIButton with Appearance.
For UILabel and UITextView, do the following. This can be done in AppDelegate application(_:didFinishLaunchingWithOptions:)
let labelAppearance = UILabel.appearance()
labelAppearance.font = UIFont.myFont()
let textFieldAppearance = UITextView.appearance()
textFieldAppearance.font = UIFont.myFont()
The previous solution, however will not update storyboard.
To see the changes visually on storyboard, you can look into the function
prepareForInterfaceBuilder()
Here is an answer that explains how to get it visually updated in storyboard, but for this you will need to use custom classes for your textFields, buttons, etc.
Code Example as per above link:
#IBDesignable
public class MyUILabel: UILabel {
public override func awakeFromNib() {
super.awakeFromNib()
configureLabel()
}
public override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
configureLabel()
}
func configureLabel() {
font = UIFont(name: Constants.DefaultFont, size: 40)
}
}

Storyboard Designtime Layout Attributes

This is a feature a like a lot when defining layouts for Android and that allows to define attributes just to design.
http://tools.android.com/tips/layout-designtime-attributes
But I didn't find any equivalent way to do the same when using storyboard to make iOS Apps.
At the moment I a cleaning all the design values on a viewDidLoad of my ViewController. Is there a way to define layout attributes as design placeholders and avoid making this setupClean step in all my view controllers?
When You want Instant Reflect of attributes/properties of particular componet in storyboard then you can achieve using #IBDesignable.
To achieve this You have to subcalss of that type of which you want to be reflected on storyboard.
Steps
Make a subclass of the type you want be reflected on storyboard, as in example here i am going to subclass UILabel as DGlabel
Make that calss as #IBDesignable, see the example
Assign that subclassed to the component from Identity Inspector, see screen shot
In subclassed calss (here in example DGLabel) declare variables of properties/attributes which you want be reflected on design as #IBInspectable, see example here i have decalred borderColor as #IBInspectable, this means this property will be listed on property inspector of Xcode
now you can change the value of that property from story board as screen shot below
import UIKit
#IBDesignable
class DGLabel: UILabel {
#IBInspectable var borderColor:UIColor = UIColor.red {
didSet {
reflectChange()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func reflectChange() {
self.layer.borderWidth = 1
self.layer.borderColor = borderColor.cgColor
}
}
Hope this will allow you to understand the process.
Yes you can do that by providing runtime attribute in "User defined runtime attributes" section under storyboard's Identity inspector. See below screenshot for setting UILabel's "text" keypath's runtime value as blank.
I think you can achieve something similar by using IBDesignable for your views. Then you may use prepareForInterfaceBuilder() and TARGET_INTERFACE_BUILDER to generate mock data for display in Interface Builder.
Did you mean this?
You can change Label Text value in storyboard from the "Attribute Inspector".

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