UIControl не вызывает обработчики событий, если UITextFields в SuperViews являются активными ответчиками - PullRequest
4 голосов
/ 07 марта 2019

UIButton

У меня есть View Controller, в который пользователь должен вводить данные уровня миссии. Контроллер представления затем имеет собственный UIView (Crew View) в своей иерархии представлений, где есть кнопка, которую пользователь может нажать, чтобы добавить людей в миссию. Эти люди также являются пользовательскими UIViews (Person Views), которые добавляются как дети в Crew View.

Макет выглядит примерно так:

VC
|_ScrollView
  |EntryFields 
  |_ Crew View (UIView subclass)
     |UIButton
     |_ Person View (UIView subclass)
        |EntryFields
        |UIButton
     |_ Person View
        |EntryFields
        |UIButton
      ...

Если для клавиатуры нет первого респондента, все нажатые кнопки запускают свои соответствующие обработчики независимо от того, где они находятся в их иерархии просмотра.

Теперь, если поле ввода в PersonView активно, я могу использовать как UIB-кнопку в PersonView, так и UIB-кнопку выше в Crew View.

Но если поле ввода на верхнем уровне активно, обработчик для кнопок не вызывается.

Примечание: я знаю, что кнопки получают сенсорные события, потому что их пользовательский интерфейс изменяется, так как активируется состояние .hllighted. Для обработчиков кнопок установлено значение .touchUpInside.


UISwitch

Это также проблема в другой части моего приложения с UISwitch. Иерархия представления выглядит следующим образом:

VC
|_ScrollView
  |Entry Fields
  |_Some UIView Subclass
    |UISwitch

Когда в сценарии была активна клавиатура (из полей ввода вверх по иерархии представления), UISwitch не будет вызывать свой обработчик (установлен на .valueChanged).

Мое решение: Я удалил целевой обработчик из UISwitch и вместо этого установил распознаватель жестов касания для всего представления, которое будет вручную запускать коммутатор, это решение работало независимо от того, был ли активен первый респондент. иерархия представления.


Кто-нибудь испытывал это раньше? Почему обработчики целевых событий не вызываются в UIControls (хотя они все еще отвечают в пользовательском интерфейсе), когда в суперпредставлении есть активные первые респонденты, но распознаватели жестов работают просто отлично.

Все построено программно, не то чтобы это имело значение, и отладчик View Hierarchy в XCode показывает, что UIControls не блокируются.


Дополнительная информация

Таким образом, EntryFields на самом деле является пользовательским компонентом (чтобы следовать дизайну материала с плавающим заполнителем). Иерархия представлений EntryField выглядит следующим образом:

EntryField (UIView subclass)
  |_ StackView
     |_ UILabel
     |_ UITextField

Все в этом проекте выполняется программно с автоматическими ограничениями макета. EntryField взаимодействует со своими делегатами, перенаправляя методы протокола из UITextField. Я не знаю, портит ли это первую цепочку респондента в приложении.


Вот пример проекта (выпотрошенная версия производственного кода), который идеально его копирует.

https://github.com/barbulescualex/55051678

Ответы [ 2 ]

2 голосов
/ 14 марта 2019

Я проверил ваш код и обнаружил проблему в объявлении вашей кнопки. Вы должны использовать ленивый, чтобы получить события. т.е.

