Articolo

Creare widget iOS nativi con Expo gestito

Come ho rilasciato widget nativi per la schermata Home di iOS in Expo gestito usando Config Plugin, App Group e archiviazione condivisa local-first, senza configurazioni manuali in Xcode.

Creare widget iOS nativi con Expo gestito

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.