«Оживляем» наше iOS-приложение с помощью Live Activity

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

В то время как Android-устройства в целом ушли в направлении простых вырезов в экране под фронтальную камеру или даже подэкранных фронталок, Apple создала совершенно новый пользовательский опыт благодаря своему новому пространству для размещения камеры — «челке» (the notch). Сегодня мы с вами обсудим, как реализовать нечто подобное в iOS.

Виджеты, которые Apple представила в iOS 14, позволяют нам просматривать информацию прямо на наших главных экранах.

Но что, если мы пойдем еще дальше и представим контекстно-зависимую информацию, которая всплывает при необходимости и не задерживается на экране слишком долго? А что, если бы это было реализовано таким образом, чтобы все это гармонично работало с самым большим обновлением для фронтальной панели, которое наши iPhone видели с момента появления челки? Больше никаких «а что, если» — встречайте Dynamic Island.

Мы уже делились своими впечатлениями о Dynamic Island и Live Activities с точки зрения дизайна, и теперь пришло время показать вам, как можно их реализовать.

iPhone 14 Pro и 14 Pro Max — единственные устройства, поддерживающие Dynamic Island, но Live Activity поддерживаются всеми телефонами, на которых может быть установлена iOS 16.1. Итак, давайте разберемся, что необходимо для их интеграции в ваши приложения.

Жизненный цикл Live Activity

Жизненный цикл — это одна из самых важных вещей, которые следует учитывать при создании приложений. Live Activity может находиться в трех состояниях:

  • Еще не запущено

  • Запущено

  • Завершено

Ваше приложение может иметь несколько Live Activity одновременно, поэтому очень важно отслеживать каждое Activity. Перед запуском Activity необходимо указать, какие данные оно должно использовать. Поскольку мы говорим о Live Activity, вы можете догадаться, что некоторые данные будут меняться с течением времени. Данные вашей Live Activity разделены на динамические и статические. Динамические данные могут обновляться с течением времени, а статические — нет.

В качестве примера давайте рассмотрим FormulaAttributes, которая будет содержать оба типа данных:

struct FormulaAttributes: ActivityAttributes {
    public typealias RaceState = ContentState
    // Здесь вы предоставляете динамические данные
    public struct ContentState: Codable, Hashable {
        var driverInFront: String
        var driverTeam: String
    }
    // А здесь вы предоставляете статические данные
    var lastPlaceDriver: String
}

После того, как Activity было запущено, оно может существовать в течение восьми часов, прежде чем оно будет остановлена ​​системой. Восьми часов более чем достаточно для большинства вариантов использования Activity, но иногда у пользователей могут случаться восьмичасовые перелеты, и тогда это восьмичасовое ограничение может вызывать некоторые сложности.

Как только Live Activity запущено и работает, вы можете обновлять динамические данные этого Activity. Следует отметить, что вы не можете обновлять Live Activity посредством вызовов API, как вы можете это делать с другими виджетами. Вместо этого вы должны обновлять его через ваше приложение или push-уведомления.

После того, как вы завершите его, по умолчанию Live Activity по-прежнему будет доступно на экране блокировки еще четыре часа. Вы можете указать, хотите ли вы немедленно уничтожить Live Activity на экране блокировки, в противном случае система сама сделает это за вас через четыре часа.

Убедитесь, что вы обновили Live Activity после его завершения, если вы не сразу удаляете его с экрана блокировки, чтобы отобразить правильное состояние. Таким образом, ваши пользователи будут видеть актуальную информацию и знать, что Live Activity было завершено с последними результатами.

Сколько у Live Activity представлений?

Как упоминалось ранее, вы можете отображать Live Activity на экране блокировки, Dynamic Island или баннере на устройствах, которые не поддерживают Dynamic Island. При разработке для устройств с поддержкой Dynamic Island существует пять представлений:

  • Минимальное (Minimal)

  • Ведущее (Leading)

  • Замыкающее (Trailing)

  • Расширенное (Expanded)

  • Для экрана блокировки (Lock Screen)

Каждое из этих представлений может сильно отличаться от остальных.

