SA1032 — Строка поиска (StoreSearch)
Содержание страницы
Одной из наиболее распространенных задач мобильных приложений является общение с сервером в Интернете — если вы пишете мобильные приложения, вам нужно знать, как загружать и скачивать данные.
С помощью этого нового приложения под названием StoreSearchвы узнаете, как отправлять HTTP GET-запросы в веб-службу, как анализировать данные JSON и как загружать файлы с сервера.
Вы собираетесь создать приложение, которое позволит вам искать в iTunes Store. Конечно, на вашем iPhone уже есть приложения для этого — “App Store” и “Music”, но что плохого в том, чтобы написать еще одно?
Apple сделала веб-сервис доступным для поиска по всему iTunes Store, и вы будете использовать его, чтобы узнать о работе в сети.
Готовое приложение будет выглядеть следующим образом:

Готовое приложение StoreSearch
Вы добавите возможность поиска к своему старому другу-табличному представлению. При нажатии на элемент в таблице появляется анимированное всплывающее окно с дополнительной информацией. А когда вы переворачиваете iPhone в альбомную ориентацию, макет приложения полностью меняется, чтобы показать результаты поиска по-другому.
В последнем приложении вы окунули палец ноги в бассейн темного режима. Теперь вы погрузитесь с головой и узнаете все о том, как поддерживать различные режимы внешнего вида, создав это приложение с нуля для поддержки как светлых, так и темных режимов.
Также будет выпущена iPad-версия приложения с пользовательским интерфейсом для iPad:

Приложение на iPad
StoreSearch заполняет недостающие фрагменты и округляет знания, полученные при разработке предыдущих приложений. Вы также узнаете, как распространять свое приложение среди бета-тестеров и как отправить его в App Store.
В этой главе вы выполните следующие действия:
- Создать проект: Создайте новый проект для вашего нового приложения. Настройте контроль версий с помощью Git.
- Создание пользовательского интерфейса: Создание пользовательского интерфейса для StoreSearch.
- Сделайте поддельный поиск: Поймите, как работает панель поиска, получив поисковый запрос и заполнив табличное представление поддельными результатами поиска.
- Создание модели данных: Создайте модель данных, которая будет содержать данные для результатов поиска и позволит расширить их в будущем.
- Нет найденных данных: Обрабатывайте ситуации “нет данных” при выполнении поиска.
Впереди еще много работы, так что давайте начнем!
Создание проекта
Запустите Xcode и создайте новый проект. Выберите шаблон приложения и заполните параметры следующим образом:
- Название продукта: StoreSearch
- Команда: Значение по умолчанию
- Идентификатор организации: com.yourname
- Интерфейс: Раскадровка
- Язык: Swift
- Используйте основные данные, включите тесты: оставьте их непроверенными
Когда вы сохраняете проект, Xcode дает вам возможность создать репозиторий Git. До сих пор вы игнорировали эту опцию, но теперь вы должны убедиться, что она включена:

Создание репозитория Git для проекта
Если вы не видите эту опцию, нажмите кнопку Параметры в левом нижнем углу диалогового окна.

Git и контроль версий
Git — это система контроля версий, которая позволяет вам делать моментальные снимки вашей работы, чтобы вы всегда могли вернуться позже и увидеть историю изменений, внесенных в проект. Еще лучше то, что такой инструмент, как Git, позволяет вам сотрудничать в одной и той же кодовой базе с несколькими людьми.
Представьте себе хаос, если бы два программиста одновременно изменили один и тот же исходный файл. Вполне возможно, что ваши изменения могут быть случайно перезаписаны изменениями коллеги. Однажды у меня была работа, где мне пришлось кричать через коридор другому программисту: “Вы используете файл X?” Просто чтобы мы не уничтожали работу друг друга.
С такой системой контроля версий, как Git, каждый программист может работать независимо над одними и теми же файлами, не опасаясь отменить работу другого. Git достаточно умен, чтобы автоматически объединять все изменения, и если есть какие-либо конфликтующие изменения, он позволит вам разрешить их вручную.
Git — не единственная система контроля версий, но она самая популярная для iOS. Многие разработчики iOS делятся своим исходным кодом на GitHub (github.com), бесплатный сайт для совместной работы, который использует Git в качестве движка. Xcode имеет встроенную поддержку Git.
Для StoreSearchвы будете использовать некоторые базовые функции Git. Даже если вы работаете в одиночку и вам не нужно беспокоиться о том, что другие программисты испортят ваш код, все равно имеет смысл его использовать. В конце концов, вы можете испортить свой собственный код, а с Git у вас всегда будет способ вернуться к старому — и работать! — версия кода.

