8

我正在创建一个 Swift 项目,我想定义一个特定的协议来强制其他组件实现一个animate方法:

protocol AnimatableBehavior {
    @IBAction func animate()
}

问题是我希望这个方法是一个IBAction,但是我从 XCode 得到这个错误:

只有实例方法可以声明为“IBAction”

我的问题是,你将如何实施这样的事情?

我考虑过:

  1. Remove @IBAction,但是我需要记住在每个实现的类中添加它。不是很优雅且容易出错。
  2. 创建一个基类而不是协议,但随后我强制所有组件对我的基类进行子类化,而不是他们自己选择的子类,因此它不是一个有效的选项。

还有其他想法吗?


编辑:回复下面的评论。

on 协议的想法IBAction是因为在项目中会有很多不同的开发者实现小的 UI 组件,所有这些组件都有 animate 方法。这些组件可以通过编程方式或通过 Interface Builder 添加,它们总是非常方便,IBAction因为我计划从 IB 文件中组合它们以最大限度地简化视图控制器(这显然是一个仅视图任务)。

因此,下面提出的在控制器中添加一个仅调用animate组件的方法的解决方案并不好,因为它是冗余代码并且使您的控制器更加依赖您的视图。

让开发人员记住IBAction在方法上添加关键字的想法是可行的,但正如我所说的那样容易出错(我的意思是会有一些人忘记它),我想确保这是始终可从 IB 访问。它还增加了额外的认知负担,因为我需要IBAction在协议中记录这种缺失并要求实现者手动添加它。

我知道这不是在 iOS 和 中工作的常见方式UIKit,但这就是我发布问题的原因,也许有人有其他想法。

4

2 回答 2

11

在协议 中有一个没有任何意义。当您从 Interface Builder 控制 + 拖动到您的实际源代码时,Interface Builder 只不过是一个关键字。@IBAction@IBAction

这只是对@IBAction实际情况和作用的简单误解。

  1. 不必将方法标记为@IBAction以便成为 UI 元素操作的目标。addTarget您可以使用UI 元素拥有的方法集以编程方式将任何方法连接到任何操作。不必将方法标记为 an@IBAction即可执行此操作。

  2. 无论协议是否将方法定义为@IBAction,符合协议的类都可以添加它(并且仍然符合协议。

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

    这两个视图控制器子类都符合协议,并且只有当您打算从界面构建器@IBAction中连接一个动作时才有必要!


最终,无论您尝试做什么,如果您认为@IBAction协议中有必要,我认为您对某事采取了错误的方法。如果不了解您实际在做什么的更多细节,很难说出正确的方法是什么,但是@IBAction加入协议从来没有意义。

对我来说,您的协议强制执行的方法似乎根本不应该与@IBAction方法绑定。相反,无论用户交互应该触发动画,都应该依次调用该animate方法。例如,如果我们不讨论协议,我的建议是这样的设置:

class ViewController: UIViewController {
    @IBAction func buttonThatStartsAnimation {
        self.animate()
    }

    func animate {
        // code that does all the animation
    }
}

因此,对于协议,我们应该在实际启动动画代码的方法(在协议的情况下,这显然是其他一些外部类)之间采取相同的职责分离,并且该animate方法应该只处理相关的动画。

重要的是,作为一般规则,您不应该直接从定义它们的类外部直接引用您的@IBAction方法或变量。@IBOutlet

于 2015-04-11T12:05:40.947 回答
0

我完全同意 OP,尽管直到Swift 3.1您不能在协议中真正将任何内容声明为@IBOutlet,@IBAction@objc。作为一种解决方法,我选择基于以下内容构建一些东西pod 'ActionKit'并编写了类似的东西:

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
    }
}

并使您的视图控制器:

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

我希望有更简单的方法,但到目前为止,您需要手动执行以下两件事:将您的按钮声明为@IBOutlet并调用设置函数。我们需要这样做的原因import ActionKit是我们不能addTarget在协议扩展中。

于 2017-06-12T16:16:08.737 回答