Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue32016.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Maui.Controls.Sample.Issues
{
[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,
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

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", "1234567890x");

var text = App.FindElement("TestEntry").GetText();
Assert.That(text, Is.EqualTo("1234567890"), "Text should be '1234567890' - the 'x' should be blocked by MaxLength");
}
}
70 changes: 67 additions & 3 deletions src/Core/src/Handlers/Entry/EntryHandler.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,14 @@ public void Connect(IEntry virtualView, MauiTextField platformView)
platformView.EditingChanged += OnEditingChanged;
platformView.EditingDidEnd += OnEditingEnded;
platformView.TextPropertySet += OnTextPropertySet;
platformView.ShouldChangeCharacters += OnShouldChangeCharacters;
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
platformView.ShouldChangeCharactersInRanges += ShouldChangeCharactersInRanges;
}
else
{
platformView.ShouldChangeCharacters += OnShouldChangeCharacters;
}
}

public void Disconnect(MauiTextField platformView)
Expand All @@ -148,7 +155,14 @@ public void Disconnect(MauiTextField platformView)
platformView.EditingChanged -= OnEditingChanged;
platformView.EditingDidEnd -= OnEditingEnded;
platformView.TextPropertySet -= OnTextPropertySet;
platformView.ShouldChangeCharacters -= OnShouldChangeCharacters;
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
platformView.ShouldChangeCharactersInRanges -= ShouldChangeCharactersInRanges;
}
else
{
platformView.ShouldChangeCharacters -= OnShouldChangeCharacters;
}

if (_set)
platformView.SelectionChanged -= OnSelectionChanged;
Expand Down Expand Up @@ -213,6 +227,56 @@ void OnTextPropertySet(object? sender, EventArgs e)
}
}

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 ??= 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;

// Paste truncation feature (matches pre-iOS 26 behavior)
if (VirtualView is not null && !shouldChange && !string.IsNullOrWhiteSpace(replacementString) &&
replacementString.Length >= maxLength)
{
VirtualView.Text = replacementString.AsSpan(0, maxLength).ToString();
}

return shouldChange;
}

bool OnShouldChangeCharacters(UITextField textField, NSRange range, string replacementString) =>
VirtualView?.TextWithinMaxLength(textField.Text, range, replacementString) ?? false;

Expand All @@ -232,4 +296,4 @@ void OnSelectionChanged(object? sender, EventArgs e)
}
}
}
}
}
Loading