Como posso habilitar Universal Links dentro de um WKWebView no iOS? Os Universal Links já podem ser resolvidos a partir de links elegíveis dentro de um WKWebView. Implementar o WKNavigationDelegate permite que o aplicativo hospedeiro personalize as políticas de roteamento — interceptando destinos próprios do aplicativo, controlando handoffs externos via decidePolicyForNavigationAction e reforçando a segurança no nível de frame.
Na arquitetura de aplicativos iOS, a interceptação de navegação em WKWebView permite que o app personalize políticas de roteamento para Universal Links, esquemas de URL personalizados e destinos web. Ao avaliar metadados de requisição de navegação dentro de um WKNavigationDelegate, o aplicativo pode rotear destinos internos, delegar alvos externos para manipuladores do sistema e aplicar políticas de segurança de frame.
| Termo | Definição | Entidade Relacionada | Intenção de Busca |
|---|---|---|---|
| WebView | Um componente de visualização baseado em WebKit que renderiza conteúdo web interativo dentro de apps iOS. | SDK iOS | Informativa / Comercial |
| Universal Links | Um mecanismo HTTPS padrão que vincula domínios verificados a visualizações de aplicativos nativos iOS. | Roteamento de Deep Link | Técnica / Informativa |
| Custom URL Scheme | Um esquema de URI definido pelo app usado para rotear URLs para um aplicativo nativo. | Deep Linking Móvel | Informativa |
Como o WKWebView e o roteamento de Universal Links interagem no iOS

Ciclos de vida de navegação do WebKit e políticas de roteamento do aplicativo
A Apple implementa Universal Links como um mecanismo de roteamento em nível de sistema suportado no Safari e em ambientes WKWebView. Quando um usuário toca em um link elegível dentro de um WKWebView, a plataforma pode resolver associações de domínio e rotear a execução de acordo com as políticas do sistema operacional.
Embora Universal Links reconhecidos pelo sistema possam transferir a execução para manipuladores nativos, ambientes de navegação embutidos frequentemente exigem uma lógica de roteamento específica do aplicativo. Por exemplo, quando um link aponta para o domínio do próprio aplicativo, desenvolvedores preferem navegar diretamente através de view controllers nativos sem disparar o relançamento do app. Implementar WKNavigationDelegate fornece controle granular sobre a avaliação de links, permitindo que equipes apliquem allowlists personalizadas e roteiem destinos internos de forma previsível.
A barreira na experiência do usuário: quando a navegação in-app aprisiona usuários em loops de redirecionamento
WebViews são frequentemente usados para hospedar microsites promocionais, centros de ajuda, catálogos de parceiros e landing pages dentro de apps iOS. Quando uma página web in-app inclui links destinados a levar usuários para outras seções do próprio app (ex: botões “Visualizar no App”) ou para apps de parceiros, a navegação padrão pode levar a uma renderização redundante:
- Renderização Web Redundante: Em vez de renderizar view controllers nativos, o usuário pode ser apresentado a versões web responsivas de páginas in-app, exigindo autenticações repetidas e degradando a consistência visual.
- Aprisionamento Web: Usuários podem ficar presos em pilhas de navegação web sem formas intuitivas de retornar às interfaces nativas principais.
- Falhas na transição entre apps: Tocar em links que apontam para serviços de terceiros (como apps de navegação, diálogos de compartilhamento social ou gateways de pagamento) exige uma delegação explícita caso esses serviços dependam de esquemas de URL personalizados.
Comparando WKWebView com SFSafariViewController para Handoffs Web-para-App
Ao arquitetar a navegação web in-app no iOS, equipes de engenharia devem escolher entre WKWebView e SFSafariViewController:
SFSafariViewController: Oferece uma interface de navegação do Safari gerenciada pelo sistema, com recursos como preenchimento automático e bloqueio de conteúdo. O aplicativo hospedeiro não pode inspecionar a atividade de navegação ou dados do site, e a personalização de UI é limitada.WKWebView: Um componente de visualização embutido hospedado dentro do processo de UI do app, executando conteúdo web em processos separados do WebKit. Ele permite personalização profunda de UI, pontes de JavaScript e integração de layout personalizado, exigindo a implementação explícita deWKNavigationDelegatepara roteamento.
Como o decidePolicyForNavigationAction intercepta o roteamento do WebKit
O pipeline de política de navegação: entendendo WKNavigationAction, request e decisionHandler
Para controlar o fluxo de navegação dentro de um WKWebView, desenvolvedores atribuem um delegate personalizado que segue a Documentação da Apple sobre WKNavigationDelegate. O ponto principal de interceptação é o método delegado:
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
)
Ações de navegação disparadas por interações do usuário, redirecionamentos programáticos ou submissões de formulários passam por este método. O objeto WKNavigationAction fornece metadados chave:
navigationAction.request.url: AURLde destino solicitada.navigationAction.navigationType: O tipo de gatilho (.linkActivated,.other,.formSubmitted).navigationAction.sourceFrame: Informações sobre o frame que iniciou a requisição.navigationAction.targetFrame: Informações sobre o frame de destino.
O decisionHandler é um closure de conclusão que informa ao WebKit se deve permitir ou cancelar a navegação.
Quando retornar .allow vs .cancel: Controlando o ciclo de vida do WebKit
A WKNavigationActionPolicy passada ao decisionHandler controla o comportamento do WebKit:
.allow: Informa ao WebKit para prosseguir com a navegação solicitada dentro da web view..cancel: Instrui o WebKit a cancelar a navegação. Esta política é executada quando o app intercepta um esquema personalizado, roteia um Universal Link internamente ou delega um alvo externo paraUIApplication.shared.open().
O decisionHandler deve ser invocado exatamente uma vez por ação de navegação.
Avaliando tipos de navegação: Distinguindo cliques (.linkActivated) de redirecionamentos automáticos
WKNavigationAction.navigationType permite distinguir interações explícitas do usuário de scripts automatizados:
.linkActivated: O usuário tocou fisicamente em uma tag de âncora (<a href="...">)..other: Representa navegações programáticas, como atualizações dewindow.location.href, meta refreshes ou chamadas iniciais dewebView.load()..formSubmitted/.formResubmitted: Representa submissões POST ou GET.
Avaliar o navigationType permite que o app aplique uma política de link explícito em nível de aplicação. Para invocações de esquemas personalizados externos, exigir .linkActivated ajuda a evitar que scripts em segundo plano disparem lançamentos de apps externos sem interação.
Manipulando políticas de decisão assíncronas sem Retain Cycles
Quando a validação de rota requer consultas a caches ou validadores de segurança:
- Garanta que o
decisionHandlerseja executado em todos os caminhos de execução, incluindo condições de erro. - Use referências fracas (
[weak self]) dentro de closures de escape para prevenir retain cycles entre oWKWebView, seu delegate e oUIViewControllerpai.
Diferenciando domínios associados do app de Universal Links externos

