Как добавить действие в 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()
Другие вопросы по тегам