简体   繁体   English

如何向另一个类中的UIButton添加操作

[英]How to add an action to a UIButton that is in another class

The code below compiles fine, but crashes with an unrecognized selector sent to instance error. 下面的代码可以正常编译,但是由于unrecognized selector sent to instance错误而崩溃。

I have one class that inherits from UIViewController : 我有一个从UIViewController继承的类:

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

        ... Other code ...

    }
}

And another class that is just a wrapper for a UIView and contains buttons: 另一个类只是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")
    }
}

For the sake of clarity, I left out a large chunk of code and kept what I thought was necessary. 为了清楚起见,我省略了大量代码,并保留了我认为必要的内容。 I think that my code doesn't work because of my misunderstanding of the how the target works in the addTarget function. 我认为我的代码无法正常工作是因为我对addTarget函数中目标的工作方式有误解。 Normally, I would just use self as the target of my button's action, so I just tried to pass along self from the view controller to the CustomToolbarWrapper 's init function. 通常,我只是将self用作按钮操作的目标,因此我只是尝试将self从视图控制器传递到CustomToolbarWrapperinit函数。

What else I have tried: 我还尝试了什么:

Changing the button's target from target to self like this: 像这样将按钮的目标从target更改为self

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

results in the app not crashing anymore. 导致应用不再崩溃。 Instead, however, I believe that line of code fails to do anything (which doesn't throw an error for some reason?) because attempting to print button.allTargets or even button.allTargets.count results in the app crashing at compile time, with an EXC_BREAKPOINT error and no error description in the console or the XCode UI (which just confuses me even more because there are no breakpoints in my code!). 但是,我认为代码行无法执行任何操作(由于某种原因不会引发错误?),因为尝试打印button.allTargets甚至button.allTargets.count导致应用在编译时崩溃,带有EXC_BREAKPOINT错误,并且控制台或XCode UI中没有错误描述(这使我更加困惑,因为我的代码中没有断点!)。

Also, making buttonPressed(_:) non-static does not change any of the previously mentioned observations. 同样,将buttonPressed(_:)非静态不会更改任何前面提到的观察结果。

Also, to make sure the button could in fact be interacted with, I added this in the viewDidLoad() of Controller : 另外,为确保按钮实际上可以与之交互,我在ControllerviewDidLoad()中添加了此按钮:

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

and added a simple testing method to Controller for the button: 并为Controller添加了一种简单的测试方法:

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

And running the code did result in "Button Pressed" being printed in the console log, so the button should be able to be interacted with by the user. 运行代码确实会导致在控制台日志中打印“已按下按钮”,因此用户应该可以与该按钮进行交互。

Feel free to let me know if you think this is not enough code to figure out the problem, and I will post more details. 如果您认为此代码不足以解决问题,请随时告诉我,我将发布更多详细信息。

Edit I prefer to keep the implementation of the button's action in the CustomToolbarWrapper class to prevent repeating code in the future, since the action will be the same no matter where an instance of CustomToolbarWrapper is created. 编辑我更喜欢将按钮动作的实现保留在CustomToolbarWrapper类中,以防止将来重复代码,因为无论在何处创建CustomToolbarWrapper实例,该动作都是相同的。

Your problem is right here: 您的问题就在这里:

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

You're passing an instance of Controller class which doesn't implement the buttonTapped(_:) selector. 您正在传递Controller类的实例,该实例未实现buttonTapped(_:)选择器。 It is implemented by your CustomToolbarWrapper class. 它由CustomToolbarWrapper类实现。 This is a bad design in general. 通常这是一个不好的设计。 You should either follow a delegate pattern, or a callback pattern. 您应该遵循委托模式或回调模式。

Updated Answer: 更新的答案:

Delegate pattern solution: 委托模式解决方案:

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

}

If you'd rather stick to your current design then just implement the following changes: 如果您希望坚持当前的设计,则只需进行以下更改:

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

}

The best option would be to add the target in your controller and then call a method in your toolbarWrapper on button press. 最好的选择是将目标添加到您的控制器中,然后在按下按钮时在您的toolbarWrapper调用一个方法。 But if you really need to keep this design, you should have a strong reference to your toolbarWrapper in your controller class, otherwise your toolbarWrapper is deallocated and nothing gets called. 但是,如果您确实需要保留此设计,则应该在控制器类中强烈引用您的toolbarWrapper ,否则您的toolbarWrapper被释放,并且不会被调用。 Also, the buttonTapped(_:) method does not need to be static. 同样, buttonTapped(_:)方法也不必是静态的。 Thus, in your controller: 因此,在您的控制器中:

class Controller: UIViewController {

    var toolbarWrapper: CustomToolbarWrapper?

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

        ... Other code ...

    }
}

And in your wrapper: 在你的包装中:

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

There is another way I would use which is delegation. 我要使用的另一种方法是委派。 The target does not necessarily have to be a controller, it can be the CustomToolbarWrapper itself. target不一定必须是控制器,它可以是CustomToolbarWrapper本身。 First, declare a protocol 首先,声明一个协议

protocol CTDelegate: AnyObject {
  func didClickButton()
}

Then in CustomToolbarWrapper add a property, weak var delegate: CTDelegate? 然后在CustomToolbarWrapper添加一个属性, weak var delegate: CTDelegate? and a button action: 和一个按钮动作:

@objc func buttonTapped(_ sender: UIButton) {

   delegate?.didClickButton()
}

So in your case, it becomes: 因此,在您的情况下,它变为:

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

Then when you go to any ViewController, conform to CTDelegate and initialize the CustomToolbarWrapper , you can set its delegate to the controller. 然后,当您转到任何ViewController时,请遵循CTDelegate并初始化CustomToolbarWrapper ,您可以将其委托设置为控制器。 eg 例如

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

and implement your action inside the method you are conforming to in your controller ie 并在您要遵循的方法中执行您的操作,例如

func didClickButton()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM