Hola, Amigos! Меня зовут Сергей Климович, я Mobile TeamLead агентства заказной разработки Amiga и соавтор телеграм-канала Flutter. Много. На канале мы уже рассказывали про Home Widget для Android, теперь пришло время поговорить про iOS. Я нашел отличную статью по этой теме и решил поделиться с вами переводом. 

С выпуском iOS17 Apple вдохнула новую жизнь в виджеты, представив интерактивные виджеты. Посмотрим, как WidgetKit можно интегрировать в существующий проект Flutter.

Инструкции для этой демонстрации будут максимально общими и простыми для выполнения. Будем использовать Movie Picker, вы можете включить его в любой проект Flutter, который пожелаете. В конце будет краткий обзор того, как может выглядеть стилизованная и полностью реализованная версия этой функции с помощью Movie Picker.

В этом уроке будем использовать пакет flutter_widgetkit. Этот пакет невероятно прост в использовании, но его возможности ограничены созданием только виджетов домашнего экрана iOS. Однако в вашем распоряжении есть несколько других пакетов, таких как home_widget, который позволяет создавать виджеты как для iOS, так и для Android, но по сути все они функционируют одинаково для iOS.

Предисловие

Прежде чем начать, убедитесь, что у вас выполнены следующие условия:

  1. Flutter и Dart установлены и настроены для разработки на вашем компьютере.

  2. Xcode установлен и настроен для разработки iOS, включая инструменты командной строки.

  3. iOS-эмулятор или реальное устройство на iOS.

Настройки Flutter

Шаг 1. Настройте проект Flutter

Если вы еще этого не сделали, создайте новый проект Flutter или используйте существующий. Откройте проект в предпочитаемом редакторе кода. Автор использует  Android Studio.

Шаг 2. Добавьте зависимости

В свой pubspec.yaml добавьте пакет flutter_widgetkit:

dependencies:
  flutter:
    sdk: flutter
  flutter_widgetkit: ^1.0.3 # Use the latest version

Шаг 3. Создание домашней страницы.

Начнем с создания простого домашнего экрана Flutter. Он будет содержать ElevatedButton и простой файл TextField. Все, что нужно от этой страницы, — это принимать текст в качестве входных данных из TextField, и при нажатии кнопки нужно, чтобы наш виджет iOS обновлялся.

var textController = TextEditingController();  
@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SizedBox(
              width: 250,
              child: TextField(
                controller: textController,
                decoration: InputDecoration(
                  hintText: "Enter widget text",
                ),
              ),
            ),
            const SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {},
              child: const Text("Send to Widget"),
            ),
          ],
        ),
      ),
    );
  }

Шаг 4. Создание WidgetData

Чтобы упростить отправку данных в iOS-виджет, создадим простой WidgetData. Нужно отправить экземпляр класса данных в виде строки JSON, которая может быть проанализирована кодом SwiftUI. На данный момент этот класс содержит только одну переменную — строку с именем text, но она может содержать любые произвольные данные, которые вы хотите отправить в свой виджет.

class WidgetData {
  final String text;
  WidgetData(this.text);
  WidgetData.fromJson(Map<String, dynamic> json) : text = json['text'];
  Map<String, dynamic> toJson() => {'text': text};
}

Шаг 5: Отправка информации

Используем WidgetKit.setItem() для отправки данных в виджет. Для этого метода потребуется key, который будет на стороне Xcode, и значение value будет данными, которые нужно отправить. Важно предоставить группу приложений, которую настроим в XCode всего за секунду, используем group.flutterioswidget.

ElevatedButton(
  onPressed: () {
    WidgetKit.setItem(
        'widgetData',
        jsonEncode(WidgetData(textController.text)),
        'group.flutterioswidget');
    WidgetKit.reloadAllTimelines();
  },
  child: const Text("Push to Widget"),
),

Вызываем WidgetKit.reloadAllTimelines(), он перезагрузит временную шкалу и произойдет перерисовка нашего виджета.

На данный момент проект должен выглядеть так
На данный момент проект должен выглядеть так

Настройки XCode

Шаг 6. Откройте проект в Xcode.

В AndroidStudio или VSCode щелкните правой кнопкой мыши папку ios > нажмите «Открыть в Xcode».

Шаг 7: Создайте новую цель

Нажмите Runner, а затем в левом нижнем углу нажмите кнопку «плюс» (+), чтобы создать новую цель.

Найдите «Расширение виджета» и щелкните результат.

Введите любое имя и нажмите «Готово».

Шаг 8. Назначьте группы приложений

Прежде чем перейти к следующему шагу, нужно назначить AppGroups этим целям. В разделе «Targets» выберите цель виджета, которая была недавно создана, и нажмите плюс (+) рядом с надписью «Capability», чтобы добавить новую. Найдите «AppGroup» и выберите первый результат.

Создайте новую группу приложений, нажав кнопку «плюс» (+). Группа приложений должна начинаться с .group, и очень важно, чтобы это соответствовало тому, что вы указали в коде Flutter.

Теперь нужно добавить такую ​​же возможность Runner. Нажмите на Runner в Targets и повторите шаги, описанные выше.

Теперь всё готово, чтобы принимать данные из нашего приложения Flutter!

SwiftUI

Шаг 9. Перейдите к коду виджета.

Перейдите к FlutterIOSWidget (или к тому, в котором есть Provider).

Шаг 10. Создание виджета SwiftUi

В нашем примере будет только одна запись данных, а именно строка с именем text.

struct WidgetData: Decodable, Hashable {
    let text: String
}

Далее нужно создать TimelineEntry, назовем его FlutterEntry:

struct FlutterEntry: TimelineEntry {
    let date: Date
    let widgetData: WidgetData?
}

Теперь нужно создать файл Provider. Если вы знакомы со SwiftUI, в этом нет ничего нового. Обратите внимание, что AppGroup, указанная в sharedDefaults, должна совпадать везде, где она была указана.

В getTimeline также создаём новый экземпляр flutterData.

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> FlutterEntry {
        FlutterEntry(date: Date(), widgetData: WidgetData(text: "Flutter iOS widget!"))
    }

    func getSnapshot(in context: Context, completion: @escaping (FlutterEntry) -> ()) {
        let entry = FlutterEntry(date: Date(), widgetData: WidgetData(text: "Flutter iOS widget!"))
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        let sharedDefaults = UserDefaults.init(suiteName: "group.flutterioswidget")
        let flutterData = try? JSONDecoder().decode(WidgetData.self, from: (sharedDefaults?
            .string(forKey: "widgetData")?.data(using: .utf8)) ?? Data())

        let entryDate = Calendar.current.date(byAdding: .hour, value: 24, to: Date())!
        let entry = FlutterEntry(date: entryDate, widgetData: flutterData)

        let timeline = Timeline(entries: [entry], policy: .atEnd)
        completion(timeline)
    }
}

Ниже показано, что виджет просто принимает текст, который был установлен из виджета Flutter, и отображает его.

struct FlutterIOSWidgetEntryView : View {
    var entry: Provider.Entry
    
    var body: some View {
        Text(entry.widgetData?.text ?? "Tap to set message.")
    }
}

struct FlutterIOSWidget: Widget {
    let kind: String = "FlutterIOSWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            FlutterIOSWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("Flutter iOS Widget")
        .description("This is an example Flutter iOS widget.")
    }
}

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

На этом всё! Полный пример можно посмотреть на github

Надеюсь, вам было интересно! Подписывайтесь на наш телеграм-канал Flutter. Много, который мы ведем командой мобильных разработчиков. Рассказываем про свой личный опыт и делимся советами от софт-скиллов до технических знаний.

Комментарии (0)