Skip to content

iOS TextBox caret seek gesture - #23920

Open
MartinZikmund wants to merge 5 commits into
masterfrom
dev/mazi/ios-text-seek
Open

iOS TextBox caret seek gesture#23920
MartinZikmund wants to merge 5 commits into
masterfrom
dev/mazi/ios-text-seek

Conversation

@MartinZikmund

@MartinZikmund MartinZikmund commented Jul 30, 2026

Copy link
Copy Markdown
Member

Fixes #23871

PR Type:

✨ Feature

What changed? 🚀

Caret seek gesture on software keyboard is now supported

ScreenRecording_07-30-2026.10-52-15_1.mp4

PR Checklist ✅

MartinZikmund and others added 2 commits July 29, 2026 15:33
…sture

Holding the space bar puts the iOS keyboard into trackpad mode, but under the
Skia renderer dragging did not move the caret: none of the three UITextInput
floating-cursor selectors were implemented, so the gesture never reached the
managed TextBox.

Adds TextBox.ProcessCaretDragGesture, a platform-agnostic caret-drag state
machine in Uno.UI, and implements begin/update/endFloatingCursorAtPoint on both
invisible native proxies to feed it. The gesture previews the caret while
dragging and commits the selection exactly once on End, so a whole drag raises a
single SelectionChanged rather than one per callback. Offsets are cumulative
from the gesture origin, matching how UIKit reports the pan translation.

base is deliberately not called: UIKit would map the point against the proxy's
system-font layout, which bears no relation to the rendered text. Hit-testing
runs managed-side against the Skia glyphs instead, and native selection changes
are ignored for the duration of the gesture.

Fixes #23871

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNXnVDJ22kXAgQL2zNWseC
The caret previewed during a caret-drag now uses SelectionHighlightColor instead
of the Foreground, so it reads as a transient selection affordance rather than an
ordinary caret.

The gesture forces a thumbless, non-blinking caret while it runs, but never put
the previous mode back. On the Android and Desktop touch conventions, where a tap
leaves an insertion handle, that handle was lost for good after a drag.

Related to #23871

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNXnVDJ22kXAgQL2zNWseC
@github-actions github-actions Bot added platform/ios 🍎 Categorizes an issue or PR as relevant to the iOS platform area/skia ✏️ Categorizes an issue or PR as relevant to Skia labels Jul 30, 2026
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 7m 28s —— View job


Code Review

  • Gather context and read changed files
  • Review TextBox.CaretDrag.skia.cs (new state machine)
  • Review InvisibleTextBoxFloatingCursor.cs (new helper)
  • Review native proxy modifications (InvisibleTextBoxViewExtension, SinglelineInvisibleTextBoxView, MultilineInvisibleTextBoxView)
  • Review runtime tests (Given_TextBox.skia.cs)
  • Review minor changes (TextBox.skia.cs, TextBoxView.skia.cs, TextBlock.skia.cs)
  • Post inline comments and summary

Summary

Verdict: ship (minor findings only)

The design is solid. The core ideas are all correct:

  • The Begin/Update/End state machine cleanly separates preview from commit, giving a single SelectionChanged per drag rather than one per callback. The cumulative-offset contract is well-documented and the tests verify it directly.
  • UIKit's unbalanced Begin/Begin/End sequences are handled correctly via ??=: the second Begin re-anchors without overwriting the saved pre-gesture CaretMode, so restoration works regardless of how many Begin callbacks UIKit fires.
  • The guard !textBoxView.IsCaretDragActive in SelectedTextRange.set is the right place to prevent native selection feedback from interfering with the managed caret during the gesture.
  • CancelCaretDrag() in TextBoxView.OnFocusStateChanged correctly cleans up any in-flight drag when focus moves away.
  • The SelectInternal(index, 0) commit in EndCaretDrag is called after CaretMode is restored, which is critical: SelectPartial reads CaretMode to decide whether to promote ThumblessCaretHidden → ThumblessCaretShowing and whether to keep touch-mode handles.

Findings by severity

