Skip to content

Add API for iOS IME Options #2108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.mpp.demo

import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Text
import androidx.compose.mpp.demo.textfield.android.demoTextFieldModifiers
import androidx.compose.mpp.demo.textfield.android.fontSize8
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.PlatformImeOptions
import platform.UIKit.UIKeyboardAppearanceDark
import platform.UIKit.UIKeyboardAppearanceDefault
import platform.UIKit.UIKeyboardAppearanceLight
import platform.UIKit.UIKeyboardTypeDecimalPad
import platform.UIKit.UIKeyboardTypeDefault
import platform.UIKit.UIKeyboardTypeEmailAddress
import platform.UIKit.UIKeyboardTypeNamePhonePad
import platform.UIKit.UIKeyboardTypeNumberPad
import platform.UIKit.UIKeyboardTypePhonePad
import platform.UIKit.UIKeyboardTypeTwitter
import platform.UIKit.UIKeyboardTypeURL
import platform.UIKit.UIKeyboardTypeWebSearch
import platform.UIKit.UIReturnKeyType
import platform.UIKit.UITextAutocapitalizationType
import platform.UIKit.UITextAutocorrectionType

private val keyboardTypes = listOf(
"Default" to UIKeyboardTypeDefault,
"Email" to UIKeyboardTypeEmailAddress,
"Number" to UIKeyboardTypeNumberPad,
"Phone" to UIKeyboardTypePhonePad,
"Name" to UIKeyboardTypeNamePhonePad,
"URL" to UIKeyboardTypeURL,
"Decimal" to UIKeyboardTypeDecimalPad,
"Twitter" to UIKeyboardTypeTwitter,
"WebSearch" to UIKeyboardTypeWebSearch,
"None" to null,
)

private val keyboardAppearances = listOf(
"Default" to UIKeyboardAppearanceDefault,
"Light" to UIKeyboardAppearanceLight,
"Dark" to UIKeyboardAppearanceDark,
)

private val returnKeyTypes = listOf(
"Default" to UIReturnKeyType.UIReturnKeyDefault,
"Go" to UIReturnKeyType.UIReturnKeyGo,
"Join" to UIReturnKeyType.UIReturnKeyJoin,
"Next" to UIReturnKeyType.UIReturnKeyNext,
"Route" to UIReturnKeyType.UIReturnKeyRoute,
"Search" to UIReturnKeyType.UIReturnKeySearch,
"Done" to UIReturnKeyType.UIReturnKeyDone,
"Emergency Call" to UIReturnKeyType.UIReturnKeyEmergencyCall,
"Null" to null
)

private val autocorrectionTypes = listOf(
"Default" to UITextAutocorrectionType.UITextAutocorrectionTypeDefault,
"No" to UITextAutocorrectionType.UITextAutocorrectionTypeNo,
"Yes" to UITextAutocorrectionType.UITextAutocorrectionTypeYes,
)

private val autocapitalizationTypes = listOf(
"None" to UITextAutocapitalizationType.UITextAutocapitalizationTypeNone,
"Sentences" to UITextAutocapitalizationType.UITextAutocapitalizationTypeSentences,
"Words" to UITextAutocapitalizationType.UITextAutocapitalizationTypeWords,
"All Characters" to UITextAutocapitalizationType.UITextAutocapitalizationTypeAllCharacters,
"Null" to null
)

@OptIn(ExperimentalComposeUiApi::class)
private val IosImeOptionsKeyboardTypeExample = Screen.Example("Keyboard Type") {
LazyColumn {
items(keyboardTypes) {
Item(it.first, PlatformImeOptions { keyboardType(it.second) })
}
}
}

@OptIn(ExperimentalComposeUiApi::class)
private val IosImeOptionsKeyboardAppearanceExample = Screen.Example("Keyboard Appearance") {
LazyColumn {
items(keyboardAppearances) {
Item(it.first, PlatformImeOptions { keyboardAppearance(it.second) })
}
}
}

@OptIn(ExperimentalComposeUiApi::class)
private val IosImeOptionsReturnKeyTypeExample = Screen.Example("Return Key Type") {
LazyColumn {
items(returnKeyTypes) {
Item(it.first, PlatformImeOptions { returnKeyType(it.second) })
}
}
}

