Code Monkey home page Code Monkey logo

onscreensizemarkup's Introduction

This project is based on a post I wrote a long time ago for my blog.

.NET MAUI library Moved to another Repository

The .NET MAUI library has been separated into its own repository. If you are looking for the library for .NET MAUI, please go here.

If you are looking for Xamarin library, keep on this current repo.

OnScreenSizeMarkup: A XAML markup extension

OnScreenSizeMarkup is XAML markup extension for controlliing UI elements depending on the device screen size category.

Where can I use it?

OnScreenSizeMarkup is currently compatible with:

  • Xamarin.Forms NuGet Stats
  • MAUI NuGet Stats

What does that mean?

It works exactly as other markup extensions such as OnPlatform or OnIdiom, allowing you to control UI views accordding to the screen sizes category a device fits in.

Screen Categories

The screen sizes are grouped into six categories:

  • ExtraSmall - Tiny devices as an Apple Watch.
  • Small - Small devices, such as a Google Pixel 5.
  • Medium - Medium devices such as IPhone 12.
  • Large - Large devices such as an IPhone 13 Pro Max.
  • ExtraLarge - Extra large devices such as tablets.

For each category we define specific value when using the markup on XAML.

There is also a DefaultSize category that should be used when only few category options are used in the markup properties, in this case a value defined in the DefaultSize property will be applied for the missing options.

Getting Started

Using the markup is very straight foward, you can apply it to most UI View elements, such as Labels, Grids, Buttons, ImageButtons, and etc.

XAML sample code:

<ContentPage  
            xmlns:markups="clr-namespace:OnScreenSizeMarkup.Forms;assembly=OnScreenSizeMarkup.Forms">
    <ContentPage.Content>

         <Grid RowDefinitions="{markups:OnScreenSize Large='200, *, 200', 
                                                     ExtraLarge='300, *, 300',
                                                     DefaultSize='100, *, 100'}">
            <Label 
                    Padding="{markups:OnScreenSize 
                            Medium='15, 15, 0, 0', 
                            Large='20, 20, 0, 0', 
                            DefaultSize='10, 10, 0, 0'}"
                    Text="Hello" TextColor="White" />
         </Grid>
    </ContentPage.Content>
</ContentPage>  

In the above example we are defining a Grid with 3 rows, with different values for RowDefinitions depending on the category of the screen fits in.

We are also defining a Label and setting it's padding to vary also depending on the screen's category.

Note You can use the markup for most View's properties, not limited to RowDefinitions or Paddings.

C# sample code:

You can also use a similar mechanism from code-behind:

  using OnScreenSizeMarkup.Forms;
  
  public MainPage()
  {
    Grid grid = new Grid
    {
        RowDefinitions =
        {
            //first row
            Markup.OnScreenSize(defaultSize: new RowDefinition { Height = new GridLength(100) },
                                large: new RowDefinition { Height = new GridLength(200) },
                                extraLarge: new RowDefinition { Height = new GridLength(300) }),
            //second row
            new RowDefinition(),

            //third row
            Markup.OnScreenSize(defaultSize: new RowDefinition { Height = new GridLength(100) },
                                large: new RowDefinition { Height = new GridLength(200) },
                                extraLarge: new RowDefinition { Height = new GridLength(300) }),
        }
    };
    grid.Children.Add(new Label
    {
        Text = "Hello",
        Padding = Markup.OnScreenSize(defaultSize: new Thickness(10, 10, 0, 0),
                                      medium: new Thickness(15, 15, 0, 0),
                                      large: new Thickness(20, 20, 0, 0))
    });
    Content = grid;      
  }
};                                                

The magic

Most of the time you don't need do implement anything in order to categorize the screen sizes, as the markup is backed by a DefaultFallbackHandler instance class, but there may be times you may need to override/customize the screen size categorization with your own rules, thats when the Category Fallback Handlers comes to the rescue.

Category fallback handlers

A category fallback handler is a class that implements ICategoryFallbackHandler interface composed of two methods which returns a ScreenCategories Enum (ExtraSmall, Small, Medium, Large, ExtraLarge) based on device's model, or device's physical screen size (Width/Height):

  • TryGetCategoryByDeviceModel - Returns a screen category by the device-model.
  • TryGetCategoryByPhysicalSize - Returns a screen category by screen measures (width/height).

