Узнайте, как глубокие ссылки обрабатываются в новом жизненном цикле приложения 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, и реализовали их в примере приложения.