Первый экран
Первый экран в StoreSearch будет иметь табличное представление с панелью поиска — давайте создадим контроллер представления для этого экрана.
- В навигаторе проекта выберите ViewController.swift, наведите курсор на имя
ViewController
класса и щелкните правой кнопкой мыши, чтобы открыть контекстное меню. Выберите в меню Refactor ▸ Переименовать… и переименуйте класс (а также связанные с ним файлы и ссылки на раскадровку) вSearchViewController
. - Запустите приложение, чтобы убедиться, что все работает. Вы должны увидеть белый экран с строкой состояния вверху.
Тестовый темный режим
Поскольку мы создаем приложение для обоих режимов внешнего вида с нуля, мы должны тестировать каждый экран для обоих режимов внешнего вида каждый раз, когда мы проводим какое-либо тестирование.
- Выберите Функции ▸ Переключите внешний вид в меню симулятора, чтобы переключить симулятор в темный режим. Теперь вы должны увидеть черный экран с белым текстом строки состояния. Все выглядит хорошо.
Вы можете снова использовать опцию меню переключения внешнего вида, чтобы переключить симулятор обратно в световой режим.
Поскольку мы будем много тестировать темный режим, есть еще один способ переключить внешний вид приложения, а также некоторые другие настройки, напрямую через Xcode.
Если приложение запущено через Xcode, нажмите кнопку Переопределения среды на панели инструментов отладки в нижней части окна редактора Xcode.

Среда Xcode переопределяет
Первоначально каждый раздел всплывающего меню будет отключен, но вы можете использовать соответствующий переключатель, чтобы включить этот раздел.
После включения вы можете изменить настройки в этом конкретном разделе, чтобы изменить внешний вид вашего приложения «на лету». Так, например, вы можете включить переключатель для раздела Внешний вид, а затем нажать на опции Светлый или Темный, чтобы переключить приложение между темным и светлым режимами.
Полезно знать разные способы тестирования вашего приложения в разных условиях окружающей среды и на самом деле тестировать все эти условия. Вы никогда не знаете, какой конкретный набор условий выявляет трудноразводимую ошибку.
Контроль версий Git
Обратите внимание, что навигатор проекта теперь показывает значки M и R рядом с некоторыми именами файлов в списке:

Xcode показывает файлы, которые были изменены
Если вы не видите эти значки, выберите в строке меню Xcode опцию Source Control ▸ Обновить состояние файла. Если это дает сообщение об ошибке или все еще не работает, просто перезапустите Xcode. В общем, это хороший совет: если Xcode ведет себя странно, перезапустите его.
M означает, что файл был изменен с момента последней фиксации, а R означает, что это файл, который был переименован.
Так что же такое коммит?
Когда вы используете систему контроля версий, такую как Git, вы должны время от времени делать моментальный снимок. Обычно вы делаете это после того, как добавили новую функцию в свое приложение, или когда исправили ошибку, или когда вам кажется, что вы внесли изменения, которые хотите сохранить. Это называется фиксацией.
Когда вы создали проект, Xcode сделал начальную фиксацию. Вы можете увидеть это в окне Истории проекта.
- Выберите навигатор управления версиями на панели Навигатор и перейдите на вкладку Репозитории, чтобы просмотреть историю проекта.:

История коммитов для этого проекта
Примечание: ваша история Git может не всегда выглядеть так же, как моя на скриншотах, так как у меня могут быть дополнительные коммиты, чем то, что я говорю вам сделать в книге. Об этом не беспокойся. Вы всегда можете сделать свои собственные коммиты – как только научитесь — в любой момент и не полагаться исключительно на инструкции в книге.
Давайте зафиксируем изменения, которые вы только что внесли. В меню Xcode выберите пункт Управление версиями ▸ Фиксация…
Откроется новое окно, в котором подробно показано, какие изменения вы внесли. Это хорошее время, чтобы быстро просмотреть изменения кода, просто чтобы убедиться, что вы не совершаете ничего такого, чего не собирались делать:

Xcode показывает изменения, внесенные с момента последней фиксации
Текстовое представление в нижней части окна с надписью “Введите сообщение о фиксации здесь” — это то место, где вы упоминаете, что изменилось в этой конкретной фиксации. Всегда полезно написать здесь короткую, но ясную причину коммита. Наличие хорошего описания поможет вам позже найти конкретные коммиты в истории вашего проекта.
Запись: Переименуйте ViewController в SearchViewController в качестве сообщения фиксации.
BOS Нажмите кнопку Commit 5 Files – количество будет меняться, в вашем случае это, вероятно, будет 3 файла. Вы увидите, что в навигаторе проекта значки M и R исчезли – по крайней мере, до тех пор, пока вы не внесете следующее изменение.
Навигатор управления версиями теперь должен показывать две фиксации.

