dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.21k stars 1.75k forks source link

[.NET 8] CustomSwipeView with SwipeItemView and Label.GestureRecognizers Commands not working anymore #19630

Open MAUIoxo opened 10 months ago

MAUIoxo commented 10 months ago

Description

I use a SwipeItemView with a Grid/HorizontalStackLayout/... within a CollectionView DataTemplate . The App was used under .NET 7 and working fine. When I migrated it to .NET 8 the Label.GestureRecognizers for my Edit/Delete-Button do not work anymore and the EditFoodItemCommand/DeleteFoodItemCommand-Handlers in the Code below do not work anymore. You can click on the Labels, but nothing happens and the Code for one of the Commands is not reached in Debug-Mode anymore. Seems like it got lost with .NET 8

<CollectionView>
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="{x:Type databaseModels:Food}">

            <controls:CustomSwipeView SwipeViewOpenFlag="{Binding SwipeViewOpenFlag, Source={RelativeSource AncestorType={x:Type viewModels:FoodsOverviewViewModel}}, Mode=TwoWay}">

                <Grid Padding="0, 5">
                    <Border Padding="20, 15, 20, 10" Style="{StaticResource BorderRoundedCornerStyle}">                                        
                        <StackLayout>

                            <Label Text="{Binding Name}" FontSize="13" FontAttributes="Bold" />

                            <Label Text="{Binding Brand}" FontSize="12" FontAttributes="Bold" TextColor="{StaticResource Gray500}" Margin="0, 5, 0, 0"/>

                            <HorizontalStackLayout Margin="0, 15, 0, 0">
                                <HorizontalStackLayout HorizontalOptions="StartAndExpand">
                                    <Image Source="avocado_orange1.png" HeightRequest="12" WidthRequest="12" Margin="0, 1, 5, 5"/>
                                    <Label Text="{loc:Translate CalculateMacros_FatsLabelString, StringFormat='{0}:'}" Style="{StaticResource FoodSelectionDetailsLabelStyle}"/>
                                    <labelControls:CultureBasedNumberLabel Text="{Binding Fats, Mode=OneWay, StringFormat='  {0:0.###}'}" Style="{StaticResource FoodSelectionDetailsLabelStyle}" />
                                </HorizontalStackLayout>

                                <HorizontalStackLayout HorizontalOptions="CenterAndExpand" Margin="5, 0, 0, 0">
                                    <Image Source="wheat_awn_solid_orange1.png" HeightRequest="20" WidthRequest="20" Margin="0, -2, 0, 0" />
                                    <Label Text="{loc:Translate CalculateMacros_CarbsLabelString, StringFormat='{0}:'}" Style="{StaticResource FoodSelectionDetailsLabelStyle}"/>
                                    <labelControls:CultureBasedNumberLabel Text="{Binding Carbohydrates, Mode=OneWay, StringFormat='  {0:0.###}'}" Style="{StaticResource FoodSelectionDetailsLabelStyle}" />
                                </HorizontalStackLayout>

                                <HorizontalStackLayout HorizontalOptions="EndAndExpand" Margin="5, 0, 0, 0">
                                    <Image Source="meat_orange1.png" HeightRequest="16" WidthRequest="16" Margin="5, -1, 3, 2" />
                                    <Label Text="{loc:Translate CalculateMacros_ProteinsLabelString, StringFormat='{0}:'}" Style="{StaticResource FoodSelectionDetailsLabelStyle}"/>
                                    <labelControls:CultureBasedNumberLabel Text="{Binding Proteins, Mode=OneWay, StringFormat='  {0:0.###}'}" Style="{StaticResource FoodSelectionDetailsLabelStyle}" />
                                </HorizontalStackLayout>
                            </HorizontalStackLayout>
                        </StackLayout>
                    </Border>
                </Grid>

                <SwipeView.RightItems>
                    <SwipeItems>
                        <SwipeItemView>

                            <Grid ColumnDefinitions="*, *" WidthRequest="160" HeightRequest="100" ColumnSpacing="0">

                                <Label Grid.Column="0" Text="{loc:Translate FoodsOverview_SwipeItemEditFoodItem}" BackgroundColor="LightGrey" Style="{StaticResource SwipeItemStyle}">
                                    <Label.GestureRecognizers>
                                        <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:FoodsOverviewViewModel}}, Path=EditFoodItemCommand}" CommandParameter="{Binding .}"/>
                                    </Label.GestureRecognizers>
                                </Label>

                                <Label Grid.Column="1" Text="{loc:Translate FoodsOverview_SwipeItemDeleteFoodItem}" BackgroundColor="{StaticResource DarkOrange1}" TextColor="White" Style="{StaticResource SwipeItemStyle}">
                                    <Label.GestureRecognizers>
                                        <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:FoodsOverviewViewModel}}, Path=DeleteFoodItemCommand}" CommandParameter="{Binding .}"/>
                                    </Label.GestureRecognizers>
                                </Label>

                            </Grid>

                        </SwipeItemView>                                        
                    </SwipeItems>
                </SwipeView.RightItems>

            </controls:CustomSwipeView>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

