Action not being triggered for UIBarButtonItem - ios

first post so apologies if I mess something up. I have researched this for hours upon hours and read other posts here on stack exchange to no avail.
I have created a nib file that defines a custom view and have defined a custom class (UIView) to manage the outlets of the custom view. As you can see from the code below excerpted from my custom UIView class associated with the nib, I have a date picker as the input view for the custom class and a UIToolBar with two UIBarButtonItems. Both of these appear as desired through a tap gesture recognizer... however the problem is the UIBarButtonItems do not call the action when tapped. Placing a breakpoint in the action function reveals that the code is never run. I feel that something with the view lifecycle is preventing a reference from being made, but I am new to Swift so some help here would be appreciated. I don't think it is selector syntax as the tap gesture recognizer works as desired. I've tried messing with button click handling access levels. I've tried doing input view setup when the view awakes from the nib as well, along with trying to put the code in different parts of the lifecycle.
If it matters for lifecycle's sake, this nib is a part of a table view cell. I call for this nib to be loaded when the table view cell awakes from it's nib.
Thanks!
#IBOutlet weak var timerStackView: UIStackView!{
didSet{
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(HandleTap(_:)))
timerStackView.addGestureRecognizer(tapGesture)
}
}
var datePicker: UIDatePicker {
let picker = UIDatePicker()
picker.backgroundColor = UIColor.black
picker.isOpaque = false
picker.setValue(UIColor.white, forKey: "textColor")
return picker
}
var datePickerAccessoryView: UIToolbar {
let accessoryView = UIToolbar()
let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.done, target: self, action: #selector(handleDatePickerButtonClick(_:)))
doneButton.tintColor = UIColor.white
let cancelButton = UIBarButtonItem(title: "Cancel", style: UIBarButtonItemStyle.plain, target: self, action: #selector(handleDatePickerButtonClick(_:)))
cancelButton.tintColor = UIColor.white
accessoryView.setItems([cancelButton, doneButton], animated: true)
return accessoryView
}
override var inputView: UIView? {return datePicker}
override var inputAccessoryView: UIView? {return datePickerAccessoryView}
override var canBecomeFirstResponder: Bool {return true}
override var canResignFirstResponder: Bool {return true}
// MARK: - Private functions
#objc fileprivate func HandleTap(_ sender: UITapGestureRecognizer) -> Void {
if !self.isFirstResponder {
switch sender.state {
case .ended:
datePicker.date = Date()
self.becomeFirstResponder()
default:
break
}
}
}
#objc #IBAction internal func handleDatePickerButtonClick(_ sender: UIBarButtonItem) -> Void {
switch sender.title! {
case "Done":
// To be implemented
case "Cancel":
// To be implemented
default:
break
}
}

You are initialising the UIToolbar without a frame and that would make it not register any touch events because they would be out of the toolbar's bounds.
Replace let accessoryView = UIToolbar() with something like let accessoryView = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 44))
Or you can call accessoryView.sizeToFit() before return accessoryView

I suppose the tap gesture recognizer is interfering with native UIBarButtonItem click events. But why do you use a gesture recognizer for that?
You should better add an action to each particular UIBarButtonItem.

Related

Swift UIGestureRecognizer is not working / is not doing anything