Минимальное представление

Как следует из названия, оно должно быть минимальным. Например, это может быть изображение, которое однозначно представляет ваше приложение. Минимальное представление отображается с правой стороны от Dynamic Island (с небольшим отступом), когда одновременно запущены сразу несколько Live Activity. Тап по представлению приведет пользователя прямо в ваше приложение.

Ведущее и замыкающее представления

Ведущее (Leading) представление будет отображаться в левой части Dynamic Island. Вы можете отобразить какую-нибудь актуальную информацию из вашего приложения, связанную с Live Activity. Но старайтесь сильно не перегружать информацией это представление.

Замыкающее (Trailing) представление будет отображаться с правой стороны Dynamic Island. Оно работает точно так же, как ведущее представление, которое вы можете наблюдать на изображении, где представлен пример Live Activity для отслеживания Formula1, над которым мы будем работать в этом примере.

Расширенное представление

Расширенное (Expanded) представление отображается если пользователь зажимает Live Activity. Оно используется для отображения дополнительной информации об Activity. В нашем примере мы будем держать пользователя в курсе, какой гонщик лидирует, а какой движется последним. Как вы можете видеть на изображении ниже, Макс уже на первом месте, а Латифи на последнем — вполне привычное зрелище, если вы следите за F1 в последнее время.

Этот пример довольно прост — вы можете кастомизировать его, настроив представления в определенных областях расширенного представления, и система сама попытается все правильно соразмерить на основе предоставленных вами представлений. На изображении ниже вы можете увидеть, какие области есть в расширенным представлением.

 

Представление для экрана блокировки

Мы будем использовать аналогичный пример, чтобы показать, как это выглядит на экране блокировки. Важным моментом здесь является то, что в этом случае мы можем использовать другое представление. Это позволяет нам повторно использовать существующий пользовательский интерфейс или создавать новые представления в зависимости от ситуации для Dynamic Island и Live Activity отдельно. Ниже представлен пример, демонстрирующий это.

Создаем наше первое Live Activity

Прежде всего, Live Activity доступны в iOS 16.1, и в этом примере я буду использовать Beta Xcode 14.1.

После того, как вы решили, какое приложение вы хотите “оживить”, зайдите в Xcode и в разделе “Targets” найдите знак “+”, где вы сможете добавить расширение с виджетами (Widget Extension).

После того, как вы добавили расширение с виджетами, проследуйте в файл Info.plist вашего приложения, добавьте логический флаг для разрешения “Supports Live Activities” и установите для него значение “YES”.

Теперь мы готовы начинать представление. Во-первых, мы определим FormulaAttributes, который я упоминал выше. Он соответствует протоколу ActivityAttributes, который помогает нам определять динамические данные внутри нашей структуры. Убедитесь, что при создании файла FormulaAttributes.swift вы включаете его как в таргеты и приложения, и виджета. 

Прежде чем мы углубимся в код, хочу предупредить, при запуске Activity ваше приложение должно быть на переднем плане (foreground), в то время как вы можете обновить или завершить его можно уже и в фоновом режиме (background).

Запуск Live Activity

Сначала мы добавим кнопку и ссылку на наше Activity внутри ContentView. Activity будет установлено ​​как опциональное, поскольку мы хотим создать его, когда оно нам понадобится, а ссылку нам нужно сохранить, чтобы мы могли позже обновить правильное Live Activity.

struct ContentView: View {
@State var activity: Activity<FormulaAttributes>?
var body: some View {
    VStack(spacing: 20) {
        Button(action: { activity = startActivity() }, label: { Text("Start Activity") })
    }
}

}

Компилятор жалуется нам, что нет функции startActivity, поэтому мы добавим его в приватное расширение ContentView. Функция startActivity возвращает только что созданное Activity.

private extension ContentView {
func startActivity() -&gt; Activity&lt;FormulaAttributes&gt;? {
    var activity: Activity&lt;FormulaAttributes&gt;?
    let attributes = FormulaAttributes(lastPlaceDriver: "Nicholas Latifi")

    do {
        let contentState = FormulaAttributes.ContentState(
            driverInFront: "Max Verstappen",
            driverTeam: "Red Bull racing"
        )
        activity = try Activity&lt;FormulaAttributes&gt;.request(attributes: attributes, contentState: contentState)
    } catch {
        print(error.localizedDescription)
    }

    return activity
}

}

