Начало работы со списками SwiftUI после WWDC 21
Списки - один из наиболее широко используемых компонентов в современных приложениях. Они позволяют приложениям отображать несколько строк данных в столбце. В UIKit списки можно разрабатывать с использованием UITableView
, и разработчикам приходится писать много кода для реализации списков. Однако в SwiftUI списки можно легко реализовать с помощью List
view. Благодаря List
разработчики теперь могут:
- Реализуйте списки с более коротким и понятным кодом.
- Придайте своим спискам лучший вид и удобство с меньшими усилиями.
- Настройте каждую строку так, чтобы она содержала различные виды представлений, например
Image
,Text
,Button
и т. Д. - Используйте тот же код для расширения приложения на другие платформы Apple, такие как iPadOS и macOS.
И что самое интересное, WWDC21 объявила о множестве функций, интегрированных в List
, что делает его не только более удобным для пользователя, но и более удобным для разработчиков. По мере продвижения по этому руководству вы познакомитесь с такими функциями, как:
- Поддержка Markdown в
Text
представлении. - Модификатор
.formatted
, простой и понятный способ форматирования даты и времени. - Модификатор .
refreshable
для обновления списка. .searchable
modifier для включения поиска..swipeActions
inList
.- Использование
.headerProminence
, чтобы сделать заголовкиList
разделов более заметными. - Использование
.listRowSeparator
и.listRowSeparatorTint
для изменения видимости и цвета разделителей строкList
соответственно.
Примечание. Вам понадобится Xcode 13. Это руководство было написано с использованием бета-версии 5. Чтобы запустить этот проект на устройстве iOS, оно должно работать под управлением бета-версии iOS 15.
Начиная
Загрузите стартовый проект из этого репозитория GitHub.
Скомпилируйте и запустите стартовый проект. Вы увидите экран приложения FavMovies
, на котором отображаются мои любимые фильмы, как показано на скриншоте ниже:
Расстроенный? Не волнуйтесь, впереди много изменений.
Теперь посмотрим на структуру папок. Помимо стандартных файлов и папок, вы увидите пять папок: Extensions
, Helpers
, JSON Files
, Models
и Views
.
Extensions
содержит расширение Bundle
, которое обрабатывает декодирование JSON. Вы будете использовать его для декодирования файлов JSON JSON Files
.
Helpers
содержит файл, который вы будете использовать для создания данных для предварительного просмотра.
Точно так же Models
содержит Movie.swift
и Genre.swift
, которые вы будете использовать в качестве моделей для фильмов, которые будут перечислены в FavMovies.
Кроме того, Views
содержит представления приложения. Сейчас он содержит только ContentView.swift
.
Составьте свой список
Пришло время составить список фильмов и отобразить его в FavMovies. Откройте Movie.swift
в Models
, чтобы найти модель Movie
. Он соответствует Identifiable
, поскольку вы будете использовать его для создания списка, в котором каждому Movie
значению присваивается уникальный идентификатор с использованием своего id
. Он также соответствует Codable
, поскольку вы будете использовать эту модель для декодирования JSON.
Наряду с id
вы увидите другие свойства, такие как name
, desc
, releaseDate
и genre
в Movie
.
genre
относится к типу Genre
, который является enum
, соответствующим String
, CaseIterable
и Codable
, и имеет несколько вариантов.
Настройка источника данных для вашего списка
Если вас беспокоит мысль о создании данных фильма, у начинающего есть эта проблема. В JSON Files
вы увидите movies.json
, который вы будете использовать для создания данных о фильмах.
Откройте ContentView.swift
в Views
. Создайте состояние с именем movies
в ContentView
, используя строку, приведенную ниже:
Приведенный выше код создает переменную состояния movies
и сохраняет данные фильма, сгенерированные путем декодирования данных JSON в movies.json
с использованием decode(_:from:)
Bundle
. На этом вы завершили настройку источника данных для списка.
Отображение данных в вашем списке
Пришло время отобразить сведения о фильмах в списке, используя только что настроенный источник данных.
Откройте ContentView.swift
и замените строку Text(“My Favorite Movies”)
в body
из ContentView
следующим текстом:
Приведенный выше код создает List
путем перебора элементов в movies
. Параметр movie
фиксирует значение каждой итерации, а представление Text
в закрытии List
обращается к name
из movie
для отображения имени фильма.
Выполните сборку и запустите, чтобы увидеть экран, подобный приведенному ниже снимку экрана:
FavMovies
теперь отображает список, содержащий названия некоторых фильмов.
Примечание. До WWDC21
List
нуждался в аргументеid
, чтобы иметь возможность перебирать элементы массива.
Но в приведенном выше коде вы использовали синтаксис, введенный после WWDC21, который устранил необходимость передавать аргумент в
id
.
Дополнительная информация о фильме, пожалуйста.
Вы успешно отобразили названия фильмов в FavMovies. Почему бы вам не сделать список более информативным, указав описание и дату выхода фильмов?
Отображение дополнительной информации в каждом элементе списка
Поскольку вам потребуется больше представлений для отображения дополнительной информации в строках, неплохо было бы создать отдельное представление, чтобы весь код не загромождался только ContentView
.
Итак, создайте новое представление SwiftUI, MovieListView.swift
в Views
. В этом представлении будет отображаться список, содержащий имя, описание и дату выпуска каждого фильма. Для этого требуются данные, и вы должны настроить MovieListView
для получения данных из ContentView
.
В MovieListView
добавьте переменную привязки, используя приведенный ниже код:
Как только вы добавите строку, указанную выше, MovieListView_Previews
покажет вам сообщение об ошибке:
В вызове отсутствует аргумент для параметра "фильмы".
Замените MovieListView()
следующей строкой кода:
Поскольку MovieListView
имеет привязку, вы задали ему постоянное значение [Movie]
, вызвав PreviewMovieGenerator.getPreviewMovie()
и сохранив его в массиве. Теперь ошибка должна исчезнуть.
Необходимая настройка почти завершена. Замените Text(“Hello, World!”)
в MovieListView
следующим кодом:
Данный код создает List
, каждая итерация которого содержит VStack
, содержащий Text
представления, отображающие имя, описание и дату выпуска каждого фильма. Кроме того, пара Spacer
представлений используется для увеличения промежутка между Text
представлениями.
Теперь замените представление List
и его содержимое в ContentView
следующим:
Код вызывает инициализатор MovieListView
и передает ему привязку $movies
, так что ContentView
действует как единственный источник истины для MovieListView
. Таким образом, если данные переменной привязки movies
MovieListView
изменяются, они также отражаются в переменной состояния movies
ContentView
.
Теперь тело ContentView
должно выглядеть следующим образом:
Строй и беги. Вы увидите экран со списком, отображающим название, описание и дату выпуска различных фильмов, как на скриншоте ниже:
Украшение элементов списка с помощью Markdown и форматирования даты
Хотя в FavMovies теперь отображаются названия фильмов, описания и даты выпуска, из-за отсутствия надлежащего форматирования текста список не выглядит приятным для глаз. Кроме того, поскольку нет правильного форматирования даты, вы можете увидеть ненужные детали в дате выпуска. Здесь на помощь приходит поддержка Markdown в Text
представлениях и модификатор .formatted
.
WWDC21 объявила о встроенной поддержке SwiftUI для рендеринга Markdown. Он включает поддержку полужирного шрифта, курсива, ссылок, зачеркивания и моноширинного форматирования, а также прост в использовании. Вы можете просто включить синтаксис Markdown в свое Text
представление и отформатировать текст соответствующим образом.
Например:
Приведенный выше код создает VStack
с Text
представлениями, содержащими тексты с синтаксисом Markdown. Результат, который он дает, показан на скриншоте ниже:
Интересная особенность ссылок заключается в том, что они перенаправляют вас на веб-страницу, на которую они ссылаются, когда на них нажимают.
Теперь приступим к кодированию. Откройте MovieListView.swift
и замените VStack
и его содержимое следующим кодом:
Приведенный выше код создает VStack
, содержащий Text
представлений с синтаксисом Markdown. В первом Text
виде название фильма выделено жирным шрифтом, а во втором - его описание выделено курсивом.
Постройте и запустите, чтобы увидеть изменения. Ваш список должен выглядеть примерно так, как показано на скриншоте ниже:
Поддержка Markdown в Text
представлениях упростила и ускорила форматирование текста. Однако есть проблема - дата выхода. Когда дело доходит до даты выпуска, достаточно только отображения месяца, дня и года. Итак, вы должны избавиться от ненужного с помощью .formatted
(а не DateFormatter
, что является более длительным процессом).
Использовать .formatted
просто. Просто рассмотрите приведенный ниже код:
В приведенной выше строке представление Text
отображает текущую дату, отформатировав ее с помощью модификатора .formatted
Date
. .formatted
принимает FormatStyle
в качестве аргумента, а в приведенном выше коде FormatStyle
равно dateTime
. Затем осуществляется доступ к year()
, day()
и month()
из dateTime
, и, следовательно, результат, который вы получаете, показан ниже:
Отлично, вы отформатировали дату в одну строку кода. Прошли те времена, когда мы использовали DateFormatter
и писали несколько строк кода, чтобы получить желаемый формат даты.
В настоящее время название месяца указано в краткой форме, но что, если вы хотите отобразить его полную форму?
Просто передайте аргумент .wide
в month()
. month()
имеет параметр format
типа Date.FormatStyle.Symbol.Month
, и значение этого параметра по умолчанию - .abbreviated
. Передавая .wide
, вы явно заявляете, что хотите получить широкую версию месяца. Попробуйте код, приведенный ниже:
В данном коде аргумент .wide
передается в month()
. Таким образом, результат будет выглядеть как на скриншоте ниже:
Таким образом вы можете отобразить полное название месяца.
Чтобы реализовать это в FavMovies
, откройте MovieListView.swift
и замените Text
представление, отображающее дату выпуска, следующим кодом:
Данный код форматирует дату с использованием .formatted
и отображает только год, день и месяц (в полной форме).
Строй и беги. Вы увидите результат, аналогичный приведенному ниже снимку экрана:
Следовательно, FavMovies
отображает список фильмов с улучшенным текстом и датой.
Добавление "крутого" обновления по запросу
Если вам нравится функция "потянуть для обновления", то вам понравится этот раздел, потому что вы узнаете, как ее реализовать. Кроме того, простота его реализации может поразить вас, потому что все, что вам нужно, - это модификатор .refreshable(action:)
в вашем List
.
Параметр action
- это асинхронное действие, которое SwiftUI выполняет, когда пользователь вытягивает список вниз в своем приложении iOS или iPadOS. Вы используете этот параметр для загрузки новых данных, и во время загрузки новых данных SwiftUI отображает индикатор обновления вверху списка. Легко, правда?
Первое, что нужно настроить - это механизм загрузки новых данных. Если вы откроете JSON Files
, вы увидите другой файл JSON с именем more_movies.json
, содержащий массив данных о новом фильме. Вы расшифруете этот файл, чтобы сгенерировать новые дополнительные данные.
Перейдите к ContentView.swift
и создайте refreshMovieList()
, используя приведенный ниже код:
Данный код создает refreshMovieList()
, который добавляет два массива, сгенерированных декодированием more_movies.json
и movies.json
, и сохраняет результат в movies
.
После этого в ContentView
ниже .navigationTitle(“My Favorite Movies”)
добавьте следующий код:
Вы применили модификатор .refreshable(action:)
к MovieListView
, поскольку его body
возвращает List
, который вы хотите обновить. Таким образом, refreshMovieList()
вызывается из action
закрытия .refreshable(action:)
, когда выполняется обновление по запросу.
Выполните сборку и запустите, чтобы увидеть в действии нажатие для обновления.
Вперед! Вы реализовали функцию "потяните для обновления" в FavMovies
.
Время реализовать поиск
Поиск - одна из самых важных функций в приложениях, отображающих большой объем информации, и SwiftUI значительно упростил ее реализацию.
Использование .searchable(text:placement:prompt:suggestions:)
позволяет легко отображать и выполнять поиск. .searchable(text:placement:prompt:suggestions:)
имеет параметр text
, который является String
привязкой, которая получает поисковый запрос, вводимый пользователем.
Точно так же вы можете использовать параметр placement
, значение которого по умолчанию - .automatic
, чтобы явно указать, где вы хотите отображать поле поиска. Вы можете отобразить его на панели навигации, боковой панели или панели инструментов явно, используя .navigationBarDrawer
, .sidebar
и .toolbar
соответственно. Не стесняйтесь экспериментировать, запуская код на разных платформах и добавляя модификатор .searchable
в различные представления в вашем приложении.
Однако установки .automatic
достаточно в большинстве случаев, поскольку платформа, на которой работает приложение, автоматически определяет его размещение. По умолчанию в iOS, iPadOS и macOS поле поиска размещается на панели инструментов, а в tvOS и watchOS поле поиска размещается внутри его содержимого.
Есть еще один параметр prompt
в .searchable(text:placement:prompt:suggestions:)
, который отображает текст подсказки для пользователя. Он принимает значение String
и не является обязательным.
Наконец, вы можете дополнительно использовать параметр suggestions
для отображения поисковых предложений, когда пользователь начинает поиск.
Отображение панели поиска
Показать строку поиска очень просто. Добавьте модификатор .searchable
к любому виду, который вы хотите отобразить.
Вам необходимо обязательно передать привязку к параметру text
.searchable
. Итак, откройте ContentView.swift
и создайте переменную состояния searchText
, используя приведенный ниже код:
Приведенный выше код инициализирует searchText
пустым String
. Затем поместите .searchable
в NavigationView
из ContentView
, добавив следующую строку кода после закрывающей фигурной скобки NavigationView
:
Таким образом, вы добавили поле поиска с привязкой к searchText
в ContentView
. Когда пользователь начинает вводить текст в поле поиска, введенный текст сохраняется в searchText
.
Создайте и запустите, чтобы найти панель поиска, добавленную в FavMovies.
На показанной записи экрана вы можете увидеть интерактивную панель поиска, добавленную в приложение, просто поместив модификатор .searchable
.
Как заставить поиск работать
До сих пор вы добавляли панель поиска в FavMovies, но она еще не работает. Пора заставить это работать.
Откройте MovieListView.swift
и добавьте привязку searchText
, используя приведенный ниже код:
Вы будете использовать привязку searchText
, созданную выше, для привязки к searchText
из ContentView
. Следовательно, текст, который пользователь вводит во время поиска, также будет передан этому searchText
.
Как только вы добавите привязку, MovieListView_Previews
попросит вас вставить аргумент параметра searchText
. Исправьте это, заменив MovieListView(movies: .constant([PreviewMovieGenerator.getPreviewMovie()]))
следующим:
В коде вы передали постоянное пустое значение String
параметру searchText
инициализатора MovieListView
в его предварительном просмотре.
Но вы все равно увидите еще одну ошибку. Создайте проект, если вы его не видите. В ContentView
для инициализации MovieListView
требуется аргумент в параметре searchText
. Замените MovieListView(movies: $movies)
следующим:
Вы передали привязку searchText
к MovieListView
из ContentView
в коде, показанном выше. Следовательно, searchText
из MovieListView
теперь может получать поисковые запросы, которые пользователь вводит в строку поиска.
Приложение должно знать, когда оно должно начать показывать результаты поиска вместо обычного списка фильмов - очевидно, когда пользователь нажимает на строку поиска и начинает печатать. К счастью, SwiftUI позволяет узнать, когда это произойдет, с помощью переменной среды isSearching
. Добавьте следующую строку под объявлением searchText
в MovieListView
:
Написанный вами код позволяет узнать, ищет пользователь или нет. Если пользователь выполняет поиск, вы увидите представление со списком результатов поиска или представление со списком всех фильмов. Таким образом, у вас будет два разных представления.
В MovieListView
создайте movieRow(movie:)
, используя приведенный ниже код:
movieRow(movie:)
возвращает VStack
, идентичный таковому в List
представлении body
из MovieListView
.
Теперь в MovieListView
создайте переменную searchResults
, используя следующий код:
searchResults
- вычисляемое свойство, которое фильтрует и возвращает все фильмы, имена которых содержат значение searchText
.
Теперь, чтобы отобразить результаты, возвращаемые searchResults
, вам необходимо настроить MovieListView
, заменив его body
следующим:
Давайте рассмотрим это шаг за шагом.
- Вы создали
List
, содержащийif-else
блок. - Если пользователь коснулся строки поиска (
isSearching
этоtrue
) и если пользователь что-то там набрал (searchText
не пусто), код отображает результаты поиска с помощьюForEach
итерации поsearchResults
и отображения представления, возвращенногоmovieRow(movie:)
. - Однако, если пользователь не выполняет операцию поиска, вводя какие-либо данные, код отображает представление, возвращенное
movieRow(movie:)
, выполнив отForEach
доmovies
.
Примечание. Вы использовали
ForEach
блок вList
, потому что использовали одно и то жеList
представление для отображения результатов поиска, а также всех фильмов. Следовательно, вам нужноForEach
для перебора данных.
Кроме того, без использования
ForEach
вList
функция смахивания не работает. Вы научитесь применять его позже в этой статье.
Собери и запусти, чтобы увидеть, как работают поисковые системы.
Ура, вы реализовали поиск в FavMovies.
Удалить? Просто проведите пальцем влево.
Если вы использовали приложение Apple Notes на iOS или iPadOS, вы, должно быть, заметили, что оно предлагает определенные параметры, такие как закрепление, удаление и т. Д., Когда вы проводите пальцем влево или вправо по заметке в списке.
Итак, не будет ли интересно, если вам удастся реализовать эту функцию в FavMovies?
Для реализации действий смахивания SwiftUI предлагает модификатор .swipeActions
. У него три параметра: edge
, allowsFullSwipe
и content
.
Используя параметр edge
, значение по умолчанию которого HorizontalEdge.trailing
, вы можете определить край представления, с которым вы хотите связать действия смахивания.
Установка для параметра allowsFullSwipe
значения true
дает возможность выполнить первое действие с полным смахиванием.
А параметр content
- это место, где вы пишете внешний вид действия смахивания.
Реализация смахивания для удаления
Теперь вы создадите свайп для удаления функции в FavMovies. Откройте MovieListView.swift
и добавьте .swipeActions
после закрывающей фигурной скобки VStack
из movieRow(movie:)
, используя следующий код:
Давайте рассмотрим это шаг за шагом.
- Вы добавили модификатор
.swipeActions
с параметромallowsFullSwipe
, установленным наtrue
. - В параметр
content
вы передалиButton
сrole
, установленным на.destructive
(придавая ему красный цвет), егоaction
настроен на удаление фильма изmovies
,id
которого совпадает сid
текущегоmovie
, а егоlabel
показывает значок корзины с текст,Delete
.
Выполните сборку и запустите, чтобы увидеть, как провести пальцем по экрану для удаления.
Поздравляем, в FavMovies
вы реализовали смахивание для удаления. Поскольку вы не передали аргумент параметру edge
, действие смахивания выполняется на конце.
Выделение заголовков разделов
SwiftUI позволяет создавать списки с разделами и их заголовками, чтобы ваши списки выглядели четко разбитыми по категориям. Эти заголовки разделов нельзя было изменить, пока WWDC21 не представил модификатор .headerProminence
, который может принимать аргумент .increased
. Он также может принимать .standard
аргумент, но .increased
увеличивает размер шрифта и вес заголовков, делая их выделяющимися.
Отображение заголовков разделов
Откройте MovieListView.swift
и создайте переменную movieListWithGenres
, используя следующий код:
Давайте разбираться в коде поэтапно.
- Вы создали
movieListWithGenres
переменную. ForEach
проходит через все случаиGenre
enum
, что стало возможным благодаря его соответствию сCaseIterable
.- В каждой итерации случаев
Genre
создается раздел, и его заголовок устанавливается наString
rawValue
жанра. Следовательно, строкаText(genre.rawValue)
устанавливает заголовок раздела. - Создается еще один блок
ForEach
, который проходит черезmovies
. Каждая итерация сохраняется вmovie
. - Если
genre
изmovie
совпадает с текущимgenre
, фильм помещается в раздел этого жанра.
Теперь замените блок ForEach
и его содержимое в блоке else
из body
из MovieListView
на movieListWithGenres
, чтобы body
выглядел как код, приведенный ниже:
Строй и беги. Приложение будет выглядеть так:
FavMovies теперь отображает список фильмов, сгруппированных по жанрам.
Изменение заметности заголовка
В настоящее время заголовок незаметен. Итак, необходимо это изменить. Откройте MovieListView.swift
. В его body
после переменной movieListWithGenres
в блоке else
добавьте следующую строку:
Вы добавили модификатор .headerProminence
с аргументом .increased
в movieListWithGenres
.
Выполните сборку и запустите, чтобы увидеть экран, похожий на запись экрана ниже:
FavMovies отображает список, содержащий заголовки с повышенной заметностью. Благодаря этому вы сделали свои заголовки заметными.
Игра с разделителем строк
В FavMovies вы, должно быть, заметили горизонтальные линии между строками разделов в списке фильмов. Они называются разделителями строк, а в WWDC21 введены .listRowSeparator()
и .listRowSeparatorTint()
, чтобы разработчики могли отображать или скрывать их и изменять их цвета соответственно.
Если вы любите играть с цветами, сейчас ваше время. Вы воспользуетесь .listRowSeparatorTint()
, чтобы изменить цвет разделителей строк. Откройте MovieListView.swift
и после закрывающей фигурной скобки .swipeActions
в movieRow(movie:)
добавьте следующую строку, чтобы придать красный цвет разделителям строк:
Вы закрасили разделители строк в красный цвет. Скомпилируйте и запустите, чтобы увидеть результат:
Вы также можете использовать другие Color
значения в .listRowSeparatorTint()
.
Но если вы не большой поклонник этих разделителей строк, вы можете скрыть их, используя .listRowSeparator(.hidden)
, где .hidden
- один из случаев Visibility
enum
. Другие случаи включают .visible
и .automatic
.
Чтобы скрыть разделители строк в FavMovies, откройте MovieListView.swift
и после закрывающей скобки .swipeActions
в movieRow(movie:)
добавьте следующую строку:
Вы просто спрятали разделители строк. Выполните сборку и запустите, чтобы увидеть, что разделители строк исчезли.
Таким образом, вы экспериментировали с видимостью и цветом разделителей строк вашего списка в FavMovies
.
Подводить итоги
Из этого руководства вы узнали основы списков SwiftUI. Попутно вы реализовали:
- Добавление поддержки Markdown в
Text
view. - Использование
.formatted
для простого и удобного форматирования даты и времени. - Обновление списка с использованием
.refreshable
. - Реализация поиска с использованием
.searchable
. - Добавление
.swipeActions
вList
. - Использование
.headerProminence
, чтобыList
заголовки разделов привлекали внимание. - Играем с
.listRowSeparator
и.listRowSeparatorTint
, чтобы изменить видимость и цвет разделителей строк дляList
соответственно.
Если вы хотите скачать готовые файлы проекта, пройдите, пожалуйста, в этот репозиторий.
Удачного кодирования!