Как добавить действие в UIButton, которая находится в другом классе - PullRequest
0 голосов
/ 08 января 2019

Код ниже компилируется нормально, но вылетает с ошибкой 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 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")
}

И запуск кода привел к тому, что «Button Pressed» был напечатан в журнале консоли, поэтому пользователь должен иметь возможность взаимодействовать с кнопкой.

Не стесняйтесь, дайте мне знать, если вы считаете, что этого кода недостаточно для выяснения проблемы, и я опубликую более подробную информацию.

Редактировать Я предпочитаю сохранять реализацию действия кнопки в классе CustomToolbarWrapper, чтобы предотвратить повторение кода в будущем, поскольку действие будет одинаковым независимо от того, где будет создан экземпляр CustomToolbarWrapper.

Ответы [ 3 ]

0 голосов
/ 08 января 2019

Лучшим вариантом будет добавить цель в ваш контроллер, а затем вызвать метод в вашем 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")
    }
}
0 голосов
/ 08 января 2019

Есть другой способ, которым я бы воспользовался, это делегирование. 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()
0 голосов
/ 08 января 2019

Ваша проблема прямо здесь:

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

}
...