UITextField Custom Clear Button - ios

I'm trying to implement custom clear button in text field using the solution on Custom Clear Button
It doesn't work, it shows default clear button. Any idea why? Following is my code:
class CustomTextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
let clearButton = UIButton(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
clearButton.setImage(UIImage(named: "Glyph/16x16/Clear")!, for: [])
self.rightView = clearButton
clearButton.addTarget(self, action: #selector(clearClicked), for: .touchUpInside)
self.clearButtonMode = .never
self.rightViewMode = .whileEditing
}
#objc override func clearClicked(sender:UIButton)
{
self.text = ""
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

As mentioned already, in your code the clearClicked method should not override as UITextField doesn't have a clearClicked method to override.
Anyway, I updated the code to work when using it with storyboards. Added the awakeFromNib method which calls the initialisation code.
class CustomTextField: UITextField {
override open func awakeFromNib() {
super.awakeFromNib()
self.initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
func initialize() {
let clearButton = UIButton(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
clearButton.setImage(UIImage(named: "Glyph/16x16/Clear")!, for: [])
self.rightView = clearButton
clearButton.addTarget(self, action: #selector(clearClicked), for: .touchUpInside)
self.clearButtonMode = .never
self.rightViewMode = .whileEditing
}
#objc func clearClicked(sender:UIButton)
{
self.text = ""
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

Related

How to make UIButton clickable outside frame of it's grand parent view

I have a series of buttons that exist outside the frame of its grand parent view. These buttons are not able to be clicked now, and I am not able to move the buttons, so how can I make them clickable outside the frame of the grand parent view? The button exists inside the a small circular UIView, and that UIView lives outside of the frame of it's parent view. I've tried using a hit test but it is still not triggering
Here is my button:
class ButtonNode: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(frame: CGRect, agree: Bool) {
self.init(frame: frame)
setupView()
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
func setupView(){
self.setTitle("+10", for: .normal)
self.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
self.setTitleColor(.lightGray, for: .normal)
self.isUserInteractionEnabled = true
self.layer.borderWidth = 1.0
self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.cornerRadius = 15
}
}
This is the setup in the parent view:
var rightLastNodeButton:UIButton?
var leftLastNodeButton:UIButton?
leftButton = ButtonNode(frame: CGRect(x: leftX, y: y, width: width, height: height), agree: false)
rightButton = ButtonNode(frame: CGRect(x: rightX, y: y, width: width, height: height), agree: false)
leftButton?.addTarget(self, action: #selector(disagreeUsers), for: .touchUpInside)
rightButton?.addTarget(self, action: #selector(agreeUsers), for: .touchUpInside)
view.addSubview(leftButton!)
view.addSubview(rightButton!)
#objc func agreeUsers(){
delegate?.showParticipantsPressed(agree: true)
}
#objc func disagreeUsers(){
delegate?.showParticipantsPressed(agree: false)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if(rightButton!.frame.contains(point)) {
return rightButton
}
if(leftButton!.frame.contains(point)){
return leftButton
}
return super.hitTest(point, with: event)
}
I had a similar issue and what worked for me was bringing the buttons to the front of the subview. Then it would work.
view.bringSubiewToFront(exampleButton)
or sending the other subviews back
view.sendSubViewToBack(viewlayer)

Button and Image Alignment issues in UIButton

So I have a UIBarbuttonItem that I am currently designing based off of a layout that I have done.
import Foundation
import UIKit
class LocationManager: UIBarButtonItem {
var viewController: MainViewController?
lazy var customButton : UIButton = {
let customButton = UIButton(type: .system)
customButton.setImage(UIImage(named: "downArrow"), for: .normal)
customButton.imageEdgeInsets = UIEdgeInsetsMake(0, 20, 0, -10)
guard let customFont = UIFont(name: "NoirPro-SemiBold", size: 20) else {
fatalError("""
Failed to load the "CustomFont-Light" font.
Make sure the font file is included in the project and the font name is spelled correctly.
"""
)
}
customButton.semanticContentAttribute = UIApplication.shared
.userInterfaceLayoutDirection == .rightToLeft ? .forceLeftToRight : .forceRightToLeft
customButton.titleLabel?.font = customFont
customButton.setTitleColor(UIColor.black, for: .normal)
return customButton
}()
override init() {
super.init()
setupViews()
}
#objc func setupViews(){
customView = customButton
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I correctly do the job of using both an image and title and setting the image insets for the button and on load the appearance is great. However, when I leave the screen and come back it seems as though everything is thrown out of wack the image gets moved back and sometimes there will be two images and one will have a distorted size.
Is there anything wrong with my custom button implementation that I am missing.
I have included images for before and after
I suggest you to make your custom button class, then make title and image by adding subviews. In this case UIImageView and UILabel. Because UIButton inherits from UIView you can easy do this. I've never had problems using this way.
Here is the code I've written for you:
import UIKit
class ViewController: UIViewController {
lazy var customButton: CustomButton = {
let button = CustomButton(frame: CGRect(x: 50,
y: 200,
width: view.frame.width - 100,
height: 50))
// This mask for rotation
button.autoresizingMask = [.flexibleLeftMargin,
.flexibleRightMargin,
.flexibleTopMargin,
.flexibleBottomMargin]
button.attrTitleLabel.text = "San Francisco, CA"
button.addTarget(self, action: #selector(chooseCity), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
view.addSubview(customButton)
}
#objc func chooseCity() {
print("Choose city button has pressed")
}
}
class CustomButton: UIButton {
private let arrowImageSize: CGSize = CGSize(width: 20, height: 20)
private let sideOffset: CGFloat = 10
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
addSubview(attrTitleLabel)
addSubview(arrowImageView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var attrTitleLabel: UILabel = {
let label = UILabel()
label.font = UIFont(name: "NoirPro-SemiBold", size: 20)
label.textColor = .black
return label
}()
lazy var arrowImageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "arrow_down")
iv.contentMode = .scaleAspectFit
return iv
}()
override func layoutSubviews() {
super.layoutSubviews()
arrowImageView.frame = CGRect(x: self.frame.width - arrowImageSize.width - sideOffset,
y: self.frame.height/2 - arrowImageSize.height/2,
width: arrowImageSize.width,
height: arrowImageSize.height)
attrTitleLabel.frame = CGRect(x: sideOffset, y: 0, width: self.frame.width - sideOffset*2 - arrowImageSize.width, height: self.frame.height)
}
}
How it looks:

Custom UITextField class only applied to first text field

I have made my first custom class in Swift and I want to use it for the text fields in my app. The problem is that it only works for the first textfield in the view controller, the class is not applied to the second one (the second one has no rounded corners, default placeholder color etc.). I have double checked that the right "custom class" is set in the storyboard for both textfields. Why isn't the class applied to both fields? Seems like a thing with a simple solution but I haven't found any...
Here is the class:
import UIKit
class RoundedUITextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
self.setAttributes()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setAttributes() {
self.layer.cornerRadius = 20.0
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: self.frame.height))
self.leftView = paddingView
self.leftViewMode = UITextFieldViewMode.always
self.textColor = UIColor.white
self.alpha = 0.7
let str = NSAttributedString(string: (self.placeholder)!, attributes: [NSForegroundColorAttributeName:UIColor.white])
self.attributedPlaceholder = str
}
}
Here is the view controller code:
class LogInViewController: UIViewController {
#IBOutlet weak var password: RoundedUITextField!
#IBOutlet weak var userName: RoundedUITextField!
override func viewDidLoad() {
super.viewDidLoad()
userName.setAttributes()
// Do any additional setup after loading the view.
}
I discovered now that the problem lies in the "setAttributes", I thought the attributes where set in the class but they're actually only set in viewDidLoad for one of them (I put it there in the beginning and forgot to remove it)... I want this to be done in the "init" of the class for all of the fields...
Put your self.setAttributes() in your awakeFromNib method and remove it from init(frame: CGRect)
override func awakeFromNib() {
super.awakeFromNib()
self.setAttributes()
}
your custom class code will be like this
import UIKit
class RoundedUITextField: UITextField {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.setAttributes()
}
func setAttributes() {
self.layer.cornerRadius = 20.0
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: self.frame.height))
self.leftView = paddingView
self.leftViewMode = UITextFieldViewMode.always
self.textColor = UIColor.white
self.alpha = 0.7
let str = NSAttributedString(string: (self.placeholder)!, attributes: [NSForegroundColorAttributeName:UIColor.white])
self.attributedPlaceholder = str
}
}
Hope this helps

How to add UIButton action in closure in custom UIToolbar class initialization?

I have a custom UIToolbar class with two UIBarButtonItem. I know I can create custom delegate action for UIBarButtonItem item. But how can I use closure as UIBarButtonItem action in custom UIToolbar class initialization?
class KeyboardToolBar: UIToolbar
{
let done = UIButton.init()
init() {
super.init(frame: .zero)
self.backgroundColor = UIColor.gray
self.sizeToFit()
let flexBarBtn = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
done.frame = CGRect(x: 0, y: 0, width: 50, height: 44)
done.setTitle("Done", for: .normal)
done.setTitleColor(.black, for: .normal)
let doneBarBtn:UIBarButtonItem! = UIBarButtonItem.init(customView: done)
self.items = [flexBarBtn,doneBarBtn]
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In ViewController
super.viewDidLoad()
txt.keyboardType = .numberPad
txt.inputAccessoryView = KeyboardToolBar()
// How can I use some thing like this
// txt.inputAccessoryView = KeyboardToolBar(doneBtnAction: {
// print("done button pressed")
// })
}
Here you go ...
class KeyboardToolBar: UIToolbar {
let done = UIButton.init()
var doneBtnAction:((Void) -> Void)?
convenience init(_ doneBtnAction: #escaping (Void) -> Void) {
self.init()
self.doneBtnAction = doneBtnAction
}
private init() {
super.init(frame: .zero)
self.backgroundColor = UIColor.gray
self.sizeToFit()
let flexBarBtn = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
done.frame = CGRect(x: 0, y: 0, width: 50, height: 44)
done.setTitle("Done", for: .normal)
done.setTitleColor(.black, for: .normal)
done.addTarget(self, action: #selector(callbackDoneButton(_:)), for: .touchUpInside)
let doneBarBtn:UIBarButtonItem! = UIBarButtonItem.init(customView: done)
self.items = [flexBarBtn,doneBarBtn]
}
func callbackDoneButton(_ id:Any) -> Void {
if self.doneBtnAction != nil {
self.doneBtnAction!()
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In ViewController
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
txt.keyboardType = .numberPad
txt.inputAccessoryView = KeyboardToolBar.init( {
(Void) -> Void in
print("done");
})
}

Custom Clear Button

I want to create custom clear button on UITextField, that is to use rightView and put image there, the problem is attaching the original clear button event to that custom rightView.
In Objective-C i can do that this way:
SEL clearButtonSelector = NSSelectorFromString(#"clearButton");
// Reference clearButton getter
IMP clearButtonImplementation = [self methodForSelector:clearButtonSelector];
// Create function pointer that returns UIButton from implementation of method that contains clearButtonSelector
UIButton * (* clearButtonFunctionPointer)(id, SEL) = (void *)clearButtonImplementation;
// Set clearTextFieldButton reference to “clearButton” from clearButtonSelector
UIButton *_clearTextFieldButton = clearButtonFunctionPointer(self, clearButtonSelector);
[_clearTextFieldButton setImage:[UIImage imageNamed:#"icon_remove"] forState:UIControlStateNormal];
self.hasClearButtonAsRightView = YES;
now how to convert this to Swift?
or any ideas to workaround it?
You can add a custom button as right view of the UITextField like this
class CustomTextField : UITextField
{
override init(frame: CGRect) {
super.init(frame: frame)
let clearButton = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 15, height: 15))
clearButton.setImage(UIImage(named: "clear.png")!, forState: UIControlState.Normal)
self.rightView = clearButton
clearButton.addTarget(self, action: "clearClicked:", forControlEvents: .touchUpInside)
self.clearButtonMode = .never
self.rightViewMode = .always
}
func clearClicked(sender: UIButton)
{
self.text = ""
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Implementing a custom text field as suggested in the other answers is not a good idea. You should try to use extensions rather than inheritance if at all possible, because with inheritance you are much more likely to need to make major changes to your codebase in response to changes, whereas using extensions you are much more flexible to change.
I strongly suggest that instead of implementing a custom text field, you extend the UITextField class like this:
extension UITextField {
func applyCustomClearButton() {
clearButtonMode = .Never
rightViewMode = .WhileEditing
let clearButton = UIButton(frame: CGRectMake(0, 0, 16, 16))
clearButton.setImage(UIImage(name: "iCFieldClear")!, forState: .Normal)
clearButton.addTarget(self, action: "clearClicked:", forControlEvents: .TouchUpInside)
rightView = clearButton
}
func clearClicked(sender:UIButton) {
text = ""
}
}
Then to use it you just do this:
yourTextField.applyCustomClearButton()
Here is my solution in Swift 3. In addition to the already existing answer, I also made sure that both left and right views of the textfield (i.e. the search magnifier image view and the custom clear button) have a padding to their left/right by overriding leftViewRect() and rightViewRect(). Otherwise, they will stick right on the edges of the textfield.
class CustomTextField: UITextField
{
fileprivate let searchImageLength: CGFloat = 22
fileprivate let cancelButtonLength: CGFloat = 15
fileprivate let padding: CGFloat = 8
override init( frame: CGRect )
{
super.init( frame: frame )
self.customLayout()
}
required init?( coder aDecoder: NSCoder )
{
super.init( coder: aDecoder )
self.customLayout()
}
override func leftViewRect( forBounds bounds: CGRect ) -> CGRect
{
let x = self.padding
let y = ( bounds.size.height - self.searchImageLength ) / 2
let rightBounds = CGRect( x: x, y: y, width: self.searchImageLength, height: self.searchImageLength )
return rightBounds
}
override func rightViewRect( forBounds bounds: CGRect ) -> CGRect
{
let x = bounds.size.width - self.cancelButtonLength - self.padding
let y = ( bounds.size.height - self.cancelButtonLength ) / 2
let rightBounds = CGRect( x: x, y: y, width: self.cancelButtonLength, height: self.cancelButtonLength )
return rightBounds
}
fileprivate func customLayout()
{
// Add search icon on left side
let searchImageView = UIImageView()
searchImageView.contentMode = .scaleAspectFit
let searchIcon = UIImage( named: "search_magnifier" )
searchImageView.image = searchIcon
self.leftView = searchImageView
self.leftViewMode = .always
// Set custom clear button on right side
let clearButton = UIButton()
clearButton.setImage( UIImage( named: "search_cancel" ), for: .normal )
clearButton.contentMode = .scaleAspectFit
clearButton.addTarget( self, action: #selector( self.clearClicked ), for: .touchUpInside )
self.rightView = clearButton
self.clearButtonMode = .never
self.rightViewMode = .whileEditing
}
#objc fileprivate func clearClicked( sender: UIButton )
{
self.text = ""
}
}
with iOS 14, none of the solution were working for me. the clear button was getting wrong offset for different device sizes.
I had the image. if you dont have it, you can download it from SF Symbols. the name is xmark.circle.fill
In the end, I used this
let customClearButton = UIButton.appearance(whenContainedInInstancesOf: [UITextField.self])
customClearButton.setImage(UIImage(named: "icon-x"), for: .normal)
Updated to Swift 5, based on #marmoy answer:
public func addClearAllCustomButton() {
clearButtonMode = .never
rightViewMode = .whileEditing
let clearButton = UIButton(frame: rightViewRect(forBounds: bounds))
clearButton.setImage(UIImage(named: "clearAll"), for: .normal)
clearButton.addTarget(self, action: #selector(didTouchClearAllButton(sender:)), for: .touchUpInside)
rightView = clearButton
}
public func removeClearAllButton() {
rightViewMode = .never
}
#objc func didTouchClearAllButton(sender: UIButton) {
text = ""
}
For rigth padding & listen the clear delegate of textfield
class SearchBoxTextField: UITextField {
override open func awakeFromNib() {
super.awakeFromNib()
self.initialize()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func initialize() {
let clearButton = UIButton(frame: CGRect(x: 0, y: 0, width: 12, height: 12))
clearButton.setImage(UIImage(named: "removeIcon")!, for: .normal)
let clearView = UIView(frame: CGRect(x: 0, y: 0, width: 22, height: 12))
clearView.addSubview(clearButton)
self.rightView = clearView
clearButton.addTarget(self, action: #selector(clearClicked), for: .touchUpInside)
self.clearButtonMode = .never
self.rightViewMode = .whileEditing
}
#objc func clearClicked(sender:UIButton) {
self.text = ""
_ = self.delegate?.textFieldShouldClear?(self)
}
}

Resources