Due to some incorrect screen size info returned by some iOS simulator devices during development and tests, it was not possible to depend only on the screen size measures for categorize screens, and in order to fix that, it was intruduced TryGetCategoryByDeviceModel method, which based on the device's model we can confidently categorize a device.

The Markup first attempts to execute TryGetCategoryByDeviceModel by passing the device's model, and in case it returns false, it tries to execute TryGetCategoryByPhysicalSize by passing a device screen size (Width/Height).

Note: For getting either the device model or the device size I use Xamarin.Essentials.

If you need to implement your own handler you MUST set its instance during the app initialization as follow:

    OnScreenSizeMarkup.Core.Manager.Current.Handler = new MyNewFallBackHandlerClass();

Default Category fallback handler

The markup comes with a DefaultFallbackHandler class which maps most iOS devices models and screens sizes (for android devices).

Here is dictionary's items of the device models and it's categories mappings we currently have:

            { "iPhone1_1", ScreenCategories.Small },
            { "iPhone1,2",  ScreenCategories.Small },
            { "iPhone2,1", ScreenCategories.Small },
            { "iPhone3,1", ScreenCategories.Small  },
            { "iPhone3,2", ScreenCategories.Small },
            { "iPhone3,3", ScreenCategories.Small },
            { "iPhone4,1",  ScreenCategories.Small },
            { "iPhone5,1",  ScreenCategories.Small },
            { "iPhone5,2", ScreenCategories.Small },
            { "iPhone5,3",  ScreenCategories.Small  },
            { "iPhone5,4", ScreenCategories.Small  },
            { "iPhone6,1",  ScreenCategories.Small},
            { "iPhone6,2",  ScreenCategories.Small  },
            { "iPhone7,1", ScreenCategories.Large },
            { "iPhone7,2", ScreenCategories.Medium },
            { "iPhone8,1",  ScreenCategories.Medium  },
            { "iPhone8,2", ScreenCategories.Large  },
            { "iPhone8,4", ScreenCategories.Medium  },
            { "iPhone9,1", ScreenCategories.Medium },
            { "iPhone9,2",  ScreenCategories.Large },
            { "iPhone9,3",  ScreenCategories.Medium },
            { "iPhone9,4", ScreenCategories.Large },
            { "iPhone10,1", ScreenCategories.Medium },
            { "iPhone10,2", ScreenCategories.Large },
            { "iPhone10,3", ScreenCategories.Medium },
            { "iPhone10,4", ScreenCategories.Medium },
            { "iPhone10,5", ScreenCategories.Large },
            { "iPhone10,6", ScreenCategories.Medium },
            { "iPhone11,2", ScreenCategories.Medium },
            { "iPhone11,4", ScreenCategories.Large },
            { "iPhone11,6", ScreenCategories.Large },
            { "iPhone11,8", ScreenCategories.Medium },
            { "iPhone12,1", ScreenCategories.Medium },
            { "iPhone12,3",  ScreenCategories.Medium },
            { "iPhone12,5", ScreenCategories.Large },
            { "iPhone12,8", ScreenCategories.Medium },
            { "iPhone13,1", ScreenCategories.Medium },
            { "iPhone13,2", ScreenCategories.Medium },
            { "iPhone13,3", ScreenCategories.Medium }, 
            { "iPhone13,4", ScreenCategories.Large },
            { "iPhone14,2", ScreenCategories.Medium },
            { "iPhone14,3", ScreenCategories.Large },
            { "iPhone14,4", ScreenCategories.Medium },
            { "iPhone14,5", ScreenCategories.Medium },
            { "Pad12,2",    ScreenCategories.ExtraLarge },
            { "iPad_9th_Gen", ScreenCategories.ExtraLarge },
            { "iPad1,2",  ScreenCategories.ExtraLarge },
            { "iPad2,1", ScreenCategories.ExtraLarge },
            { "iPad2,2", ScreenCategories.ExtraLarge },
            { "iPad2,3",  ScreenCategories.ExtraLarge },
            { "iPad2,4", ScreenCategories.ExtraLarge },
            { "iPad2,5", ScreenCategories.ExtraLarge },
            { "iPad2,6", ScreenCategories.ExtraLarge },
            { "iPad2,7", ScreenCategories.ExtraLarge },
            { "iPad3,1", ScreenCategories.ExtraLarge },
            { "iPad3,2", ScreenCategories.ExtraLarge },
            { "iPad3,3", ScreenCategories.ExtraLarge },
            { "iPad3,4", ScreenCategories.ExtraLarge },
            { "iPad3,5", ScreenCategories.ExtraLarge },
            { "iPad3,6", ScreenCategories.ExtraLarge },
            { "iPad4,1", ScreenCategories.ExtraLarge },
            { "iPad4,2", ScreenCategories.ExtraLarge },
            { "iPad4,3", ScreenCategories.ExtraLarge },
            { "iPad4,4", ScreenCategories.ExtraLarge },
            { "iPad4,5", ScreenCategories.ExtraLarge },
            { "iPad4,6", ScreenCategories.ExtraLarge },
            { "iPad4,7", ScreenCategories.ExtraLarge },
            { "iPad4,8", ScreenCategories.ExtraLarge },
            { "iPad4,9", ScreenCategories.ExtraLarge },
            { "iPad5,1", ScreenCategories.ExtraLarge },
            { "iPad5,2", ScreenCategories.ExtraLarge },
            { "iPad5,3", ScreenCategories.ExtraLarge },
            { "iPad5,4", ScreenCategories.ExtraLarge },
            { "iPad6,4", ScreenCategories.ExtraLarge },
            { "iPad6,7",ScreenCategories.ExtraLarge },
            { "iPad6,8", ScreenCategories.ExtraLarge },
            { "iPad6,11", ScreenCategories.ExtraLarge }, 
            { "iPad6,12", ScreenCategories.ExtraLarge },
            { "iPad7,1", ScreenCategories.ExtraLarge },
            { "iPad7,2", ScreenCategories.ExtraLarge },
            { "iPad7,3", ScreenCategories.ExtraLarge },   
            { "iPad7,4", ScreenCategories.ExtraLarge },   
            { "iPad7,5", ScreenCategories.ExtraLarge },   
            { "iPad7,6", ScreenCategories.ExtraLarge },   
            { "iPad7,11", ScreenCategories.ExtraLarge },   
            { "iPad7,12", ScreenCategories.ExtraLarge },
            { "iPad8,1", ScreenCategories.ExtraLarge },   
            { "iPad8,2", ScreenCategories.ExtraLarge },   
            { "iPad8,3", ScreenCategories.ExtraLarge },   
            { "iPad8,4", ScreenCategories.ExtraLarge},   
            { "iPad8,5", ScreenCategories.ExtraLarge },   
            { "iPad8,6", ScreenCategories.ExtraLarge },   
            { "iPad8,7", ScreenCategories.ExtraLarge },   
            { "iPad8,8", ScreenCategories.ExtraLarge },   
            { "iPad8,9", ScreenCategories.ExtraLarge },   
            { "iPad8,10", ScreenCategories.ExtraLarge },   
            { "iPad8,11", ScreenCategories.ExtraLarge },   
            { "iPad8,12", ScreenCategories.ExtraLarge },   
            { "iPad11,1", ScreenCategories.ExtraLarge },   
            { "iPad11,2", ScreenCategories.ExtraLarge },   
            { "iPad11,3", ScreenCategories.ExtraLarge },   
            { "iPad11,4", ScreenCategories.ExtraLarge },   
            { "iPad11,6", ScreenCategories.ExtraLarge },   
            { "iPad11,7", ScreenCategories.ExtraLarge },   
            { "iPad12,1", ScreenCategories.ExtraLarge },   
            { "iPad12,2", ScreenCategories.ExtraLarge },   
            { "iPad13,1", ScreenCategories.ExtraLarge },   
            { "iPad13,2", ScreenCategories.ExtraLarge },   
            { "iPad13,4", ScreenCategories.ExtraLarge },   
            { "iPad13,5", ScreenCategories.ExtraLarge },   
            { "iPad13,6", ScreenCategories.ExtraLarge },   
            { "iPad13,7", ScreenCategories.ExtraLarge },   
            { "iPad13,8", ScreenCategories.ExtraLarge },   
            { "iPad13,9", ScreenCategories.ExtraLarge },   
            { "iPad13,10", ScreenCategories.ExtraLarge },   
            { "iPad13,11", ScreenCategories.ExtraLarge },
            { "iPad14,1", ScreenCategories.ExtraLarge },   
            { "iPad14,2", ScreenCategories.ExtraLarge },

