I am subclassing NSLayoutConstraint to provide custom spacing depending on a flag. Is it possible to have this reflected in the Interface Builder? I tried using IBDesignable and prepareForInterfaceBuilder, but it has no effect on the NSLayoutConstraint in the Interface Builder (only at runtime does it reflect).
Here's what I'm trying:
#IBDesignable
class CustomLayoutConstraint: NSLayoutConstraint {
#IBInspectable var doubleConstant: Bool = false
override func awakeFromNib() {
super.awakeFromNib()
handleSelection()
}
override func prepareForInterfaceBuilder() {
handleSelection()
}
func handleSelection() {
if doubleConstant {
constant *= 2
}
}
}
I have a flag in IBInspectable that will double the constant at runtime if set. Is there a way for this to be reflected in the StoryBoard so it's clear something will be happening?
Related
ViewController:
class ViewController: UIViewController, ViewSpecificController {
typealias RootView = CustomView
override func viewDidLoad() {
super.viewDidLoad()
view().configure()
}
override func loadView() { self.view = CustomView() }
}
UIView:
class CustomView: UIView {
func configure() {
backgroundColor = .orange
translatesAutoresizingMaskIntoConstraints = false
addConstraints()
}
func addConstraints() {
var constraints = [NSLayoutConstraint]()
constraints.append(self.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor))
constraints.append(self.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor))
constraints.append(self.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor))
constraints.append(self.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor))
NSLayoutConstraint.activate(constraints)
}
}
Executing this code results in an error "[LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want."
I tried to initialize UIView, the same error appeared there. How to fix it?
From what i can see it looks like your CustomView class is trying to set constraints to itself. The constraints aren't needed as the ViewController will handle sizing it automatically once you replace the original in loadView(). Removing your addConstraints() method from configure() should solve your problem. See if that works...
I'm making button changing style of needed labels, so I created my own class and using appearance() on that, but it is not working. What should I do to fix that?
I've tried the same what I've seen with UILabel class, but made my own subclass:
#IBOutlet weak var SomeLabel: MyUILabel
class MyUILabel: UILabel {}
override func viewDidLoad() {
super.viewDidLoad()
MyUILabel.appearance().textColor = UIColor.red
}
I expected to change color of all labels of class MyUILabel, but it doesn't work, only if I do this with common UILabel.
Do below and this works. I have tested this on simulator.
MyUILabel
import UIKit
class MyUILabel : UILabel {
static func setCustomColor(color: UIColor) {
UILabel.appearance().textColor = color
}
}
In ViewDidLoad have below
override func viewDidLoad() {
super.viewDidLoad()
MyUILabel.setCustomColor(color: UIColor.purple)
}
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.
I have a #IBDesignable UIView (ContainerView) that has one subView (also #IBDesignable). I would very much like to be able to update the constraints of the subView in the code in a way were they are automatically updated in InterfaceBuilder. Example:
#IBDesignable class ContainerView: UIView {
#IBOutlet var mySubView: MyView!
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
self.setup()
}
override func layoutSubviews() {
super.layoutSubviews()
self.setup()
}
private func setup() {
self.mySubView.leadingConstraint.constant = MyResource.sizes.defaultLeading
}
}
This will work just fine at runtime, but it crashes the IBDesignablesAgent because mySubView is nil when running prepareForInterfaceBuilder.
I want to do it this way to be able to set my constraints globally in some constants, but keep the view representation in my xib files.
Does anyone have a work around for this, or am I reaching for the impossible here?
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
}
}