should I don't use addTarget of UIButton in viewDidLoad - ios

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.

Related

Swift - Push to ViewController from UIButton in CollectionViewCell

I am trying to make my button, when tapped, to push to a new View Controller. I've tried many different ways but it won't trigger the function that I have it linked to. I also checked the 3D stack view of my layers and the button is on top and clickable, even when I check the background color, it's not being covered by anything else.
Does anyone have any ideas to what I am doing wrong?
For now I am trying to make the button print out the sentence in the console, however whenever I press it, the string doesn't pop up, so I haven't bothered to connect it to the view controller yet.
Also, I am coding this app without storyboards.
Here is my code below.
It is under the MainPageCell class declared as a UICollectionViewCell
private let playButton: UIButton = {
let button = UIButton()
button.setTitle("", for: .normal)
button.backgroundColor = .clear
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(MainPageCell.buttonTapped), for: .touchUpInside)
return button
}()
#objc func buttonTapped() {
print("I PRESSED THE BUTTON")
}
This line is wrong:
button.addTarget(self, action: #selector(MainPageCell.buttonTapped), for: .touchUpInside)
You cannot assign self as the action target in a property declaration initializer, because the instance designated by self does not exist yet. There is no error or warning (I regard that as a bug), but the action method is never called.
Move that assignment elsewhere and rewrite it, like this:
self.playButton.addTarget(self, action: #selector(MainPageCell.buttonTapped), for: .touchUpInside)
Maybe try defining your button action under the UIView Class, I've had a problem like that before, only worked when i linked it to the View Class, Good luck

Cannot override mutable property 'refreshControl' of type 'UIRefreshControl?' with covariant type 'UIRefreshControl'

I use a UIRefreshControl in my tableview for pull new data. But when I define the control, below error shows. I've googled for a while, but find nothing useful.
I use Xcode Version 9.2 (9C40b), swift4, IOS11.2
Cannot override mutable property 'refreshControl' of type
'UIRefreshControl?' with covariant type 'UIRefreshControl'
var refreshControl = UIRefreshControl()
If you want to define another control, then change the name... refreshControl is a UIScrollView/UITableViewController ivar since iOS10/iOS6 [respectively]. You're trying to redefine it and that is what causing the error. Or you can use the supplied one of course.
You probably meant something like self.refreshControl = UIRefreshControl()
Swift 4/5
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl = UIRefreshControl()
self.refreshControl!.attributedTitle = NSAttributedString(string: "Pull to refresh")
self.refreshControl!.addTarget(self, action: #selector(refresh(sender:)), for: UIControl.Event.valueChanged)
}
#objc func refresh(sender:AnyObject) {
// Code to refresh table view
self.refreshControl!.endRefreshing()
}
The compiler is preventing you from specifying a more stringent requirement in your subclass than the one set by your superclass.
Suppose the compiler allowed you to override an Optional property with a non-Optional type. What happens if someone does this:
let tableView: UITableView = CoolTableViewSubclass()
tableView.refreshControl = nil // but my overridden property can't be nil!
As a subclass, you need to work ok when someone treats you as if you were your superclass. So the compiler stops you in this case because it can already tell that you're breaking this rule.
Mike Ash wrote this up in an unsurprisingly good post if you're curious.

How does UIButton addTarget self work?

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(...).

Is addTarget safe to use?

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

how to programmatically fake a touch event to a UIButton?

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)

Resources