@OptIn(ExperimentalComposeUiApi::class)
private val IosImeOptionsIsSecureTextEntryExample = Screen.Example("Is Secure Text Entry") {
LazyColumn {
item { Item("Is Secure", PlatformImeOptions { isSecureTextEntry(true) }) }
item { Item("Is Not Secure", PlatformImeOptions { isSecureTextEntry(false) }) }
}
}

@OptIn(ExperimentalComposeUiApi::class)
private val IosImeOptionsEnablesReturnKeyTypeAutomaticallyExample = Screen.Example("Enables Return Key Type Automatically") {
LazyColumn {
item {
Item(
"Enables Return Key Type Automatically",
PlatformImeOptions { enablesReturnKeyAutomatically(true) }
)
}
item {
Item(
"Doesn't Enable Return Key Type Automatically",
PlatformImeOptions { enablesReturnKeyAutomatically(false) }
)
}
}
}

@OptIn(ExperimentalComposeUiApi::class)
private val IosImeOptionsAutocapitalizationTypeExample = Screen.Example("Autocapitalization Type") {
LazyColumn {
items(autocapitalizationTypes) {
Item(it.first, PlatformImeOptions { autocapitalizationType(it.second) })
}
}
}

@OptIn(ExperimentalComposeUiApi::class)
private val IosImeOptionsAutocorrectionTypeExample = Screen.Example("Autocapitalization Type") {
LazyColumn {
items(autocorrectionTypes) {
Item(it.first, PlatformImeOptions { autocorrectionType(it.second) })
}
}
}

val IosImeOptionsExample = Screen.Selection(
"iOS Platform IME Options",
IosImeOptionsKeyboardTypeExample,
IosImeOptionsKeyboardAppearanceExample,
IosImeOptionsReturnKeyTypeExample,
IosImeOptionsIsSecureTextEntryExample,
IosImeOptionsEnablesReturnKeyTypeAutomaticallyExample,
IosImeOptionsAutocapitalizationTypeExample,
IosImeOptionsAutocorrectionTypeExample
)

@Composable
private fun Item(
title: String,
options: PlatformImeOptions
) {
Text(title)
EditLine(options)
}