Gerenciando destinos próprios vs. delegação de link no nível do sistema
Para rotas próprias do aplicativo, evite tentar reentrar no mesmo app através de uma busca de Universal Link redundante. Manipule destinos próprios diretamente através do roteador interno do aplicativo.
Essa separação arquitetural garante uma navegação fluida:
- Domínios Associados do App: Se o host da URL corresponder ao domínio associado do app (
app.example.com), cancele a navegação na web view (decisionHandler(.cancel)), valide o caminho da rota e passe os parâmetros diretamente para o roteador interno do aplicativo. - Aplicativos Externos: Se a URL aponta para um parceiro externo ou esquema personalizado permitido, aplique uma barreira de link explícito (
navigationType == .linkActivated), cancele a navegação e encaminhe a requisição paraUIApplication.shared.open(url).
Arquitetando validação de rotas internas: Extraindo caminhos e parâmetros via AppRouteValidator
Quando uma URL recebida corresponde ao domínio associado, ela deve passar por um validador rigoroso antes de disparar transições de view controller.
O modelo AppRouteValidator:
- Valida o caminho da URL contra uma allowlist de rotas internas suportadas (ex:
/open/,/product/,/promo/,/checkout/). - Extrai parâmetros de query (ex:
id,promo,utm_source). - Reforça restrições de conjuntos de caracteres, limites de tamanho e rejeição de chaves duplicadas.
Manipulando Universal Links de terceiros via delegação ao UIApplication

