Skip to content

Commit 8655f8a

Browse files
authored
Fixed crash during triple tap on empty textfield with usingNativeTextInput flag enabled (#2910)
Fixed the crash during triple tap on empty textfield with `usingNativeTextInput` flag enabled by changing non-optional cast of `UITextRange` to `TextRange` to optional Fixes: [CMP-9881](https://youtrack.jetbrains.com/issue/CMP-9881) [ios].niti. Crash during triple tap on empty line with feature flag enabled ## Testing This should be tested by QA ## Release Notes ### Fixes - iOS - Fixed the crash during triple tap on empty textfield with `usingNativeTextInput` flag enabled
1 parent 69abcd7 commit 8655f8a

1 file changed

Lines changed: 22 additions & 14 deletions

File tree

compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/window/IntermediateTextInputUIView.ios.kt

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@ internal class IntermediateTextInputUIView(
275275
* @return A substring of a document that falls within the specified range.
276276
*/
277277
override fun textInRange(range: UITextRange): String? {
278-
return input?.textInRange(range.toTextRange())
278+
val textRange = range.toTextRange() ?: return null
279+
return input?.textInRange(textRange)
279280
}
280281

281282
/**
@@ -285,8 +286,9 @@ internal class IntermediateTextInputUIView(
285286
* @param withText A string to replace the text in range.
286287
*/
287288
override fun replaceRange(range: UITextRange, withText: String) {
289+
val textRange = range.toTextRange() ?: return
288290
input?.withBatch {
289-
input?.replaceRange(range.toTextRange(), withText)
291+
input?.replaceRange(textRange, withText)
290292
}
291293
}
292294

@@ -482,19 +484,20 @@ internal class IntermediateTextInputUIView(
482484
withinRange: UITextRange
483485
): NSInteger {
484486
return if (usingNativeTextInput) {
485-
if (!withinRange.isValid()) { return 0L }
487+
val withinTextRange = withinRange.toTextRange() ?: return 0L
486488
val intermediatePosition = (position as? IntermediateTextPosition)?.position ?: 0
487-
(intermediatePosition - withinRange.toTextRange().start).toLong()
489+
(intermediatePosition - withinTextRange.start).toLong()
488490
} else 0L
489491
}
490492

491493
override fun positionWithinRangeAtCharacterOffset(
492494
range: UITextRange,
493495
atCharacterOffset: NSInteger
494496
): UITextPosition = if (usingNativeTextInput) {
495-
val textRange = range.toTextRange()
497+
val fallback = IntermediateTextPosition(0)
498+
val textRange = range.toTextRange() ?: return fallback
496499
(textRange.start + atCharacterOffset.toInt()).takeIf { range.isValid() && it in textRange }
497-
?.let { IntermediateTextPosition(it) } ?: IntermediateTextPosition(0)
500+
?.let { IntermediateTextPosition(it) } ?: fallback
498501
} else {
499502
IntermediateTextPosition(0)
500503
}
@@ -503,11 +506,13 @@ internal class IntermediateTextInputUIView(
503506
range: UITextRange,
504507
farthestInDirection: UITextLayoutDirection
505508
): UITextPosition = if (usingNativeTextInput) {
509+
val fallback = IntermediateTextPosition(0)
510+
val textRange = range.toTextRange() ?: return fallback
506511
PlatformTextLayoutDirection(farthestInDirection)?.let { direction ->
507-
input?.positionWithinRange(range.toTextRange(), direction)?.let {
512+
input?.positionWithinRange(textRange, direction)?.let {
508513
IntermediateTextPosition(it)
509514
}
510-
} ?: IntermediateTextPosition(0)
515+
} ?: fallback
511516
} else {
512517
IntermediateTextPosition(0)
513518
}
@@ -540,8 +545,10 @@ internal class IntermediateTextInputUIView(
540545

541546
override fun firstRectForRange(range: UITextRange): CValue<CGRect> {
542547
if (usingNativeTextInput) {
543-
return input?.firstSelectionRectForRange(range.toTextRange())?.asCGRect()
544-
?: CGRectZero.readValue()
548+
val fallback = CGRectZero.readValue()
549+
val textRange = range.toTextRange() ?: return fallback
550+
return input?.firstSelectionRectForRange(textRange)?.asCGRect()
551+
?: fallback
545552
} else {
546553
return CGRectNull.readValue()
547554
}
@@ -807,7 +814,7 @@ internal class IntermediateTextInputUIView(
807814
}
808815

809816
private fun UITextRange.isValid(): Boolean {
810-
val range = this.toTextRange()
817+
val range = this.toTextRange() ?: return false
811818
val textEndPos = input?.endOfDocument() ?: 0
812819
return range.start in 0..range.end && range.end <= textEndPos
813820
}
@@ -865,9 +872,10 @@ private class IntermediateTextRange(
865872
}
866873
}
867874

868-
private fun UITextRange.toTextRange(): TextRange {
869-
val start = (start() as IntermediateTextPosition).position
870-
val end = (end() as IntermediateTextPosition).position
875+
// Despite UITextRange being declared as non-null, iOS can still pass null to methods that take a UITextRange parameter.
876+
private fun UITextRange.toTextRange(): TextRange? {
877+
val start = (start() as? IntermediateTextPosition)?.position ?: return null
878+
val end = (end() as? IntermediateTextPosition)?.position ?: return null
871879
return TextRange(start, end)
872880
}
873881

0 commit comments

Comments
 (0)