July 2025
I have written this post last year. I was pleasantly surpised to find out it has been mentioned on some other blogs, like Telerik’s. Recently, I read Matt Goldman’s post in which he asked for contributions for the .NET MAUI UI July 2025 edition. Besides this post, I have two more post, which will be added tomorrow and the day after tomorrow.
Looking at the previous editions of MAUI UI July, I am sure you might find useful and fun articles, all around MAUI UI. I strongly suggest to have a look.
This post describes briefly how I solved the issues around screen sizes on mobile devices in MAUI. The source code for this solution can be found on my github account: Byte217.MAUI on GitHub.
Recently, I developed an application with .NET MAUI called ‘Smart Letter Board’. For optimal functionality it was necessary to display the UI elements as big as possible. I also wanted to keep the UI elements in proportion; they should stay in the same ratio as I designed them. I realized that this could be a challenge because the dimensions of all the devices differ a lot; tablets tend to be more squarish than phones, and on laptops the app would be resizable.
I started with three possible configurations:
- numeric keys on top of the alphanumeric keys; this would fit best on squarish devices
- numerics keys on the right of the alphanumeric keys; this would fit best on rectangular devices
- numeric keys on a separate page, this would show the buttons at their largest of the three configurations
The idea was to request the device’s dimensions and then choose the best configuration. This worked really well for my Windows application. However, I quickly learned that my calculations were way off on other devices. This was caused by status and navigation bars. Like many others before me, I first tried to get the dimensions of these bars and use them in my calculations, but eventually, this didn’t work out.
After some research, I realized that all platforms and OS versions use different sizes for status bars, navigation bars and tab bars. I didn’t want to add too much platform-specific code to my application.
1. How to get the ‘right’ dimensions
Fortunately, I read a post mentioning the OnSizeAllocated() method of the ContentPage, which forms the base of the solution.
The width and the height that get passed into this method represent the drawable area of the page. This means that hen you use these dimensions, you don’t need to make up for any bars anymore. An additional benefit is that it is called with every change in the page’s size. However, be aware not to perform any performance-intensive actions in OnSizeAllocated because it is called many times when resizing. Therefore, alls to a database or web service will give you problems.
MainPage.cs
protected override void OnSizeAllocated(double width, double height) { // width and height are the dimensions of the drawable area. // This excludes all the status bars, navigation bars etc. // Only do this if the current page is being being resized AppShell appShell = Microsoft.Maui.Controls.Application.Current.MainPage as AppShell; if (appShell.CurrentPage == this) { if (BindingContext is MainViewModel viewModel) { // iOS has reserved a safeArea on the borders of the screen. // This must be taken into account when calculating the page layouts Thickness safeArea = On().SafeAreaInsets(); viewModel.CreatePageLayouts(width, height, new Models.Thickness(safeArea.Left, safeArea.Top, safeArea.Right, safeArea.Bottom)); } base.OnSizeAllocated(width, height); } }
2. How to size the UI elements according to the screen size
When I started adding buttons to the page, I looked at the Styles.xaml and noticed that buttons were displayed with a width and height of 44 pixels by default. Other elements had their own defaults. As described above, I had three configurations in mind. First, I had to calculate how much space this would take up using the default dimensions. The next step would be to calculate the multiplier; the number needed to show them on a larger scale.
For example, a multiplier with a value of 2 would show the buttons at 88px.
This calculation of the Multiplier is done in the PageLayoutFactory. In the sample code, I only calculate the value for one PageLayout. In Smart Letter Board I calculate the Multiplier for all three configuration and choose the best one for the device. I have left this out in this example, because it doesn’t add value for the understanding of the mechanism.
PageLayoutFactory.cs
public PageLayout CreateMinimalPageLayout() { // Setting the SafeArea is important, because it used to calculate the Padding PageLayout layout = new() { PageLayoutType = PageLayoutType.Minimal, SafeArea = _safeArea }; // Calculate the required minimal dimensions // Width = Padding Left | Button 1 | InnerSpacing | Button 2 | InnerSpacing | ... | Button 10 | OuterSpacing | Right Column Width | Padding Right // Height = Padding Top | Row Height | InnerSpacing | Button Height for Suggestions | InnerSpacing | Key Row 1 | InnerSpacing | ... | Key Row 4 | Padding Bottom layout.Width = layout.Padding.Left + (10 * layout.ButtonWidth) + (9 * layout.InnerSpacing) + layout.OuterSpacing + layout.RightColumnWidth + layout.Padding.Right; layout.Height = layout.Padding.Top + (5 * layout.ButtonHeight) + layout.RowHeight + (5 * layout.InnerSpacing) + layout.Padding.Bottom; // Calcute the multipliers by dividing the actual size with the minimal size double horizontalMultiplier = _width / layout.Width; double verticalMultiplier = _height / layout.Height; // Choose the smallest multiplier to ensure that all the layout elements will fit double multiplier = Math.Min(horizontalMultiplier, verticalMultiplier); layout.Multiplier = multiplier; layout.IsValid = (multiplier >= 1.0); return layout; }
To keep the XAML code clear, I created a custom button class with a Bindable Property called PageLayout. When this property is set or changed, it sets the width, height and other properties of the actual button. Without this additional Bindable Property, I would have had to set all the button properties individually in XAML.
Noticed that binding is of the type ObservablePageLayout. More about that in the next section.
Button.cs
public class Button : Microsoft.Maui.Controls.Button { public static readonly BindableProperty PageLayoutProperty = BindableProperty.Create(nameof(PageLayout), typeof(ObservablePageLayout), typeof(Button), new ObservablePageLayout(), propertyChanged: OnPageLayoutChanged); public static void OnPageLayoutChanged(BindableObject bindable, object oldValue, object newValue) { if (bindable is Button button) { if (newValue is ObservablePageLayout pageLayout) { button.MinimumWidthRequest = pageLayout.ButtonWidth; button.MinimumHeightRequest = pageLayout.ButtonHeight; button.Padding = new Thickness(pageLayout.ButtonPaddingHorizontal, pageLayout.ButtonPaddingVertical); button.FontSize = pageLayout.FontSize; button.CornerRadius = pageLayout.ButtonCornerRadius; } } } public ObservablePageLayout PageLayout { get => (ObservablePageLayout)GetValue(PageLayoutProperty); set => SetValue(PageLayoutProperty, value); } }
3. Adding an ObservablePageLayout
To make the bindable property on the button work, I would have to add the INotifyPropertyChanged interface to the PageLayout class. However, I want to keep my architecture clean. I consider observability closer to the presentation layer, and I don’t want to add observability to my models, which are also used to persist data. In the ‘Smart Letter Board’ solution, I have an extra Services project that uses the Models and EF Core to store the data in a SQLite database.
When you look at the source code, you will find an ObservableModels project. This contains all the models that need to be Observable for instant refresh in the UI.
The additional benefit of having a derived class ObservablePageLayout is that I can multiply the base values of the PageLayout with its Multiplier to give me the values that I want to use in the page.
ObservablePageLayout.cs
public class ObservablePageLayout : PageLayout, INotifyPropertyChanged { public new double OuterSpacing { get { return (double)Math.Floor(base.OuterSpacing * base.Multiplier); } set { base.OuterSpacing = value; NotifyPropertChanged(); } } // More properties }
Conclusion
Override ContentPage.OnSizeAllocated() to get the dimensions of the drawable area of your device. Once you have the dimensions of the drawable area you don’t have to take into account the dimensions of status, navigation and tab bars.
I have added the source code of this example to my github account. The README.md contains somewhat more documentation. This is only an example to show a solution for sizing in MAUI. The buttons don’t have event handlers or commands connected. In this example I have simplified the code as much as possible to focus only on the resizing.