Skip to content
Closed
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
78 changes: 78 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue30085.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;

namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 30085, "Entry with IsPassword toggling loses previously entered text on iOS when IsPassword is re-enabled", PlatformAffected.iOS)]
public class Issue30085 : TestContentPage
{
public Issue30085()
{
}

protected override void Init()
{
var entry = new Entry
{
IsPassword = true,
Placeholder = "Password",
Background = Brush.Gray,
ReturnType = ReturnType.Done,
AutomationId = "passwordEntry"
};

var toggleButton = new Button
{
Text = "Toggle Password Visibility",
AutomationId = "toggleButton"
};

var resultLabel = new Label
{
Text = "Enter text, toggle twice, then type more text. Original text should be preserved.",
AutomationId = "resultLabel"
};

var statusLabel = new Label
{
Text = "Status: Ready for testing",
AutomationId = "statusLabel"
};

toggleButton.Clicked += (sender, e) =>
{
entry.IsPassword = !entry.IsPassword;
toggleButton.Text = entry.IsPassword ? "Show Password" : "Hide Password";
statusLabel.Text = $"Status: Password visibility is {(entry.IsPassword ? "hidden" : "visible")}";
};

// Add a text changed handler to show current text for testing
entry.TextChanged += (sender, e) =>
{
resultLabel.Text = $"Current text: '{e.NewTextValue}' (Length: {e.NewTextValue?.Length ?? 0})";
};

Content = new StackLayout
{
Padding = 20,
Children =
{
resultLabel,
entry,
toggleButton,
statusLabel,
new Label
{
Text = "Test Steps:\n" +
"1. Type 'password123' in the entry field\n" +
"2. Tap 'Toggle Password Visibility' to show text\n" +
"3. Tap 'Toggle Password Visibility' again to hide text\n" +
"4. Type '456' at the end\n" +
"5. The text should be 'password123456'",
FontSize = 12,
TextColor = Colors.Gray
}
}
};
}
}
}
5 changes: 5 additions & 0 deletions src/Core/src/Platform/iOS/TextFieldExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ public static void UpdateIsPassword(this UITextField textField, IEntry entry)
{
if (entry.IsPassword && textField.IsFirstResponder)
{
// Store the current text before disabling to prevent text loss
var currentText = textField.Text;
textField.Enabled = false;
textField.SecureTextEntry = true;
textField.Enabled = entry.IsEnabled;
// Restore the text after re-enabling if it was cleared
if (textField.Text != currentText)
textField.Text = currentText;
textField.BecomeFirstResponder();
}
else
Expand Down
51 changes: 51 additions & 0 deletions src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -863,5 +863,56 @@ int GetNativeSelectionLength(EntryHandler entryHandler)

return -1;
}

[Fact(DisplayName = "Password Toggling Preserves Text When Focused")]
public async Task PasswordTogglingPreservesTextWhenFocused()
{
var initialText = "password123";
var additionalText = "456";
var expectedFinalText = initialText + additionalText;

var entry = new EntryStub()
{
Text = initialText,
IsPassword = true
};

await InvokeOnMainThreadAsync(async () =>
{
var handler = CreateHandler<EntryHandler>(entry);
var platformView = GetNativeEntry(handler);

// Simulate the field being focused (this is key to reproducing the issue)
platformView.BecomeFirstResponder();

// Verify initial state - password mode with text
Assert.True(GetNativeIsPassword(handler));
Assert.Equal(initialText, GetNativeText(handler));

// Toggle password off (text becomes visible)
entry.IsPassword = false;
handler.UpdateValue(nameof(IEntry.IsPassword));

// Verify password mode is off and text is preserved
Assert.False(GetNativeIsPassword(handler));
Assert.Equal(initialText, GetNativeText(handler));

// Toggle password back on (this is where the bug occurs)
entry.IsPassword = true;
handler.UpdateValue(nameof(IEntry.IsPassword));

// Verify password mode is on and text is still preserved
Assert.True(GetNativeIsPassword(handler));
Assert.Equal(initialText, GetNativeText(handler));

// Simulate user typing additional text (this triggers the text loss bug)
SetNativeText(handler, expectedFinalText);

// Verify the original text is still preserved
Assert.Equal(expectedFinalText, GetNativeText(handler));

platformView.ResignFirstResponder();
});
}
}
}