Ваш коммит будет занесен в историю проекта
Если вы дважды щелкните конкретный коммит, Xcode покажет вам изменения для этого коммита. Вы будете делать коммиты на регулярной основе, и к концу книги вы станете профессионалом в этом :]
Создайте пользовательский интерфейс
StoreSearch все еще мало что делает. В этом разделе вы создадите пользовательский интерфейс следующим образом — панель поиска поверх табличного представления:

Приложение с панелью поиска и табличным представлением
Несмотря на то, что этот экран использует знакомое табличное представление, это не контроллер табличного представления, а обычный UIViewController
— проверьте определение класса в SearchViewController.swift, если вы не уверены.
Вы не обязаны использовать a UITableViewController
в качестве базового класса для контроллера представления только потому, что у вас есть табличное представление в пользовательском интерфейсе. Для этого приложения я покажу вам, как это сделать.

UITableViewController против UIViewController
Так в чем же разница между контроллером табличного представления и обычным контроллером представления?
Во — первых, UITableViewController
— это подкласс UIViewController
-он может делать все, что может обычный контроллер представления. Тем не менее, он оптимизирован для использования с табличными представлениями и имеет некоторые интересные дополнительные функции.
Например, если ячейка таблицы содержит текстовое поле, нажатие на это текстовое поле вызовет экранную клавиатуру. UITableViewController
автоматически прокручивает ячейки в сторону от клавиатуры, чтобы вы всегда могли видеть, что печатаете.
Вы не получите такое поведение бесплатно с помощью простого UIViewController
— если вам нужна эта функция, вам придется запрограммировать ее самостоятельно.
UITableViewController
у него есть большое ограничение: его основной вид должен UITableView
занимать все пространство экрана, за исключением возможной панели навигации вверху и панели инструментов или панели вкладок внизу.
Если ваш экран состоит только из a UITableView
, то имеет смысл сделать его a UITableViewController
. Но если вы хотите иметь и другие представления (или элементы управления), то более простой UIViewController
вариант-это вариант.
Вот почему вы не используете a UITableViewController
в этом приложении. Помимо табличного представления, в приложении есть еще одно представление-a UISearchBar
. Можно поместить строку поиска внутри табличного представления в виде специального заголовка или сделать так, чтобы строка поиска отображалась как часть панели навигации, но для этого приложения она будет располагаться над табличным представлением.

Настройка раскадровки
- Откройте раскадровку и используйте панель инструментов Interface Builder, чтобы переключиться на iPhone SE (2-е поколение). На самом деле не имеет значения, какую модель iPhone вы выберете здесь, но iPhone SE делает его проще всего следовать вместе с этой книгой.
- Также установите внешний вид на Темный внешнийвид. Опять же, на самом деле не имеет значения, какой внешний вид вы используете, но из последних нескольких приложений вы знаете, что световой режим (который используется по умолчанию) в целом работает нормально. Таким образом, использование темного режима позволяет сразу увидеть любые визуальные/цветовые проблемы, которые могут возникнуть специально для темного режима.
- Перетащите новое табличное представление — не контроллер табличного представления — в существующий контроллер представления.
- Сделайте вид таблицы таким же большим, как основной вид (375 на 667 точек), а затем используйте меню Добавить новые ограничения внизу, чтобы прикрепить вид таблицы к краям экрана так, чтобы left=0, top=0, right=0, bottom=0.
Не забудьте снять флажок Ограничивать поля, если он установлен. Каждый экран имеет 16-точечные поля слева и справа, но вы можете изменить их размер. Когда включена опция “Ограничить поля”, вы закрепляете эти поля. Здесь это никуда не годится; вместо этого вы хотите прикрепить табличное представление к краю экрана.

Создание ограничений для закрепления табличного представления
Это привязывает табличное представление к краям его супервизии. Теперь таблица всегда будет заполнять весь экран, независимо от размера экрана устройства.
- Из библиотеки объектов перетащите строку поиска на контур документа так, чтобы она располагалась чуть ниже табличного представления.
Причина, по которой вы перетаскиваете строку поиска в контур документа, заключается в том, что если вы переместите строку поиска в главное представление, она, скорее всего, будет помещена внутри табличного представления, а не снаружи.
Вы хотите, чтобы строка поиска располагалась на том же уровне, что и табличное представление в контуре документа:

Строка поиска должна быть ниже табличного представления (слева), а не внутри (справа).
Если вы поместили строку поиска в Табличное представление, вы можете взять ее в контуре документа и перетащить под Табличное представление.
Закрепите строку поиска так, чтобы top=0, left=0и right=0 — всего 3 ограничения.

Ограничения для строки поиска
Вам не нужно закреплять нижнюю часть строки поиска или ограничивать ее по высоте. Строки поиска имеют внутреннюю высоту 51 пункт.
BOS В инспекторе атрибутов строки поиска измените текст заполнителя на название приложения, исполнителя, песню, альбом, электронную книгу.
На данный момент дизайн контроллера представления должен выглядеть следующим образом:

