3

I am trying to create an onboarding view with CarouselView. I have a position indicator and a button to change the carousel view item. When I press the button, it should display the next item. Alternatively, if I click on the indicator, it should change the item too!

The problem is, without swiping the view at least once or setting the position to zero from the UI (by going to an arbitrary position and pressing the first index from the indicator.), I can't control its position. (Demo in the gif below).

enter image description here

Here is my CaouselView XAML

 <Grid RowDefinitions="*,Auto">
        <CarouselView x:Name="OnBoardingCarouselView"
                      HorizontalOptions="FillAndExpand"
                      VerticalOptions="FillAndExpand"
                      IsBounceEnabled="False"
                      Loop="False" ItemsSource="{Binding IntroScreens}"
                      PositionChangedCommand="{Binding HandlePositionChangedCommand}"
                      PositionChangedCommandParameter="{Binding Source={RelativeSource Self}, Path=Position}"
                      IndicatorView="CarouselIndicator"
                      Position="{Binding CarouselPosition, Mode=TwoWay}">
            <CarouselView.ItemTemplate>
                <DataTemplate x:DataType="models:IntroScreen">
                    <Grid RowDefinitions="60*,40*">
                        <Image Source="{Binding Image}" HeightRequest="200"></Image>
                        <VerticalStackLayout Grid.Row="1">
                            <Label Text="{Binding IntroTitle}"
                                   FontSize="Title"></Label>
                            <Label Text="{Binding IntroDescription}"
                                   FontSize="Body"></Label>
                        </VerticalStackLayout>
                    </Grid>
                </DataTemplate>
            </CarouselView.ItemTemplate>
        </CarouselView>
        <Grid Grid.Row="1" ColumnDefinitions="*,Auto">
            <IndicatorView x:Name="CarouselIndicator" Grid.Column="0"
                           IndicatorsShape="Circle"
                           IndicatorSize="12"
                           IndicatorColor="{StaticResource Secondary}"
                           SelectedIndicatorColor="{StaticResource Primary}"
                           VerticalOptions="Center"></IndicatorView>
            <StackLayout Grid.Column="1">
                <Button x:Name="NextButton" Text="Next" 
                        Command="{Binding HandleNextButtonClickCommand}"
                        IsVisible="{Binding NextButtonVisibility}"></Button>
                <Button x:Name="EnterButton" Text="Enter" 
                        Command="{Binding HandleEnterButtonClickCommand}"
                        IsVisible="{Binding EnterButtonVisibility}"></Button>
            </StackLayout>
        </Grid>

I have tried to set starting position like this, and it is not working -

    public IntroScreenView()
    {
        InitializeComponent();

        // Check if the position preset is working
        OnBoardingCarouselView.Position = 1;
    }

Here is my corresponding view model -

[ObservableObject]
    public partial class IntroScreenViewModel
    {
        [ObservableProperty]
        private int carouselPosition;
        [ObservableProperty]
        private bool nextButtonVisibility;
        [ObservableProperty]
        private bool enterButtonVisibility;
        [ObservableProperty]
        private ObservableCollection<IntroScreen> introScreens;

        public IntroScreenViewModel()
        {
            EnterButtonVisibility = false;
            NextButtonVisibility = true;
            IntroScreens = new ObservableCollection<IntroScreen>
            {
                new()
                {
                    IntroTitle = "Intro Title One",
                    IntroDescription = "Lorem is some time ipsum but ipsum can't be lorem.",
                    Image = "cr_1.svg"
                },
                new()
                {
                    IntroTitle = "Intro Title Two",
                    IntroDescription = "Lorem is some time ipsum but ipsum can't be lorem.",
                    Image = "cr_2.svg"
                },
                new()
                {
                    IntroTitle = "Intro Title Three",
                    IntroDescription = "Lorem is some time ipsum but ipsum can't be lorem.",
                    Image = "cr_3.svg"
                }
            };
            // commenting the following line or setting to any position 
            // does not affect anything
            CarouselPosition = 0;
        }
        [RelayCommand]
        public void HandlePositionChanged(int position)
        {
            // here position variable always gets the correct position
            if (position == IntroScreens.Count - 1)
            {
                EnterButtonVisibility = true;
                NextButtonVisibility = false;
            }
            else
            {
                EnterButtonVisibility = false;
                NextButtonVisibility = true;
            }
        }
        [RelayCommand]
        public void HandleNextButtonClick()
        {
            if (CarouselPosition + 1 < introScreens.Count)
                CarouselPosition++;
        }

        [RelayCommand]
        public void HandleEnterButtonClick()
        {
            Application.Current!.MainPage = new LoginPageView();
        }
    }

Full source code is here https://github.com/MahmudX/Binimoy

Mahmudul Hasan
  • 798
  • 11
  • 35
  • Can you create a small GitHub demo of this issue if possible? – FreakyAli Sep 28 '22 at 00:35
  • You can’t set the Position until after ItemsSource is loaded, because it has to validate that the position is less than the total number of items – Jason Sep 28 '22 at 01:19
  • the position is binded to `CarouselPosition` variable in TwoWay mode. So, when the source is loaded, it should be set to 0, I guess. – Mahmudul Hasan Sep 28 '22 at 01:29
  • @FreakyAli I have edited the post and added the GitHub repo URL. – Mahmudul Hasan Sep 28 '22 at 01:34
  • You are manually setting it to 1 in your constructor, which will break the binding set in the XAML – Jason Sep 28 '22 at 01:39
  • I added that just for testing. Removing that line of code does not change anything. – Mahmudul Hasan Sep 28 '22 at 01:50
  • I see the line `CarouselPosition = 0;` in viewmodel. As a test, what happens if you do `MainThread.BeginInvokeOnMainThread(async () => { await Task.Delay(3000); CarouselPosition = 1; });` instead? This should wait three seconds, then change it. – ToolmakerSteve Sep 28 '22 at 23:56

2 Answers2

3

I also encountered this problem. After several hours of experiments, I realized that problem is in Loop property.

Setting Loop="True" in CarouselView resolves this problem.

Anyway, it's MAUI bug.

0

@doss-bilure The answer is correct and solves this problem.

Just a little note: if you edit collection CarouselView, it automatically changes Position, so it matches the previously selected item. If you want to add an item without this change, you have to edit both Position and CurrentItem

  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/33979285) – Brisbe Mar 13 '23 at 14:44