I have extended the UIView class and added a property for cornerRadius. The property does set to desired value. I have made two custom classes one derives from UITextField and another from UILabel. UITextField gets rounded corners but UILabel does not.
Any help in this regard will be highly appreciated.
#IBDesignable
public class BLabel: UILabel {
public override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = cornerRadius
layer.masksToBounds = true
clipsToBounds = true
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
extension UIView {
#IBInspectable
var cornerRadius : CGFloat {
get {return layer.cornerRadius}
set {layer.cornerRadius = newValue}
}
}
In your BLabel class you access the cornerRadius property of your UIView extension in the init method. This is before you have any chance to set a specific corner radius value so it will be 0.
There's no point to the line layer.cornerRadius = cornerRadius in the init method of BLabel. Simply create the BLabel instance and then set its cornerRadius property.
let label = BLabel(frame: someFrame)
label.cornerRadius = 5
Are you sure your UITextField is responding to cornerRadius? Or are you maybe just seeing the normal rounded corners?
Try changing your BLabel to this - it will make sure the initializations are being called properly:
#IBDesignable
public class BLabel: UILabel {
public override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
public override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
public override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
func commonInit() {
// As noted by "rmaddy" ---
// setting .cornerRadius here does nothing, as it is always equal to Zero
// the UIView extension will handle it
//layer.cornerRadius = cornerRadius
layer.masksToBounds = true
clipsToBounds = true
// the following just makes it easy to confirm
// that this code is being executed
backgroundColor = UIColor.red
textColor = UIColor.yellow
textAlignment = .center
}
}
I would like to thank #rmaddy for help. I am writing this for the benefit for all. The code given by rmaddy works. But, after testing it I figured out that it is not required. Just setting layer.masksToBounds = true in UIView extension cornerRadius setter method does the trick. So the entire problem was solved by just this one line of code.
So the final code looks like this and it works:
#IBDesignable
public class BTextField: UITextField {
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
#IBDesignable
public class BLabel: UILabel {
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
extension UIView {
#IBInspectable
var cornerRadius : CGFloat {
get {return layer.cornerRadius}
set {layer.cornerRadius = newValue
layer.masksToBounds = true}
}
}
I hope it helps others also.
Related
I have a class called badge that inherits from another class and I want the constructor method to tell the style of the notification.
but I'm having this error:
Property 'self.notificationStyle' not initialized at super.init call
DefaultTableViewCell class
final public class DefaultTableViewCell: UITableViewCell
enum NotificationStyle {
case numberedSquare, circle
}
var notificationStyle: NotificationStyle = .numberedSquare
my goal is whenever someone instantiates this Badge class, it will be necessary to inform the notificationStyle of it, being square or circular in this case.
how can i solve this?
Badge class
#objc public class Badge: NotifyLabel
var notificationStyle: DefaultTableViewCell.NotificationStyle
init(frame: CGRect, notificationStyle: DefaultTableViewCell.NotificationStyle) {
self.notificationStyle = notificationStyle
super.init(frame: frame)
setup(notificationStyle: notificationStyle)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup(notificationStyle: notificationStyle)
}
init(notificationStyle: DefaultTableViewCell.NotificationStyle) {
self.notificationStyle = notificationStyle
super.init(frame: .zero)
setup(notificationStyle: notificationStyle)
}
func configureBadgeNotificationStyle(notificationStyle: DefaultTableViewCell.NotificationStyle) {
textAlignment = NSTextAlignment.center
layer.borderWidth = 1
layer.borderColor = Color.bostonRed.cgColor
clipsToBounds = true
textStyle = .label
backgroundColor = Color.white
switch notificationStyle {
case .circle:
layer.cornerRadius = 8
default:
layer.cornerRadius = 2
}
}
private func setup(notificationStyle: DefaultTableViewCell.NotificationStyle) {
configureBadgeNotificationStyle(notificationStyle: notificationStyle)
configureAccessibility()
}
NotifyLabel class
public class NotifyLabel: UILabel
public init(textStyle: TextStyle) {
self.textStyle = textStyle
super.init(frame: .zero)
applyTextStyle()
}
public override init(frame: CGRect) {
super.init(frame: frame)
applyTextStyle()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
applyTextStyle()
}
You can fix this by adding a line to set notificationStyle to a default value in init?(coder aDecoder: NSCoder):
required init?(coder aDecoder: NSCoder) {
self.notificationStyle = .numberedSquare //<-- Here
super.init(coder: aDecoder)
setup(notificationStyle: notificationStyle)
}
You have to do this because in your declaration of notificationStyle, there's no default value and it must have a value before calling super.init. In your other initializers, you set it based on the incoming arguments.
This is an initializer that it sounds like you're not using anyway, but it is required with UIViews that we implement this required initializer.
I have a UITextView custom class:
class TitleTextView: UITextView {
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
func setup() {
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
textColor = .brand100
backgroundColor = .clear
isUserInteractionEnabled = false
textAlignment = .left
isScrollEnabled = false
let frameWidth = Constants.screenSize.width * 87.5 / 100
font = UIFont.OpenSans(.semibold, size: (frameWidth * 8.55 / 100))
}
}
I used this text view custom class inner a UIView.
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
And I called this UIView in a UIViewController.
private func setupTitleView() {
let titleView = UINib(nibName: "TitleView", bundle: .main).instantiate(withOwner: nil, options: nil).first as! UIView
titleView.frame = contentHeaderView.bounds
contentHeaderView.addSubview(titleView)
view.layoutIfNeeded()
}
But when I set the textColor property in my custom UIView (MyCustomHeaderView) the color doesn't change.
Do you have any idea about why the reason that my UITextView doesn't apply the color that I set in my custom UIView?
I called layoutIfNeed() but this doesn't work.
It's because you are doing everything inside the layoutSubviews Which in itself is really bad practice.
In your case you instantiate the CustomHeaderView and the layout for that is called, hence calling layoutSubviews next step is that the textView is added to your CustomHeaderView and then the textView's layoutSubviews is called and will override your color.
You can solve this in two ways i believe. Altho i don't work with Nibs and storyboards,
first:
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
Second, this is a big maybe:
class MyCustomHeaderView: UIView{
#IBOutlet weak var titleTextView: TitleTextView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
defer {
backgroundColor = .brand100
titleTextView.text = "Market Place"
titleTextView.textColor = .brand400
layoutIfNeeded()
}
}
}
Defer will wait till everything is been initialised before running whatever is in the block. I don't know tho how that works with layoutSubviews
I have added a custom class to UILabel.
Custom Class is:
#IBDesignable class CustomLabel: UILabel {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
}
private func setup() {
self.textColor = UIColor.blue
}
}
But I cant see the changes in storyboard. How can it is able to see the changes in the interface builder??
You have to add IBInspectable properties to see changes in storyboard
here is example
#IBDesignable class RoundedTextField: UITextField {
#IBInspectable var cornerRadius:CGFloat = 0 // You will see this in storyboard
#IBInspectable var borderColor:UIColor = .green // You will see this in storyboard
override func awakeFromNib() {
super.awakeFromNib()
self.borderStyle = .none
self.layer.cornerRadius = cornerRadius
self.layer.borderColor = borderColor.cgColor
self.layer.borderWidth = 2.0
self.backgroundColor = UIColor.white
self.layer.masksToBounds = true
}
}
Don't forgot to set class
Here how you can see in stoyrboard
I have a UIView as below:
import UIKit
#IBDesignable
class CHRAlertView: UIView {
#IBOutlet var icon:UILabel!
#IBOutlet var alertText:UITextView!
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
private func initialize(){
self.backgroundColor = UIColor.green
}
}
Based on how #IBDesignable works, this should show up in IB with a green background, but I get the clear color like this:
Why is this not functioning as expected? I need the background color to show in IB based on the defaults set in my #IBDesignable.
Since backgroundColor is an IB property not created via #IBInspectable, it always seems to overwrite whatever is in the init or draw methods. Meaning, if it is "default" in IB, it causes it to be overwritten with nil. However, if set in the prepareForInterfaceBuilder method backgroundColor works and shows in IB. So, the backgroundColor, it can be reasonably assumed, must be set at runtime. To do this I have the below:
//------------------
//Setup and initialization
//------------------
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.initialize()
}
//Setups content, styles, and defaults for the view
private func initialize(){
self.staticContent()
self.initStyle()
}
//Sets static content for the view
private func staticContent() {
}
//Styles the view's colors, borders, etc at initialization
private func initStyle(){
}
//Styles the view for variables that must be set at runtime
private func runtimeStyle(){
if self.backgroundColor == nil {
self.backgroundColor = UIColor.green
}
}
override func prepareForInterfaceBuilder() {
self.runtimeStyle()
}
override func awakeFromNib() {
super.awakeFromNib()
self.runtimeStyle()
}
This defaults the backgroundColor if it is "default" (read nil) in IB to a color, but does not use the UIColor.green if a backgroundColor is set in IB, which is exactly what I need.
Shoutout to Eridius in the #swift-lang irc for helping me get to this answer.
In Storyboard I have UITextField, I also created custom class:
class WLTextField: UITextField {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
layer.cornerRadius = 2
layer.masksToBounds = true
layer.borderColor = UIColor.grayColor().CGColor
layer.borderWidth = 1
}
}
Then attached this class to my UITextField:
But, there is no result for this in Storyboard. How can I achieve this? I need to get such effect like it is with #IBDesignables. But since I know what I need to set up for every text field, I do not need set a value and then update this within didSet.
Do I need to set at least one value to make changes live in Storyboard on some UIView?
In order for a component to render in the Interface Builder you must flag the class as #IBDesignable and you must implement the initialise init(frame: CGRect) -
#IBDesignable class WLTextField: UITextField {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupLayer()
}
required override init(frame: CGRect) {
super.init(frame: frame)
setupLayer()
}
func setupLayer () {
layer.cornerRadius = 2
layer.masksToBounds = true
layer.borderColor = UIColor.grayColor().CGColor
layer.borderWidth = 1
}
}