Strange issue with 2 initializers in Swift - ios

I want to create 2 initializers for my custom button class which looks like:
protocol CustomButtonProtocol {
var title: String { get set }
var cornerRadius: CGFloat { get set }
var backgroundColor: UIColor { get set }
}
struct Button: CustomButtonProtocol {
var title: String
var cornerRadius: CGFloat
var backgroundColor: UIColor
}
class CustomButton: UIButton {
var title: String? {
didSet {
self.setTitle(title, for: .normal)
}
}
var cornerRadius: CGFloat = 0.0 {
didSet {
self.layer.cornerRadius = cornerRadius
}
}
var color: UIColor? {
didSet {
self.backgroundColor = color
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
init(with frame: CGRect, button: Button) {
super.init(frame: frame)
self.title = button.title
self.cornerRadius = button.cornerRadius
self.backgroundColor = button.backgroundColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I want to have 2 initializers, the first one is simple init, the second one with my Button passed in, but the issue is that it changes ONLY background color but does not title and cornerRadius properties and I cannot find why it happens. Maybe I do not see something, could you please help me with finding the bug?

You have used didSet to update value to button property.
It won't call didSet while setting value inside init.
background color is getting set because you set backgroundColor by mistake in place of color
self.backgroundColor = button.backgroundColor
you should use/call below inside/outside init once.
self.setTitle(title, for: .normal)
self.layer.cornerRadius = cornerRadius
self.backgroundColor = color

Consider that property observers willSet / didSet are not called in init methods
From the documentation:
When you assign a default value to a stored property, or set its initial value within an initializer, the value of that property is set directly, without calling any property observers.
A solution is to call the code in the observers also in the init method
init(with frame: CGRect, button: Button) {
super.init(frame: frame)
self.title = button.title
self.setTitle(self.title, for: .normal)
self.cornerRadius = button.cornerRadius
self.layer.cornerRadius = self.cornerRadius
self.color = button.backgroundColor
self.backgroundColor = self.color
}
If color represents always the background color the property is actually redundant.

Related

Constrained UIButton frame is not updating to the buttons constrained position in Xcode storyboard

I have the following class that creates a UIButton subclass. I have set this up so the button automatically constrains itself to the superview and sets it's height.
#IBDesignable
class PrimaryButtonConstrained: UIButton {
#IBInspectable
var cornerRadius: CGFloat = 8 {
didSet {
setupPrimaryConstrainedButton()
}
}
#IBInspectable
var borderWidth: CGFloat = 0 {
didSet {
setupPrimaryConstrainedButton()
}
}
#IBInspectable
var topConstraint: CGFloat = 100 {
didSet {
layoutSubviews()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupPrimaryConstrainedButton()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupPrimaryConstrainedButton()
}
override class var requiresConstraintBasedLayout: Bool {
return true
}
override func layoutSubviews() {
super.layoutSubviews()
translatesAutoresizingMaskIntoConstraints = false
leadingAnchor.constraint(equalTo: superview!.layoutMarginsGuide.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: superview!.layoutMarginsGuide.trailingAnchor).isActive = true
heightAnchor.constraint(equalToConstant: 50).isActive = true
topAnchor.constraint(equalTo: superview!.topAnchor, constant: topConstraint).isActive = true
}
func setupPrimaryConstrainedButton() {
setTitleColor(.white, for: .normal)
setTitleColor(.gray, for: .disabled)
setTitleColor(.orange, for: .highlighted)
layer.borderWidth = borderWidth
layer.cornerRadius = cornerRadius
if isEnabled {
backgroundColor = .orange
} else {
backgroundColor = .gray
}
}
override public func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupPrimaryConstrainedButton()
}
}
The button constraints itself correctly in interface builder when you set the button class, however the frame of the button does not update to the buttons new location as shown.
Does anyone have any ideas on how to fix the frame so it still encloses the button?

Why does my Swift button show a small blue square when tapped?

I created a custom button that is designable in storyboard, and i styled the button select state. Now when i tap the button blue square appear above that. This is my code:
Swift version: 4.0
#IBDesignable class CircularButton: UIButton {
#IBInspectable var borderColor: UIColor = UIColor(red: 255.0, green: 153.0, blue: 0.0, alpha: 1.0)
var borderWidth: CGFloat = 3.0
#IBInspectable var bgColor: UIColor? {
didSet {
updateButton()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setUpButton()
self.addTarget(self, action: #selector(btnTapp), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setUpButton() {
self.backgroundColor = self.bgColor
let cornerRadius = (self.frame.size.width + self.frame.size.height) / 4
self.layer.cornerRadius = cornerRadius
}
#objc func btnTapp(_ sender: UIButton) {
if isSelected == false {
isSelected = !isSelected
} else if isSelected == true {
isSelected = !isSelected
}
}
override var isSelected: Bool {
didSet {
updateState(state: isSelected)
}
}
func updateState(state: Bool) {
if state == false {
self.layer.borderWidth = 0.0
self.layer.borderColor = borderColor.cgColor
print("fls")
} else if state == true {
self.layer.borderWidth = 3.0
self.layer.borderColor = borderColor.cgColor
print("tre")
}
}
}
these are image of my problem in selected state:
is unselect state
TylerTheCompiler wrote:
If your button's type is "System" in your storyboard, try setting it to "Custom".
Then, original poster wrote:
so tnx comrade the problem is solved
(Just marking this as Answered, so it doesn't look unanswered in the future.)

IBDesignable class don't seems to work properly

I'm building a new iOS app with swift and trying to make a custom IBDesignable UIButton class to use in the designer.
My code does not seems to work either in the builder or the app itself at runtime.
The default value i gave to the IBInspectable vars does not apply to them.
#IBDesignable class MyButton: UIButton {
#IBInspectable var backColor: UIColor = UIColor.red {
didSet {
self.backgroundColor = backColor
}
}
#IBInspectable var textColor: UIColor = UIColor.white {
didSet {
self.setTitleColor(textColor, for: .normal)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
}
private func setup() {
titleLabel?.font = UIFont.systemFont(ofSize: 17)
contentEdgeInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
layer.cornerRadius = (frame.height / 2)
setTitle(title(for: .normal)?.uppercased(), for: .normal)
}
}
I expect to see the button in the designer/app with a red background and a white text and none of these happens.
I'm using Swift 4.2 and Xcode 10.1.
add this two func s
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
override func layoutSubviews() {
super.layoutSubviews()
self.setup()
}
no need for init
Setting a property default value does not invoke the didSet clause.
You need something like:
private func setup() {
titleLabel?.font = UIFont.systemFont(ofSize: 17)
contentEdgeInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
layer.cornerRadius = (frame.height / 2)
setTitle(title(for: .normal)?.uppercased(), for: .normal)
// I would do it differently, but this is just for demo purposes
// Also, can't assign a prop to itself, so using a temp
var tmp = self.backColor
self.backColor = tmp
tmp = self.textColor
self.textColor = tmp
}
Additionally, Xcode is not very good with IBDesignable, in swift or objective C.
Make sure the menu item: Editor -> Automatically Refresh Views is checked.
You can also explicitly do Editor -> Refresh All Views.
From my experience - this stops working after a while, and you need to quit Xcode and restart it to make it work again.
Make sure that your button is set to type .custom in your Interface Builder.
This is not obvious but the default button type is .system, which cannot be customized. I saw people having no problems with this but in my applications this was always a problem.
Another problem is that if you keep your inspectable values empty, the didSet won't be called and the default value won't be applied.
#IBInspectable var backColor: UIColor? = UIColor.red {
didSet {
updateBackground()
}
}
private func updateBackground() {
self.backgroundColor = backColor
}
func setup() {
...
updateBackground()
}

#IBDesignable not working in iOS swift 3

What I did:
Deleted derived data, Restarted xcode, Editor->Refresh all views
I am getting Designables build failed in storyboard when I click on Editor->Refresh all views.
Please check the following code. What am I missing? Textfiled is not getting updated in the storyboard when I change the #IBInspectable value from the attributes inspector.
import UIKit
import QuartzCore
#IBDesignable
class BorderedFloatingTF: UITextField {
required init?(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
setup()
}
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 20, dy: 0)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return textRect(forBounds: bounds)
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
}
// properties..
#IBInspectable var enableTitle : Bool = false
#IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable var borderWidth: Int = 1 {
didSet {
layer.borderWidth = CGFloat(borderWidth)
}
}
#IBInspectable var placeHolderColor: UIColor = UIColor.white {
didSet {
self.attributedPlaceholder = NSAttributedString(string:self.placeholder != nil ? self.placeholder! : "", attributes:[NSForegroundColorAttributeName: placeHolderColor])
}
}
fileprivate func setup() {
borderStyle = UITextBorderStyle.none
layer.borderWidth = CGFloat(borderWidth)
layer.borderColor = borderColor.cgColor
placeHolderColor = UIColor.white
}
}
Try this
import QuartzCore
#IBDesignable
class BorderedFloatingTF: UITextField {
required init?(coder aDecoder:NSCoder) {
super.init(coder:aDecoder)
setup()
}
override init(frame:CGRect) {
super.init(frame:frame)
setup()
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 20, dy: 0)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return textRect(forBounds: bounds)
}
// properties..
#IBInspectable var enableTitle : Bool = false
#IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable var borderWidth: Int = 1 {
didSet {
layer.borderWidth = CGFloat(borderWidth)
}
}
#IBInspectable var placeHolderColor: UIColor = UIColor.white {
didSet {
self.attributedPlaceholder = NSAttributedString(string:self.placeholder != nil ? self.placeholder! : "", attributes:[NSForegroundColorAttributeName: placeHolderColor])
}
}
func setup() {
borderStyle = UITextBorderStyle.none
layer.borderWidth = CGFloat(borderWidth)
layer.borderColor = borderColor.cgColor
placeHolderColor = UIColor.white
}
}
Your IBDesignables & IBInspectables both are working fine.
I had this problem with a custom class loading from a xib. Finally stumbled on this solution (had to use the correct bundle for the class, rather than Bundle.main):
#IBDesignable
class DataEntryView: UIView {
#IBOutlet var contentView: DataEntryView!
#IBInspectable var hasError : Bool = false
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
private func commonInit() {
let bundle = Bundle.init(for: type(of: self))
bundle.loadNibNamed("DataEntryView", owner: self, options: nil)
addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
contentView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true
contentView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true
contentView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
}
}
I had the same problem on a project. I did the following steps to fix the issue.
In the custom IBDesignable view, make sure that you override both methods
required init?(coder aDecoder: NSCoder)
override init(frame: CGRect)
Add following to your Runpath Search Paths in Build Settings
#loader_path/Frameworks
$(CONFIGURATION_BUILD_DIR)
Clean your project, delete derived data.
That should be all fine.
In my project i was missing for setting its value Type. Your problem is not about that. But for other people who are having same issue with me, I wanted to explain.
Example:
I was writing like below:
#IBInspectable public var rowOrColoumnCount = 0
But It should have been like:
#IBInspectable public var rowOrColoumnCount : Int = 0
Check whether it's correct Module specified in "Module" field. It's specified "Template1" now, is it correct?
try also to make all #IBInspectable properties as public
Try to leave only this part:
#IBDesignable
class BorderedFloatingTF: UITextField {
// properties..
#IBInspectable var enableTitle : Bool = false
#IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable var borderWidth: Int = 1 {
didSet {
layer.borderWidth = CGFloat(borderWidth)
}
}
#IBInspectable var placeHolderColor: UIColor = UIColor.white {
didSet {
self.attributedPlaceholder = NSAttributedString(string:self.placeholder != nil ? self.placeholder! : "", attributes:[NSForegroundColorAttributeName: placeHolderColor])
}
}
}
Change the custom class back to what it was (UITextField?)
Only set the File's Owner in the xib to BorderedFloatingTF

