UIView's border color in Interface builder doesn't work?
Asked Answered
S

16

78

I am trying to set up a view's layer properties via IB. Everything works except for color of the border (property layer.borderColor):

enter image description here

I remember running into this problem a year ago and I ended up doing it programatically. And still, I can do this programmatically, but I am curious why the layer.borderColorproperty never works via interface builder. I don't want to import QuartzCore, and then write extra line of code just because of this, seems like an overkill.

Scrummage answered 9/2, 2013 at 22:19 Comment(4)
"write extra line of code just because of this, seems like an overkill" - your app must be containing at most 10 lines of code then (with the C standard headers included) :PPsychodiagnosis
@H2CO3 lol...i just don't like writing code that i don't have toScrummage
I've made this mistake in the past.. I hope you are aware of it.. otherwise check the first answer from this: #3980751Duramen
You can do this with a proxy property! See my answer in #12301756Woodcraft
D
83

It's possible to do this, but it's not a built-in feature. This is because the Color type in the User Defined Runtime Attributes panel creates a UIColor, but layer.borderColor holds a CGColorRef type. Unfortunately, there's no way to assign a CGColorRef type in Interface Builder.

However, this is possible through a proxy property. See Peter DeWeese's answer to a different question for a possible solution to this problem. His answer defines a category that allows a proxy color to be set through Interface Builder.

Draggletailed answered 21/2, 2013 at 19:16 Comment(1)
A computed property is not required. You can find an answer here that shows how to get this to work in a storyboard with layer.borderColor. https://mcmap.net/q/262749/-uiview-39-s-border-color-in-interface-builder-doesn-39-t-workNervy
Y
52

You have to create Category for CALayer:

CALayer+UIColor.h

#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>

@interface CALayer(UIColor)

// This assigns a CGColor to borderColor.
@property(nonatomic, assign) UIColor* borderUIColor;

@end

CALayer+UIColor.m

#import "CALayer+UIColor.h"

@implementation CALayer(UIColor)

- (void)setBorderUIColor:(UIColor*)color {
    self.borderColor = color.CGColor;
}

- (UIColor*)borderUIColor {
    return [UIColor colorWithCGColor:self.borderColor];
}

@end

And then in User Defined Runtime attributes You can use it as it is on image below:

enter image description here

For Swift it is much more simple:

import QuartzCore

extension CALayer {
    @IBInspectable var borderUIColor: UIColor? {
        get {
            guard let borderColor = borderColor else { return nil }
            return UIColor(cgColor: borderColor)
        }
        
        set {
            borderColor = newValue?.cgColor
        }
    }
}

Then in Xcode you can use it like this:

enter image description here

Once you choose something it is automatically added to your runtime attributes.

Yettie answered 16/1, 2015 at 14:56 Comment(4)
This is the easiest solution and one that I implemented. Downside is you can forget it's there (it's only a few lines!).Pammy
Why borderUIColor property is assign instead of strong? It's an object type, doesn't this lead to a problem?Sedgewinn
^ because it doesn't hold the value, it passes it on.Anders
Actually, getter in your example can be simpler: borderColor.map { UIColor(cgColor: $0) }Hamforrd
T
27

Copy and paste this class:

import UIKit

@IBDesignable class BorderView : UIView {
    @IBInspectable var borderColor: UIColor = .clear {
        didSet {
        layer.borderColor = borderColor.cgColor
        }
    }

    @IBInspectable var borderWidth: CGFloat = 0 {
        didSet {
            layer.borderWidth = borderWidth
        }
    }

    @IBInspectable var cornerRadius: CGFloat = 0 {
        didSet {
            layer.cornerRadius = cornerRadius
        }
    }
}

Now in Interface Builder, go to the Identity inspector and set your view as a CustomView class.

After that, check out your Attributes Inspector:

Attributes inspector with the new IBInspectable options

No need to mess around with user defined runtime attributes anymore. And your changes will also show up on the canvas!

Toothwort answered 20/8, 2016 at 15:44 Comment(0)
M
20

My two cents for porting Bartłomiej Semańczyk's answer to Swift:

Create an extension for CALayer in your view controller:

import UIKit

