Hallo, ich bin Ayumu Kitabayashi und entwickle allein die iPhone-App minulo. Sie blendet die Reels in Instagram, die Shorts in YouTube und den „Für dich“-Feed von TikTok aus, lässt die Direktnachrichten aber stehen. Die App ist mit Expo / React Native gebaut; für Screen Time nutze ich die Bibliothek react-native-device-activity von Robert Herber und ergänze sie an einigen Stellen mit eigenem Swift-Code.
In diesem Beitrag geht es um ein Detail, das in Apples Dokumentation kaum vorkommt: Man kann aus dem Sperrbildschirm von Screen Time (der „Shield“) heraus nicht einfach die eigene App öffnen. Ich beschreibe, was nicht funktioniert hat, welchen Umweg ich jetzt nehme und welche Nebenwirkungen man dabei beachten muss.
Das Ziel
Der Ablauf sollte so aussehen:
- Die offizielle Instagram-App wird mit Screen Time gesperrt (FamilyControls / ManagedSettings, also ein
shield). - Tippt man auf das Instagram-Symbol, erscheint der Sperrbildschirm. Sein Aussehen kommt aus einer ShieldConfiguration-Extension.
- Der Hauptknopf heißt „In minulo öffnen“. Ein Tipp darauf soll minulo öffnen und direkt zur Instagram-Ansicht ohne Reels springen.
Schritt 1 und 2 sind Standard. Schritt 3 ist das Problem.
1. Eine URL aus ShieldAction öffnen: nichts passiert
Knopfdrücke landen in der ShieldAction-Extension, genauer in ShieldActionDelegate.handle(action:for:completionHandler:). Der naheliegende Gedanke: Dort das eigene URL-Schema öffnen.
let context = NSExtensionContext()
context.open(url) { _ in }
Auf einem echten Gerät passiert damit nichts. Es gibt keinen Fehler und keinen Absturz, die Anfrage wird einfach still ignoriert. Ich gehe deshalb davon aus, dass es aus der Shield-Extension keinen direkten Weg gibt, eine andere App zu öffnen, auch nicht die eigene. Wer das testet, sollte das auf dem Gerät tun und nicht nur im Simulator: Ein stilles „Nichts“ fällt sonst erst spät auf.
2. Der Umweg: eine lokale Mitteilung
Lokale Mitteilungen kann die Extension dagegen verschicken. Also schickt ShieldAction beim Tipp auf den Knopf eine Mitteilung, und ein Tipp auf diese Mitteilung öffnet die App.
sendNotification(contents: [
"title": title, // Text, den die App vorher in die App Group geschrieben hat (in der Sprache des Nutzers)
"body": body,
"userInfo": ["url": url], // die App liest das aus und wechselt den Bildschirm
"identifier": "minulo-open", // gleiche ID, also ersetzt sie die vorige Mitteilung
"interruptionLevel": "active",
], placeholders: [:])
Drei Punkte dazu:
- Texte aus der App Group. Die Extension kennt die Sprache des Nutzers nicht ohne Weiteres. Die App schreibt Titel und Text deshalb vorab lokalisiert in die UserDefaults der App Group, und die Extension liest sie nur.
-
Feste
identifier. Wer mehrmals tippt, bekommt nicht fünf Mitteilungen untereinander, sondern immer nur eine. -
Die App macht fast nichts. Sie reagiert auf den Tipp auf die Mitteilung und öffnet die URL aus
userInfo.url.
Für den Nutzer sind es damit zwei Tipps: Knopf auf dem Sperrbildschirm, dann die Mitteilung, die sofort oben erscheint. Das ist nicht so elegant wie ein direkter Sprung, funktioniert aber zuverlässig.
3. completionHandler nicht sofort aufrufen
Sobald completionHandler aufgerufen wird, kann das System die Extension beenden. Dabei kann es passieren, dass die Mitteilung noch gar nicht registriert ist. Für den Knopf, der die Mitteilung auslöst, warte ich deshalb 0,4 Sekunden, bevor ich antworte:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
completionHandler(.defer)
}
Die Antwort ist .defer, also bleibt der Sperrbildschirm offen. Die kurze Wartezeit sieht der Nutzer daher nicht. Bei allen anderen Knöpfen antworte ich ohne Verzögerung.
4. Ohne Erlaubnis für Mitteilungen ist der Knopf tot
Der ganze Umweg hängt an der Erlaubnis für Mitteilungen. Hat jemand sie nicht erteilt, passiert beim Tipp auf „In minulo öffnen“ überhaupt nichts. Ein Knopf, der nicht reagiert, ist schlimmer als gar kein Knopf.
Deshalb schreibt die App den Status der Erlaubnis als Flag in die App Group. Ist die Erlaubnis nicht da, zeigt der Sperrbildschirm nur noch einen Knopf: „Schließen“. ShieldConfiguration (für das Aussehen) und ShieldAction (für das Verhalten) lesen beide dasselbe Flag, sodass Anzeige und Reaktion zusammenpassen. Ohne diese Abstimmung zeigt die eine Extension noch den Knopf an, während die andere ihn schon ignoriert.
5. ShieldConfiguration kann nicht in die App Group schreiben
Ich wollte zählen, wie oft der Sperrbildschirm angezeigt wird, und habe dafür aus der ShieldConfiguration-Extension in die UserDefaults der App Group geschrieben. Das wurde vom iOS-Sandboxing still ignoriert: In meiner Auswertung kam bei jedem Eintrag „kein Wert“ an. Lesen funktioniert, Schreiben nicht.
Gezählt wird jetzt in ShieldAction, also dann, wenn ein Knopf gedrückt wird. Das misst etwas anderes als „angezeigt“, aber es kommt wenigstens an.
6. Der App-Name fehlt manchmal
Ich setze den Namen der gesperrten App (etwa „Instagram“) in den Text und in die URL des Knopfes ein, damit minulo direkt die richtige Ansicht öffnet. Apple übergibt der Extension diesen Namen aber nicht immer. Mein Code rechnet deshalb damit, dass er leer ist. Je nachdem, wie man Platzhalter ersetzt, landet sonst wörtlich die Zeichenfolge applicationName im Text oder in der URL.
7. Prozent-Kodierung mit eigenem Zeichensatz
Wenn der Name in den Query-String der URL kommt, wirkt CharacterSet.alphanumerics wie die richtige Wahl. Es enthält aber auch Nicht-ASCII-Buchstaben, also etwa Umlaute oder japanische Zeichen, und die werden dann nicht kodiert. Ein Name mit Leerzeichen oder Sonderzeichen kann außerdem dazu führen, dass URL(string:) nil liefert und wieder still nichts passiert. Ich gebe die erlaubten Zeichen deshalb ausdrücklich an: ASCII-Buchstaben, Ziffern und -._~.
let allowed = CharacterSet(charactersIn:
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
Zusammenfassung
- Aus ShieldAction lässt sich die eigene App nicht direkt öffnen. Der Umweg ist eine lokale Mitteilung.
- Vor dem
completionHandlerkurz warten und.deferzurückgeben, damit die Extension nicht vor der Mitteilung beendet wird. - Ohne Erlaubnis für Mitteilungen bleibt nur ein „Schließen“-Knopf. Beide Extensions lesen dasselbe Flag.
- ShieldConfiguration kann aus der App Group lesen, aber nicht schreiben.
- Der App-Name kann fehlen, und die Kodierung braucht einen eigenen ASCII-Zeichensatz.
Zu minulo
Reels, Shorts und den TikTok-Feed auszublenden ist in minulo kostenlos. Zusätzlich kann man die offiziellen Apps mit Screen Time sperren; kostenlos geht das jeweils bis zu zwei Stunden am Stück. Solange eine offizielle App gesperrt ist, kommen ihre Mitteilungen nicht an; so ist minulo gebaut. Die Direktnachrichten bleiben in minulo erreichbar.
Falls ihr an ähnlichen Screen-Time-Funktionen baut und andere Umwege gefunden habt, freue ich mich über einen Kommentar.
Top comments (0)