I have a small issue that I couldn't fix for a few hours now! I have a simple ViewController with a label, which is linked to my ViewController class. I also set up an UIGestureRecognizer to change the text of the label when the label is clicked. But weirdly nothing happens at all.
The text of the label changes from whatever is set in the Storyboard to "Hello", so the setup is correct. But when I click the label, nothing happens.
Here's the entire ViewController class:
import UIKit
class PremiumViewController: UIViewController {
// Views
#IBOutlet weak var premiumLabel: UILabel!
// View Did Load
override func viewDidLoad() {
super.viewDidLoad()
premiumLabel.text = "Hello"
// Tap listener
let tap = UIGestureRecognizer(target: self, action: #selector(PremiumViewController.clicked))
premiumLabel.isUserInteractionEnabled = true
premiumLabel.addGestureRecognizer(tap)
}
#objc func clicked() {
premiumLabel.text = "You clicked me"
}
}
Use UITapGestureRecognizer instead of UIGestureRecognizer
change from
let tap = UIGestureRecognizer(target: self, action: #selector(PremiumViewController.clicked))
to
let tap = UITapGestureRecognizer(target: self, action: #selector(PremiumViewController.clicked))

How to detect longpress in BarButtonitem

I have a UIBarButtonItem in navigation bar. When a user clicks it, it pops to another viewController.
Now i want that when user long-press on that button (navigation bar button) I want to show a help message.
I want help to detect the onlick event and longpress event separately.
You should create a button and set UITapGestureRecognizer & UILongPressGestureRecognizer to your button
// Create a button
let yourButton = UIButton()
yourButton.backgroundColor = .red
yourButton.setTitle("long press", for: .normal)
// Create a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap))
// Create a long gesture recognizer
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(long))
// You can set minimum duration of the press action
longGesture.minimumPressDuration = 3 //The default duration is 0.5 seconds.
// Add your gestures to button
yourButton.addGestureRecognizer(longGesture)
yourButton.addGestureRecognizer(tapGesture)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: yourButton)
#objc private func didTap() {
print("Did Tap")
}
#objc private func long() {
// You can show the help message in here
print("Long press")
}
try this in view didload:
let back = UIImage(named: "header_backarrow")
let backView = UIImageView(image: back)
backView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissManual))
backView.addGestureRecognizer(tap)
let backItem = UIBarButtonItem(customView: backView)
navigationItem.leftBarButtonItem = backItem
In the viewDidAppear of your view controller you can add this :
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(myCalledFunction))
(myUIBarButton.value(forKey: "view") as? UIView)?.addGestureRecognizer(gestureRecognizer)
This is difficult because UIBarButton doesn't really expose its view and so you cannot directly add a gestureRecognizer to it.
You can however get a reference to its view by using the value(forKey:) method and then play with it.
Do not do this in the viewDidLoad as it is necessary for the view to have already been created in order for this to work.
All of the other answers require implementing a UIBarButtonItem(customView:) to achieve this. However this can be implemented with any UIBarButtonItem instance without implementing your own gesture recognizer code.
An #IBAction can actually be passed a second parameter containing the UIEvent triggering the action. For example, instead of defining -
#objc func doSomething(sender: UIBarButtonItem) {
}
We can define -
#objc func doSomething(sender: UIBarButtonItem, forEvent event: UIEvent) {
guard let touch = event.allTouches?.first else { return }
if touch.tapCount == 1 {
// Handle tap
} else if touch.tapCount == 0 {
// Handle long press
}
}
Source : http://li366-68.members.linode.com/2016/09/07/detecting-long-presses-on-uibarbuttonitems.html
so I found that UIBarButton has not property like longpress so all I do is take a UIButton give it longpress gesture and add that UIButton in navigation bar as UIBarButtonItem.
I hope it will helpful for someone else who is facing same problem.
let btn = UIButton(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
btn.backgroundColor = .green
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(longpress))
btn.addGestureRecognizer(gesture)
let barbtn = UIBarButtonItem(customView: btn)
self.navigationItem.rightBarButtonItem = barbtn
thank you :)

UIBarButtonItem position different when placed via storyboard vs. programmatically

