Дополнение к Лекции 8: как скрыть клавиатуру после ввода текста.

Если вам нужен ввод текста в Swift приложении, то вы помещаете текстовое поле Text Field на пользовательский интерфейс. Когда пользователь кликнет на этом поле, появится клавиатура из нижней части экрана, позволяя пользователю начать набор текста. Многие думают, что клавиатура должна «скрыться», когда пользователь закончит печатать. Хотя это, конечно, правильно, но это не единственная причина «скрыть» клавиатуру. Более правильно думать о том, что клавиатура должна «скрыться», когда пользователь перестал взаимодействовать с ней. Например, когда он «тапнет» (tap) на кнопке, коснется чего-то на экране за пределами текстового поля или будет скроллить Table View. Таких ситуаций много, поэтому указание клавиатуре — «скрыться», отдано в iOS на откуп разработчику.
Распространенной ошибкой многих начинающих iOS программистов является незнание того, что этап «скрытия» клавиатуры не встроен в логику работу с текстовым полем Text Field.  Если вы кликните на текстовом поле Text Field, а затем попытаетесь кликнуть где-нибудь еще…. то ничего не происходит: клавиатура остается на экране и блокирует весь пользовательский интерфейс. В этом посте мы постараемся это исправить.
Мы узнаем как и где «скрыть» клавиатуру в iOS 8, используя Swift.

 Проводить испытание будем на простейшем приложении, код которого находится на Github и которое по существу имеет два файла ViewController.swift и Main.storyboard. На storyboard  расположено  текстовое поле и кнопка «Button«.
Screen Shot 2015-05-04 at 12.35.32 AM
Запускаем приложение, набираем текст и кликаем за пределами текстового поля в трех указанных местах — клавиатура не скрывается.
Screen Shot 2015-05-03 at 6.27.53 PM
Сначала выяснил КАК можно скрыть клавиатуру.
Это можно поправить разными способами, но идея заключается в том, чтобы прекратить редактирование в текстовом поле, тогда курсор «уйдет» с текстового поля и клавиатура «скроется».

Для любого элемента пользовательского интерфейса,  который является экземпляром класса UIResponder, в том числе и для текстового поля UITextField,  можно вызвать метод
                resignFirstResponder()
и появившаяся после взаимодействия с этим элементом клавиатура исчезнет.
Можно было бы написать
                textField.resignFirstResponder()
но на storyboard может быть несколько текстовых полей, и не хотелось бы работать с каждым текстовым полем индивидуально, то есть создавать outlet для каждого текстового поля и повторять вышеуказанную строку столько раз, сколько у вас текстовых полей. В этом ключе существуют два других альтернативных подхода.

Подход 1: использование метода endEditing для UIView

Предполагая, что ваше текстовое поле является subview вашего топового view, вы можете  вставить строку кода
               view.endEditing(true)
Мы здесь не используем textField.resignFirstResponder(). Почему? Потому что хотя textField.resignFirstResponder() работал бы прекрасно для нашего приложения, вариант множества текстовых полей textField потребовал бы множества вызовов textField.resignFirstResponder(), для каждого поля отдельно. При использовании    view.endEditing(true)  нам понадобится только одна строка кода, которая не включает названия специфических текстовых полей и необходимость иметь их outlets.
Еще одним преимуществом этого подхода является то, что вы можете использовать возвращаемое значение метода , чтобы узнать, «скрылась» клавиатура или нет.
Ключевая проблема здесь — это необходимость иметь ссылку на view, так что вы можете использовать этот подход только из View Controller. На практике это и есть наиболее распространенный случай.
Куда поместить эту строку кода? Об этом — чуть позже.

Подход 2: использование цепочки ответчиков (Responder Chain)

UIApplication.sharedApplication().sendAction(«resignFirstResponder», to:nil,from:nil,forEvent:nil)
Этот код будет скрывать вашу клавиатуру без указания кому посылается сообщение resignFirstResponder. Это самый независимый способ скрыть клавиатуру — так называемый  nil-targeted Action подход, использующий экземпляр класса UIApplication, который работает повсюду. Этот подход также предполагает одну строку кода и никаких outlets для текстовых полей.

Куда поместить эту строку кода? Об этом — в следующем разделе.

