File: Platform\iOS\Extensions\ButtonExtensions.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using CoreGraphics;
using Foundation;
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Internals;
using ObjCRuntime;
using UIKit;
using static Microsoft.Maui.Controls.Button;
 
namespace Microsoft.Maui.Controls.Platform
{
	public static class ButtonExtensions
	{
		/// <summary>
		/// Compute the <see cref="CGRect"/> for the title of the button.
		/// </summary>
		/// <param name="platformButton"></param>
		/// <param name="widthConstraint"></param>
		/// <param name="heightConstraint"></param>
		/// <returns>Returns the <see cref="CGRect"/> that contains the button's title.</returns>
		/// <remarks>The <see cref="NSStringDrawingOptions.UsesDeviceMetrics"/> flag is useful for when there is truncation in the title so we will take the max width of the two bounding rects. The GetBoundingRect method does not always give a great representation of what characters will be visible on the button but will not be needed when the UIButton.Configuration API is implemented. </remarks>
		internal static CGRect GetTitleBoundingRect(this UIButton platformButton, double widthConstraint, double heightConstraint)
		{
			if ((platformButton.CurrentAttributedTitle != null || platformButton.CurrentTitle != null)
				&& widthConstraint > 0 && heightConstraint > 0)
			{
				var title =
					   platformButton.CurrentAttributedTitle ??
					   new NSAttributedString(platformButton.CurrentTitle, new UIStringAttributes { Font = platformButton.TitleLabel.Font });
 
				// Use the available height when calculating the bounding rect
				var lineHeight = platformButton.TitleLabel.Font.LineHeight;
				var availableHeight = heightConstraint;
 
				// If the line break mode is one of the truncation modes, limit the height to the line height
				if (platformButton.TitleLabel.LineBreakMode == UILineBreakMode.HeadTruncation ||
					platformButton.TitleLabel.LineBreakMode == UILineBreakMode.MiddleTruncation ||
					platformButton.TitleLabel.LineBreakMode == UILineBreakMode.TailTruncation ||
					platformButton.TitleLabel.LineBreakMode == UILineBreakMode.Clip)
				{
					availableHeight = Math.Min(lineHeight, heightConstraint);
				}
 
				var availableSize = new CGSize(widthConstraint, availableHeight);
 
				var boundingRect = title.GetBoundingRect(
					availableSize,
					NSStringDrawingOptions.UsesLineFragmentOrigin | NSStringDrawingOptions.UsesFontLeading,
					null);
 
				// The width is more usually more accurate when using the device metrics with truncatated text
				var boundingRectWithDeviceMetrics = title.GetBoundingRect(
					availableSize,
					NSStringDrawingOptions.UsesLineFragmentOrigin | NSStringDrawingOptions.UsesFontLeading | NSStringDrawingOptions.UsesDeviceMetrics,
					null);
 
				return new CGRect(boundingRectWithDeviceMetrics.Location,
					new CGSize(Math.Ceiling(Math.Max(boundingRect.Width, boundingRectWithDeviceMetrics.Width)),
						Math.Ceiling(Math.Min(availableHeight, boundingRect.Height))));
			}
 
			return CGRect.Empty;
		}
 
		public static void UpdatePadding(this UIButton platformButton, Button button)
		{
			var padding = button.Padding;
			if (padding.IsNaN)
				padding = ButtonHandler.DefaultPadding;
 
			platformButton.UpdatePadding(padding);
		}
 
		internal static void UpdateContentEdgeInsets(this UIButton platformButton, Button button, Thickness thickness)
		{
			platformButton.UpdateContentEdgeInsets(thickness);
		}
 
		public static void UpdateContentLayout(this UIButton platformButton, Button button)
		{
			(button as IView)?.InvalidateMeasure();
		}
 
		public static void UpdateText(this UIButton platformButton, Button button)
		{
			var text = TextTransformUtilites.GetTransformedText(button.Text, button.TextTransform);
			platformButton.SetTitle(text, UIControlState.Normal);
 
			// The TitleLabel retains its previous text value even after a new value is assigned. As a result, the label does not display the updated text and reverts to the old value when the button is re-measured
			if (string.IsNullOrEmpty(button.Text))
			{
				platformButton.TitleLabel.Text = string.Empty;
			}
 
			// Content layout depends on whether or not the text is empty; changing the text means
			// we may need to update the content layout
			platformButton.UpdateContentLayout(button);
		}
 
		public static void UpdateLineBreakMode(this UIButton nativeButton, Button button)
		{
			nativeButton.TitleLabel.LineBreakMode = button.LineBreakMode switch
			{
				LineBreakMode.NoWrap => UILineBreakMode.Clip,
				LineBreakMode.WordWrap => UILineBreakMode.WordWrap,
				LineBreakMode.CharacterWrap => UILineBreakMode.CharacterWrap,
				LineBreakMode.HeadTruncation => UILineBreakMode.HeadTruncation,
				LineBreakMode.TailTruncation => UILineBreakMode.TailTruncation,
				LineBreakMode.MiddleTruncation => UILineBreakMode.MiddleTruncation,
				_ => throw new ArgumentOutOfRangeException()
			};
		}
	}
}