Swift Setting subclass UIButton ButtonType - ios

I have a simple subclass of UIButton
class IconButton: UIButton {
init(type: FontAwesomeStyle, icon: FontAwesome, color: UIColor = .black, size: CGFloat = 20) {
super.init(frame: CGRect.zero)
let iconAsText = String.fontAwesomeIcon(name: icon)
let iconText = NSMutableAttributedString(string: iconAsText, attributes: [
NSAttributedString.Key.font: UIFont.fontAwesome(ofSize: size, style: type)
])
setAttributedTitle(iconText, for: .normal)
setTitleColor(color, for: .normal)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The problem I am having is that I would like the button to have the behavior that system buttons have. Specifically when you press and hold the button, it changes color.
let button = UIButton(type: .system)
Since buttonType is a get only property of UIButton and UIButton.init(type: UIButton.ButtonType) is a convenience initailizer, I am not sure how to implement this a subclass.

Still not sure if its possible to replicate the .system button type in a subclass, but the solution for getting the behavior I wanted was the following:
class IconButton: UIButton {
override var isHighlighted: Bool {
willSet(newVal) {
if newVal != isHighlighted {
// newVal is true when the button is being held down
}
}
}
// Rest of class...
}

Related

Is there a way to set the buttonType of a custom button class in Swift (ex: setting the button type to .system)?

I have this custom button class below:
import UIKit
class CustomButton: UIButton {
init(title: String) {
super.init(frame: .zero)
let height: CGFloat = 38
setTitleColor(UIColor.white, for: .normal)
setTitle(title, for: .normal)
setDimensions(height: height, width: 120)
layer.cornerRadius = height/7
backgroundColor = .red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And here is the code in my controller to create a button using this class:
private let loginButton: UIButton = {
let button = CustomButton(title: "Login")
button.isEnabled = false
button.addTarget(self, action: #selector(handleLogin), for: .touchUpInside)
return button
}()
I can't assign it a buttonType because buttonType is a get only property. Usually I just assign the buttonType by saying let button = UIButton(type: .system) but above, I'm creating the button by making it a button of the CustomButton class. How do I set a button type to this custom button class? Thanks.
Since the button type is a get-only property, the only way to set it is to call the init(type:) initialiser. To call it, you can do something like this:
class CustomButton: UIButton {
convenience init(title: String, type: UIButton.ButtonType = .system) {
self.init(type: type)
let height: CGFloat = 38
setTitleColor(UIColor.white, for: .normal)
setTitle(title, for: .normal)
setDimensions(height: height, width: 120)
layer.cornerRadius = height/7
backgroundColor = .red
self.buttonType
}
...
}
Notice that init(type:) is a convenience initialiser, so it must be delegated from a convenience initialiser. This means that you can't initialise any of your stored properties in your init(title:type:), but it doesn't seem like you have any anyway. Stored properties can only be initialised in their property initialisers inline. It is also arguable whether a button with customisable stored properties like that can still be called a "system" type button.
Another thing to note is that since the class only has one convenience initialiser, it inherits all the initialisers from its superclass, hence why it is self.init(type: type) and not super.init(type: type), and why there is no need to declare the required initialiser.

Changing isSecureTextEntry is being used on a wrong textField

I made a subclass of a UIView with a text field and a button which allows to configure if text field is secured via isSecureTextEntry property.
I am using two instances of that view one for setting a password and another one for confirming it in a view controller like this
let passwordTextField = PasswordTextFieldView(placeholder: "New password")
let confirmPasswordTextField = PasswordTextFieldView(placeholder: "Confirm password")
Text field subclass code
final class PasswordTextFieldView: UIView {
lazy var textField: UITextField = {
let textField = UITextField()
textField.textColor = .black
textField.placeholder = placeholder
textField.textAlignment = .left
textField.textContentType = .password
textField.autocorrectionType = .no
textField.isSecureTextEntry = true
return textField
}()
private let securedButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "eye.slash.fill")?.withRenderingMode(.alwaysTemplate), for: .normal)
button.tintColor = .gray
button.addTarget(self, action: #selector(securedButtonTapped), for: .touchUpInside)
return button
}()
private var isSecured: Bool = true
var placeholder: String
required init(placeholder: String) {
self.placeholder = placeholder
super.init(frame: CGRect.zero)
// Layout views
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func securedButtonTapped() {
isSecured.toggle()
securedButton.setImage(UIImage(systemName: isSecured ? "eye.slash.fill" : "eye.fill"), for: .normal)
textField.isSecureTextEntry = isSecured
}
}
So the problem is, that tapping a button changes isSecureTextEntry on a textField which is being edited. How to fix it?
When you create your let variable with a self calling block, you shouldn't use self as it's not determined at that moment. You can add print to see it's value, it's not your view, because the view is not yet created.
Sometimes it'll be correct value at the end(I tried running your code and it run fine on my simulator), and sometimes it's not.
You have two options in such cases:
Replace let with lazy var. In this case self if always gonna be determined
private lazy var securedButton: UIButton = {
...
}
Move adding target to your init. If you're using many inits don't forget to add to all of them, preferably moving to an other function. I prefer this way to make sure all my props are let if I don't need to change it. Like this:
required init(placeholder: String) {
self.placeholder = placeholder
super.init(frame: CGRect.zero)
initialize()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initialize()
// I know you're not using this initializer, just in case you'd need in future
}
private func initialize() {
securedButton.addTarget(self, action: #selector(securedButtonTapped), for: .touchUpInside)
}

UIButton property observers didset never called

I have a simple button in a cocoapods library, I need to change the tintcolor when it is isHighlighted however isHighlighted gets called but didset or willset never get called.
I am ofcourse setting my button to be highlighted, and I call this function after I dequeue my tableview cell
func setupCell() {
let myCustomButton = UIButton.button(title: "Button")
myCustomButton.isHighlighted = true
}
My UIButton Extension
extension UIButton {
public static func button(title: String) -> UIButton {
let button = UIButton(type: .custom)
button.setTitle(title, for: .normal)
button.setTitleColor(UIColor.gray, for: .normal)
return button
}
override open var isHighlighted: Bool {
didSet {
print("Never gets here, ever!")
}
}
}
How can I trigger didSet? Is there any other way apart from setting isHighlighted to true?

Disable editing of UITextField Without disabling subviews [duplicate]

This question already has answers here:
Easy way to disable a UITextField?
(5 answers)
Closed 5 years ago.
I have UITextField and I put a UIButton in the left view of it. I want to disable editing of UITextField while my UIButton response to on click action. I tried textField.isUserInteractionEnabled and also textField.isEnabled but both of them also disable my UIButton. is there any way to do this? my custom UITextField class is like this
import UIKit
#IBDesignable
class UITextFieldWithButton: UITextField, UITextFieldDelegate {
private var happyButton: UIButton = UIButton(type: .system)
#IBInspectable
var buttonText: String {
get {
let string = happyButton.titleLabel!.text!
let start = string.index(string.startIndex, offsetBy: 2)
let end = string.endIndex
return String(happyButton.titleLabel!.text![start..<end])
}
set {
happyButton.setTitle(" " + newValue, for: .normal)
happyButton.sizeToFit()
self.leftView = happyButton
self.leftViewMode = .always
}
}
var isButtonEnable: Bool {
get {
return self.isButtonEnable
}
set {
happyButton.isEnabled = newValue
}
}
var buttonDelegate: UITextFieldWithButtonDelegate?
required override init(frame: CGRect) {
super.init(frame: frame)
delegate = self
happyButton.addTarget(self,action: #selector(pressButton(_:)), for: .touchUpInside)
happyButton.titleLabel?.font = UIFont(name: (font?.fontName)!, size: (font?.pointSize)!)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
delegate = self
happyButton.addTarget(self,action: #selector(pressButton(_:)), for: .touchUpInside)
happyButton.titleLabel?.font = UIFont(name: (font?.fontName)!, size: (font?.pointSize)!)
}
#objc private func pressButton(_ sender: UIButton){
if let click = buttonDelegate {
click.clickOnUITextFieldButton(self)
}
}
}
protocol UITextFieldWithButtonDelegate {
func clickOnUITextFieldButton(_ sender: UITextFieldWithButton)
}
"I tried textField.isUserInteractionEnabled and also textField.isEnabled but both of them also disable my UIButton" :your code is working fine you might have added button behind uitextfield try to move it forward in view hierarchy

How to set addtarget to custom UIView

I have a custom UIView called Icons. Everything is working perfectly fine right now except one thing. The problem that i am having is being able to set userinteraction to be working or to either make the button i have in my class to be working. When I add a target to the button, i get an error that crashes everything and with the userinteraction to the view, i get no response.
class Icons: UIView {
let xUnit = UIScreen.mainScreen().bounds.width/20
let yUnit = UIScreen.mainScreen().bounds.height/30
var name: String = String()
var image: UIImage = UIImage()
let font = UIFont(name: "HelveticaNeue-Thin", size: 16.0)!
override init(frame: CGRect) {
super.init(frame: frame)
self.userInteractionEnabled = true
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addCustomView(name:String, image: UIImage ){
let label: UILabel = UILabel()
label.frame = CGRectMake(0, 1.5*xUnit, 3.4*xUnit, 2.7*xUnit)
label.textAlignment = NSTextAlignment.Center
label.font = font
label.text = name
self.addSubview(label)
let btn: UIButton = UIButton()
btn.frame=CGRectMake(0.3*xUnit, 0, 2.5*xUnit, 2.5*xUnit)
btn.setImage(image, forState: .Normal)
self.addSubview(btn)
}
}
And this is the class I'm trying to get connect it with which is in a completely different class
func tapSettings(tapGestureRecognizer: UITapGestureRecognizer){
let touchPoint = tapGestureRecognizer.locationInView(self.view)
if(CGRectContainsPoint(settings.frame, touchPoint)){
print("hi")
}
}
when I am trying to connect it to the button I say this:
btn.addTarget(self,action: #selector(HomeScreen.tapSettings))
and when I try and connect it to the view I say this :
let settingsTouch = UITapGestureRecognizer(target: self, action: #selector(HomeScreen.tapSettings))
settingsTouch.numberOfTapsRequired = 1
settingsTouch.numberOfTouchesRequired = 1
self.view.addGestureRecognizer(settingsTouch)
There are two problems in your code:
You are setting the target of the button to self when you should be setting it to an instance of HomeScreen, if that's where the method you want to call is declared
The argument a button action method receives is not UITapGestureRecognizer, but the clicked button instead, so you should replace your tapSettings method with something like this:
func tapSettings(button: UIButton){
try this
btn.addTarget(HomeScreen.self, action: #selector(HomeScreen.tapSettings(_:)), forControlEvents: UIControlEvents.TouchUpInside)
I hope this helps

Resources