Mở app của mình từ màn hình chặn Screen Time trên iOS: ShieldAction không mở được URL, nên đi qua thông báo
Mình là Ayumu Kitabayashi, đang tự làm một app iPhone tên là minulo (ẩn Reels của Instagram và Shorts của YouTube nhưng vẫn giữ tin nhắn). App dùng Screen Time (FamilyControls / ManagedSettings) để chặn app chính thức, rồi cho người dùng bấm nút trên màn hình chặn để chuyển sang app của mình. Hóa ra việc "mở app của mình từ màn hình chặn" không làm thẳng được, nên mình ghi lại vài đường vòng cho ai làm điều tương tự.
App viết bằng React Native (Expo); phần Screen Time dựa trên thư viện react-native-device-activity (tác giả Robert Herber) và có thêm code riêng.
Mục tiêu
- Dùng Screen Time để chặn app chính thức như Instagram (shield của ManagedSettings)
- Khi bấm vào icon app chính thức, màn hình chặn hiện ra (giao diện tùy chỉnh bằng ShieldConfiguration)
- Bấm nút chính "Mở trong minulo" thì minulo mở ra, sang Instagram không có Reels
1. Mở URL từ ShieldAction thì không có gì xảy ra
Sự kiện bấm nút đi vào handle(action:for:completionHandler:) của ShieldActionDelegate. Nếu ở đây mở URL scheme của app mình:
let context = NSExtensionContext()
context.open(url) { _ in }
thì trên máy thật không có gì xảy ra. Không có lỗi, yêu cầu bị bỏ qua trong im lặng. Có thể coi như không có cách nào mở trực tiếp app khác (kể cả app của mình) từ extension của màn hình chặn.
2. Gửi một thông báo cục bộ và để người dùng bấm vào
Thay vào đó, trong ShieldAction mình gửi một local notification; bấm vào thông báo thì app mở ra.
sendNotification(contents: [
"title": title, // chuỗi app đã ghi sẵn vào App Group (theo ngôn ngữ)
"body": body,
"userInfo": ["url": url], // app nhận và chuyển màn hình
"identifier": "minulo-open", // cùng id nên thông báo cũ được thay thế
"interruptionLevel": "active",
], placeholders: [:])
Phía app chỉ cần nhận sự kiện bấm thông báo và mở userInfo.url. Kết quả là 2 lần chạm: bấm nút trên màn hình chặn → thông báo hiện ngay phía trên → bấm vào thì minulo mở.
3. Chờ một chút rồi mới gọi completionHandler
Gọi completionHandler xong thì extension có thể bị dọn đi trước khi thông báo kịp đăng ký. Với nút gửi thông báo, mình chờ 0,4 giây rồi mới trả về. Giá trị trả về là .defer (màn hình chặn không đóng), nên chờ cũng không ảnh hưởng.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
completionHandler(.defer)
}
4. Nếu chưa cho phép thông báo thì nút sẽ "chết"
Vì đi qua thông báo, nếu người dùng chưa cho phép thông báo thì bấm nút cũng không có gì xảy ra. Một nút bấm mà không phản hồi là tệ nhất, nên app ghi trạng thái quyền thông báo vào App Group; khi không có quyền, màn hình chặn chỉ có một nút "Đóng". Cả ShieldConfiguration và ShieldAction đều đọc cùng cờ này.
5. ShieldConfiguration không ghi được vào App Group
Mình muốn đếm số lần màn hình chặn hiện ra, nên thử ghi vào UserDefaults của App Group từ extension ShieldConfiguration. Nhưng sandbox của iOS bỏ qua trong im lặng (trên công cụ đo, mọi giá trị nhận được đều trống). Đọc thì được, ghi thì không. Mình chuyển sang đếm ở phía ShieldAction (khi nút được bấm).
6. Có lúc không nhận được tên app
Mình đưa tên app bị chặn (Instagram…) vào chữ trên màn hình chặn và vào URL của nút. Nhưng Apple có lúc không truyền tên cho extension, nên phải thiết kế với giả định có thể rỗng. Tùy cách thay thế chuỗi, có thể chữ "applicationName" bị chèn nguyên văn.
7. Mã hóa ký tự trong URL
Khi đưa tên vào query của URL, nếu dùng CharacterSet.alphanumerics thì các ký tự không phải ASCII (tiếng Nhật, tiếng Việt có dấu…) cũng được tính là "chữ và số" và lọt qua không mã hóa. Mình dùng tập ký tự chỉ gồm chữ số ASCII và -._~.
let allowed = CharacterSet(charactersIn:
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~")
Tóm tắt
- Không mở trực tiếp app của mình từ ShieldAction được → đi qua local notification
- Chờ một chút rồi trả
.deferđể extension không bị dọn trước khi thông báo đăng ký xong - Không có quyền thông báo thì nút vô dụng → khi đó chỉ để nút "Đóng"
- ShieldConfiguration không ghi được vào App Group (đọc thì được)
App trên App Store (tính năng ẩn miễn phí): https://apps.apple.com/vn/app/id6809352229 Danh sách các app và cách ẩn Reels/Shorts: https://github.com/ayumu-kitabayashi/awesome-reels-shorts-blockers
All Rights Reserved