Контроллер представления поиска с панелью поиска и табличным представлением
Подключение к розеткам
Вы знаете, что будет дальше — подключение панели поиска и табличного представления к выходам на контроллере представления.
- Добавьте следующие выходы в SearchViewController.swift:
@IBOutlet weak var searchBar: UISearchBar! @IBOutlet weak var tableView: UITableView!
- Вернитесь к раскадровке и подключите панель поиска и табличное представление к соответствующим выходам — Control-перетащите из контроллера представления к объекту, который вы хотите подключить.
- Запустите приложение, чтобы убедиться, что все работает правильно.
Примечание: Возможно, вы заметили, что при запуске приложения внешний вид становится светлым — если, конечно, вы уже не переключили его на симуляторе. Если это вас смущает, обратите внимание, что хотя в Interface Builder для внешнего вида установлено значение Dark, это не означает, что приложение будет работать в темном режиме. Вместо этого приложение будет показывать любой внешний вид, установленный на вашем устройстве или симуляторе.
Поэтому, если вы хотите видеть приложение в темном режиме при его запуске, вам придется явно переключить устройство или симулятор в темный режим.
Делайте поддельные поиски
Прежде чем внедрять поиск в iTunes Store, полезно понять, как UISearchBar
работает этот компонент.
В этом разделе вы получите поисковый запрос из строки поиска и используете его, чтобы поместить некоторые поддельные результаты поиска в табличное представление. Как только это заработает, вы сможете встроить веб-службу. Детские шаги!
BOS Запустите приложение. Если вы нажмете на строку поиска, появится экранная клавиатура — если вы находитесь на симуляторе, вам может потребоваться нажать ⌘K, чтобы открыть клавиатуру, и Shift+⌘K, чтобы разрешить ввод текста с клавиатуры Mac.
Однако он ничего не сделает, если вы введете поисковый запрос и нажмете кнопку Поиска.
Прослушивание строки поиска сделано — как же иначе? — с делегатом. Давайте поместим этот код делегата в расширение.
Добавить делегат панели поиска
- Добавьте следующее в нижнюю часть SearchViewController.swift после заключительной закрывающей скобки:
// MARK: - Search Bar Delegate extension SearchViewController: UISearchBarDelegate { func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { print("The search text is: '\(searchBar.text!)'") } }
Напомним, что вы можете использовать расширения для организации исходного кода. Помещая все UISearchBarDelegate
это в собственное расширение, вы удерживаете его вместе в одном месте и не мешаете остальной части кода.
UISearchBarDelegate
Протокол имеет методsearchBarSearchButtonClicked(_:)
, который вызывается, когда пользователь нажимает кнопку поиска на клавиатуре. Вы реализуете этот метод, чтобы поместить некоторые поддельные данные в таблицу. Позже вы заставите этот метод отправлять сетевой запрос в iTunes Store, чтобы найти песни, фильмы и электронные книги, которые соответствуют тексту поиска, введенному пользователем, но давайте не будем делать слишком много новых вещей сразу!
На данный момент все, что делает новый код, — это выводит поисковый запрос из строки поиска в консоль Xcode.
Совет: я всегда помещаю строки между одинарными кавычками, когда использую
print()
их . Таким образом, вы можете легко увидеть, есть ли в строке конечные или начальные пробелы. Также обратите внимание, чтоsearchBar.text
это необязательно, поэтому нам нужно его развернуть. На самом деле он никогда не вернетсяnil
, так что а!
справится просто отлично.
- В раскадровке Control-перетащите из строки поиска в поисковый контроллер представления или желтый круг вверху. Подключитесь к делегату.
- Запустите приложение, введите что-то в строке поиска и нажмите кнопку Поиска. Теперь панель отладки Xcode должна напечатать введенный вами текст.
Показать поддельные результаты
- Добавьте следующее новое (и пустое) расширение в SearchViewController.swift:
// MARK: - Table View Delegate extension SearchViewController: UITableViewDelegate, UITableViewDataSource { }
Приведенное выше расширение будет обрабатывать все методы делегирования, связанные с табличным представлением. Вы, конечно, могли бы добавить их как два отдельных расширения, если хотите, но я предпочитаю хранить весь код, связанный с делегатом представления таблицы, в одном месте.
Добавление протоколов UITableViewDataSource
and UITableViewDelegate
не было необходимым для предыдущих приложений, потому что вы использовали a UITableViewController
в каждом случае. UITableViewController
уже соответствует этим протоколам под капотом.
SearchViewController
однако это обычный контроллер представления, и поэтому вы должны подключить источник данных и делегировать протоколы самостоятельно.
- В этот момент Xcode должен пожаловаться, что ваш код не соответствует
UITableViewDataSource
протоколу. Измените расширение следующим образом, чтобы добавить минимальный код, необходимый на данный момент:
`extension SearchViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int
) -> Int {
return 0
}
func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
return UITableViewCell()
}
}`
Это просто говорит табличному представлению, что у него еще нет строк. Скоро вы дадите ему некоторые поддельные данные для отображения, но пока вы просто хотите иметь возможность скомпилировать код без ошибок.
Часто вы можете объявить протокол соответствующим протоколу, не реализуя ни одного из его методов — например, это прекрасно работает для UISearchBarDelegate
. Протокол может иметь необязательные и обязательные методы. Если вы забудете требуемый метод, вы обычно увидите, как Xcode жалуется, как вы это делали выше.
- В раскадровке Control-перетащите из табличного представления в Поисковый контроллер представления. Подключитесь к источнику данных. Повторите это, чтобы подключиться к делегату.
Если вам интересно, как вы дважды подключили что — то к delegate
свойству в контроллере представления поиска — сначала к строке поиска , а затем к табличному представлению,-то, как Interface Builder представляет это, немного вводит в заблуждение: выход делегата не из SearchViewController
, а принадлежит тому, что вы контролируете-перетаскиваете. Таким образом, вы подключили SearchViewController
его к delegate
розетке в строке поиска, а также к delegate
розеткам (и dataSource
) в табличном представлении:

