#IBDesignable UIView not calling init within storyboard - ios

I have a UIView that is is #IBDesignable
#IBDesignable
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
sharedInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedInit()
}
private func sharedInit(){
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .blue
}
}
When I place this a UIView in the Storyboard, and assigned its class to MyView in the Identity inspector, the UIView still has a default background colour. Why is its background colour not UIColor.blue in the Storyboard? And, how can I make it like this, please?
Thanks for any help.

Initializer which initialize this view from storyboard will call on runtime, for updating view within storyboard in compile time you should try to include prepareForInterfaceBuilder which updates storyboard xib files in compile time.
I suggest you to do multiple things when you are going to create #IBDesignable classes :
Mark class with #IBDesignable tag
Mark UIView property with #IBInspectable, then you will be able to change the value for this property using StoryBoard
Set the configuration code in willSet of that property which is observer for changes before the property takes the value, or didSet after the property received the value.
Do your additional setup in prepareForInterfaceBuilder() which is overriding from its super class kind of UIView
Simple and easy !
Your code should looks like this :
Swift 5 :
import UIKit
#IBDesignable
class myView: UIView {
#IBInspectable var storyBoardColor : UIColor = .red {
willSet(myVariableNameToCatch) {
self.backgroundColor = myVariableNameToCatch
}
}
fileprivate func sharedInit(){
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = storyBoardColor
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
sharedInit()
}
}
myView had initial value of red for storyBoardColor and you can change it from storyBoard ;)

Once you make your view with tag #IBDesignable. next thing is to set your properties with #IBInspectable.
#IBDesignable
class MyView: UIView {
#IBInspectable var myBackgroundColour: UIColor? = nil {
willSet(v) {
self.backgroundColor = v
}
}
// YOUR EXISTING CODE HERE
}
Now, when you set the MyView as a class name in Identity Inspector. You will be able to see your inspectable property in Attributes Inspector. there you can set the colour and it will be reflected instantly to your custom view.
I don't see any usefulness to set the background colour with your custom property because, UIView has the default property to set the background colour.
Hope it helps.

Swift 4.2 tested and working. This is the cleanest solution.
In Xcode make sure Editor -> Automatically refresh views is checked.
import UIKit
#IBDesignable
class BoxView: UIView {
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderColor = borderColor?.cgColor
layer.borderWidth = borderWidth
layer.cornerRadius = cornerRadius
}
#IBInspectable var borderColor: UIColor? {
didSet {
layer.borderColor = borderColor?.cgColor
setNeedsLayout()
}
}
#IBInspectable var borderWidth: CGFloat = 0.0 {
didSet {
layer.borderWidth = borderWidth
setNeedsLayout()
}
}
#IBInspectable var cornerRadius: CGFloat = 0.0 {
didSet {
layer.cornerRadius = cornerRadius
setNeedsLayout()
}
}
}
Create the BoxView.swift file above.
Select a UIView in InterfaceBuilder.
In the Identity Inspector select Custom Class -> Class to "BoxView".
In the Attributes Inspector set borderColor, borderWidth, and borderRadius.

Related

Reusable .xib + cocoa class components, how to auto size and style superview in storyboard?

