
Мы все любим разрабатывать мобильные приложения с помощью React Native, который обеспечивает кроссплатформенную интеграцию и широко используется как для iOS, так и для Android. Но знаете, что еще интереснее?
Нетрудно догадаться, что речь идет о приложении на React Native, поддерживающем умные часы.
В этой статье вы узнаете, как интегрировать приложение Apple watchOS с приложением на React Native. Кроме того, мы создадим виджет, который можно установить в циферблате часов в качестве дополнительной функции.
Необходимые условия
- Базовые знания о React Native, Xcode и SwiftUI.
- Xcode с симулятором устройств с поддержкой watchOS.
Настройка среды разработки
Чтобы создать watchOS-приложение для приложения на базе React Native, будем использовать Xcode — интегрированную среду разработки, предоставляемую Apple.
Кроме того, понадобится WatchKit — фреймворк, предоставляемый Apple для создания watchOS-приложений. С помощью WatchKit можно не только создавать watchOS-приложения, но и программировать их для подключения и работы с приложениями на других устройствах Apple.
Установка необходимых зависимостей для watchOS и виджетов
Для реализации связи между iOS- и watchOS-приложениями будем использовать библиотеку react-native-watch-connectivity.
Чтобы установить этот пакет, можете использовать yarn или npm в зависимости от вашего проекта:
npm install react-native-watch-connectivity - save
yarn add react-native-watch-connectivity
Не забудьте установить менеджер зависимостей CocoaPods!
cd ios && pod install && cd ..
Теперь создадим самый базовый экран React Native, на котором будет переменная count с кнопками + и -.
import React, {useState} from 'react';
import {Button, Text, View} from 'react-native';
function App() {
const [count, setCount] = useState(0);
return (
<View
style={{
height: '100%',
flexDirection: 'column',
justifyContent: 'center',
}}>
<View style={{alignItems: 'center'}}>
<Button title="+" onPress={() => setCount(count => count + 1)} />
<Text style={{margin: 24}}>{count}</Text>
<Button title="_" onPress={() => setCount(count => count - 1)} />
</View>
</View>
);
}
Добавление поддержки watchOS-приложения
Теперь, настроив приложение для iOS, начнем разработку watchOS-приложения.
Откройте проект iOS с помощью Xcode. На панели инструментов выберите File -> New -> Target.
Увидев следующее окно, выберите верхнюю вкладку watchOS, а под этой вкладкой выберите App.

Вы увидите еще одно окно, в котором нужно предоставить более подробную информацию о watchOS-приложении.
Укажите имя приложения и идентификатор пакета для приложения watchOS.