Куда помещать код?

Из двух приведенных выше строк кода воспользуемся наиболее распространенной строкой кода view.endEditing(true) и рассмотрим КУДА разместить код в зависимости от разных ситуаций потери взаимодействия с текстовым полем.

1. В Action методе кнопки.

Если вы хотите, чтобы клавиатура скрывать только при нажатии на кнопку, а при возвращении в текстовое поле опять появлялась и позволяла продолжать редактирование — помещайте в Action метод
Screen Shot 2015-05-03 at 8.20.51 PM
Если вы кликните на текстовом поле, то клавиатура появляется.
Screen Shot 2015-05-03 at 8.31.35 PM
Если вы кликните на кнопке, то клавиатура исчезает.
Screen Shot 2015-05-03 at 8.32.11 PM
Если вы опять переместите курсор на текстовое поле, то клавиатура  вновь появится, позволяя вам продолжить редактирование. Именно этот случай должен работать в демонстрационном примере «Autolayout» в Лекции 8 при нажатии кнопки «Login«. Вы можете этим и ограничиться. Но для полноты картины я продолжу изложение. Код находится на Github.

2. Переопределение метода View Controller
touchesBegan(touches: Set<NSObject>,withEvent event: UIEvent)

Если вы хотите, чтобы клавиатура «скрывалась» при любом клике за пределами текстового поля (dismiss keyboard), но не на элементах UI, то в соответствующем View Controller нужно переопределись метод

touchesBegan(touches: Set<NSObject>,withEvent event: UIEvent)

и поместить туда одну строку для «скрытия» клавиатуры следующим образом:
Screen Shot 2015-05-03 at 9.05.37 PM
Запустите приложение и убедитесь, что клики за пределами текстового поля приводят к «скрытию» клавиатуры. Если вы опять переместите курсор в текстовое поле, то клавиатура появится вновь, то есть похоже на ситуацию с кнопкой. Заметьте, что в методе используется «родной» для Swift тип данных Set, который работает с версией Swift 1.2  и выше.
Однако этот код не будет работать, если у вас есть Scroll View (примером которого является рассматриваемая в Лекции 9 UITableView). Потому что Scroll View находится внутри вашего топового  view, и он первым получит события touches (прикосновения пальцев), которые по умолчанию не будут передавать вашему view. В этом случае вам нужно использовать «распознаватель жестов» ( gesture recognizer), и в обработчике «распознавателя жестов»  вызвать один из приведенных в начале поста методов для «скрытия» клавиатуры, например, можно использовать строку кода view.endEditing(true) 

Screen Shot 2015-05-05 at 11.40.42 PM
Способ с  «распознавателем жеста» tap на вашем  view для обычного View Controller (без Scroll View) является точным аналогом способа с touchesBegan(touches: Set<NSObject>,withEvent event: UIEvent), но его действие распространяется на Scroll View.

В нашем примере схематично показано добавление «распознаватель жеста» tap в методе viewDidLoad, что очень не эффективно. Желательно добавлять «распознаватель жеста» tap только в случае, если клавиатура появилась на экране, и соответственно убирать его после того, как клавиатура скрылась. Этот способ изложен ниже.

3. Использование метода textFieldShoudReturn делегата UITextFieldDelegate

Если вы после редактирования текстового поля нажмете на клавиатуре клавишу «Return«, то клавиатура не «скроется», как это следовало бы ожидать по логике вещей. Для того, чтобы это произошло, нужно использовать делегата UITextFieldDelegate текстового поля и его метод textFieldShoulReturn

1. Создаем outlet для нашего текстового поля с именем textField

2. Подтверждаем протокол делегирования UITextFieldDelegate

3. Делаем наш View Controller делегатом протокола UITextFieldDelegate

4. Переопределяем метод textFieldShoudReturn, явно ссылаемся на наше текстовое поле и используем метод resignFirstResponser,  указанный в начале этого поста.

