Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] Fix for Shell custom FlyoutIcon display problem #26016

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

Tamilarasan-Paranthaman
Copy link
Contributor

@Tamilarasan-Paranthaman Tamilarasan-Paranthaman commented Nov 21, 2024

Issue: The flyout icon is aligned to the center instead of the left, causing the title to extend beyond the view.

Root Cause of the issue

  • The custom image size is too large, causing it to render in the center and leaving extra space on the left.

Description of Change

  • I resized the custom image to match the hamburger size using the UIGraphics concept and implemented caching to prevent redundant image rendering.

Reference for icon size: https://github.com/dotnet/maui/blob/main/src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellPageRendererTracker.cs#L450

Issues Fixed

Fixes #25920

Tested the behaviour in the following platforms

  • Android
  • Windows
  • iOS
  • Mac

Screenshot

iOS

Before Issue Fix After Issue Fix

@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Nov 21, 2024
@jsuarezruiz jsuarezruiz added platform/iOS 🍎 area-controls-shell Shell Navigation, Routes, Tabs, Flyout labels Nov 21, 2024
@jsuarezruiz
Copy link
Contributor

jsuarezruiz commented Nov 21, 2024

/azp run

This comment was marked as outdated.

@Tamilarasan-Paranthaman Tamilarasan-Paranthaman marked this pull request as ready for review November 21, 2024 13:19
@Tamilarasan-Paranthaman Tamilarasan-Paranthaman requested a review from a team as a code owner November 21, 2024 13:20
@@ -336,7 +336,7 @@ void UpdateLeftToolbarItems()

if (image != null)
{
icon = result?.Value;
icon = ResizeImage(result?.Value, new CGSize(23f, 23f));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is iOS the only one that doesn't resize?
Is this size correct under all the screen options? (@2x etc) https://developer.apple.com/design/human-interface-guidelines/toolbars

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is iOS the only one that doesn't resize? Is this size correct under all the screen options? (@2x etc) https://developer.apple.com/design/human-interface-guidelines/toolbars

@jsuarezruiz, I have tested the icon under all device screen options, and it works properly on all devices. The size I am using for the custom icon is based on the default Hamburger icon size. Please refer to the code below for your reference

Reference for icon size:

@@ -0,0 +1,27 @@
#if !WINDOWS
// In Windows, the foreground color is not applied to the custom icon,
// so as of now, the test is not applicable for Windows.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows is the only one having a different behavior, right?
Could you open a new issue and include the link in the comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows is the only one having a different behavior, right? Could you open a new issue and include the link in the comment.

@jsuarezruiz, the new issue for the Windows platform has been logged and I have added a comment. Could you please check and provide your concerns if any?

// https://github.com/dotnet/maui/issues/26148

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

}
}
}
UIImage ResizeImage(UIImage sourceImage, CGSize targetSize)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates a new image with new data, and potentially wastes memory. I feel like we have this problem solved somewhere before that just scales the image. Let me see...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This place:

static UIImage ResizeImageSource(UIImage sourceImage, nfloat maxWidth, nfloat maxHeight, CGSize originalImageSize, bool shouldScaleUp = false)
{
if (sourceImage is null || sourceImage.CGImage is null)
return null;
maxWidth = (nfloat)Math.Min(maxWidth, originalImageSize.Width);
maxHeight = (nfloat)Math.Min(maxHeight, originalImageSize.Height);
var sourceSize = sourceImage.Size;
float maxResizeFactor = (float)Math.Min(maxWidth / sourceSize.Width, maxHeight / sourceSize.Height);
if (maxResizeFactor > 1 && !shouldScaleUp)
return sourceImage;
return UIImage.FromImage(sourceImage.CGImage, sourceImage.CurrentScale / maxResizeFactor, sourceImage.Orientation);
}

I think we can maybe extract this into some helper class, maybe an extension for UIImage?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we probably want to preserve aspect ratio, the code sourceImage.Draw(new CGRect(0, 0, targetSize.Width, targetSize.Height)); will stretch in this PR. So the scaling way we have in button is probably safer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattleibow, Thank you for the suggestion. I have moved the ResizeImageSource method from Button.iOS to the UIImageExtensions class. I applied the same logic in the ShellPageRendererTracker for the custom FlyoutIcon and also updated the button related code. Could you please review these changes and let me know if you have any concerns?

