[iOS] Fix Picker CharacterSpacing lost after item selection when Title is set#34974
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34974Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34974" |
There was a problem hiding this comment.
Pull request overview
Fixes an iOS/MacCatalyst regression where Picker.CharacterSpacing is lost after an item is selected when Title is set, by re-applying kerning after the native text is assigned.
Changes:
- Re-apply
CharacterSpacingafterplatformPicker.Textis set during selection updates (both programmatic and “Done” selection paths). - Add an iOS device test to validate
CharacterSpacingis preserved acrossSelectedIndexupdates.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs | Adds a device test asserting character spacing is preserved when SelectedIndex changes programmatically. |
| src/Core/src/Platform/iOS/PickerExtensions.cs | Ensures UpdatePicker always re-applies character spacing after setting Text. |
| src/Core/src/Handlers/Picker/PickerHandler.iOS.cs | Re-applies character spacing after assigning Text in the “Done button” selection flow. |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please try the ai's suggestions?
…ability (#35133) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! > **Depends on #35136** (pipeline category detection — should merge first) ## What this does Two things: ### 1. UI test category detection in PR review During the PR review workflow, Step 0.5 detects which UI test categories the PR impacts and writes the result to the AI summary comment. This gives reviewers visibility into which UI tests are relevant. **Detection** reuses the 3-tier script from #35136 (test attributes → source paths → AI reasoning). **AI summary** shows a new 🧪 UI Tests section with detected categories before the gate section. ### 2. Gate reliability fixes Multiple fixes to make the gate (`verify-tests-fail.ps1`) more deterministic: | Fix | Problem it solves | |-----|-------------------| | **Absolute path resolution** | Gate scripts not found on Linux CI agents (`Resolve-Path`, `GetFullPath`) | | **File existence check** | Instant cryptic failure when verify script is missing — now logs clear error | | **3x retry on ENV ERROR** | Emulator timeouts, ADB failures, app crashes — transient issues that pass on retry | | **Strip bad report blocks** | Old verify script produces `Passed: False` with empty counts — stripped instead of shown | | **Gate log in fallback** | When report is missing, shows last 20 lines of gate output instead of just `❌ FAILED / Platform: IOS` | ## Files | File | Changes | |------|---------| | `.github/scripts/Review-PR.ps1` | Step 0.5 category detection + all 5 gate fixes | | `.github/scripts/post-ai-summary-comment.ps1` | Add `uitests` phase to render detected categories | | `.github/pr-review/pr-preflight.md` | Step 7: AI identifies impacted UI test categories | ## Validation — PR reviewer builds (Apr 26) 10 builds against real PRs — all succeeded ✅. Category detection shown in AI summary comment. | PR | Categories Detected | Build | AI Summary | |----|-------------------|-------|------------| | #35037 (WebView theme) | `ViewBaseTests,WebView` | [13940071](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=13940071) | [comment](#35037 (comment)) | | #35031 (Shell memory leak) | `Shell` | [13940072](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=13940072) | [comment](#35031 (comment)) | | #35020 (XAML Hot Reload) | _(none — XAML only)_ | [13940073](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=13940073) | ✅ Shows "No UI test categories" | | #35008 (Shell SearchHandler) | `Shell` | [13940074](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=13940074) | ✅ | | #34997 (RadioButton gradient) | `RadioButton,ViewBaseTests` | [13940075](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=13940075) | ✅ | | #34980 (DatePicker rotation) | `ViewBaseTests` | [13940076](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=13940076) | ✅ | | #34974 (Picker CharacterSpacing) | `ViewBaseTests` | [13940077](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=13940077) | ✅ | | #34923 (SwipeView threshold) | `SwipeView,ViewBaseTests` | [13940078](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=13940078) | ✅ | | #34907 (CollectionView ScrollTo) | `CollectionView` | [13940079](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=13940079) | ✅ | | #34845 (RefreshView binding) | `RefreshView,ViewBaseTests` | [13940080](https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=13940080) | ✅ | --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
MauiBot
left a comment
There was a problem hiding this comment.
🤖 Automated review — alternative fix proposed
The expert-reviewer evaluation compared the PR fix against #1 automatically generated candidates and selected try-fix-1 as the strongest fix.
Why: try-fix-1 passed all 4 regression tests empirically using a SetTextWithCharacterSpacing helper that atomically constructs NSAttributedString with KerningAdjustment instead of the PR's fragile read-modify-write pattern. It also covers the OnEditing handler gap the PR missed and avoids the MacCatalyst test crash entirely.
Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.
Candidate diff (`try-fix-1`)
diff --git a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
index a54299d290..11fe557578 100644
--- a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
+++ b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
@@ -255,7 +255,7 @@ namespace Microsoft.Maui.Handlers
if (VirtualView == null || PlatformView == null || pickerSource == null)
return;
- PlatformView.Text = VirtualView.GetItem(pickerSource.SelectedIndex);
+ PlatformView.SetTextWithCharacterSpacing(VirtualView.GetItem(pickerSource.SelectedIndex), VirtualView.CharacterSpacing);
VirtualView.SelectedIndex = pickerSource.SelectedIndex;
}
@@ -423,7 +423,7 @@ namespace Microsoft.Maui.Handlers
// Reset the TextField's Text so it appears as if typing with a keyboard does not work.
var selectedIndex = virtualView.SelectedIndex;
- platformView.Text = virtualView.GetItem(selectedIndex);
+ platformView.SetTextWithCharacterSpacing(virtualView.GetItem(selectedIndex), virtualView.CharacterSpacing);
// Also clears the undo stack (undo/redo possible on iPads)
platformView.UndoManager?.RemoveAllActions();
diff --git a/src/Core/src/Platform/iOS/PickerExtensions.cs b/src/Core/src/Platform/iOS/PickerExtensions.cs
index c2de04d1fd..13e429ae28 100644
--- a/src/Core/src/Platform/iOS/PickerExtensions.cs
+++ b/src/Core/src/Platform/iOS/PickerExtensions.cs
@@ -1,6 +1,7 @@
#nullable enable
using System;
using Foundation;
+using UIKit;
namespace Microsoft.Maui.Platform
{
@@ -36,18 +37,45 @@ namespace Microsoft.Maui.Platform
platformPicker.UpdateAttributedPlaceholder(new NSAttributedString(picker.Title ?? string.Empty, null, picker?.TitleColor?.ToPlatform()));
}
+ /// <summary>
+ /// Sets text on a picker using AttributedText with kern to avoid iOS clearing
+ /// character spacing when plain Text is assigned.
+ /// </summary>
+ /// <remarks>
+ /// Unlike UpdateCharacterSpacing (which reads back AttributedText and adds kern),
+ /// this creates a fresh NSAttributedString to work around an iOS issue where
+ /// reading AttributedText immediately after setting Text doesn't reliably
+ /// preserve kern in all handler lifecycle contexts.
+ /// </remarks>
+ internal static void SetTextWithCharacterSpacing(this MauiPicker platformPicker, string text, double characterSpacing)
+ {
+ if (characterSpacing == 0)
+ {
+ platformPicker.Text = text;
+ return;
+ }
+
+ var attributes = new UIKit.UIStringAttributes
+ {
+ KerningAdjustment = (float)characterSpacing
+ };
+ platformPicker.AttributedText = new NSAttributedString(text ?? string.Empty, attributes);
+ }
+
internal static void UpdatePicker(this MauiPicker platformPicker, IPicker picker, int? newSelectedIndex = null)
{
var selectedIndex = newSelectedIndex ?? picker.SelectedIndex;
if (selectedIndex != -1)
{
- platformPicker.Text = picker.GetItem(selectedIndex);
+ platformPicker.SetTextWithCharacterSpacing(picker.GetItem(selectedIndex), picker.CharacterSpacing);
}
else
{
platformPicker.Text = null;
platformPicker.UpdatePickerTitle(picker);
+ // Applies character spacing to the placeholder when no item is selected
+ platformPicker.UpdateCharacterSpacing(picker);
}
var pickerView = platformPicker.UIPickerView;
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Issue Details
Root Cause
Description of Change
Issues Fixed
Fixes #34971
Validated the behaviour in the following platforms
Output
Before.mov
After.mov