Как Создать Виджет Экрана Блокировки в iOS?

28 декабря 2022

20 декабря 2022 г. | Разработка | Ли Ках Сенг

 

В iOS 16 Apple провела масштабную модернизацию Экран Блокировки. Одной из самых ожидаемых функций, которая появилась вместе с обновлением, - виджеты Экрана Блокировки. Как следует из названия, виджеты Экрана Блокировки — это виджеты, отображающие легко просматриваемый контент, который постоянно виден на экране блокировки iPhone и iPad.

Поскольку и виджеты Главного Экрана, и виджеты Экрана Блокировки работают на WidgetKit, способ создания виджета Экран Блокировки очень похож на то, как мы создаем виджеты Главного Экрана. Поэтому в этой статье я не буду показывать вам, как настроить и создать виджет с нуля, как это было описано в моей предыдущей статье.

Вместо этого я сосредоточусь на том, как обновить код существующих виджетов Главного Экрана для поддержки виджетов Экрана Блокировки.

С учетом всего сказанного, давайте начнем!

 

Краткое Резюме

В демонстрационных целях давайте обновим View Size Widget, который я создал в предыдущей статье. Вкратце напомню, что View Size Widget — это статический виджет Главного Экрана, который отображает размер вью самого виджета. Вот как это выглядит:

Static Home Screen Widget example in iOS

View Size Widget

Вот полная реализация View Size Widget:

import WidgetKit
import SwiftUI

// MARK: - The Timeline Entry
struct ViewSizeEntry: TimelineEntry {
    let date: Date
    let providerInfo: String
}

// MARK: - The Widget View
struct ViewSizeWidgetView : View {
   
    let entry: ViewSizeEntry

    var body: some View {
        GeometryReader { geometry in
            VStack {
                
                // Show view size
                Text("\(Int(geometry.size.width)) x \(Int(geometry.size.height))")
                    .font(.system(.title2, weight: .bold))
                
                // Show provider info
                Text(entry.providerInfo)
                    .font(.footnote)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.green)
        }
    }
}

// MARK: - The Timeline Provider
struct ViewSizeTimelineProvider: TimelineProvider {
    
    typealias Entry = ViewSizeEntry
    
    func placeholder(in context: Context) -> Entry {
        // This data will be masked
        return ViewSizeEntry(date: Date(), providerInfo: "placeholder")
    }

    func getSnapshot(in context: Context, completion: @escaping (Entry) -> ()) {
        let entry = ViewSizeEntry(date: Date(), providerInfo: "snapshot")
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        let entry = ViewSizeEntry(date: Date(), providerInfo: "timeline")
        let timeline = Timeline(entries: [entry], policy: .never)
        completion(timeline)
    }
}

// MARK: - The Widget Configuration
@main
struct ViewSizeWidget: Widget {
    
    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "com.SwiftSenpaiDemo.ViewSizeWidget",
            provider: ViewSizeTimelineProvider()
        ) { entry in
            ViewSizeWidgetView(entry: entry)
        }
        .configurationDisplayName("View Size Widget")
        .description("This is a demo widget.")
        .supportedFamilies([
            .systemSmall,
            .systemMedium,
            .systemLarge,
        ])
    }
}

 

Если приведенный выше код не имеет для вас никакого смысла, прежде чем продолжить, я настоятельно рекомендую вам сначала прочитать мою статью под названием «Начало работы с WidgetKit».

 

Добавление Виджетов Экрана Блокировки в Ваши Приложения

Добавление Поддерживаемых Семейств Виджетов

В iOS 16 Apple представила 3 новых семейства виджетов, которые представляют 3 разных типа виджетов Экрана Блокировки, а именно: accessoryCircular, accessoryRectangular и accessorInline.

The new widget families for Lock Screen widgets in iOS 16

Новые семейства виджетов для виджетов Экрана Блокировки

Давайте продолжим и сделаем эти 3 новых семейства виджетов поддерживаемыми. Это все, что нам нужно сделать, чтобы добавить поддержку виджета Экрана Блокировки в наше существующее расширение виджета.

struct ViewSizeWidget: Widget {

    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "com.SwiftSenpaiDemo.ViewSizeWidget",
            provider: ViewSizeTimelineProvider()
        ) { entry in
            ViewSizeWidgetView(entry: entry)
        }
        .configurationDisplayName("View Size Widget")
        .description("This is a demo widget.")
        .supportedFamilies([
            .systemSmall,
            .systemMedium,
            .systemLarge,

            // Add Support to Lock Screen widgets
            .accessoryCircular,
            .accessoryRectangular,
            .accessoryInline,
        ])
    }
}

 

Однако, если вы попытаетесь показать виджет на экране блокировки, вы заметите, что UI нашего существующего виджета выглядит не очень в этих новых форм-факторах. Более того, для семейства accessorInline невозможно получить даже размер вью.