# Severity File Finding
1 Medium TextBox.CaretDrag.skia.cs:59 _cachedCaretDragBrush replaced without Dispose() — leaks a CompositionColorBrush each time the selection highlight colour changes mid-drag (same gap exists in GetOpaqueCaretBrush in TextBox.skia.cs).
2 Low TextBox.CaretDrag.skia.cs:147 Math.Clamp(y, Math.Min(minY, maxY), Math.Max(minY, maxY)) — the inner Min/Max are redundant since Uno's layout always places the first character at or above the last; Math.Clamp(y, minY, maxY) is sufficient.
3 Low Given_TextBox.skia.cs:6556 The blink-suppression test uses 8 × 200 ms (1.6 s) to assert the caret stays visible; the timer is already stopped so this cannot race, but the poll count wastes CI time. The post-gesture blink check (20 × 100 ms = 2 s) could flip flaky on a slow CI shard against a 500 ms blink interval — a 30–40 iteration budget would be safer.
4 Info InvisibleTextBoxFloatingCursor.cs:46 The return value of ProcessCaretDragGesture(End) is silently discarded; an explicit _ = discard would clarify intent.

Cross-cutting notes

  • Platform targeting is correct: the core state machine lives in .skia.cs, the UIKit wiring lives in the AppleUIKit runtime project, and the shared helper InvisibleTextBoxFloatingCursor is correctly placed there too.
  • No event Action / event Action<T> violations found.
  • No async void or unguarded CancellationToken concerns — the gesture callbacks are all synchronous.
  • Conventional Commits format is followed by both commits (feat(ios) and fix(skia)).
  • PR description checklist still needs: the issue number filled in (closes #23871 is already in the body text but the checklist line is blank), and the "What changed?" section is empty.

Comment thread src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.CaretDrag.skia.cs Outdated
Comment thread src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.CaretDrag.skia.cs Outdated
@MartinZikmund MartinZikmund changed the title iOS TextBox seek gesture iOS TextBox caret seek gesture Jul 30, 2026
Drops the redundant Min/Max around the vertical clamp bounds, discards the
ignored End result explicitly, and retimes the blink test — fewer iterations
while frozen, more headroom for the resume check so a loaded CI shard does not
turn it flaky.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNXnVDJ22kXAgQL2zNWseC
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 3m 25s —— View job


Follow-up Review (after 44760a4)

  • Read updated files to verify applied fixes
  • Check for any new issues in the updated commit
  • Confirm no inline comments warranted on the new code
  • Update summary

Verdict: ship

All three applied findings from the initial review are correctly addressed in 44760a4. No new issues introduced.

Applied fixes verified

Finding Change Assessment
Math.Clamp redundancy (TextBox.CaretDrag.skia.cs:147) Math.Clamp(anchor.Y + cumulativeOffset.Y, minY, maxY) — inner Min/Max removed Correct. minY ≤ maxY is a layout invariant; the wrapper was unjustified defensiveness.
Blink-test timing (Given_TextBox.skia.cs) Freeze loop 8×200 ms → 4×200 ms; resume poll 20×100 ms → 40×100 ms Correct. 4 iterations still spans more than one 500 ms blink interval; 40 iterations (~4 s) gives ample headroom on loaded CI shards.
Explicit End discard (InvisibleTextBoxFloatingCursor.cs:46) _ = extension?.ProcessCaretDragGesture(TextBox.CaretDragPhase.End, default) Correct. Matches the existing discard idiom in TextBox.skia.cs:92.

Declined finding — refutation accepted

Finding #1 (dispose _cachedCaretDragBrush before replacing): the author's explanation holds. CompositionColorBrush on the Skia target carries only a Color and paints through a static readonly SKPaint shared across all instances (CompositionColorBrush.skia.cs). CompositionObject.Dispose() calls only StopAllAnimations() — no native resource is freed. The previous brush also stays referenced via displayBlock.RenderCaret until the next assignment, so disposing it early would be unsafe, not merely redundant.

Cross-cutting — no issues

  • Platform targeting remains correct: core state machine in .skia.cs, UIKit wiring in the AppleUIKit runtime project, InvisibleTextBoxFloatingCursor helper shared there.
  • Timer management in EndCaretDrag is safe: the _timer.Start() after CaretMode is restored is a harmless double-start when restoring to ThumblessCaretShowing (the setter already starts it); for CaretWithThumbsOnlyEndShowing the setter stops it and the explicit start is skipped.
  • The ??= guard for unbalanced UIKit Begin/Begin/End sequences is correct and tested.
  • No event Action, async void, or CancellationToken concerns.
  • Conventional Commits format is followed.

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-23920/wasm-skia-net9/index.html

Reverts the SelectionHighlightColor tint from 43fa17f — the dragged caret keeps
the ordinary Foreground brush. The caret-mode restore from that commit stays: it
fixes the insertion handle being lost after a drag on the Android and Desktop
touch conventions.

Adds autoscrolling, which the gesture previously skipped. With text wider than
the viewport the previewed caret could walk off-screen, because the hit-test runs
against the full text layout while UpdateScrolling only ever looked at the
committed selection. It now follows the preview index. The anchor and hit-test
both work in text coordinates, which are independent of the scroll offset, so
scrolling mid-gesture cannot skew the mapping.

Related to #23871

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNXnVDJ22kXAgQL2zNWseC
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 1m 15s —— View job


Follow-up Review (after new push)

  • Read InvisibleTextBoxFloatingCursor.cs to assess the code-quality finding
  • Apply fix if valid
  • Post inline comment
  • Update summary

Verdict: ship (one nit)

One code-quality finding carried forward from the github-code-quality bot:

# Severity File Finding
1 Info InvisibleTextBoxFloatingCursor.cs:26,38 == true on a bool? should be ?? false — identical semantics, removes the CodeQL SimplifyBooleanExpression warning. Inline suggestion posted on line 38; same fix applies to line 26.

I prepared the fix locally (e4dc9b71) but push was denied by branch protections — the author will need to apply it. The two-character change on each line:

// Before
IsActive = extension?.ProcessCaretDragGesture(...) == true;

// After
IsActive = extension?.ProcessCaretDragGesture(...) ?? false;

Everything else from the previous two rounds remains ship-ready — no new issues introduced in this push.

@MartinZikmund
MartinZikmund marked this pull request as ready for review July 30, 2026 10:10
Copilot AI review requested due to automatic review settings July 30, 2026 10:10
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 3m 0s —— View job


Code Review

  • Read current file state
  • Check for remaining open issues
  • Post inline comments for remaining findings
  • Update summary

Verdict: ship (two nits)

Full review pass completed across all 9 changed files (4 new, 5 modified). The design is sound and all findings from the previous two rounds have been correctly addressed. Two new nits surfaced in the == true pattern scan:

# Severity File Finding
1 Info InvisibleTextBoxFloatingCursor.cs:26 == true on bool? — same CodeQL SimplifyBooleanExpression warning as line 38 (already fixed in the previous round); line 26 (Begin) was missed. Inline suggestion posted.
2 Info InvisibleTextBoxViewExtension.cs:233 IsCaretDragActive => _owner?.TextBox?.IsCaretDragActive == true — same pattern, same fix: ?? false. Inline suggestion posted.

I applied both fixes locally (2bc0de4d) but the push was denied by branch protection — the three-character change on each line (== true?? false) will need to be applied by the author.

Cross-cutting — no new issues

All previously confirmed items remain correct:

  • State machine (Begin/Update/End), ??= guard for unbalanced UIKit sequences, CaretMode restore-before-commit ordering, _timer.Stop() after mode change, CancelCaretDrag() on focus loss — all verified clean.
  • Platform targeting is correct: core state machine in .skia.cs, UIKit wiring in the AppleUIKit runtime project, shared helper InvisibleTextBoxFloatingCursor placed there.
  • No event Action, async void, or CancellationToken concerns.
  • Conventional Commits format followed across all 4 commits.
  • Runtime test coverage is present and well-structured.

Replaces `== true` with `?? false` on both ProcessCaretDragGesture call sites,
clearing the CodeQL SimplifyBooleanExpression warning. Same semantics — null
still means the gesture was declined.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WNXnVDJ22kXAgQL2zNWseC
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MartinZikmund's task in 1m 2s —— View job


Follow-up Review (after 6073e99)

  • Load review tools
  • Read current file state
  • Verify fix applied correctly on both lines
  • Scan full diff for any new issues
  • Update summary

Verdict: ship

The single-commit change in 6073e99c is minimal and correct — no new issues introduced.

Applied fix verified

Finding Change Assessment
== true on bool? in InvisibleTextBoxFloatingCursor.cs (lines 26 & 38) extension?.ProcessCaretDragGesture(...) == trueextension?.ProcessCaretDragGesture(...) ?? false Correct. Identical semantics (null extension → false), removes the CodeQL SimplifyBooleanExpression warning on both call sites. The End path correctly keeps the _ = discard (no return value used there).

Cross-cutting — no issues

All findings from the three prior review rounds have been resolved or declined with accepted justification:

  • Math.Clamp redundancy — applied in 44760a4d
  • Blink-test timing — applied in 44760a4d
  • Explicit End discard — applied in 44760a4d
  • CompositionColorBrush dispose — declined; Dispose() is a no-op on Skia and early disposal would be unsafe
  • == true?? false — applied in 6073e99c (both line 26 and line 38)

No event Action, async void, CancellationToken, or platform-targeting concerns remain. Conventional Commits format is followed throughout.

public void Begin(InvisibleTextBoxViewExtension? extension, CGPoint point)
{
_origin = point;
IsActive = extension?.ProcessCaretDragGesture(TextBox.CaretDragPhase.Begin, default) ?? false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same == true on a bool? as line 38 (flagged by CodeQL). The null-coalescing form is cleaner and removes the SimplifyBooleanExpression warning:

Suggested change
IsActive = extension?.ProcessCaretDragGesture(TextBox.CaretDragPhase.Begin, default) ?? false;
IsActive = extension?.ProcessCaretDragGesture(TextBox.CaretDragPhase.Begin, default) ?? false;

// While a caret drag is running, UIKit's own selection updates are computed against the proxy's
// system-font layout, which bears no relation to the Skia-rendered text. The managed side owns
// the caret for the duration of the gesture.
internal bool IsCaretDragActive => _owner?.TextBox?.IsCaretDragActive == true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same == true pattern on a bool? (the null-conditional chain makes the type nullable). Same fix as the two lines in InvisibleTextBoxFloatingCursor.cs:

Suggested change
internal bool IsCaretDragActive => _owner?.TextBox?.IsCaretDragActive == true;
internal bool IsCaretDragActive => _owner?.TextBox?.IsCaretDragActive ?? false;

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

Caret blinking may not reliably resume after ending a caret-drag gesture (e.g., when the pre-drag caret mode was hidden), due to incomplete timer restart logic.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Adds support for the iOS “floating cursor” (spacebar trackpad) caret-seek gesture when using the Skia TextBox implementation, by routing UIKit floating-cursor callbacks into a managed caret-drag preview/commit flow.

Changes:

  • Introduces a managed caret-drag state machine (Begin/Update/End/Cancel) that previews caret position without committing selection until gesture end.
  • Hooks UIKit floating cursor callbacks (Begin/Update/EndFloatingCursor) in the invisible iOS proxy text controls and suppresses native→managed selection sync during the drag.
  • Adds Skia runtime tests covering preview behavior, commit semantics, clamping/scroll-follow, and focus-cancel behavior.
File summaries
File Description
src/Uno.UI/UI/Xaml/Controls/TextBox/TextBoxView.skia.cs Cancels an in-flight caret drag when the TextBox loses focus.
src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.skia.cs Renders/scolls based on caret-drag preview index while drag is active.
src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.CaretDrag.skia.cs Implements caret-drag gesture phases with preview+commit behavior and caret-mode handling.
src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.skia.cs Exposes RenderCaret getter so tests can validate rendered caret index.
src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBox.skia.cs Adds runtime tests for caret-drag gesture behavior.
src/Uno.UI.Runtime.Skia.AppleUIKit/UI/Xaml/Controls/TextBox/SinglelineInvisibleTextBoxView.cs Overrides UIKit floating cursor callbacks and suppresses native selection sync during caret drag.
src/Uno.UI.Runtime.Skia.AppleUIKit/UI/Xaml/Controls/TextBox/MultilineInvisibleTextBoxView.cs Same as single-line proxy, for multiline UITextView-based proxy.
src/Uno.UI.Runtime.Skia.AppleUIKit/UI/Xaml/Controls/TextBox/InvisibleTextBoxViewExtension.cs Adds managed forwarding helpers for caret-drag gestures and exposes caret-drag active state.
src/Uno.UI.Runtime.Skia.AppleUIKit/UI/Xaml/Controls/TextBox/InvisibleTextBoxFloatingCursor.cs Translates UIKit floating cursor points into cumulative offsets for managed gesture processing.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Low

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +146 to +155
// Restored before committing so the selection transitions in SelectPartial see the real mode.
if (previousMode is { } mode)
{
CaretMode = mode;
}

if (CaretMode is CaretDisplayMode.ThumblessCaretShowing)
{
_timer.Start(); // resume blinking
}
Copilot AI review requested due to automatic review settings July 30, 2026 10:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

Caret blinking may not resume if the gesture starts while the caret is in the hidden blink phase, leaving the caret stuck invisible after the drag ends.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Comments suppressed due to low confidence (1)

src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.CaretDrag.skia.cs:155

  • EndCaretDrag stops the caret blink timer during the gesture but only restarts it when restoring to ThumblessCaretShowing. If the drag begins while the caret is currently in ThumblessCaretHidden (mid-blink), restoring that mode leaves the timer stopped and the caret can remain hidden indefinitely after the gesture ends.
		if (CaretMode is CaretDisplayMode.ThumblessCaretShowing)
		{
			_timer.Start(); // resume blinking
		}
  • Files reviewed: 9/9 changed files
  • Comments generated: 0 new
  • Review effort level: Low

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-23920/wasm-skia-net9/index.html

@nventive-devops

Copy link
Copy Markdown
Contributor

The build 224946 found UI Test snapshots differences: skia-linux-screenshots: 57, skia-windows-screenshots: 2348, wasm: 11

Details
  • skia-linux-screenshots: 57 changed over 2348

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • ClipboardTests.png-dark
    • DropDownButtonPage.png-dark
    • DropDownButtonPage.png
    • CalendarView_Theming.png-dark
    • CalendarView_Theming.png
    • ContextRequested.png-dark
    • ContextRequested.png
    • Gamepad_CurrentReading.png-dark
    • Gamepad_CurrentReading.png
    • Gamepad_Enumeration.png-dark
    • Gamepad_Enumeration.png
    • ButtonClippingTestsControl.png-dark
    • ButtonClippingTestsControl.png
    • Focus_FocusVisual_Properties.png-dark
    • DisplayInformation.png-dark
    • ImageIconPage.png-dark
    • Examples.png-dark
    • Examples.png
    • ExpanderColorValidationPage.png-dark
    • ExpanderColorValidationPage.png
  • skia-windows-screenshots: 2348 changed over 2392

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • Accessibility_ScreenReader.png-dark
    • Attributed_text_Simple.png
    • AutomationPropertiesExtensions_Role.png
    • AutoSuggestBox_Keyboard.png
    • BasicAcrylicBrushTest2.png-dark
    • Border_CornerRadius_Alignments.png-dark
    • Border_CornerRadius_Toggle.png-dark
    • Border_Simple_No_Background.png
    • Border_With_RotateTransform.png-dark
    • AnimatedIconPage.png-dark
    • Border_With_RotateTransform.png
    • ApplicationViewMode.png
    • Button_Opacity_Automated.png-dark
    • AppXamlDefinedResources.png-dark
    • Button_Opacity_Automated.png
    • AutoBorderStretchwithrightmargin.png-dark
    • Capture.png-dark
    • AutoSuggestBox_BitmapIcon.png-dark
    • Capture.png
    • BadgeNotificationTests.png
  • wasm: 11 changed over 1076

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • UITests.Uno_Web.Http.CookieManagerTests
    • UITests.Shared.Windows_UI_Xaml_Controls.MediaPlayerElement.MediaPlayerElement_Minimal
    • UITests.Shared.Windows_UI_Xaml_Controls.MediaPlayerElement.MediaPlayerElement_Ogg_Extension
    • UITests.Shared.Microsoft_UI_Xaml_Controls.ExpanderTests.WinUIExpanderPage
    • UITests.Microsoft_UI_Xaml_Controls.WebView2Tests.WebView2_NavigationProperties
    • UITests.Windows_UI_Xaml_Controls.CalendarView.CalendarView_Theming
    • SamplesApp.Wasm.Windows_UI_Xaml_Controls.ListView.ListView_IsSelected
    • SamplesApp.Microsoft_UI_Xaml_Controls.WebView2Tests.WebView2_EnableDevTools
    • Uno.UI.Samples.Content.UITests.WebView.WebView_AnchorNavigation
    • SamplesApp.Windows_UI_Xaml_Controls.ListView.ListViewSelectedItems
    • UITests.Windows_UI_Xaml_Media_Animation.ColorAnimation_Background

@ajpinedam

Copy link
Copy Markdown
Contributor

Tested on device (iPhone 14 Pro, iOS 26).

Single-line works well — the caret tracks the gesture accurately.

Multiline is where it breaks down. The invisible proxy view is sized once when entry starts and never updated afterwards, so as the TextBox grows the proxy keeps its original frame. Its wrap points then diverge from the Skia-rendered text, and vertical (line-to-line) movement lands on the wrong line.

Two commits from #23909 address exactly this, and I confirmed they cherry-pick cleanly onto your branch:

  • 974de91660 — hook TextBox.SizeChanged to re-apply the proxy frame during entry. XamlRoot.InvalidateOverlays() currently has no caller on Apple UIKit, so the frame is only ever set at focus time.
  • 8737e750b9 — size the off-screen proxy to the ContentElement text area rather than the control bounds, and match its font size to the TextBox.

Happy to push them onto your branch if that's easier.

@ajpinedam ajpinedam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comments of my findngs above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/skia ✏️ Categorizes an issue or PR as relevant to Skia platform/ios 🍎 Categorizes an issue or PR as relevant to the iOS platform

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS][Skia] Keyboard spacebar trackpad gesture (hold space to move cursor) does not move the TextBox caret

5 participants