How to create a custom button and use multiple times in best way in iOS?

I have created a custom UIButton in ios. I want to use the same custom button ten times with the same attributes but different titles. What is the most efficient and clever way to do this without repeating the same code for each button? Should it be a struct or a class or something else? How can I implement it? Attributes that I have changed for my custom button is as follows:
#IBOutlet weak var button_1: UIButton!
button_1.frame = CGRectMake(0.0, 0.0, button_1.frame.width, button_1.frame.height)
button_1.clipsToBounds = true
button_1.layer.cornerRadius = button_1.frame.width/2.0
button_1.layer.borderColor = UIColor.whiteColor().CGColor
button_1.layer.borderWidth=2.0
You can create your own class that extends from UIButton
import UIKit
#IBDesignable
final class CoyoteButton: UIButton {
var borderWidth: CGFloat = 2.0
var borderColor = UIColor.white.cgColor
#IBInspectable var titleText: String? {
didSet {
self.setTitle(titleText, for: .normal)
self.setTitleColor(UIColor.black,for: .normal)
}
}
override init(frame: CGRect){
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
func setup() {
self.clipsToBounds = true
self.layer.cornerRadius = self.frame.size.width / 2.0
self.layer.borderColor = borderColor
self.layer.borderWidth = borderWidth
}
}
To set a text to the button, just write myButton.titleText = "myText".
Then you can drag and drop a UIButton from the interface builder and then change the class of that button to your own MyOwnButton, or create one by code.
You need to create the extension of UIButton like this
extension UIButton {
class func attributedButton(frame: CGRect) -> UIButton {
let button = UIButton(frame: frame)
button.clipsToBounds = true
button.layer.cornerRadius = button_1.frame.width/2.0
button.layer.borderColor = UIColor.whiteColor().CGColor
button.layer.borderWidth = 2.0
return button
}
}
Now call this method like this way
let button = UIButton.attributedButton(frame: CGRectMake(20, 20, 10, 40)
button.setTitle("Title", forState: .Normal)
self.view.addSubview(button)
Now you can use this method every time you want to create a button with these fixed attributes.
question author marked correct answer (author: Brigadier), but it not works properly in swift 4 (maybe in other versions to, have not checked).
For example when I set button title it is not shown in run time. the reason is following: it is not called super.layoutSubviews().
The correct way is here:
#IBDesignable
class MyOwnButton: UIButton {
var borderWidth = 2.0
var boderColor = UIColor.whiteColor().CGColor
#IBInspectable
var titleText: String? {
didSet {
self.setTitle(titleText, forState: .Normal)
self.setTitleColor(UIColor.blackColor(), forState: .Normal)
}
}
override init(frame: CGRect){
super.init(frame: frame)
}
required init?(aDecoder: NSCoder) {
super.init(aDecoder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
func setup() {
self.clipsToBounds = true
self.layer.cornerRadius = self.frame.size.width / 2.0
self.layer.borderColor = borderColor
self.layer.borderWidth = borderWidth
}
}
I have a repo with a custom button.
You could use it: https://github.com/titopalito/CustomUIButton
It is not mine, I got it from a tutorial ;)
Best regards.

Resources