I am trying to extend the UIButton class by adding a cornerRadius property which can be changed at the design time without having to build the app. I am using the following extension class:
import UIKit
#IBDesignable
extension UIButton {
#IBInspectable var cornerRadius :CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
But when I make a change of the property cornerRadius in the Storyboard I do not see the change happening live! Am I missing something!
Extensions don't honor the IBDesignable qualifier. Only actual subclasses do. Annoying but true.
try this code:
#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
}
}
}
}
this will show effect on runtime
Related
I am currently at the start of a project and trying to make some universal inspectable/designable variables for UIViews, UIButtons, and UIImageViews to make designing in InterfaceBuilder easier. For example:
#IBInspectable
var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
}
}
One suggestion I've seen to accomplish this is:
extension UIView {
#IBInspectable
var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
}
}
}
However, the problem with this is that now all subclasses of UIView, show the inspectable properties, which is problematic when I will only be using these properties on maybe three classes, and I add more properties like border width/color, shadow radius, opacity, offset, and color, and these take up a lot of the inspector pane in IB.
I'd like something like this:
#IBDesignable class MyView: UIView, MyDesignable {}
#IBDesignable class MyButton: UIButton, MyDesignable {}
#IBDesignable class MyImageView: UIImageView, MyDesignable {}
protocol MyDesignable {}
extension MyDesignable where Self: UIView {
#IBInspectable
var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
}
}
...
Unfortunately, #IBInspectable springs the error Only instance properties can be declared #IBInspectable.
I also tried:
#IBDesignable class MyView: UIView {}
#IBDesignable class MyButton: UIButton {}
#IBDesignable class MyImageView: UIImageView {}
private extension UIView {
#IBInspectable
var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
}
}
...
}
However, other UIView subclasses still showed the inspectable properties.
Hopefully the question and intent behind it make sense. Please let me know if you have any suggestions. Thank you!
I'm trying to apply IBInspectable to all types of view like UIView, UIButton, UITextField, UIImageView, etc.
Here's what I did:
#IBDesignable
class BaseView: UIView
{
override func layoutSubviews() {
super.layoutSubviews()
updateCornerRadius()
}
#IBInspectable var rounded: Bool = false {
didSet {
updateCornerRadius()
}
}
#IBInspectable var cornerRadius: CGFloat = 4 {
didSet {
updateCornerRadius()
}
}
private func updateCornerRadius() {
layer.cornerRadius = rounded ? cornerRadius : 0
}
}
It is working fine with all UIView using BaseView subclasses in storyboard, but how can I use this for buttons, text fields, image views, etc?
May be using protocols or extensions to avoid repeating this code to all other types of views...
You should use an extension instead of a subclass, and access the layer property directly:
extension UIView
{
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius;
}
set(value) {
layer.cornerRadius = value;
}
}
var rounded: Bool {
return layer.cornerRadius > 0.0;
}
}
Note: Since you're not implementing drawRect using #IBDesignable is needless. You can't implement roundedas a settable property this way, but you can have a read-only property for that.
It is the subClass of UIView. To use the these class you have to create subClass of UIImage, UIButton etc.
So create subclasses of each component like this
class ButtonSubClass: UIButton {}// for button subclass
class ImageSubClass: UIImage {}// for image subclass
I made a extension of CALayer for border color as a runtime attribute, but it is not working.
It shows default black color only.
extension CALayer {
var borderUIColor: UIColor {
set {
self.borderColor = newValue.cgColor
}
get {
return UIColor(cgColor: self.borderColor!)
}
}
}
You should handle nil values with care, and may create an extension to UIView which declares the property as #IBInspectabe:
import UIKit
extension UIView {
#IBInspectable var borderColor: UIColor? {
get {
if let color = layer.borderColor {
return UIColor(cgColor: color)
}
else {
return nil
}
}
set { layer.borderColor = newValue?.cgColor }
}
}
This makes it much easier to set the border color in Attribute Inspector.
EDIT: Your example works for me with Xcode 9.0 and 8.3.3 as well. Probably it was a bug in a beta version.
Try this:
extension UILabel{
func setBorderColor(width:CGFloat,color:UIColor) -> Void
{
self.layer.borderColor = color.cgColor
self.layer.borderWidth = width
}
}
and implement this function wherever you want,like this
label.setBorderColor(width: 2.0, color: UIColor.brown)
Hope this helps!
So I'm fairly new to the whole UIAppearance approach to doing things, and doing it with swift. Hurray for not a ton of documentation out there.
I'm trying to set my border radius through UIAppearance with something along the lines of:
CircleButton.appearance.roundBorderRadius = 9
My CircleButton class implementation:
public class CircleButton : UIButton{
#nonobjc var roundBorderRadius: CGFloat? {
get { return self.layer.cornerRadius }
set {
self.layer.cornerRadius = newValue!
}
}
}
And I hook everything up in Storyboard to a ViewController that contains a CircleButton. No Compilation or Build errors.
However, at runtime I'm getting a:
"Thread 1: EXC_BAD_ACCESS" error on:
CircleButton.appearance.roundBorderRadius = 9
Any advice?
Remove #nonobjc and add dynamic, then change the type from CGFloat? to CGFloat and remove the ! after newValue in the setter, like so:
public class CircleButton: UIButton {
dynamic var roundBorderRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
}
}
}
I Set CornerRadius and BorderWidth for UIbutton in User Defined Runtime Attributes. Without adding layer.borderColor it works Well and Display border in black color. But when add layer.borderColor does not work(does not show border).
For Swift:
Swift 3:
extension UIView {
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
#IBInspectable var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue
}
}
#IBInspectable var borderColor: UIColor? {
get {
return UIColor(cgColor: layer.borderColor!)
}
set {
layer.borderColor = newValue?.cgColor
}
}
}
Swift 2.2:
extension UIView {
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
#IBInspectable var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set {
layer.borderWidth = newValue
}
}
#IBInspectable var borderColor: UIColor? {
get {
return UIColor(CGColor: layer.borderColor!)
}
set {
layer.borderColor = newValue?.CGColor
}
}
}
I got answer. Change borderColor instead of layer.borderColor:
and add this code in .m file:
#import <QuartzCore/QuartzCore.h>
#implementation CALayer (Additions)
- (void)setBorderColorFromUIColor:(UIColor *)color
{
self.borderColor = color.CGColor;
}
#end
Tick properties in Attribute Inspector
Swift 4, Xcode 9.2 - Use IBDesignable and IBInspectable to build custom controls and live preview the design in Interface Builder.
Here is a sample code in Swift, place just below the UIKit in ViewController.swift:
#IBDesignable extension UIButton {
#IBInspectable var borderWidth: CGFloat {
set {
layer.borderWidth = newValue
}
get {
return layer.borderWidth
}
}
#IBInspectable var cornerRadius: CGFloat {
set {
layer.cornerRadius = newValue
}
get {
return layer.cornerRadius
}
}
#IBInspectable var borderColor: UIColor? {
set {
guard let uiColor = newValue else { return }
layer.borderColor = uiColor.cgColor
}
get {
guard let color = layer.borderColor else { return nil }
return UIColor(cgColor: color)
}
}
}
If you go to the Attributes inspectable of the view, you should find these properties visually, edit the properties:
The changes are also reflected in User Defined Runtime Attributes:
Run in build time and Voila! you will see your clear rounded button with border.
The explanation, perhaps being lost in some of the other answers here:
The reason that this property is not being set is that layer.borderColor needs a value with type CGColor.
But only UIColor types can be set via Interface Builder's User Defined Runtime Attributes!
So, you must set a UIColor to a proxy property via Interface Builder, then intercept that call to set the equivalent CGColor to the layer.borderColor property.
This can be accomplished by creating a Category on CALayer, setting the Key Path to a unique new "property" (borderColorFromUIColor), and in the category overriding the corresponding setter (setBorderColorFromUIColor:).
There is a much better way to do this! You should use #IBInspectable. Check out Mike Woelmer's blog entry here:
https://spin.atomicobject.com/2017/07/18/swift-interface-builder/
It actually adds the feature to IB in Xcode! Some of the screenshots in other answers make it appear as though the fields exist in IB, but at least in Xcode 9 they do not. But following his post will add them.
This works for me.
Swift 3, Xcode 8.3
CALayer extension:
extension CALayer {
var borderWidthIB: NSNumber {
get {
return NSNumber(value: Float(borderWidth))
}
set {
borderWidth = CGFloat(newValue.floatValue)
}
}
var borderColorIB: UIColor? {
get {
return borderColor != nil ? UIColor(cgColor: borderColor!) : nil
}
set {
borderColor = newValue?.cgColor
}
}
var cornerRadiusIB: NSNumber {
get {
return NSNumber(value: Float(cornerRadius))
}
set {
cornerRadius = CGFloat(newValue.floatValue)
}
}
}
In case of Swift, function doesn't work. You'll need a computed property to achieve the desired result:
extension CALayer {
var borderColorFromUIColor: UIColor {
get {
return UIColor(CGColor: self.borderColor!)
} set {
self.borderColor = newValue.CGColor
}
}
}
You have set the data values for the radius and the width set to be a string, but it should properly be to be set to a number, not a string
When you get it working, this will not be visible while looking at the storyboard, but will be when the app is running unless you have taken steps to make it #IBDesigneable.