canOpenURL quedó obsoleto: qué llamar en su lugar
Apple retiró en tres oraciones un método que venía incluido desde iOS 3.0: “canOpenURL: quedó obsoleto. Intenta abrir la URL y maneja cualquier fallo en lugar de validarla primero. Usar universal links en vez de esquemas de URL personalizados elimina por completo la necesidad de esa validación”.1
La entrada lleva el radar 179874781 y aparece bajo UIKit, Deprecations, justo debajo del mandato del ciclo de vida basado en escenas que impide que las apps se lancen.1 La entrada vecina nombra una consecuencia. La de canOpenURL no nombra ninguna.
Cada una de las tres oraciones es una instrucción distinta, y solo la segunda responde a la pregunta que de verdad se hace quien desarrolla: ¿qué escribo en su lugar? La respuesta de Apple cambia la forma de la llamada, no solamente el nombre.
TL;DR
- Apple declaró obsoleto
canOpenURL(_:)en la versión 27.0 de iOS, iPadOS, Mac Catalyst, tvOS y visionOS, con el mensaje “Prefiere intentar abrir las URL y manejar cualquier fallo”.2 Sin versión de eliminación, sin consecuencia en tiempo de ejecución. - El cambio que de verdad muerde es un número, y vive en la discusión del propio método obsoleto en lugar de en alguna nota de la versión: “Las apps enlazadas en iOS 27 o posterior están limitadas a un máximo de 25 entradas en la clave
LSApplicationQueriesSchemes”, frente a las 50 anteriores.2 El disparador es el SDK contra el que enlazas. - El reemplazo mecánico es
open(_:options:completionHandler:)y su booleano, que no necesita entrada en la lista de esquemas permitidos: “el métodoopen(_:options:completionHandler:)no está sujeto al requisito deLSApplicationQueriesSchemes”.2 - Un patrón se queda sin reemplazo, porque
canOpenURLresponde antes de que dibujes nada mientras que intentar responde por consecuencia: un éxito pone otra app en primer plano.2 Lo que sobrevive esuniversalLinksOnly, una opción deopendisponible desde iOS 10 que Apple no ha declarado obsoleta: “abre la URL solo si la URL es un universal link válido y hay una app instalada capaz de abrirla”.3 - SwiftUI nunca incluyó el método, y su booleano de finalización ya significa “puede abrir” y no “abrió”.415 En siete de mis apps publicadas y 463 archivos Swift propios,
canOpenURLyLSApplicationQueriesSchemesaparecen cero veces cada uno.6
La obsolescencia sin consecuencia, y el número que sí la tiene
Apple escribió una obsolescencia, no una eliminación. La página del símbolo lleva deprecatedAt 27.0 en las cinco plataformas donde existe UIApplication, y el resumen, la discusión, el contrato del valor de retorno y la declaración siguen todos en su sitio.2 Ninguna página de Apple nombra una versión en la que el método deje de funcionar, y el precedente más cercano apunta en la dirección contraria: openURL(_:), obsoleto desde iOS 10.0, todavía tiene página, y su nota dice hoy “Llamar a este método no tiene efecto”.7 Una década de obsolescencia produjo un método inerte antes que ausente, lo cual es precedente y no calendario.
El anuncio también es más estrecho que la obsolescencia. La nota de la versión vive en una página que Apple titula “iOS & iPadOS 27 Beta 4 Release Notes” y no aparece en ningún otro lugar, mientras que los metadatos del símbolo declaran el método obsoleto igualmente en tvOS y visionOS, y ni la página de novedades de UIKit de junio de 2026 ni las notas de la versión de Xcode 27 lo mencionan en absoluto.1289 La página del símbolo es aquí la mitad duradera del registro.
Lo cual convierte al número enterrado en la verdadera historia. Dentro de la discusión del propio método obsoleto, en el mismo inciso que exige la declaración en primer lugar: “Las apps enlazadas en iOS 15 o posterior están limitadas a un máximo de 50 entradas en la clave LSApplicationQueriesSchemes. Las apps enlazadas en iOS 27 o posterior están limitadas a un máximo de 25 entradas en la clave LSApplicationQueriesSchemes”.2
La lista de esquemas permitidos sobrevive a la obsolescencia del único método al que sirve, y el tope se reduce a la mitad para las apps enlazadas contra el nuevo SDK. Apple enuncia el tope y ahí se detiene. Nada dice qué ocurre pasada la entrada 25, y leer juntas las dos mitades del inciso da una respuesta probable antes que documentada: los esquemas no declarados siempre devuelven false, así que una app que declara 40 esquemas y vuelve a enlazar probablemente empiece a tratar 15 de ellos como ausentes. Apple nunca dice cuáles 15, ni que el truncamiento siga el orden del array. Trata el mecanismo como inferencia y el riesgo como real, porque un false se ve idéntico tanto si falta la app como si falta la declaración.
El párrafo siguiente de esa misma discusión trae un segundo límite, y los dos son alternativas antes que sumandos. Apple los ata al SDK contra el que enlazaste: “Si enlazas tu app contra una versión anterior de iOS pero se ejecuta en iOS 9.0 o posterior, puedes llamar a este método hasta 50 veces. Al alcanzar ese límite, las llamadas siguientes siempre devuelven false. Si el usuario reinstala o actualiza la app, iOS reinicia el límite”.2 Lee la condición. Ese presupuesto pertenece a las apps enlazadas antes de iOS 9, algo que hoy no puedes publicar. Toda app actual vive bajo la otra rama, el tope de la declaración, que es el que Apple recortó de 50 entradas a 25. Ambos se agotan hasta el mismo false silencioso, y ambos están documentados únicamente dentro del método que Apple acaba de declarar obsoleto.
La lectura en clave de privacidad también es inferencia. canOpenURL era la forma estándar de detectar qué apps tenía instaladas una persona, una señal del dispositivo antes que una comprobación de enlaces, y la propia definición de fingerprinting de Apple cubre las API que “se usan indebidamente para acceder a señales del dispositivo e intentar identificar el dispositivo o al usuario”.10 Apple nunca conecta ambas cosas: el método no aparece en ninguna lista de API de motivo obligatorio, y la nota de la versión, el mensaje de obsolescencia y la página del símbolo dan todos una justificación puramente mecánica.1210 Un tope de consultas reducido a la mitad encaja con la historia de privacidad, y Apple no la ha puesto por escrito.
Qué prometía realmente canOpenURL
Un true era una garantía sobre la llamada siguiente, no una descripción de la URL: “Cuando este método devuelve true, iOS garantiza que las llamadas posteriores al método open(_:options:completionHandler:) con la misma URL lanzarán correctamente una app capaz de manejarla”.2 Un false era ambiguo por diseño, con dos causas y sin manera de saber cuál se disparó: “false si el dispositivo no tiene instalada una app registrada para manejar el esquema de la URL, o si no has declarado el esquema de la URL en tu archivo Info.plist”.2
Hay tres cosas que nunca respondió y que es fácil suponer que sí: “El valor de retorno no indica la validez de la URL, si el recurso especificado existe ni, en el caso de un universal link, si el dispositivo tiene instalada una app registrada para responder a ese universal link”.2 La tercera cláusula importa para la migración que Apple recomienda, y vuelve más abajo.
La propiedad que hacía al método estructuralmente útil es la que nada reemplaza. canOpenURL lleva la palabra clave nonisolated en su declaración, y Apple afirma sin rodeos que “puedes llamar a este método con seguridad desde un hilo distinto al principal”.2 Un booleano síncrono disponible fuera del actor principal puede condicionar una decisión de disposición antes de que se renderice nada.
Intentar y manejar: la forma que cambia
La instrucción de reemplazo de Apple ocupa una sola cláusula, y la comparación entre el antes y el después parece trivial.
// Before: validate, then open. Requires an Info.plist declaration.
let url = URL(string: "someapp://profile/42")!
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
} else {
presentWebFallback()
}
<key>LSApplicationQueriesSchemes</key>
<array>
<string>someapp</string>
</array>
// After: attempt, then handle. No declaration needed.
let url = URL(string: "someapp://profile/42")!
let opened = await UIApplication.shared.open(url)
if !opened {
presentWebFallback()
}
La entrada del Info.plist desaparece, y Apple lo dice directamente: “A diferencia de este método, el método open(_:options:completionHandler:) no está sujeto al requisito de LSApplicationQueriesSchemes. Si hay una app disponible para manejar la URL, el sistema la lanzará, aunque no hayas declarado el esquema”.2 Un código base que migre por completo borra la clave, y con ella el problema de las 25 entradas.
Dos diferencias sobreviven a la reescritura, y ambas son estructurales.
La primera es el aislamiento y el momento de la llamada. La declaración de UIApplication dice @MainActor class UIApplication, así que open se ejecuta en el actor principal, y su resumen lo llama por su nombre: “Intenta abrir de forma asíncrona el recurso en la URL especificada”.1112 El booleano síncrono y ejecutable fuera del hilo principal ya no está, de modo que la lógica de validar-y-luego-abrir que viva en una capa de modelo, en una cola en segundo plano o en una función auxiliar no aislada tiene que mudarse o volverse async.
La segunda es que ya no puedes preguntar en privado. Cuando el intento tiene éxito, otra app queda en primer plano: “iOS lanza esa app y le pasa la URL. (Lanzar la app trae la otra app al primer plano)”.12 La respuesta llega como efecto colateral de actuar sobre ella. Apple no documenta nada visible para el caso fallido, solo que “se llama al manejador de finalización con el parámetro de éxito establecido en false”, pero todo patrón que necesitaba la respuesta antes de la acción la pierde: mostrar una fila de “abrir en otra app” solo cuando esa app está, ordenar una hoja para compartir según lo que hay instalado o elegir un valor por defecto entre destinos que compiten.12
La propia documentación de Apple tampoco se ha puesto al día. La página de open(_:options:completionHandler:) sigue indicando: “Para determinar si hay instalada una app capaz de manejar la URL, llama al método canOpenURL(_:) antes que a este”.12 La página del reemplazo recomienda el método obsoleto.
La comprobación de presencia que sobrevive
Hay una vía documentada que responde a la pregunta de canOpenURL intentando, sin nada visible para el usuario cuando la respuesta es no. UIApplication.OpenExternalURLOptionsKey.universalLinksOnly existe desde iOS 10, Apple no la ha declarado obsoleta y su comportamiento es exactamente una comprobación de presencia: “el método abre la URL solo si la URL es un universal link válido y hay una app instalada capaz de abrirla”.3
// Presence check with no side effect when the app is absent.
let url = URL(string: "https://myphotoapp.example.com/albums?albumname=vacation")!
let installed = await UIApplication.shared.open(
url,
options: [.universalLinksOnly: true]
)
if !installed {
presentWebFallback() // nothing opened, nothing switched
}
La rama del false es la parte valiosa. No se lanzó ningún navegador, ninguna app pasó al frente, y quien llama se enteró de lo que canOpenURL solía decirle. La contrapartida se esconde en el nombre de la opción: sin ella, un intento sobre https tiene éxito siempre que un navegador pueda encargarse, ya que “si no hay ninguna app disponible para manejar un universal link, iOS lo enruta al navegador predeterminado de la persona, permitiendo que el sitio web asociado responda”.2 La opción compra un booleano con significado a costa de suprimir el respaldo que hace agradables a los universal links. Apple define el valor como “un objeto NSNumber que contiene un valor booleano”, algo que un true de Swift satisface mediante el puente con Objective-C.3
El precio es arquitectónico, y es la tercera oración de Apple. Los universal links necesitan una asociación en ambos sentidos: “Cuando alguien instala tu app, el sistema comprueba un archivo almacenado en tu servidor web para verificar que tu sitio web permite a tu app abrir URL en su nombre. Solo tú puedes almacenar ese archivo en tu servidor, lo que asegura la asociación entre tu sitio web y tu app”.13 Un archivo en un servidor bajo tu control es exactamente lo que un esquema personalizado nunca exigió, y por eso la migración funciona para tu propia familia de apps y no hace nada por un tercero que solo publica theirapp://. Vienen de la mano dos sorpresas documentadas: tu app abriendo tu propio universal link no se enruta hacia tu app, y un toque sobre el mismo dominio mientras navegas por tu sitio en Safari se queda en Safari.13
Y ahí es donde aterriza la tercera cláusula de antes: canOpenURL tampoco respondía nunca la pregunta de presencia para un universal link.2 Migrar elimina la detección de presencia en lugar de portarla, porque un intento plano sobre https ya tiene éxito tanto si respondió la app como si respondió solo el sitio web. Un patrón que funcionaba únicamente porque los esquemas personalizados filtraban el estado de instalación se va junto con los esquemas, y la nota de la versión de Apple presenta esa pérdida como la razón para migrar.
SwiftUI nunca tuvo el método
El lado de SwiftUI es corto, y la noticia es buena. EnvironmentValues.openURL, OpenURLAction y Link llevan disponibilidad desde iOS 14.0 sin obsolescencia, y ninguno de los tres expone una comprobación de validez.4514
Lo que SwiftUI ofrece en su lugar es intentar-y-manejar con la semántica que Apple quiere ahora en todas partes, y la documentación del parámetro de finalización dice que el booleano responde a la vieja pregunta: “Un closure al que el método llama tras determinar si puede abrir la URL, pero posiblemente antes de abrirla del todo. El closure recibe un valor booleano que indica si el método puede abrir la URL”.15 Puede abrir, no abrió. El propio ejemplo de Apple lo imprime:
openURL(url) { accepted in
print(accepted ? "Success" : "Failure")
}
Link no expone ningún booleano y delega en el entorno, donde la acción por defecto ya implementa la historia de los universal links: “la acción por defecto abre un Universal Link en la app asociada si es posible, o en el navegador web predeterminado del usuario si no lo es”.5 Una OpenURLAction personalizada que devuelva .handled, .discarded o .systemAction intercepta cada Link y cada enlace en markdown dentro de un Text que lea la acción desde ese entorno.5
Hay un hueco que importa antes de planificar una migración solo con SwiftUI. OpenURLAction no tiene diccionario de opciones. Sus firmas de llamada son callAsFunction(_:), callAsFunction(_:completion:) y la incorporación de iOS 26 callAsFunction(_:prefersInApp:), y ninguna de las tres acepta universalLinksOnly.1518 Una comprobación de presencia sin efectos colaterales sigue implicando llamar a UIApplication.shared.open.
Siete apps, 463 archivos, cero llamadas
Audité mi propio portafolio antes de escribir sobre el de nadie más, esperando una lista de migración. No hay nada que migrar.6
| App | Archivos Swift | canOpenURL |
LSApplicationQueriesSchemes |
Puntos de llamada que abren URL |
|---|---|---|---|---|
| Reps | 77 | 0 | 0 | 5 |
| Return | 57 | 0 | 0 | 2 |
| Banana List | 55 | 0 | 0 | 2 |
| Ace Citizenship | 26 | 0 | 0 | 2 |
| Water | 34 | 0 | 0 | 0 |
| ResumeGeni | 71 | 0 | 0 | 12 |
| Yawara | 143 | 0 | 0 | 0 |
| Total | 463 | 0 | 0 | 23 |
El cero sobrevivió a tres pasadas que fueron ampliando el alcance, hasta cada paquete Swift incorporado y cada tipo de archivo en cada repositorio.6 Tampoco hay nada que declare un esquema consultado en 192 archivos .plist, .pbxproj, .entitlements y .xcconfig, lo cual es coherente: una app sin ninguna llamada a canOpenURL no tiene motivo para declarar ninguno.
La razón es mundana y probablemente común. De los 23 puntos de llamada, nueve abren un documento legal, cuatro derivan al producto web de la propia app, dos abren census.gov desde una alerta para que el usuario encuentre a su representante, dos abren la URL de una oferta de trabajo que llega desde una API, dos son exportaciones file: exclusivas de macOS, y uno es UIApplication.openSettingsURLString detrás de una alerta de “se requiere acceso a Salud”. Ninguno de esos 20 consulta por otra app, porque cada destino es o bien https, al que Safari siempre responde, o bien una URL del sistema. Los tres restantes concentran todo el comportamiento interesante.
El único lugar que valida antes de abrir no está haciendo aquello de lo que trata la obsolescencia: el flujo de facturación de ResumeGeni hace POST a /api/me/portal y luego comprueba url.scheme == "https" sobre la URL que devuelve el servidor, por si un servidor comprometido o con errores le entrega a la app una URL file: o de esquema personalizado.6 La nota de Apple apunta a validar si existe una app de destino y no dice nada sobre confiar en entradas remotas, así que borrar esa protección sería una regresión de seguridad, no una migración.
Los otros dos son las derivaciones de macOS de Banana List, y uno de ellos guarda la única comprobación de presencia genuina del portafolio. La app pregunta NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.anthropic.claudefordesktop") != nil antes de entregar una extensión .mcpb incluida a Launch Services, con un botón cercano que abre claude.com/download para quien primero necesite la app. El comentario del código nombra el motivo: sin la comprobación, macOS muestra su propio diálogo de “no hay ninguna aplicación configurada para abrir el documento”.6 Otra plataforma, y el dato más útil de la auditoría, porque captura en la práctica el modo de fallo de intentar-y-manejar. Un intento fallido no siempre es silencioso, y cuando el sistema habla por ti el usuario lee un error del sistema en vez de tu respaldo.
Ningún repositorio declara applinks:, así que ninguna app de aquí admite universal links, y la solución arquitectónica de Apple también está sin pagar de mi lado.6 Un esquema personalizado cuesta una entrada en el Info.plist; un universal link cuesta un archivo en un servidor web, un entitlement y un dominio bajo tu control.
Lo cual señala dónde está el trabajo de verdad: no en el código de las apps propias, sino en las hojas para compartir que se reordenan según la app instalada, en los selectores de “abrir en” y en los SDK de atribución, nada de lo cual queda descartado por un repositorio limpio.
La auditoría en sí es más fácil aquí que en otros puntos del ciclo 27. A diferencia de las claves de la pantalla de lanzamiento, que llegan desde los ajustes de compilación y derrotan por completo a una búsqueda de texto, LSApplicationQueriesSchemes no tiene equivalente con INFOPLIST_KEY_ en la referencia de ajustes de compilación de Apple, así que un grep es el primer movimiento correcto, y saber de quién son los esquemas que aparecen en ese array te dice qué dependencia quiere la respuesta.16
Preguntas frecuentes
¿canOpenURL deja de funcionar en iOS 27?
No, y Apple no ha dicho cuándo lo hará. El símbolo lleva deprecatedAt 27.0, y todo el resto de la página, incluidos el contrato del valor de retorno, la garantía sobre la llamada siguiente a open, el tope de la declaración y el presupuesto heredado de llamadas, sigue documentado.2 No aparece ninguna versión de eliminación en la nota de la versión, en la página del símbolo ni en las notas de la versión de Xcode 27.129 Planifica pensando en una advertencia y un deterioro lento, no en una ruptura.
¿Qué escribo en su lugar si necesito saber si la app está instalada?
Depende de si controlas el destino. Si lo controlas, publica un universal link y pasa universalLinksOnly a open, que Apple documenta como abrir la URL solo cuando es un universal link válido con una app instalada que pueda recogerlo, de modo que un false significa que no se abrió nada y no cambió nada.3 El costo es un archivo de asociación en ambos sentidos en tu servidor web.13 Si un tercero solo publica un esquema personalizado, no existe reemplazo: canOpenURL sigue respondiendo, sigue necesitando su declaración en el Info.plist, y el tope de esa declaración se reduce a la mitad, hasta 25 entradas, para las apps enlazadas en iOS 27 o posterior.2 El código en SwiftUI no necesita ningún cambio en este frente, porque openURL y Link nunca ofrecieron la comprobación.414
¿Sigo necesitando LSApplicationQueriesSchemes?
Solo para canOpenURL. La documentación de Launch Services de Apple define la clave enteramente en términos del método obsoleto: “especifica los esquemas de URL que quieres que la app pueda usar con el método canOpenURL: de la clase UIApplication”.17 La clave no tiene página alguna en la referencia moderna de Information Property List de Apple, y la discusión del método obsoleto enlaza al archivo histórico para encontrarla.217 El reemplazo no necesita nada, porque Apple exime a open del requisito de declaración sin más.2 Termina de migrar y el array desaparece; conserva una sola llamada y conservas el array, el requisito de declaración y el tope más pequeño.
¿La App Store rechazará una compilación con más de 25 entradas?
Ninguna página de Apple lo dice. El tope aparece en un solo lugar, la discusión de canOpenURL(_:), y Apple lo formula como un límite sobre lo que contiene la clave antes que como una regla de envío.2 Nada en las notas de la versión de iOS 27, en la página de novedades de UIKit ni en las notas de la versión de Xcode 27 le atribuye una consecuencia de revisión, y el síntoma documentado para un esquema que el sistema trata como no declarado es un false sencillo en tiempo de ejecución.1289 Las App Store Review Guidelines no contienen ninguna aparición de LSApplicationQueriesSchemes, canOpenURL ni de la cifra de 25 entradas, que es donde viviría una regla de envío si existiera.19 Así que el fallo para el que hay que prepararse es una respuesta equivocada en tu propio código antes que una compilación rechazada.
Conclusiones clave
Para quienes desarrollan en iOS:
- Reemplaza if canOpenURL(url) { open(url) } por let opened = await open(url) con un respaldo sobre !opened, y borra la entrada correspondiente de LSApplicationQueriesSchemes cuando no quede ninguna llamada.2
- Conserva cualquier comprobación de esquema que hagas sobre una URL que llegue desde un servidor. La nota de Apple apunta a validar la presencia de una app, no la entrada remota, y ambas cosas se ven idénticas en un grep.
Para equipos que publican deep links entre apps de una misma familia o SDK:
- Recurre a universalLinksOnly en vez de a canOpenURL cuando necesites una respuesta de presencia, y presupuesta el archivo de dominios asociados que exige.313 Es la única comprobación documentada que no deja nada visible para el usuario cuando la app falta.
- Cuenta las entradas de tu array LSApplicationQueriesSchemes antes de enlazar contra el SDK de iOS 27. Todo lo que pase de 25 entradas cae fuera del tope documentado, y el síntoma de Apple para un esquema no declarado es un false sencillo, que se lee exactamente igual que una app que no está instalada.2
Para quienes gestionan versiones:
- No programes esta obsolescencia como bloqueante de una versión. Sin fecha de eliminación y sin consecuencia en tiempo de ejecución, queda por detrás de la clave de la pantalla de lanzamiento que cuesta un rechazo, del mandato de escenas que impide el lanzamiento y de la macro @State que rompe la compilación.
- Trata el tope de 25 entradas como el único punto con un disparador real, porque depende del SDK contra el que enlazas y no del sistema operativo que ejecutan tus usuarios.2
El ciclo 27 va llegando con pesos distintos, y leer el peso es la manera de gastar bien el ciclo: la clave de la pantalla de lanzamiento bloquea un envío, el mandato de escenas impide que una app se lance, la macro @State rompe una compilación, On Demand Resources pone en marcha un reloj de migración, y canOpenURL solo advierte. Fabricar urgencia alrededor de la advertencia desperdicia un ciclo. La línea que vale la pena vigilar está en un párrafo de discusión y no en una nota de la versión, y es un número. El índice completo de la serie es la Serie del ecosistema Apple.
Referencias
-
Apple, iOS & iPadOS 27 Release Notes, sección de UIKit, Deprecations (radar 179874781). La página se titulaba a sí misma “iOS & iPadOS 27 Beta 4 Release Notes” en el momento de la consulta, igual que todas las demás páginas de notas de versión citadas en este artículo, así que trata como provisional toda la redacción de notas de versión que aparece aquí; los metadatos de disponibilidad de las páginas de símbolos son el registro más duradero. Fuente de la entrada completa citada aquí: “
canOpenURL:quedó obsoleto. Intenta abrir la URL y maneja cualquier fallo en lugar de validarla primero. Usar universal links en vez de esquemas de URL personalizados elimina por completo la necesidad de esa validación”. La entrada sigue inmediatamente a la del ciclo de vida basado en escenas (radar 141837548), “Las apps compiladas con el SDK más reciente deben adoptar el ciclo de vida basado en escenas o no se lanzarán”. Verificado contra el JSON de documentación de Apple el 26 de julio de 2026, porque la página HTML se renderiza mediante JavaScript. Ese mismo JSON contiene exactamente una aparición decanOpenURLy una del radar 179874781. Las notas de versión de tvOS 27, visionOS 27, watchOS 27 y macOS 27 se consultaron el mismo día, tituladas respectivamente “tvOS 27 Beta 4”, “visionOS 27 Beta 4”, “watchOS 27 Beta 4” y “macOS 27 Golden Gate Beta 4”, y no contienen ninguna aparición de ninguna de las dos cadenas. ↩↩↩↩↩↩ -
Apple, canOpenURL(_:), referencia del método de instancia de UIKit. Declarado
nonisolated func canOpenURL(_ url: URL) -> Bool, introducido en iOS 3.0 (Mac Catalyst 13.1, tvOS 9.0, visionOS 1.0) y marcado condeprecatedAt27.0 en iOS, iPadOS, Mac Catalyst, tvOS y visionOS, cada uno con el mensaje de disponibilidad “Prefiere intentar abrir las URL y manejar cualquier fallo”. El resumen de obsolescencia de la página repite palabra por palabra dos de las tres oraciones de la nota de versión: “Intenta abrir la URL y maneja cualquier fallo en lugar de validarla primero. Usar universal links en vez de esquemas de URL personalizados elimina por completo la necesidad de esa validación”. Fuente de la documentación del valor de retorno (“falsesi el dispositivo no tiene instalada una app registrada para manejar el esquema de la URL, o si no has declarado el esquema de la URL en tu archivoInfo.plist; en caso contrario,true”), de la garantía (“Cuando este método devuelvetrue, iOS garantiza que las llamadas posteriores al métodoopen(_:options:completionHandler:)con la misma URL lanzarán correctamente una app capaz de manejarla. El valor de retorno no indica la validez de la URL, si el recurso especificado existe ni, en el caso de un universal link, si el dispositivo tiene instalada una app registrada para responder a ese universal link”), de la nota sobre hilos (“Puedes llamar a este método con seguridad desde un hilo distinto al principal”), del inciso sobre la lista de esquemas permitidos citado aquí, con ambos topes (“Las apps enlazadas en iOS 15 o posterior están limitadas a un máximo de 50 entradas en la claveLSApplicationQueriesSchemes. Las apps enlazadas en iOS 27 o posterior están limitadas a un máximo de 25 entradas en la claveLSApplicationQueriesSchemes”), del presupuesto de llamadas en tiempo de ejecución citado aquí (“Si enlazas tu app contra una versión anterior de iOS pero se ejecuta en iOS 9.0 o posterior, puedes llamar a este método hasta 50 veces. Al alcanzar ese límite, las llamadas siguientes siempre devuelvenfalse. Si el usuario reinstala o actualiza la app, iOS reinicia el límite”), de la exención (“A diferencia de este método, el métodoopen(_:options:completionHandler:)no está sujeto al requisito deLSApplicationQueriesSchemes. Si hay una app disponible para manejar la URL, el sistema la lanzará, aunque no hayas declarado el esquema”) y del respaldo de los universal links (“si no hay ninguna app disponible para manejar un universal link, iOS lo enruta al navegador predeterminado de la persona, permitiendo que el sitio web asociado responda”). Una oración del inciso resulta extraña tal como está publicada: “Este método siempre devuelvefalsepara esquemas no declarados, incluso si el dispositivo no tiene instalada una app registrada”. La afirmación de este artículo sobre valoresfalseambiguos se apoya en la sección del valor de retorno y no en esa oración. Verificado contra el JSON de documentación de Apple el 26 de julio de 2026. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple, UIApplication.OpenExternalURLOptionsKey.universalLinksOnly, referencia de la propiedad de tipo de UIKit. Disponible desde iOS 10.0 (Mac Catalyst 13.1, tvOS 10.0, visionOS 1.0), sin metadatos de obsolescencia al 26 de julio de 2026. Fuente del resumen (“Las URL deben ser universal links y tener una app configurada para abrirlas”) y de la discusión citada aquí: “Cuando incluyes esta clave en el diccionario de opciones del método
open(_:options:completionHandler:), el método abre la URL solo si la URL es un universal link válido y hay una app instalada capaz de abrirla. El valor de esta clave es un objetoNSNumberque contiene un valor booleano”. ↩↩↩↩↩ -
Apple, EnvironmentValues.openURL, referencia de la propiedad de instancia de SwiftUI. Declarada
@MainActor @preconcurrency var openURL: OpenURLAction, disponible desde iOS 14.0, iPadOS 14.0, Mac Catalyst 14.0, macOS 11.0, tvOS 14.0, visionOS 1.0 y watchOS 7.0, sin metadatos de obsolescencia al 26 de julio de 2026. Fuente del ejemploopenURL(url) { accepted in ... }reproducido aquí y de la descripción de la acción por defecto citada en este artículo. ↩↩↩ -
Apple, OpenURLAction, referencia de la estructura de SwiftUI. Declarada
@MainActor @preconcurrency struct OpenURLAction, disponible desde iOS 14.0 sin metadatos de obsolescencia. Fuente de “El sistema proporciona una acción de apertura de URL por defecto cuyo comportamiento depende del contenido de la URL. Por ejemplo, la acción por defecto abre un Universal Link en la app asociada si es posible, o en el navegador web predeterminado del usuario si no lo es”, y de la afirmación de que una acción personalizada se aplica a “la vistaLinkintegrada y a las vistasTextcon enlaces en markdown, o a los enlaces en cadenas con atributos”. Los miembros deResultmencionados en este artículo provienen de la página hija, Apple, OpenURLAction.Result, que enumerahandled,discarded,systemAction,systemAction(_:)y el método de tipo de iOS 26systemAction(_:prefersInApp:). ↩↩↩ -
Revisión del autor sobre siete proyectos publicados para plataformas Apple (Reps, Return, Banana List, Ace Citizenship, Water, ResumeGeni y Yawara) en macOS 26.5.2, el 26 de julio de 2026, usando ripgrep sobre código Swift propio y excluyendo
build/,DerivedData/,.build/,Pods/,Carthage/,.swiftpm/,SourcePackages/y loscheckouts/de paquetes. La cobertura de archivos se reconcilió confindfrente argen cada repositorio (77, 57, 55, 26, 34, 71 y 143 archivos) para confirmar que no se omitiera código Swift propio ignorado por git. El cero decanOpenURLse verificó de tres maneras: Swift propio; Swift con--no-ignore --hidden, lo que incluye los productos de compilación y todos los checkouts de paquetes incorporados (3.946 archivos Swift en el árbol de ResumeGeni, 15.760 en el árbol compartido de 941Kit); y todos los tipos de archivo con--no-ignore. Cero en todas las pasadas, incluido el código de terceros e incorporado.LSApplicationQueriesSchemeseINFOPLIST_KEY_LSApplicationQueriesSchemesdevolvieron cero en 192 archivos.plist,.pbxproj,.entitlementsy.xcconfig. Los 23 puntos de llamada comprenden seis vistasLinkde SwiftUI, tres llamadas aUIApplication.shared.open, 12 llamadas aopenURL(...)(todas en ResumeGeni, desde seis declaraciones@Environment(\.openURL)) y dos llamadas aNSWorkspace.shared.openen el código de macOS de Banana List; el conteo deLink(exige un límite de palabra, porque un patrón simple también coincide conNavigationLink(y con varios tipos...Link(propios del proyecto. No existe ningún uso deSFSafariViewControllerni deWKWebViewen ningún proyecto, y ningún repositorio declaraapplinks:. La protección de facturación de ResumeGeni está enProfile/ProfileView.swift:1746; la comprobación de presencia de Banana List y su comentario explicativo están enBanana List/SettingsView.swift:321y:329, y la frase “no hay ninguna aplicación configurada para abrir el documento” citada en este artículo es la redacción de ese comentario del código y no una transcripción del diálogo de macOS; el deep link a Ajustes de Return está enReturn/ContentView.swift:222. El target principal de la app Reps declaraSUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx"; ninguna afirmación sobre plataformas en este artículo se deriva de una clave*_DEPLOYMENT_TARGET, y deSUPPORTED_PLATFORMSno se infiere el alcance completo de plataformas de ninguna app, ya que solo 40 de las 80 configuraciones de compilación de estos proyectos la definen siquiera. ↩↩↩↩↩↩ -
Apple, openURL(_:), referencia del método de instancia de UIKit. Declarado
func openURL(_ url: URL) -> Bool, introducido en iOS 2.0 y declarado obsoleto en iOS 10.0 (Mac Catalyst 13.1). Fuente de la nota de obsolescencia actual: “Llamar a este método no tiene efecto. Usa el métodoopen(_:options:completionHandler:)en su lugar”. Consultado el 26 de julio de 2026. ↩ -
Apple, UIKit updates, Apple Developer Documentation. La sección de junio de 2026 tiene cuatro subsecciones (General, App life cycle, Drag and drop y Text views) e incluye el mandato del ciclo de vida basado en escenas bajo App life cycle: “A partir de iOS 27, las apps compiladas con el SDK más reciente deben usar el ciclo de vida basado en escenas o no se lanzarán”. Se buscó
canOpenURLyLSApplicationQueriesSchemesel 26 de julio de 2026; ninguno aparece en ningún punto de la página. ↩↩ -
Apple, Xcode 27 Release Notes. Se buscó
canOpenURL,LSApplicationQueriesSchemesy el radar 179874781 el 26 de julio de 2026; no aparece ninguno. ↩↩↩ -
Apple, Describing use of required reason API, documentación de Bundle Resources. Fuente de la definición de fingerprinting de Apple citada aquí: “Algunas API que tu app usa para ofrecer su funcionalidad principal… tienen el potencial de usarse indebidamente para acceder a señales del dispositivo e intentar identificar el dispositivo o al usuario, lo que se conoce como fingerprinting. Con independencia de que el usuario dé permiso a tu app para rastrear, el fingerprinting no está permitido”. Búsqueda del 26 de julio de 2026:
canOpenURLyLSApplicationQueriesSchemesno aparecen en la página, lo que sustenta la afirmación de aquí de que Apple nunca vincula el método con el fingerprinting. La página describe el requisito de declaración y remite la lista de categorías a la documentación deNSPrivacyAccessedAPIType. La lectura en clave de privacidad de la obsolescencia que hace este artículo es una inferencia del autor, no la justificación declarada por Apple. ↩↩ -
Apple, UIApplication, referencia de la clase de UIKit. Declarada
@MainActor class UIApplication, disponible desde iOS 2.0 sin metadatos de obsolescencia. Fuente del aislamiento en el actor principal queopen(_:options:completionHandler:)hereda y del quecanOpenURL(_:)se desmarca connonisolated. ↩ -
Apple, open(_:options:completionHandler:), referencia del método de instancia de UIKit. Disponible desde iOS 10.0 sin metadatos de obsolescencia, declarado tanto en forma con manejador de finalización como en forma
async:func open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:], completionHandler completion: (@MainActor @Sendable (Bool) -> Void)? = nil)yfunc open(_ url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:]) async -> Bool. Fuente del resumen (“Intenta abrir de forma asíncrona el recurso en la URL especificada”), del comportamiento de lanzamiento citado aquí (“Si el esquema de URL especificado lo maneja otra app, iOS lanza esa app y le pasa la URL. (Lanzar la app trae la otra app al primer plano). Si ninguna app es capaz de manejar el esquema especificado, se llama al manejador de finalización con el parámetro de éxito establecido enfalse”) y de la indicación que todavía recomienda el método obsoleto: “Para determinar si hay instalada una app capaz de manejar la URL, llama al métodocanOpenURL(_:)antes que a este. Asegúrate de leer la descripción de ese método para una nota importante sobre cómo registrar los esquemas que quieres emplear”. Consultado el 26 de julio de 2026. ↩↩↩↩ -
Apple, Allowing apps and websites to link to your content, documentación de Xcode. Fuente del requisito de asociación del lado del servidor (“Cuando alguien instala tu app, el sistema comprueba un archivo almacenado en tu servidor web para verificar que tu sitio web permite a tu app abrir URL en su nombre. Solo tú puedes almacenar ese archivo en tu servidor, lo que asegura la asociación entre tu sitio web y tu app”), del respaldo del navegador (“Si la persona no ha instalado tu app, el sistema abre la URL en su navegador web predeterminado, permitiendo que tu sitio web la maneje”), de la nota de que una app que abre su propio universal link no se enruta hacia sí misma (“Si tu app usa uno de los métodos anteriores para abrir un universal link hacia tu sitio web, el enlace no se abrirá en tu app”) y del comportamiento en Safari dentro del mismo dominio descrito aquí. La página menciona
EnvironmentValues.openURLde SwiftUI yopen(_:options:completionHandler:)de UIKit entre las llamadas que enrutan universal links. ↩↩↩↩ -
Apple, Link, referencia de la estructura de SwiftUI. Declarada
@MainActor @preconcurrency struct Link<Label> where Label : View, disponible desde iOS 14.0, macOS 11.0 y watchOS 7.0 sin metadatos de obsolescencia. Fuente del comportamiento por defecto citado aquí: “Cuando una persona toca o hace clic en unLink, el comportamiento por defecto depende del contenido de la URL. Por ejemplo, SwiftUI abre un Universal Link en la app asociada si es posible, o en el navegador web predeterminado del usuario si no lo es”. ↩↩ -
Apple, OpenURLAction.callAsFunction(_:completion:), referencia del método de instancia de SwiftUI. Declarado
@MainActor @preconcurrency func callAsFunction(_ url: URL, completion: @escaping (Bool) -> Void). Fuente de la semántica de finalización citada aquí: “Un closure al que el método llama tras determinar si puede abrir la URL, pero posiblemente antes de abrirla del todo. El closure recibe un valor booleano que indica si el método puede abrir la URL”. El método hermanocallAsFunction(_:)recibe solo una URL. Consultado el 26 de julio de 2026. ↩↩↩ -
Apple, Build settings reference, documentación de Xcode. Se buscó
INFOPLIST_KEY_LSApplicationQueriesSchemesel 26 de julio de 2026: el ajuste no aparece, mientras queINFOPLIST_KEY_LSApplicationCategoryType,INFOPLIST_KEY_LSBackgroundOnly,INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlaceeINFOPLIST_KEY_LSUIElementsí lo hacen. Un paso de compilación que fusione su propia lista de propiedades todavía puede inyectar la clave, así que la ausencia convierte a una búsqueda en el repositorio en el primer movimiento correcto antes que en una auditoría completa. ↩ -
Apple, Launch Services Keys, Information Property List Key Reference (archivo histórico de Apple). Fuente de la definición de la clave: “
LSApplicationQueriesSchemes(Array - iOS) Especifica los esquemas de URL que quieres que la app pueda usar con el métodocanOpenURL:de la claseUIApplication. Por cada esquema de URL que quieras que tu app use con el métodocanOpenURL:, agrégalo como una cadena en este array”. La página afirma que la clave “es compatible con iOS 9.0 y posteriores” y no menciona ningún tope de entradas. El archivo histórico es el destino del enlace que aparece dentro de la propia discusión decanOpenURL(_:). La clave no tiene página en la referencia actual de Information Property List de Apple, verificado el 26 de julio de 2026:documentation/bundleresources/information-property-list/lsapplicationqueriesschemes.jsondevuelve HTTP 404, mientras que páginas hermanas de clavesLS*comolsapplicationcategorytypeylsbackgroundonlydevuelven 200. El propio JSON del índice de la referencia tampoco sirve como prueba en ningún sentido, ya que enumera solo siete grupos de claves de primer nivel y no nombra ninguna claveLS*individual;lsapplicationcategorytype, que sí tiene página activa, tampoco figura ahí. ↩↩ -
Apple, OpenURLAction.callAsFunction(_:prefersInApp:), referencia del método de instancia de SwiftUI. Declarado
@MainActor @preconcurrency func callAsFunction(_ url: URL, prefersInApp: Bool), disponible desde iOS 26.0, iPadOS 26.0, Mac Catalyst 26.0, macOS 26.0, tvOS 26.0, visionOS 26.0 y watchOS 26.0. Aparece en la página deOpenURLActionbajo Instance Methods y no bajo Calling the action. Recibe un único booleano en lugar de un diccionario de opciones, así que es la tercera y última firma de llamada, y ninguna de las tres aceptauniversalLinksOnly. Consultado el 26 de julio de 2026. ↩ -
Apple, App Store Review Guidelines. Se buscó
LSApplicationQueriesSchemes,canOpenURLy “25 entries” el 26 de julio de 2026: cero apariciones de cada uno. Citado para sustentar la ausencia de una regla de envío antes que cualquier afirmación positiva. ↩