Supporting new form factors for Lock Screen widgets in iOS 16

View Size Widget в новых форм-фактора

Чтобы со всем этим разобраться, нам нужно будет создать 3 отдельных вью SwiftUI для каждого из этих семейств виджетов.

 

Реализация UI Виджетов Экрана Блокировки

Предположим, что желаемые UI виджетов Экрана Блокировки следующие:

Lock Screen widgets in iOS 16
Виджет экрана блокировки размера просмотра

Мы можем реализовать каждый из них так:

/// Widget view for `accessoryInline `
struct InlineWidgetView: View {
    
    var body: some View {
        Text("🤷🏻‍♂️ View size not available 🤷🏻‍♀️")
    }
}

/// Widget view for `accessoryRectangular`
struct RectangularWidgetView: View {
    
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                AccessoryWidgetBackground()
                    .cornerRadius(8)
                GeometryReader { geometry in
                    Text("\(Int(geometry.size.width)) x \(Int(geometry.size.height))")
                        .font(.headline)
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                }
            }
        }
    }
}

/// Widget view for `accessoryCircular`
struct CircularWidgetView: View {
    
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                AccessoryWidgetBackground()
                VStack {
                    Text("W: \(Int(geometry.size.width))")
                        .font(.headline)
                    Text("H: \(Int(geometry.size.height))")
                        .font(.headline)
                }
            }
        }
    }
}

 

Обратите внимание, что я использую AccessoryWidgetBackground() в качестве фонового вью для RectangularWidgetView и CircularWidgetView. Это SwiftUI-вью со стандартным внешним видом. Мы можем поместить его в ZStack за контентом виджета, чтобы создать виджеты экрана блокировки с непрозрачным фоном.

Примечание:

Если вы создаете отдельный файл SwiftUI для каждого вью виджета, обязательно назначьте им таргет расширения виджета. Кроме того, обязательно импортируйте модуль WidgetKit, если вы используете AccessoryWidgetBackground() в качестве фона.

Со всеми этими SwiftUI-вью мы можем вернуться к реализации ViewSizeWidget и соответствующим образом обновить его вью:

struct ViewSizeWidgetView: View {

    let entry: ViewSizeEntry

    // Obtain the widget family value
    @Environment(\.widgetFamily)
    var family

    var body: some View {

        switch family {
        case .accessoryRectangular:
            RectangularWidgetView()
        case .accessoryCircular:
            CircularWidgetView()
        case .accessoryInline:
            InlineWidgetView()
        default:
            // UI for Home Screen widget
            HomeScreenWidgetView(entry: entry)
        }
    }
}


В приведенном выше коде обратите внимание, как мы получаем значение среды widgetFamily и используем его для условного возврата правильного SwiftUI-вью для каждого семейства виджетов.

 

Обработка Отсечения Контента

На этом этапе все выглядит нормально при работе на больших устройствах, таких как iPhone 14 Pro Max. Однако, если мы переключимся на устройства с меньшим размером экрана, такие как iPhone 14, вы заметите, что содержимое встроенного виджета обрезается.

iOS Lock Screen widget with truncated content

Виджет блокировки экрана с усеченным содержимым

Чтобы сделать это, мы можем использовать ViewThatFits для предоставления другого, меньшего вью, когда большее - обрезается. Вот как:


struct InlineWidgetView: View {
    
    var body: some View {
        ViewThatFits {
            // Provide 2 subviews for `ViewThatFits` evaluation
            // Prioritizing from top to bottom
            Text("🤷🏻‍♂️ View size not available 🤷🏻‍♀️")
            Text("🤷🏻‍♂️ Nope! 🤷🏻‍♀️")
        }
    }
}

 

ViewThatFits действует как вью, которое будет оценивать свое сабвью в порядке сверху вниз и возвращать первое, которое лучше всего соответствует текущему контексту. Это означает, что в большинстве случаев мы захотим упорядочить самое большое сабвью вверху, за которым следуют другие альтернативные вью, которые меньше.

Параллельное сравнение iPhone 14 и iPhone 14 Pro Max.

Fixing truncated content in iOS Lock Screen widget
Исправление усеченного контента в виджете Экрана Блокировки

Таким образом, мы успешно обновили код наших существующих виджетов Главного Экрана для поддержки виджетов Экрана Блокировки. Браво!

Полный образец кода здесь. Пожалуйста!

Вот и все! Создать виджет Экрана Блокировки на самом деле довольно просто.

Была ли эта статья полезной? Если да, не стесняйтесь ознакомиться с другими моими статьями, связанными с разработкой iOS, здесь. Пожалуйста, следите за мной в Twitter и подписывайтесь на мою рассылку, чтобы не пропустить ни одной из моих будущих статей.

Спасибо за прочтение. 👨🏻‍💻

Содержание