Теперь, когда мы рассмотрели реализацию нашего Live Activity, мы можем приступить к изучению обновлений Activity.

Обновление Live Activity

Добавьте еще одну кнопку внутри ContentView, точно такую ​​же, как мы добавили, чтобы запустить Activity и вызвать updateActivity. Внутри приватного расширения ContentView добавьте следующую функцию:

func updateActivity() {
Task {
let contentState = FormulaAttributes.ContentState(
driverInFront: "not Lewis Hamilton",
driverTeam: "Mercedes"
)
await activity?.update(using: contentState)
}
}

Функция получит ссылку, которую мы сохранили для нашей Activity, и вызовет функцию .update(using:) с новым содержимым. Она обернута в Task, так как это асинхронная функция.

В этом примере показано, как обновить наше Live Activity из приложения, но, как упоминалось ранее, вы также можете сделать это с помощью push-уведомлений. Код будет немного отличаться от нашего примера — вам нужно будет запустить Live Activity, указав параметр pushType. Чтобы оно могло получать уведомления, вам нужно будет отправить pushToken из вашей Live Activity на сервер.

Завершение Live Activity

Завершение Live Activity также можно выполнить из приложения и с помощью уведомлений. Как я уже упоминал ранее, убедитесь, что вы обновили Live Activity актуальной информацией, прежде чем завершить его.

Добавьте еще одну кнопку в ContentView, которая будет ссылаться на функцию endActivity. Внутри приватного расширения ContentView добавьте следующую функцию:

func endActivity() {
Task {
for activity in Activity<FormulaAttributes>.activities {
await activity.end(dismissalPolicy: .immediate)
}
}
}

Функция endActivity проходит через все запущенные Live Activity с FormulaAttributes и завершает их, указав, что они должны немедленно удалить Activity с экрана блокировки. Как и функция update(using:), end(dismissalPolicy:) также является асинхронной функцией.

Пользовательский интерфейс Live Activity

Поскольку в этом примере мы используем только виджет Live Activity, мы снабдим его аннотацией @main@main. Если вы используете больше виджетов, сгруппируйте их в WidgetBundle. В приведенном ниже коде показан пользовательский интерфейс для виджета Formula1, который использовался для создания предыдущих скриншотов.

@main
struct FormulaActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: FormulaAttributes.self) { context in
// Создайте здесь представление, которое будет отображаться на экране блокировки и на главном экране в виде баннера
// Для устройств, которые не поддерживают Dynamic Island
} dynamicIsland: { context in
        DynamicIsland {
            // Это содержимое будет отображаться, когда пользователь расширяет Island

            DynamicIslandExpandedRegion(.center) {
                VStack {
                    Text("Driver in front is \(context.state.driverInFront) 						
Источник: https://habr.com/ru/company/otus/blog/701104/


Интересные статьи

Интересные статьи

Работая над проектом, у меня возникла задача: возвращать информацию об Exception в формате JSON если client в запросе указывает поддерживаемый им MIME тип application/json используя заголовок Accept. ...
Привет, я Катя, системный аналитик в Ozon. Одна из первых задач, которую мне поручили здесь — проверить качество географических данных. Формально эта задача больше относится к анализу данных, чем к си...
В этой статье я расскажу об одном из методов для устранения дисбаланса предсказываемых классов. Важно уточнить, что многие методы, которые строят вероятностные модели, пр...
Для хранения тестовых данных обычно требуется такой тип данных, который:• допускает объявление нескольких свойств;• имеет минимальное поведение или вообще его не имеет;• позволяет легко с...
Привет Хабр. Сегодня мы продолжим тему SDR-приема и обработки сигналов. Приемом аналогового ТВ я заинтересовался совершенно случайно, после вопроса одного из читателей. Однако это оказалось не...