Как добавить действие в UIButton, которая находится в другом классе
Код ниже компилируется нормально, но вылетает с 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
"s init
функция.
Что еще я пробовал:
Изменение цели кнопки с target
в self
как это:
button.addTarget(self, action: #selector(CustomToolbar.buttonTapped(_:)), for: .touchUpInside)
в результате приложение больше не падает. Однако вместо этого я считаю, что строка кода ничего не делает (что по какой-то причине не выдает ошибку?) Из-за попытки печати button.allTargets
или даже button.allTargets.count
приводит к сбою приложения во время компиляции с ошибкой EXC_BREAKPOINT и отсутствием описания ошибки в консоли или пользовательском интерфейсе XCode (что еще больше смущает меня, поскольку в моем коде нет точек останова!).
Также, делая 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")
}
А выполнение кода привело к тому, что в журнале консоли была напечатана "Нажатая кнопка", поэтому пользователь должен взаимодействовать с кнопкой.
Не стесняйтесь сообщить мне, если вы считаете, что этого кода недостаточно, чтобы выяснить проблему, и я опубликую более подробную информацию.
Редактировать Я предпочитаю сохранить действие кнопки в CustomToolbarWrapper
класс, чтобы предотвратить повторение кода в будущем, так как действие будет одинаковым независимо от того, где экземпляр CustomToolbarWrapper
создано.
3 ответа
Лучшим вариантом будет добавить цель в ваш контроллер, а затем вызвать метод в вашем 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")
}
}
Ваша проблема прямо здесь:
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)
}
}
Есть другой способ, которым я бы воспользовался, это делегирование. 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()