Quando uma página web dentro de um WKWebView aponta para serviços externos, o aplicativo hospedeiro pode delegar o roteamento para o sistema iOS:
let options: [UIApplication.OpenExternalURLOptionsKey: Any] = [
.universalLinksOnly: true
]
UIApplication.shared.open(url, options: options) { success in
if !success {
// Fallback de política: carregar destino web caso nenhum app nativo manipule o Universal Link
}
}
Gerenciando fallbacks de esquemas personalizados (myapp://) junto com HTTPS
Embora Universal Links HTTPS representem o deep linking padrão, esquemas personalizados (myapp:// ou partnerapp://) continuam comuns em campanhas promocionais.
Em uma implementação unificada de WKNavigationDelegate:
- Esquemas não-HTTP/HTTPS são inspecionados primeiro. Se o esquema coincidir com um protocolo permitido e satisfizer a política de link explícito (
navigationType == .linkActivated), o delegate verifica o host e os parâmetros antes de despachar paraUIApplication.shared.open(). - Esquemas não reconhecidos ou invocações em segundo plano não solicitadas são cancelados imediatamente.
[Usuário Interage com Link no iOS WKWebView]
│
▼
[WKNavigationDelegate: decidePolicyForNavigationAction]
│
┌─────────────┴─────────────┐
▼ ▼
[!action.sourceFrame.isMainFrame] [action.sourceFrame.isMainFrame]
│ │
▼ ▼
[Barreira de Segurança de Subframe] [Inspecionar Esquema & Host de Destino]
├─ HTTP(S) -> .allow │
└─ Não-Web -> .cancel ┌───────────┼───────────┐
(Suprimir Subframe) ▼ ▼ ▼
[Domínio Host] [Web Externa] [Esquema Customizado]
│ │ │
▼ ▼ ▼
[AppRoute] [Verificar Link] [Verificar Link]
├─ Válido -> ├─ Parceiro-> ├─ Válido & Clique->
│ Interno │ Abrir App │ Abrir App
└─ Inválido-> └─ Web -> └─ Inválido/Auto->
.cancel .allow .cancel
Como reforçar a segurança do Main Frame e prevenir Iframe Hijacking

Tratando a navegação web embutida como entrada não confiável: Padrões OWASP
De acordo com o Guia da OWASP para Deep Links Inseguros, todas as URLs e cargas de parâmetros processadas por manipuladores de navegação móvel devem ser tratadas como entrada externa e não confiável.
Páginas web renderizadas dentro de um WKWebView podem carregar scripts de terceiros, banners publicitários ou conteúdo gerado pelo usuário. Se um delegate de navegação encaminhar URLs arbitrárias para view controllers nativos sem validação, parâmetros inesperados podem atingir rotas internas sensíveis.
Isolando navegações do Main Frame de Iframes e novos alvos de janela
De acordo com a Documentação da Apple sobre WKNavigationAction, avaliar a segurança do frame exige verificar o frame de origem:
sourceFrame.isMainFrame == true: A navegação foi iniciada diretamente pelo frame do documento principal.sourceFrame.isMainFrame == false: A navegação foi iniciada por um subframe ou iframe embutido.targetFrame == nil: A navegação solicita um novo alvo de janela (ex: âncora comtarget="_blank").
Para prevenir o Iframe Hijacking — onde um iframe tenta lançar apps externos ou disparar transições nativas em segundo plano — o delegate deve avaliar sourceFrame.isMainFrame. Se a origem for um iframe, permita a navegação HTTP/HTTPS padrão, mas bloqueie esquemas personalizados ou handoffs nativos.
Prevenindo invocações maliciosas de protocolo e flooding de esquemas em segundo plano
Verificações de frame impedem que subframes disparem invocações de esquema externas sem interação:
if !navigationAction.sourceFrame.isMainFrame {
let scheme = url.scheme?.lowercased() ?? ""
if scheme == "http" || scheme == "https" {
decisionHandler(.allow) // Permitir navegação HTTP(S) padrão em subframes
} else {
decisionHandler(.cancel) // Suprimir esquemas não-web vindos de subframes
}
return
}
Reforçando allowlists de caminhos e parâmetros no roteamento do cliente
Ambas as URLs de domínios associados internos e esquemas customizados devem passar por modelos validadores antes da execução:
- Allowlisting de Prefixos de Caminho: Reforce prefixos de rota aprovados, rejeitando caminhos arbitrários.
- Filtragem de Chaves de Query: Descarte chaves de consulta inesperadas para prevenir poluição de parâmetros.
- Restrições de tipo e tamanho: Restrinja valores a conjuntos de caracteres alfanuméricos e imponha limites de tamanho (
caracteres).
Implementação de WKNavigationDelegate em Swift para produção
Estruturando o CustomWebViewController e a arquitetura de delegate em Swift
Um controlador WKWebView de produção coordena configuração web, avaliação de políticas, roteamento interno e delegação externa. A implementação encapsula regras de validação dentro de classes dedicadas (AppRouteValidator e CustomSchemeValidator).
Implementando modelos AppRouteValidator e CustomSchemeValidator
Os modelos validadores impõem uma segurança rigorosa de fail-closed:
AppRouteValidatorvalida domínios associados internos, verificando prefixos de caminho e higienizando parâmetros de query.CustomSchemeValidatorverifica esquemas personalizados autorizados, hosts permitidos e higieniza valores de query.
O OpoInstall pode ser integrado junto a uma camada de roteamento própria do aplicativo para atribuição e recuperação de parâmetros adiados. Consulte a documentação de integração do SDK para guias abrangentes.
A implementação técnica abaixo demonstra como configurar um WKNavigationDelegate seguro em Swift:
// iOS: CustomWebViewController com WKNavigationDelegate rigoroso e Segurança de Frame
import UIKit
import WebKit
struct ValidatedAppRoute {
let path: String
let queryParams: [String: String]
}
// 1. Validador para Domínio Associado do App (Rotas Internas)
class AppRouteValidator {
private static let allowedPrefixes = ["/open/", "/product/", "/promo/", "/checkout/"]
private static let allowedQueryKeys = Set(["target", "id", "promo", "utm_source"])
static func validate(url: URL) -> ValidatedAppRoute? {
let path = url.path
guard allowedPrefixes.contains(where: { path.hasPrefix($0) }) else {
return nil
}
var sanitizedParams: [String: String] = [:]
var seenKeys = Set<String>()
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems {
let validChars = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
for item in queryItems {
guard allowedQueryKeys.contains(item.name), !seenKeys.contains(item.name) else { return nil }
seenKeys.insert(item.name)
let value = item.value ?? ""
if value.count <= 64 && value.rangeOfCharacter(from: validChars.inverted) == nil {
sanitizedParams[item.name] = value
} else {
return nil
}
}
}
return ValidatedAppRoute(path: path, queryParams: sanitizedParams)
}
}
// 2. Validador para Esquemas Customizados Externos (myapp://)
class CustomSchemeValidator {
private static let allowedSchemes = Set(["myapp"])
private static let allowedHosts = Set(["open", "product", "event"])
private static let allowedPathPrefixes = ["/detail/", "/view/", "/main/"]
private static let allowedQueryKeys = Set(["target", "id", "promo", "utm_source"])
static func validate(url: URL) -> URL? {
guard let scheme = url.scheme?.lowercased(), allowedSchemes.contains(scheme) else {
return nil
}
guard let host = url.host?.lowercased(), allowedHosts.contains(host) else {
return nil
}
let path = url.path
if !path.isEmpty && !allowedPathPrefixes.contains(where: { path.hasPrefix($0) }) {
return nil
}
var seenKeys = Set<String>()
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems {
let validChars = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
for item in queryItems {
guard allowedQueryKeys.contains(item.name), !seenKeys.contains(item.name) else { return nil }
seenKeys.insert(item.name)
let value = item.value ?? ""
if value.count > 64 || value.rangeOfCharacter(from: validChars.inverted) != nil {
return nil
}
}
}
return url
}
}
// 3. UIViewController que hospeda o WKWebView com Interceptação Segura
class CustomWebViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
private let hostAssociatedDomain = "app.example.com"
private let allowedExternalPartnerHosts = Set(["partner.example.com"])
override func viewDidLoad() {
super.viewDidLoad()
let configuration = WKWebViewConfiguration()
webView = WKWebView(frame: view.bounds, configuration: configuration)
webView.navigationDelegate = self
view.addSubview(webView)
}
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
// Check 1: Enforce boundary (sourceFrame) para prevenir iframe hijacking
if !navigationAction.sourceFrame.isMainFrame {
let scheme = url.scheme?.lowercased() ?? ""
if scheme == "http" || scheme == "https" {
decisionHandler(.allow)
} else {
decisionHandler(.cancel)
}
return
}
let scheme = url.scheme?.lowercased() ?? ""
let isExplicitLinkActivation = (navigationAction.navigationType == .linkActivated)
// Check 2: Manipular Domínio Associado do App
if scheme == "https", let host = url.host?.lowercased(), host == hostAssociatedDomain {
if let validatedRoute = AppRouteValidator.validate(url: url) {
AppInternalRouter.shared.navigate(to: validatedRoute)
}
decisionHandler(.cancel)
return
}
// Check 3: Manipular Esquemas Customizados (myapp://)
if scheme != "http" && scheme != "https" && scheme != "about" {
if isExplicitLinkActivation, let validatedURL = CustomSchemeValidator.validate(url: url) {
UIApplication.shared.open(validatedURL, options: [:], completionHandler: nil)
}
decisionHandler(.cancel)
return
}
// Check 4: Destinos Externos e New-Window (target="_blank")
if scheme == "http" || scheme == "https" {
let host = url.host?.lowercased() ?? ""
if allowedExternalPartnerHosts.contains(host) && isExplicitLinkActivation {
let options: [UIApplication.OpenExternalURLOptionsKey: Any] = [
.universalLinksOnly: true
]
UIApplication.shared.open(url, options: options) { [weak self] success in
if !success {
guard let self = self else { return }
self.webView.load(navigationAction.request)
}
}
decisionHandler(.cancel)
return
}
if navigationAction.targetFrame == nil {
webView.load(navigationAction.request)
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
return
}
decisionHandler(.allow)
}
}
class AppInternalRouter {
static let shared = AppInternalRouter()
func navigate(to route: ValidatedAppRoute) {}
}
Execução thread-safe: Garantindo transições na Main Actor
Em modelos de concorrência Swift, callbacks de WKNavigationDelegate são isolados na main actor. Roteamento de aplicativos e transições de view controller são executados nela, mantendo a segurança de thread.
Erros e matriz diagnóstica de navegação Deep Link no WKWebView
Guia de solução de problemas
A matriz abaixo descreve modos de falha comuns ao gerenciar deep links e esquemas dentro de um WKWebView no iOS:
| Sintoma | Causa Raiz | Versões | Ponto de Verificação | Remediação |
|---|---|---|---|---|
| Universal Link abre na Web | Domínio próprio não interceptado | iOS 9+ | decidePolicyForNavigationAction sem tratamento |
Interceptar domínio host, rotear internamente, .cancel |
| Link de domínio próprio falha | Chamada UIApplication.open no próprio domínio |
iOS 9+ | UIApplication.shared.open chamado no próprio host |
Evitar abrir a si mesmo; usar roteador interno |
| Esquema Customizado falha | Protocolo não-HTTP não reconhecido pelo WebKit | iOS 9+ | Esquema não delegado ao UIApplication |
Interceptar no delegate, validar, abrir via UIApplication |
| Iframe Hijacking | Subframe disparando esquema externo | iOS 9+ | sourceFrame.isMainFrame não verificado |
Proteger com if !sourceFrame.isMainFrame e suprimir esquemas não-web |
| Problema de transição de UI | Transições executadas fora da main thread | iOS 9+ | Falta de despacho para a main-actor | Garantir execução na main-actor para roteador e transições |
Perguntas Frequentes (FAQ)
Como desenvolvedores podem interceptar Universal Links em um WKWebView?
Posso usar UIApplication.shared.open para abrir meu próprio app a partir de um WKWebView?
Como prevenir que iframes embutidos em um WKWebView disparem lançamentos de apps externos?
Resumo e framework de decisão
Lidar com Universal Links e esquemas personalizados dentro de um WKWebView no iOS exige preencher a lacuna entre o container de renderização do WebKit e os ciclos de vida de navegação nativos do UIKit. Depender de políticas padrão de navegação pode impedir transições suaves quando uma lógica de roteamento própria é necessária.
Ao implementar um WKNavigationDelegate robusto que verifica limites de frames, reforça políticas conservadoras de ativação de links em handoffs externos, analisa domínios associados internos via validadores rigorosos e delega alvos externos com segurança para UIApplication.shared.open, equipes de engenharia mantêm uma navegação controlada enquanto se protegem contra o iframe protocol hijacking.
Para explorar arquiteturas de deep linking e roteamento de parâmetros nativos no iOS, consulte a documentação de integração do SDK.
Materiais relacionados
-
Conceitos: Roteamento WebView iOS, Interceptação de Universal Links, WKNavigationDelegate, Isolamento de Fronteiras de Frame
-
Tecnologias: Apple WebKit, iOS UIKit, WKWebView, SDK iOS do OpoInstall
-
Padrões: IETF RFC 3986, Especificação de Domínios Associados da Apple, Guia de Teste de Segurança de Aplicativos Móveis (MASTG) da OWASP
-
APIs:
WKNavigationDelegate.decidePolicyForNavigationAction,WKNavigationAction.sourceFrame,UIApplication.shared.open -
Documentação Oficial & Referências:
Share this article



