1

I am creating a vegetation monitoring application that I have been developing in MAUI for the firs time. In Xamarin forms, I did not encounter this problem. I have a data entry form as can be seen below:

The data entry form

When I the proceed to enter the data, if the entry is located well above the soft keyboard, it works without any issues. I can add the data and manually close the keyboard.

However, when I have an entry below where the keyboard is shown, and the modal page moves up to show the entry, I get a weird jumping animation of the page every time I type a letter, and when I manually close the keyboard, it doesn't refresh the view, but the view stay where it was moved to, as can be seen in the pictures below:

Entry at the bottom of page

Keyboard moves Modal page up

Modal page not moving down after keyboard was closed.

I tried adding a workaround NuGet package called PureWeen.Maui.FixesAndWorkarounds. That didn't work. I tried the Maui Community Toolkit Keyboard extensions, that didn't work. I tried adding the following lines at the top of the xaml for this page, but it didn't work:

xmlns:android="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;assembly=Microsoft.Maui.Controls"
             android:Application.WindowSoftInputModeAdjust="Resize"

My xaml looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ElephantMonitoring.DistanceSampling.Views.DSRecordingAddEditView"
             xmlns:helpers ="clr-namespace:ElephantMonitoring.HelperClasses"
             xmlns:valueConverters = "clr-namespace:ElephantMonitoring.Value_Converters"
             Disappearing="ContentPage_Disappearing"
             xmlns:android="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;assembly=Microsoft.Maui.Controls"
             android:Application.WindowSoftInputModeAdjust="Resize">

    <!--VALUE CONVERTER: ERROR MESSAGE TO BOOL-->
    <ContentPage.Resources>
        <valueConverters:ErrorTextToBoolConverter x:Key="errorToBool"/>
        <valueConverters:GPSAccuracyIndicatorValueConverter x:Key="gpsAccuracy"/>
    </ContentPage.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="60"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="10"/>
        </Grid.RowDefinitions>

        <!--HEADER-->
        <Frame Style="{StaticResource HeaderCard}" Grid.Row="0">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="60"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="60"/>
                </Grid.ColumnDefinitions>

                <Button Grid.Column="0" HeightRequest="50" WidthRequest="50" BackgroundColor="DarkGreen" Command="{Binding BackCommand}"
                        HorizontalOptions="Start">
                    <Button.ImageSource>
                        <FontImageSource Glyph="{x:Static helpers:FASolid.ArrowLeft}" FontFamily="FASolid" Size="30"/>
                    </Button.ImageSource>
                </Button>

                <Label Grid.Column="1" Text="{Binding Title, FallbackValue=PAGE_TITLE}" Style="{StaticResource HeaderLabel}"
                           HorizontalOptions="Center"/>

                <Button Grid.Column="2" HeightRequest="50" WidthRequest="50" BackgroundColor="DarkGreen" Command="{Binding AcceptCommand}"
                        HorizontalOptions="End">
                    <Button.ImageSource>
                        <FontImageSource Glyph="{x:Static helpers:FASolid.FloppyDisk}" FontFamily="FASolid" Size="30"/>
                    </Button.ImageSource>
                </Button>
            </Grid>
        </Frame>

        <!--DATA FIELDS-->
        <ScrollView Grid.Row="1" Margin="0,20,0,0">
            <VerticalStackLayout>
                <!--Perpendicuar distance - ENTRY-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Perpendicular distance from transect (*)" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.PerpendicularDistanceError, FallbackValue=False, Converter={StaticResource errorToBool}}"
                           Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.PerpendicularDistanceError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Entry Style="{StaticResource EntryBase}" Text="{Binding RecordingModel.PerpendicularDistance}"
                               Keyboard="Numeric"/>
                    </VerticalStackLayout>
                </Frame>

                <!--Plant Location - GPS LOCATION GROUP-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Loading indicator that will be shown when the GPS is trying to get a location-->
                        <HorizontalStackLayout HorizontalOptions="Center" IsVisible="{Binding ShowGPSLoading}">
                            <ActivityIndicator Color="DarkGreen" IsRunning="True" IsVisible="True" HeightRequest="40"/>
                            <Label Text="Getting device location..." Style="{StaticResource InformationLabelHeader}"
                                   VerticalTextAlignment="Center"/>
                        </HorizontalStackLayout>
                        
                        <!--The fields-->
                        <Grid Margin="0,10,0,0">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="60"/>
                            </Grid.ColumnDefinitions>

                            <VerticalStackLayout Grid.Column="0">
                                <!--Start Location S - ENTRY-->
                                <VerticalStackLayout>
                                    <!--Caption-->
                                    <Label Text="Plant Location South (decimal degrees) (*)" Style="{StaticResource EntryCaptionLabel}"/>
                                    <!--Error Box-->
                                    <Frame IsVisible="{Binding RecordingModel.PlantLocationSError, FallbackValue=False, Converter={StaticResource errorToBool}}"
                           Style="{StaticResource ErrorCard}">
                                        <Label Text="{Binding RecordingModel.PlantLocationSError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                                    </Frame>

                                    <Entry Style="{StaticResource EntryBase}" Text="{Binding RecordingModel.PlantLocationS}"
                                           Keyboard="Numeric"/>
                                </VerticalStackLayout>

                                <!--End Location E - ENTRY-->
                                <VerticalStackLayout>
                                    <!--Caption-->
                                    <Label Text="Plant Location East (decimal degrees) (*)" Style="{StaticResource EntryCaptionLabel}"/>
                                    <!--Error Box-->
                                    <Frame IsVisible="{Binding RecordingModel.PlantLocationEError, FallbackValue=False, Converter={StaticResource errorToBool}}"
                           Style="{StaticResource ErrorCard}">
                                        <Label Text="{Binding RecordingModel.PlantLocationEError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                                    </Frame>

                                    <Entry Style="{StaticResource EntryBase}" Text="{Binding RecordingModel.PlantLocationE}" Keyboard="Numeric"/>
                                </VerticalStackLayout>

                                <!--Accuracy Indicator - LABEL-->
                                <HorizontalStackLayout>
                                    <!--Caption-->
                                    <Label Text="Accuracy: " Style="{StaticResource EntryCaptionLabel}"/>
                                    <Label Text="{Binding RecordingModel.LocationAccuracy, FallbackValue=NA}" Style="{StaticResource InformationLabelHeader}"/>
                                    <Label Text="m" Style="{StaticResource InformationLabelHeader}"/>
                                    <Label Text="{Binding RecordingModel.LocationAccuracy, FallbackValue=NONE, Converter={StaticResource gpsAccuracy}}" 
                                           Style="{StaticResource InformationLabelHeader}"/>
                                </HorizontalStackLayout>
                            </VerticalStackLayout>

                            <Button HeightRequest="50" WidthRequest="50" BackgroundColor="DarkGreen" Grid.Column="1" 
                        Command="{Binding GetGPSLocationCommand}"
                    Margin="0,0,5,0">
                                <Button.ImageSource>
                                    <FontImageSource Glyph="{x:Static helpers:FASolid.LocationDot}" FontFamily="FASolid" Size="30"/>
                                </Button.ImageSource>
                            </Button>

                        </Grid>
                    </VerticalStackLayout>
                </Frame>

                <!--Trunk Circumference - ENTRY-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Trunk circumference (m) (*)" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.TrunkCircumferenceError, FallbackValue=False, Converter={StaticResource errorToBool}}"
                           Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.TrunkCircumferenceError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Entry Style="{StaticResource EntryBase}" Text="{Binding RecordingModel.TrunkCircumference}"
                               Keyboard="Numeric"/>
                    </VerticalStackLayout>
                </Frame>

                <!--Damaged trunk circumference - ENTRY-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Damaged trunk circumference (m) (*)" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.DamagedTrunkCircError, FallbackValue=False, Converter={StaticResource errorToBool}}"
                           Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.DamagedTrunkCircError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Entry Style="{StaticResource EntryBase}" Text="{Binding RecordingModel.DamagedTrunkCirc}"
                               IsReadOnly="True">
                            <Entry.GestureRecognizers>
                                <TapGestureRecognizer Command="{Binding AddDamagedTrunkCommand}"/>
                            </Entry.GestureRecognizers>
                        </Entry>
                    </VerticalStackLayout>
                </Frame>

                <!--Plant height - ENTRY-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Plant Height (m) (*)" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.PlantHeightError, FallbackValue=False, Converter={StaticResource errorToBool}}"
                           Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.PlantHeightError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Entry Style="{StaticResource EntryBase}" Text="{Binding RecordingModel.PlantHeight}"
                               Keyboard="Numeric"/>
                    </VerticalStackLayout>
                </Frame>

                <!--Crown Diameter - ENTRY-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Crown Diameter (m) (*)" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.CrownDiameterError, FallbackValue=False, Converter={StaticResource errorToBool}}"
                           Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.CrownDiameterError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Entry Style="{StaticResource EntryBase}" Text="{Binding RecordingModel.CrownDiameter}"
                               IsReadOnly="True">
                            <Entry.GestureRecognizers>
                                <TapGestureRecognizer Command="{Binding AddCrownDiameterCommand}"/>
                            </Entry.GestureRecognizers>
                        </Entry>
                    </VerticalStackLayout>
                </Frame>

                <!--First Leaves height - ENTRY-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="First Leaves Height (m) (*)" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.FirstLeavesHeightError, FallbackValue=False, Converter={StaticResource errorToBool}}"
                           Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.FirstLeavesHeightError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Entry Style="{StaticResource EntryBase}" Text="{Binding RecordingModel.FirstLeavesHeight}"
                               Keyboard="Numeric"/>
                    </VerticalStackLayout>
                </Frame>

                <!--Toppled - PICKER-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Is the tree Toppled?" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.ToppledError, FallbackValue=False, Converter={StaticResource errorToBool}}" 
                               Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.ToppledError}" 
                                Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Picker 
                        SelectedItem="{Binding RecordingModel.Toppled}"
                        TitleColor="{StaticResource PrimaryMid}"
                        Style="{StaticResource PickerBase}"
                        BackgroundColor="White">
                            <Picker.Items>
                                <x:String>YES</x:String>
                                <x:String>NO</x:String>
                            </Picker.Items>
                        </Picker>
                    </VerticalStackLayout>
                </Frame>

                <!--Top Kill - PICKER-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Is Top Kill evident?" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.TopKillError, FallbackValue=False, Converter={StaticResource errorToBool}}" 
                               Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.TopKillError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Picker 
                        SelectedItem="{Binding RecordingModel.TopKill}"
                        TitleColor="{StaticResource PrimaryMid}"
                        Style="{StaticResource PickerBase}"
                        BackgroundColor="White">
                            <Picker.Items>
                                <x:String>YES</x:String>
                                <x:String>NO</x:String>
                            </Picker.Items>
                        </Picker>
                    </VerticalStackLayout>
                </Frame>
                
                <!--Coppiced - PICKER-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Is the tree Coppicing?" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.CoppicedError, FallbackValue=False, Converter={StaticResource errorToBool}}" 
                               Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.CoppicedError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Picker 
                        SelectedItem="{Binding RecordingModel.Coppiced}"
                        TitleColor="{StaticResource PrimaryMid}"
                        Style="{StaticResource PickerBase}"
                        BackgroundColor="White">
                            <Picker.Items>
                                <x:String>YES</x:String>
                                <x:String>NO</x:String>
                            </Picker.Items>
                        </Picker>
                    </VerticalStackLayout>
                </Frame>

                <!--Utilization Scale - PICKER-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Utilization on scale" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.UtilizationScaleError, FallbackValue=False, Converter={StaticResource errorToBool}}" 
                               Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.UtilizationScaleError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Picker 
                        SelectedItem="{Binding RecordingModel.UtilizationScale}"
                        TitleColor="{StaticResource PrimaryMid}"
                        Style="{StaticResource PickerBase}"
                        BackgroundColor="White">
                            <Picker.Items>
                                <x:String>1</x:String>
                                <x:String>2</x:String>
                                <x:String>3</x:String>
                                <x:String>4</x:String>
                            </Picker.Items>
                        </Picker>
                    </VerticalStackLayout>
                </Frame>

                <!--Fire Damaged - PICKER-->
                <Frame Padding="2" Margin="2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Fire damage evident?" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="{Binding RecordingModel.FireDamageError, FallbackValue=False, Converter={StaticResource errorToBool}}" 
                               Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding RecordingModel.FireDamageError}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Picker 
                        SelectedItem="{Binding RecordingModel.FireDamage}"
                        TitleColor="{StaticResource PrimaryMid}"
                        Style="{StaticResource PickerBase}"
                        BackgroundColor="White">
                            <Picker.Items>
                                <x:String>YES</x:String>
                                <x:String>NO</x:String>
                            </Picker.Items>
                        </Picker>
                    </VerticalStackLayout>
                </Frame>

                <!--Notes - ENTRY-->
                <Frame Padding="2" Margin="2, 2, 2, 2" BorderColor="Black">
                    <VerticalStackLayout>
                        <!--Caption-->
                        <Label Text="Notes" Style="{StaticResource EntryCaptionLabel}"/>
                        <!--Error Box-->
                        <Frame IsVisible="False"
                           Style="{StaticResource ErrorCard}">
                            <Label Text="{Binding SomeBinding}" 
                       Style="{StaticResource ErrorCardLabel}"/>
                        </Frame>

                        <Editor Style="{StaticResource EditorBase}" Text="{Binding PlantSpeciesItem.ScientificName}"
                                Placeholder="Enter notes here" x:Name="notesEditor" Focused="notesEditor_Focused"/>
                    </VerticalStackLayout>
                </Frame>

            </VerticalStackLayout>
        </ScrollView>

    </Grid>