@Composable
private fun EditLine(
options: PlatformImeOptions,
text: String = ""
) {
val keyboardController = LocalSoftwareKeyboardController.current
val state = rememberSaveable { mutableStateOf(text) }
BasicTextField(
modifier = demoTextFieldModifiers,
value = state.value,
singleLine = true,
keyboardOptions = KeyboardOptions(
platformImeOptions = options
),
keyboardActions = KeyboardActions { keyboardController?.hide() },
onValueChange = { state.value = it },
textStyle = TextStyle(fontSize = fontSize8),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ val IosSpecificFeatures = Screen.Selection(
InteropViewAndSemanticsConfigMerge,
InteropExample,
ReusableMapsExample,
UpdatableInteropPropertiesExample
UpdatableInteropPropertiesExample,
IosImeOptionsExample
)
2 changes: 1 addition & 1 deletion compose/ui/ui-text/api/desktop/ui-text.api
Original file line number Diff line number Diff line change
Expand Up @@ -1380,7 +1380,7 @@ public final class androidx/compose/ui/text/input/PasswordVisualTransformation :
public fun hashCode ()I
}

public final class androidx/compose/ui/text/input/PlatformImeOptions {
public class androidx/compose/ui/text/input/PlatformImeOptions {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @igordmn for awareness: changing actual skiko/non-android variant of an empty (not yet adopted) class to open.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer adding uikit-only open variant of the PlatformImeOptions, keeping final for all other platforms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not compilation target specific. It's about how non-android variants handle that, so I see no reason to introduce the diff here.

Keep only skiko actual variant was an explicit ask from me above

public static final field $stable I
public fun <init> ()V
}
Expand Down
66 changes: 62 additions & 4 deletions compose/ui/ui-text/api/ui-text.klib.api
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Klib ABI Dump
// Targets: [iosArm64.uikitArm64, iosSimulatorArm64.uikitSimArm64, iosX64.uikitX64, js, macosArm64, macosX64, wasmJs]
// Alias: apple => [iosArm64.uikitArm64, iosSimulatorArm64.uikitSimArm64, iosX64.uikitX64, macosArm64, macosX64]
// Alias: ios => [iosArm64.uikitArm64, iosSimulatorArm64.uikitSimArm64, iosX64.uikitX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
Expand Down Expand Up @@ -505,10 +506,6 @@ final class androidx.compose.ui.text.input/PasswordVisualTransformation : androi
final fun hashCode(): kotlin/Int // androidx.compose.ui.text.input/PasswordVisualTransformation.hashCode|hashCode(){}[0]
}

final class androidx.compose.ui.text.input/PlatformImeOptions { // androidx.compose.ui.text.input/PlatformImeOptions|null[0]
constructor <init>() // androidx.compose.ui.text.input/PlatformImeOptions.<init>|<init>(){}[0]
}

final class androidx.compose.ui.text.input/SetComposingRegionCommand : androidx.compose.ui.text.input/EditCommand { // androidx.compose.ui.text.input/SetComposingRegionCommand|null[0]
constructor <init>(kotlin/Int, kotlin/Int) // androidx.compose.ui.text.input/SetComposingRegionCommand.<init>|<init>(kotlin.Int;kotlin.Int){}[0]

Expand Down Expand Up @@ -1816,6 +1813,10 @@ final value class androidx.compose.ui.text/TextRange { // androidx.compose.ui.te
}
}

open class androidx.compose.ui.text.input/PlatformImeOptions { // androidx.compose.ui.text.input/PlatformImeOptions|null[0]
constructor <init>() // androidx.compose.ui.text.input/PlatformImeOptions.<init>|<init>(){}[0]
}

open class androidx.compose.ui.text.input/TextInputService { // androidx.compose.ui.text.input/TextInputService|null[0]
constructor <init>(androidx.compose.ui.text.input/PlatformTextInputService) // androidx.compose.ui.text.input/TextInputService.<init>|<init>(androidx.compose.ui.text.input.PlatformTextInputService){}[0]

Expand Down Expand Up @@ -2185,6 +2186,63 @@ final fun androidx.compose.ui.text.platform/androidx_compose_ui_text_platform_Na
// Targets: [apple]
final fun androidx.compose.ui.text/androidx_compose_ui_text_WeakKeysCache$stableprop_getter(): kotlin/Int // androidx.compose.ui.text/androidx_compose_ui_text_WeakKeysCache$stableprop_getter|androidx_compose_ui_text_WeakKeysCache$stableprop_getter(){}[0]

// Targets: [ios]
final class androidx.compose.ui.text.input/PlatformImeOptionsConfiguration { // androidx.compose.ui.text.input/PlatformImeOptionsConfiguration|null[0]
final fun autocapitalizationType(platform.UIKit/UITextAutocapitalizationType?): androidx.compose.ui.text.input/PlatformImeOptionsConfiguration // androidx.compose.ui.text.input/PlatformImeOptionsConfiguration.autocapitalizationType|autocapitalizationType(platform.UIKit.UITextAutocapitalizationType?){}[0]
final fun autocorrectionType(platform.UIKit/UITextAutocorrectionType?): androidx.compose.ui.text.input/PlatformImeOptionsConfiguration // androidx.compose.ui.text.input/PlatformImeOptionsConfiguration.autocorrectionType|autocorrectionType(platform.UIKit.UITextAutocorrectionType?){}[0]
final fun enablesReturnKeyAutomatically(kotlin/Boolean): androidx.compose.ui.text.input/PlatformImeOptionsConfiguration // androidx.compose.ui.text.input/PlatformImeOptionsConfiguration.enablesReturnKeyAutomatically|enablesReturnKeyAutomatically(kotlin.Boolean){}[0]
final fun isSecureTextEntry(kotlin/Boolean?): androidx.compose.ui.text.input/PlatformImeOptionsConfiguration // androidx.compose.ui.text.input/PlatformImeOptionsConfiguration.isSecureTextEntry|isSecureTextEntry(kotlin.Boolean?){}[0]
final fun keyboardAppearance(kotlin/Long): androidx.compose.ui.text.input/PlatformImeOptionsConfiguration // androidx.compose.ui.text.input/PlatformImeOptionsConfiguration.keyboardAppearance|keyboardAppearance(kotlin.Long){}[0]
final fun keyboardType(kotlin/Long?): androidx.compose.ui.text.input/PlatformImeOptionsConfiguration // androidx.compose.ui.text.input/PlatformImeOptionsConfiguration.keyboardType|keyboardType(kotlin.Long?){}[0]
final fun returnKeyType(platform.UIKit/UIReturnKeyType?): androidx.compose.ui.text.input/PlatformImeOptionsConfiguration // androidx.compose.ui.text.input/PlatformImeOptionsConfiguration.returnKeyType|returnKeyType(platform.UIKit.UIReturnKeyType?){}[0]
final fun textContentType(kotlin/String?): androidx.compose.ui.text.input/PlatformImeOptionsConfiguration // androidx.compose.ui.text.input/PlatformImeOptionsConfiguration.textContentType|textContentType(kotlin.String?){}[0]
}

// Targets: [ios]
final val androidx.compose.ui.text.input/androidx_compose_ui_text_input_PlatformImeOptionsConfiguration$stableprop // androidx.compose.ui.text.input/androidx_compose_ui_text_input_PlatformImeOptionsConfiguration$stableprop|#static{}androidx_compose_ui_text_input_PlatformImeOptionsConfiguration$stableprop[0]

// Targets: [ios]
final val androidx.compose.ui.text.input/autocapitalizationType // androidx.compose.ui.text.input/autocapitalizationType|@androidx.compose.ui.text.input.PlatformImeOptions{}autocapitalizationType[0]
final fun (androidx.compose.ui.text.input/PlatformImeOptions).<get-autocapitalizationType>(): platform.UIKit/UITextAutocapitalizationType? // androidx.compose.ui.text.input/autocapitalizationType.<get-autocapitalizationType>|<get-autocapitalizationType>@androidx.compose.ui.text.input.PlatformImeOptions(){}[0]

// Targets: [ios]
final val androidx.compose.ui.text.input/autocorrectionType // androidx.compose.ui.text.input/autocorrectionType|@androidx.compose.ui.text.input.PlatformImeOptions{}autocorrectionType[0]
final fun (androidx.compose.ui.text.input/PlatformImeOptions).<get-autocorrectionType>(): platform.UIKit/UITextAutocorrectionType? // androidx.compose.ui.text.input/autocorrectionType.<get-autocorrectionType>|<get-autocorrectionType>@androidx.compose.ui.text.input.PlatformImeOptions(){}[0]

// Targets: [ios]
final val androidx.compose.ui.text.input/enablesReturnKeyAutomatically // androidx.compose.ui.text.input/enablesReturnKeyAutomatically|@androidx.compose.ui.text.input.PlatformImeOptions{}enablesReturnKeyAutomatically[0]
final fun (androidx.compose.ui.text.input/PlatformImeOptions).<get-enablesReturnKeyAutomatically>(): kotlin/Boolean // androidx.compose.ui.text.input/enablesReturnKeyAutomatically.<get-enablesReturnKeyAutomatically>|<get-enablesReturnKeyAutomatically>@androidx.compose.ui.text.input.PlatformImeOptions(){}[0]

// Targets: [ios]
final val androidx.compose.ui.text.input/hasExplicitTextContentType // androidx.compose.ui.text.input/hasExplicitTextContentType|@androidx.compose.ui.text.input.PlatformImeOptions{}hasExplicitTextContentType[0]
final fun (androidx.compose.ui.text.input/PlatformImeOptions).<get-hasExplicitTextContentType>(): kotlin/Boolean // androidx.compose.ui.text.input/hasExplicitTextContentType.<get-hasExplicitTextContentType>|<get-hasExplicitTextContentType>@androidx.compose.ui.text.input.PlatformImeOptions(){}[0]

// Targets: [ios]
final val androidx.compose.ui.text.input/isSecureTextEntry // androidx.compose.ui.text.input/isSecureTextEntry|@androidx.compose.ui.text.input.PlatformImeOptions{}isSecureTextEntry[0]
final fun (androidx.compose.ui.text.input/PlatformImeOptions).<get-isSecureTextEntry>(): kotlin/Boolean? // androidx.compose.ui.text.input/isSecureTextEntry.<get-isSecureTextEntry>|<get-isSecureTextEntry>@androidx.compose.ui.text.input.PlatformImeOptions(){}[0]

// Targets: [ios]
final val androidx.compose.ui.text.input/keyboardAppearance // androidx.compose.ui.text.input/keyboardAppearance|@androidx.compose.ui.text.input.PlatformImeOptions{}keyboardAppearance[0]
final fun (androidx.compose.ui.text.input/PlatformImeOptions).<get-keyboardAppearance>(): kotlin/Long // androidx.compose.ui.text.input/keyboardAppearance.<get-keyboardAppearance>|<get-keyboardAppearance>@androidx.compose.ui.text.input.PlatformImeOptions(){}[0]

// Targets: [ios]
final val androidx.compose.ui.text.input/keyboardType // androidx.compose.ui.text.input/keyboardType|@androidx.compose.ui.text.input.PlatformImeOptions{}keyboardType[0]
final fun (androidx.compose.ui.text.input/PlatformImeOptions).<get-keyboardType>(): kotlin/Long? // androidx.compose.ui.text.input/keyboardType.<get-keyboardType>|<get-keyboardType>@androidx.compose.ui.text.input.PlatformImeOptions(){}[0]

// Targets: [ios]
final val androidx.compose.ui.text.input/returnKeyType // androidx.compose.ui.text.input/returnKeyType|@androidx.compose.ui.text.input.PlatformImeOptions{}returnKeyType[0]
final fun (androidx.compose.ui.text.input/PlatformImeOptions).<get-returnKeyType>(): platform.UIKit/UIReturnKeyType? // androidx.compose.ui.text.input/returnKeyType.<get-returnKeyType>|<get-returnKeyType>@androidx.compose.ui.text.input.PlatformImeOptions(){}[0]

// Targets: [ios]
final val androidx.compose.ui.text.input/textContentType // androidx.compose.ui.text.input/textContentType|@androidx.compose.ui.text.input.PlatformImeOptions{}textContentType[0]
final fun (androidx.compose.ui.text.input/PlatformImeOptions).<get-textContentType>(): kotlin/String? // androidx.compose.ui.text.input/textContentType.<get-textContentType>|<get-textContentType>@androidx.compose.ui.text.input.PlatformImeOptions(){}[0]

// Targets: [ios]
final fun androidx.compose.ui.text.input/PlatformImeOptions(kotlin/Function1<androidx.compose.ui.text.input/PlatformImeOptionsConfiguration, kotlin/Unit>? = ...): androidx.compose.ui.text.input/PlatformImeOptions // androidx.compose.ui.text.input/PlatformImeOptions|PlatformImeOptions(kotlin.Function1<androidx.compose.ui.text.input.PlatformImeOptionsConfiguration,kotlin.Unit>?){}[0]

// Targets: [ios]
final fun androidx.compose.ui.text.input/androidx_compose_ui_text_input_PlatformImeOptionsConfiguration$stableprop_getter(): kotlin/Int // androidx.compose.ui.text.input/androidx_compose_ui_text_input_PlatformImeOptionsConfiguration$stableprop_getter|androidx_compose_ui_text_input_PlatformImeOptionsConfiguration$stableprop_getter(){}[0]

// Targets: [js, wasmJs]
final class androidx.compose.ui.text.intl/PlatformLocale { // androidx.compose.ui.text.intl/PlatformLocale|null[0]
constructor <init>() // androidx.compose.ui.text.intl/PlatformLocale.<init>|<init>(){}[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ import androidx.compose.runtime.Immutable
* Used to configure the platform specific IME options.
*/
@Immutable
actual class PlatformImeOptions
actual open class PlatformImeOptions
Loading