@sheiksyedm sheiksyedm added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Dec 6, 2024
@PureWeen PureWeen self-assigned this Dec 12, 2024
@PureWeen
Copy link
Member

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@PureWeen
Copy link
Member

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@jsuarezruiz
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Member

@PureWeen PureWeen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you split the android change out from this PR?

I think the resizing parts of this for the iOS Icon is going to need a little bit deeper of an analysis.

Like, I'm curious if users have an icon that's 30x30, or just slightly bigger than 40x40 if we want to resize.

Playing with the sandbox here's a few sizes

I don't know if we can force an opinion here on the size of that icon for users. If this is an issue in our template we should generate an image at the size it should be

30x30
image

23x23
image

40x40
image

@Tamilarasan-Paranthaman Tamilarasan-Paranthaman changed the title [iOS] [Android] Fix for Shell custom FlyoutIcon display problem [iOS] Fix for Shell custom FlyoutIcon display problem Jan 31, 2025
@Ahamed-Ali
Copy link
Contributor

Can you split the android change out from this PR?

I think the resizing parts of this for the iOS Icon is going to need a little bit deeper of an analysis.

Like, I'm curious if users have an icon that's 30x30, or just slightly bigger than 40x40 if we want to resize.

Playing with the sandbox here's a few sizes

I don't know if we can force an opinion here on the size of that icon for users. If this is an issue in our template we should generate an image at the size it should be

30x30 image

23x23 image

40x40 image

Hi @PureWeen , I have created a separate PR 27502 for Android changes. Please review it and share your concerns.

@jsuarezruiz
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@jsuarezruiz
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@@ -25,6 +26,24 @@ public static UIImage ScaleImage(this UIImage target, float maxWidth, float maxH
return ScaleImage(target, new CGSize(targetWidth, targetHeight), disposeOriginal);
}

internal static UIImage ResizeImageSource(this UIImage sourceImage, nfloat maxWidth, nfloat maxHeight, CGSize originalImageSize, bool shouldScaleUp = false)
{
if (sourceImage is null || sourceImage.CGImage is null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use the null-conditional operator to simplify the check:
if (sourceImage?.CGImage is null)

if (maxResizeFactor > 1 && !shouldScaleUp)
return sourceImage;

return UIImage.FromImage(sourceImage.CGImage, sourceImage.CurrentScale / maxResizeFactor, sourceImage.Orientation);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UIImage.FromImage creates a new instance of UIImage with the specified parameters. Could modify the implementation to just transform the current one?

internal static UIImage ResizeImageSource(this UIImage sourceImage, nfloat maxWidth, nfloat maxHeight, CGSize originalImageSize, bool shouldScaleUp = false)
{
	if (sourceImage?.CGImage is null)
		return null;

	maxWidth = (nfloat)Math.Min(maxWidth, originalImageSize.Width);
	maxHeight = (nfloat)Math.Min(maxHeight, originalImageSize.Height);

	var sourceSize = sourceImage.Size;
	var maxResizeFactor = (nfloat)Math.Min(maxWidth / sourceSize.Width, maxHeight / sourceSize.Height);

	if (maxResizeFactor > 1 && !shouldScaleUp)
		return sourceImage;

	var newSize = new CGSize(sourceSize.Width * maxResizeFactor, sourceSize.Height * maxResizeFactor);

	UIGraphics.BeginImageContextWithOptions(newSize, false, 0.0f);
	sourceImage.Draw(new CGRect(0, 0, newSize.Width, newSize.Height));
	var resizedImage = UIGraphics.GetImageFromCurrentImageContext();
	UIGraphics.EndImageContext();

	return resizedImage;
}

This approach uses the image context to resize the image, should be more efficient than creating a new instance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-controls-shell Shell Navigation, Routes, Tabs, Flyout community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/iOS 🍎
Projects
Status: Changes Requested
Development

Successfully merging this pull request may close these issues.

.NET MAUI set AppShell custom FlyoutIcon display problem
6 participants