logo hsb.horse
← Voltar para o índice do blog

Blog

Implementar o handoff de desbloqueio da Shield Extension para o app principal via App Group

Não é possível iniciar o app principal diretamente a partir de ShieldActionDelegate. Este texto organiza um padrão de handoff em que a extensão grava um pedido de desbloqueio no armazenamento compartilhado do App Group e o app lê depois.

Publicado:

Ao escrever o tratamento dos botões em ShieldActionDelegate, chega um momento em que você quer abrir o app principal dali mesmo e mostrar uma tela de desbloqueio. Só que esse fluxo não pode ser escrito de forma direta. Quando se está começando em iOS, é uma daquelas partes em que entender por que isso não funciona é difícil, então vale começar pelo contexto.

Por que uma Extension não pode abrir o app diretamente

ShieldActionDelegate só pode retornar dois valores: .close e .defer. Em outras plataformas, este seria o ponto para usar um URL scheme e abrir o app, mas no contexto do shield esse caminho não funciona.

É razoável supor que a Apple feche essa rota para preservar a consistência da experiência do shield. Se o shield virar um atalho de fuga, o bloqueio perde o sentido. Se fosse permitido à extension pular direto para o app principal, dependendo da implementação seria possível remover a restrição a partir de dentro da própria restrição. Isso vale para extensions iOS de forma geral: operações diretas sobre o app principal são limitadas.

O padrão de handoff via App Group

A implementação realista é um handoff assíncrono por meio de armazenamento compartilhado.

App Group é o mecanismo que permite ao app principal e à extension compartilhar dados. Normalmente, processos iOS não acessam os dados uns dos outros, mas, quando pertencem ao mesmo App Group, podem ler e escrever em UserDefaults compartilhados ou em um contêiner de arquivos comum.

Na Shield Action Extension, grava-se no contêiner compartilhado do App Group um sinal de que existe um pedido de desbloqueio pendente. Quando o usuário abre o app manualmente depois, o app principal lê esse sinal e inicia o fluxo de desbloqueio.

// Shield Action Extension 内
override func handle(
action: ShieldAction,
for webDomain: WebDomainToken,
completionHandler: @escaping (ShieldActionResponse) -> Void
) {
let defaults = UserDefaults(suiteName: "group.com.example.stoicdns")
defaults?.set(true, forKey: "pendingUnlockRequest")
defaults?.set(Date(), forKey: "unlockRequestedAt")
completionHandler(.close)
}
// メインアプリの起動時 / フォアグラウンド復帰時
func checkPendingUnlock() {
let defaults = UserDefaults(suiteName: "group.com.example.stoicdns")
guard defaults?.bool(forKey: "pendingUnlockRequest") == true else { return }
defaults?.removeObject(forKey: "pendingUnlockRequest")
showUnlockFlow()
}

O shield se fecha com .close. O usuário precisa voltar para a tela inicial e abrir o app manualmente.

Por que salvar também um timestamp

Se o horário do pedido for salvo junto, fica mais fácil escrever a lógica que ignora solicitações antigas. Uma regra como “pedidos com mais de 30 minutos são inválidos” evita que o app mostre uma tela de desbloqueio inesperada muito tempo depois.

Aceitar isso como decisão de UX

Se a expectativa for um fluxo de “um toque do shield direto para a tela de desbloqueio”, esse padrão pode parecer limitado. Mas esse é o alcance permitido pela interface da Apple.

Também dá para olhar por outro ângulo: perceber o shield e então abrir o app de propósito já faz parte do autocontrole. Ter uma etapa extra de fricção pode combinar melhor com o objetivo do produto do que uma saída em um único toque.

Do ponto de vista de implementação, o mais rápido é aceitar essa restrição e seguir em frente.