【问题标题】:How to add an action to a UIButton that is in another class如何向另一个类中的 UIButton 添加操作
【发布时间】:2019-01-07 23:49:09
【问题描述】:

下面的代码可以正常编译,但会因unrecognized selector sent to instance 错误而崩溃。

我有一个继承自 UIViewController 的类:

class Controller: UIViewController {
    override func viewDidLoad() {
        let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
        let toolbar = toolbarWrapper.toolbarView
        view.addSubview(toolbar)

        ... Other code ...

    }
}

还有另一个类,它只是 UIView 的包装器并包含按钮:

class CustomToolbarWrapper {

    var toolbarView: UIView

    init(view: UIView, target: Any) {
        let height: CGFloat = 80
        toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
        let button = UIButton()

        ... Some button layout code ...

        button.addTarget(target, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
        toolbarView.addSubview(button)
    }

    @objc static func buttonTapped(_ sender: Any) {
        print("button tapped")
    }
}

为了清楚起见,我省略了一大段代码并保留了我认为必要的部分。我认为我的代码不起作用,因为我误解了目标在 addTarget 函数中的工作方式。通常,我只会使用self 作为按钮操作的目标,所以我只是尝试将self 从视图控制器传递给CustomToolbarWrapperinit 函数。

我还尝试了什么:

将按钮的目标从target 更改为self,如下所示:

button.addTarget(self, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)

导致应用不再崩溃。然而,相反,我认为这行代码无法执行任何操作(由于某种原因不会引发错误?),因为尝试打印 button.allTargets 甚至 button.allTargets.count 会导致应用程序在编译时崩溃,并且EXC_BREAKPOINT 错误并且在控制台或 XCode UI 中没有错误描述(这让我更加困惑,因为我的代码中没有断点!)。

此外,将buttonPressed(_:) 设为非静态不会改变前面提到的任何观察结果。

另外,为了确保按钮实际上可以交互,我在ControllerviewDidLoad() 中添加了这个:

for subview in toolbar.subviews? {
    if let button = subview as? UIButton {
        button.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)
    }
}

并在Controller 中为按钮添加了一个简单的测试方法:

@objc func buttonPressed(_ sender: UIButton) {
    print("Button Pressed")
}

运行代码确实会在控制台日志中打印“Button Pressed”,因此用户应该能够与该按钮进行交互。

如果您认为这不足以解决问题,请随时告诉我,我会发布更多详细信息。

编辑 我更喜欢将按钮操作的实现保留在 CustomToolbarWrapper 类中,以防止将来重复代码,因为无论在哪里创建 CustomToolbarWrapper 的实例,操作都是相同的。