Also, for other screens sizes such as Android devices, here is the mapping based on the screen size:

  private static ScreenSizeInfo[] screenSizeCategories = new ScreenSizeInfo[]
    {
        new ScreenSizeInfo(480,800, ScreenCategories.ExtraSmall), 
        new ScreenSizeInfo(720,1280, ScreenCategories.Small), 
        new ScreenSizeInfo(750,1334, ScreenCategories.Medium), 
        new ScreenSizeInfo(1125,2436, ScreenCategories.Medium),
        new ScreenSizeInfo(828,1792, ScreenCategories.Medium),
        new ScreenSizeInfo(1080,1920, ScreenCategories.Large),
        new ScreenSizeInfo(1242,2688, ScreenCategories.Large),
        new ScreenSizeInfo(1284,2778, ScreenCategories.Large),
        new ScreenSizeInfo(1440,3200, ScreenCategories.ExtraLarge),
        new ScreenSizeInfo(2732,2048, ScreenCategories.ExtraLarge),
    };

Points of attention

There are some custom Xamarin.Forms types such as Xamarin.Forms.Color in which it's value conversion are not currently supported which leads the markup to handle incorrectly these types.

I intent to implement these value conversions along the time.

onscreensizemarkup's People

Contributors

carolzbnbr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

onscreensizemarkup's Issues

