下面的代码可以正常编译,但是由于出现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
从视图控制器传递到CustomToolbarWrapper
的{{1}}函数
我还尝试了什么:
将按钮的目标从init
更改为target
,如下所示:
self
导致应用不再崩溃。相反,我认为代码行无法执行任何操作(由于某种原因不会引发错误?),因为尝试打印button.addTarget(self, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
甚至button.allTargets
会导致应用在编译时崩溃一次,出现EXC_BREAKPOINT错误,并且控制台或XCode UI中没有错误描述(这更使我感到困惑,因为我的代码中没有断点!)。
此外,将button.allTargets.count
设为非静态不会更改任何前面提到的观察结果。
此外,为确保按钮实际上可以与之交互,我在buttonPressed(_:)
的{{1}}中添加了此按钮:
viewDidLoad()
并在Controller
中为按钮添加了一种简单的测试方法:
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")
}
实例,该操作都是相同的。
答案 0 :(得分:1)
您的问题就在这里
let toolbarWrapper = CustomToolbarWrapper(view: view, target: self)
您正在传递Controller
类的实例,该实例未实现buttonTapped(_:)
选择器。它由您的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)
}
}
答案 1 :(得分: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 :(得分:1)
我还有另一种使用委托的方式。 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()