Предоставление дополнительной информации для watchOS-приложения
В окне, показанном выше, необходимо указать, есть ли у вас уже существующее iOS-приложение (existing iOS App). В данном случае у нас есть проект iOS-приложения. Поэтому выберем “Watch App for Existing iOS App”, а в выпадающем списке ниже — таргет iOS-приложения.
После нажатия кнопки Finish («Завершение») вы заметите, что в проект Xcode добавится новая папка. В этой папке находятся файлы проекта watchOS.
Разработка UI watchOS-приложения
Теперь приступим к разработке UI (пользовательского интерфейса) для watchOS-приложения. Для начала откройте файл ContentView.swift, который содержит код SwiftUI.
В коде SwiftUI отобразим простой текстовый элемент, который будет показывать значение счетчика.
ContentView должен выглядеть следующим образом:
struct ContentView: View {
var Body: some View {
VStack {
Text("")
}
}
}
Реализация функциональности
Теперь настроим механизм двусторонней связи между приложениями iOS и watchOS.
Отправка сообщений из iOS-приложения в watchOS-приложение
- iOS-приложение в качестве отправителя
Для отправки сообщений в watchOS-приложение потребуется использовать пакет, установленный нами в приложении на базе React Native.
Импортируем функции sendMessage и getReachability из react-native-watch-connectivity. Будем использовать их, чтобы определить доступность часов для отправки переменной count в watchOS-приложение.
Модифицируем действия с кнопками, как показано ниже:
import { sendMessage, getReachability } from 'react-native-watch-connectivity';
...
const [count, setCount] = useState(0);
<Button
title="+"
onPress={async () => {
const isReachable = await getReachability();
if (isReachable) {
const newCount = count + 1;
setCount(newCount);
// Этот метод отправит сообщение в приложение watchOS
sendMessage(
{ count: newCount },
(replyObj: any) => {
console.log("reply from watchOS app: ", replyObj);
},
(error) => {
console.log("error sending message: ", error);
}
);
}
}}
/>;
- watchOS-приложение в качестве приемника
Чтобы настроить watchOS-приложение на прием сообщений, отправляемых из iOS-приложения, создадим новый Swift-файл и назовем его ConnectionHelper.swift. Будем использовать этот класс для перехвата сообщений и событий, исходящих из iOS-приложения.
Для начала создадим класс ConnectionHelper и внутри него настроим сессию и события часов.
class ConnectionHelper: NSObject {
var session: WCSession
init(session: WCSession = .default) {
self.session = session
super.init()
if WCSession.isSupported() {
session.delegate = self
session.activate()
}
}
}
Этот код выдаст ошибку, что заставит нас реализовать необходимые методы в созданном классе для делегата сессии часов.
Чтобы сохранить чистоту кода, создайте расширение класса ConnectionHelper и реализуйте в нем WCSessionDelegate. Расширение также должно реализовать метод, показанный ниже, поскольку это необходимый метод.
Информация: расширения в Swift позволяют добавить новую функциональность в существующий класс.
extension ConnectionHelper: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
}
Этот метод необходим для индикации изменений в состоянии активации WCSession. Он позволяет увидеть, что связь между iOS-приложением и сопряженными с ним часами Apple Watch активна. В противном случае отправляется ошибка.
Следующим шагом будет получение событий и сообщений, отправленных из iOS-приложения в watchOS-приложение, для чего реализуем метод didReceiveMessage в расширении. Теперь расширение ConnectionHelper должно выглядеть следующим образом:
extension ConnectionHelper: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
}
}
С помощью объявленного выше метода обновим значение счетчика. Сначала объявим его внутри класса ConnectionHelper. Теперь этот класс должен выглядеть следующим образом:
class ConnectionHelper: NSObject, ObservableObject {
@Published var count = 0
var session: WCSession
init(session: WCSession = .default) {
self.session = session
super.init()
if WCSession.isSupported() {
session.delegate = self
session.activate()
}
}
}
Мы объявили переменную count, аннотировали ее как Published и реализовали протокол ObservableObject в классе, чтобы отслеживать изменение переменной count.
Теперь нужно реализовать логику, которая будет обновлять значение count каждый раз при получении события от iOS-приложения. Для этого воспользуемся методом didReceiveMessage, определенным в расширении ConnectionHelper.
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
guard let newCount = message["count"] as? Int else { return }
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.count = newCount
replyHandler(["success": true, "count": count])
}
}
Информация: при получении сообщения в watchOS-приложение из приложения iOS можно отправить ответ, используя обратный вызов replyHandler.
Теперь пришло время использовать созданную переменную счетчика в UI watchOS-приложения. Инстанцируем класс ConnectionHelper в ContentView и используем свойство count для отображения его под текстовым элементом.
struct ContentView: View {
@ObservedObject var connectionHelper = ConnectionHelper()
var body: some View {
VStack {
Text("\(connectionHelper.count)")
}
}
}
Односторонняя связь между приложением iOS и приложением watchOS завершена.
Отправка сообщений из приложения watchOS в приложение iOS
- watchOS-приложение в качестве отправителя
Чтобы реализовать отправку сообщений из watchOS-приложения в iOS-приложение, сначала создадим вспомогательную функцию внутри класса ConnectionHelper. Эта функция будет отправлять переменную count в виде сообщения из watchOS- в iOS-приложение, используя созданную ранее сессию часов.
func sendNewCount() {
let messageData = ["count": self.count]
self.session.sendMessage(messageData) { reply in
print("received response :", reply)
} errorHandler: { error in
print("error sending message :", error)
}
}
На следующем этапе модифицируем файл ContentView, чтобы добавить кнопки с текстом + и -, а затем добавим к ним действия. Как показано в приведенном ниже фрагменте кода, изменяем переменную count и используем функцию sendNewCount для отправки ее в iOS-приложение.
var body: some View {
VStack {
Button("+", action: {
connectionHelper.count += 1
connectionHelper.sendNewCount()
})
Text("\(connectionHelper.count)")
Button("-", action: {
connectionHelper.count -= 1
connectionHelper.sendNewCount()
})
}
}
Теперь осталось настроить iOS-приложение, чтобы оно прослушивало сообщения, отправленные из watchOS-приложения.
- iOS-приложение в качестве приемника
Чтобы обрабатывать входящие сообщения, сначала нужно использовать watchEvents для прослушивания сообщений; они импортируются из пакета react-native-watch-connectivity.
import {
...
watchEvents,
...
} from 'react-native-watch-connectivity';
...
const [count, setCount] = useState(0);
useEffect(() => {
watchEvents.on("message", (message) => {
const newCount = message.count as number;
if (newCount !== null && newCount !== undefined) {
setCount(newCount);
}
});
}, []);
Вот теперь все. Запустим оба приложения и попробуем их в деле.

Добавление виджетов в приложение watchOS
Настройка таргета Widget Extension
Чтобы внедрить поддержку виджетов в watchOS-приложение, нужно создать новый таргет в том же проекте Xcode.
Добавим в проект новый таргет под названием Widget Extension (расширение виджета), аналогичный таргету watchOS.

После нажатия кнопки “Next” (“Далее”) надо указать название продукта для таргета-виджета.

После установки таргета Widget Extension в проекте появится новая папка, названная в данном случае CounterWidget. Изначально эта папка будет содержать два Swift-файла: AppIntent и CounterWidget.