I have implemented reusable button component using combination of .xib and cocoa class following this guide
It works, but there is an issue. In order to use it in one of my main View Storyboards I have to first drag in a normal view (referenced as superview in question title) and then apply my Button class, to make it a button.
This works, but initial view height and width alongside its white background persist, so I have to always manually rewrite those when I use my component, which in itself results in poor reusability.
Ideally, I'd like to drag in a view, set it to Button class and thats it, that view should instantly take buttons height and width and have transparent background. Is something like this achievable?
To light more context on this issue here are few useful bits of my implementation
1. Reusable component made as a .xib view and its own cocoa class
2. Contents of Button.swift
import UIKit
#IBDesignable class Button: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var label: UILabel!
#IBInspectable var buttonLabel: String? {
get {
return label.text
}
set(buttonLabel) {
label.text = buttonLabel
}
}
override init(frame: CGRect) {
super.init(frame: frame)
componentInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
componentInit()
}
private func componentInit() {
let bundle = Bundle(for: Button.self)
bundle.loadNibNamed("Button", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
}
3. Example usage (inside one of my main view storyboards) demonstrating how ordinary view is turned into a button, but has issues with height, width and background color
P.S ^ if its hard to tell what is going on in a gif above, I basically drag UIView into a story board and give it custom class attribute of Button, this turns that view into a button.
EDIT: Just to make it clearer, my question is: Can I apply width, height and transparent colour to my XIB's parent / super view? End goal here is to just drag in a view onto storyboard, give it custom class of a Button and thats it, it should be sized properly and have transparent background, as opposed to how it is at the moment (view doesn't get sized as button and has white background)
You have to pin your subviews in Button properly and also in Main.storyboard. Then your custom view will autosize. And clear color is working.
import UIKit
#IBDesignable
class Button: UIView {
#IBOutlet var contentView: UIView!
#IBOutlet weak var label: UILabel!
#IBInspectable
var backColor: UIColor = .clear {
didSet {
backgroundColor = backColor
contentView.backgroundColor = backColor
}
}
override var backgroundColor: UIColor? {
get {
return backgroundColor
} set {
}
}
#IBInspectable
var buttonLabel: String? {
get {
return label.text
}
set(buttonLabel) {
label.text = buttonLabel
}
}
override init(frame: CGRect) {
super.init(frame: frame)
componentInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
componentInit()
}
private func componentInit() {
let bundle = Bundle(for: Button.self)
bundle.loadNibNamed("Button", owner: self, options: nil)
addSubview(contentView)
contentView.frame = bounds
backgroundColor = backColor
contentView.backgroundColor = backColor
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
// for static height
contentView.heightAnchor.constraint(equalToConstant: 70).isActive = true
}
}
To ensure you CustomView would size itself properly, you can use bottom constraint >= 0. After testing reset to equals.

UIButton's layer.cornerRadius removes visibility of button title

I have added a custom class for my buttons where I set corner radius (to save some code for multiple VCs) but once I set it, title from my buttons disappears. You can see I have a button title set and it worked ok before choosing a custom class.
Background of my button is the gray color with alpha. I have tried to play with the .isOpaque setting but got no luck getting the title back. Any idea what could cause this problem?
#IBDesignable class RoundedButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 8
override func layoutSubviews() {
layer.cornerRadius = cornerRadius
}
}
Edit: Solved! Thanks u/zombie for an explenation!
The title does not show because its frame was not updated.
To fix the layout you need to call super.layoutSubviews
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = cornerRadius
}
Your approach might prevent any update of the radius outside of your variable.
here is a better way to do it:
#IBDesignable class RoundedButton: UIButton {
private var defaultCornerRadius: CGFloat = 8
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
}
}
override init(frame: CGRect) {
super.init(frame: frame)
cornerRadius = defaultCornerRadius
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
cornerRadius = aDecoder.decodeObject(forKey: "cornerRadius") as? CGFloat ?? defaultCornerRadius
}
override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
aCoder.encode(cornerRadius, forKey: "cornerRadius")
}
}
You forget
override func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = cornerRadius
}

How to set a default title in UIButton subclass that works in Interface Builder (Xcode 9)

