Keywords: Swift | Target-Action Pattern | Objective-C Compatibility
Abstract: This article delves into the technical challenges of passing arguments to selectors when using UITapGestureRecognizer in Swift. By analyzing common errors such as "Argument of '#selector' does not refer to an '@Objc' method" and "Method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C," it explains the fundamentals of the Target-Action pattern, Objective-C compatibility requirements, and correct parameter-passing methods. Key topics include standard function signatures in Target-Action, accessing model objects via properties instead of direct parameter passing, and alternative approaches using custom sender objects. With code examples, the article offers practical solutions and best practices to help developers avoid pitfalls and build more robust iOS applications.
Fundamentals of the Target-Action Pattern
In iOS development, the Target-Action pattern is a common event-handling mechanism that allows an object (the target) to respond to action messages sent by another object, such as a gesture recognizer or button. Originating from Objective-C, this pattern requires adherence to Objective-C runtime rules when used in Swift. The core principle is that action methods must have specific function signatures to ensure compatibility with Objective-C.
Analysis of Common Errors
Developers often encounter two main errors. First, the error "Argument of '#selector' does not refer to an '@Objc' method, property, or initializer" indicates that the method referenced by the selector is not marked as @objc. This occurs because the #selector syntax requires methods to be visible in the Objective-C runtime, whereas Swift methods are not exposed to Objective-C by default. Adding the @objc modifier can resolve this, but only if the method's parameter types can be represented in Objective-C.
Second, the error "Method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C" points out that parameter types, such as custom Model classes, cannot be represented in Objective-C. Objective-C only supports specific types, like primitives, classes, or classes conforming to @objc protocols. If a Model class is not marked as @objc or does not inherit from NSObject, it cannot be passed directly.
Correct Methods for Parameter Passing
According to the Target-Action pattern, action method signatures should follow one of these forms: no parameters, accepting a sender parameter (of type Any), or accepting sender and event parameters. For example:
func handleTap() {
// No parameters
}
func handleTap(sender: Any) {
// Sender parameter
}
func handleTap(sender: Any, forEvent event: UIEvent) {
// Sender and event parameters
}
In #selector, only the function signature should be included, without passing parameters. For instance, for func handleTap(sender: UIGestureRecognizer), the correct selector is #selector(handleTap(sender:)). If the model object is a property of the view controller, it can be accessed directly in the method without passing it as a parameter:
class ViewController: UIViewController {
var modelObj: Model?
override func viewDidLoad() {
super.viewDidLoad()
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
self.imageView.addGestureRecognizer(gesture)
}
@objc func handleTap(sender: UIGestureRecognizer) {
if let model = self.modelObj {
// Use the model object
}
}
}
Alternative Approach: Custom Sender Objects
Another method involves passing parameters via custom sender objects. For example, create a CustomButton class inheriting from UIButton and add extra properties:
class CustomButton: UIButton {
var name: String = ""
var customObject: Any?
}
// Set properties and add action
btnFullRemote.name = "Example"
btnFullRemote.customObject = modelObj
btnFullRemote.addTarget(self, action: #selector(btnFullRemote(_:)), for: .touchUpInside)
@objc func btnFullRemote(_ sender: Any) {
if let button = sender as? CustomButton {
let name = button.name
let object = button.customObject
}
}
This approach allows storing arbitrary data in the sender but requires caution regarding type safety and forced casting risks. It is suitable for complex scenarios, but accessing via properties is generally recommended for cleaner code.
Summary and Best Practices
When passing arguments to selectors in Swift, prioritize the standard forms of the Target-Action pattern by accessing model objects via properties rather than direct parameter passing. Ensure methods are marked as @objc and parameter types are compatible with Objective-C. For custom types, make them inherit from NSObject or mark them as @objc. Avoid including parameters in #selector and utilize the sender parameter to obtain the gesture recognizer instance. These practices help in writing more maintainable and compatible code, reducing runtime errors.