Соединения контроллера представления поиска с другими объектами
- Создайте и запустите приложение, чтобы убедиться, что все по-прежнему работает.
Примечание: Заметили ли вы разницу между этими методами источника данных и методами предыдущих приложений? Посмотрите внимательно…
Ответ: У них нет
override
ключевого слова.В предыдущих приложениях
override
это было необходимо, потому что вы имели дело с подклассомUITableViewController
, который уже предоставляет свою собственную версию методовtableView(_:numberOfRowsInSection:)
andtableView(_:cellForRowAt:)
.В этих приложениях вы “переопределяли” или заменяли эти методы своими собственными версиями, отсюда и необходимость в
override
ключевом слове.Однако здесь ваш базовый класс-это не контроллер табличного представления, а обычный
UIViewController
. Такой контроллер представления еще не имеет никаких методов табличного представления, поэтому вы здесь ничего не переопределяете.
Как вы уже знаете, табличное представление нуждается в какой-то модели данных. Начнем с простого Array
.
- Добавьте переменную экземпляра для массива — это идет внутри
class
скобок, а не в любом из расширений.:
var searchResults = [String]()
Метод делегата строки поиска поместит некоторые поддельные данные в этот массив, а затем отобразит их с помощью таблицы.
Замените searchBarSearchButtonClicked(_:)
метод на:
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { searchResults = [] for i in 0...2 { searchResults.append( String( format: "Fake Result %d for '%@'", i, searchBar.text! ) ) } tableView.reloadData() }
Здесь обозначение []
означает, что вы создаете экземпляр нового String
массива и заменяете им содержимое searchResults
свойства. Это делается каждый раз, когда пользователь выполняет поиск. Если уже был предыдущий массив результатов, то он выбрасывается и освобождается. Вы также могли бы написатьsearchResults = [String]()
, чтобы сделать то же самое.
Вы добавляете в массив строку с некоторым текстом. Просто для развлечения это повторяется 3 раза, так что ваша модель данных будет иметь три строки.
Когда вы пишете for i in 0...2
, он создает цикл, который повторяется три раза, потому что закрытый диапазон 0...2
содержит числа 0, 1 и 2. Обратите внимание, что это отличается от полуоткрытого диапазона 0..<2
, который содержит только 0 и 1. Вы также могли бы написать1...3
, но, как вы уже обнаружили, программисты любят начинать отсчет с 0 :]
Вы уже видели строки формата раньше. Спецификатор формата %d
является заполнителем для целых чисел. Точно %f
так же это относится и к числам с плавающей запятой. Заполнитель %@
предназначен для всех других типов объектов, таких как строки.
Последний оператор в методе перезагружает табличное представление, чтобы сделать новые строки видимыми, а это значит, что вы также должны адаптировать методы источника данных для чтения из этого массива.
- Замените методы в расширении делегата табличного представления следующими::
`func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int
) -> Int {
return searchResults.count
}
func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
let cellIdentifier = «SearchResultCell»
var cell = tableView.dequeueReusableCell(
withIdentifier: cellIdentifier)
if cell == nil {
cell = UITableViewCell(
style: .default, reuseIdentifier: cellIdentifier)
}
cell.textLabel!.text = searchResults[indexPath.row]
return cell
}`
Весь вышеприведенный код уже должен быть вам хорошо знаком. Вы просто возвращаете количество строк для отображения на основе содержимого searchResults
массива и создаете UITableViewCell
вручную для отображения строк таблицы.
- Запустите приложение. Если вы что-то ищете, несколько поддельных результатов добавляются в модель данных и отображаются в таблице.
Найдите что-то еще, и табличное представление обновится новыми поддельными результатами.

