feat(core): allow changing or disabling the input accessory view on iOS (#13208)

* feat(core): allow changing or disabling the input accessory view on iOS

needs https://github.com/tauri-apps/wry/pull/1544

* remove unused code

* fix imports

* lint

* fix features

* wry 0.51.2
This commit is contained in:
Lucas Fernandes Nogueira 2025-04-12 21:10:07 -03:00 committed by GitHub
parent 0d39ff6b09
commit ea36294cbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 230 additions and 23 deletions

View File

@ -0,0 +1,7 @@
---
"@tauri-apps/api": minor:feat
"tauri-utils": minor:feat
---
Added `disableInputAccessoryView: bool` config for iOS.

View File

@ -0,0 +1,6 @@
---
"tauri-runtime": minor:feat
"tauri-runtime-wry": minor:feat
---
Added `WebviewAttributes::input_accessory_view_builder` on iOS.

View File

@ -0,0 +1,5 @@
---
"tauri": minor:feat
---
Added `WebviewWindowBuilder::with_input_accessory_view_builder` and `WebviewBuilder::with_input_accessory_view_builder` on iOS.

7
Cargo.lock generated
View File

@ -8408,6 +8408,7 @@ dependencies = [
"objc2 0.6.0",
"objc2-app-kit",
"objc2-foundation 0.3.0",
"objc2-ui-kit",
"objc2-web-kit",
"percent-encoding",
"plist",
@ -8753,6 +8754,8 @@ dependencies = [
"gtk",
"http 1.2.0",
"jni",
"objc2 0.6.0",
"objc2-ui-kit",
"raw-window-handle",
"serde",
"serde_json",
@ -10816,9 +10819,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wry"
version = "0.51.1"
version = "0.51.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48846531c50ee2e209a396ddd24af04ca1584be814e750fb81b395c8e7983ff9"
checksum = "c886a0a9d2a94fd90cfa1d929629b79cfefb1546e2c7430c63a47f0664c0e4e2"
dependencies = [
"base64 0.22.1",
"block2 0.6.0",

View File

@ -561,6 +561,11 @@
"description": "on macOS and iOS there is a link preview on long pressing links, this is enabled by default.\n see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview",
"default": true,
"type": "boolean"
},
"disableInputAccessoryView": {
"description": "Allows disabling the input accessory view on iOS.\n\n The accessory view is the view that appears above the keyboard when a text input element is focused.\n It usually displays a view with \"Done\", \"Next\" buttons.",
"default": false,
"type": "boolean"
}
},
"additionalProperties": false

View File

@ -17,7 +17,7 @@ rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
wry = { version = "0.51", default-features = false, features = [
wry = { version = "0.51.2", default-features = false, features = [
"drag-drop",
"protocol",
"os-webview",

View File

@ -30,7 +30,7 @@ use tauri_runtime::{
UserAttentionType, UserEvent, WebviewDispatch, WebviewEventId, WindowDispatch, WindowEventId,
};
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg(target_vendor = "apple")]
use objc2::rc::Retained;
#[cfg(target_os = "macos")]
use tao::platform::macos::{EventLoopWindowTargetExtMacOS, WindowBuilderExtMacOS};
@ -42,9 +42,11 @@ use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};
use webview2_com::FocusChangedEventHandler;
#[cfg(windows)]
use windows::Win32::Foundation::HWND;
#[cfg(target_os = "ios")]
use wry::WebViewBuilderExtIos;
#[cfg(windows)]
use wry::WebViewBuilderExtWindows;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg(target_vendor = "apple")]
use wry::{WebViewBuilderExtDarwin, WebViewExtDarwin};
use tao::{
@ -2987,7 +2989,7 @@ impl<T: UserEvent> Runtime<T> for Wry<T> {
}
#[cfg(target_os = "ios")]
fn run_return<F: FnMut(RunEvent<T>) + 'static>(mut self, callback: F) -> i32 {
fn run_return<F: FnMut(RunEvent<T>) + 'static>(self, callback: F) -> i32 {
self.run(callback);
0
}
@ -4627,9 +4629,16 @@ fn create_webview<T: UserEvent>(
webview_builder.with_allow_link_preview(webview_attributes.allow_link_preview);
}
#[cfg(target_os = "ios")]
{
if let Some(input_accessory_view_builder) = webview_attributes.input_accessory_view_builder {
webview_builder = webview_builder
.with_input_accessory_view_builder(move |webview| input_accessory_view_builder.0(webview));
}
}
#[cfg(target_os = "macos")]
{
use wry::WebViewBuilderExtDarwin;
if let Some(position) = &webview_attributes.traffic_light_position {
webview_builder = webview_builder.with_traffic_light_inset(*position);
}

View File

@ -47,6 +47,13 @@ gtk = { version = "0.18", features = ["v3_24"] }
[target."cfg(target_os = \"android\")".dependencies]
jni = "0.21"
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
objc2 = "0.6"
objc2-ui-kit = { version = "0.3.0", default-features = false, features = [
"UIView",
"UIResponder",
] }
[target."cfg(target_os = \"macos\")".dependencies]
url = "2"

View File

@ -34,6 +34,12 @@ type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
#[cfg(target_os = "ios")]
type InputAccessoryViewBuilderFn = dyn Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
+ Send
+ Sync
+ 'static;
/// Download event.
pub enum DownloadEvent<'a> {
/// Download requested.
@ -193,7 +199,7 @@ impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
}
/// The attributes used to create an webview.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct WebviewAttributes {
pub url: WebviewUrl,
pub user_agent: Option<String>,
@ -236,6 +242,37 @@ pub struct WebviewAttributes {
/// on macOS and iOS there is a link preview on long pressing links, this is enabled by default.
/// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
pub allow_link_preview: bool,
/// Allows overriding the the keyboard accessory view on iOS.
/// Returning `None` effectively removes the view.
///
/// The closure parameter is the webview instance.
///
/// The accessory view is the view that appears above the keyboard when a text input element is focused.
/// It usually displays a view with "Done", "Next" buttons.
///
/// # Stability
///
/// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.
#[cfg(target_os = "ios")]
pub input_accessory_view_builder: Option<InputAccessoryViewBuilder>,
}
#[cfg(target_os = "ios")]
#[non_exhaustive]
pub struct InputAccessoryViewBuilder(pub Box<InputAccessoryViewBuilderFn>);
#[cfg(target_os = "ios")]
impl std::fmt::Debug for InputAccessoryViewBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("InputAccessoryViewBuilder").finish()
}
}
#[cfg(target_os = "ios")]
impl InputAccessoryViewBuilder {
pub fn new(builder: Box<InputAccessoryViewBuilderFn>) -> Self {
Self(builder)
}
}
impl From<&WindowConfig> for WebviewAttributes {
@ -281,6 +318,12 @@ impl From<&WindowConfig> for WebviewAttributes {
}
builder.javascript_disabled = config.javascript_disabled;
builder.allow_link_preview = config.allow_link_preview;
#[cfg(target_os = "ios")]
if config.disable_input_accessory_view {
builder
.input_accessory_view_builder
.replace(InputAccessoryViewBuilder::new(Box::new(|_webview| None)));
}
builder
}
}
@ -315,6 +358,8 @@ impl WebviewAttributes {
background_throttling: None,
javascript_disabled: false,
allow_link_preview: true,
#[cfg(target_os = "ios")]
input_accessory_view_builder: None,
}
}

