What would be the best strategy for IBActions in Swift protocols?

I am creating a Swift project, and I want to define a specific protocol that uses other components to implement the animate method:

 protocol AnimatableBehavior { @IBAction func animate() } 

The problem is that I want this method to be IBAction , but I get this error from Xcode:

Only instance methods can be declared "IBAction"

My question is: how would you implement such a thing?

I reviewed:

  • Remove @IBAction , but then I need to remember adding it to every class that implements. Not very elegant and error prone.
  • Create a base class instead of a protocol, but then I use all the components to subclass my base class instead of my own, so it is not a valid option.

Any other ideas?


EDIT: response to comments below.

The idea of IBAction in the protocol is that the project will have many different developers implementing small components of the user interface, all of which have an animation method. Components can be added programmatically or using Interface Builder , and it’s very convenient that they are always IBAction , because I plan to compile them from IB files to simplify View Viewers to the maximum extent (and this is clearly a viewing-only task).

Therefore, the solution proposed below to add a method to a controller that simply calls the animate component is not good, because it is redundant code and makes your controller more dependent on your presentation.

The idea of ​​letting the developer remember to add the IBAction keyword in the method is workable, but, as I said, this is error prone (and I mean that some will forget about it), and I want to make sure that it is always available from IB. It also adds extra cognitive load because I will need to document this IBAction flaw in the protocol and ask the developer to add it manually.

I know that this is not the usual way of working in iOS and UIKit , but that is why I posted the question, maybe someone has an alternative idea.

+6
source share
2 answers

It makes no sense to have @IBAction in the protocol. @IBAction is nothing more than a keyword for Interface Builder that has a hook when you control + drag and drop from Interface Builder to your actual source code.

This is just a simple misunderstanding of what @IBAction really is and does.

  • The method should not be marked as @IBAction , so that it becomes an object of actions of the interface element. You programmatically connect any method to any action using a set of addTarget methods that have UI elements. For this, the method should not be marked as @IBAction .

  • Regardless of whether the protocol defines the method as @IBAction , the class matching the protocol can add it (and still conform to the protocol.

     protocol FooProtocol { func doSomething() } class ViewControllerA: UIViewController, FooProtocol { @IBAction func doSomething() { // do something } } class ViewControllerB: UIViewController, FooProtocol { func doSomething() { // do something } } 

    Both of these subclasses of the view controller conform to the protocol, and @IBAction is required ONLY if you intend to connect the action from the interface constructor!


Ultimately, whatever you try to do, if you think that @IBAction necessary in your protocol, I think you're wrong about something. It's hard to say what the correct approach will be, without knowing the details of what you are actually doing, but for @IBAction it makes no sense to belong to the protocol.

It seems to me that the methods that your protocol uses should not be tied to @IBAction methods at @IBAction . Instead, any user interaction should trigger the animation, and the animate method should be called in turn. For example, if we were not talking about the protocol, my recommendation would be this:

 class ViewController: UIViewController { @IBAction func buttonThatStartsAnimation { self.animate() } func animate { // code that does all the animation } } 

Thus, with the protocol, we must adhere to the same division of responsibilities between the method that actually initiates the animation code (which in the case of protocols, this is obviously some other external class), and the animate method should only ever process the corresponding animations.

It is also important that you should not directly refer to your @IBAction methods or your @IBOutlet variables directly outside the class that defines them.

+9
source

I completely agree with the OP, although prior to Swift 3.1 you cannot declare anything at all as @IBOutlet , @IBAction , @objc , etc. in the protocol. As a workaround, I decided to build something based on the pod 'ActionKit' and wrote something like:

 protocol RequiresAnimation { var animateButton: UIButton! { get } func enableAnimateButton() func actionAnimate() } extension RequiresAnimation where Self: UIViewController { func enableAnimateButton() { animateButton.addControlEvent(.touchUpInside) { self.actionAnimate() } } func actionAnimate() { // animate here } } 

And make your controller look like:

 class MyViewController: UIViewController, RequiresAnimation { @IBOutlet var animateButton: UIButton! override func viewDidLoad() { super.viewDidLoad() enableAnimateButton() } } 

I'm sorry that there would not be an easier approach, but for now you need to do these two things manually: declaring your button as @IBOutlet and calling the configuration function. The reason we need to import ActionKit is because we cannot addTarget in the protocol extension.

0
source

Source: https://habr.com/ru/post/984982/


All Articles