Приложение показывает поддельные результаты при поиске
Улучшения пользовательского интерфейса
Однако есть некоторые проблемы с текущей реализацией приложения – некоторые из них сразу бросаются в глаза, в то время как другие немного более тонкие.
Вставки содержимого табличного представления
Первое, что вы могли заметить: первая строка табличного представления скрыта под строкой поиска.
Это не так уж странно, потому что вы помещаете строку поиска поверх таблицы, скрывая часть табличного представления ниже.
Вы можете исправить это несколькими различными способами:
- Измените ограничение верхнего макета табличного представления, чтобы оно соответствовало нижнему краю строки поиска.
- Сделайте панель поиска частично полупрозрачной, чтобы содержимое ячеек таблицы просвечивало насквозь.
- Используйте атрибут вставки содержимого табличного представления, чтобы разрешить область, охватываемую строкой поиска.
Вы пойдете с вариантом № 3. К сожалению, атрибут вставки содержимого недоступен в Конструкторе интерфейсов. Таким образом, это должно быть сделано из кода.
- Добавьте следующую строку в конец
viewDidLoad()
in SearchViewController.swift:
tableView.contentInset = UIEdgeInsets(top: 51, left: 0, bottom: 0, right: 0)
Это говорит табличному представлению добавить 51-точечное поле вверху для учета строки поиска.
Теперь первая строка всегда будет видна, и при прокрутке табличного представления ячейки по-прежнему будут находиться под строкой поиска. Неплохо.
Не забудьте переключить внешний вид со светлого на темный (или наоборот), чтобы убедиться, что изменения пользовательского интерфейса работают правильно для обоих режимов внешнего вида.
Отклонить клавиатуру при поиске
Не очень приятно, что клавиатура остается на экране после нажатия кнопки поиска. Он закрывает около половины табличного представления, и нет никакого способа отключить клавиатуру.
- Добавьте следующую строку в начало
searchBarSearchButtonClicked(_:)
:
searchBar.resignFirstResponder()
Это говорит UISearchBar
о том, что он больше не должен прослушивать ввод с клавиатуры. В результате клавиатура будет скрываться до тех пор, пока вы снова не нажмете на строку поиска.
Вы также можете настроить табличное представление таким образом, чтобы отклонить клавиатуру жестом.
- В раскадровке выберите Табличный вид. Перейдите в инспектор атрибутов и установите вид прокрутки — клавиатуру для интерактивного отключения.
Расширить панель поиска до области состояния
Строка поиска имеет слегка резкую линию над ней, чтобы отделить ее от области состояния. Кроме того (гораздо более тонко), если вы прокрутите представление таблицы, вы увидите текст строки через крошечный промежуток между строкой поиска и строкой состояния – это более очевидно в темном режиме, чем в светлом.
Было бы намного лучше, если бы область строки состояния была объединена со строкой поиска. Существует метод делегирования для UINavigationBar``UISearchBar
элементов and, который позволяет элементу указывать свое верхнее положение.
- Добавьте в
SearchBarDelegate
расширение следующий метод::
func position(for bar: UIBarPositioning) -> UIBarPosition { return .topAttached }
Теперь приложение выглядит намного лучше:

Строка поиска “прикреплена” к верхней части экрана

Поскольку вы внесли изменения в пользовательский интерфейс, не забудьте также проверить наличие темного внешнего вида. В данном конкретном случае вам не нужно ничего решать.
Если у вас зоркий глаз, вы можете заметить, что теперь над первой строкой табличного представления и строкой поиска немного больше места-это связано с объединением строки состояния и строки поиска и устранением разделительной линии между ними. Это дополнительное пространство составляет около 4 очков.
Если вы хотите, вы можете настроить вставку содержимого табличного представления на эту величину, чтобы все снова выглядело правильно. Я оставляю это вам в качестве упражнения :]
Документация по API
Если бы вы заглянули в документацию APIUISearchBarDelegate
, то не нашли position(for:)
бы метода, который вы использовали выше.
Вместо этого он является частью UIBarPositioningDelegate
протокола, который UISearchBarDelegate
протокол расширяет — подобно классам, протоколы могут наследовать от других протоколов.
Xcode поставляется с большой библиотекой документации для разработки приложений iOS. В основном все, что вам нужно знать, находится здесь. Научитесь пользоваться браузером документации Xcode — он станет вашим лучшим другом!
Есть несколько способов получить доступ к документации для элемента в Xcode. Существует быстрая справка, которая показывает информацию об элементе под текстовым курсором:

Просто откройте инспектор быстрой справки, и он покажет контекстно-зависимую справку. Наведите текстовый курсор на элемент, о котором вы хотите узнать больше, и инспектор предоставит краткое описание. Вы можете щелкнуть любую из синих текстовых ссылок в сводке, чтобы перейти к полной документации.
Вы также можете получить всплывающую справку. Удерживая нажатой клавишу Option (Alt), наведите курсор на элемент, о котором вы хотите узнать больше. Затем щелкните мышью:

И конечно же, есть полноценное окно документации. Вы можете получить доступ к нему из меню Справка в разделе Документация разработчика. Используйте панель вверху для поиска элемента, о котором вы хотите узнать больше:


Создание модели данных
До сих пор вы добавляли String
объекты в searchResults
массив, но это немного ограничено. Результаты поиска, которые вы получите в iTunes Store, включают название продукта, имя исполнителя, ссылку на изображение, цену покупки и многое другое.
Вы не можете вместить все это в одну строку, поэтому давайте создадим новый класс для хранения этих данных.
Класс SearchResult
- Добавьте новый файл в проект, используя шаблон файла Swift. Назовите новый класс SearchResult.
- Добавьте следующее в SearchResult.swift:
class SearchResult { var name = "" var artistName = "" }
Это добавляет два свойства к новому SearchResult
классу. Через некоторое время вы добавите еще несколько человек.
SearchViewController
Вам нужно изменить searchResults
массив, чтобы он содержал экземпляры SearchResult
.
- В SearchViewController.swift измените объявление свойства:
var searchResults = [SearchResult]()
- Затем измените
for in
цикл в методе делегата строки поиска на:
for i in 0...2 { let searchResult = SearchResult() searchResult.name = String(format: "Fake Result %d for", i) searchResult.artistName = searchBar.text! searchResults.append(searchResult) }
Это создает экземпляр SearchResult
объекта и просто помещает какой-то поддельный текст в его name``artistName
свойства и. Опять же, вы делаете это в цикле, потому что просто иметь один результат поиска сам по себе немного грустно.
- На этом этапе
tableView(_:cellForRowAt:)
все еще ожидается, что массив будет содержать строки. Итак, обновите этот метод:
func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { . . . if cell == nil { cell = UITableViewCell(style: .subtitle, // change reuseIdentifier: cellIdentifier) } // Replace all the code below this point let searchResult = searchResults[indexPath.row] cell.textLabel!.text = searchResult.name cell.detailTextLabel!.text = searchResult.artistName return cell }
Вместо обычной ячейки табличного представления код теперь использует стиль ячейки “субтитры”. Вы помещаете содержимое artistName
свойства в текстовую метку субтитров.
Запустите приложение; оно должно выглядеть так::

Поддельные результаты в ячейке субтитров
Никаких результатов не найдено
Когда вы добавляете функции поиска в свои приложения, вам необходимо справиться со следующими ситуациями:
- Пользователь еще не выполнил поиск.
- Пользователь выполнил поиск и получил один или несколько результатов. Вот что происходит в текущей версии приложения: для каждого поиска вы получите несколько
SearchResult
объектов. - Пользователь выполнил поиск, но никаких результатов не получил. Обычно неплохо прямо сказать пользователю, что результатов не было. Если вы вообще ничего не показываете, пользователь может задаться вопросом, действительно ли был выполнен поиск или нет.
Несмотря на то, что приложение еще не выполняет никакого реального поиска, нет причин, по которым вы не можете подделать и последний сценарий.
Ручка не получает никаких результатов
В защиту хорошего вкуса приложение вернет 0 результатов, когда пользователь будет искать “Джастин Бибер”, просто чтобы вы знали, что приложение может справиться с такой ситуацией.
BOS In searchBarSearchButtonClicked(_:)
, поместите следующий if
оператор вокруг for in
цикла:
. . . if searchBar.text! != "justin bieber" { for i in 0...2 { . . . } } . . .
Изменение здесь довольно простое — вы добавили if
оператор, который предотвращает создание любых SearchResult
объектов, если текст равен "justin bieber"
.
BOS Запустите приложение и выполните поиск “justin bieber” — обратите внимание на все строчные буквы. Стол должен оставаться пустым.
На данный момент вы не знаете, провалился ли поиск или не было никаких результатов. Вы можете улучшить пользовательский опыт, показав вместо этого текст “(Ничего не найдено)”, чтобы пользователь без тени сомнения знал, что результатов поиска не было.
Изменить последнюю часть наtableView(_:cellForRowAt:)
:
if cell == nil { . . . } // New code if searchResults.count == 0 { cell.textLabel!.text = "(Nothing found)" cell.detailTextLabel!.text = "" } else { let searchResult = searchResults[indexPath.row] cell.textLabel!.text = searchResult.name cell.detailTextLabel!.text = searchResult.artistName } // End of new code return cell
Одного этого недостаточно. Когда в массиве ничего нет, searchResults.count
это 0, верно? Но это также означает, что numberOfRowsInSection
будет возвращено 0, а представление таблицы останется пустым — эта строка “Ничего не найдено” никогда не появится.
Изменение наtableView(_:numberOfRowsInSection:)
:
func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int { if searchResults.count == 0 { return 1 } else { return searchResults.count } }
Теперь, если результатов нет, метод возвращает 1 для строки с текстом “(Ничего не найдено)”. Это работает, потому что и numberOfRowsInSection
то, и cellForRowAt
другое проверяется для этой особой ситуации.
BOS Попробуйте это:

Можно надеяться…
Обработка без результатов при запуске приложения
К сожалению, текст “Ничего не найдено” также появляется изначально, когда пользователь еще ничего не искал. Это просто глупо.
Проблема в том, что у вас нет возможности отличить “еще не искали” от “ничего не нашли”. Прямо сейчас вы можете только сказатьsearchResults
, пуст ли массив, но не то, что вызвало это.
Упражнение: Как бы вы решили эту маленькую проблему?
Есть два очевидных решения, которые приходят на ум:
- Измените
searchResults
на необязательный. Если это такnil
, то есть он не имеет значения, то пользователь еще не искал. Это отличается от случая, когда пользователь выполнял поиск и совпадения не были найдены. - Используйте отдельную логическую переменную, чтобы отслеживать, был ли поиск уже выполнен или нет.
Может быть заманчиво выбрать необязательный вариант, но лучше избегать дополнительных вариантов, если это возможно. Они усложняют логику, могут привести к сбою приложения, если вы не развернете их должным образом, и везде требуют if let
инструкций. Опционы, конечно, имеют свое применение, но здесь они на самом деле не нужны.
Итак, мы выберем логическое значение. Но не стесняйтесь вернуться и попробовать опционально самостоятельно, а также сравнить различия. Это будет отличное упражнение!
BOS Все еще в SearchViewController.swiftдобавьте новую переменную экземпляра.:
var hasSearched = false
BOS В методе делегата строки поиска установите для этой переменной значение true
. На самом деле не имеет значения, где вы это делаете, главное, чтобы это произошло до перезагрузки табличного представления.
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { . . . hasSearched = true // Add this line tableView.reloadData() }
И, наконец, изменитеtableView(_:numberOfRowsInSection:)
, чтобы посмотреть на значение этой новой переменной.:
func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int { if !hasSearched { return 0 } else if searchResults.count == 0 { return 1 } else { return searchResults.count } }
Теперь табличное представление остается пустым до тех пор, пока вы не выполните первый поиск чего-либо. Попробуй это сделать! Позже вы увидите гораздо лучший способ справиться с этим с помощью anenum
, и это поразит вас!
Обработка выбора
Еще одна вещь, если вы в данный момент нажмете на строку, она станет выбранной и останется выбранной.
BOS Чтобы исправить это, добавьте следующие методы в расширение делегата табличного представления:
`func tableView(
_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath
) {
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(
_ tableView: UITableView,
willSelectRowAt indexPath: IndexPath
) -> IndexPath? {
if searchResults.count == 0 {
return nil
} else {
return indexPath
}
}`
Этот tableView(_:didSelectRowAt:)
метод просто отменит выбор строки с анимацией, в то время willSelectRowAt
как гарантирует, что вы можете выбирать строки только тогда, когда у вас есть фактические результаты поиска.
Если вы сейчас нажмете на строку (Ничего не найдено), то заметите, что она вообще не выбрана. На самом деле строка все еще может стать серой, если вы ненадолго надавите на нее. Это происходит потому, что вы не изменили selectionStyle
свойство ячейки. Скоро ты это исправишь.
Это хорошее время для фиксации ваших изменений. Перейдите в систему управления версиями ▸ Commit… — или нажмите сочетание клавиш ⌘+Option+C.
Убедитесь, что все измененные файлы выбраны/отмечены в списке слева, просмотрите свои изменения и введите хорошее сообщение о фиксации — что — то вроде “Добавить строку поиска и представление таблицы. Поиск помещает поддельные результаты в таблицу на данный момент”. Нажмите кнопку фиксации, чтобы закончить.
Примечание: Сообщения о фиксации принято писать в настоящем времени. Вот почему я написал “Добавить строку поиска” вместо » Добавил строку поиска”.
Приложение пока не очень впечатляет, но вы заложили фундамент для того, что должно произойти. У вас есть панель поиска и вы знаете, как действовать, когда пользователь нажимает кнопку поиска. Приложение также имеет простую модель данных, состоящую из массива с SearchResult
объектами, и может отображать эти результаты поиска в табличном виде как для светлого, так и для темного внешнего вида.
Вы можете найти файлы проекта для этой главы в строке поиска 32 в папке исходного кода.