View File

@ -561,6 +561,11 @@
"description": "on macOS and iOS there is a link preview on long pressing links, this is enabled by default.\n see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview",
"default": true,
"type": "boolean"
},
"disableInputAccessoryView": {
"description": "Allows disabling the input accessory view on iOS.\n\n The accessory view is the view that appears above the keyboard when a text input element is focused.\n It usually displays a view with \"Done\", \"Next\" buttons.",
"default": false,
"type": "boolean"
}
},
"additionalProperties": false

View File

@ -1777,6 +1777,16 @@ pub struct WindowConfig {
/// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
#[serde(default = "default_true", alias = "allow-link-preview")]
pub allow_link_preview: bool,
/// Allows disabling the input accessory view on iOS.
///
/// The accessory view is the view that appears above the keyboard when a text input element is focused.
/// It usually displays a view with "Done", "Next" buttons.
#[serde(
default,
alias = "disable-input-accessory-view",
alias = "disable_input_accessory_view"
)]
pub disable_input_accessory_view: bool,
}
impl Default for WindowConfig {
@ -1834,6 +1844,7 @@ impl Default for WindowConfig {
background_throttling: None,
javascript_disabled: false,
allow_link_preview: true,
disable_input_accessory_view: false,
}
}
}
@ -3163,6 +3174,7 @@ mod build {
let background_throttling = opt_lit(self.background_throttling.as_ref());
let javascript_disabled = self.javascript_disabled;
let allow_link_preview = self.allow_link_preview;
let disable_input_accessory_view = self.disable_input_accessory_view;
literal_struct!(
tokens,
@ -3218,7 +3230,8 @@ mod build {
background_color,
background_throttling,
javascript_disabled,
allow_link_preview
allow_link_preview,
disable_input_accessory_view
);
}
}

