[iOS 26] Fix Entry MaxLength not enforced due to new multi-range delegate#32045
[iOS 26] Fix Entry MaxLength not enforced due to new multi-range delegate#32045kubaflo merged 4 commits intodotnet:inflight/currentfrom
Conversation
|
Wouldn't it be better to use a runtime check instead of a compile-time check? |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
| return VirtualView?.TextWithinMaxLength(textField.Text, ranges[0].RangeValue, replacementString) ?? false; | ||
|
|
There was a problem hiding this comment.
Only the first range (ranges[0]) is evaluated; additional ranges are ignored, which can allow combined edits to exceed MaxLength or incorrectly deny valid input. Iterate all ranges to compute the prospective final text before enforcing MaxLength (e.g., build the resulting string by applying each range mutation) or extend VirtualView.TextWithinMaxLength to accept multiple ranges.
| return VirtualView?.TextWithinMaxLength(textField.Text, ranges[0].RangeValue, replacementString) ?? false; | |
| { | |
| // Apply all ranges to the text to simulate the final result | |
| string originalText = textField.Text ?? string.Empty; | |
| var textChars = originalText.ToCharArray(); | |
| var resultText = originalText; | |
| // If multiple ranges, apply them in order | |
| // iOS passes the replacement string for all ranges, so we apply the replacement string to each range | |
| // To avoid index shifting, process ranges in reverse order | |
| var sortedRanges = ranges.Select(r => r.RangeValue).OrderByDescending(r => r.Location).ToArray(); | |
| foreach (var range in sortedRanges) | |
| { | |
| resultText = resultText.Remove((int)range.Location, (int)range.Length) | |
| .Insert((int)range.Location, replacementString); | |
| } | |
| // Now check if the result is within max length | |
| return VirtualView?.TextWithinMaxLength(resultText, new Foundation.NSRange(0, resultText.Length), string.Empty) ?? false; | |
| } |
| if (ranges is { Length: > 0 }) | ||
| return VirtualView?.TextWithinMaxLength(textField.Text, ranges[0].RangeValue, replacementString) ?? false; | ||
|
|
||
| return true; | ||
| } | ||
| #else | ||
| bool OnShouldChangeCharacters(UITextField textField, NSRange range, string replacementString) => | ||
| VirtualView?.TextWithinMaxLength(textField.Text, range, replacementString) ?? false; | ||
| #endif | ||
|
|
There was a problem hiding this comment.
[nitpick] Two separate methods duplicate MaxLength enforcement logic with different parameter shapes. Consider extracting a shared helper that computes the prospective text (supporting single or multiple ranges) to reduce duplication and ensure consistent behavior across iOS versions.
| if (ranges is { Length: > 0 }) | |
| return VirtualView?.TextWithinMaxLength(textField.Text, ranges[0].RangeValue, replacementString) ?? false; | |
| return true; | |
| } | |
| #else | |
| bool OnShouldChangeCharacters(UITextField textField, NSRange range, string replacementString) => | |
| VirtualView?.TextWithinMaxLength(textField.Text, range, replacementString) ?? false; | |
| #endif | |
| return IsTextWithinMaxLength(textField.Text, replacementString, ranges); | |
| } | |
| #else | |
| bool OnShouldChangeCharacters(UITextField textField, NSRange range, string replacementString) => | |
| IsTextWithinMaxLength(textField.Text, replacementString, range); | |
| #endif | |
| /// <summary> | |
| /// Helper to compute the prospective text after replacement and check MaxLength. | |
| /// Supports both single and multiple ranges. | |
| /// </summary> | |
| bool IsTextWithinMaxLength(string currentText, string replacementString, params object[] ranges) | |
| { | |
| if (VirtualView == null) | |
| return false; | |
| string prospectiveText = currentText; | |
| if (ranges.Length == 1 && ranges[0] is NSRange singleRange) | |
| { | |
| prospectiveText = ReplaceTextInRange(currentText, singleRange, replacementString); | |
| } | |
| else if (ranges.Length > 0 && ranges[0] is NSValue) | |
| { | |
| // Multiple ranges (iOS 16+) | |
| foreach (var obj in ranges) | |
| { | |
| if (obj is NSValue val) | |
| { | |
| var range = val.RangeValue; | |
| prospectiveText = ReplaceTextInRange(prospectiveText, range, replacementString); | |
| } | |
| } | |
| } | |
| return VirtualView.MaxLength <= 0 || prospectiveText.Length <= VirtualView.MaxLength; | |
| } | |
| /// <summary> | |
| /// Helper to replace text in a given NSRange. | |
| /// </summary> | |
| string ReplaceTextInRange(string text, NSRange range, string replacement) | |
| { | |
| if (range.Location > text.Length) | |
| return text; | |
| int start = (int)range.Location; | |
| int length = (int)range.Length; | |
| if (start + length > text.Length) | |
| length = text.Length - start; | |
| return text.Substring(0, start) + replacement + text.Substring(start + length); | |
| } |
There was a problem hiding this comment.
ReplaceTextInRange() can be removed. Instead the .Replace(range, replacementString) method on the NSString can be used.
| if (ranges is { Length: > 0 }) | ||
| return VirtualView?.TextWithinMaxLength(textField.Text, ranges[0].RangeValue, replacementString) ?? false; | ||
|
|
||
| return true; | ||
| } | ||
| #else | ||
| bool OnShouldChangeCharacters(UITextField textField, NSRange range, string replacementString) => | ||
| VirtualView?.TextWithinMaxLength(textField.Text, range, replacementString) ?? false; | ||
| #endif | ||
|
|
There was a problem hiding this comment.
[nitpick] Two separate methods duplicate MaxLength enforcement logic with different parameter shapes. Consider extracting a shared helper that computes the prospective text (supporting single or multiple ranges) to reduce duplication and ensure consistent behavior across iOS versions.
| if (ranges is { Length: > 0 }) | |
| return VirtualView?.TextWithinMaxLength(textField.Text, ranges[0].RangeValue, replacementString) ?? false; | |
| return true; | |
| } | |
| #else | |
| bool OnShouldChangeCharacters(UITextField textField, NSRange range, string replacementString) => | |
| VirtualView?.TextWithinMaxLength(textField.Text, range, replacementString) ?? false; | |
| #endif | |
| var prospectiveText = ComputeProspectiveText(textField.Text, ranges, replacementString); | |
| return VirtualView?.TextWithinMaxLength(prospectiveText) ?? false; | |
| } | |
| #else | |
| bool OnShouldChangeCharacters(UITextField textField, NSRange range, string replacementString) | |
| { | |
| var prospectiveText = ComputeProspectiveText(textField.Text, range, replacementString); | |
| return VirtualView?.TextWithinMaxLength(prospectiveText) ?? false; | |
| } | |
| #endif | |
| /// <summary> | |
| /// Computes the prospective text after applying the replacement string to the given ranges. | |
| /// </summary> | |
| string ComputeProspectiveText(string currentText, NSValue[] ranges, string replacementString) | |
| { | |
| if (ranges == null || ranges.Length == 0) | |
| return currentText; | |
| // For iOS 26+, multiple ranges can be replaced at once. | |
| var text = currentText; | |
| foreach (var rangeValue in ranges) | |
| { | |
| var range = rangeValue.RangeValue; | |
| text = text.Remove((int)range.Location, (int)range.Length) | |
| .Insert((int)range.Location, replacementString); | |
| } | |
| return text; | |
| } | |
| /// <summary> | |
| /// Computes the prospective text after applying the replacement string to the given range. | |
| /// </summary> | |
| string ComputeProspectiveText(string currentText, NSRange range, string replacementString) | |
| { | |
| if (range.Length == 0) | |
| return currentText.Insert((int)range.Location, replacementString); | |
| return currentText.Remove((int)range.Location, (int)range.Length) | |
| .Insert((int)range.Location, replacementString); | |
| } |
PR Review - iOS 26 MaxLength FixThank you for this contribution! I've conducted a thorough review including testing on iOS 26 simulator. The fix correctly addresses the iOS 26 API change, but there are a few important items to address before merge. ✅ What's Working Well
🔴 Critical Issues1. Multiple Ranges Not Fully HandledThe current implementation only processes the first range from the array: if (ranges is { Length: > 0 })
return VirtualView?.TextWithinMaxLength(textField.Text, ranges[0].RangeValue, replacementString) ?? false;Problem: iOS 26's new delegate method supports multiple simultaneous text range replacements (e.g., multi-cursor editing). By only checking Suggested Fix: Process all ranges to calculate the final text length: #if IOS26_0_OR_GREATER
bool ShouldChangeCharactersInRanges(UITextField textField, NSValue[] ranges, string replacementString)
{
if (ranges == null || ranges.Length == 0)
return true;
var currentText = textField.Text ?? string.Empty;
// Apply all range replacements to calculate final text
// Process in reverse order to avoid index shifting issues
var sortedRanges = ranges.Select(r => r.RangeValue)
.OrderByDescending(r => r.Location)
.ToArray();
foreach (var range in sortedRanges)
{
// Validate range bounds
if (range.Location + range.Length > currentText.Length)
return false;
currentText = currentText.Remove((int)range.Location, (int)range.Length)
.Insert((int)range.Location, replacementString);
}
// Check if final text is within MaxLength
return VirtualView?.MaxLength < 0 || currentText.Length <= VirtualView.MaxLength;
}
#endif2. Missing UI Test CoverageThis is a high-priority regression (P/0) that should include automated tests to prevent future regressions. Required: Add UI test following the two-project pattern:
Example test structure: // TestCases.Shared.Tests/Tests/Issues/Issue32016.cs
public class Issue32016 : _IssuesUITest
{
public override string Issue => "iOS 26 MaxLength not enforced on Entry";
public Issue32016(TestDevice device) : base(device) { }
[Test]
[Category(UITestCategories.Entry)]
public void EntryMaxLengthEnforcedOnIOS26()
{
App.WaitForElement("TestEntry");
// Type characters up to MaxLength
App.Tap("TestEntry");
App.EnterText("TestEntry", "1234567890"); // MaxLength = 10
var text = App.FindElement("TestEntry").GetText();
Assert.That(text.Length, Is.EqualTo(10), "Text should be exactly 10 characters");
// Verify additional typing is blocked
App.EnterText("TestEntry", "X");
text = App.FindElement("TestEntry").GetText();
Assert.That(text.Length, Is.EqualTo(10), "Text should still be 10 characters");
}
}🟡 Suggestions1. Editor Control May Need Same FixThe 2. Add XML DocumentationConsider adding XML doc comments to explain the iOS 26 API change: /// <summary>
/// iOS 26+ delegate method for handling text changes across multiple ranges.
/// Replaces the single-range ShouldChangeCharacters method on earlier iOS versions.
/// </summary>
/// <param name="textField">The text field being edited</param>
/// <param name="ranges">Array of NSRange values indicating which text ranges are being replaced</param>
/// <param name="replacementString">The replacement text for all specified ranges</param>
/// <returns>True if the change should be allowed (respecting MaxLength), false otherwise</returns>📋 Action ItemsBefore merge:
Testing Notes: Tested on iOS 26.0 simulator - compilation successful, app deploys without crashes. The fix correctly uses conditional compilation for iOS 26+. |
| @@ -136,7 +136,11 @@ public void Connect(IEntry virtualView, MauiTextField platformView) | |||
| platformView.EditingChanged += OnEditingChanged; | |||
| platformView.EditingDidEnd += OnEditingEnded; | |||
| platformView.TextPropertySet += OnTextPropertySet; | |||
| #if IOS26_0_OR_GREATER | |||
There was a problem hiding this comment.
I believe this will not work, since the Nuget package doesn't have a compilation per iOS version. I believe here you would like to use OperatingSystem.IsIOSVersionAtLeast(int major, int minor = 0, int build = 0) API.
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
PR #32045 Review - iOS 26 Entry MaxLength FixPR Title: [iOS 26] - The MaxLength property value is not respected on an Entry control - fix Executive SummaryThis PR fixes a critical bug where Testing Performed:
Key Finding: The PR successfully implements multi-range handling but misses a critical paste truncation feature that exists in pre-iOS 26 implementations, resulting in a user experience regression. Test ResultsEnvironment SetupiOS 26.0 Testing:
iOS 18.5 Testing (Backward Compatibility):
Test Application ConfigurationCreated comprehensive test scenarios in Sandbox app with 7 Entry controls:
Deployment ResultsiOS 26.0: ✅ App launches successfully, Entry controls render correctly iOS 18.5: ✅ App launches successfully, confirming backward compatibility Validation:
Critical Issues Found 🔴Issue 1: Missing Paste Truncation Feature (BEHAVIORAL REGRESSION)Severity: 🔴 CRITICAL - User experience regression Through deep code analysis comparing the new implementation with the existing Pre-iOS 26 Behavior ( // cut text when user is pasting a text longer that maxlength
if(!shouldChange && !string.IsNullOrWhiteSpace(replacementString) &&
replacementString!.Length >= textInput.MaxLength)
textInput.Text = replacementString!.Substring(0, textInput.MaxLength);What it does: When user pastes text exceeding MaxLength, it truncates and allows the paste. iOS 26+ Behavior (PR's new code, lines 264-265): var maxLength = VirtualView?.MaxLength ?? -1;
return maxLength < 0 || currentText.Length <= maxLength;What it does: When text exceeds MaxLength, returns Impact Example:
Why this is critical: iOS 26 users lose the "smart paste truncation" feature that has existed in .NET MAUI Entry controls since inception. This creates an inconsistent user experience across iOS versions. Recommended Fix: bool ShouldChangeCharactersInRanges(UITextField textField, NSValue[] ranges, string replacementString)
{
if (ranges == null || ranges.Length == 0)
return true;
var maxLength = VirtualView?.MaxLength ?? -1;
if (maxLength < 0)
return true;
// Handle null replacement string defensively
replacementString = replacementString ?? string.Empty;
var currentText = textField.Text ?? string.Empty;
// Copy and sort ranges (existing code is correct)
var count = ranges.Length;
var rangeArray = new NSRange[count];
for (int i = 0; i < count; i++)
rangeArray[i] = ranges[i].RangeValue;
Array.Sort(rangeArray, (a, b) => (int)(b.Location - a.Location));
// Simulate all range replacements (existing code is correct)
for (int i = 0; i < count; i++)
{
var range = rangeArray[i];
var start = (int)range.Location;
var length = (int)range.Length;
if (start < 0 || length < 0 || start > currentText.Length || start + length > currentText.Length)
return false;
var before = start > 0 ? currentText.Substring(0, start) : string.Empty;
var afterIndex = start + length;
var after = afterIndex < currentText.Length ? currentText.Substring(afterIndex) : string.Empty;
currentText = before + replacementString + after;
}
var shouldChange = currentText.Length <= maxLength;
// ✅ ADD: Paste truncation feature (matches pre-iOS 26 behavior)
if (!shouldChange && !string.IsNullOrWhiteSpace(replacementString) &&
replacementString.Length >= maxLength)
{
VirtualView.Text = replacementString.Substring(0, maxLength);
}
return shouldChange;
}Issue 2: Null String Handling (POTENTIAL CRASH)Severity: 🟡 MODERATE - Defensive programming gap Pre-iOS 26 (line 39 in ITextInputExtensions.cs): var addLength = replacementString?.Length ?? 0; // Safe null handlingiOS 26+ (this PR) (line 260): currentText = before + replacementString + after; // Direct concatenationIssue: If Question: Can iOS pass Recommended Fix: Add null coalescing at the start of the method: replacementString = replacementString ?? string.Empty;This matches the defensive pattern used throughout the .NET MAUI codebase. Issue 3: Multi-Range Replacement Logic Needs ClarificationSeverity: The PR applies the same for (int i = 0; i < count; i++)
{
// ...
currentText = before + replacementString + after; // Same string for each range
}According to Apple's documentation:
Interpretation: This suggests iOS selects ONE range to replace with Current PR: Replaces ALL ranges with the same Question for PR Author:
This needs validation with real-world multi-range scenarios to confirm the implementation matches iOS 26's actual behavior. Suggestions 🟡1. Add XML DocumentationCurrent: The new Suggested Addition: /// <summary>
/// Validates text changes across multiple ranges for iOS 26+ (multi-range text input).
/// This method handles complex text input scenarios like IME input, autocorrect,
/// and predictive text that may affect multiple text regions simultaneously.
/// </summary>
/// <param name="textField">The UITextField being edited.</param>
/// <param name="ranges">Array of NSValue objects containing NSRange values representing
/// the text ranges to be replaced. Ranges are processed in descending order to avoid
/// index shifting.</param>
/// <param name="replacementString">The replacement string to insert into the specified ranges.</param>
/// <returns>
/// <c>true</c> if the text change should be allowed (respects MaxLength);
/// <c>false</c> to reject the change (would exceed MaxLength or invalid ranges).
/// </returns>
/// <remarks>
/// iOS 26+ calls this method instead of ShouldChangeCharacters for multi-range edits.
/// The method simulates all range replacements to calculate the final text length
/// before validating against MaxLength. When paste operations exceed MaxLength, the
/// pasted text is truncated to fit within the limit (matching pre-iOS 26 behavior).
/// </remarks>
bool ShouldChangeCharactersInRanges(UITextField textField, NSValue[] ranges, string replacementString)Reasoning: Helps future maintainers understand the multi-range logic, iOS 26 platform requirement, and the descending sort strategy. Priority: Medium 2. Add Early Exit for No MaxLengthCurrent: bool ShouldChangeCharactersInRanges(UITextField textField, NSValue[] ranges, string replacementString)
{
if (ranges == null || ranges.Length == 0)
return true;
var currentText = textField.Text ?? string.Empty;
// ... range processing ...
var maxLength = VirtualView?.MaxLength ?? -1;
return maxLength < 0 || currentText.Length <= maxLength;
}Suggestion (add early exit): bool ShouldChangeCharactersInRanges(UITextField textField, NSValue[] ranges, string replacementString)
{
// Early exit: No MaxLength restriction
var maxLength = VirtualView?.MaxLength ?? -1;
if (maxLength < 0)
return true;
if (ranges == null || ranges.Length == 0)
return true;
// Handle null replacement string
replacementString = replacementString ?? string.Empty;
var currentText = textField.Text ?? string.Empty;
// ... range processing ...
return currentText.Length <= maxLength;
}Reasoning:
Priority: Low (performance optimization, not critical) Positive Feedback ✅1. Excellent Multi-Range Handling LogicThe core multi-range processing is very well designed: Array.Sort(rangeArray, (a, b) => (int)(b.Location - a.Location));✅ Descending sort prevents index shifting bugs - This is a clever solution to a complex problem. When multiple ranges are replaced, processing them from highest to lowest position ensures that earlier replacements don't invalidate later range positions. Example of why this matters: 2. Robust Bounds Validationif (start < 0 || length < 0 || start > currentText.Length || start + length > currentText.Length)
return false;✅ Comprehensive edge case handling:
This defensive programming approach prevents crashes from malformed platform data. 3. Perfect Runtime Version Detectionif (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
platformView.ShouldChangeCharactersInRanges += ShouldChangeCharactersInRanges;
}
else
{
platformView.ShouldChangeCharacters += OnShouldChangeCharacters;
}✅ Best practices followed:
4. Backward Compatibility Maintained✅ Tested on iOS 18.5: The app runs successfully with the same behavior, confirming the conditional logic correctly falls back to the legacy ✅ No breaking changes: Existing iOS/MacCatalyst apps continue to work exactly as before on older OS versions. 5. Comprehensive Test Coverage✅ UI test included: Proper test page in 6. Clean, Minimal Changes✅ Surgical fix: Only modifies Test Coverage AssessmentExisting Test CoverageThe PR includes appropriate UI test coverage: HostApp Test Page ( [Issue(IssueTracker.Github, 32016, "iOS 26 MaxLength not enforced on Entry", PlatformAffected.iOS)]
public class Issue32016 : ContentPage
{
public Issue32016()
{
Content = new Entry()
{
AutomationId = "TestEntry",
MaxLength = 10,
};
}
}Appium Test ( [Test]
[Category(UITestCategories.Entry)]
public void EntryMaxLengthEnforcedOnIOS26()
{
App.WaitForElement("TestEntry");
App.Tap("TestEntry");
App.EnterText("TestEntry", "1234567890"); // MaxLength = 10
var text = App.FindElement("TestEntry").GetText();
Assert.That(text!.Length, Is.EqualTo(10));
// Verify additional typing is blocked
App.EnterText("TestEntry", "X");
text = App.FindElement("TestEntry").GetText();
Assert.That(text!.Length, Is.EqualTo(10)); // Still 10
}Assessment: ✅ Good coverage for basic scenario
Test Gaps (Future Enhancements)The included test is sufficient for this PR as it validates the core bug fix. However, additional scenarios could be tested in follow-up work: Paste Behavior (High Priority - related to Critical Issue #1):
Multi-Range Scenarios (Medium Priority - related to Issue #3):
Edge Cases (Low Priority):
Verdict: Current test coverage validates the fix works for the reported issue. The paste truncation issue I identified should be addressed before merge, and a test for paste behavior should be added. Documentation AssessmentPR Description✅ Clear and comprehensive:
Code Documentation
Inline Comments:
Security & Safety AssessmentInput Validation: ✅ Good
Memory Management: ✅ Correct
Platform Safety: ✅ Appropriate
Concerns:
Platform-Specific ConsiderationsiOS 26 API ChangesRoot Cause of Bug: Apple introduced
The old Apple's Documentation (from web search):
This confirms multi-range support is for complex input methods, not just a simple API rename. MacCatalyst Support✅ The PR correctly includes Backward Compatibility✅ Tested on iOS 18.5: Confirmed the app works correctly using the legacy Questions for PR AuthorTo complete this review, please clarify:
Recommendation🔴 REQUEST CHANGES The PR needs modifications before it can be merged. Why Request ChangesCritical Issue: The missing paste truncation feature (Critical Issue #1) is a user experience regression that affects all iOS 26 users. This changes expected behavior that has existed in .NET MAUI since inception. Must be addressed:
What Works WellThe multi-range handling logic is excellent:
The PR is 90% of the way there - it just needs the paste truncation feature restored to maintain feature parity with existing behavior. Next Steps
Once these changes are made, this PR will be an excellent fix for the iOS 26 MaxLength issue. SummaryThis is a well-implemented fix for a critical iOS 26 bug. The multi-range handling logic shows strong understanding of the problem and uses a clever sorting strategy to avoid index shifting issues. The runtime version detection is clean, backward compatibility is maintained, and test coverage is appropriate. However, the PR introduces a behavioral regression by removing the paste truncation feature that exists in pre-iOS 26 implementations. This needs to be restored to maintain consistent user experience across iOS versions. With the paste truncation feature added back and null safety improvements, this PR will be ready to merge and will serve .NET MAUI developers well. Code Quality: ⭐⭐⭐⭐⭐ Excellent (multi-range logic is very well designed) Thank you for working on this important iOS 26 fix! The core implementation is solid - it just needs the paste truncation feature restored to be complete. |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
- Changed from checking text.Length == 10 - Now verifies text == '1234567890' (without the 'x') - Properly validates that MaxLength blocks character beyond limit - Addresses @jfversluis feedback about test flaw
30ab832 to
62d0197
Compare
🤖 AI Summary📊 Expand Full Review🔍 Pre-Flight — Context & Validation📝 Review Session — Use AsSpan for iOS entry truncation ·
|
| File | Type | Changes |
|---|---|---|
src/Core/src/Handlers/Entry/EntryHandler.iOS.cs |
Fix | +67, -3 |
src/Controls/tests/TestCases.HostApp/Issues/Issue32016.cs |
Test (HostApp) | +15 |
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32016.cs |
Test (NUnit) | +26 |
.github/agent-pr-session/pr-32045.md |
+246 |
⚠️ Critical Issue: Agent Session File in PR
The file .github/agent-pr-session/pr-32045.md is an agent review session state file from a prior AI agent run and should NOT be committed to the repository. This file needs to be removed from the PR before merging.
PR Discussion Summary
| Reviewer | Issue | Status |
|---|---|---|
| baaaaif | Use runtime check instead of compile-time check | ✅ DONE (uses OperatingSystem.IsIOSVersionAtLeast(26)) |
| pictos | Use OperatingSystem.IsIOSVersionAtLeast() API |
✅ DONE |
| PureWeen | Remove LINQ usage | ✅ DONE (current code uses for loops) |
| jfversluis | Test should enter 11 chars to actually test MaxLength | ✅ DONE (tests "1234567890x", expects "1234567890") |
| pmahend1 | Verify exact text content | ✅ DONE (asserts Is.EqualTo("1234567890")) |
| pmahend1 | Use AsSpan instead of Substring | Substring) |
| Copilot | Multi-range handling only uses first range | ✅ DONE (current code processes all ranges) |
| Copilot | Code duplication between methods | |
| jBijsterboschNL | Use NSString.Replace(range, replacementString) |
Prior Agent Review
A prior agent review was run (2026-01-17) with s/agent-review-incomplete label:
- Pre-Flight: ✅ COMPLETE
- Gate: "PASSED" on iOS 18.5 (bug does NOT reproduce on iOS 18.5 — invalid verification)
- Fix: INCOMPLETE (only 1/5 models ran)
- Report: COMPLETE (but based on incomplete fix phase)
Key improvement: iOS 26.0 and 26.1 simulators ARE available on this machine, enabling proper gate verification.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #32045 | Runtime check + ShouldChangeCharactersInRanges handler with reverse-order range processing |
⏳ PENDING (Gate) | EntryHandler.iOS.cs (+67, -3) |
Original PR |
🚦 Gate — Test Verification
📝 Review Session — Use AsSpan for iOS entry truncation · c204ed1
Result: ✅ PASSED
Platform: iOS 26
Mode: Full Verification
- Tests FAIL without fix ✅ (MaxLength not enforced on iOS 26)
- Tests PASS with fix ✅ (MaxLength correctly enforced)
Test: Issue32016 - Entry with MaxLength=10, enters "1234567890x" (11 chars), asserts text == "1234567890"
Infrastructure: iOS 26 simulator was available and functional. No issues encountered.
Note: Previous agent review ran Gate on iOS 18.5 (bug does NOT reproduce on 18.5), so that verification was invalid. This run correctly used iOS 26 where the bug actually manifests.
🔧 Fix — Analysis & Comparison
📝 Review Session — Use AsSpan for iOS entry truncation · c204ed1
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (Sonnet) | Subscribe to BOTH delegates simultaneously; arithmetic net-length: currentLen - totalRemoved + replacement.Length * ranges.Length |
✅ PASS | EntryHandler.iOS.cs (+33, -1) |
Clever but arithmetic can be incorrect for overlapping ranges |
| 2 | try-fix (Opus) | Reactive enforcement in EditingChanged via platformView.UpdateMaxLength(virtualView) — no new delegate needed |
✅ PASS | EntryHandler.iOS.cs (+8, -2) |
Simplest fix but characters flash briefly before removal (UX issue) |
| 3 | try-fix (GPT-5.2) | Override InsertText in MaxLengthMauiTextField subclass; enforce before insertion using SelectedTextRange |
✅ PASS | EntryHandler.iOS.cs (+46) |
Added subclass complexity; doesn't cover paste via ReplaceRange |
| 4 | try-fix (GPT-5.3-codex) | State-restore rollback in EditingChanged |
❌ FAIL | N/A | Build error (CS1739 unrelated to fix) |
| 5 | try-fix (Gemini) | Adapter: redirect single-range → legacy OnShouldChangeCharacters; block all multi-range edits when MaxLength set |
✅ PASS | EntryHandler.iOS.cs (+25) |
Too aggressive: blocks all multi-range edits even when under MaxLength |
| 6 | try-fix (GPT-5.2 R2) | Simulate result + truncate replacement + apply to textField.Text manually + return false (smart truncation) |
✅ PASS | EntryHandler.iOS.cs (+95) |
More complex than PR fix; similar approach but more invasive |
| 7 | try-fix (GPT-5.3-codex R2) | Unified pre-commit simulation path; delegates route to same enforcement logic | ✅ PASS | EntryHandler.iOS.cs (+multiple) |
Required modifying test utilities to pass |
| 8 | try-fix (Gemini R2) | Override ReplaceRange(UITextRange, string) in MauiTextField; truncate at protocol level |
✅ PASS | MauiTextField.cs (+37), TextFieldExtensions.cs (+5) |
Requires public API addition; more invasive |
| 9 | try-fix (GPT-5.2 R3) | IME-aware: skip truncation when MarkedTextRange != null, apply when clears in EditingChanged |
✅ PASS | EntryHandler.iOS.cs (+16) |
Variation of #2 with IME awareness; still reactive (flicker risk) |
| 10 | try-fix (GPT-5.3-codex R3) | Override SetMarkedText/UnmarkText in MauiTextField for IME enforcement |
✅ PASS | MauiTextField.cs, EntryHandler.iOS.cs, PublicAPI.Unshipped.txt |
Multiple file changes; IME-specific path |
| 11 | try-fix (Gemini R3) | Override UnmarkText in MauiTextField only |
✅ PASS | MauiTextField.cs, TextFieldExtensions.cs, PublicAPI.Unshipped.txt |
Adds public API; only covers commit path, not typing |
| PR | PR #32045 | Runtime check → subscribe to ShouldChangeCharactersInRanges on iOS 26+; process all ranges in reverse order with string simulation; maintain paste truncation |
✅ PASS (Gate) | EntryHandler.iOS.cs (+67, -3) |
Original PR — validated by Gate on iOS 26 |
Cross-Pollination Summary
| Round | Sonnet | Opus | GPT-5.2 | GPT-5.3-codex | Gemini |
|---|---|---|---|---|---|
| Round 2 | NO NEW IDEAS | NO NEW IDEAS | NEW IDEA (#6) | NEW IDEA (#7) | NEW IDEA (#8) |
| Round 3 | NO NEW IDEAS | NO NEW IDEAS | NEW IDEA (#9) | NEW IDEA (#10) | NEW IDEA (#11) |
| Round 4 | NO NEW IDEAS | NO NEW IDEAS | NEW IDEA | NO NEW IDEAS | NO NEW IDEAS |
Exhausted: Yes (max 3 cross-pollination rounds; 4/5 models confirmed NO NEW IDEAS in Round 4)
Selected Fix: PR's Fix
Reason: The PR's fix is the most correct and complete solution:
- Uses the exact iOS 26-prescribed API (
ShouldChangeCharactersInRanges) - Pre-emptive enforcement — blocks characters BEFORE insertion (no UX flicker)
- Handles ALL ranges in correct reverse order (critical for string index stability)
- Maintains paste truncation behavior for backward compatibility
- Defensive null checks and range validation
- Doesn't require modifying unrelated files or adding public API
Why alternatives don't beat it:
- [Draft] Readme WIP #1: Arithmetic net-length is incorrect for overlapping ranges
- Update README.md #2, [Spec] Shadows #9: Reactive approach causes character flash before removal
- Third #3, [Spec] Shapes #8, [Spec] Rounded Corners #10, [Spec] Brushes #11: Requires modifying MauiTextField or adding public API — more invasive
- Update README.md #5: Blocks ALL multi-range edits (too aggressive — breaks IME composition)
- [Spec] Transitions #6, [Spec] TabView #7: More complex than PR's fix with no benefit
📋 Report — Final Recommendation
📝 Review Session — Use AsSpan for iOS entry truncation · c204ed1
⚠️ Final Recommendation: REQUEST CHANGES
Summary
The fix for iOS 26 Entry.MaxLength is technically correct and well-implemented. Multi-model try-fix exploration (11 attempts across 5 models) confirmed the PR's approach is the best solution. However, one blocker must be resolved before merge: an AI agent session file (.github/agent-pr-session/pr-32045.md) was accidentally committed to the repository and must be removed.
The fix logic also has a paste truncation edge case worth reviewing.
Root Cause
iOS 26 introduced UITextFieldDelegate.ShouldChangeCharactersInRanges(NSValue[], string) which accepts multiple text ranges simultaneously, replacing the old ShouldChangeCharacters(NSRange, string) method. On iOS 26, the old method is never called. MAUI's MaxLength enforcement exclusively used the old delegate, causing it to break silently on iOS 26.
Fix Quality
Correct approach. The PR:
- ✅ Uses
OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26)runtime check (not compile-time) - ✅ Processes ALL ranges in reverse order (critical — forward processing invalidates subsequent string indices)
- ✅ Simulates all range replacements to compute prospective text length before deciding
- ✅ Maintains paste truncation behavior (backward compatibility with pre-iOS 26)
- ✅ Defensive null checks and range bounds validation
- ✅ No LINQ usage
Verified by Gate: Tests FAIL without fix on iOS 26, PASS with fix ✅
Confirmed by 11 multi-model try-fix attempts: All simpler alternatives had flaws (arithmetic errors, UX flicker, too-aggressive blocking, added complexity). PR's fix is the most correct and complete.
Issues to Address
🔴 Blocker: Agent Session File in Repository
File: .github/agent-pr-session/pr-32045.md
This is an AI agent review session state file generated by a prior automated review. It should never be committed to the production repository. This file must be removed before merge.
Fix: Remove the file from the PR:
git rm .github/agent-pr-session/pr-32045.md
git commit -m "Remove agent session file"🟡 Suggestion: Paste Truncation Logic
The paste truncation in ShouldChangeCharactersInRanges uses replacementString.Length >= maxLength as the trigger, then sets VirtualView.Text = replacementString[0..maxLength]. This ignores the existing text context and can produce incorrect results (e.g., if user selects a partial range and pastes, the prefix of existing text is discarded).
Recommend reviewing this logic to match the pre-iOS 26 TextWithinMaxLength behavior more accurately, or document it as an intentional trade-off.
🟡 Minor: Substring vs AsSpan
Range simulation loop uses Substring for string building. The paste truncation already uses AsSpan (as pmahend1 suggested). Consider using AsSpan consistently, or documenting why Substring is acceptable for the range simulation path.
Test Quality
✅ Tests are correct:
- Enters 11 characters ("1234567890x") with MaxLength=10
- Asserts exact text == "1234567890" (character beyond MaxLength is blocked)
- Tests properly validate the fix
Title & Description
✅ Title and description are excellent — no changes recommended.
Try-Fix Results Summary
11 alternative approaches explored. 10 passed tests. PR's fix selected because:
- Other passing approaches had design flaws (UX flicker, incorrect arithmetic, invasive changes, or only covered specific edge cases)
- PR's fix uses the exact iOS 26 API as intended
- Reverse-order range simulation is the correct algorithm for this use case
Selected Fix: PR's fix (with the agent session file removed and paste truncation reviewed)
🔧 Try-Fix Analysis: ✅ 1 passed
✅ Fix 11
Approach: Override UnmarkText in MauiTextField
Override 'unmarkText' (UITextInput protocol method) in 'MauiTextField' to enforce MaxLength when committing predictive or composed text. When text is committed (unmark), check length and truncate to MaxLength before finalizing.
Different from existing fix: This addresses the predictive text entry directly at the protocol level rather than relying on 'ShouldChangeCharacters' which might be bypassed or behave differently on iOS 26+ for predictive text.
diff --git a/src/Core/src/Platform/iOS/MauiTextField.cs b/src/Core/src/Platform/iOS/MauiTextField.cs
index f1bee62410..8643df4067 100644
--- a/src/Core/src/Platform/iOS/MauiTextField.cs
+++ b/src/Core/src/Platform/iOS/MauiTextField.cs
@@ -84,5 +84,15 @@ namespace Microsoft.Maui.Platform
public event EventHandler? TextPropertySet;
[UnconditionalSuppressMessage("Memory", "MEM0001", Justification = "Proven safe in test: MemoryTests.HandlerDoesNotLeak")]
internal event EventHandler? SelectionChanged;
+
+ internal int MaxLength { get; set; } = int.MaxValue;
+
+ public override void UnmarkText()
+ {
+ base.UnmarkText();
+
+ if (MaxLength >= 0 && Text?.Length > MaxLength)
+ Text = Text.Substring(0, MaxLength);
+ }
}
}
\ No newline at end of file
diff --git a/src/Core/src/Platform/iOS/TextFieldExtensions.cs b/src/Core/src/Platform/iOS/TextFieldExtensions.cs
index 880bbdf886..15f81f4cda 100644
--- a/src/Core/src/Platform/iOS/TextFieldExtensions.cs
+++ b/src/Core/src/Platform/iOS/TextFieldExtensions.cs
@@ -72,6 +72,9 @@ namespace Microsoft.Maui.Platform
public static void UpdateMaxLength(this UITextField textField, IEntry entry)
{
+ if (textField is MauiTextField mauiTextField)
+ mauiTextField.MaxLength = entry.MaxLength;
+
var newText = textField.AttributedText.TrimToMaxLength(entry.MaxLength);
if (newText != null && textField.AttributedText != newText)
textField.AttributedText = newText;
diff --git a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
index ebd197b919..4b85bb20df 100644
--- a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
+++ b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
@@ -1,2 +1,3 @@
#nullable enable
override Microsoft.Maui.Platform.MauiView.DidUpdateFocus(UIKit.UIFocusUpdateContext! context, UIKit.UIFocusAnimationCoordinator! coordinator) -> void
+override Microsoft.Maui.Platform.MauiTextField.UnmarkText() -> void
Analysis
Result: Pass
What happened:
The tests passed. I implemented the fix by overriding UnmarkText in MauiTextField and ensuring MaxLength is enforced there.
Why it worked:
The issue on iOS 26+ (likely iOS 16+) is that predictive text input (and possibly other input methods) bypasses the standard ShouldChangeCharacters delegate method or commits text in a way that doesn't respect the delegate's return value. By overriding UnmarkText, which is called when marked text (predictive text) is committed, we can inspect the final text length and truncate it if necessary. This happens at the UITextInput protocol level, which is lower level and more reliable for this scenario.
Insights:
- The MauiTextField didn't have access to MaxLength previously, so I added an internal property and updated TextFieldExtensions to populate it.
- Overriding UnmarkText requires updating the Public API baseline (PublicAPI.Unshipped.txt) because MauiTextField is a public class and overriding a virtual member changes the API surface (or at least Roslyn tracks it).
- This approach avoids modifying the handler logic significantly and keeps the platform-specific workaround contained within the platform view (MauiTextField).
📋 Expand PR Finalization Review
Title: ✅ Good
Current: [iOS 26] Fix Entry MaxLength not enforced due to new multi-range delegate
Description: ✅ Excellent
Description needs updates. See details below.
Code Review: ⚠️ Issues Found
Code Review — PR #32045
PR: #32045
Scope: EntryHandler.iOS.cs, Issue32016.cs (HostApp + NUnit)
🔴 Critical Issues
1. Agent tracking file committed to repository
- File:
.github/agent-pr-session/pr-32045.md - Problem: This file is an agent review tracking artifact that was included in the PR branch. It documents internal agent review state (fix candidates, try-fix attempts, gate status) and should NOT be committed to the repository.
- Recommendation: Remove this file from the PR before merge. It has no value as repository content and exposes internal agent implementation details. If it needs to persist, it should go in a gitignored location.
🟡 Medium Issues
2. HostApp test PlatformAffected.iOS excludes MacCatalyst
- File:
src/Controls/tests/TestCases.HostApp/Issues/Issue32016.cs - Problem: The
[Issue]attribute specifiesPlatformAffected.iOS, but the bug (and fix) also affects MacCatalyst 26 as stated in the PR description. The fix explicitly includesOperatingSystem.IsMacCatalystVersionAtLeast(26)in the runtime check. - Recommendation: Change to
PlatformAffected.iOS | PlatformAffected.MacOS(or the equivalent MacCatalyst flag) so the test runs on MacCatalyst CI lanes as well.
// Current
[Issue(IssueTracker.Github, 32016, "iOS 26 MaxLength not enforced on Entry", PlatformAffected.iOS)]
// Recommended
[Issue(IssueTracker.Github, 32016, "iOS/MacCatalyst 26 MaxLength not enforced on Entry", PlatformAffected.iOS | PlatformAffected.MacOS)]3. Paste truncation condition in multi-range context may be incorrect
- File:
src/Core/src/Handlers/Entry/EntryHandler.iOS.cs,ShouldChangeCharactersInRangesmethod - Problem: The paste truncation logic checks
replacementString.Length >= maxLengthindependently of multi-range context:
if (VirtualView is not null && !shouldChange && !string.IsNullOrWhiteSpace(replacementString) &&
replacementString.Length >= maxLength)
{
VirtualView.Text = replacementString.AsSpan(0, maxLength).ToString();
}When multiple ranges are being replaced (e.g., multi-cursor editing), the paste truncation ignores the text that remains from non-replaced ranges. The final text would be (remaining chars) + replacementString[0..maxLength], which might exceed maxLength rather than enforcing it. This is the same pattern as the existing TextWithinMaxLength extension method for single-range, but multi-range makes it more complex.
- Risk: Low in practice (multi-range replacements with paste overflow are rare), but could allow text beyond MaxLength in edge cases.
- Recommendation: At minimum, add a comment acknowledging this edge case. A more complete fix would recalculate the truncation taking all ranges into account.
🟢 Minor Issues
4. Missing newline at end of test files
- Files:
src/Controls/tests/TestCases.HostApp/Issues/Issue32016.cssrc/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32016.cs
- Problem: Both files end with
\ No newline at end of file. Standard C# files should end with a newline. - Recommendation: Add a newline at the end of both files.
5. No test coverage for paste scenario
- File:
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32016.cs - Problem: The PR description specifically mentions "paste truncation feature to maintain pre-iOS 26 behavior" as a key part of the fix, but the test only exercises keyboard input (
App.EnterText). There's no test verifying that pasting text longer than MaxLength correctly truncates to MaxLength. - Recommendation: Consider adding a second test method that simulates paste (or explicitly notes this is not covered in the test due to Appium paste limitations).
✅ Looks Good
- Runtime version check: Correctly uses
OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26)(not compile-time#if). - Reverse-order range processing: Array is sorted descending by
Locationbefore simulation — this is the correct approach to avoid index invalidation. - Null safety:
ranges == nullcheck,replacementString ??= string.Empty,textField.Text ?? string.Empty— all defensive. - Out-of-bounds range validation: Returns
falsewhen range would read out-of-bounds incurrentText, consistent withTextWithinMaxLengthbehavior for the single-range case. - Disconnect parity: Same OS version check in
Disconnect()correctly unsubscribes the matching delegate. - No LINQ: Uses manual
forloops andArray.Sort— no LINQ usage (consistent with@PureWeen's earlier feedback being already addressed or not applicable to current code). - AsSpan for paste truncation: Paste truncation correctly uses
replacementString.AsSpan(0, maxLength).ToString()(efficient, no extra allocation). - Test correctness: Enters 11 characters (
"1234567890x") withMaxLength=10and asserts exact text"1234567890"— properly validates enforcement. - Reuses existing pattern:
OnShouldChangeCharacters→TextWithinMaxLengthfor pre-iOS 26 path is unchanged and correct.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 32045Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 32045" |
…gate (dotnet#32045) <!-- 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! ### Description of Change Fixes Entry MaxLength property not being enforced on iOS 26 and MacCatalyst 26. **Root cause:** iOS 26 introduced a new delegate method `ShouldChangeCharactersInRanges(NSValue[], string)` that accepts multiple text ranges instead of the old `ShouldChangeCharacters(NSRange, string)` method. The old delegate is no longer called on iOS 26, so MAUI's MaxLength enforcement (which only subscribed to the old delegate) stopped working. **Fix:** - Use runtime version check to subscribe to the correct delegate: - iOS 26+: Subscribe to `ShouldChangeCharactersInRanges` - iOS <26: Subscribe to `ShouldChangeCharacters` (existing behavior) - Process all ranges in reverse order and simulate replacements to calculate final text length - Include paste truncation feature to maintain pre-iOS 26 behavior (truncates long pastes to MaxLength) - Add defensive null checks and range validation **Key insight:** Multiple ranges must be processed in **reverse order** (highest location first) because simulating replacements changes string indices. Processing forward would invalidate subsequent range locations. **What to avoid:** - ❌ Only checking the first range - breaks marked text/IME composition - ❌ Processing ranges in forward order - index calculations become incorrect after first replacement - ❌ Removing paste truncation - breaks backward compatibility **References:** - [UITextFieldDelegate documentation](https://developer.apple.com/documentation/uikit/uitextfielddelegate/textfield(_:shouldchangecharactersin:replacementstring:)) - [What's new in UIKit 26](https://sebvidal.com/blog/whats-new-in-uikit-26/) ### Issues Fixed Fixes dotnet#32016 Fixes dotnet#33316 <img width="1021" height="451" alt="Screenshot 2025-10-17 at 16 14 05" src="https://github.com/user-attachments/assets/cbb3e8ea-b677-4787-8d6b-4b98935d8951" /> https://sebvidal.com/blog/whats-new-in-uikit-26/ <img width="760" height="517" alt="Screenshot 2025-10-17 at 16 14 21" src="https://github.com/user-attachments/assets/e5b2eeff-1c6e-4a91-b34a-ff2de4ff6e23" /> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gate (#32045) <!-- 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! ### Description of Change Fixes Entry MaxLength property not being enforced on iOS 26 and MacCatalyst 26. **Root cause:** iOS 26 introduced a new delegate method `ShouldChangeCharactersInRanges(NSValue[], string)` that accepts multiple text ranges instead of the old `ShouldChangeCharacters(NSRange, string)` method. The old delegate is no longer called on iOS 26, so MAUI's MaxLength enforcement (which only subscribed to the old delegate) stopped working. **Fix:** - Use runtime version check to subscribe to the correct delegate: - iOS 26+: Subscribe to `ShouldChangeCharactersInRanges` - iOS <26: Subscribe to `ShouldChangeCharacters` (existing behavior) - Process all ranges in reverse order and simulate replacements to calculate final text length - Include paste truncation feature to maintain pre-iOS 26 behavior (truncates long pastes to MaxLength) - Add defensive null checks and range validation **Key insight:** Multiple ranges must be processed in **reverse order** (highest location first) because simulating replacements changes string indices. Processing forward would invalidate subsequent range locations. **What to avoid:** - ❌ Only checking the first range - breaks marked text/IME composition - ❌ Processing ranges in forward order - index calculations become incorrect after first replacement - ❌ Removing paste truncation - breaks backward compatibility **References:** - [UITextFieldDelegate documentation](https://developer.apple.com/documentation/uikit/uitextfielddelegate/textfield(_:shouldchangecharactersin:replacementstring:)) - [What's new in UIKit 26](https://sebvidal.com/blog/whats-new-in-uikit-26/) ### Issues Fixed Fixes #32016 Fixes #33316 <img width="1021" height="451" alt="Screenshot 2025-10-17 at 16 14 05" src="https://github.com/user-attachments/assets/cbb3e8ea-b677-4787-8d6b-4b98935d8951" /> https://sebvidal.com/blog/whats-new-in-uikit-26/ <img width="760" height="517" alt="Screenshot 2025-10-17 at 16 14 21" src="https://github.com/user-attachments/assets/e5b2eeff-1c6e-4a91-b34a-ff2de4ff6e23" /> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gate (#32045) <!-- 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! ### Description of Change Fixes Entry MaxLength property not being enforced on iOS 26 and MacCatalyst 26. **Root cause:** iOS 26 introduced a new delegate method `ShouldChangeCharactersInRanges(NSValue[], string)` that accepts multiple text ranges instead of the old `ShouldChangeCharacters(NSRange, string)` method. The old delegate is no longer called on iOS 26, so MAUI's MaxLength enforcement (which only subscribed to the old delegate) stopped working. **Fix:** - Use runtime version check to subscribe to the correct delegate: - iOS 26+: Subscribe to `ShouldChangeCharactersInRanges` - iOS <26: Subscribe to `ShouldChangeCharacters` (existing behavior) - Process all ranges in reverse order and simulate replacements to calculate final text length - Include paste truncation feature to maintain pre-iOS 26 behavior (truncates long pastes to MaxLength) - Add defensive null checks and range validation **Key insight:** Multiple ranges must be processed in **reverse order** (highest location first) because simulating replacements changes string indices. Processing forward would invalidate subsequent range locations. **What to avoid:** - ❌ Only checking the first range - breaks marked text/IME composition - ❌ Processing ranges in forward order - index calculations become incorrect after first replacement - ❌ Removing paste truncation - breaks backward compatibility **References:** - [UITextFieldDelegate documentation](https://developer.apple.com/documentation/uikit/uitextfielddelegate/textfield(_:shouldchangecharactersin:replacementstring:)) - [What's new in UIKit 26](https://sebvidal.com/blog/whats-new-in-uikit-26/) ### Issues Fixed Fixes #32016 Fixes #33316 <img width="1021" height="451" alt="Screenshot 2025-10-17 at 16 14 05" src="https://github.com/user-attachments/assets/cbb3e8ea-b677-4787-8d6b-4b98935d8951" /> https://sebvidal.com/blog/whats-new-in-uikit-26/ <img width="760" height="517" alt="Screenshot 2025-10-17 at 16 14 21" src="https://github.com/user-attachments/assets/e5b2eeff-1c6e-4a91-b34a-ff2de4ff6e23" /> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gate (#32045) <!-- 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! ### Description of Change Fixes Entry MaxLength property not being enforced on iOS 26 and MacCatalyst 26. **Root cause:** iOS 26 introduced a new delegate method `ShouldChangeCharactersInRanges(NSValue[], string)` that accepts multiple text ranges instead of the old `ShouldChangeCharacters(NSRange, string)` method. The old delegate is no longer called on iOS 26, so MAUI's MaxLength enforcement (which only subscribed to the old delegate) stopped working. **Fix:** - Use runtime version check to subscribe to the correct delegate: - iOS 26+: Subscribe to `ShouldChangeCharactersInRanges` - iOS <26: Subscribe to `ShouldChangeCharacters` (existing behavior) - Process all ranges in reverse order and simulate replacements to calculate final text length - Include paste truncation feature to maintain pre-iOS 26 behavior (truncates long pastes to MaxLength) - Add defensive null checks and range validation **Key insight:** Multiple ranges must be processed in **reverse order** (highest location first) because simulating replacements changes string indices. Processing forward would invalidate subsequent range locations. **What to avoid:** - ❌ Only checking the first range - breaks marked text/IME composition - ❌ Processing ranges in forward order - index calculations become incorrect after first replacement - ❌ Removing paste truncation - breaks backward compatibility **References:** - [UITextFieldDelegate documentation](https://developer.apple.com/documentation/uikit/uitextfielddelegate/textfield(_:shouldchangecharactersin:replacementstring:)) - [What's new in UIKit 26](https://sebvidal.com/blog/whats-new-in-uikit-26/) ### Issues Fixed Fixes #32016 Fixes #33316 <img width="1021" height="451" alt="Screenshot 2025-10-17 at 16 14 05" src="https://github.com/user-attachments/assets/cbb3e8ea-b677-4787-8d6b-4b98935d8951" /> https://sebvidal.com/blog/whats-new-in-uikit-26/ <img width="760" height="517" alt="Screenshot 2025-10-17 at 16 14 21" src="https://github.com/user-attachments/assets/e5b2eeff-1c6e-4a91-b34a-ff2de4ff6e23" /> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gate (#32045) <!-- 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! ### Description of Change Fixes Entry MaxLength property not being enforced on iOS 26 and MacCatalyst 26. **Root cause:** iOS 26 introduced a new delegate method `ShouldChangeCharactersInRanges(NSValue[], string)` that accepts multiple text ranges instead of the old `ShouldChangeCharacters(NSRange, string)` method. The old delegate is no longer called on iOS 26, so MAUI's MaxLength enforcement (which only subscribed to the old delegate) stopped working. **Fix:** - Use runtime version check to subscribe to the correct delegate: - iOS 26+: Subscribe to `ShouldChangeCharactersInRanges` - iOS <26: Subscribe to `ShouldChangeCharacters` (existing behavior) - Process all ranges in reverse order and simulate replacements to calculate final text length - Include paste truncation feature to maintain pre-iOS 26 behavior (truncates long pastes to MaxLength) - Add defensive null checks and range validation **Key insight:** Multiple ranges must be processed in **reverse order** (highest location first) because simulating replacements changes string indices. Processing forward would invalidate subsequent range locations. **What to avoid:** - ❌ Only checking the first range - breaks marked text/IME composition - ❌ Processing ranges in forward order - index calculations become incorrect after first replacement - ❌ Removing paste truncation - breaks backward compatibility **References:** - [UITextFieldDelegate documentation](https://developer.apple.com/documentation/uikit/uitextfielddelegate/textfield(_:shouldchangecharactersin:replacementstring:)) - [What's new in UIKit 26](https://sebvidal.com/blog/whats-new-in-uikit-26/) ### Issues Fixed Fixes #32016 Fixes #33316 <img width="1021" height="451" alt="Screenshot 2025-10-17 at 16 14 05" src="https://github.com/user-attachments/assets/cbb3e8ea-b677-4787-8d6b-4b98935d8951" /> https://sebvidal.com/blog/whats-new-in-uikit-26/ <img width="760" height="517" alt="Screenshot 2025-10-17 at 16 14 21" src="https://github.com/user-attachments/assets/e5b2eeff-1c6e-4a91-b34a-ff2de4ff6e23" /> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gate (#32045) <!-- 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! ### Description of Change Fixes Entry MaxLength property not being enforced on iOS 26 and MacCatalyst 26. **Root cause:** iOS 26 introduced a new delegate method `ShouldChangeCharactersInRanges(NSValue[], string)` that accepts multiple text ranges instead of the old `ShouldChangeCharacters(NSRange, string)` method. The old delegate is no longer called on iOS 26, so MAUI's MaxLength enforcement (which only subscribed to the old delegate) stopped working. **Fix:** - Use runtime version check to subscribe to the correct delegate: - iOS 26+: Subscribe to `ShouldChangeCharactersInRanges` - iOS <26: Subscribe to `ShouldChangeCharacters` (existing behavior) - Process all ranges in reverse order and simulate replacements to calculate final text length - Include paste truncation feature to maintain pre-iOS 26 behavior (truncates long pastes to MaxLength) - Add defensive null checks and range validation **Key insight:** Multiple ranges must be processed in **reverse order** (highest location first) because simulating replacements changes string indices. Processing forward would invalidate subsequent range locations. **What to avoid:** - ❌ Only checking the first range - breaks marked text/IME composition - ❌ Processing ranges in forward order - index calculations become incorrect after first replacement - ❌ Removing paste truncation - breaks backward compatibility **References:** - [UITextFieldDelegate documentation](https://developer.apple.com/documentation/uikit/uitextfielddelegate/textfield(_:shouldchangecharactersin:replacementstring:)) - [What's new in UIKit 26](https://sebvidal.com/blog/whats-new-in-uikit-26/) ### Issues Fixed Fixes #32016 Fixes #33316 <img width="1021" height="451" alt="Screenshot 2025-10-17 at 16 14 05" src="https://github.com/user-attachments/assets/cbb3e8ea-b677-4787-8d6b-4b98935d8951" /> https://sebvidal.com/blog/whats-new-in-uikit-26/ <img width="760" height="517" alt="Screenshot 2025-10-17 at 16 14 21" src="https://github.com/user-attachments/assets/e5b2eeff-1c6e-4a91-b34a-ff2de4ff6e23" /> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## What's Coming .NET MAUI inflight/candidate introduces significant improvements across all platforms with focus on quality, performance, and developer experience. This release includes 66 commits with various improvements, bug fixes, and enhancements. ## Activityindicator - [Android] Implemented material3 support for ActivityIndicator by @Dhivya-SF4094 in #33481 <details> <summary>🔧 Fixes</summary> - [Implement material3 support for ActivityIndicator](#33479) </details> - [iOS] Fix: ActivityIndicator IsRunning ignores IsVisible when set to true by @bhavanesh2001 in #28983 <details> <summary>🔧 Fixes</summary> - [[iOS] [ActivityIndicator] `IsRunning` ignores `IsVisible` when set to `true`](#28968) </details> ## Button - [iOS] Button RTL text and image overlap - fix by @kubaflo in #29041 ## Checkbox - [iOS/MacCatalyst] Fix CheckBox foreground color not resetting when set to null by @Ahamed-Ali in #34284 <details> <summary>🔧 Fixes</summary> - [[iOS] Color of the checkBox control is not properly worked on dynamic scenarios](#34278) </details> ## CollectionView - [iOS] Fix: CollectionView does not clear selection when SelectedItem is set to null by @Tamilarasan-Paranthaman in #30420 <details> <summary>🔧 Fixes</summary> - [CollectionView not being able to remove selected item highlight on iOS](#30363) - [[MAUI] Select items traces are preserved](#26187) </details> - [iOS] CV2 ItemsLayout update by @kubaflo in #28675 <details> <summary>🔧 Fixes</summary> - [CollectionView CollectionViewHandler2 doesnt change ItemsLayout on DataTrigger](#28656) - [iOS CollectionView doesn't respect a change to ItemsLayout when using Items2.CollectionViewHandler2](#31259) </details> - [iOS][CV2] Fix CollectionView renders large empty space at bottom of view by @devanathan-vaithiyanathan in #31215 <details> <summary>🔧 Fixes</summary> - [[iOS] [MacCatalyst] CollectionView renders large empty space at bottom of view](#17799) - [[iOS/Mac] CollectionView2 EmptyView takes up large horizontal space even when the content is small](#33201) </details> - [iOS] Fixed issue where group Header/Footer template was set to all items when IsGrouped was true for an ObservableCollection by @Tamilarasan-Paranthaman in #29144 <details> <summary>🔧 Fixes</summary> - [[iOS] Group Header/Footer Repeated for All Items When IsGrouped is True for ObservableCollection in CollectionView](#29141) </details> - [Android] Fix CollectionView selection crash with HeaderTemplate by @NirmalKumarYuvaraj in #34275 <details> <summary>🔧 Fixes</summary> - [[Bug] [Android] System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index](#34247) </details> ## DateTimePicker - [iOS] Fix TimePicker AM/PM frequently changes when the app is closed and reopened by @devanathan-vaithiyanathan in #31066 <details> <summary>🔧 Fixes</summary> - [[iOS] TimePicker AM/PM frequently changes when the app is closed and reopened](#30837) - [Maui 10 iOS TimePicker Strange Characters in place of AM/PM](#33722) </details> - Android TimePicker ignores 24 hour system setting when using Format Property - fix by @kubaflo in #28797 <details> <summary>🔧 Fixes</summary> - [Android TimePicker ignores 24 hour system setting when using Format Property](#28784) </details> ## Drawing - [iOS, Mac, Windows] GraphicsView: Fix Background/BackgroundColor not updating by @NirmalKumarYuvaraj in #31254 <details> <summary>🔧 Fixes</summary> - [[iOS, Mac, Windows] GraphicsView does not change the Background/BackgroundColor](#31239) </details> - [iOS] GraphicsView DrawString - fix by @kubaflo in #26304 <details> <summary>🔧 Fixes</summary> - [DrawString not rendering in iOS.](#24450) - [GraphicsView DrawString not rendering in iOS](#8486) - [DrawString doesn't work on maccatalyst](#4993) </details> - [Android] - Fix Shadow Rendering For Transparent Fill, Stroke (Lines), and Text on Shapes by @prakashKannanSf3972 in #29528 <details> <summary>🔧 Fixes</summary> - [Ellipse Transparency Not Rendered When Drawing Arc Inside the Ellipse Using GraphicsView on Android](#29394) </details> - Revert "[iOS, Mac, Windows] GraphicsView: Fix Background/BackgroundColor not updating (#31254)" by @Ahamed-Ali via @Copilot in #34508 ## Entry - [iOS 26] Fix Entry MaxLength not enforced due to new multi-range delegate by @kubaflo in #32045 <details> <summary>🔧 Fixes</summary> - [iOS 26 - The MaxLength property value is not respected on an Entry control.](#32016) - [.NET MAUI Entry Maximum Length not working on iOS and macOS](#33316) </details> - [iOS] Fixed Entry with IsPassword toggling loses previously entered text by @SubhikshaSf4851 in #30572 <details> <summary>🔧 Fixes</summary> - [Entry with IsPassword toggling loses previously entered text on iOS when IsPassword is re-enabled](#30085) </details> ## Essentials - Fix for FilePicker PickMultipleAsync nullable reference type by @SuthiYuvaraj in #33163 <details> <summary>🔧 Fixes</summary> - [FilePicker PickMultipleAsync nullable reference type](#33114) </details> - Replace deprecated NetworkReachability with NWPathMonitor on iOS/macOS by @jfversluis via @Copilot in #32354 <details> <summary>🔧 Fixes</summary> - [NetworkReachability is obsolete on iOS/maccatalyst 17.4+](#32312) - [Use NWPathMonitor on iOS for Essentials Connectivity](#2574) </details> ## Essentials Connectivity - Update Android Connectivity implementation to use modern APIs by @jfversluis via @Copilot in #30348 <details> <summary>🔧 Fixes</summary> - [Update the Android Connectivity implementation to user modern APIs](#30347) </details> ## Flyout - [iOS] Fixed Flyout icon not updating when root page changes using InsertPageBefore by @Vignesh-SF3580 in #29924 <details> <summary>🔧 Fixes</summary> - [[iOS] Flyout icon not replaced by back button when root page is changed using InsertPageBefore](#29921) </details> ## Flyoutpage - [iOS] Flyout Items Not Displayed in RightToLeft FlowDirection in Landscape - fix by @kubaflo in #26762 <details> <summary>🔧 Fixes</summary> - [Flyout Items Not Displayed in RightToLeft FlowDirection on iOS in Landscape Orientation and Hamburger Icon Positioned Incorrectly](#26726) </details> ## Image - [Android] Implemented Material3 support for Image by @Dhivya-SF4094 in #33661 <details> <summary>🔧 Fixes</summary> - [Implement Material3 support for Image](#33660) </details> ## Keyboard - [iOS] Fix gap at top of view after rotating device while Entry keyboard is visible by @praveenkumarkarunanithi in #34328 <details> <summary>🔧 Fixes</summary> - [Focusing and entering texts on entry control causes a gap at the top after rotating simulator.](#33407) </details> ## Label - [Android] Support for images inside HTML label by @kubaflo in #21679 <details> <summary>🔧 Fixes</summary> - [Label with HTML TextType does not display images on Android](#21044) </details> - [fix] ContentLabel Moved to a nested class to prevent CS0122 in external source generators by @SubhikshaSf4851 in #34514 <details> <summary>🔧 Fixes</summary> - [[MAUI] Building Maui App with sample content results CS0122 errors.](#34512) </details> ## Layout - Optimize ordering of children in Flex layout by @symbiogenesis in #21961 - [Android] Fix control size properties not available during Loaded event by @Vignesh-SF3580 in #31590 <details> <summary>🔧 Fixes</summary> - [CollectionView on Android does not provide height, width, logical children once loaded, works fine on Windows](#14364) - [Control's Loaded event invokes before calling its measure override method.](#14160) </details> ## Mediapicker - [iOS/Android] MediaPicker: Fix image orientation when RotateImage=true by @michalpobuta in #33892 <details> <summary>🔧 Fixes</summary> - [MediaPicker.PickPhotosAsync does not preserve image orientation](#32650) </details> ## Modal - [Windows] Fix modal page keyboard focus not shifting to newly opened modal by @jfversluis in #34212 <details> <summary>🔧 Fixes</summary> - [Keyboard focus does not shift to a newly opened modal page: Pressing enter clicks the button on the page beneath the modal page](#22938) </details> ## Navigation - [iOS26] Apply view margins in title view by @kubaflo in #32205 <details> <summary>🔧 Fixes</summary> - [NavigationPage TitleView iOS 26](#32200) </details> - [iOS] System.NullReferenceException at NavigationRenderer.SetStatusBarStyle() by @kubaflo in #29564 <details> <summary>🔧 Fixes</summary> - [System.NullReferenceException at NavigationRenderer.SetStatusBarStyle()](#29535) </details> - [iOS 26] Fix back button color not applied for NavigationPage by @Shalini-Ashokan in #34326 <details> <summary>🔧 Fixes</summary> - [[iOS] Color not applied to the Back button text or image on iOS 26](#33966) </details> ## Picker - Fix Picker layout on Mac Catalyst 26+ by @kubaflo in #33146 <details> <summary>🔧 Fixes</summary> - [[MacOS 26] Text on picker options are not centered on macOS 26.1](#33229) </details> ## Progressbar - [Android] Implemented Material3 support for ProgressBar by @SyedAbdulAzeemSF4852 in #33926 <details> <summary>🔧 Fixes</summary> - [Implement Material3 support for Progressbar](#33925) </details> ## RadioButton - [iOS, Mac] Fix for RadioButton TextColor for plain Content not working by @HarishwaranVijayakumar in #31940 <details> <summary>🔧 Fixes</summary> - [RadioButton: TextColor for plain Content not working on iOS](#18011) </details> - [All Platforms] Fix RadioButton warning when ControlTemplate is set with View content by @kubaflo in #33839 <details> <summary>🔧 Fixes</summary> - [Seeking clarification on RadioButton + ControlTemplate + Content documentation](#33829) </details> - Visual state change for disabled RadioButton by @kubaflo in #23471 <details> <summary>🔧 Fixes</summary> - [RadioButton disabled UI issue - iOS](#18668) </details> ## SafeArea - [Android] Fix for TabbedPage BottomNavigation BarBackgroundColor not extending to system navigation bar by @praveenkumarkarunanithi in #33428 <details> <summary>🔧 Fixes</summary> - [[Android] TabbedPage BottomNavigation BarBackgroundColor does not extend to system navigation bar area in Edge-to-Edge mode](#33344) </details> ## ScrollView - [Android] ScrollView: Fix HorizontalScrollBarVisibility not updating immediately at runtime by @SubhikshaSf4851 in #33528 <details> <summary>🔧 Fixes</summary> - [Runtime Scrollbar visibility not updating correctly on Android and macOS platforms.](#33400) </details> - Fixed crash when calling ItemsView.ScrollTo on unloaded CollectionView by @kubaflo in #25444 <details> <summary>🔧 Fixes</summary> - [App crashes when calling ItemsView.ScrollTo on unloaded CollectionView](#23014) </details> ## Shell - [Shell] Update logic for iOS large title display in ShellItemRenderer by @kubaflo in #33246 - [iOS][Shell] Fix navigation lifecycle and back button for More tab (>5 tabs) by @kubaflo in #27932 <details> <summary>🔧 Fixes</summary> - [OnAppearing and OnNavigatedTo does not work when using extended Tabbar (tabbar with more than 5 tabs) on IOS.](#27799) - [Shell.BackButtonBehavior does not work when using extended Tabbar (tabbar with more than 5 tabs)on IOS.](#27800) - [Shell TabBar More button causes ViewModel command binding disconnection on back navigation](#30862) - [Content page onappearing not firing if tabs are on the more tab on IOS](#31166) </details> - [iOS 26] Fix tab bar ghosting when navigating from modal to tabbed Shell content by @SubhikshaSf4851 in #34254 <details> <summary>🔧 Fixes</summary> - [[iOS] Tab bar ghosting issue on iOS 26 (liquid glass)](#34143) </details> - Fix for Shell tab visibility not updating when navigating back multiple pages by @BagavathiPerumal in #34403 <details> <summary>🔧 Fixes</summary> - [Changing Shell Tab Visibility when navigating back multiple pages ignores Shell Tab Visibility](#33351) </details> - [iOS/Mac] Fixed OnBackButtonPressed not firing for Shell Navigation Bar Button by @Dhivya-SF4094 in #34401 <details> <summary>🔧 Fixes</summary> - [[iOS] OnBackButtonPressed not firing for Shell Navigation Bar button](#34190) </details> ## Slider - [iOS] Fix for Slider ThumbImageSource is not centered properly on iOS 26 by @HarishwaranVijayakumar in #34019 <details> <summary>🔧 Fixes</summary> - [[iOS 26] Slider ThumbImageSource is not centered properly](#33967) </details> - [Android] Fix improper rendering of ThumbimageSource in Slider by @NirmalKumarYuvaraj in #34064 <details> <summary>🔧 Fixes</summary> - [[Slider] MAUI Slider thumb image is big on android](#13258) </details> ## Stepper - [iOS] Fix Stepper layout overlap in landscape on iOS 26 by @Vignesh-SF3580 in #34325 <details> <summary>🔧 Fixes</summary> - [[.NET10] D10 - Customize cursor position - Rotating simulator makes the button and label overlap](#34273) </details> ## SwipeView - [iOS] SwipeView: Honor FontImageSource.Color in SwipeItem icon by @kubaflo in #27389 <details> <summary>🔧 Fixes</summary> - [[iOS] SwipeView: SwipeItem.IconImageSource.FontImageSource color value not honored](#27377) </details> ## Switch - [Android] Fix Switch thumb shadow missing when ThumbColor is set by @Shalini-Ashokan in #33960 <details> <summary>🔧 Fixes</summary> - [Android Switch Control Thumb Shadow](#19676) </details> ## Toolbar - [iOS/Mac Catalyst 26] Fix Shell.ForegroundColor not applied to ToolbarItems by @SyedAbdulAzeemSF4852 in #34085 <details> <summary>🔧 Fixes</summary> - [[iOS26] Shell.ForegroundColor is not applied to ToolbarItems](#34083) </details> - [Android] VoiceOver on Toolbar Item by @kubaflo in #29596 <details> <summary>🔧 Fixes</summary> - [VoiceOver on Toolbar Item](#29573) - [SemanticProperties do not work on ToolbarItems](#23623) </details> <details> <summary>🧪 Testing (11)</summary> - [Testing] Additional Feature Matrix Test Cases for CollectionView by @TamilarasanSF4853 in #32432 - [Testing] Feature Matrix UITest Cases for VisualStateManager by @LogishaSelvarajSF4525 in #34146 - [Testing] Feature Matrix UITest Cases for Clip by @TamilarasanSF4853 in #34121 - [Testing] Feature matrix UITest Cases for Map Control by @HarishKumarSF4517 in #31656 - [Testing] Feature matrix UITest Cases for Visual Transform Control by @HarishKumarSF4517 in #32799 - [Testing] Feature Matrix UITest Cases for Shell Pages by @NafeelaNazhir in #33945 - [Testing] Feature Matrix UITest Cases for Triggers by @HarishKumarSF4517 in #34152 - [Testing] Refactoring Feature Matrix UITest Cases for CheckBox Control by @LogishaSelvarajSF4525 in #34283 - Resolve UI test Build Sample failures - Candidate March 16 by @Ahamed-Ali in #34442 - Fix the failures in the Candidate branch- March 16 by @Ahamed-Ali in #34453 <details> <summary>🔧 Fixes</summary> - [March 16th, Candidate](#34437) </details> - Fixed the iOS 18.5 Candidate failures (March 16,2026) by @Ahamed-Ali in #34593 <details> <summary>🔧 Fixes</summary> - [March 16th, Candidate](#34437) </details> </details> <details> <summary>📦 Other (2)</summary> - Fixed candidate test failures caused by PR #33428. by @Ahamed-Ali in #34515 <details> <summary>🔧 Fixes</summary> - [[.NET10] On Android, there's a big space at the top for I, M and N2 & N3](#34509) </details> - Revert "[iOS] Button RTL text and image overlap - fix (#29041)" in b0497af </details> <details> <summary>📝 Issue References</summary> Fixes #2574, Fixes #4993, Fixes #8486, Fixes #13258, Fixes #14160, Fixes #14364, Fixes #17799, Fixes #18011, Fixes #18668, Fixes #19676, Fixes #21044, Fixes #22938, Fixes #23014, Fixes #23623, Fixes #24450, Fixes #26187, Fixes #26726, Fixes #27377, Fixes #27799, Fixes #27800, Fixes #28656, Fixes #28784, Fixes #28968, Fixes #29141, Fixes #29394, Fixes #29535, Fixes #29573, Fixes #29921, Fixes #30085, Fixes #30347, Fixes #30363, Fixes #30837, Fixes #30862, Fixes #31166, Fixes #31239, Fixes #31259, Fixes #32016, Fixes #32200, Fixes #32312, Fixes #32650, Fixes #33114, Fixes #33201, Fixes #33229, Fixes #33316, Fixes #33344, Fixes #33351, Fixes #33400, Fixes #33407, Fixes #33479, Fixes #33660, Fixes #33722, Fixes #33829, Fixes #33925, Fixes #33966, Fixes #33967, Fixes #34083, Fixes #34143, Fixes #34190, Fixes #34247, Fixes #34273, Fixes #34278, Fixes #34437, Fixes #34509, Fixes #34512 </details> **Full Changelog**: main...inflight/candidate
…gate (dotnet#32045) <!-- 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! ### Description of Change Fixes Entry MaxLength property not being enforced on iOS 26 and MacCatalyst 26. **Root cause:** iOS 26 introduced a new delegate method `ShouldChangeCharactersInRanges(NSValue[], string)` that accepts multiple text ranges instead of the old `ShouldChangeCharacters(NSRange, string)` method. The old delegate is no longer called on iOS 26, so MAUI's MaxLength enforcement (which only subscribed to the old delegate) stopped working. **Fix:** - Use runtime version check to subscribe to the correct delegate: - iOS 26+: Subscribe to `ShouldChangeCharactersInRanges` - iOS <26: Subscribe to `ShouldChangeCharacters` (existing behavior) - Process all ranges in reverse order and simulate replacements to calculate final text length - Include paste truncation feature to maintain pre-iOS 26 behavior (truncates long pastes to MaxLength) - Add defensive null checks and range validation **Key insight:** Multiple ranges must be processed in **reverse order** (highest location first) because simulating replacements changes string indices. Processing forward would invalidate subsequent range locations. **What to avoid:** - ❌ Only checking the first range - breaks marked text/IME composition - ❌ Processing ranges in forward order - index calculations become incorrect after first replacement - ❌ Removing paste truncation - breaks backward compatibility **References:** - [UITextFieldDelegate documentation](https://developer.apple.com/documentation/uikit/uitextfielddelegate/textfield(_:shouldchangecharactersin:replacementstring:)) - [What's new in UIKit 26](https://sebvidal.com/blog/whats-new-in-uikit-26/) ### Issues Fixed Fixes dotnet#32016 Fixes dotnet#33316 <img width="1021" height="451" alt="Screenshot 2025-10-17 at 16 14 05" src="https://github.com/user-attachments/assets/cbb3e8ea-b677-4787-8d6b-4b98935d8951" /> https://sebvidal.com/blog/whats-new-in-uikit-26/ <img width="760" height="517" alt="Screenshot 2025-10-17 at 16 14 21" src="https://github.com/user-attachments/assets/e5b2eeff-1c6e-4a91-b34a-ff2de4ff6e23" /> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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!
Description of Change
Fixes Entry MaxLength property not being enforced on iOS 26 and MacCatalyst 26.
Root cause: iOS 26 introduced a new delegate method
ShouldChangeCharactersInRanges(NSValue[], string)that accepts multiple text ranges instead of the oldShouldChangeCharacters(NSRange, string)method. The old delegate is no longer called on iOS 26, so MAUI's MaxLength enforcement (which only subscribed to the old delegate) stopped working.Fix:
ShouldChangeCharactersInRangesShouldChangeCharacters(existing behavior)Key insight: Multiple ranges must be processed in reverse order (highest location first) because simulating replacements changes string indices. Processing forward would invalidate subsequent range locations.
What to avoid:
References:
Issues Fixed
Fixes #32016
Fixes #33316
https://sebvidal.com/blog/whats-new-in-uikit-26/
