0

下面的代码可以正常编译,但会因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 ...

    }
}

另一个类只是 a 的包装器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'sinit函数。

我还尝试了什么:

将按钮的目标从更改targetself

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

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

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

另外,为了确保按钮实际上可以交互,我在以下位置添加了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")
}

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

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

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

4

3 回答 3

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

}
于 2019-01-08T00:02:38.020 回答
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")
    }
}
于 2019-01-08T00:15:32.680 回答
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()
于 2019-01-08T00:50:52.327 回答