</ContentPage>

If anyone have found a solution for this issue, pleas let me know.

  • Re page position not restoring when keyboard closes, I wonder if there is some timing issue/race condition. If you were testing on an emulator, try on an actual device. Same? – ToolmakerSteve May 26 '23 at 20:38
  • Based on your code, I created a demo, but I couldn't reproduce this problem. Could you please post the steps of reproducing this problem and more code snippets? – Jessie Zhang -MSFT May 29 '23 at 06:38
  • @ToolmakerSteve I have this issue more so on an actual device. It becomes quite problematic once you are out in the field and cant save the data that you have just collected, because the screen is shifted up. – Fanie Lombard Jun 02 '23 at 05:25
  • @Jessie Zhang -MSFT yes, if you scroll down the page to the Notes entry at the bottom, and type something, and then manually close the keyboard, you should be able to reproduce the problem. – Fanie Lombard Jun 02 '23 at 05:28
  • @ToolmakerSteve what do you mean by "timing issue/race condition"? – Fanie Lombard Jun 02 '23 at 05:29
  • I was hoping it was only on emulators, which are relatively slow. Disregard that comment, since it is worse on actual devices. Start with a new Maui project, and add just enough code to make the problem happen. Put that into a public github repository, so it can be tested by others. If no one here sees a fix, then create a new issue at `github maui issues`, with the description you have here, and a link to that repo. – ToolmakerSteve Jun 02 '23 at 06:54

0 Answers0