I'm a bit skeptical recently, with the use of addTarget() to UITableViewCell in cellForRowAtIndexPath. I'm also eager to know what is the best practice to listen button event from UITableViewCell.
My confusion begins when I see I've no way to de-register the addTarget listener that I adds to an UIButton resides in UITableViewCell:
Code for cellForRowAtIndexPath:
cell.button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
The above code registers a listener to UIButton that resides in UITableViewCell, but I see there no reference of de-registering them. I'm not sure if this process is automatic or not (to addTarget-mechanism), I haven't found any reference that said so in Apple doc (as far I've searched, at least), also.
So, my question is, is addTarget use to UITableViewCell buttons are good to use? Does they all de-registers when view controller disappears?
Or, it would be good if I use an addObserver?
override func viewDidLoad()
{
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "onCellButtonPressed:", name: "cellButtonPressed", object: nil)
}
func onCellButtonPressed(notification:NSNotification)
{
if let sender = notification.object as? UIButton
{
...
}
}
In UITableViewCell code:
#IBAction func onButtonPressed(sender: AnyObject)
{
NSNotificationCenter.defaultCenter().postNotificationName("cellButtonPressed", object: sender)
}
Any suggestion on this, would be appreciated.
The target passed to addTarget is not retained. It is safe to use; you don't need to "deregister" (or remove) the target. This is the standard mechanism for control actions (like button presses) and you should use it (versus notifications).
Related
My senior was reviewing my code and he found that I have used UIButton addTarget method like this
override func viewDidLoad() {
super.viewDidLoad()
self.btnAccount.addTarget(self, action: #selector(Accounts(_:)), for: .touchUpInside)
}
Now he is saying that you should not use addTarget in viewDidLoad it will take time(kind of memory management thing I didn't get it) to load view controller but I didn't find it relevant
that's why I am asking this question did I made some mistake by doing this should I always make actions
I didn't hear of that and even if it is true, you should never try to do premature optimization on your app. UIButton is a UIControl object, which follows an event-listener pattern, which is often implemented with a hashmap (NSDictionary in Objective-C) of targets ('aka' Listeners or Observers) and it is not very time-consuming operation.
I personally prefer to setup all UI component right at the beginning:
lazy var btnAccount: UIButton = {
let btn = UIButton
// setup button's appearance
btn.addTarget(self, action: #selector(Accounts(_:)), for: .touchUpInside)
return btn
}()
P.S. Please ask him about the source of the fact and let me know.
When I want a tap response on my main View in my ViewController
A. I could create an IBOutlet as below
class ViewController: UIViewController {
#IBOutlet var tapGesture: UITapGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
tapGesture.addTarget(self, action: #selector(tapped))
}
#objc private func tapped(_: UITapGestureRecognizer) {
print("Log is here")
}
}
Or
B. I could an IBAction on the TapGesture such as below
class ViewController: UIViewController {
#IBAction func tapGestureAction(_ sender: Any) {
print("Log is here")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
Is there a preferred approach of one above the other? If not, which situation should we use A approach, and which we should use B approach?
Option B, i.e. just having the #IBAction outlet would be preferred when you already created your UITapGestureRecognizer in the storyboard, as this encapsulates as much logic as possible in the storyboard, reducing the overhead of reading unnecessary code and potential regressions if/when the code is refactored (but the storyboard remains unchanged).
You can still mark the #IBAction private (as it's effectively the same as using an #objc attribute). Also, if you need to access the gesture recognizer itself, you can have a regular #IBOutlet with a didSet to modify it, or change sender: Any to sender: UITapGestureRecognizer to access it in the action.
It is an interesting question, from my perspective this depends on how much from your application is in the storyboard or you want it explicitly written in the code.
My recommendation will be if you are doing something small and it should be done fast to use your storyboard. But if you have a big project with a big team then it will be better to have it in the code.
The other thing that can be a key factor for these approaches will be who is the owner of the reference and do you want to have some interactions of the gesture. For example, I have a gesture that should be enabled in specific cases and for others, it should be disabled. For this, you need to have a reference in the code.
What I'm trying to explain is that you should think for criteria like how and when you can use this gesture. And based on this to decide if you need less code or reference to the gesture or whatever you need
I try figure out why self point to the GameViewController instead of Answer
GameViewController.swift
class GameViewController: UIViewController {
var gameplay = QuestionsController(colors: colors)
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(gameplay.answersController.answers[0].button)
}
func didPressAnswerButton(sender: UIButton!) {
sender.setTitle("Im from GameViewController class", forState: .Normal)
}
}
QuestionsController.swift
class QuestionsController {
var question: Question
var answersController: AnswersController
}
AnswersController.swift
class AnswersController {
var answers = [Answer]()
func prepareAnswers() {
let answer = Answer()
answers.append(answer)
}
}
Answer.swift
class Answer{
let button: UIButton
func prepareButton() {
let answerButton = AnswerButton(type: .System)
answerButton.addTarget(self, action: "didPressAnswerButton:", forControlEvents: .TouchUpInside)
button = answerButton
}
func didPressAnswerButton(sender: UIButton!) {
sender.setTitle("Im from Answer class", forState: .Normal)
}
}
addTarget:action:forControlEvents: tells the control (answerButton in this case) what method to call, and what object to call it on, when the user taps the button. Looking at your code in more detail:
answerButton.addTarget(self, action: "didPressAnswerButton:", forControlEvents: .TouchUpInside)
When the user taps a button, the TouchUpInside event fires on the answerButton, and when that happens we want to invoke a method didPressAnswerButton: on an Answer object
So, we need to tell answerButton what do do when this TouchUpEvent fires. You do this calling the addTarget:action:forControlEvents method on the answerButton
The self argument tells the answerButton what object to notify about the event: it is the target. In this context, self is an Answer object.
The "didPressAnswerButton:" argument indicates what method the answerButton should call in response to the tap event: this is the action
This is the target-action mechanism of Objective-C/Cocoa. It's a very common pattern, it's worth it to read the linked documentation to learn a bit more about how it works. The key is that this is based on Objective-C* message passing: in the code above, "didPressAnswerButton:" indicates a selector, which when paired with a target (self), tells the answerButton how to send a "message" to the target when the user taps the button.
Also, note that when you are editing a storyboard and ctrl-drag from a button to your view controller and select a method, you are also setting up a target/action using this same mechanism. You select the target object by dragging to the view controller icon (or some other icon), and then you pick the action/selector when clicking on a method name in the popup.
* Target-Action was originally designed for Objective-C, but for the common case of implementing a view controller, you can assume Swift works the same way. Just note when reading documentation that Swift uses simple strings for actions, whereas Objective-C uses #selector(...).
and thanks in advance for taking the time to help.
Inside my CellForRowAtIndexPath, I have the following line:
cell.timeView.addTarget(self, action: "ButtonDidPress:", forControlEvents: UIControlEvents.TouchUpInside)
and my selector function is:
func ButtonDidPress (sender: DesignableView!){
let view:DesignableView = sender
cell.timeView.shadowColor = UIColor.yellowColor()
table.reloadData()
}
and the error i get is:
unrecognized selector sent to instance
I'm thinking that perhaps one can't send a View as a selector (am I using the correct terminology?), but how else can I target that particular view in that cell?
UPDATE:
I also tried using gestureRecognizer instead:
var tap = UIGestureRecognizer(target: self, action: Selector( "viewDidTap:"))
cell.timeView.addGestureRecognizer(tap)
and
func viewDidTap (sender: DesignableView!){
but I got the same error here.
Thanks!
There's a couple of strange things happening in your code. It seems you want to change the shadowColor property of timeView when a user touch it, right?
Two possible solutions are:
(This one is IMO the better one) Change DesignableView to inherit from UIButton. Then you can set:
timeView.addTarget(self, action: "ButtonDidPress:", forControlEvents: .TouchUpInside). Make sure you set it just once for each cell. Otherwise you will get multiple calls on one tap.
Use UITapGestureRecognizer, but you should put it in your UITableViewCell subclass, not to the view controller. Also, the sender in viewDidTap is not the view itself, but the recognizer. So the method will go like this:
func viewDidTap(sender: UITapGestureRecognizer) {
let location = sender.locationInView(sender.view)
if timeView.hitTest(location, withEvent: nil) == timeView {
timeView.shadowColor = UIColor.yellowColor()
// table.reloadData() - you don't need to reload the table
}
}
I'm writing some unit tests and, because of the nature of this particular app, it's important that I get as high up the UI chain as possible. So, what I'd like to do is programmatically trigger a button-press, as if the user had pressed the button in the GUI.
(Yes, yes -- I could just call the IBAction selector but, again, the nature of this particular app makes it important that I fake the actual button press, such that the IBAction be called from the button, itself.)
What's the preferred method of doing this?
It turns out that
[buttonObj sendActionsForControlEvents:UIControlEventTouchUpInside];
got me exactly what I needed, in this case.
EDIT: Don't forget to do this in the main thread, to get results similar to a user-press.
For Swift 3:
buttonObj.sendActions(for: .touchUpInside)
An update to this answer for Swift
buttonObj.sendActionsForControlEvents(.TouchUpInside)
EDIT: Updated for Swift 3
buttonObj.sendActions(for: .touchUpInside)
Swift 3:
self.btn.sendActions(for: .touchUpInside)
If you want to do this kind of testing, you’ll love the UI Automation support in iOS 4. You can write JavaScript to simulate button presses, etc. fairly easily, though the documentation (especially the getting-started part) is a bit sparse.
In this case, UIButton is derived from UIControl. This works for object derived from UIControl.
I wanted to reuse "UIBarButtonItem" action on specific use case. Here, UIBarButtonItem doesn't offer method sendActionsForControlEvents:
But luckily, UIBarButtonItem has properties for target & action.
if(notHappy){
SEL exit = self.navigationItem.rightBarButtonItem.action;
id world = self.navigationItem.rightBarButtonItem.target;
[world performSelector:exit];
}
Here, rightBarButtonItem is of type UIBarButtonItem.
For Xamarin iOS
btnObj.SendActionForControlEvents(UIControlEvent.TouchUpInside);
Reference
Swift 5:
class ViewController: UIViewController {
#IBOutlet weak var theTextfield: UITextField!
#IBOutlet weak var someButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
theTextfield.text = "Pwd"
someButton.sendActions(for: .touchUpInside)
}
#IBAction func someButtonTap(_ sender: UIButton) {
print("button tapped")
}
}
It's handy for people who write Unit Tests without UI Tests ;-)
Swift 5 way to solve it for UIBarButtonItem, which does not have sendAction method like UIButton etc.
extension UIBarButtonItem {
func sendAction() {
guard let myTarget = target else { return }
guard let myAction = action else { return }
let control: UIControl = UIControl()
control.sendAction(myAction, to: myTarget, for: nil)
}
}
And now you can simply:
let action = UIBarButtonItem(title: "title", style: .done, target: self, action: #selector(doSomething))
action.sendAction()
Swift 4:
self .yourButton(self)