Saltar al contenido

Multiventana en móviles

Tauri admite múltiples ventanas en Android e iOS, lo que permite a tu aplicación mostrar contenido lado a lado en tabletas o en escenas separadas en iPad.

En Android, las ventanas múltiples se implementan utilizando Activity Embedding, lo que permite al sistema mostrar dos actividades lado a lado en pantallas grandes.

En iOS, las ventanas múltiples utilizan la API UIScene, lo que permite a los usuarios de iPad abrir varias instancias de tu aplicación en ventanas separadas.

En teléfonos, el sistema generalmente no distribuye dos ventanas lado a lado. En Android, crear otra ventana sigue iniciando una actividad separada, pero en pantallas de tamaño de teléfono móvil normalmente se añade a la pila de actividades posterior (back stack), por lo que presionar Atrás regresa a la actividad anterior en lugar de cerrar una división. En iOS (especialmente en iPhone), abrir o crear otra ventana a menudo reemplaza la UI actual con el contenido de la nueva escena en lugar de mantener ambas visibles a la vez; las verdaderas ventanas concurrentes siguen siendo una experiencia de iPad (y Stage Manager).

Ambas plataformas requieren un permiso de capacidad para crear nuevas ventanas desde el frontend.

Añade el permiso core:webview:allow-create-webview-window a tu archivo de capacidad para que tu frontend pueda crear nuevas ventanas.

Si estás creando múltiples ventanas, usa un comodín o lista cada etiqueta de ventana en el array windows:

src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": ["core:default", "core:webview:allow-create-webview-window"]
}

La función multiventana en Android utiliza Activity Embedding para dividir actividades lado a lado en pantallas grandes (tabletas, plegables). Debes crear una Activity de Android para cada tipo de ventana, configurar reglas de división y registrar un inicializador.

  1. Añade las librerías de AndroidX requeridas a tu build.gradle.kts:

    src-tauri/gen/android/app/build.gradle.kts
    dependencies {
    // ... existing dependencies
    implementation("androidx.window:window:1.5.0")
    implementation("androidx.startup:startup-runtime:1.2.0")
    }
  2. Crea una clase Kotlin para cada tipo de ventana adicional. Cada actividad debe extender de TauriActivity:

    src-tauri/gen/android/app/src/main/java/com/example/app/DetailActivity.kt
    package com.example.app
    import android.os.Bundle
    import android.os.PersistableBundle
    class DetailActivity: TauriActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    }
    }
  3. Registra la nueva actividad y habilita activity embedding añadiendo el espacio de nombres tools y la propiedad de embedding:

    src-tauri/gen/android/app/src/main/AndroidManifest.xml
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <application ...>
    <property
    android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
    android:value="true" />
    <!-- Existing MainActivity -->
    <activity
    android:name=".MainActivity"
    android:exported="true"
    ...>
    ...
    </activity>
    <!-- New activity for the detail window -->
    <activity android:name=".DetailActivity" android:exported="true" />
    <!-- Register the split initializer -->
    <provider android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data android:name="${applicationId}.SplitInitializer"
    android:value="androidx.startup" />
    </provider>
    </application>
    </manifest>
  4. Crear el inicializador de división (Split Initializer)

    Sección titulada “Crear el inicializador de división”

    El inicializador carga las reglas de pares divididos al iniciar la app:

    src-tauri/gen/android/app/src/main/java/com/example/app/SplitInitializer.kt
    package com.example.app
    import android.content.Context
    import androidx.startup.Initializer
    import androidx.window.core.ExperimentalWindowApi
    import androidx.window.embedding.RuleController
    @OptIn(ExperimentalWindowApi::class)
    class SplitInitializer : Initializer<RuleController> {
    override fun create(context: Context): RuleController {
    return RuleController.getInstance(context).apply {
    setRules(RuleController.parseRules(context, R.xml.main_split_config))
    }
    }
    override fun dependencies(): List<Class<out Initializer<*>>> {
    return emptyList()
    }
    }
  5. Crea un recurso XML que indique al sistema cómo emparejar actividades y dividir la pantalla:

    src-tauri/gen/android/app/src/main/res/xml/main_split_config.xml
    <resources
    xmlns:window="http://schemas.android.com/apk/res-auto">
    <SplitPairRule
    window:splitRatio="0.33"
    window:splitLayoutDirection="locale"
    window:splitMinWidthDp="840"
    window:splitMaxAspectRatioInPortrait="alwaysAllow"
    window:finishPrimaryWithSecondary="never"
    window:finishSecondaryWithPrimary="never"
    window:clearTop="false">
    <SplitPairFilter
    window:primaryActivityName=".MainActivity"
    window:secondaryActivityName=".DetailActivity"/>
    </SplitPairRule>
    </resources>

    Atributos clave:

    • splitRatio — cómo se divide la pantalla (0.33 otorga a la actividad principal un tercio)
    • splitMinWidthDp — ancho mínimo de pantalla para activar la división (840dp apunta a tabletas)
    • splitMaxAspectRatioInPortrait — establécelo en alwaysAllow para permitir la división en modo retrato (vertical)
    • primaryActivityName / secondaryActivityName — qué par de actividades activa la división

