[英]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
从视图控制器传递到CustomToolbarWrapper
的init
函数。
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
: 另外,为确保按钮实际上可以与之交互,我在
Controller
的viewDidLoad()
中添加了此按钮:
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.