Not necessary for the code above, but to have the example more complete here is the code for the CustomSwipeView

public class CustomSwipeView : SwipeView
{
    public static readonly BindableProperty SwipeViewOpenFlagProperty = BindableProperty.Create(nameof(SwipeViewOpenFlag), typeof(bool), typeof(CustomSwipeView), true, propertyChanged: OnPropertyChanged);

    public bool SwipeViewOpenFlag
    {
        get { return (bool)GetValue(SwipeViewOpenFlagProperty); }
        set { SetValue(SwipeViewOpenFlagProperty, value); }
    }

    private static void OnPropertyChanged(BindableObject sender, object oldValue, object newValue)
    {
        SwipeView swipeView = sender as SwipeView;
        var swipeViewOpeFlag = (bool)newValue;

        if (swipeView != null)
        {
            if (swipeViewOpeFlag == true)
            {
                swipeView.Close();

                ((CustomSwipeView)sender).SwipeViewOpenFlag = false;
            }
        }
    }
}

Steps to Reproduce

Starting with the Example Project:

  1. Got to TabView1ViewModel.cs and navigate to method private async Task ExcludeFromStoreSelectionCommandHandler(StoreSelection storeItemToExclude) { ... } and set a breakpoint e.g. in line 161
  2. Got to StoreSelectionItemView.xaml
  3. There are two implementations the customized SwipeItemView is not working whereas the SwipeItem implementation with the same Command binding reaches the breakpoint
<SwipeView.RightItems>
    <SwipeItems>
        <!-- NOT WORKING -->
        <SwipeItemView>

            <Grid RowDefinitions="*" ColumnDefinitions="*" WidthRequest="80" HeightRequest="100" ColumnSpacing="0">

                <Label Grid.Column="0" Text="Exclude" BackgroundColor="{StaticResource Tertiary}" TextColor="White" Style="{StaticResource SwipeItemStyle}">
                    <Label.GestureRecognizers>
                         <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:TabView1ViewModel}}, Path=ExcludeFromStoreSelectionCommand}" 
                                               CommandParameter="{Binding .}"/>
                    </Label.GestureRecognizers>
                </Label>

            </Grid>

        </SwipeItemView>-->

        <!-- WORKING -->
        <!-- <SwipeItem Text="EXCLUDE" 
                   BackgroundColor="{StaticResource DarkOrange1}" 
                   Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:TabView1ViewModel}}, Path=ExcludeFromStoreSelectionCommand}"
                   CommandParameter="{Binding .}">

        </SwipeItem> -->

    </SwipeItems>
</SwipeView.RightItems>

Link to public reproduction project repository

TapGestureRecognizerNotWorking_19630

Version with bug

8.0.3 also tested with 8.0.10-nightly.10061 and it is not working there

