如何向另一个 class 中的 UIButton 添加操作
How to add an action to a UIButton that is in another class
下面的代码可以正常编译,但会因 unrecognized selector sent to instance
错误而崩溃。
我有一个 class 继承自 UIViewController
:
class Controller: UIViewController {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
... Other code ...
}
}
另一个 class 只是 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
从视图控制器传递给 CustomToolbarWrapper
的 init
函数.
我还尝试了什么:
将按钮的目标从 target
更改为 self
,如下所示:
button.addTarget(self, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
导致应用程序不再崩溃。但是,相反,我认为该行代码无法执行任何操作(由于某种原因不会引发错误?),因为尝试打印 button.allTargets
甚至 button.allTargets.count
会导致应用程序在编译时崩溃时间,出现 EXC_BREAKPOINT 错误,控制台或 XCode UI 中没有错误描述(这让我更加困惑,因为我的代码中没有断点!)。
此外,使 buttonPressed(_:)
非静态不会改变前面提到的任何观察结果。
此外,为了确保按钮实际上可以与之交互,我在 Controller
的 viewDidLoad()
中添加了这个:
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" 打印在控制台日志中,因此用户应该能够与该按钮进行交互。
如果您认为这些代码不足以解决问题,请随时告诉我,我会 post 更多详细信息。
编辑
我更喜欢在 CustomToolbarWrapper
class 中保留按钮操作的实现,以防止将来重复代码,因为无论在何处创建 CustomToolbarWrapper
的实例,操作都是相同的.
你的问题就在这里:
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
您正在传递一个 Controller
class 的实例,它没有实现 buttonTapped(_:)
选择器。它由您的 CustomToolbarWrapper
class 实施。这通常是一个糟糕的设计。您应该遵循委托模式或回调模式。
更新答案:
委托模式解决方案:
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)
}
}
最好的选择是在控制器中添加目标,然后在按下按钮时调用 toolbarWrapper
中的方法。但是如果你真的需要保留这个设计,你应该在你的控制器 class 中有一个对你的 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")
}
}
我会使用另一种方式,即委派。 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()
下面的代码可以正常编译,但会因 unrecognized selector sent to instance
错误而崩溃。
我有一个 class 继承自 UIViewController
:
class Controller: UIViewController {
override func viewDidLoad() {
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
let toolbar = toolbarWrapper.toolbarView
view.addSubview(toolbar)
... Other code ...
}
}
另一个 class 只是 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
从视图控制器传递给 CustomToolbarWrapper
的 init
函数.
我还尝试了什么:
将按钮的目标从 target
更改为 self
,如下所示:
button.addTarget(self, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
导致应用程序不再崩溃。但是,相反,我认为该行代码无法执行任何操作(由于某种原因不会引发错误?),因为尝试打印 button.allTargets
甚至 button.allTargets.count
会导致应用程序在编译时崩溃时间,出现 EXC_BREAKPOINT 错误,控制台或 XCode UI 中没有错误描述(这让我更加困惑,因为我的代码中没有断点!)。
此外,使 buttonPressed(_:)
非静态不会改变前面提到的任何观察结果。
此外,为了确保按钮实际上可以与之交互,我在 Controller
的 viewDidLoad()
中添加了这个:
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" 打印在控制台日志中,因此用户应该能够与该按钮进行交互。
如果您认为这些代码不足以解决问题,请随时告诉我,我会 post 更多详细信息。
编辑
我更喜欢在 CustomToolbarWrapper
class 中保留按钮操作的实现,以防止将来重复代码,因为无论在何处创建 CustomToolbarWrapper
的实例,操作都是相同的.
你的问题就在这里:
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
您正在传递一个 Controller
class 的实例,它没有实现 buttonTapped(_:)
选择器。它由您的 CustomToolbarWrapper
class 实施。这通常是一个糟糕的设计。您应该遵循委托模式或回调模式。
更新答案:
委托模式解决方案:
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)
}
}
最好的选择是在控制器中添加目标,然后在按下按钮时调用 toolbarWrapper
中的方法。但是如果你真的需要保留这个设计,你应该在你的控制器 class 中有一个对你的 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")
}
}
我会使用另一种方式,即委派。 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()