diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/ClearButtonVisiblityButton_TextPresent.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/ClearButtonVisiblityButton_TextPresent.png index b2800bdf85f8..f03ec48edae6 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/ClearButtonVisiblityButton_TextPresent.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/ClearButtonVisiblityButton_TextPresent.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/EntryClearButtonColorShouldMatchTextColor.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/EntryClearButtonColorShouldMatchTextColor.png index 95446262f897..44e40b771b5b 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/EntryClearButtonColorShouldMatchTextColor.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/EntryClearButtonColorShouldMatchTextColor.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/EntryClearButtonColorShouldUpdateOnThemeChange.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/EntryClearButtonColorShouldUpdateOnThemeChange.png index 5723dcdc4e4f..7d3256839f5c 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/EntryClearButtonColorShouldUpdateOnThemeChange.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/EntryClearButtonColorShouldUpdateOnThemeChange.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyClearButtonVisiblityWhenTextAlignedHorizontally.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyClearButtonVisiblityWhenTextAlignedHorizontally.png index 961618f2213c..cece5ed4fcf8 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyClearButtonVisiblityWhenTextAlignedHorizontally.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyClearButtonVisiblityWhenTextAlignedHorizontally.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyClearButtonVisiblityWhenTextAlignedVertically.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyClearButtonVisiblityWhenTextAlignedVertically.png index 11b9c297042e..6bf658693f19 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyClearButtonVisiblityWhenTextAlignedVertically.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyClearButtonVisiblityWhenTextAlignedVertically.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyTextWhenKeyboardTypeSet.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyTextWhenKeyboardTypeSet.png index 32e91cf48a98..fb5dad134dec 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyTextWhenKeyboardTypeSet.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyTextWhenKeyboardTypeSet.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyTextWhenReturnTypeSet.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyTextWhenReturnTypeSet.png index 3c5e7d1ec2d2..551969ee1113 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyTextWhenReturnTypeSet.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios-26/VerifyTextWhenReturnTypeSet.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ClearButtonVisiblityButton_TextPresent.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ClearButtonVisiblityButton_TextPresent.png index dcd269ae9055..18d67ccb5c11 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ClearButtonVisiblityButton_TextPresent.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/ClearButtonVisiblityButton_TextPresent.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldMatchTextColor.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldMatchTextColor.png index 95446262f897..c902135f67cd 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldMatchTextColor.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldMatchTextColor.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldUpdateOnThemeChange.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldUpdateOnThemeChange.png index 5723dcdc4e4f..d9262374641d 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldUpdateOnThemeChange.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/EntryClearButtonColorShouldUpdateOnThemeChange.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearButtonVisiblityWhenTextAlignedHorizontally.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearButtonVisiblityWhenTextAlignedHorizontally.png index 983d8edf2283..fea803ae33fc 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearButtonVisiblityWhenTextAlignedHorizontally.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearButtonVisiblityWhenTextAlignedHorizontally.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearButtonVisiblityWhenTextAlignedVertically.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearButtonVisiblityWhenTextAlignedVertically.png index 6926d1969238..03b130a4c1df 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearButtonVisiblityWhenTextAlignedVertically.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearButtonVisiblityWhenTextAlignedVertically.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearVisiblityButtonWhenTextColorChanged.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearVisiblityButtonWhenTextColorChanged.png index 3399eb244c1e..adeabbcf7f08 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearVisiblityButtonWhenTextColorChanged.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyClearVisiblityButtonWhenTextColorChanged.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyTextWhenKeyboardTypeSet.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyTextWhenKeyboardTypeSet.png index 96b5837a77f8..448c7f107876 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyTextWhenKeyboardTypeSet.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyTextWhenKeyboardTypeSet.png differ diff --git a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyTextWhenReturnTypeSet.png b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyTextWhenReturnTypeSet.png index 0011d2802ed1..c635625f6a3f 100644 Binary files a/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyTextWhenReturnTypeSet.png and b/src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyTextWhenReturnTypeSet.png differ diff --git a/src/Core/src/Platform/iOS/TextFieldExtensions.cs b/src/Core/src/Platform/iOS/TextFieldExtensions.cs index 7d1040697be5..f72c4f56fc69 100644 --- a/src/Core/src/Platform/iOS/TextFieldExtensions.cs +++ b/src/Core/src/Platform/iOS/TextFieldExtensions.cs @@ -276,7 +276,19 @@ internal static void UpdateClearButtonColor(this UITextField textField, IEntry e return renderer.CreateImage((context) => { - image.Draw(CGPoint.Empty, CGBlendMode.Normal, 1.0f); + // Draw a bitmap-backed copy of the clear button's image rather than the image itself. + // Drawing the image directly produced reduced alpha, and since the SourceIn fill below + // caps the final color's alpha at the drawn shape's alpha, that reduced alpha made the + // tinted clear button appear dimmed. Drawing a plain CGImage-backed UIImage avoids this + // and renders at full opacity. + var bitmapImage = image.CGImage is CGImage cgImage + ? new UIImage(cgImage, image.CurrentScale, image.Orientation) + : image; + // bitmapImage's Size can be smaller than the original image's Size, so drawing it at + // CGPoint.Empty shifts it to the top-left instead of centering it. Center it manually + // to preserve the original image's rendered position. + var origin = new CGPoint((size.Width - bitmapImage.Size.Width) / 2, (size.Height - bitmapImage.Size.Height) / 2); + bitmapImage.Draw(origin, CGBlendMode.Normal, 1.0f); color.ColorWithAlpha(1.0f).SetFill(); var rect = new CGRect(CGPoint.Empty.X, CGPoint.Empty.Y, image.Size.Width, image.Size.Height); diff --git a/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.iOS.cs index 1cb5c3f8895e..03216162bbed 100644 --- a/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.iOS.cs +++ b/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.iOS.cs @@ -143,6 +143,40 @@ await AttachAndRun(entry, async (handler) => }); } + [Fact(DisplayName = "Clear button tint matches TextColor at full opacity")] + public async Task ClearButtonTintMatchesTextColorAtFullOpacity() + { + EntryStub entry = new EntryStub + { + Text = "MAUI", + ClearButtonVisibility = ClearButtonVisibility.WhileEditing, + TextColor = null + }; + + await AttachAndRun(entry, async (handler) => + { + await AssertEventually(() => handler.PlatformView.IsLoaded()); + Assert.True(handler.PlatformView.BecomeFirstResponder()); + await AssertEventually(() => handler.PlatformView.IsFirstResponder); + + var clearButton = GetNativeClearButton(handler); + Assert.NotNull(clearButton); + + entry.TextColor = Colors.Blue; + handler.UpdateValue(nameof(IEntry.TextColor)); + + var tintedImage = clearButton.ImageForState(UIControlState.Normal); + Assert.NotNull(tintedImage); + + Assert.Equal(UIImageRenderingMode.Automatic, tintedImage.RenderingMode); + + // the clear button was tinted from the system symbol image, which UIImage.Draw re-rasterizes + // at reduced opacity (~20%), so the glyph never reached full opacity and looked dimmed. + // The tinted glyph must contain fully opaque pixels so it matches TextColor. + Assert.Equal(byte.MaxValue, GetMaxAlpha(tintedImage)); + }); + } + [Fact] public async Task NextMovesToNextEntry() { @@ -885,6 +919,43 @@ bool GetNativeClearButtonVisibility(EntryHandler entryHandler) => static UIButton GetNativeClearButton(EntryHandler entryHandler) => GetNativeEntry(entryHandler).ValueForKey(new NSString("clearButton")) as UIButton; + static byte GetMaxAlpha(UIImage image) + { + const int bitsPerComponent = 8; + const int bytesPerPixel = 4; // R, G, B, A (CGImageAlphaInfo.PremultipliedLast) + const int alphaByteOffset = 3; // A is the 4th byte within each RGBA pixel + + var cgImage = image.CGImage; + Assert.NotNull(cgImage); + + var width = (int)cgImage.Width; + var height = (int)cgImage.Height; + var bytesPerRow = width * bytesPerPixel; + var pixels = new byte[height * bytesPerRow]; + + using var colorSpace = CGColorSpace.CreateDeviceRGB(); + using (var context = new CGBitmapContext(pixels, width, height, bitsPerComponent, bytesPerRow, colorSpace, CGImageAlphaInfo.PremultipliedLast)) + { + context.DrawImage(new CGRect(0, 0, width, height), cgImage); + } + + byte maxAlpha = 0; + for (int i = alphaByteOffset; i < pixels.Length; i += bytesPerPixel) + { + if (pixels[i] == byte.MaxValue) + { + return byte.MaxValue; + } + + if (pixels[i] > maxAlpha) + { + maxAlpha = pixels[i]; + } + } + + return maxAlpha; + } + UITextAlignment GetNativeHorizontalTextAlignment(EntryHandler entryHandler) => GetNativeEntry(entryHandler).TextAlignment;