I have a running project and now it is nearly completed but now i have a method in app delegate class and i want that this method should be called on all of the buttons in the project. One simple way is that i should add call to method in each button listener code.
But is there a way that i can make it wired with Button action "Touch up inside"
Override sendActionsForControlEvents by subclassing the UIButton class.
- (void)sendActionsForControlEvents:(UIControlEvents)controlEvents {
if(controlEvents == UIControlEventTouchUpInside) {
//call your method.
}
[super sendActionsForControlEvents:controlEvents];
}
You might have to go through all UIButtons and change its class to that of your subclass. I haven't tried this yet but I think you can give it a go.
You can use extension for this
extension UIButton
{
override open func sendActions(for controlEvents:UIControlEvents)
{
if controlEvents == .touchUpInside
{
// Type your custom execution here
}
}
}
This will get called for every event on button
swift 5
extension UIButton {
open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
//your code
super.sendAction(action, to: target, for: event)
}
}
I found this as a simplest way to implement a common action for every button click. This is way more simpler than subclassing every button in the app.
Related
How can I catch any touch up event in my application view without affecting any other event in subview or subview of the subview?
Currently whenever I add UILongPressGestureRecognizer to my root view, all other addTarget functions break in subviews and their subviews.
func gestureRecognizer(_: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith: UIGestureRecognizer) -> Bool {
return true
}
override func viewDidLoad() {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
button.setTitle("Click me", for: .normal)
button.addTarget(self, action: #selector(buttonClick), for: .touchDown)
self.view.addSubview(button)
initLongTapGesture() // This kills the button click handler.
}
func initLongTapGesture () {
let globalTap = UILongPressGestureRecognizer(target: self, action: #selector(tapHandler))
globalTap.delegate = self
globalTap.minimumPressDuration = 0
self.view.addGestureRecognizer(globalTap)
}
#objc func tapHandler(gesture: UITapGestureRecognizer) {
if ([.ended, .cancelled, .failed].contains(gesture.state)) {
print("Detect global touch end / up")
}
}
#objc func buttonClick() {
print("CLICK") // Does not work when initLongTapGesture() is enabled
}
The immediate solution to allow the long press gesture to work without preventing buttons from working is to add the following line:
globalTap.cancelsTouchesInView = false
in the initLongTapGesture function. With that in place you don't need the gesture delegate method (which didn't solve the issue anyway).
The big question is why are you setting a "long" press gesture to have a minimum press duration of 0?
If your goal is to monitor all events in the app then you should override the UIApplication method sendEvent. See the following for details on how to subclass UIApplication and override sendEvent:
Issues in overwriting the send event of UIApplication in Swift programming
You can also go through these search results:
https://stackoverflow.com/search?q=%5Bios%5D%5Bswift%5D+override+UIApplication+sendEvent
Another option for monitoring all touches in a given view controller or view is to override the various touches... methods from UIResponder such as touchesBegan, touchesEnded, etc. You can override these in a view controller class, for example, to track various touch events happening within that view controller.
Unrelated but it is standard to use the .touchUpInside event instead of the touchDown event when handing button events.
I have a UIMenu on my app and I want to detect when a user taps outside(dismiss) the UIMenu. But it seems like Apple does not support any delegates by default for this action.
Because when a user taps outside I want to change the image of the button.
Sample Image
One rather hacky way I found, is to subclass UIButton and override contextMenuInteractionWillEndFor.
class MyButton: UIButton {
override func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willEndFor configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionAnimating?) {
super.contextMenuInteraction(interaction, willEndFor: configuration, animator: animator)
print("ending!")
}
}
contextMenuInteractionWillEndFor is the delegate method that is called when you dismiss a UIMenu, but if you are setting the button's menu property, the button itself will become the delegate of the UIContextMenuInteraction, and you can't set it to something else, which is why you have to subclass UIButton.
Compare this to when you are adding a context menu using addInteraction, where you have control over the UIContextMenuInteractionDelegate.
I want to define action for my KCFloatingActionButton. UITapGestureRecognizer is defined for the fab button in ViewController, but I want to do this action in manager class or on another page. I got the KCFloatingActionButton view, but I can't give it a click action.When I click on KCFloatingActionButton I don't want to define a new UITapGestureRecognizer with addGestureRecognizer ,I want it to take the action it is defined.
I can do this for UIButton or UIBarButtonItem as follows
button.sendActions(for: .touchUpInside)
barButtonItem.target?.perform(barButtonItem.action, with: nil)
How can I do this action for KCFloatingActionButton ?
You can try something like this:
button.addTarget(self, action: #selector(handleButtonAction), for: .touchUpInside)
#objc func handleButtonAction(){
//Put your logic here
}
The self in addTarget refers where will be the action to be executed, in this case will be in the current ViewController.
The #selector(handleButtonAction) is the function that will be executed when the user touch the button, in this case will be an Objective-C function (that's why is use the "#selector").
The .touchUpInside is the event that has to be triggered to execute the function.
And finally the function itself #objc func handleButtonAction() { } will execute all the actions that you define for the touch up event.
the #objc attribute comes in: when you apply it to a class or method it instructs Swift to make those things available to Objective-C as well as Swift code.
I want to receive information about mouse move events during button click (mouse up)
I'm adding NSTrackingArea to view that I want to track mouse move and mouse dragged events on, but I still don't receive these events.
I assume that mouseDown in NSButton is blocking mouse events, so the only solution I come up with is overriding mouseDown function for NSButton and not calling super.mouseDown, but then I need to handle button selection manually and I'm not sure if this is right approach for this.
Is this a right solution for my problem? Will there be no problems? Is there a better solution?
Here is code for test, just add button to new project and assign TestButton class to it.
class TestButton: NSButton {
override func mouseDown(with event: NSEvent) {
print("Mouse up")
super.mouseDown(with: event) // After removing events works.
print("Mouse down")
}
}
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let trackingArea = NSTrackingArea(rect: view.bounds, options: [.activeAlways, .mouseMoved, .enabledDuringMouseDrag], owner: self, userInfo: nil)
view.addTrackingArea(trackingArea)
}
override func mouseMoved(with event: NSEvent) {
print("Mouse moved")
super.mouseMoved(with: event)
}
override func mouseDragged(with event: NSEvent) {
print("Mouse dragged")
super.mouseDragged(with: event)
}
}
I had a similar requirement for my project. So initially, I started out with NSButton just like you. But, it turned out to be more hectic than I thought. Handling the action and selected state where the main concerns I faced. You could proceed with NSButton if it actually meets up with your requirement. But, I eventually moved to NSView and customised it. So, few of my implementation for handling move and drag event implementation I've open-sourced here is the link.
The traditional way to do this is to use a custom subclass of NSButtonCell and override continueTracking(last:current:in:) (inherited from NSCell). Your override should generally call through to super and return what it returns, but you can do something else in addition, to respond to the mouse movements. The issue is that NSCell and its subclasses have been "soft deprecated" for a while.
That said, it would be very surprising to me as a user that anything other than the button would react to the mouse movements while I'm interacting with the button (clicked in it and dragging).
I have two textFields and a Done button in my VC but I'm having some problems with the ending of editing.
My textFieldDidEndEditing method is called when I tap on one textField after being inside the other one, or when I tap outside the textField (because I added a tap recognizer to the parent view) but not when I tap the Done button.
And, most important (especially when I run on an actual device), the keyboard won't disappear under any of these circumstances, even though my textFieldDidEndEditing method calls resignFirstResponder().
Why isn't the keyboard dismissing? Also, is there a way to have textFieldDidEndEditing get called when I tap outside the field just automatically (without having it come from the tap recognizer)? It just seems like this should be how it works, but if I'm wrong, I'm wrong.
Here's some pertinent parts of my code.
1.Trying to dismiss the keyboard. The first part of this method works, and the value is stored (when the method is called at all, that is). At no point does the cursor disappear from the textField, nor does the keyboard get dismissed.
func textFieldDidEndEditing(_ textField: UITextField) {
if let playerName = textField.text, let playerNum = nameFields.index(of: textField) {
playerNames[playerNum] = playerName
}
resignFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textFieldDidEndEditing(textField)
return true
}
Also, here's a curious thing: when I set a breakpoint in textFieldDidEndEditing and debug, enter a value in one field and hit Done, it segues to the next scene, and then stops at textFieldDidEndEditing, which at this point has no effect (the values may be stored but they aren't reflected in the new scene).
2.Trying to add the tap recognizer to the done button. I don't have an outlet to the done button in my code, just out of laziness, so that's probably the best solution. But, I'm still interested in why this doesn't work. This is identical to the code that defines the tap recognizer that's working in the parent view.
func dismiss(_ sender:UITapGestureRecognizer) {
nameFields.forEach { textFieldDidEndEditing($0) }
}
override func viewDidAppear(_ animated: Bool) {
for view in view.subviews where view is UIButton {
let dismissTextField = UITapGestureRecognizer(target: self, action: #selector(dismiss(_:)))
dismissTextField.numberOfTapsRequired = 1
view.addGestureRecognizer(dismissTextField)
}
}
You need to call resignFirstResponder inside textFieldShouldReturn method instead of calling textFieldDidEndEditing.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
Also in your TapGesture method simply call endEditing(_:) with your view instead of looping through the array of textFields.
func dismiss(_ sender:UITapGestureRecognizer) {
self.view.endEditing(true)
}
Swift 5: This solution below is much easier actually.
Simply subclass your ViewController with the text fields to UITextFieldDelegate like this:
class CreateGroupViewController: UIViewController, UITextFieldDelegate {
then if you named your UITextFields: nameTextField & occupationTextField then add the following delegations to your viewDidLoad() method:
self.nameTextField.delegate = self
self.occupationTextField.delegate = self
Then simply add the generic function to your file:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
If you do it this way, then for every textField reference outlet you create, all of them will hide the keyboard after the user hits the return button no matter which textField he's typing. For each textField you add to your view, add another self.nextTextField.delegate = self line to your viewDidLoad.
Remember to test this with your iPhone/iDevice plugged into your developer computer bc XCode's simulator doesn't bring up the keyboard (bc you're normally testing using a full keyboard). Or if you've set up your testing hardware (iPhone) via WiFi you can do it that way also. Otherwise, your users will be "testing" this on TestFlight.