Is this a regression from previous behavior?

Yes, this used to work in .NET MAUI

Last version that worked well

7.0.101

Affected platforms

iOS, Android, I was not able test on other platforms

Affected platform versions

iOS 16.2, Android with Google Pixel 5 - API 33 (Android 13.0 - API 33)

Did you find any workaround?

No workaround so far. Tried with HorizontalStackLayout and Grid, but not working

Relevant log output

No response

PureWeen commented 10 months ago

Can you test with the latest nightly build? https://github.com/dotnet/maui/wiki/Nightly-Builds

MAUIoxo commented 10 months ago

Sorry for the belated response!

Tested with "8.0.6-nightly.9863" and it was working both on Android and on iOS

Great to see that progress :)

MAUIoxo commented 8 months ago

I am sorry to say that, but I was progressing with my project and found a scenario in 8.0.10-nightly.10061 in which it is not working unfortunately, also with a newer version :(

Therefore, I added and example project https://github.com/MAUIoxo/TapGestureRecognizerNotWorking_19630 which demonstrates that it is not working when I have to following XAML code:

<SwipeItemView>

    <Grid RowDefinitions="*" ColumnDefinitions="*" WidthRequest="80" HeightRequest="100" ColumnSpacing="0">

        <Label Grid.Column="0" Text="Exclude" BackgroundColor="{StaticResource Tertiary}" TextColor="White" Style="{StaticResource SwipeItemStyle}">
            <Label.GestureRecognizers>
                 <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:TabView1ViewModel}}, Path=ExcludeFromStoreSelectionCommand}" 
                                       CommandParameter="{Binding .}"/>
            </Label.GestureRecognizers>
        </Label>

    </Grid>

</SwipeItemView>

TapGestureRecognizerNotWorking


When using the non customized SwipeItem I reach my breakpoint in the TabView1ViewModel.cs in method ExcludeFromStoreSelectionCommandHandler:

<SwipeItem Text="EXCLUDE" 
           BackgroundColor="{StaticResource DarkOrange1}" 
           Command="{Binding Source={RelativeSource AncestorType={x:Type viewModels:TabView1ViewModel}}, Path=ExcludeFromStoreSelectionCommand}"
           CommandParameter="{Binding .}">

</SwipeItem>

TapGestureRecognizerWorking


In case I am allowed to guess, I would say this has something to do with nesting depth or where it is located underneath. I use the exact same SwipeItemView in a different place in my App in which I first detected that it worked again (see History of comments above). In this case of the attached App it doesn’t work

MAUIoxo commented 8 months ago

Any workarounds to that? See no other ways to customize that and get the command fire :(

MAUIoxo commented 8 months ago

@PureWeen - Do you think there is a way to look at this soon? This is really bothering development when there is no workaround as it seems right now

kevinxufei commented 8 months ago

Verified this issue with Visual Studio 17.10.0 Preview 1. Can repro on Android/iOS platform with sample project.

Hunte060708 commented 6 months ago

@PureWeen - Do you think there is a way to look at this soon? This is really bothering development when there is no workaround as it seems right now

Have you found a workaround? Can Repro in my own Project with Visual Studio 17.9.6 - Only on iOS.

MAUIoxo commented 6 months ago

I found something, but that is not possible in general. In my case, I wanted to just customize the font settings and have only one element in the SwipeItem, a simple „Delete“ Button. So, I really used a Button and implemented its Clicked-Event. In the handler I used a message and WeakReference-Manager to send a Message to the ViewModel in which I could delete the item then. Not the best way, but I could achieve it doing this and have this as a workaround for this bug.

MAUIoxo commented 6 months ago

Last week, I found a funny workaround for a different issue in which I simply had to derive from a Switch and use this CustomSwitch in my XAML. Haven’t tested that here, but as this was a funny fix, why not try it here and maybe it also works. This fix did not make sense at all, but you never know and it works here, too. Let us know if you find out anything