extension CALayer {
    func borderUIColor() -> UIColor? {
        return borderColor != nil ? UIColor(CGColor: borderColor!) : nil
    }

    func setBorderUIColor(color: UIColor) {
        borderColor = color.CGColor
    }
}
Merca answered 18/5, 2015 at 19:43 Comment(1)
Hello @Eduardo, then how will I use this in Storyboard?Lithometeor
D
11

Use IBDesignable instead of Runtime Attributes it is more clear.

Put this code in any class and edit the properties direct on the storyboard.

import UIKit

@IBDesignable extension UIView {
    @IBInspectable var borderColor:UIColor? {
        set {
            layer.borderColor = newValue!.CGColor
        }
        get {
            if let color = layer.borderColor {
                return UIColor(CGColor:color)
            }
            else {
                return nil
            }
        }
    }
    @IBInspectable var borderWidth:CGFloat {
        set {
            layer.borderWidth = newValue
        }
        get {
            return layer.borderWidth
        }
    }
    @IBInspectable var cornerRadius:CGFloat {
        set {
            layer.cornerRadius = newValue
            clipsToBounds = newValue > 0
        }
        get {
            return layer.cornerRadius
        }
    }
}
Discretional answered 16/2, 2016 at 17:32 Comment(4)
This saves a major headache when you're applying these to several items. Slick little tidbit.Paapanen
I'm sorry but this doesn't work. I have just tried it myself. @IBDesignable doesn't work on extensions of UIView. #29907355Toothwort
Just tried with some tiny upper/lower case problem with Swift 3.0. Shame that I did not know this before.Icebox
As of Xcode 9 & Swift 3, this actually does work at runtime. It just doesn't display the changes in the interface builder.Polypody
M
5

Here's a quick way to overcome this. Categories...

@interface UIView (IBAppearance)

@property (nonatomic, strong) UIColor *borderColor;

@end

You don't have to store it, it's just nice so you can query later. The important thing is taking the value and assigning the UIColor's CGColor to the layer.

#import <objc/runtime.h>

#define BORDER_COLOR_KEYPATH @"borderColor"

@implementation UIView (IBAppearance)

- (void)setBorderColor:(UIColor *)borderColor {
    UIColor *bc = objc_getAssociatedObject(self, BORDER_COLOR_KEYPATH);
    if(bc == borderColor) return;
    else {
        objc_setAssociatedObject(self, BORDER_COLOR_KEYPATH, borderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        self.layer.borderColor = [borderColor CGColor];
    }
}

- (UIColor *)borderColor {
    return objc_getAssociatedObject(self, BORDER_COLOR_KEYPATH);
}

@end

Of course, in the Interface Builder you're not setting the value on layer.borderColor, rather just on borderColor.

Manutius answered 16/11, 2013 at 22:21 Comment(2)
i think there is no need to use associated objects because something may happen and your borderColor will be out of sync with layer.borderColor. just return [UIColor colorWithCGColor:self.layer.borderColor]Pollywog
You're absolutely right @storoj. It's a pattern we use for adding properties and being able to store them using categories. It's misapplied here.Manutius
H
5

In Swift, you can extend the UIButton class and add an @IBInspectable that will enable you to select a color from storyboard and set it's color (with width of 1 which can be changed). Add this at the end of your view controller:

extension UIButton{
    @IBInspectable var borderColor: UIColor? {
        get {
            return UIColor(CGColor: layer.borderColor!)
        }
        set {
            layer.borderColor = newValue?.CGColor
            layer.borderWidth = 1
        }
    }
}
Hypnosis answered 22/1, 2016 at 22:56 Comment(0)
A
4

In order to make CALayer KVC-compliant for the property borderColorFromUIColor, simply implement the

layer.borderColorFromUIColor=[UIColor red];

This link have awnser

Authorized answered 17/11, 2014 at 8:33 Comment(0)
L
2

I met the same issue, I worked around it by creating a custom button class:

class UIButtonWithRoundBorder: UIButton {

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.layer.cornerRadius = 6
    self.layer.borderWidth = 1
    self.layer.borderColor = UIColor.whiteColor().CGColor
    self.clipsToBounds = true
}

}

