logo hsb.horse
← Retour au blog

Blog

L’interface Shield iOS nécessite deux extensions, pas une

L’écran Shield affiché par ManagedSettings repose sur deux cibles distinctes : une Action Extension et une Configuration Extension. Voici les bons NSExtensionPointIdentifier et les classes principales à utiliser.

Publié:

Quand on configure des règles de blocage avec ManagedSettings, iOS affiche un écran Shield lorsqu’un site bloqué est ouvert. Il y a un piège dans lequel tombent presque toutes les personnes qui essaient de personnaliser cet écran pour la première fois : une seule extension ne suffit pas. Il en faut deux.

Une extension iOS est un petit processus qui s’exécute séparément de l’application principale. Dans Xcode, on l’ajoute comme une cible supplémentaire. Une Shield Extension est une extension dédiée à l’écran Shield et fonctionne indépendamment de l’application principale.

Shield Action Extension

Elle gère ce qui se passe quand l’utilisateur appuie sur un bouton de l’écran Shield. La classe principale, c’est-à-dire le point d’entrée de l’extension, hérite de ShieldActionDelegate.

import ManagedSettings
class ShieldActionExtension: ShieldActionDelegate {
override func handle(
action: ShieldAction,
for application: ApplicationToken,
completionHandler: @escaping (ShieldActionResponse) -> Void
) {
completionHandler(.close)
}
}

Le point d’extension est com.apple.ManagedSettings.shield-action-service.

Cette extension importe ManagedSettings, pas ManagedSettingsUI. La confusion est fréquente, donc mieux vaut le noter explicitement.

Shield Configuration Extension

Elle sert à personnaliser l’apparence de l’écran Shield. On l’utilise pour adapter le titre, le texte et les libellés des boutons au contexte de l’application. La classe principale hérite de ShieldConfigurationDataSource.

import ManagedSettingsUI
import ManagedSettings
class ShieldConfigurationExtension: ShieldConfigurationDataSource {
override func configuration(
shielding application: Application
) -> ShieldConfiguration {
return ShieldConfiguration(
backgroundBlurStyle: .systemUltraThinMaterial,
title: ShieldConfiguration.Label(text: "集中モード中です", color: .label)
)
}
}

Le point d’extension est com.apple.ManagedSettingsUI.shield-configuration-service. Ici, il faut importer ManagedSettingsUI.

Réglages Info.plist

Il faut définir le bon Extension Point Identifier dans le Info.plist de chaque cible.

Pour l’Action Extension :

<key>NSExtensionPointIdentifier</key>
<string>com.apple.ManagedSettings.shield-action-service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ShieldActionExtension</string>

Pour la Configuration Extension :

<key>NSExtensionPointIdentifier</key>
<string>com.apple.ManagedSettingsUI.shield-configuration-service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ShieldConfigurationExtension</string>

Ne pas oublier la configuration App Group

Les deux extensions et l’application principale doivent partager le même App Group. Les App Groups permettent à plusieurs cibles de partager des données. Sur iOS, les applications ne peuvent normalement pas échanger directement des données, mais des cibles appartenant au même App Group peuvent lire et écrire dans un conteneur partagé.

Dans Xcode, ajoutez App Groups dans Signing & Capabilities pour chaque cible et définissez le même identifiant de groupe. Il faut le faire pour les trois cibles : l’application principale, l’Action Extension et la Configuration Extension. Quand on débute sur iOS, il est facile de croire que tout est configuré alors qu’une cible manque encore. S’il en manque une seule, le partage de données ne fonctionnera pas sur appareil réel.

Ce qu’on peut faire depuis l’écran Shield

ShieldActionDelegate ne peut renvoyer que deux valeurs : .close et .defer. .close ferme l’écran Shield tout en conservant le blocage. .defer sert à déclencher une interaction supplémentaire.

Il n’est pas possible, via cette interface, d’ouvrir directement l’application principale depuis l’écran Shield pour afficher un écran de déverrouillage. C’est aussi une contrainte qui surprend quand on débute en développement iOS. J’ai séparé ce modèle d’implémentation dans un autre article.