Узнайте, как глубокие ссылки обрабатываются в новом жизненном цикле приложения SwiftUI, и откройте для себя элегантный способ управления ими.
Обзор
В WWDC20 Apple представила SwiftUI 2.0 и множество новых функций, появившихся в iOS 14. Согласно прошлогоднему докладу, эти новые функции будут поддерживаться только SwiftUI.
Чтобы сделать его еще более привлекательным, Apple выпустила новый жизненный цикл приложения SwiftUI: приложения на 100% написаны на SwiftUI. Просто используя @main
и соблюдая новый протокол, App
мы можем сообщить Xcode, где находится начальная точка нашего приложения. Но что происходит с AppDelegate
UIKit? Этот файл пригодился:
- Настроить сторонние библиотеки при запуске
- Обработка уведомлений
- Обрабатывайте глубокие и универсальные ссылки
В этом руководстве мы сосредоточимся на глубоких ссылках и узнаем, как использовать новые модификаторы SwiftUI для прослушивания этих событий, а также представим аккуратный и понятный подход к их обработке. Обратите внимание: все, что мы говорим о глубоких ссылках, применимо и к универсальным ссылкам. Единственное дополнение - вам нужно поддерживать связанные домены.
Введение: Календарь поездок
В этой статье мы будем использовать пример приложения под названием "Календарь поездок". Это приложение отображает список ваших поездок и позволяет перейти к подробному просмотру. Вы также можете добавить или удалить поездку.
Как мы можем реализовать глубокую ссылку, чтобы пользователь мог перейти к подробному представлению, просто используя код SwiftUI? Мы увидим, как работать с глубокими ссылками в следующих нескольких разделах. Вы можете найти полную реализацию этого руководства в моем репозитории здесь:
Настройка ссылок на контент в приложении
Если вы знакомы с настройкой типов URL-адресов в своем приложении, вы можете пропустить этот раздел. В противном случае продолжайте читать.
Глубинные ссылки - это просто URL-адреса со схемой, однозначно указывающей на ваше приложение. Например, для Календаря поездок мы могли бы придумать такую схему, как tcal
. Таким образом, любой URL с этой схемой запустит наше приложение.
Для этого перейдите в файл проекта в Xcode. Выберите цель, перейдите в «Информация» и прокрутите вниз, пока не найдете «Типы URL». Нажмите кнопку + и добавьте свою схему в раздел «Схемы URL».
Обработка глубоких ссылок
SwiftUI 2.0 поставляется с новым модификатором onOpenURL
. Этот модификатор доступен в любом View
. Хотя вы можете просто реализовать onOpenURL
на любом View
, которому это может понадобиться, это не лучшая идея - вы, скорее всего, будете повторять код для обработки глубинной ссылки, то есть для определения действия, которое должно быть выполнено для конкретной ссылки.
Мое предлагаемое решение - использовать App
для этой обработки и environment
для распространения ссылки и соответствующей реакции на это изменение.
@main struct TripsCalendarApp: App { var body: some Scene { WindowGroup { ContentView() .onOpenURL { // code here } } } }
Разрешение глубоких ссылок
Единственная цель DeepLinker
- обрабатывать глубокие ссылки. Теперь есть два типа глубоких ссылок, которые интересуют Календарь поездок:
home
: просто открыть приложение на главном экранеdetails
: для перехода к подробному просмотру
class Deeplinker { enum Deeplink: Equatable { case home case details(reference: String) } ... }
Теперь, зачем нам обрабатывать URL-адреса на главном экране? Что ж, это экран, который обычно сначала предоставляется любому пользователю при запуске приложения, но что произойдет, если пользователь находится на любом экране вашего приложения, переходит в фоновый режим и нажимает на ссылку на контент? В этом случае нет навигации вперед - только назад. Это то, с чем мы тоже хотим справиться:
func manage(url: URL) -> Deeplink? { guard url.scheme == URL.appScheme else { return nil } guard url.pathComponents.contains(URL.appDetailsPath) else { return .home } guard let query = url.query else { return nil } let components = query.split(separator: ",").flatMap { $0.split(separator: "=") } guard let idIndex = components.firstIndex(of: Substring(URL.appReferenceQueryName)) else { return nil } guard idIndex + 1 < components.count else { return nil } return .details(reference: String(components[idIndex.advanced(by: 1)])) }
Итак, в основном, Trips Calendar Deep Linker проверяет схему URL-адресов, чтобы убедиться, что она соответствует схеме приложения. Если путь URL-адреса не найден, по умолчанию используется домашний экран. В противном случае он ищет параметр в URL-запросе для создания details
ссылки на контент.
Итак, теперь из файла App
мы можем обрабатывать глубокие ссылки и выполнять действия, когда URL-адрес распознается:
.onOpenURL { url in let deeplinker = Deeplinker() guard let deeplink = deeplinker.manage(url: url) else { return } // do something }
Использование ценностей среды
Новым в SwiftUI является модификатор onchange(of:perform:)
. Это очень полезно для наблюдения за изменениями в значениях среды - например, scenePhase
, который заменяет SceneDelegate
уведомления жизненного цикла приложения (фоновые, активные, неактивные и т. Д.). Мы можем использовать тот же подход и создать свои собственные EnvironmentKey
для глубоких ссылок:
struct DeeplinkKey: EnvironmentKey { static var defaultValue: Deeplinker.Deeplink? { return nil } } extension EnvironmentValues { var deeplink: Deeplinker.Deeplink? { get { self[DeeplinkKey] } set { self[DeeplinkKey] = newValue } } }
Таким образом, App
получит глубокую ссылку и внедрит ее как значение среды:
@main struct TripsCalendarApp: App { @State var deeplink: Deeplinker.Deeplink? ... var body: some Scene { WindowGroup { ContentView() ... .environment(\.deeplink, deeplink) .onOpenURL { url in let deeplinker = Deeplinker() guard let deeplink = deeplinker.manage(url: url) else { return } self.deeplink = deeplink } } } }
Обратите внимание, как App
определяет переменную State
и вставляет ее в свой ContentView
как переменную environment
. Это сделает его доступным не только в ContentView
, но и для всех его дочерних элементов:
+ ContentView + CalendarView + List + CalendarView
Например, внутри CalendarView
:
@State var cellSelected: Int? @Environment(\.deeplink) var deeplink var body: some View { List { ForEach((0..<trips.count)) { index in NavigationLink(destination: TripDetailView(trip: trips[index]), tag: index, selection: $cellSelected) { CalendarEntryView(trip: trips[index]) .onTapGesture { cellSelected = index } } } .onChange(of: deeplink, perform: { deeplink in // process deeplink cellSelected = indexInsideDeeplink } }
Примечание. Переменные среды, а также объекты среды доступны только из иерархии представлений. Это означает, что при отправке или представлении другого представления вам нужно будет снова передать значение среды.
Например, в ContetnView
:
.sheet(isPresented: $isNewTripPresented) { NavigationView { NewTripView() .environment(\.deeplink, deeplink) } } }
А потом внутри NextTripView
:
@Environment(\.presentationMode) var presentationMode @Environment(\.deeplink) var deeplink var body: some View { ... .onChange(of: deeplink, perform: { deeplink in guard let _ = deeplink else { return } presentationMode.wrappedValue.dismiss() }) }
Сброс значений среды
После использования значение среды не сбрасывается, как сейчас. Это означает, что если пользователь снова нажмет на ту же ссылку, ничего не произойдет, поскольку среда deepLink
не изменилась.
В файле App
сбросьте ссылку на контент через несколько миллисекунд:
.onOpenURL { url in // proccess deeplink DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { self.deeplink = nil } }
Тестирование глубоких ссылок
Если вы используете настоящее устройство, просто нажмите на самостоятельно созданный URL-адрес, содержащий схему вашего приложения (отправьте себе электронное письмо с этим или запишите URL-адрес в Notes). Для симуляторов вы можете использовать командную строку для отправки глубинной ссылки на ваше приложение. В нашем случае мы можем использовать это:
xcrun simctl openurl booted "tcal://www.trips.calendar.com/details?reference=55091-231959"
Посмотрите это в действии:
Выводы
Новая версия SwiftUI предоставляет гораздо больше инструментов, чем ее предшественница, что является явной попыткой Apple улучшить SwiftUI и побудить разработчиков использовать его. AppDelegate
больше не нужен в SwiftUI.
Мы видели чистую реализацию, которая следует за SRP и избегает повторения кода, что упрощает расширение и изменение. Мы также изучили некоторые из новых модификаторов, представленных в WWDC20, и реализовали их в примере приложения.