[iOS] Fix Picker CharacterSpacing ignored on initial load#34957
[iOS] Fix Picker CharacterSpacing ignored on initial load#34957SyedAbdulAzeemSF4852 wants to merge 3 commits intodotnet:mainfrom
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://github.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34957Or
iex "& { $(irm https://github.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34957" |
There was a problem hiding this comment.
Pull request overview
Fixes iOS/Mac Catalyst Picker so CharacterSpacing is applied on initial load and preserved when the selected item text is updated.
Changes:
- Added iOS device tests validating initial
CharacterSpacingapplication and preservation afterSelectedIndexupdates. - Reapplies
CharacterSpacingwheneverMauiPicker.UpdatePicker(...)updates the displayed text. - Defers
CharacterSpacingmapping until the platform view is loaded (iOS handler mapping).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs | Adds coverage for initial and post-selection-change CharacterSpacing behavior on iOS. |
| src/Core/src/Platform/iOS/PickerExtensions.cs | Ensures UpdatePicker reapplies CharacterSpacing after updating Text/placeholder. |
| src/Core/src/Handlers/Picker/PickerHandler.iOS.cs | Updates iOS mapper to apply CharacterSpacing after load; minor formatting change nearby. |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the Ai's summary?
@kubaflo , Reviewed the AI summary. The gate was reported as failed, but I verified locally that the test fails without the fix and passes with the fix applied.
Applied the suggested simplification by removing the OnLoaded deferral in MapCharacterSpacing and replacing it with a direct UpdateCharacterSpacing call. Regarding the UpdatePickerFromPickerSource path — that regression (character spacing lost when user physically selects from the picker wheel) is tracked and fixed in #34974. |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the ai's summary?
1019e1a to
fa1d2e7
Compare
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 #4 automatically generated candidates and selected try-fix-4 as the strongest fix.
Why: try-fix-4 passes tests in two verified runs and addresses all four CharacterSpacing failure paths (PropertyMapper ordering, UpdatePicker, UpdatePickerFromPickerSource, OnEditing) with no public API surface changes, while the PR's fix only covers 1/4 paths and failed the Gate. try-fix-2 also passes but introduces a public Text setter override in MauiPicker which adds API surface unnecessarily.
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-4`)
diff --git a/src/Core/src/Handlers/Picker/PickerHandler.cs b/src/Core/src/Handlers/Picker/PickerHandler.cs
index b5be3950c5..7881781acd 100644
--- a/src/Core/src/Handlers/Picker/PickerHandler.cs
+++ b/src/Core/src/Handlers/Picker/PickerHandler.cs
@@ -22,9 +22,9 @@ namespace Microsoft.Maui.Handlers
#if __ANDROID__
[nameof(IView.IsEnabled)] = MapIsEnabled,
#endif
+ [nameof(IPicker.SelectedIndex)] = MapSelectedIndex,
[nameof(IPicker.CharacterSpacing)] = MapCharacterSpacing,
[nameof(IPicker.Font)] = MapFont,
- [nameof(IPicker.SelectedIndex)] = MapSelectedIndex,
[nameof(IPicker.TextColor)] = MapTextColor,
[nameof(IPicker.Title)] = MapTitle,
[nameof(IPicker.TitleColor)] = MapTitleColor,
diff --git a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
index 9189e7ca44..caf17fef36 100644
--- a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
+++ b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
@@ -254,6 +254,7 @@ namespace Microsoft.Maui.Handlers
return;
PlatformView.Text = VirtualView.GetItem(pickerSource.SelectedIndex);
+ PlatformView.UpdateCharacterSpacing(VirtualView);
VirtualView.SelectedIndex = pickerSource.SelectedIndex;
}
@@ -365,6 +366,7 @@ namespace Microsoft.Maui.Handlers
var selectedIndex = virtualView.SelectedIndex;
platformView.Text = virtualView.GetItem(selectedIndex);
+ platformView.UpdateCharacterSpacing(virtualView);
// 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..1b026e0960 100644
--- a/src/Core/src/Platform/iOS/PickerExtensions.cs
+++ b/src/Core/src/Platform/iOS/PickerExtensions.cs
@@ -50,6 +50,8 @@ namespace Microsoft.Maui.Platform
platformPicker.UpdatePickerTitle(picker);
}
+ platformPicker.UpdateCharacterSpacing(picker);
+
var pickerView = platformPicker.UIPickerView;
pickerView?.ReloadAllComponents();
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the AI's suggestions?
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 #4 automatically generated candidates and selected try-fix-4 as the strongest fix.
Why: Try-fix-4 extracts a centralized UpdatePickerText() helper in PickerExtensions.cs that sets text AND applies CharacterSpacing together, then routes both UpdatePicker and UpdatePickerFromPickerSource through it. This covers all text-update paths (including the dismiss-without-change path that the PR's fix misses) without adding new state to any class, and adds a third test verifying the previously-uncovered PickerSource interaction scenario.
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-4`)
diff --git a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
index a54299d290..d73cfa9ab7 100644
--- a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
+++ b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
@@ -250,12 +250,12 @@ namespace Microsoft.Maui.Handlers
handler.PlatformView?.UpdateIsOpen(picker);
}
- void UpdatePickerFromPickerSource(PickerSource? pickerSource)
+ internal void UpdatePickerFromPickerSource(PickerSource? pickerSource)
{
if (VirtualView == null || PlatformView == null || pickerSource == null)
return;
- PlatformView.Text = VirtualView.GetItem(pickerSource.SelectedIndex);
+ PlatformView.UpdatePickerText(VirtualView, pickerSource.SelectedIndex);
VirtualView.SelectedIndex = pickerSource.SelectedIndex;
}
diff --git a/src/Core/src/Platform/iOS/PickerExtensions.cs b/src/Core/src/Platform/iOS/PickerExtensions.cs
index c2de04d1fd..6c101d027b 100644
--- a/src/Core/src/Platform/iOS/PickerExtensions.cs
+++ b/src/Core/src/Platform/iOS/PickerExtensions.cs
@@ -40,15 +40,7 @@ namespace Microsoft.Maui.Platform
{
var selectedIndex = newSelectedIndex ?? picker.SelectedIndex;
- if (selectedIndex != -1)
- {
- platformPicker.Text = picker.GetItem(selectedIndex);
- }
- else
- {
- platformPicker.Text = null;
- platformPicker.UpdatePickerTitle(picker);
- }
+ platformPicker.UpdatePickerText(picker, selectedIndex);
var pickerView = platformPicker.UIPickerView;
pickerView?.ReloadAllComponents();
@@ -65,5 +57,20 @@ namespace Microsoft.Maui.Platform
pickerView?.Select(Math.Max(selectedIndex, 0), 0, true);
}
+
+ internal static void UpdatePickerText(this MauiPicker platformPicker, IPicker picker, int selectedIndex)
+ {
+ if (selectedIndex != -1)
+ {
+ platformPicker.Text = picker.GetItem(selectedIndex);
+ }
+ else
+ {
+ platformPicker.Text = null;
+ platformPicker.UpdatePickerTitle(picker);
+ }
+
+ platformPicker.UpdateCharacterSpacing(picker);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs
index 45e8e9efe1..54d6e38183 100644
--- a/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs
+++ b/src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs
@@ -1,4 +1,4 @@
-fusing System.Threading.Tasks;
+using System.Threading.Tasks;
using Microsoft.Maui.DeviceTests.Stubs;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Handlers;
@@ -113,6 +113,33 @@ namespace Microsoft.Maui.DeviceTests
Assert.Equal(xplatCharacterSpacing, nativeCharacterSpacing);
}
+ [Fact(DisplayName = "CharacterSpacing Maintained After PickerSource Update")]
+ public async Task CharacterSpacingMaintainedAfterPickerSourceUpdate()
+ {
+ const double xplatCharacterSpacing = 4;
+
+ var picker = new PickerStub
+ {
+ Items = new[] { "Item 1", "Item 2", "Item 3" },
+ SelectedIndex = 0,
+ CharacterSpacing = xplatCharacterSpacing
+ };
+
+ var nativeCharacterSpacing = await InvokeOnMainThreadAsync(() =>
+ {
+ var handler = CreateHandler(picker);
+ var pickerView = GetNativePicker(handler).UIPickerView;
+ var source = (PickerSource)pickerView.Model;
+
+ source.Selected(pickerView, 2, 0);
+ handler.UpdatePickerFromPickerSource(source);
+
+ return GetNativeCharacterSpacing(handler);
+ });
+
+ Assert.Equal(xplatCharacterSpacing, nativeCharacterSpacing);
+ }
+
MauiPicker GetNativePicker(PickerHandler pickerHandler) =>
pickerHandler.PlatformView;
@@ -168,4 +195,4 @@ namespace Microsoft.Maui.DeviceTests
UIControlContentVerticalAlignment GetNativeVerticalTextAlignment(PickerHandler pickerHandler) =>
GetNativePicker(pickerHandler).VerticalAlignment;
}
-}
\ No newline at end of file
+}
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 #2 automatically generated candidates and selected try-fix-2 as the strongest fix.
Why: The PR's fix (adding UpdateCharacterSpacing inside UpdatePicker) fails because a recursive UpdatePicker call triggered by BindableProperty propagation overwrites the applied spacing. Try-fix-2 overrides MauiPicker.Text setter to self-apply stored CharacterSpacing on every text assignment, covering all paths (initial load, programmatic SelectedIndex change, user physical selection, EditingChanged events) without call-site coordination. Both gate-equivalent tests pass.
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-2`)
diff --git a/src/Core/src/Platform/iOS/MauiPicker.cs b/src/Core/src/Platform/iOS/MauiPicker.cs
index 06956ddc67..d1541a0aa9 100644
--- a/src/Core/src/Platform/iOS/MauiPicker.cs
+++ b/src/Core/src/Platform/iOS/MauiPicker.cs
@@ -11,6 +11,10 @@ namespace Microsoft.Maui.Platform
{
readonly HashSet<string> _enableActions;
+ // Stores character spacing so it can be re-applied when Text is set,
+ // since setting plain Text replaces AttributedText and clears the kern attribute.
+ internal double CharacterSpacing { get; set; }
+
public MauiPicker(UIPickerView? uIPickerView)
{
UIPickerView = uIPickerView;
@@ -22,6 +26,23 @@ namespace Microsoft.Maui.Platform
[UnconditionalSuppressMessage("Memory", "MEM0002", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")]
public UIPickerView? UIPickerView { get; set; }
+ public override string? Text
+ {
+ get => base.Text;
+ set
+ {
+ base.Text = value;
+ // Setting plain Text replaces AttributedText, stripping the kern attribute.
+ // Re-apply character spacing so it persists through any text change.
+ if (CharacterSpacing != 0)
+ {
+ var textAttr = AttributedText?.WithCharacterSpacing(CharacterSpacing);
+ if (textAttr != null)
+ AttributedText = textAttr;
+ }
+ }
+ }
+
public override bool CanPerform(Selector action, NSObject? withSender)
=> _enableActions.Contains(action.Name);
}
diff --git a/src/Core/src/Platform/iOS/PickerExtensions.cs b/src/Core/src/Platform/iOS/PickerExtensions.cs
index c2de04d1fd..f37490df2f 100644
--- a/src/Core/src/Platform/iOS/PickerExtensions.cs
+++ b/src/Core/src/Platform/iOS/PickerExtensions.cs
@@ -15,6 +15,19 @@ namespace Microsoft.Maui.Platform
public static void UpdateTextColor(this MauiPicker platformPicker, IPicker picker) =>
platformPicker.TextColor = picker.TextColor?.ToPlatform();
+ public static void UpdateCharacterSpacing(this MauiPicker platformPicker, IPicker picker)
+ {
+ // Store the spacing so the Text setter override can re-apply it on every text change.
+ platformPicker.CharacterSpacing = picker.CharacterSpacing;
+ // Apply to current AttributedText (handles runtime updates when text is already set).
+ var textAttr = platformPicker.AttributedText?.WithCharacterSpacing(picker.CharacterSpacing);
+ if (textAttr != null)
+ platformPicker.AttributedText = textAttr;
+ var placeholderAttr = platformPicker.AttributedPlaceholder?.WithCharacterSpacing(picker.CharacterSpacing);
+ if (placeholderAttr != null)
+ platformPicker.AttributedPlaceholder = placeholderAttr;
+ }
+
public static void UpdateSelectedIndex(this MauiPicker platformPicker, IPicker picker) =>
platformPicker.UpdatePicker(picker, picker.SelectedIndex);
diff --git a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
index 46f10522b9..0e5f5ca61c 100644
--- a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
+++ b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
@@ -6,3 +6,6 @@ override Microsoft.Maui.Platform.MauiTextView.TextAlignment.get -> UIKit.UITextA
override Microsoft.Maui.Platform.MauiTextView.TextAlignment.set -> void
override Microsoft.Maui.Platform.MauiView.DidUpdateFocus(UIKit.UIFocusUpdateContext! context, UIKit.UIFocusAnimationCoordinator! coordinator) -> void
static Microsoft.Maui.Handlers.DatePickerHandler.MapFlowDirection(Microsoft.Maui.Handlers.IDatePickerHandler! handler, Microsoft.Maui.IDatePicker! datePicker) -> void
+override Microsoft.Maui.Platform.MauiPicker.Text.get -> string?
+override Microsoft.Maui.Platform.MauiPicker.Text.set -> void
+static Microsoft.Maui.Platform.PickerExtensions.UpdateCharacterSpacing(this Microsoft.Maui.Platform.MauiPicker! platformPicker, Microsoft.Maui.IPicker! picker) -> void
diff --git a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
index 46f10522b9..0e5f5ca61c 100644
--- a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
+++ b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
@@ -6,3 +6,6 @@ override Microsoft.Maui.Platform.MauiTextView.TextAlignment.get -> UIKit.UITextA
override Microsoft.Maui.Platform.MauiTextView.TextAlignment.set -> void
override Microsoft.Maui.Platform.MauiView.DidUpdateFocus(UIKit.UIFocusUpdateContext! context, UIKit.UIFocusAnimationCoordinator! coordinator) -> void
static Microsoft.Maui.Handlers.DatePickerHandler.MapFlowDirection(Microsoft.Maui.Handlers.IDatePickerHandler! handler, Microsoft.Maui.IDatePicker! datePicker) -> void
+override Microsoft.Maui.Platform.MauiPicker.Text.get -> string?
+override Microsoft.Maui.Platform.MauiPicker.Text.set -> void
+static Microsoft.Maui.Platform.PickerExtensions.UpdateCharacterSpacing(this Microsoft.Maui.Platform.MauiPicker! platformPicker, Microsoft.Maui.IPicker! picker) -> void
🤖 AI Summary
📊 Review Session —
|
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
📱 PickerHandlerTests (CharacterSpacingInitializesCorrectly, CharacterSpacingMaintainedAfterSelectedIndexChange) Category=Picker |
✅ FAIL — 442s | ❌ FAIL — 210s |
🔴 Without fix — 📱 PickerHandlerTests (CharacterSpacingInitializesCorrectly, CharacterSpacingMaintainedAfterSelectedIndexChange): FAIL ✅ · 442s
(truncated to last 15,000 chars)
LoopRun + 1208
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000019fda49e8 CoreFoundation`CFRunLoopRunSpecific + 572
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x00000001a1374c78 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x00000001a13e83a4 Foundation`-[NSRunLoop(NSRunLoop) runUntilDate:] + 100
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x00000001048a6f28 mlaunch`xamarin_dyn_objc_msgSend + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x0000000300bccecc
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x0000000300f33d08
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x0000000300bc8244
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x0000000300b61ad4
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x000000030037d81c
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x000000010660cc04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #16: 0x000000010648ad30 libcoreclr.dylib`MethodDescCallSite::CallTargetWorker(unsigned long long const*, unsigned long long*, int) + 836
�[40m�[37mdbug�[39m�[22m�[49m: frame #17: 0x0000000106391350 libcoreclr.dylib`RunMain(MethodDesc*, short, int*, PtrArray**) + 648
�[40m�[37mdbug�[39m�[22m�[49m: frame #18: 0x0000000106391688 libcoreclr.dylib`Assembly::ExecuteMainMethod(PtrArray**, int) + 264
�[40m�[37mdbug�[39m�[22m�[49m: frame #19: 0x00000001063b929c libcoreclr.dylib`CorHost2::ExecuteAssembly(unsigned int, char16_t const*, int, char16_t const**, unsigned int*) + 640
�[40m�[37mdbug�[39m�[22m�[49m: frame #20: 0x000000010637f650 libcoreclr.dylib`coreclr_execute_assembly + 232
�[40m�[37mdbug�[39m�[22m�[49m: frame #21: 0x00000001048a2140 mlaunch`mono_jit_exec + 204
�[40m�[37mdbug�[39m�[22m�[49m: frame #22: 0x00000001048a5ecc mlaunch`xamarin_main + 884
�[40m�[37mdbug�[39m�[22m�[49m: frame #23: 0x00000001048a71f4 mlaunch`main + 64
�[40m�[37mdbug�[39m�[22m�[49m: frame #24: 0x000000019f91ab98 dyld`start + 6076
�[40m�[37mdbug�[39m�[22m�[49m: thread #2
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7b8b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: thread #3
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc79c34 libsystem_kernel.dylib`mach_msg2_trap + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fc8c3a0 libsystem_kernel.dylib`mach_msg2_internal + 76
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000019fc82764 libsystem_kernel.dylib`mach_msg_overwrite + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000019fc79fa8 libsystem_kernel.dylib`mach_msg + 24
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000010637d2f4 libcoreclr.dylib`MachMessage::Receive(unsigned int) + 80
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000010637c61c libcoreclr.dylib`SEHExceptionThread(void*) + 164
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #4, name = '.NET SynchManager'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7fd04 libsystem_kernel.dylib`kevent + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000106371304 libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ReadBytesFromProcessPipe(int, unsigned char*, int) + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001063709f0 libcoreclr.dylib`CorUnix::CPalSynchronizationManager::WorkerThread(void*) + 164
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010637a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #5, name = '.NET EventPipe'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc82498 libsystem_kernel.dylib`poll + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000010666ce90 libcoreclr.dylib`ds_ipc_poll(_DiagnosticsIpcPollHandle*, unsigned long, unsigned int, void (*)(char const*, unsigned int)) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010671abb0 libcoreclr.dylib`ds_ipc_stream_factory_get_next_available_stream(void (*)(char const*, unsigned int)) + 756
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x0000000106718a68 libcoreclr.dylib`server_thread(void*) + 372
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000010637a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #6, name = '.NET DebugPipe'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7a678 libsystem_kernel.dylib`__open + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fc856a4 libsystem_kernel.dylib`open + 64
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010666da84 libcoreclr.dylib`TwoWayPipe::WaitForConnection() + 40
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x0000000106668578 libcoreclr.dylib`DbgTransportSession::TransportWorker() + 232
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001066675c8 libcoreclr.dylib`DbgTransportSession::TransportWorkerStatic(void*) + 40
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000010637a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #7, name = '.NET Debugger'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7d3cc libsystem_kernel.dylib`__psynch_cvwait + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fcbc09c libsystem_pthread.dylib`_pthread_cond_wait + 984
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010636ef6c libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ThreadNativeWait(CorUnix::_ThreadNativeWaitData*, unsigned int, CorUnix::ThreadWakeupReason*, unsigned int*) + 320
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010636ebec libcoreclr.dylib`CorUnix::CPalSynchronizationManager::BlockThread(CorUnix::CPalThread*, unsigned int, bool, bool, CorUnix::ThreadWakeupReason*, unsigned int*) + 380
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001063730cc libcoreclr.dylib`CorUnix::InternalWaitForMultipleObjectsEx(CorUnix::CPalThread*, unsigned int, void* const*, int, unsigned int, int, int) + 1600
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000106665da8 libcoreclr.dylib`DebuggerRCThread::MainLoop() + 228
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000106665c70 libcoreclr.dylib`DebuggerRCThread::ThreadProc() + 256
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x0000000106665a24 libcoreclr.dylib`DebuggerRCThread::ThreadProcStatic(void*) + 56
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x000000010637a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #8
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7d3cc libsystem_kernel.dylib`__psynch_cvwait + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fcbc09c libsystem_pthread.dylib`_pthread_cond_wait + 984
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010636ef6c libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ThreadNativeWait(CorUnix::_ThreadNativeWaitData*, unsigned int, CorUnix::ThreadWakeupReason*, unsigned int*) + 320
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010636ebec libcoreclr.dylib`CorUnix::CPalSynchronizationManager::BlockThread(CorUnix::CPalThread*, unsigned int, bool, bool, CorUnix::ThreadWakeupReason*, unsigned int*) + 380
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001063730cc libcoreclr.dylib`CorUnix::InternalWaitForMultipleObjectsEx(CorUnix::CPalThread*, unsigned int, void* const*, int, unsigned int, int, int) + 1600
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x00000001064c0078 libcoreclr.dylib`FinalizerThread::WaitForFinalizerEvent(CLREvent*) + 240
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x00000001064c01d8 libcoreclr.dylib`FinalizerThread::FinalizerThreadWorker(void*) + 264
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x000000010645dfa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x000000010645e48c libcoreclr.dylib`ManagedThreadBase::FinalizerBase(void (*)(void*)) + 36
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x00000001064c0350 libcoreclr.dylib`FinalizerThread::FinalizerThreadStart(void*) + 88
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x000000010637a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #9, name = '.NET SigHandler'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7a7dc libsystem_kernel.dylib`read + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000104a34e98 libSystem.Native.dylib`SignalHandlerLoop + 96
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #10
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7fd04 libsystem_kernel.dylib`kevent + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000104a334a4 libSystem.Native.dylib`SystemNative_WaitForSocketEvents + 80
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x0000000300cdc7b4
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x0000000300cdc4f4
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x0000000300cdc41c
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000300bc1e50
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000300bc1c38
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x0000000300bc1b60
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x000000010660cc04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x000000010648a988 libcoreclr.dylib`DispatchCallSimple(unsigned long*, unsigned int, unsigned long long, unsigned int) + 268
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x000000010649cc6c libcoreclr.dylib`ThreadNative::KickOffThread_Worker(void*) + 148
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x000000010645dfa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x000000010645e45c libcoreclr.dylib`ManagedThreadBase::KickOff(void (*)(void*), void*) + 32
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x000000010649cd44 libcoreclr.dylib`ThreadNative::KickOffThread(void*) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x000000010637a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #11
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc79c34 libsystem_kernel.dylib`mach_msg2_trap + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fc8c3a0 libsystem_kernel.dylib`mach_msg2_internal + 76
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000019fc82764 libsystem_kernel.dylib`mach_msg_overwrite + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000019fc79fa8 libsystem_kernel.dylib`mach_msg + 24
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000019fda6c0c CoreFoundation`__CFRunLoopServiceMachPort + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000019fda5528 CoreFoundation`__CFRunLoopRun + 1208
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000019fda49e8 CoreFoundation`CFRunLoopRunSpecific + 572
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x00000001a1374c78 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x00000001048a6f28 mlaunch`xamarin_dyn_objc_msgSend + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x0000000300f2a3dc
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x0000000300f2a2a0
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x0000000300f2a0d4
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x0000000300f27058
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x0000000300bc1df8
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x0000000300bc1c38
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x0000000300bc1b60
�[40m�[37mdbug�[39m�[22m�[49m: frame #16: 0x000000010660cc04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #17: 0x000000010648a988 libcoreclr.dylib`DispatchCallSimple(unsigned long*, unsigned int, unsigned long long, unsigned int) + 268
�[40m�[37mdbug�[39m�[22m�[49m: frame #18: 0x000000010649cc6c libcoreclr.dylib`ThreadNative::KickOffThread_Worker(void*) + 148
�[40m�[37mdbug�[39m�[22m�[49m: frame #19: 0x000000010645dfa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #20: 0x000000010645e45c libcoreclr.dylib`ManagedThreadBase::KickOff(void (*)(void*), void*) + 32
�[40m�[37mdbug�[39m�[22m�[49m: frame #21: 0x000000010649cd44 libcoreclr.dylib`ThreadNative::KickOffThread(void*) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #22: 0x000000010637a0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #23: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #12, name = 'com.apple.CFSocket.private'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc84c2c libsystem_kernel.dylib`__select + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fdcca80 CoreFoundation`__CFSocketManager + 704
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #13
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7b8b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: (lldb) detach
�[40m�[37mdbug�[39m�[22m�[49m: Process 9401 detached
�[40m�[37mdbug�[39m�[22m�[49m: (lldb) quit
�[40m�[37mdbug�[39m�[22m�[49m: 9401 Execution timed out after 60 seconds and the process was killed.
�[40m�[37mdbug�[39m�[22m�[49m: Process mlaunch exited with 137
�[40m�[37mdbug�[39m�[22m�[49m: Failed to list crash reports from device.
�[40m�[37mdbug�[39m�[22m�[49m: Test run started but crashed and no test results were reported
�[40m�[37mdbug�[39m�[22m�[49m: No crash reports, waiting 30 seconds for the crash report service...
�[41m�[30mfail�[39m�[22m�[49m: Application test run crashed
Failed to launch the application, please try again. If the problem persists, try rebooting MacOS
�[40m�[32minfo�[39m�[22m�[49m: Uninstalling the application 'com.microsoft.maui.core.devicetests' from 'iPhone 11 Pro'
�[40m�[37mdbug�[39m�[22m�[49m:
�[40m�[37mdbug�[39m�[22m�[49m: Running /Applications/Xcode_26.1.1.app/Contents/Developer/usr/bin/simctl
�[40m�[37mdbug�[39m�[22m�[49m: An error was encountered processing the command (domain=com.apple.CoreSimulator.SimError, code=405):
�[40m�[37mdbug�[39m�[22m�[49m: Unable to lookup in current state: Shutdown
�[40m�[37mdbug�[39m�[22m�[49m: Process simctl exited with 149
�[41m�[30mfail�[39m�[22m�[49m: Failed to uninstall the app bundle! Check logs for more details!
XHarness exit code: 83 (APP_LAUNCH_FAILURE)
Passed: 0
Failed: 0
Tests completed with exit code: 83
🟢 With fix — 📱 PickerHandlerTests (CharacterSpacingInitializesCorrectly, CharacterSpacingMaintainedAfterSelectedIndexChange): FAIL ❌ · 210s
(truncated to last 15,000 chars)
de:beforeDate:] + 212
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x00000001a13e83a4 Foundation`-[NSRunLoop(NSRunLoop) runUntilDate:] + 100
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x00000001007d2f28 mlaunch`xamarin_dyn_objc_msgSend + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x0000000103f1de14
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x0000000104090e38
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x0000000103f177a4
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x0000000103eb10b4
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x00000001036ccd54
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x0000000102550c04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #16: 0x00000001023ced30 libcoreclr.dylib`MethodDescCallSite::CallTargetWorker(unsigned long long const*, unsigned long long*, int) + 836
�[40m�[37mdbug�[39m�[22m�[49m: frame #17: 0x00000001022d5350 libcoreclr.dylib`RunMain(MethodDesc*, short, int*, PtrArray**) + 648
�[40m�[37mdbug�[39m�[22m�[49m: frame #18: 0x00000001022d5688 libcoreclr.dylib`Assembly::ExecuteMainMethod(PtrArray**, int) + 264
�[40m�[37mdbug�[39m�[22m�[49m: frame #19: 0x00000001022fd29c libcoreclr.dylib`CorHost2::ExecuteAssembly(unsigned int, char16_t const*, int, char16_t const**, unsigned int*) + 640
�[40m�[37mdbug�[39m�[22m�[49m: frame #20: 0x00000001022c3650 libcoreclr.dylib`coreclr_execute_assembly + 232
�[40m�[37mdbug�[39m�[22m�[49m: frame #21: 0x00000001007ce140 mlaunch`mono_jit_exec + 204
�[40m�[37mdbug�[39m�[22m�[49m: frame #22: 0x00000001007d1ecc mlaunch`xamarin_main + 884
�[40m�[37mdbug�[39m�[22m�[49m: frame #23: 0x00000001007d31f4 mlaunch`main + 64
�[40m�[37mdbug�[39m�[22m�[49m: frame #24: 0x000000019f91ab98 dyld`start + 6076
�[40m�[37mdbug�[39m�[22m�[49m: thread #2
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7b8b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: thread #3
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7b8b0 libsystem_kernel.dylib`__workq_kernreturn + 8
�[40m�[37mdbug�[39m�[22m�[49m: thread #4
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc79c34 libsystem_kernel.dylib`mach_msg2_trap + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fc8c3a0 libsystem_kernel.dylib`mach_msg2_internal + 76
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000019fc82764 libsystem_kernel.dylib`mach_msg_overwrite + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000019fc79fa8 libsystem_kernel.dylib`mach_msg + 24
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001022c12f4 libcoreclr.dylib`MachMessage::Receive(unsigned int) + 80
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x00000001022c061c libcoreclr.dylib`SEHExceptionThread(void*) + 164
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #5, name = '.NET SynchManager'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7fd04 libsystem_kernel.dylib`kevent + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x00000001022b5304 libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ReadBytesFromProcessPipe(int, unsigned char*, int) + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001022b49f0 libcoreclr.dylib`CorUnix::CPalSynchronizationManager::WorkerThread(void*) + 164
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x00000001022be0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #6, name = '.NET EventPipe'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc82498 libsystem_kernel.dylib`poll + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x00000001025b0e90 libcoreclr.dylib`ds_ipc_poll(_DiagnosticsIpcPollHandle*, unsigned long, unsigned int, void (*)(char const*, unsigned int)) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010265ebb0 libcoreclr.dylib`ds_ipc_stream_factory_get_next_available_stream(void (*)(char const*, unsigned int)) + 756
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010265ca68 libcoreclr.dylib`server_thread(void*) + 372
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001022be0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #7, name = '.NET DebugPipe'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7a678 libsystem_kernel.dylib`__open + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fc856a4 libsystem_kernel.dylib`open + 64
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001025b1a84 libcoreclr.dylib`TwoWayPipe::WaitForConnection() + 40
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x00000001025ac578 libcoreclr.dylib`DbgTransportSession::TransportWorker() + 232
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001025ab5c8 libcoreclr.dylib`DbgTransportSession::TransportWorkerStatic(void*) + 40
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x00000001022be0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #8, name = '.NET Debugger'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7d3cc libsystem_kernel.dylib`__psynch_cvwait + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fcbc09c libsystem_pthread.dylib`_pthread_cond_wait + 984
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001022b2f6c libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ThreadNativeWait(CorUnix::_ThreadNativeWaitData*, unsigned int, CorUnix::ThreadWakeupReason*, unsigned int*) + 320
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x00000001022b2bec libcoreclr.dylib`CorUnix::CPalSynchronizationManager::BlockThread(CorUnix::CPalThread*, unsigned int, bool, bool, CorUnix::ThreadWakeupReason*, unsigned int*) + 380
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001022b70cc libcoreclr.dylib`CorUnix::InternalWaitForMultipleObjectsEx(CorUnix::CPalThread*, unsigned int, void* const*, int, unsigned int, int, int) + 1600
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x00000001025a9da8 libcoreclr.dylib`DebuggerRCThread::MainLoop() + 228
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x00000001025a9c70 libcoreclr.dylib`DebuggerRCThread::ThreadProc() + 256
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x00000001025a9a24 libcoreclr.dylib`DebuggerRCThread::ThreadProcStatic(void*) + 56
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x00000001022be0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #9
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7d3cc libsystem_kernel.dylib`__psynch_cvwait + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fcbc09c libsystem_pthread.dylib`_pthread_cond_wait + 984
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x00000001022b2f6c libcoreclr.dylib`CorUnix::CPalSynchronizationManager::ThreadNativeWait(CorUnix::_ThreadNativeWaitData*, unsigned int, CorUnix::ThreadWakeupReason*, unsigned int*) + 320
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x00000001022b2bec libcoreclr.dylib`CorUnix::CPalSynchronizationManager::BlockThread(CorUnix::CPalThread*, unsigned int, bool, bool, CorUnix::ThreadWakeupReason*, unsigned int*) + 380
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x00000001022b70cc libcoreclr.dylib`CorUnix::InternalWaitForMultipleObjectsEx(CorUnix::CPalThread*, unsigned int, void* const*, int, unsigned int, int, int) + 1600
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000102404078 libcoreclr.dylib`FinalizerThread::WaitForFinalizerEvent(CLREvent*) + 240
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x00000001024041d8 libcoreclr.dylib`FinalizerThread::FinalizerThreadWorker(void*) + 264
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x00000001023a1fa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x00000001023a248c libcoreclr.dylib`ManagedThreadBase::FinalizerBase(void (*)(void*)) + 36
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x0000000102404350 libcoreclr.dylib`FinalizerThread::FinalizerThreadStart(void*) + 88
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x00000001022be0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #10, name = '.NET SigHandler'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7a7dc libsystem_kernel.dylib`read + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x0000000100960e98 libSystem.Native.dylib`SignalHandlerLoop + 96
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #11
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc7fd04 libsystem_kernel.dylib`kevent + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000010095f4a4 libSystem.Native.dylib`SystemNative_WaitForSocketEvents + 80
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000010402ca9c
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000010402c7dc
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000010402c43c
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x0000000103f114c0
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x0000000103f11218
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x0000000103f11108
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x0000000102550c04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x00000001023ce988 libcoreclr.dylib`DispatchCallSimple(unsigned long*, unsigned int, unsigned long long, unsigned int) + 268
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x00000001023e0c6c libcoreclr.dylib`ThreadNative::KickOffThread_Worker(void*) + 148
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x00000001023a1fa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x00000001023a245c libcoreclr.dylib`ManagedThreadBase::KickOff(void (*)(void*), void*) + 32
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x00000001023e0d44 libcoreclr.dylib`ThreadNative::KickOffThread(void*) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x00000001022be0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #12
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc79c34 libsystem_kernel.dylib`mach_msg2_trap + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fc8c3a0 libsystem_kernel.dylib`mach_msg2_internal + 76
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000019fc82764 libsystem_kernel.dylib`mach_msg_overwrite + 484
�[40m�[37mdbug�[39m�[22m�[49m: frame #3: 0x000000019fc79fa8 libsystem_kernel.dylib`mach_msg + 24
�[40m�[37mdbug�[39m�[22m�[49m: frame #4: 0x000000019fda6c0c CoreFoundation`__CFRunLoopServiceMachPort + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #5: 0x000000019fda5528 CoreFoundation`__CFRunLoopRun + 1208
�[40m�[37mdbug�[39m�[22m�[49m: frame #6: 0x000000019fda49e8 CoreFoundation`CFRunLoopRunSpecific + 572
�[40m�[37mdbug�[39m�[22m�[49m: frame #7: 0x00000001a1374c78 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 212
�[40m�[37mdbug�[39m�[22m�[49m: frame #8: 0x00000001007d2f28 mlaunch`xamarin_dyn_objc_msgSend + 160
�[40m�[37mdbug�[39m�[22m�[49m: frame #9: 0x0000000104079bd4
�[40m�[37mdbug�[39m�[22m�[49m: frame #10: 0x0000000104079a98
�[40m�[37mdbug�[39m�[22m�[49m: frame #11: 0x00000001040798cc
�[40m�[37mdbug�[39m�[22m�[49m: frame #12: 0x00000001040764c8
�[40m�[37mdbug�[39m�[22m�[49m: frame #13: 0x0000000103f11468
�[40m�[37mdbug�[39m�[22m�[49m: frame #14: 0x0000000103f11218
�[40m�[37mdbug�[39m�[22m�[49m: frame #15: 0x0000000103f11108
�[40m�[37mdbug�[39m�[22m�[49m: frame #16: 0x0000000102550c04 libcoreclr.dylib`CallDescrWorkerInternal + 132
�[40m�[37mdbug�[39m�[22m�[49m: frame #17: 0x00000001023ce988 libcoreclr.dylib`DispatchCallSimple(unsigned long*, unsigned int, unsigned long long, unsigned int) + 268
�[40m�[37mdbug�[39m�[22m�[49m: frame #18: 0x00000001023e0c6c libcoreclr.dylib`ThreadNative::KickOffThread_Worker(void*) + 148
�[40m�[37mdbug�[39m�[22m�[49m: frame #19: 0x00000001023a1fa8 libcoreclr.dylib`ManagedThreadBase_DispatchOuter(ManagedThreadCallState*) + 248
�[40m�[37mdbug�[39m�[22m�[49m: frame #20: 0x00000001023a245c libcoreclr.dylib`ManagedThreadBase::KickOff(void (*)(void*), void*) + 32
�[40m�[37mdbug�[39m�[22m�[49m: frame #21: 0x00000001023e0d44 libcoreclr.dylib`ThreadNative::KickOffThread(void*) + 172
�[40m�[37mdbug�[39m�[22m�[49m: frame #22: 0x00000001022be0fc libcoreclr.dylib`CorUnix::CPalThread::ThreadEntry(void*) + 364
�[40m�[37mdbug�[39m�[22m�[49m: frame #23: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: thread #13
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x0000000000000000
�[40m�[37mdbug�[39m�[22m�[49m: thread #14, name = 'com.apple.CFSocket.private'
�[40m�[37mdbug�[39m�[22m�[49m: frame #0: 0x000000019fc84c2c libsystem_kernel.dylib`__select + 8
�[40m�[37mdbug�[39m�[22m�[49m: frame #1: 0x000000019fdcca80 CoreFoundation`__CFSocketManager + 704
�[40m�[37mdbug�[39m�[22m�[49m: frame #2: 0x000000019fcbbbc8 libsystem_pthread.dylib`_pthread_start + 136
�[40m�[37mdbug�[39m�[22m�[49m: (lldb) detach
�[40m�[37mdbug�[39m�[22m�[49m: Process 10712 detached
�[40m�[37mdbug�[39m�[22m�[49m: (lldb) quit
�[40m�[37mdbug�[39m�[22m�[49m: 10712 Execution timed out after 60 seconds and the process was killed.
�[40m�[37mdbug�[39m�[22m�[49m: Process mlaunch exited with 137
�[40m�[37mdbug�[39m�[22m�[49m: Failed to list crash reports from device.
�[40m�[37mdbug�[39m�[22m�[49m: Test run started but crashed and no test results were reported
�[40m�[37mdbug�[39m�[22m�[49m: No crash reports, waiting 30 seconds for the crash report service...
�[40m�[37mdbug�[39m�[22m�[49m: Found 1 new crash report(s)
�[40m�[37mdbug�[39m�[22m�[49m: - Adding /Users/cloudtest/Library/Logs/DiagnosticReports/Microsoft.Maui.Core.DeviceTests-2026-05-03-060558.ips
�[40m�[37mdbug�[39m�[22m�[49m: Successfully copied Microsoft.Maui.Core.DeviceTests-2026-05-03-060558.ips
�[41m�[30mfail�[39m�[22m�[49m: Application test run crashed
Failed to launch the application, please try again. If the problem persists, try rebooting MacOS
�[40m�[32minfo�[39m�[22m�[49m: Uninstalling the application 'com.microsoft.maui.core.devicetests' from 'iPhone 11 Pro'
�[40m�[37mdbug�[39m�[22m�[49m:
�[40m�[37mdbug�[39m�[22m�[49m: Running /Applications/Xcode_26.1.1.app/Contents/Developer/usr/bin/simctl
�[40m�[37mdbug�[39m�[22m�[49m: Process simctl exited with 0
�[40m�[32minfo�[39m�[22m�[49m: Application 'com.microsoft.maui.core.devicetests' was uninstalled successfully
XHarness exit code: 83 (APP_LAUNCH_FAILURE)
Passed: 88
Failed: 6
Tests completed with exit code: 83
⚠️ Failure Details
- ❌ PickerHandlerTests (CharacterSpacingInitializesCorrectly, CharacterSpacingMaintainedAfterSelectedIndexChange) FAILED with fix (should pass)
Device tests: 6 of 94 failed
📁 Fix files reverted (3 files)
eng/pipelines/ci-copilot.ymlsrc/Core/src/Handlers/Picker/PickerHandler.iOS.cssrc/Core/src/Platform/iOS/PickerExtensions.cs
🧪 UI Tests — Category Detection
Detected UI test categories: Picker,ViewBaseTests
🔍 Regression Cross-Reference
🔍 Regression Cross-Reference
🟢 No regression risks detected. No labeled bug-fix PRs in the last 6 months touched the modified files.
🔍 Pre-Flight — Context & Validation
Issue: #34955 - [iOS] Picker ignores CharacterSpacing on initial load
PR: #34957 - [iOS] Fix Picker CharacterSpacing ignored on initial load
Platforms Affected: iOS, macOS (MacCatalyst)
Files Changed: 2 implementation, 1 test
Key Findings
- Root Cause:
MapCharacterSpacingruns beforeMapSelectedIndexin the mapper. WhenUpdateCharacterSpacingis called on initial load,AttributedTextisnull(no text yet) so the spacing is a no-op. WhenMapSelectedIndexfires next viaUpdateSelectedIndex→UpdatePicker, text is set but CharacterSpacing is not reapplied. - Fix Applied:
UpdatePicker()inPickerExtensions.csnow callsplatformPicker.UpdateCharacterSpacing(picker)after settingplatformPicker.Text. This ensures spacing is applied whenever text is set. - Remaining Gap (tracked in [iOS] Fix Picker CharacterSpacing lost after item selection when Title is set #34974):
UpdatePickerFromPickerSource(called when user physically selects from the picker wheel) setsPlatformView.Textdirectly without reapplying CharacterSpacing. A reviewer suggested addingPlatformView.UpdateCharacterSpacing(VirtualView)there. Author deferred this to [iOS] Fix Picker CharacterSpacing lost after item selection when Title is set #34974. - Whitespace-only change in
PickerHandler.iOS.csline 258 (blank line between two statements) — no functional effect. - Two device tests added covering initial CharacterSpacing and CharacterSpacing maintenance after SelectedIndex change.
- The PR has the
s/agent-suggestions-implementedlabel — author previously applied suggestions from a prior agent review.
Prior Agent Reviews
- A prior agent review existed (s/agent-reviewed, s/agent-changes-requested, s/agent-fix-win labels).
- Copilot reviewer had flagged
UpdatePickerFromPickerSourceas a regression path; author responded that this is tracked separately in [iOS] Fix Picker CharacterSpacing lost after item selection when Title is set #34974. - Applied simplification: removed OnLoaded deferral in MapCharacterSpacing, replaced with direct UpdateCharacterSpacing call.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: high
Errors: 0 | Warnings: 1 | Suggestions: 2
Key code review findings:
⚠️ src/Core/src/Handlers/Picker/PickerHandler.iOS.cs:258—UpdatePickerFromPickerSourcesetsPlatformView.Text(plain string, wipes kerning) but does NOT reapplyUpdateCharacterSpacing. When the user confirms the same selected item (no index change),PropertyChangeddoes not fire and CharacterSpacing is permanently lost.- 💡
src/Core/src/Handlers/Picker/PickerHandler.iOS.cs:~427—OnEditingalso sets plain text without reapplying CharacterSpacing (low risk in practice). - 💡
src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs:~162—GetNativeCharacterSpacingcould silently return 0 ifAttributedTextis unexpectedly null.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34957 | Add UpdateCharacterSpacing call inside UpdatePicker() in PickerExtensions.cs so spacing is re-applied whenever text is set |
❌ FAILED (Gate) | PickerExtensions.cs, PickerHandler.iOS.cs (whitespace), PickerHandlerTests.iOS.cs |
Original PR — gate failed |
🔬 Code Review — Deep Analysis
Code Review — PR #34957
Independent Assessment
What this changes: Adds a call to UpdateCharacterSpacing(picker) inside PickerExtensions.UpdatePicker() immediately after the text is set. Also adds a cosmetic blank line in PickerHandler.iOS.cs and a newline-at-EOF fix. Two new iOS device tests verify the behavior.
Inferred motivation: Setting UITextField.Text (plain string) on a MauiPicker replaces AttributedText with a plain NSAttributedString, silently wiping all previously applied KerningAdjustment attributes. Since CharacterSpacing is mapped before SelectedIndex in the handler's PropertyMapper, character spacing is applied when there is no text yet (a no-op), then SelectedIndex mapping sets the text as plain string, permanently losing the kerning. Any programmatic SelectedIndex change afterwards has the same effect.
Reconciliation with PR Narrative
Author claims: CharacterSpacing is applied before SelectedIndex in the mapper (making it a no-op), and any subsequent index change also wipes it. Fix: call UpdateCharacterSpacing inside UpdatePicker so kerning is reapplied every time the text is written.
Agreement: Diagnosis and fix are exactly right. The fix directly mirrors the pattern already used in TimePickerExtensions.UpdateTime(), which has an identical mauiTimePicker.UpdateCharacterSpacing(timePicker) call at the end of the text-setting block. The PR correctly identifies both the initialization issue and the programmatic update issue.
Findings
⚠️ Warning — UpdatePickerFromPickerSource has an unguarded same-item edge case
File: src/Core/src/Handlers/Picker/PickerHandler.iOS.cs, lines 258–260
PlatformView.Text = VirtualView.GetItem(pickerSource.SelectedIndex); // plain text → wipes kerning
VirtualView.SelectedIndex = pickerSource.SelectedIndex; // fires PropertyChanged only if index CHANGEDThis is the code path called from FinishSelectItem when the user taps "Done" on the picker. When the user picks a different item, PropertyChanged fires on VirtualView.SelectedIndex, which triggers MapSelectedIndex → UpdateSelectedIndex → UpdatePicker → UpdateCharacterSpacing. Kerning is restored. ✅
However, if the user opens the picker and confirms the same item (no index change), PropertyChanged does not fire, UpdatePicker is never called, and the kerning is permanently lost after PlatformView.Text = .... Adding PlatformView.UpdateCharacterSpacing(VirtualView); explicitly after line 258 would close this gap and remove the dependency on the indirect PropertyChanged round-trip:
PlatformView.Text = VirtualView.GetItem(pickerSource.SelectedIndex);
PlatformView.UpdateCharacterSpacing(VirtualView); // ← add this
VirtualView.SelectedIndex = pickerSource.SelectedIndex;💡 Suggestion — OnEditing sets plain text without re-applying spacing
File: src/Core/src/Handlers/Picker/PickerHandler.iOS.cs, line ~427
OnEditing fires via EditingChanged, which is subscribed as a safeguard against virtual-keyboard input on the picker field. This code path also sets plain text without re-applying UpdateCharacterSpacing. In practice, EditingChanged should not fire during normal UIPickerView interaction, so this is low risk.
💡 Suggestion — GetNativeCharacterSpacing is fragile when AttributedText is null
File: src/Core/tests/DeviceTests/Handlers/Picker/PickerHandlerTests.iOS.cs, line ~162
return mauiPicker.AttributedText.GetCharacterSpacing(); // null-deref if no item selectedGetCharacterSpacing handles null safely (returns 0), but if AttributedText is ever null unexpectedly, the test will silently pass with 0 == 0 instead of the expected 4 == 4.
Devil's Advocate
On the UpdatePickerFromPickerSource concern: One could argue the indirect path (PropertyChanged → MapSelectedIndex → UpdatePicker) already restores kerning for the common case (user picks a different item), and the same-item edge case is rare. However, relying on an indirect side-effect for correctness — especially when the two-line fix is trivial — is fragile.
On the core fix: The approach is unambiguously correct. It matches the TimePickerExtensions precedent, null safety is handled by ?., there's no performance concern, and MacCatalyst is covered by the same shared file.
On the CI failure: "Run Integration Tests MultiProject macOS" failed with a 2-hour timeout. This is not related to Picker changes and is a known infrastructure flake.
Verdict: NEEDS_CHANGES
Confidence: high
Errors: 0 | Warnings: 1 | Suggestions: 2
Summary: The core fix is correct and follows the established TimePickerExtensions pattern. However, UpdatePickerFromPickerSource still drops character spacing when the user confirms the same item without changing selection. A single PlatformView.UpdateCharacterSpacing(VirtualView) call after line 258 closes this gap.
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Handler-level NSAttributedString override in MapCharacterSpacing + call from MapSelectedIndex/Reload/UpdatePickerFromPickerSource | ✅ PASS | 1 file | Diverges from DatePicker/TimePicker pattern; expert flagged font-attribute risk |
| 2 | try-fix | SetPickerItemText atomic helper in PickerExtensions.cs; used at all 3 text-clobbering sites (UpdatePicker, UpdatePickerFromPickerSource, OnEditing) |
✅ PASS | 2 files | Comprehensive; follows MAUI pattern; fixes all paths |
| 3 | try-fix | Mapper reordering (SelectedIndex before CharacterSpacing) + dependent UpdateValue call | ❌ FAIL | 2 files | Test: Expected 4, Actual 0; ordering approach unreliable |
| 4 | try-fix | Override MauiPicker.Text setter to auto-reapply CharacterSpacing; store spacing on platform view | ✅ PASS | 4 files (incl. PublicAPI.Unshipped.txt) | Works but adds public API surface |
| PR | PR #34957 | Add UpdateCharacterSpacing call in UpdatePicker() in PickerExtensions.cs |
❌ FAILED (Gate) | 2 files (+ 1 test) | Core logic correct but Gate failed; leaves UpdatePickerFromPickerSource unguarded |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-sonnet-4.6 | 2 | Yes | defaultTextAttributes/typingAttributes to make kerning "sticky" at field level so Text assignments auto-carry the attribute. Caveat: defaultTextAttributes may only affect user-typed text, not programmatically-set text. |
| (Additional models) | 2 | NO NEW IDEAS | Models agreed remaining approaches were covered by attempts 1-4 |
Exhausted: Yes — 4 models queried, 3 passing candidates found, 1 failure documented, new idea from cross-pollination has known caveat and try-fix-2/4 already cover the use case.
Selected Fix: Candidate #2 (try-fix-2) — SetPickerItemText helper approach.
Reason: Tests pass, follows established MAUI iOS extension method pattern (matches DatePicker/TimePicker), fixes all 3 text-clobbering sites comprehensively, minimal API surface change (internal helper only), and makes the intent explicit through a named helper function.
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Issue #34955, 2 impl files + 1 test file |
| Code Review | NEEDS_CHANGES (high) | 0 errors, 1 warning, 2 suggestions |
| Gate | ❌ FAILED | ios — tests did not behave as expected |
| Try-Fix | ✅ COMPLETE | 4 attempts, 3 passing |
| Report | ✅ COMPLETE |
Code Review Impact on Try-Fix
The code review warning about UpdatePickerFromPickerSource directly guided try-fix exploration. Try-fix-2 (the winning candidate) specifically addressed this gap by fixing all three text-clobbering sites rather than just UpdatePicker. The expert reviewer's inline findings confirmed the same two unfixed paths (UpdatePickerFromPickerSource and OnEditing), corroborating the code-review warning. Try-fix-3 attempted the mapper-reordering idea surfaced by code review analysis of the PropertyMapper, providing empirical confirmation that lifecycle-based fixes are insufficient.
Summary
The PR's core fix (adding UpdateCharacterSpacing in UpdatePicker()) correctly addresses the initial-load scenario where CharacterSpacing is a no-op because MapCharacterSpacing fires before any text exists. However, two additional text-clobbering sites remain unfixed: UpdatePickerFromPickerSource (user physically selects from picker wheel) and OnEditing (keyboard-reset guard). The Gate failed for this PR. An alternative fix (try-fix-2) passed all tests, covers all three sites, and follows the established MAUI iOS extension method pattern.
Winning candidate: try-fix-2 (SetPickerItemText atomic helper)
Root Cause
MapCharacterSpacing runs before MapSelectedIndex in the PropertyMapper. When UpdateCharacterSpacing is called initially, AttributedText is null (no text yet) → no-op. When MapSelectedIndex fires, UpdatePicker sets platformPicker.Text as a plain string, replacing AttributedText and silently wiping any kerning. Additionally, UpdatePickerFromPickerSource and OnEditing also set .Text as plain strings, creating three text-clobbering sites in total.
Fix Quality
PR's fix: Correct for the initial-load path but incomplete — UpdatePickerFromPickerSource still drops CharacterSpacing when the user confirms the same item (no index change means no PropertyChanged, so MapSelectedIndex never re-fires and UpdateCharacterSpacing is never called). Gate failed independently.
try-fix-2 (recommended): Introduces SetPickerItemText internal helper in PickerExtensions.cs that atomically sets .Text and applies KerningAdjustment. Used at all three sites. Tests: 2523 passed, 0 failed. Minimal API surface change (internal helper), follows DatePicker/TimePicker pattern, no public API changes.
Candidate Comparison
| Candidate | Tests | Sites Fixed | Pattern Match | Public API Change | Rank |
|---|---|---|---|---|---|
| try-fix-2 | ✅ PASS | All 3 | ✅ Follows MAUI pattern | None | #1 — Winner |
| try-fix-4 | ✅ PASS | All 3 (via Text override) | Moderate | Yes (PublicAPI.Unshipped) | #2 |
| try-fix-1 | ✅ PASS | 4 sites | ❌ Diverges | None | #3 |
| pr-plus-reviewer | Not tested | 2+ sites | ✅ Follows MAUI pattern | None | #4 |
| pr | ❌ Gate FAILED | 1 of 3 | ✅ Follows MAUI pattern | None | #5 |
| try-fix-3 | ❌ FAIL | 0 | N/A | None | #6 |
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 #2 automatically generated candidates and selected try-fix-2 as the strongest fix.
Why: try-fix-2 introduces a SetPickerItemText atomic helper in PickerExtensions.cs that sets text and reapplies CharacterSpacing together, used at all three text-clobbering sites (UpdatePicker, UpdatePickerFromPickerSource, OnEditing). It passed all 2523 device tests with zero failures, follows the established MAUI iOS extension method pattern (matching DatePicker/TimePicker), makes no public API changes, and comprehensively fixes all paths where plain .Text assignment silently wipes AttributedText kerning — gaps that the original PR left open.
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-2`)
diff --git a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
index a54299d290..34f77a735b 100644
--- a/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
+++ b/src/Core/src/Handlers/Picker/PickerHandler.iOS.cs
@@ -255,7 +255,9 @@ namespace Microsoft.Maui.Handlers
if (VirtualView == null || PlatformView == null || pickerSource == null)
return;
- PlatformView.Text = VirtualView.GetItem(pickerSource.SelectedIndex);
+ // Use SetPickerItemText to atomically set text with character spacing,
+ // preventing plain .Text assignment from silently dropping AttributedText kerning.
+ PlatformView.SetPickerItemText(VirtualView.GetItem(pickerSource.SelectedIndex), VirtualView.CharacterSpacing);
VirtualView.SelectedIndex = pickerSource.SelectedIndex;
}
@@ -423,7 +425,8 @@ 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);
+ // Use SetPickerItemText to preserve character spacing when resetting text.
+ platformView.SetPickerItemText(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..e6d36c0b27 100644
--- a/src/Core/src/Platform/iOS/PickerExtensions.cs
+++ b/src/Core/src/Platform/iOS/PickerExtensions.cs
@@ -36,18 +36,38 @@ namespace Microsoft.Maui.Platform
platformPicker.UpdateAttributedPlaceholder(new NSAttributedString(picker.Title ?? string.Empty, null, picker?.TitleColor?.ToPlatform()));
}
+ // Sets picker item text and applies character spacing atomically in one helper.
+ // Using a shared helper guarantees all code paths (UpdatePicker, UpdatePickerFromPickerSource,
+ // OnEditing) preserve character spacing - callers cannot accidentally skip the spacing step.
+ // Follows the established MAUI iOS pattern: set .Text first (UIKit synthesises AttributedText
+ // with font/color), then overlay KerningAdjustment via WithCharacterSpacing.
+ internal static void SetPickerItemText(this MauiPicker platformPicker, string? text, double characterSpacing)
+ {
+ platformPicker.Text = text;
+ if (characterSpacing != 0)
+ {
+ var textAttr = platformPicker.AttributedText?.WithCharacterSpacing(characterSpacing);
+ if (textAttr != null)
+ platformPicker.AttributedText = textAttr;
+ }
+ }
+
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.SetPickerItemText(picker.GetItem(selectedIndex), picker.CharacterSpacing);
}
else
{
platformPicker.Text = null;
platformPicker.UpdatePickerTitle(picker);
+ // Apply character spacing to placeholder when no item is selected
+ var phAttr = platformPicker.AttributedPlaceholder?.WithCharacterSpacing(picker.CharacterSpacing);
+ if (phAttr != null)
+ platformPicker.AttributedPlaceholder = phAttr;
}
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 #34955
Validated the behaviour in the following platforms
Output
Before.mov
After.mov