Screen Shot 2015-05-04 at 12.26.30 AM
Вместо того, чтобы назначать себя делегатом textField.delegate = self  в viewDidLoad, вы можете конфигурировать это в Инспекторе Связей (Connection Inspector).
Добавим на наш пользовательский интерфейс еще одно текстовое поле textField2, создадим outlet (пункт 1), выполним назначение делегата на storyboard (пункт 2)
Screen Shot 2015-05-04 at 9.24.31 AM
и добавим строку в метод textFieldShoudReturn (пункт4).
В результате получаем следующий код
Screen Shot 2015-05-24 at 7.21.37 PM
Чтобы задействовать клавишу Return на клавиатуре для скрытия клавиатуры, нужно для каждого текстового поля создать outlet, привязать его делегата к View Controller и задействовать метод делегата textFieldShoudReturn.

4. Эффективное добавление «распознаватель жеста» tap для скрытия клавиатуры в Scroll View

Для того, чтобы  добавлять «распознаватель жеста» tap только в случае, если клавиатура появилась на экране, и соответственно убирать его после того, как клавиатура скрылась, можно использовать механизм уведомлений (похоже на «радиостанцию»), который позволяет узнать, когда клавиатура появилась на экране, а когда скрылась. Поэтому подписываемся на эти события (мы будем постоянно слушать эти «радиостанции» ) в методе «жизненного цикла» viewDidAppear, то есть когда наш view уже появился на экране.

Screen Shot 2015-05-06 at 12.03.45 AM
Мы просим сформированный по умолчанию центр уведомлений NSNotificationCenter.defaultCenter() дать знать нашему классу, если клавиатура появилась на экране («радиостанция» UIKeyboardWillShowNotification), или если она «скрылась» ( «радиостанция» UIKeyboardWillHideNotification). Если произойдет одно из этих двух событий, то центр уведомлений автоматически вызовет наши функции (которые мы еще не написали): keyboardWillShow: и keyboardWillHide:
Функции keyboardWillShow необходимо создать и добавить «распознаватель жестов» (если он уже не существует, что не должно произойти, но в любом случае надо быть внимательным) к топовому view. «Распознаватель жестов» распознает жест tap на нашем топовом view и обрабатывает его уже знакомой нам функцией dismissKeyboard  
Screen Shot 2015-05-05 at 11.57.11 PM
При применении «распознаватель жестов» для Scroll View вам придется установить свойство cancelsTouchesInView  жеста в false, чтобы он передал событие touch другим views (например, ячейке Table View. По умолчанию он установлен true, что означает, что событие touch будет уничтожено и дальше передаваться не будет.
Клавиатура будет «скрываться», если пользователь один раз «тапнет» в области, расположенной над клавиатурой. Но мы не хотим, чтобы этот код вызывался, когда клавиатуры нет на экране. Конечно, это не приведет к аварийному завершению приложения, но зачем нам куча кода, в котором нет необходимости? Давайте уберем «распознаватель жестов»  в методе keyboardWillHide, как только клавиатура «скрылась»:
Screen Shot 2015-05-05 at 11.59.13 PM
Центр уведомлений — затратный ресурс, поэтому как только наш View Controller «ушел» с экрана, мы должны убрать все «радиостанции»
Screen Shot 2015-05-05 at 11.09.05 PM
Вернемся к нашему экспериментальному приложению.
Я добавила еще один Second View Controller, на котором размещено только единственное текстовое поле
Screen Shot 2015-05-06 at 12.15.14 AM
и вставила только код для «скрытия» клавиатуры с помощью «распознавателя жеста» tap. Достичь  Second View Controller в приложении можно с помощью кнопки «Second View Controller» на основном View Controller.
Код находится на Github.
Аналогичная методика для Table View  представлена в Github.

Методика «скрытия» клавиатуры в случае Scroll View для работы со storyboard и в коде изложена в

Dismiss Keyboard When Tap Outside a UITextField/UITextView и DisMissKeyBoard by touching background of UITableView 
Dismissing the iOS Keyboard When User Taps Off of It.

Использовались материалы

How to Dismiss UITextField’s Keyboard in your Swift App
The easy way to dismiss the keyboard in iOS
Swift iOS Tutorial — Dismiss the Keyboard (Xcode 6 Beta)

Дополнение к Лекции 8: как скрыть клавиатуру после ввода текста.: 4 комментария

  1. на swift 2.1 примеры по кнопке Return и по нажатию в любую область экрана для скрытия клавиатуры не работают.

Обсуждение закрыто.