using OnPlatform with maui:OnScreenSize what is the way to do it

Hi
Not sure if this is a bug or not supported or cannot be done or I am doing all wrong :)

In my app I have a FontStyles.xaml resourcedictionary and I tried to do

		<OnPlatform x:Key="DefaultFontIconSize" x:TypeArguments="x:Double">
			<OnPlatform.Platforms>
				<On Platform="Android">
					<maui:OnScreenSize
						Default="25"
						ExtraSmall="16"
						Large="40"
						Medium="30"
						Small="20" />
				</On>
				<On Platform="iOS">
					<maui:OnScreenSize
						Default="25"
						ExtraSmall="16"
						Large="40"
						Medium="30"
						Small="20" />
				</On>
			</OnPlatform.Platforms>
		</OnPlatform>

But I get this

An error occurred: 'Não foi posivel determinar a propriedade para fornecer o valor.'. Callstack: ' at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.ProvideValue(Object& value, ElementNode node, Object source, XmlName propertyName)
at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.Visit(ElementNode node, INode parentNode)
etc....

My question is the above possible?
thanks

OnScreensize returns extralarge on android emulator

Hi
I really like this nuget package and this is what we really need , I have a problem in a maui app I am building
I have 2 emulators set up as follows - notice size see pic below -
Despite what I do to set the font size it always detected it as extra large. Am I missing something?

<Label Grid.Row="2" FontSize="{maui:OnScreenSize ExtraLarge=80, Large=28, Default=18, Medium=12, Small=8, ExtraSmall=6}" Text="Sample Text using OnScreenSize" />

image

  1. Just run your solution
  2. removed everything
  3. updated to .net 7
  4. Referenced your 2.1.2 nuget

image

Any suggestions?

Custom Category fallback handlers

I'm not sure if it a bug but I created a custom handler and I added the line
OnScreenSizeMarkup.Core.Manager.Current.Handler = new MyNewFallBackHandlerClass();
to my App.cs file but my code never gets called and the category is still the one created by the default handler.
Do I need to add it to some where else or call something to do the categorization again with my new handler
If i manually call the TryGetCategoryByPhysicalSize function after i set the new instance then it call my code but the category is never updated.

Xamarin Fail to classify this device screen size based on Width/Height

Hi

I dont think this is a bug but more how we should handle on our side and get some advice from you .
We noticed that we get lot of crashes with this info in AppCenter (this is a xamarin app)

System.TypeInitializationException: Fail to classify this device screen size based on Width/Height (1440x3216). Maybe you need to implement your own version of the handler.

I have been through the code and we always have a defaultsize.

