onKeyEvent is not triggered for BackSpace in iOS s...
# compose-ios
f
onKeyEvent is not triggered for BackSpace in iOS soft keyboard. Any idea why this does not works for iOS targets?
Copy code
Modifier.onKeyEvent { keyEvent ->
    Napier.d(tag = "OTPVerification") { "Keyboard button clicked ${keyEvent.key}" }
    if (keyEvent.type == KeyEventType.KeyUp && keyEvent.key == Key.Backspace) {
        // code...
    }
    true
}
a
Are you getting an event for other keys?
Ah, I see you found the already-reported issue at https://github.com/JetBrains/compose-multiplatform/issues/3855
f
Yes i also recently checked.
Yes i was getting events but will check once again
m
Please is there any update on this?
f
This is not fixed yet
m
That sucks... Please do you know of any workaround or hack to get around this?
f
Whats yr usecase ?
m
Creating an OTP input field. I had to create a backspace button UI for iOS.
a
onPreviewKeyEvent
also doesn’t work?
m
Yeah. Same thing for both. The entire code block is never called, regardless of whatever key is pressed
f
Yes @Alexander Maryanovsky doesn’t work
@Mofe Ejegi for the same usecase I reported here. But i was able to came up with a work around. Will share code snippets with you.
m
Thank you. I just noticed yours is also an OTP implementation. Looking forward to seeing your solution
f
You can use something like this. All OTP boxes inside single BasicInputField.
Copy code
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun OTPInputField(
    modifier: Modifier = Modifier,
    otpText: String,
    maxOTPCount: Int = 6,
    isError: Boolean = false,
    errorMsg: String = String.EMPTY,
    onOtpTextChange: (String) -> Unit,
    onOTPFilled: (String) -> Unit
) {
    val keyboardController = LocalSoftwareKeyboardController.current
    val focusManager = LocalFocusManager.current

    Column {
        BasicTextField(
            value = TextFieldValue(otpText, selection = TextRange(otpText.length)),
            onValueChange = {textField ->
                if (textField.text.length <= maxOTPCount) {
                    val otpFilled: Boolean =  textField.text.length == maxOTPCount

                    Napier.d { "Line68: onValueChange, text: ${textField.text}" }
                    onOtpTextChange.invoke(textField.text)

                   
                    if (otpFilled) {
                        onOTPFilled.invoke(textField.text)
                    }
                }

            },
            decorationBox = {
                Row(
                    horizontalArrangement = Arrangement.Center,
                ) {
                    repeat(maxOTPCount) { index ->
                        val char = when {
                            index >= otpText.length -> ""
                            else -> otpText[index].toString()
                        }

                        val check1: Boolean = char.isEmpty() && index == otpText.length
                        val filledBox: Boolean = index < otpText.length
                        var check2: Boolean by remember { mutableStateOf(false) }

                        LaunchedEffect(key1 = check2) {
                            delay(500)
                            check2 = !check2
                        }
                        Box(contentAlignment = Alignment.Center) {
                            Text(
                                modifier = Modifier
                                    .size(54.dp)
                                    .border(
                                        width = 1.dp,
                                        color = if (isError) LocalThemeColors.current.errorColors.error_500
                                        else if (filledBox) LocalThemeColors.current.primaryColor.primary_500
                                        else LocalThemeColors.current.greyPaletteColors.grey_300,
                                        shape = RoundedCornerShape(8.dp)
                                    )
                                    .padding(vertical = 13.dp),
                                text = char,
                                textAlign = TextAlign.Center,
                                style = LocalTextStyles.current.H3Bold
                            )
                            if (check1 and check2) {
                                Box(
                                    modifier = Modifier.width(2.dp)
                                        .height(LocalDimensions.current.dp20)
                                        .background(color = LocalThemeColors.current.greyPaletteColors.grey_900)
                                )
                            }
                        }

                        HSpacer(LocalDimensions.current.dp8)
                    }

                }
            },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Number,
                imeAction = ImeAction.Done
            ),
            keyboardActions = KeyboardActions(
                onDone = {
                    keyboardController?.hide()
                    focusManager.clearFocus()
                }
            ),
            singleLine = true
        )
        if (isError) ErrorIconLabel(errorText = errorMsg)
    }
}
Driver code
Copy code
// Four digit OTP-BOX
var otpText: String by remember { mutableStateOf(String.EMPTY) }

OTPInputField(
    otpText = otpText,
    maxOTPCount = 4,
    onOtpTextChange = {newOTP: String ->
        otpText = newOTP
    },
    isError = isError,
    errorMsg = errorText,
    onOTPFilled = {otp: String ->
        // otp string is complete otp.
        // callback trigurred when all boxes are filled.
    }
)
m
Ohh, I see. I saw a similar approach elsewhere. Thanks for this @Farhazul Mullick