Swift: Custom UIButton Class in View Controller Touch Event Not Trigger? - ios

I created a custom UIButton class as so:
class CustomButton: UIButton
{
required init(frame: CGRect, title: String, alignment: NSTextAlignment)
{
super.init(frame: frame)
// Set properties
// self.addTarget(self,
// action: #selector(touchCancel),
// for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
// #objc fileprivate func touchCancel()
// {
// print("TOUCHED")
// }
}
In my main UIViewController, I have the following implementation:
class MainViewController: UIViewController
{
fileprivate var customBtn: CustomButton {
let frame = CGRect(x: 48.0,
y: 177.0,
width: 80.0,
height: 40.0)
let custom = CustomButton(frame: frame,
title: "Test",
alignment: NSTextAlignment.right)
return custom
}
override func viewDidLoad()
{
super.viewDidLoad()
view.addSubView(customBtn)
customBtn.addTarget(self,
action: #selector(touchCancel),
for: .touchUpInside)
}
#objc public func touchCancel()
{
print("TOUCHED")
}
}
However, adding a target to customBtn in my main UIViewController does not get triggered. As shown in the CustomButton class with the commented out code, I can add a target there, which does get triggered.
I'm just wondering why a defined function in another class cannot be used to add as a target to a custom UIButton?....or maybe my implementation is incorrect?
Thanks!

You may need a closure not a computed property
lazy var customBtn: CustomButton = {
let frame = CGRect(x: 48.0, y: 177.0, width: 80.0, height: 40.0)
let custom = CustomButton(frame: frame, title: "Test",alignment: NSTextAlignment.right)
return custom
}()
Here inside MainViewController
customBtn.addTarget(self,
action: #selector(touchCancel),
for: .touchUpInside)
you add the target to a newly created instance not to the one you added as a subview and it's the main difference between your implementation ( computed property ) and closure

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)

Swift: Target not getting called on a subclassed UIButton

CustomButton.swift
class CustomButton: UIButton {
override func draw(_ rect: CGRect) {
//drawing code
}
}
ViewController.swift
let testCustom = CustomButton()
testCustom.draw(CGRect(x: 0, y: 0, width: 0, height: 0))
testCustom.isUserInteractionEnabled = true
testCustom.addTarget(self, action: #selector(Start(_:)), for: .touchUpInside)
self.view.addSubview(testCustom)
#objc func Start(_ sender: CustomButton) {
print("pressed start")
}
The button appears on screen but the function does not get called upon pressing the button. Any ideas why?
I also tried the function and addTarget code within the CustomButton.swift but couldn't get that to trigger either.
Thanks for any help!
The following is a simple example of how to instantiate a UIButton subclass in a view controller (UIViewController). It's tested under Swift 4.2.
// Subclassing UIButton //
import UIKit
class MyButton: UIButton {
var tintColor0: UIColor!
var tintColor1: UIColor!
var borderColor: UIColor!
var backColor: UIColor!
var cornerRadius: CGFloat!
required init(frame: CGRect, tintColor0: UIColor, tintColor1: UIColor, borderColor: UIColor, backColor: UIColor, cornerRadius: CGFloat, titleString: String) {
super.init(frame: frame)
self.tintColor0 = tintColor0
self.tintColor1 = tintColor1
self.borderColor = borderColor
self.backColor = backColor
self.cornerRadius = cornerRadius
self.setTitle(titleString, for: .normal)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
self.setTitleColor(tintColor0, for: .normal)
self.setTitleColor(tintColor1, for: .highlighted)
self.layer.borderColor = borderColor.cgColor
self.layer.cornerRadius = cornerRadius
self.layer.borderWidth = 1.0
self.layer.backgroundColor = backColor.cgColor
}
}
// View controller //
import UIKit
class ViewController: UIViewController {
// MARK: - Variables
// MARK: - IBOutlet
// MARK: - IBAction
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
let buttonRect = CGRect(x: 20.0, y: 160.0, width: 100.0, height: 32.0)
let myButton = MyButton(frame: buttonRect, tintColor0: UIColor.black, tintColor1: UIColor.gray, borderColor: UIColor.orange, backColor: UIColor.white, cornerRadius: 8.0, titleString: "Hello")
myButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
view.addSubview(myButton)
}
#objc func buttonTapped(_ sender: UIButton) {
print("Hello!?")
}
}
#MuhammadWaqasBhati made a good point asking about the frame.
I was using addSublayer to draw the path I had created onto the screen. My mistake here was that I was setting values in the draw() function and adding the CAShapeLayer with addSublayer, but a frame for the button wasn't being set.
Even though the layer that is drawn is a sublayer of the button, it appears at the coordinates and dimensions provided for the layer, without any relation to the frame of its "parent" button.
The frame of the button could be (0, 0, 0, 0) or (0, 0, 100, 100) and the image drawn in addSublayer could still be at (250, 200, 75, 80) so that the visible image would be in one spot of the screen, but the actual button is in an unrelated spot to what is visible in its sublayer.

UITextField Custom Clear Button

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)
}
}

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:

IBDesignable Subclass of TextField renders to IB but not to simulator / device

I created a subclass of UITextField that adds a floating button to the right hand side of the text-field. It's all good in the IB, but for some reason the button image does not show up in the simulator or on the device.
Using the view debugger, I can see the button is there, right where it is supposed to be. I can even tap it and get the target action to trigger. However, the image is not visible on the screen.
I tried messing with the clipsToBounds properties of both the TextView and the button to no avail.
I also know the image asset is working, because I made an an image view and set the image to the same image asset, and it appears just fine. The class for the TextField is below:
#IBDesignable
class ButtonedTextField: UITextField {
#IBInspectable var buttonImage: UIImage?
var button: UIButton?
var buttonAction: (() -> ())?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
configure()
}
#objc private func buttonActionWrap() {
buttonAction?()
}
private func configure() {
let button = UIButton(type: .custom)
button.addTarget(self, action: #selector(buttonActionWrap), for: .touchUpInside)
let height = self.frame.height
let buttonHeight = height - 10
button.frame = CGRect(x: self.frame.width - (buttonHeight + 10), y: 5, width: buttonHeight, height: buttonHeight)
button.setImage(buttonImage, for: .normal)
self.button = button
self.addSubview(button)
bringSubview(toFront: button)
}
}
Any thoughts?

Resources