We dont want the app to crash/hang etc.. and seems to happen with Oppo phones and we dont have one to test on..

do you need to thrown an Error in your ScreenCategoryextension.GetHandlerCategory? Surely could return a default instead of the exception .. what do you think?

or in Xamarin how do I add another an additinal screensize

    private static ScreenSizeInfo[] screenSizeCategories = new ScreenSizeInfo[]
     {                     
        new ScreenSizeInfo(1440,3216, ScreenCategories.ExtraLarge),
    };

Any advice welcome!

thanks again for this control.

Using StaticResource causes an invalid exception

Hi
This is a brilliant nuget!!!
I am sure its working as designed but using on existing app when lots of values that are defined int the App.Xaml.

if you have something like this in your App.Xaml

  <OnPlatform
          x:Key="LargeSize"
          x:TypeArguments="x:Double"
          Android="18"
          iOS="20" />

and then you try to do this

FontSize="{markups:OnScreenSize 
                        Small='{StaticResource SmallSize}',
                        Medium='{StaticResource DefaultSize}', 
                        Large='{StaticResource LargeSize}'}" 

Than it will crash with a cast exception.

Is there a way that it could possibly handle a value defined in the app.xaml as a static resource?

Many thanks

Platform implementation not found

Describe the bug
i get error 'Platform implementation not found' if i debug this on windows machine

image

vs 2022 win 10

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

Suggestion - Maui Removal of throwing exception and return an enum or a result

Given the issue we faced in the Xamarin version , would you consider removing throwing exceptions when ScreenCategories.Not set and returning either a default value or just not set in the Maui version

Also removing throwing exceptions whenever possible as those will just crash the app or even prevent the app from loading, your control is used in many styles that run at the very beginning of the app and an error will prevent the app from running.

As you know very well google api its not correct all the time and new devices like oppo have difference measures and we cannot possibly prevent all the issues and add mapping to all possible sizes, so in my view returning a default value is better than the app crashing,
thanks again

Use of Static or Dynamic Resource causes exception

When a Static or Dynamic Resource is used for a size parameter, an "Exception has been thrown by the target of an invocation" occurs.

To Reproduce

Given the following valid/working example of using OnScreenSizeMarkup:

<Grid Padding="{markups:OnScreenSize DefaultSize='5,20,15,5'}">
</Grid>

Converting it into a Static Resource produces the exception:

<ContentPage.Resources>
    <Thickness x:Key="PaddingThickness">15,20,15,5</Thickness>
</ContentPage.Resources>

<Grid Padding="{markups:OnScreenSize DefaultSize={StaticResource PaddingThickness}}">
</Grid>

Definining as a Dynamic Resource also yields the same issue:


<ResourceDictionary ...>
    <Thickness x:Key="PaddingThickness">15,20,15,5</Thickness>
</ResourceDictionary>

<Grid Padding="{markups:OnScreenSize DefaultSize={DynamicResource PaddingThickness}}">
</Grid>

Single quotes around the size doesn't seem to matter:

<Grid Padding="{markups:OnScreenSize DefaultSize='{StaticResource PaddingThickness}'}">
</Grid>

Expected behavior
The Thickness resource should be evaluated and used just as if it were in the Markup directly.

Environment:

  • iOS 12+
  • Xamarin.Forms 5.0.0.2545

Additional context
Not sure if I'm missing something or just not doing it right, but I've tried a bunch of different variations such as defining strings and doubles rather than Thickness. Can't seem to get anything to work.

This may be related to issue #8, which was closed by the contributor. In that issue, a sample was given using a StaticResource, but even that example yields the same error for me.

I'm currently unable to migrate to Maui due to dependencies, so I'm unable to see if the issue exists there unfortunately.

Edit: This is a little unrelated and doesn't give me exactly what I want, but just thought I'd mention that if I use a Style instead, it (unsurprisingly) works as a Dynamic Resource:

<Style TargetType="Grid" Class="PaddingThickness">
    <Setter 
        Property="Padding" 
        Value="{markups:OnScreenSize DefaultSize='15,20,15,5'}" />
</Style>

<Grid StyleClass="PaddingThickness">
</Grid>

Edit: I should mention, I don't really need this to be resolved. Just discovered it and figured I would share it here in case anyone else had the issue.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.