Quando si sviluppa una moderna app di utilità, un’interfaccia bellissima all’interno dell’app non basta più. Il vero campo di battaglia per la fidelizzazione degli utenti è la schermata Home.
Quando sviluppavo WaterPaw, il mio tracker d’idratazione minimalista, i widget nativi per iOS erano una funzione irrinunciabile. Gli utenti dovrebbero poter dare un’occhiata alla schermata Home e controllare i propri anelli d’idratazione. Tuttavia, se lavori nell’ecosistema React Native / Expo, conosci il timore storico legato a questo compito: i widget iOS richiedono di scrivere Swift nativo, configurare App Extension personalizzate e gestire i target di firma del codice in Xcode; farlo manualmente rompe del tutto il flusso fluido e gestito di Expo.
Ecco l’architettura pulita e automatizzata che ho usato per rilasciare widget iOS nativi con gli Expo Config Plugin e l’archiviazione condivisa local-first.
La sfida architetturale: la sandbox tra app e widget
Poiché WaterPaw è local-first e attenta alla privacy, i dati restano interamente sul dispositivo. Tuttavia, un widget iOS viene eseguito in un processo separato del sistema operativo: non può leggere il normale AsyncStorage dell’app né i file della sua directory dei documenti.
Per colmare questa distanza senza avviare un backend cloud, occorre configurare un contenitore sandbox di Apple chiamato App Group:
[ WaterPaw React Native App ]
→ (Writes to Shared Suite)
→ [ iOS App Group Container ]
→ [ iOS Native Home Widget ] ← (Reads via Swift)Passaggio 1: scrivere i dati da React Native
Ogni volta che un utente registra dell’acqua nel livello JavaScript, dobbiamo inviare quei dati all’App Group condiviso. Possiamo usare react-native-shared-group-preferences per gestire il collegamento con il bridge nativo:
import SharedGroupPreferences from 'react-native-shared-group-preferences';
const APP_GROUP = 'group.com.yourgroupname';
export const syncDataToWidget = async (currentMl: number, goalMl: number) => {
const dataForWidget = {
progress: currentMl / goalMl,
displayString: `${currentMl} / ${goalMl} ml`,
updatedAt: new Date().toISOString()
};
try {
await SharedGroupPreferences.setItem('WidgetData', dataForWidget, APP_GROUP);
} catch (error) {
console.error('Failed to sync data to iOS App Group Suite', error);
}
};Passaggio 2: leggere i dati condivisi in Swift
Nella directory nativa Swift del widget puoi ora leggere istantaneamente dalla stessa suite dell’App Group usando UserDefaults(suiteName:). Così tutto resta locale, immediato e privato al 100%:
import WidgetKit
import SwiftUI
struct HydrationEntry: TimelineEntry {
let date: Date
let progress: Double
let displayString: String
}
struct WaterPawWidgetEntryView: View {
var entry: Provider.Entry
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("WaterPaw")
.font(.caption)
.foregroundColor(.secondary)
Text(entry.displayString)
.font(.title2)
.bold()
ProgressView(value: entry.progress)
.progressViewStyle(LinearProgressViewStyle(tint: .blue))
}
.padding()
}
}Passaggio 3: automatizzare Xcode con gli Expo Config Plugin 🛠️
Qui gli sviluppatori di solito incontrano problemi. Se esegui npx expo prebuild, Expo cancella completamente qualsiasi modifica manuale o target di Xcode che hai configurato per il widget.
Per mantenere un flusso completamente gestito, isoliamo il codice Swift in una directory targets/ autonoma e usiamo un Config Plugin (come expo-target-extension) per automatizzare l’assemblaggio nativo. Questo istruisce Expo a creare automaticamente il target del widget, collegare le fasi di compilazione Swift e inserire gli entitlement dell’App Group ogni singola volta:
{
"expo": {
"name": "WaterPaw",
"slug": "waterpaw",
"plugins": [
[
"expo-target-extension",
{
"targetName": "WaterPawWidget",
"dir": "targets/widgets",
"entitlements": {
"com.apple.security.application-groups": ["group.com.yourgroupname"]
}
}
]
]
}
}Inserendo questa configurazione in app.json, eliminando la directory ios/ ed eseguendo npx expo prebuild --clean, l’ambiente Xcode nativo viene configurato completamente in modo automatico.
Conclusione: non fare eject, automatizza
Spostare interamente i flussi delle estensioni native nei Config Plugin mantiene il progetto mobile pulito e a prova di futuro. Conservi i cicli di iterazione rapidi di Expo e offri ai tuoi utenti una funzione di sistema iOS profondamente integrata e a latenza zero.
Quando si sviluppano utility, lo sforzo in più per rendere i dati leggibili a colpo d’occhio sulla schermata Home ripaga enormemente in termini di coinvolgimento a lungo termine. Ho usato questa architettura per implementare gli anelli di progresso e il monitoraggio della mascotte all’interno di WaterPaw.