Then in IB, change the type from "UIButton" to "UIButtonWithRoundBorder".

Simple and handy too. :)

Laborer answered 28/7, 2016 at 3:45 Comment(0)
C
2

swift4

extension CALayer {

  open override func setValue(_ value: Any?, forKey key: String) {

    /// If key is borderColor, and the value is the type of a UIColor.
     if key == "borderColor" , let color = value as? UIColor {

        /// After converting UIColor to CGColor, call the system method.
        return super.setValue(color.cgColor, forKey: key)
     }

     super.setValue(value, forKey: key)
   }
}
Croesus answered 16/11, 2018 at 4:3 Comment(3)
Please provide some explanation to your code & w help to fix the issueShoshana
This is actually the working solution!Alkanet
Adding an extension like this is unreliable at best. Definitely not the way to do it in 2021.Epicenter
W
1

You can set a value for the "borderColor" key in the XIB and use:

extension UIView {

    open override func setValue(_ value: Any?, forKey key: String) {
        guard key == "borderColor", let color = value as? UIColor else {
            super.setValue(value, forKey: key)
            return
        }

        layer.borderColor = color.cgColor
    }
}
Wallenstein answered 14/9, 2017 at 9:33 Comment(2)
Excellent. This works in Xcode 13.4 with layer.borderColor and not needing to use a computed property. Just needed an import UIKit and it works as written.Nervy
my only corrections were to change UIView to CGLayer and to change layer.borderColor to self.borderColor. Your answer is the only solution I have seen that answers this question as asked. Wonderful work.Nervy
E
1

Swift 5.2 - Answer of Fede Henze's

@IBDesignable extension UIView {

@IBInspectable var borderColor:UIColor? {
    set {
        layer.borderColor = newValue!.cgColor
    }
    get {
        if let color = layer.borderColor {
            return UIColor(cgColor:color)
        }
        else {
            return nil
        }
    }
}
@IBInspectable var borderWidth:CGFloat {
    set {
        layer.borderWidth = newValue
    }
    get {
        return layer.borderWidth
    }
}
@IBInspectable var cornerRadius:CGFloat {
    set {
        layer.cornerRadius = newValue
        clipsToBounds = newValue > 0
    }
    get {
        return layer.cornerRadius
    }
}
}
Earphone answered 15/7, 2020 at 5:49 Comment(0)
L
0

I think it may be because you have masksToBounds set to YES. I don't think the border is drawn within the bounds of the layer, so it won't be drawn since you're hiding everything outside of its bounds.

Lavernelaverock answered 10/2, 2013 at 8:32 Comment(1)
Hmmmm...not @ my desktop so will experiment with that, but if I set masksToBounds to NO, then I can't have UIImageView rounded/corner radius :(Scrummage
F
0

borderColor will not work UNLESS the borderWidth property of the layer is set to a value greater than 0.

Swift 3:

button.layer.borderColor = UIColor.white.cgColor
button.layer.borderWidth = 1.0 // Default value is 0, that's why omitting this line will not make the border color show.
Femmine answered 6/2, 2017 at 11:6 Comment(1)
Your answer has nothing to do with the question. The poster said he knows how to do it programmatically. Also, you can see that he already set the borderWidth to be >1 in his screenshot. He asked about doing this specifically in IB.Upanchor
Z
0

You can customise border with 2 methods. First one is this. Just click on the object go to the identity inspector and set the attributes.

enter image description here

Second one is this. make an IBOutlet of required object and put this code in view did load.

@IBOutlet weak var uploadView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        uploadView.layer.cornerRadius = 10
        uploadView.layer.borderWidth = 1.0
        uploadView.layer.borderColor = #colorLiteral(red: 0.08235294118, green: 0.5058823529, blue: 0.9450980392, alpha: 1)
    }
Zipangu answered 25/10, 2018 at 7:13 Comment(0)
B
0

try this in User Defined Runtime attribute:

  1. Key Path: layer.borderUIColor
  2. type: Color
  3. Value: --you prefered color--
Ballonet answered 8/2, 2021 at 10:48 Comment(1)
You need an extension on CGLayer to do that. You also misspelled "your".Nervy

© 2022 - 2024 — McMap. All rights reserved.