I've been playing with the IBInspectable/IBDesignable like in this article: http://nshipster.com/ibinspectable-ibdesignable/.
It shows how you can make an extension to add extra editing options in storyboard. The problem however is that you can't see these changes reflected in the preview. For this you need to subclass, use IBDesignable and do the didset{} stuff.
The problem with this is that you need to make a subclass of every stock UIView subclass. So a subclass of UILabel, UITextField and so on. Every time you have to copy/paste the regular UIView stuff like borders and corner radius.
I don't believe Swift supports multiple inheritance, which would have made this much easier.
Let's say your IBDesignable subclass of UIView is called IBView. Is there a way to make e.g. UILabel be a subclass of IBView instead of UIView?
In the end I'm looking if there is a way to make IBDesignable less tedious.
Like you, I'm trying to find a better solution to work with IBDesignable.
What I did to avoid repeat the same code:
I made an extension of UIView to insert all the #IBInspectables I want (like corder radius, border width...).
I created my #IBDesignables that only inherit from UIView, UIButton (or any subclass of UIView I want) in order to render on Interface Builder.
Check this code:
extension UIView {
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
#IBInspectable var borderColor: UIColor? {
get {
return UIColor(cgColor: layer.borderColor ?? UIColor.clear.cgColor)
}
set {
layer.borderColor = newValue?.cgColor
}
}
#IBInspectable var borderWidth: Double {
get {
return Double(layer.borderWidth)
}
set {
layer.borderWidth = CGFloat(newValue)
}
}
}
#IBDesignable
class BorderView: UIView { }
#IBDesignable
class BorderButton: UIButton { }
I hope it helps you!
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
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 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
I am using the swift extension.
extension UIView {
#IBInspectable var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
layer.masksToBounds = newValue > 0
}
}
}
Which is cookie cutter from several examples regarding #IBInspectable. However, when I use this in my project, the storyboard does not update while viewing in xcode.
When compiling and running however, it does show the rounded corners.
If anyone has any suggestions, it would be very very much appreciated. The project is here. github.com/captainchung/test
You also need to set your view as #IBDesignable. You can try to set it over the extension but not sure it would work, it would work if you subclass UIVIew with your own implementation and then use #IBDesignable and #IBInspectable