In my app, I have a toolbar with UIBarButtonItems.
In most circumstances, the UIBarButtonItems are set via storyboard, and look as follows:
In a special case, I have to replace one UIBarButtonItem programmatically. This is done with the following code:
let rotatingButton = UIButton(type: .custom)
rotatingButton.setImage(UIImage(named: "LocalizationInUseNoFix"), for: .normal)
rotatingButton.addTarget(self, action: #selector(localizationButtonTapped), for: .touchUpInside)
rotatingButton.rotateStart()
let barButtonItem = UIBarButtonItem(customView: rotatingButton)
leftBarButtonItems![2] = barButtonItem
When the rotatingButton is displayed in the toolbar, it placed at a different position. It is shifted to the right, as you can see here:
How can I achieve to place both UIBarButtonItems at the same position?
EDIT:
By now I realized that the horizontal shift of the programmatically created UIBarButtonItem is not always the same, without any changes to the code: Sometimes it is shifted left, and not right:
EDIT 2:
I found a workaround:
If I set a width constrain to my button like
rotatingButton.widthAnchor.constraint(equalToConstant: 40).isActive = true
then the button is apparently always correctly placed. But I hate to hard-code constraints like this.
Is there a more elegant way to do it?
Try the below steps to perform your task:
Store left bar button items into an NSMutableArray
Replace desired UIBarbuttonItem
Set leftbarbuttonitems to this new array
Hope this steps will work
When you set the image on UIBarButton programmatically, the contentmode of the leftBarButtonItems becomes 'left' and rightBarButtonItems become 'right'. But from storyboard, it is centered. Set the image and adjust the contentMode as required.
All are working fine for Navigationbar and Toolbar
class ViewController: UIViewController {
#IBOutlet weak var toolbar: UIToolbar!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func leftAction(_ sender: Any) {
}
#IBAction func rightAction(_ sender: Any) {
}
#IBAction func changeLeftItems(_ sender: Any) {
if let items = self.navigationItem.leftBarButtonItems {
var addItems = [UIBarButtonItem]()
addItems.append(contentsOf: items)
let barItem = UIBarButtonItem(title: "3", style: UIBarButtonItemStyle.plain, target: self, action: #selector(ViewController.leftAction(_:)))
addItems.append(barItem)
self.navigationItem.leftBarButtonItems = addItems
}
if let items = self.toolbar.items {
var addItems = [UIBarButtonItem]()
addItems.append(contentsOf: items)
let barItem = UIBarButtonItem(title: "L3", style: UIBarButtonItemStyle.plain, target: self, action: #selector(ViewController.leftAction(_:)))
addItems.insert(barItem, at: 2)
self.toolbar.setItems(addItems, animated: true)
}
}
}
This is the best solution I found so far:
Get the width of a view of another bar button item using key value coding. This is from Jeremy W. Sherman’s answer here.
Please note that it does not use any private API, see the discussion there. The worst thing that can happen is that the view property of the UIBarButtonItem cannot be accessed. In this case, I use a default value:
var leftBarButtonItems = self.navigationItem.leftBarButtonItems
let rotatingButton = UIButton(type: .custom)
rotatingButton.setImage(UIImage(named: "LocalizationInUseNoFix"), for: .normal)
rotatingButton.addTarget(self, action: #selector(localizationButtonTapped), for: .touchUpInside)
rotatingButton.rotateStart()
// Get the width of the bar button items if possible, else set default
let leftmostBarButtonItem = leftBarButtonItems![0]
let barButtonItemWidth: CGFloat
if let leftmostBarButtonItemView = leftmostBarButtonItem.value(forKey: "view") as? UIView {
barButtonItemWidth = leftmostBarButtonItemView.frame.size.width
} else {
barButtonItemWidth = 40.0
}
rotatingButton.widthAnchor.constraint(equalToConstant: barButtonItemWidth).isActive = true
let barButtonItem = UIBarButtonItem(customView: rotatingButton)
leftBarButtonItems![2] = barButtonItem
self.navigationItem.leftBarButtonItems = leftBarButtonItems
This is working fine for me. Best way is identify item to replace and change the content
#IBAction func changeLeftItems(_ sender: Any) {
if let items = self.toolbar.items {
var addItems = [UIBarButtonItem]()
addItems.append(contentsOf: items)
let barItem = UIBarButtonItem(title: "L5", style: UIBarButtonItemStyle.plain, target: self, action: #selector(ViewController.leftAction(_:)))
addItems.remove(at: 1)
addItems.insert(barItem, at: 1)
self.toolbar.setItems(addItems, animated: true)
}
}

How to add buttons above keyboard

How to add button above the keyboard like this one in Stack Exchange app? And when you long press the text in UITextView How to add "Select" and "Select All"?
The first question, you can set textField's inputAccessoryView to your custom view, this can customize the keyboard's header.
The result:
You can do it like below;
first, you should instance the view you want to add above the keyboard.
class ViewController : UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.inputAccessoryView = Bundle.main.loadNibNamed("CustomAccessoryView", owner: self, options: nil)?.first as! UIView?
In your CustomAccessoryView, you can set the action of the button:
import UIKit
class CustomAccessoryView: UIView {
#IBAction func clickLoveButton(_ sender: UIButton) {
print("Love button clicked")
}
}
I would recommend to create a toolbar for your UITextField's accessoryView property.
The idea is to add this toolbar once, before the textfield would show for the first time. Therefore, we assign the delegate to self, and override the textFieldShouldBeginEditing delegate call with our implementation to add the accessoryView.
Here is a simple example, how can u achieve it:
import UIKit
class ViewController: UIViewController {
// your `UITextfield` instance
// Don't forget to attach it from the IB or create it programmaticly
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Assign the delegate to self
textField.delegate = self
}
}
// MARK: Create extension to conform to UITextfieldDelegate
extension ViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
setupTextFieldsAccessoryView()
return true
}
func setupTextFieldsAccessoryView() {
guard textField.inputAccessoryView == nil else {
print("textfields accessory view already set up")
return
}
// Create toolBar
let toolBar: UIToolbar = UIToolbar(frame:CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 44))
toolBar.barStyle = UIBarStyle.black
toolBar.isTranslucent = false
// Add buttons as `UIBarButtonItem` to toolbar
// First add some space to the left hand side, so your button is not on the edge of the screen
let flexsibleSpace: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil) // flexible space to add left end side
// Create your first visible button
let doneButton: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(didPressDoneButton))
// Note, that we declared the `didPressDoneButton` to be called, when Done button has been pressed
toolBar.items = [flexsibleSpace, doneButton]
// Assing toolbar as inputAccessoryView
textField.inputAccessoryView = toolBar
}
func didPressDoneButton(button: UIButton) {
// Button has been pressed
// Process the containment of the textfield or whatever
// Hide keyboard
textField.resignFirstResponder()
}
}
This should be your output:
You'll have to use the inputAccessoryView of your textfield.
you can put the code snippet below in your viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 60))
button.backgroundColor = UIColor.blue
button.setTitle("NEXT", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.addTarget(self, action: #selector(self. yourButton), for: .touchUpInside)
numtextField.inputAccessoryView = button
}
#objc func nextButton()
{
print("do something")
}
Just copy and paste simple code for you accessory button embedded with keypad
func addKeyboardToolbar() {
let ViewForDoneButtonOnKeyboard = UIToolbar()
ViewForDoneButtonOnKeyboard.sizeToFit()
let button = UIButton.init(type: .custom)
button.setImage(UIImage.init(named: "login-logo"), for: UIControlState.normal)
button.addTarget(self, action:#selector(doneBtnfromKeyboardClicked), for:.touchUpInside)
button.frame = CGRect.init(x: 0, y: 0, width:UIScreen.main.bounds.width, height: 30) //CGRectMake(0, 0, 30, 30)
let barButton = UIBarButtonItem.init(customView: button)
ViewForDoneButtonOnKeyboard.items = [barButton]
postTextView.inputAccessoryView = ViewForDoneButtonOnKeyboard
}
func doneBtnfromKeyboardClicked (){
self.contentView.endEditing(true)
}
to add a toolbar with a done button which dismisses the keyboard above a UITextField you can write a UITextField extension with the following function:
public func addAccessoryView() {
let doneButton = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self, action: "resignFirstResponder")
let flexSpace: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let toolbar = UIToolbar()
toolbar.barStyle = UIBarStyle.Default
toolbar.translucent = true
toolbar.tintColor = Color.blue
toolbar.sizeToFit()
toolbar.setItems([flexSpace, doneButton], animated: false)
toolbar.userInteractionEnabled = true
self.inputAccessoryView = toolbar
}
you can then call the function in your textfield like this:
textfield.addAccessoryView()

UITapGestureRecognizer not working inside of custom class (that's not a view controller)

I have a custom class Overlay where I added UIButton. When the button is clicked, a method should be called:
class Overlay {
func show(onView view: UIView, frame: CGRect) {
let dismissButton = UIButton()
dismissButton.frame = frame
dismissButton.setTitle("Dismiss", for: .normal)
dismissButton.setTitleColor(Project.Color.failure, for: .normal)
dismissButton.titleLabel?.font = Project.Typography.lightFont.withSize(22)
view.addSubview(dismissButton)
dismissButton.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissBtnTapped(tap:)))
dismissButton.addGestureRecognizer(tap)
}
#objc func dismissBtnTapped(tap: UITapGestureRecognizer) {
print("TEST")
}
I call show(...) inside my ViewController, passing in its view and a frame.
But the tapGestrueRecognizer is not working. Any ideas?
Thank you.
Edit: I tried putting this code directly inside my ViewController. Then it works. I'm not sure why, though, and that's not a viable solution for me, unfortunately :/
Edit 2:
That's how I call it:
let overlay = Overlay()
overlay.show(onView: self.view, frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: 150))
You are already adding a button try adding a target to it instead of a gesture,
and make your overlay variable global.
class YourControllerClass: UIViewController {
let overlay = Overlay()
...
func show(onView: UIView, frame: CGRect) {
...
dismissButton.addTarget(self, action: #selector(dismissBtnTapped(_:)), forControlEvents: .TouchUpInside)
}
func dismissBtnTapped(sender:UIButton){
}
}
Hope this helps.
You not need to add tapgesturerecognizer on uibutton, you can directly add target on it something like,
dismissButton.addTarget(self, action: #selector(dismissBtnTapped), forControlEvents: .TouchUpInside)
and remove parameter from dismissBtnTapped method!
Make sure your view which you are passing as onView in show(onView: UIView, frame: CGRect) method is enabled for user interactions.
As an example my script
step 1
class MyIcon {
var targetController = UIViewController()
func show(_ targetController:UIViewController, _ view: UIView, _ frame: CGRect) {
self.targetController = targetController
let icon = UIImageView()
icon.frame = frame
icon.image = UIImage(named: "image_user.png")
icon.isUserInteractionEnabled = true
view.addSubview(icon)
let tap = UITapGestureRecognizer(target: self.targetController, action: #selector(iconTapped))
icon.addGestureRecognizer(tap)
}
#objc func iconTapped(_ icon: UIImageView) {
print("Yo hoo! This worked!")
}
}
step 2
class MyController: UIViewController{
let myicon = MyIcon()
override func viewDidLoad() {
super.viewDidLoad()
myicon.show(self, self.view, self.view.frame)
}
#IBAction func iconTapped(_ icon: UIImageView){
myicon.iconTapped(icon)
}
}
Non of the above solve the problem here.
The point is, if you create the instance of the custom class inside of an event the instance is deleted after the event is completed since it is not associated with any persistent element in your app. Thats why you have to instantiate the object of the class as an attribute of the superview.

Resources