I have a button that repeats throughout my app, so I created a subclass to avoid having to set all the basic properties every time.
I am able to set the background colour, the text colour, round the corners.
However, things fall apart when I try to set a default title - something other than "Button".
In Interface Builder it ignores the title, but it also then ignores the font colour, which works when I don't set the title.
If I run the app, it all looks fine, but one major point of using Interface Builder is to save the step of constantly running the app to check basic UI layout.
Here is the subclass.
Note that if you comment out the 2 setTitle lines, the button shows the correct text colour (white).
import UIKit
#IBDesignable class ContinueButton: UIButton {
#IBInspectable var titleColour: UIColor = .white {
didSet {
setTitleColor(titleColour, for: .normal)
}
}
#IBInspectable var bgColour: UIColor = UIColor.gray {
didSet {
backgroundColor = bgColour
}
}
#IBInspectable var buttonTitle: String = "Continue" {
didSet {
setTitle(buttonTitle, for: .normal)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setAttributes()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setAttributes()
}
public func setAttributes() {
setTitleColor(titleColour, for: .normal)
backgroundColor = bgColour
setTitle(buttonTitle, for: .normal)
}
override public func layoutSubviews() {
super.layoutSubviews()
setAttributes()
layer.cornerRadius = 0.5 * bounds.size.height
clipsToBounds = true
}
}
ps, My main objective is to create a reusable custom button that takes care of setting a bunch of defaults. If there's a better way to achieve that, I'd be very happy to hear that - especially if it could be done visually rather than through code.
Thanks for any advice you can give,
-Nico
Just override prepareForInterfaceBuilder() and add setAttributes().
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setAttributes()
}
The tintColor property will do the trick.
button.tintColor = UIColor.red
but to make sure to override the prepareForInterfaceBuilder()
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
button.tintColor = UIColor.red
}

Subclassing UIView, UIViewController and show the defaults visually in the storyboard and Interface Builder

Is there a way to setup subclasses of UIView and UIViewController in Swift to that all new UIView created with Xcode interface builder are using the default values from my subclasses.
In other words, if I create this code for a subclass of UIView :
import UIKit
#IBDesignable
class GenericUIView: UIView {
// MARK: Initialization
#IBInspectable
var myBackgroundColor : UIColor = UIColor.orange
override init (frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.layer.backgroundColor = (myBackgroundColor as! CGColor)
}
}
What do I need to do so that I can see immediately the changes in the Interface Builder of Xcode?
I want to be able to theme my app without having to edit all the properties by hand and still see them visually without building the solution. I know it is sort of possible with overriding drawRect() but that could potentially slow down my app performance when it might be just setting up background and text colour.
Please provide a solution in Swift language if possible.
Patrick
Does this help? You would have to change all of your views to use this class instead of UIView though. You'd add other properties like backgroundcolor in the same way (as you seem to know).
//
// DesignableView.swift
//
import UIKit
#IBDesignable
class DesignableView: UIView {
#IBInspectable var borderWidth: CGFloat = 0.0 {
didSet {
self.layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: UIColor = UIColor.clear {
didSet {
self.layer.borderColor = borderColor.cgColor
}
}
#IBInspectable var cornerRadius: CGFloat = 0.0 {
didSet {
self.layer.cornerRadius = cornerRadius
}
}
}
Ok, I think I have it. Can anyone confirm if this is Best Practice and if this would cause other issue in my program later? It's actually quite simpler then I thought :
import UIKit
#IBDesignable
class GView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
self.layer.backgroundColor = UIColor.orange.cgColor
}
}

How to custom this image tab in UIView,using #IBDesignable and #IBInspectable is better

How to draw the following tab image in UIView?The text is changeable,which implies that the image could stretch in width. And I know in XCode6,it supports live render.So I think if possible,it's color,text,and size could be set in attributes inspector.
You have to create a custom class, based on UIView. This class is declared as #IBDesignable and has #IBInspectable properties. Override UIView.drawRect() and you are totally free on how your view gets displayed.
Here is a sample class to get you started.
import UIKit
#IBDesignable
class MyTabView: UIView {
#IBInspectable tabTitle: String = ""
#IBInspectable tabColor: UIColor = UIColor.clearColor()
override init(frame: CGRect) {
super.init(frame: frame)
// Initialization code
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func prepareForInterfaceBuilder() {
// stuff for interface builder only
}
override func drawRect(rect: CGRect)
{
// this is where your view gets drawed
self.layer.cornerRadius = 10
self.layer.masksToBounds = true
}
}

Resources