View File

@ -97,11 +97,14 @@ tray-icon = { version = "0.20", default-features = false, features = [
gtk = { version = "0.18", features = ["v3_24"] }
webkit2gtk = { version = "=2.0.1", features = ["v2_40"], optional = true }
# darwin
[target.'cfg(target_vendor = "apple")'.dependencies]
objc2 = "0.6"
# macOS
[target.'cfg(target_os = "macos")'.dependencies]
embed_plist = "1.2"
plist = "1"
objc2 = "0.6"
objc2-foundation = { version = "0.3", default-features = false, features = [
"std",
"NSData",
@ -145,6 +148,9 @@ jni = "0.21"
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
libc = "0.2"
swift-rs = "1"
objc2-ui-kit = { version = "0.3.0", default-features = false, features = [
"UIView",
] }
[build-dependencies]
glob = "0.3"

View File

@ -390,8 +390,6 @@ impl<R: Runtime> AppHandle<R> {
///
/// Needs to be called from Main Thread
pub async fn fetch_data_store_identifiers(&self) -> crate::Result<Vec<[u8; 16]>> {
use std::sync::Mutex;
let (tx, rx) = tokio::sync::oneshot::channel::<Result<Vec<[u8; 16]>, tauri_runtime::Error>>();
let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));
let runtime_handle = self.runtime_handle.clone();
@ -415,8 +413,6 @@ impl<R: Runtime> AppHandle<R> {
///
/// Needs to be called from Main Thread
pub async fn remove_data_store(&self, uuid: [u8; 16]) -> crate::Result<()> {
use std::sync::Mutex;
let (tx, rx) = tokio::sync::oneshot::channel::<Result<(), tauri_runtime::Error>>();
let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));
let runtime_handle = self.runtime_handle.clone();

View File

@ -992,6 +992,36 @@ fn main() {
.allow_link_preview(allow_link_preview);
self
}
/// Allows overriding the the keyboard accessory view on iOS.
/// Returning `None` effectively removes the view.
///
/// The closure parameter is the webview instance.
///
/// The accessory view is the view that appears above the keyboard when a text input element is focused.
/// It usually displays a view with "Done", "Next" buttons.
///
/// # Stability
///
/// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.
#[cfg(target_os = "ios")]
pub fn with_input_accessory_view_builder<
F: Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
+ Send
+ Sync
+ 'static,
>(
mut self,
builder: F,
) -> Self {
self
.webview_attributes
.input_accessory_view_builder
.replace(tauri_runtime::webview::InputAccessoryViewBuilder::new(
Box::new(builder),
));
self
}
}
/// Webview.

View File

@ -54,6 +54,8 @@ mod desktop_commands {
javascript_disabled: bool,
#[serde(default = "default_true")]
allow_link_preview: bool,
#[serde(default)]
pub disable_input_accessory_view: bool,
}
#[cfg(feature = "unstable")]
@ -72,6 +74,15 @@ mod desktop_commands {
builder.webview_attributes.background_throttling = config.background_throttling;
builder.webview_attributes.javascript_disabled = config.javascript_disabled;
builder.webview_attributes.allow_link_preview = config.allow_link_preview;
#[cfg(target_os = "ios")]
if config.disable_input_accessory_view {
builder
.webview_attributes
.input_accessory_view_builder
.replace(tauri_runtime::InputAccessoryViewBuilder::new(Box::new(
|_webview| None,
)));
}
builder
}
}

View File

@ -27,10 +27,7 @@ use crate::{
UserAttentionType,
},
};
use tauri_utils::{
config::{BackgroundThrottlingPolicy, Color, WebviewUrl, WindowConfig},
Theme,
};
use tauri_utils::config::{BackgroundThrottlingPolicy, Color, WebviewUrl, WindowConfig};
use url::Url;
use crate::{
@ -825,8 +822,6 @@ impl<R: Runtime, M: Manager<R>> WebviewWindowBuilder<'_, R, M> {
/// # Examples
///
/// ```rust
/// use tauri::{WebviewWindowBuilder, Runtime};
///
/// const INIT_SCRIPT: &str = r#"
/// if (window.location.origin === 'https://tauri.app') {
/// console.log("hello world from js init script");
@ -869,8 +864,6 @@ impl<R: Runtime, M: Manager<R>> WebviewWindowBuilder<'_, R, M> {
/// # Examples
///
/// ```rust
/// use tauri::{WebviewWindowBuilder, Runtime};
///
/// const INIT_SCRIPT: &str = r#"
/// if (window.location.origin === 'https://tauri.app') {
/// console.log("hello world from js init script");
@ -1115,6 +1108,58 @@ impl<R: Runtime, M: Manager<R>> WebviewWindowBuilder<'_, R, M> {
self.webview_builder = self.webview_builder.disable_javascript();
self
}
/// Allows overriding the the keyboard accessory view on iOS.
/// Returning `None` effectively removes the view.
///
/// The closure parameter is the webview instance.
///
/// The accessory view is the view that appears above the keyboard when a text input element is focused.
/// It usually displays a view with "Done", "Next" buttons.
///
/// # Examples
///
/// ```
/// fn main() {
/// tauri::Builder::default()
/// .setup(|app| {
/// let mut builder = tauri::WebviewWindowBuilder::new(app, "label", tauri::WebviewUrl::App("index.html".into()));
/// #[cfg(target_os = "ios")]
/// {
/// window_builder = window_builder.with_input_accessory_view_builder(|_webview| unsafe {
/// let mtm = objc2_foundation::MainThreadMarker::new_unchecked();
/// let button = objc2_ui_kit::UIButton::buttonWithType(objc2_ui_kit::UIButtonType(1), mtm);
/// button.setTitle_forState(
/// Some(&objc2_foundation::NSString::from_str("Tauri")),
/// objc2_ui_kit::UIControlState(0),
/// );
/// Some(button.downcast().unwrap())
/// });
/// }
/// let webview = builder.build()?;
/// Ok(())
/// });
/// }
/// ```
///
/// # Stability
///
/// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.
#[cfg(target_os = "ios")]
pub fn with_input_accessory_view_builder<
F: Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
+ Send
+ Sync
+ 'static,
>(
mut self,
builder: F,
) -> Self {
self.webview_builder = self
.webview_builder
.with_input_accessory_view_builder(builder);
self
}
}
/// A type that wraps a [`Window`] together with a [`Webview`].
@ -1919,7 +1964,7 @@ impl<R: Runtime> WebviewWindow<R> {
}
/// Set the window theme.
pub fn set_theme(&self, theme: Option<Theme>) -> crate::Result<()> {
pub fn set_theme(&self, theme: Option<tauri_utils::Theme>) -> crate::Result<()> {
self.window.set_theme(theme)
}
}

View File

@ -800,6 +800,13 @@ interface WebviewOptions {
* see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
*/
allowLinkPreview?: boolean
/**
* Allows disabling the input accessory view on iOS.
*
* The accessory view is the view that appears above the keyboard when a text input element is focused.
* It usually displays a view with "Done", "Next" buttons.
*/
disableInputAccessoryView?: boolean
}
export { Webview, getCurrentWebview, getAllWebviews }

View File

@ -2411,6 +2411,13 @@ interface WindowOptions {
* see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
*/
allowLinkPreview?: boolean
/**
* Allows disabling the input accessory view on iOS.
*
* The accessory view is the view that appears above the keyboard when a text input element is focused.
* It usually displays a view with "Done", "Next" buttons.
*/
disableInputAccessoryView?: boolean
}
function mapMonitor(m: Monitor | null): Monitor | null {