lazy var addButton : UIButton = {
        let button = UIButton()
        button.setTitle("Add Person", for: .normal)
        button.setTitleColor(UIColor.purple, for: .normal)
        button.setTitleColor(UIColor.green, for: .highlighted)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.addTarget(self, action: #selector(addPersonField(_:)), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = true
        return button
    }()

выход

https://drive.google.com/open?id=1_u4a3anvOPuZGlJ3VtM15D6aBdXsclMm

self : ключевое слово self ссылается на текущий экземпляр класса, из в этом экземпляре класса.

func addTarget(_ target: Any?, 
        action: Selector, 
           for controlEvents: UIControl.Event)

Target : объект, метод действия которого вызывается.

Почему работает self в замыкании и обращается к моему контроллеру представления, если щелкнуть обычно против не работает, если в суперпредставлении есть активное текстовое поле:

Из документов

Элемент управления не сохраняет объект в целевом параметре. это ваша обязанность поддерживать сильную ссылку на цель объект, пока он прикреплен к элементу управления.

т.е. Это зависит от текущего состояния экземпляра (жизненного цикла) и от того, как iOS его обрабатывает.

Когда мы используем let myButton: UIButton = {...}(), вы сразу присваиваете значение переменной myButton, есть вероятность, что «self» не было инициализировано ОС . Чтобы убедиться, что когда мы добавляем цель к нашему объекту кнопки, 'self' правильно инициализируется, мы используем ленивое ключевое слово

Lazy : Ленивая инициализация (также иногда называемая отложенной реализацией или отложенной загрузкой) - это метод задержки создания объекта или какого-либо другого дорогостоящего процесса до тех пор, пока он не понадобится. При программировании для iOS полезно убедиться, что вы используете только ту память, которая вам нужна, когда вам это нужно.

С помощью lazy мы гарантируем, что наш элемент управления будет иметь сильную ссылку на текущий класс, который будет обрабатывать действие

1 голос
/ 12 марта 2019

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

Проверьте это ( видео ) и давайте посмотрим, есть ли оно у вас или нет.

Теперь у меня есть следующая иерархия в моем демонстрационном проекте:

enter image description here

И когда я запускаю его, это выглядит так:

enter image description here

Цветовая легенда:

  • КРАСНЫЙ: вид ВК (самообзор)

  • СИНИЙ: ScrollView

  • ЗЕЛЕНЫЙ: вид экипажа

У меня вообще нет проблем, как вы можете видеть на видео, что отличается от вашего?

ПОСЛЕДНЕЕ РЕДАКТИРОВАНИЕ:

Хорошо, проблема более чем бросается в глаза, хорошая новость в том, что проблемы нет, это просто то, как работает iOS, и учитывая эту возможность, я собираюсь объяснитькак можно более подробно, что на самом деле происходит.

Во-первых, ваш вопрос заключался в том, почему он работает с нижними текстовыми полями, но не работает с верхними.

ОТВЕТ:вы объявляете addButton как let вместо lazy var , затем добавляете в качестве цели self , что в этой области замыкание само по себе и после выхода из области действия, если вы поместите точку останова в func addPersonField , вы должны увидеть, что sender.allTargets не имеет целей.Это нормально для компилятора, так как вы можете, было время, когда вы не могли этого сделать, но теперь вы можете, потому что target объявлен как Any? , что означает, что вы даже можете установить nil в качестве цели ивы будете испытывать то же поведение.

Теперь вы можете задаться вопросом, почему оно работает с self как замыкание, которое освобождается после выхода из области действия, или почему оно работает так же хорошо с nil. doc для addTarget (_: action: for:)

говорит, что:

Если вы укажете nil, UIKit выполнит поискцепочка респондента для объекта, который отвечает на указанное сообщение действия и доставляет сообщение этому объекту.

Это ваш PersonsView , который не освобождается, поскольку всегда включенэкран и имеет указанное действие ваш func addPersonField .Вот почему это работает, прежде чем вы начнете использовать какие-либо текстовые поля (я знаю, что это работает с нижними, я доберусь туда).

Почему это работает с нижними текстовыми полями, вы будете удивляться, верно?Ну, опять же ... вы делаете магию здесь, не зная, если вы нажмете на нижнее textField, этот объект станетFirstResponder, теперь, когда происходит событие (например, ваш .touchUpInside на Add Person), если firstResponder не может его обработать, UIKit отправляет событие в родительский объект UIView текстового поля, который в данном случае является stackView, если stackView не может его обработать,событие отправляется в родительский UIView стека ViewView, который является PersonsView - золотым, поскольку он отвечает на указанный вами селектор addPersonField (_:) .

См. PersonView ниже: personsView - debug

С другой стороны, когда вы нажимаете текстовые поля TOP (эти EntryFields), они внедряются горизонтальное представление стека , которое встроено в вертикальное отображение стека , которое содержится в ScrollView .Теперь, если вы следили за мной здесь, у вас появилась идея, что цепочка ответчика идет от EntryField -> Horizontal StackView -> Vertical StackView -> ScrollView -> и т. Д. , но он не выглядит в других стеках, содержащих ваше PersonsView , где вы определили селектор, поэтому онздесь не работает.

Посмотрите, как стеки находятся на одном уровне, встроены в тот же VerticalStackView, который встроен в ScrollView ниже: top stackview and bottom stackview on the same level - debug

Даже если вы нажмете на Top EntryField / TextField и нажмете клавишу возврата на клавиатуре, вы вызовете view.textField.resignFirstResponder(), что снова активирует кнопку «Добавить человека».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...