En iOS, multiventana utiliza la API UIScene. Los usuarios de iPad pueden abrir nuevas ventanas manteniendo presionado el icono de la aplicación y seleccionando "Nueva ventana", o tu aplicación puede crearlas mediante programación.

  1. Habilitar soporte de escenas (Scene Support)

    Sección titulada “Habilitar soporte de escenas”

    Crea un archivo Info.ios.plist en tu directorio src-tauri para declarar el soporte de escenas:

    src-tauri/Info.ios.plist
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    <key>UIApplicationSceneManifest</key>
    <dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <true/>
    <key>UISceneConfigurations</key>
    <dict/>
    </dict>
    </dict>
    </plist>
  2. Cuando un usuario solicita una nueva ventana en iPad (por ejemplo, manteniendo presionado el icono de la app), Tauri emite un evento RunEvent::SceneRequested. Manéjalo para crear una nueva ventana:

    src-tauri/src/lib.rs
    #[cfg_attr(mobile, tauri::mobile_entry_point)]
    pub fn run() {
    #[cfg(target_os = "ios")]
    let mut counter = 0;
    tauri::Builder::default()
    .setup(|app| {
    tauri::WebviewWindowBuilder::new(
    app, "main", tauri::WebviewUrl::default()
    ).build()?;
    Ok(())
    })
    .build(tauri::generate_context!())
    .expect("error while running tauri application")
    .run(move |app, event| {
    #[cfg(target_os = "ios")]
    if let tauri::RunEvent::SceneRequested { .. } = event {
    counter += 1;
    tauri::WebviewWindowBuilder::new(
    app,
    format!("main-{counter}"),
    tauri::WebviewUrl::default(),
    )
    .build()
    .unwrap();
    }
    #[cfg(not(target_os = "ios"))]
    let _ = (app, event);
    });
    }

Puedes crear ventanas adicionales tanto desde Rust como desde la API JavaScript del frontend. El WebviewWindowBuilder (Rust) y WebviewWindow (JavaScript) aceptan opciones específicas de la plataforma:

Opciones de Android:

  • activityName — el nombre de la clase Activity de Android a crear para esta ventana.
  • createdByActivityName — el nombre de la Activity que está creando esta ventana. Esto determina a qué pila de actividades pertenece la nueva actividad, lo cual es importante para que las reglas de división funcionen correctamente. Cuando no se establece, se hereda automáticamente del gestor (p. ej. al construir desde un manejador de Window o Webview).

Opciones de iOS:

  • requestedBySceneIdentifier — establece el identificador de la UIScene que solicita la creación de esta nueva escena, estableciendo una relación entre ambas escenas. Por defecto, el sistema utiliza la escena en primer plano. Cuando no se establece, se hereda automáticamente del gestor.
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
function openDetail(id) {
const webview = new WebviewWindow(`detail-${id}`, {
url: `detail/${id}`,
activityName: 'DetailActivity',
});
webview.once('tauri://created', () => {
console.log('window created');
});
webview.once('tauri://error', (e) => {
console.error(e);
});
}

Una vez que se ha creado una ventana, puedes obtener su identificador específico de la plataforma:

const activityName = await window.activityName();
const sceneId = await window.sceneIdentifier();

Estos getters son útiles para hacer referencia a la identidad de una ventana al crear ventanas relacionadas. Por ejemplo, puedes leer la activityName de una ventana para pasarla como createdByActivityName en una nueva ventana, o leer sceneIdentifier para pasarlo como requestedBySceneIdentifier.


© 2026 Colaboradores de Tauri. CC-BY / MIT