【问题讨论】:

    标签: ios swift uikit target-action


    【解决方案1】:

    最好的选择是在控制器中添加目标,然后在按下按钮时调用toolbarWrapper 中的方法。但是如果你真的需要保持这个设计,你应该在你的控制器类中强烈引用你的toolbarWrapper,否则你的toolbarWrapper 被释放并且没有被调用。此外,buttonTapped(_:) 方法不需要是静态的。因此,在您的控制器中:

    class Controller: UIViewController {
    
        var toolbarWrapper: CustomToolbarWrapper?
    
        override func viewDidLoad() {
            toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
            let toolbar = toolbarWrapper.toolbarView
            view.addSubview(toolbar)
    
            ... Other code ...
    
        }
    }
    

    在你的包装中:

    class CustomToolbarWrapper {
    
        var toolbarView: UIView
    
        init(view: UIView, target: Any) {
            let height: CGFloat = 80
            toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height,width: view.frame.width, height: height))
            let button = UIButton()
    
            ... Some button layout code ...
    
            button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
            toolbarView.addSubview(button)
        }
    
        @objc func buttonTapped(_ sender: Any) {
            print("button tapped")
        }
    }
    

    【讨论】:

    • 这实际上对我有用!比我想象的要简单得多...谢谢!
    【解决方案2】:

    我会使用另一种方式,即委托。 target 不一定是控制器,它可以是 CustomToolbarWrapper 本身。 首先,声明一个协议

    protocol CTDelegate: AnyObject {
      func didClickButton()
    }
    

    然后在CustomToolbarWrapper 中添加一个属性weak var delegate: CTDelegate? 和一个按钮操作:

    @objc func buttonTapped(_ sender: UIButton) {
    
       delegate?.didClickButton()
    }
    

    所以在你的情况下,它变成:

    button.addTarget(self, action: #selector(CustomToolbarWrapper.buttonTapped(_:)), for: .touchUpInside)
    

    然后当你去任何一个ViewController,符合CTDelegate并初始化CustomToolbarWrapper,你可以设置它的委托给控制器。 例如

    let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
    toolbarWrapper.delegate = self
    

    并在您在控制器中符合的方法内实施您的操作,即

    func didClickButton()
    

    【讨论】:

    • 这很有趣。我没有考虑使用委托,但我需要在CustomToolbarWrapper 中实现我的操作,因为否则我必须在多个视图控制器之间复制/粘贴相同的代码。我可以在视图控制器中创建委托的实例并在CustomToolbarWrapper 中实现委托吗?这会按预期工作吗?
    • 委托旨在使按钮按下操作不依赖于工具栏包装器,而是依赖于实现者。除非,我误解了你的初衷
    • 我认为你可以在委托协议的扩展中实现它,以防止重复代码
    • 好的,我试试看。
    • 是的,根据我的帖子,buttonTappedCustomToolbarWrapper
    【解决方案3】:

    你的问题就在这里:

    let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
    

    您正在传递一个未实现 buttonTapped(_:) 选择器的 Controller 类的实例。它由您的 CustomToolbarWrapper 类实现。总的来说,这是一个糟糕的设计。您应该遵循委托模式或回调模式。

    更新答案:

    委托模式解决方案:

    class Controller: UIViewController, CustomToolbarWrapperDelegate {
        override func viewDidLoad() {
            let toolbarWrapper = CustomToolbarWrapper(view: view, buttonDelegate: self)
            let toolbar = toolbarWrapper.toolbarView
            view.addSubview(toolbar)
        }
    
        // MARK: - CustomToolbarWrapperDelegate
        func buttonTapped(inToolbar toolbar: CustomToolbarWrapper) {
            print("button tapped")
        }
    }
    
    protocol CustomToolbarWrapperDelegate: AnyObject {
        func buttonTapped(inToolbar toolbar: CustomToolbarWrapper) -> Void
    }
    
    class CustomToolbarWrapper {
    
        var toolbarView: UIView
        weak var buttonDelegate: CustomToolbarWrapperDelegate?
    
        init(view: UIView, buttonDelegate: CustomToolbarWrapperDelegate?) {
            let height: CGFloat = 80
            toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
            self.buttonDelegate = buttonDelegate
            let button = UIButton()
            button.addTarget(self, action: #selector(self.buttonTapped(_:)), for: .touchUpInside)
            toolbarView.addSubview(button)
        }
    
        @objc private func buttonTapped(_ sender: Any) {
            // Your button's logic here. Then call the delegate:
            self.buttonDelegate?.buttonTapped(inToolbar: self)
        }
    
    }
    

    如果您宁愿坚持当前的设计,那么只需实施以下更改:

    class Controller: UIViewController {
        override func viewDidLoad() {
            let toolbarWrapper = CustomToolbarWrapper(view: view, target: self, selector: #selector(self.buttonTapped(_:)), events: .touchUpInside)
            let toolbar = toolbarWrapper.toolbarView
            view.addSubview(toolbar)
        }
    
        @objc private func buttonTapped(_ sender: Any) {
            print("button tapped")
        }
    }
    
    class CustomToolbarWrapper {
    
        var toolbarView: UIView
    
        init(view: UIView, target: Any?, selector: Selector, events: UIControlEvents) {
            let height: CGFloat = 80
            toolbarView = UIView(frame: CGRect(x: 0, y: view.frame.height - height, width: view.frame.width, height: height))
            let button = UIButton()
    
            button.addTarget(target, action: selector, for: events)
            toolbarView.addSubview(button)
        }
    
    }
    

    【讨论】:

    • CustomToolbarWrapper 的实例将有一个具有相同操作的按钮,无论该实例位于哪个类,这就是为什么我宁愿在CustomToolbarWrapper 中实现按钮的操作。否则,我将不得不在每个具有 CustomToolbarWrapper 实例的视图控制器中复制并粘贴相同的 buttonTapped 函数。
    • @RPatel99 有道理。在这种情况下,我建议您使用委托模式。查看我的更新答案
    • 其他答案之一解决了我的问题,而我不必实现委托,所以我将其标记为正确,但使用委托可能仍然是更好的约定,即使看起来有点复杂,所以我仍然会对此表示赞同。
    猜你喜欢
    • 1970-01-01
    • 2015-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多