Not sure if my thinking here is correct but I have similar animations I use throughout my iOS project and I would like to condense it to 1 file and reuse wherever I want.
A brief example. In my animations file I have a scale animation
Animations.swift
class Animations {
class func scaleSmall(_ view: UIView) {
let scaleAnim = POPBasicAnimation(propertyNamed: kPOPLayerScaleXY)
scaleAnim?.toValue = NSValue(cgSize: CGSize(width: 0.9, height: 0.9))
view.layer.pop_add(scaleAnim, forKey: "scaleSmallAnim")
}
}
Here I have one of my many swift files in my View folder and I would like to add that animation to the button
Button.swift
class Button: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
self.addTarget(self, action: #selector(Animations.scaleSmall(_:)), for: .touchDown)
}
}
I thought I would be able to reference the animation from an additional file however everytime I do it this way I get the same error
Argument of '#selector' refers to instance method 'scaleSmall' that is not exposed to Objective-C
Am I referencing this function wrong?
try changing class func scaleSmall(_ view: UIView) {
to
#objc class func scaleSmall(view: UIView) {
I've confirmed my comment, so I'm posting an answer. Methods for UIButton need to be bridged to Obj-C. That's what #Kostas Tsoleridis suggests with his answer as well - it is not mixing two languages in one file, you are just marking the method for the compiler. Other solution would be to inherit from NSObject by your Animations class.
Now, as your confusion mentioned in a comment - it worked, because your Button class inherits from UIButton which is both from Obj-C world, and also inherits from NSObject down the chain.
To also address the issue mentioned in a comment under #Kostas Tsoleridis answer (and to be honest I should have thought about it before) - you can't pass self as a target and use a method from another class (even a static one). To solve this, you can use a singleton instance of your Animations class, something like this :
class Animations {
static let sharedInstance = Animations()
#objc class func scaleSmall(_ view: UIView) {
// your code
}
}
let button = UIButton()
button.addTarget(Animations.sharedInstance, action: #selector(Animations.scaleSmall(_:)), for: .touchDown)
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.
using swift, I'm trying to create a dynamic and generic control to reuse it, basically, the control should have a general behavior inside.
To be more specific, I have a UIScrollView and it's filled using UIViews, when you click over an UIView, the background should change.
That is working correctly.
But, for the implementation, my class of the generic control, accepts a Selector as parameter.
Both works separately, but together are not working.
The specific part of codes are:
Generic class
let clickAgendaEvent = UITapGestureRecognizer(target: self, action: #selector (self.agendaClicked (_:)))
cellSubView.addGestureRecognizer(clickAgendaEvent)
cell.addSubview(cellSubView)
let itemClickedEvent = UITapGestureRecognizer(target: viewController.self, action: self.agendaItemClicked! )
cell.addGestureRecognizer(itemClickedEvent)
And a ViewController with an implementation like this:
#objc func eventDailyAgenda(sender:UIView!){
print("Item clicked!")
}
As you can see, the second event, is not inside of the generic class, the second event is a separated implementation if the ViewController.
But, the generic class, would be implemented for other UIViewController.
Someone have an idea about how can handle it?
Look into UIGestureRecognizerDelegate to handle both gestures simultaneously. Check out the callback gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) . Return true to have both handled simultaneously.
The easier way to do it, is implementing UIGestureRecognizerDelegate.
But for my specific case didn't work, because, I have different classes implementing the behavior.
But, I found a way to do it.
And is implementing NSObject (if you are in a UIViewController you don't have to implement it.
For my case I have my class:
class MyClass:NSObject {
func createAll(){
let clickAgendaEvent = UITapGestureRecognizer(target: self, action: #selector (self.agendaClicked (_:)))
cellSubView.addGestureRecognizer(clickAgendaEvent)
cellSubView.tag = index
cell.addSubview(cellSubView)
}
#objc func agendaClicked(_ sender:AnyObject) {
baseView.setTransparentToSubViews()
print("Here")
let tap = sender as! UITapGestureRecognizer
tap.view?.backgroundColor = UIColor().hexStringToUIColor(hex: "#e0e0e0")
if let v = tap.view {
// use button
print("The tag is \(v.tag)")
}
if let c: NSObject.Type = NSClassFromString(viewController.className) as? NSObject.Type{
let c_tmp = c.init()
c_tmp.perform(Selector(("test")))
c.perform(Selector(("static_test")))
}
}
}
Add in your ViewController
#objc public func test(){
print("This is Test!!!")
}
#objc public class func static_test(){
print("This is Static Test")
}
And this extension:
import Foundation
import UIKit
extension UIViewController {
var className: String {
return NSStringFromClass(self.classForCoder)
}
}
Now, you can execute all the methods you want.
For this case, once the user touch an element, the next event is fired from the touch, and not is necessary to add a new delegate.
Maybe is not the better way, but is totally functional.
This question already has answers here:
"classname has no member functionname" when adding UIButton target
(4 answers)
Attach parameter to button.addTarget action in Swift
(14 answers)
Closed 6 years ago.
In Swift 2 this used to work (I have left out table view methods intentionally)...
import Foundation
import UIKit
private extension Selector {
static let didTapButton = #selector(TableVC.buttonTapped(_ :))
}
class TableVC: UITableViewController{
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let btnAccView:UIButton = UIButton(frame: CGRectMake(0, 0, 27, 27))
btnAccView.addTarget(self, action: .didTapButton, forControlEvents: UIControlEvents.TouchUpInside)
btnAccView.tag = indexPath.row
}
func buttonTapped(sender: UIButton){
print("button tapped at row \(sender.tag)")
}
}
In Swift 3 this causes the error : "TableVC has no member buttonTapped".
I just need to write a selector that takes an argument.
I have tried fiddling with the syntax in every way I can think of but nothing has worked. Thanks for your help
First of all, you need to modify buttonTapped so that the first argument label is suppressed. Insert an underscore before the internal argument label so that it looks like this:
func buttonTapped(_ sender: UIButton) {
Before, its method signature was TableVC.buttonTapped(sender:), and now its signature will be TableVC.buttonTapped(_:). You might have to remove the space between the underscore and the colon in your extension, but it should work after that.
As a side note, is there any particular reason you're using an extension on Selector here? That extension model is most useful typically with stuff like notification names that can be easily misspelled, resulting in tricky bugs. Since the Swift compiler automatically validates #selector declarations no matter where they're placed, you can just put it right into your code—no extension necessary.
You can save yourself some grief by letting the compiler infer some of the function signature. In your case...
class TableVC: UIViewController {
func buttonTapped(sender: UIButton) { /*...*/ }
}
You can refer to it from outside the TableVC class as either:
#selector(TableVC.buttonTapped(sender:))
#selector(TableVC.buttonTapped)
The second works because there's only one buttonTapped function on TableVC, so the compiler doesn't need the full signature to disambiguate it.
From code inside of TableVC you can simplify it a step further and just use #selector(buttonTapped), because at that point you're in the class scope and it can safely infer a method of the class.
(The #selector(TableVC.buttonTapped(_:)) line you're using works only if your method is declared as func buttonTapped(_ sender: UIButton). For your use case, it doesn't matter whether the sender parameter has an argument label or not — you just have to be consistent about it between your func declaration and your #selector expression.)
And as #Bob notes, there's no need for the Selector extension, because the compiler already validates the selector.
Use following way to implement selector for button action:
import UIKit
class MyViewController: UIViewController {
let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
override init?(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
let action = #selector(MyViewController.tappedButton)
myButton.addTarget(self, action: action, forControlEvents: .touchUpInside)
}
func tappedButton(sender: UIButton?) {
print("tapped button")
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
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(...).
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)