Разработка и реализация UI виджета и его функциональности
Прежде чем модифицировать UI, необходимо настроить переменную count в Widget Extension.
Для этого откройте файл AppIntent, объявите переменную currentCount и настройте конструкторы, как показано в приведенном ниже фрагменте кода:
import AppIntents
struct ConfigurationAppIntent: WidgetConfigurationIntent {
...
var currentCount = 0
init() {}
init(currentCount: Int = 0) {
self.currentCount = currentCount
}
}
Чтобы начать разработку UI виджета, откройте файл CounterWidget. Прокрутите вниз до EntryView, который был автоматически сгенерирован Xcode при создании Widget Extension. Создадим очень простой текстовый компонент и внутри него отобразим текущую переменную count, как показано ниже:
struct CounterWidgetEntryView: View {
var entry: Provider.Entry
var body: some View {
Text("Count: \(entry.configuration.currentCount)").multilineTextAlignment(.center)
}
}
Обмен данными между watchOS-приложением и Widget Extension
Теперь нужно отобразить исходную переменную count, объявленную в watchOS-приложении в UI виджета. Но Apple не предоставляет прямого способа обмена данными между watchOS-приложением и виджетом. Поэтому будем хранить переменную count в UserDefaults (это локальное хранилище, предоставляемое Apple).
Подробнее о UserDefaults можно почитать здесь.
Чтобы разделить одно свойство между watchOS-приложением и Widget Extension, нужно добавить новую возможность (capability) под названием App Groups как для watchOS, так и для Widget Extension.
Для этого откройте Project Settings -> выберите таргет, а затем выберите вкладку Signing & Capabilities. Нажмите кнопку + Capability для своего таргета в этом окне.

После добавления App Groups в качестве новой возможности вы увидите этот раздел в показанном ниже окне. Нажмите кнопку + и добавьте идентификатор.

Теперь укажите валидный идентификатор контейнера (который также будет использоваться в качестве имени набора для UserDefaults), где будут храниться данные, используемые несколькими таргетами в проекте.

Примечание: обязательно выполните этот шаг как для watchOS-приложения, так и для Widget Extension.

Файл CounterWidget будет содержать всю логику, управляющую пользовательским интерфейсом (UI) и его функциональностью. Однако главной функцией является функция timeline, которая отвечает за обновление UI виджета через заданные промежутки времени (подробную информацию можно найти здесь).
Теперь настроим механизм обмена данными между watchOS-приложением и Widget Extension.
Объявим новую переменную appCount в классе ConnectionHelper, который объявлен в папке таргета watchOS, и аннотируем ее с помощью AppStorage, используя UserDefaults, как показано ниже.
Кроме того, чтобы сделать переменную appCount функциональной, будем присваивать ей новое значение каждый раз, когда count изменяется.
class ConnectionHelper: NSObject, ObservableObject {
@AppStorage("appCount", store: UserDefaults(suiteName: "group.watchWidget.count")) var appCount = 0
@Published var count = 0 {
didSet {
appCount = count;
WidgetCenter.shared.reloadAllTimelines()
}
}
// ... Остальной код
}
На следующем этапе, чтобы отобразить ее в пользовательском интерфейсе виджета, нам нужно изменить Timeline Provider виджета. Для этого перейдем к предварительно сгенерированной структуре Provider в файле CounterWidget.
Прежде всего, необходимо объявить переменную appCount в области видимости этой структуры и изменить функции провайдера, чтобы они выглядели так, как показано ниже:
struct Provider: AppIntentTimelineProvider {
// Добавьте следующую строку, чтобы использовать переменную appCount, хранящуюся в UserDefaults:
@AppStorage("appCount", store: UserDefaults(suiteName: "group.watchWidget.count")) var appCount = 0
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationAppIntent(currentCount: appCount))
}
func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry {
var config = configuration
config.currentCount = appCount
return SimpleEntry(date: Date(), configuration: config)
}
func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline<SimpleEntry> {
var config = configuration
config.currentCount = appCount
let entry = SimpleEntry(date: Date(), configuration: config)
return Timeline(entries: [entry], policy: .atEnd)
}
func recommendations() -> [AppIntentRecommendation<ConfigurationAppIntent>] {
// Создайте массив со всеми предварительно настроенными виджетами для показа.
[AppIntentRecommendation(intent: ConfigurationAppIntent(currentCount: appCount), description: "Example Widget")]
}
}
Мы успешно настроили Widget Extension приложения watchOS для отображения значения счетчика. Теперь запустим его и опробуем.

Попробуем изменить значение счетчика из приложения iOS.

Полная версия проекта доступна в этом репозитории GitHub.
Заключение
Мы успешно интегрировали приложение на базе React Native с watchOS, а также добавили поддержку виджета для циферблатов часов.
Теперь ваша очередь создавать потрясающие приложения с помощью WatchKit и WidgetKit. Удачи!
Читайте также:
- Frontend Masters: принципы SOLID в React/React Native
- Дизайн системы для Чайников. Создаём стиль для приложения на React Native за 3 простых шага
- Стратегии рендеринга, которые должен знать каждый React-разработчик
Читайте нас в Telegram, VK и Дзен
Перевод статьи Kunjal Soni: React Native App with Apple Watch & Widget Support





