Pourquoi ajouter une activité en direct en premier lieu ?
L’idée de base est qu’en tant qu’utilisateur, vous n’avez pas besoin d’ouvrir l’application chaque fois que vous avez besoin de vérifier des informations pertinentes cruciales à un moment donné.
Voici un exemple simple du fonctionnement de votre activité en direct. Disons que vous avez commandé un service. En plus du statut général « Votre commande est en route », un widget s’affichera sur votre écran de verrouillage avec toutes les informations nécessaires, telles que le statut de la commande, l’heure de livraison, les détails sur le coursier/chauffeur, etc.
Ajout à la production
De nombreux articles décrivent le processus d’ajout d’une activité en direct à un projet. La seule différence est qu’aucun d’entre eux ne partage l’expérience pratique de l’ajout de la fonctionnalité à un projet en cours. C’est ce dont je veux vous parler dans cet article.
Notre processus a commencé par une démonstration de la fonctionnalité d’activité en direct pour les entreprises. Le but était de « vendre » l’idée. Nous avons créé une application de démonstration librement basée sur les articles pertinents et la documentation officielle. Cela nous a aidés à démontrer assez clairement l’idée de base.

Après une courte période d’approbation, nous avons ensuite intégré la fonctionnalité Live Activity dans le projet inDrive. Nous avons constitué un groupe d’action, comprenant, outre moi, deux autres développeurs iOS.
Lors de l’intégration dans un projet fini, nous nous sommes heurtés à un certain nombre de problèmes qui devaient être résolus :
- XcodeGen et la première exécution – comment ajouter une nouvelle cible à project.yml, en particulier lorsqu’il doit être complet avec la prise en charge de Live Activity ?
- Une compréhension approfondie du fonctionnement de la notification push avec la fonction d’activité en direct.
- Nous avons notre propre système de conception ; comment cela peut-il être utilisé dans l’activité en direct ?
- Comment connecter des ressources de traduction.
- Comment lier l’UDF à l’activité en direct.
XcodeGen et la première exécution
Dans notre projet, nous utilisons XcodeGen pour générer le fichier *.xcodeproj. C’était un peu un défi, car nous n’avions pas utilisé de widgets dans le projet auparavant. Nous avons dû ajouter des modèles spécifiques au fichier project.yml du module principal de notre application. Un drapeau devait être ajouté à la section info de la cible principale :
SupportsLiveActivities: true
Ensuite, nous devons créer un modèle pour le widget Live Activity lui-même :
LiveActivity:
type: app-extension
platform: iOS
info:
path: "${target_name}/SupportingFiles/Info.plist"
properties:
CFBundleDisplayName: ${target_name}
CFBundleShortVersionString: *cfBundleShortVersionString
NSExtension:
NSExtensionPointIdentifier: "com.apple.widgetkit-extension"
settings:
base:
TARGETED_DEVICE_FAMILY: "$(inherited)"
PRODUCT_BUNDLE_IDENTIFIER: ${bundleId}
configs:
debug:
PROVISIONING_PROFILE_SPECIFIER: "match Development ${bundleId}"
CODE_SIGN_IDENTITY: ""
DEBUG_INFORMATION_FORMAT: ""
release:
PROVISIONING_PROFILE_SPECIFIER: "match AppStore ${bundleId}"
CODE_SIGN_IDENTITY: ""
dependencies:
- framework: SwiftUI.framework
implicit: true
- framework: WidgetKit.framework
implicit: true
Et ne pas oublier les éléments suivants dans la cible principale :
dependencies:
- target: LiveActivity
Assurez-vous de spécifier le groupe approprié, qui doit d’abord être lié au profil de provisionnement (également distinct pour ce groupe).
Après avoir parcouru tous les mouvements et terminé avec succès la création tant attendue, votre fonction d’activité en direct doit être présentée pour la première fois. Ici, nous laisserons de côté la partie sur la configuration de l’état du contenu et la définition des propriétés statiques et celles qui doivent être mises à jour. Il est important de se souvenir d’ajouter « main » car vous ne pourrez pas exécuter le widget sans lui.
@available(iOSApplicationExtension 16.1, *)
@main
struct Widgets: WidgetBundle {
var body: some Widget {
LiveActivityWidgetView()
}
}
See below for how to launch Live Activity (iOS 16.1):
public func startWith(_ attributes: Attributes?, state: Attributes.ContentState, pushType: PushType?) {
// 1
guard ActivityAuthorizationInfo().areActivitiesEnabled,
let attributes = attributes,
activity.isNil
else { return }
do {
// 2
activity = try Activity<Attributes>.request(
attributes: attributes,
contentState: state,
pushType: pushType
)
if let token = activity?.pushToken {
let unwrappedToken = token.map { String(format: "%02x", $0) }.joined()
logger.debug("Live Activity token: \(unwrappedToken)")
// 3
props.action.execute(with: .didStartActivityWith(token: unwrappedToken))
} else {
logger.error("Failed Live Activity")
}
// 4
Task {
guard let activity = activity else { return }
for await data in activity.pushTokenUpdates {
let token = data.map { String(format: "%02x", $0) }.joined()
logger.debug("Live Activity token updates: \(token)")
props.action.execute(with: .didPushTokenUpdates(token: token))
}
}
} catch {
logger.error("Failed Live Activity: \(error.localizedDescription)")
props.action.execute(with: .didFailStartLiveActivity(error: error))
}
}
Nous devons nous assurer que l’utilisateur peut afficher l’activité en direct activée dans les paramètres et qu’il n’y a pas d’activités en cours.
1. Lors de la création d’une demande d’activité, nous devons communiquer le pushType
:
.token
– Mettez à niveau l’activité en direct via les notifications push.nil
— Uniquement pendant le cycle de vie de l’application.
2. En cas de mise à niveau via .token
, le pushToken arrivera de manière asynchrone et doit être envoyé au backend. Cela garantira que l’équipe backend sait que nous sommes prêts à recevoir des mises à jour pour l’activité en direct.
3. Le jeton d’activité en direct est mis à jour selon que l’application est en cours d’exécution ou téléchargée. Nous devons garder une trace de cela et le signaler au backend.
Pour mettre à jour Activity via l’application, si vous ne souhaitez pas utiliser les notifications push (iOS 16.1) :
Task {
await activity.update(using: state)
}
Et pour terminer le processus :
Task {
await activity.end(using: state, dismissalPolicy: .immediate)
}
Après avoir appelé le startWith()
méthode, vous devriez voir l’activité en direct sur votre écran. Si vous suivez cette voie, assurez-vous de partager vos cas dans les commentaires.
Une compréhension approfondie du fonctionnement de la notification push avec la fonction d’activité en direct
Initialement, nous avons implémenté la fonctionnalité de base sans notifications push. La fonctionnalité d’activité en direct est conçue pour pouvoir être mise en œuvre sans utiliser de notifications push, uniquement en fonction des statuts intégrés à l’application. Ensuite, nous nous sommes posé ces questions :
- Comment l’activité en direct sait-elle quel push lui est destiné ? C’est vraiment toute la magie Apple sous le capot. Sur la base de la charge utile et de l’ID de la notification push entrante, le système détermine lui-même à quelle activité en direct les informations entrantes se réfèrent.
- Ces notifications push peuvent-elles être capturées dans l’application elle-même ? Au moment de la rédaction, Apple n’a fourni aucune information sur la façon d’attraper une notification push dans une application en cours d’exécution. Les gars et moi avons vérifié si cette technique fonctionnait :
application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
Cette méthode ne sera pas appelée dans le cas des notifications push d’activité en direct.
- Comment tester les notifications push lorsqu’il n’y a toujours pas de backend ? Nous avons conçu nous-mêmes une charge utile appropriée et mis en œuvre le flux de notification push habituel sur l’application de test. Ensuite, nous avons utilisé ceci.
La plupart du travail devait être fait sur le backend. Nous avons envoyé des exemples de demandes à APNS indiquant quelle charge utile le client attendrait. Restez à l’écoute pour plus de détails sur nos efforts de mise en œuvre du backend dans la deuxième partie de l’article.
curl -v \
--header "apns-topic:{Your App Bundle ID}.push-type.liveactivity" \
--header "apns-push-type:liveactivity" \
--header "authorization: bearer $AUTHENTICATION_TOKEN" \
--data \
'{"aps": {
"timestamp":1663300480,
"event": "update",
"content-state": {
"playerOnFirst": "Tony Stark",
"currentLap": 2
},
"alert": {
"title": "Race Update",
"body": "Tony Stark is now leading the race!"
}
}}' \
--http2 \
https://${APNS_HOST_NAME}/3/device/$DEVICE_TOKEN2
{
"aps": {
"timestamp": 1669698850,
"event": "update",
"content-state": {
"rideStatus": "on_ride",
"time": 5
}
}
}
Système de conception propriétaire et activité en direct
Nous n’avions pas utilisé SwiftUI dans le projet auparavant, c’était donc un autre défi pour nous de comprendre. Après un lancement réussi via l’application de démonstration, nous nous sommes mis à aplanir les problèmes mineurs et à essayer les composants communs du système de conception.
1. Les composants de base, tels que les couleurs, les polices et les icônes, n’étaient pas un problème important car à partir d’iOS 15, Apple a ajouté un moyen simple d’utiliser les composants UIKit dans SwiftUI.
Nous sommes allés un peu plus loin et l’avons implémenté nativement. Incidemment, nous avons l’intention de publier les composants de base en Open Source afin que vous puissiez y jeter un coup d’œil.
2. Apple fournit un mécanisme simple pour envelopper UIView dans SwiftUI.View (UIViewRepresentable), qui était censé nous faciliter la vie, mais il s’est avéré quelque chose comme ceci :
Ou comme ceci :

Même si c’était prévu comme ça 🙂
Et comme ça :
Comme nous n’avions pas trouvé d’explication à cela en ligne, nous avons décidé de supprimer certaines fonctionnalités jusqu’à ce que nous passions les composants à SwiftUI natif. Si vous avez des idées…