3
votes

I am having some issues with bindings using xamarin forms IOS. I have bindings That display a loading label in the center of the screen when the first page appears.

After The listview loads the data the Loading label is hidden through bindings and the list view is displayed.

In the android project I have in this repo Xamarin forms IOS Android Test

It works fine. In the ios in the repro project the loading label is hidden when the application first loads and will display if you tap on the screen. If you continue the loading label disappears and you see a blank screen but if you tap on the screen again the list data view appears.

I am thinking this is a bug with xamarin but I am hoping that my bindings are just incorrect. I work around for this issue would be appreciated. I also two other problems. When you click on a list view item and the navigate back the data is not refreshed because onappearing is not triggered.

In android it is triggered without an issue. Sometimes onappearing does trigger after you tap on the screen like the steps mentioned above. The last issue is that when switching between tabs IOS does not trigger on appearing. With android it does. Any help on this issue would be greatly appreciated. I have done extensive searching on solutions for these problems and have yet to find and answer. Thanks!

Here is some code snippets to help. This code is also in the GitHub repo if you want to test it out.

In XAML

<Label x:Name="xEmptyListView"
         FontSize="16"
         HorizontalOptions="CenterAndExpand"
         HorizontalTextAlignment="Center"
         VerticalOptions="CenterAndExpand"             
         Text="{Binding ListViewVisibleText, Mode=TwoWay}"
         IsVisible="{Binding IsListViewLabelEmptyVisible, Mode=TwoWay }"/>

  <ListView x:Name="ItemsListView"
            ItemsSource="{Binding Items}"
            VerticalOptions="FillAndExpand"
            HasUnevenRows="true"
            RefreshCommand="{Binding LoadItemsCommand}"
            IsPullToRefreshEnabled="true"
            IsRefreshing="{Binding IsBusy, Mode=OneWay}"
            CachingStrategy="RecycleElement"
            ItemSelected="OnItemSelected"
            IsVisible="{Binding IsListViewVisible, Mode=TwoWay}"
            IsEnabled="{Binding IsActivityRunning, Mode=TwoWay, Converter={StaticResource InverseBooleanConverter}}">

ViewModel

private bool activityRunning { get; set; }

    public bool IsActivityRunning
    {
        get { return activityRunning; }
        set
        {
            if (activityRunning == value)
                return;

            activityRunning = value;
            OnPropertyChanged("IsActivityRunning");
        }
    }

    private string listViewVisibleText { get; set; }
    public string ListViewVisibleText
    {
        get { return listViewVisibleText; }
        set
        {
            if (listViewVisibleText == value)
                return;

            listViewVisibleText = value;
            OnPropertyChanged("ListViewVisibleText");
        }
    }



    private bool listViewLabelEmptyVisible { get; set; }

    public bool IsListViewLabelEmptyVisible
    {
        get
        {
            if (Items == null || Items.Count == 0)
            {
                if (IsBusy)
                {
                    ListViewVisibleText = "Loading...";
                }
                else
                {

                    ListViewVisibleText = "No Items found";


                }
                listViewLabelEmptyVisible = true;
            }
            else
            {
                ListViewVisibleText = string.Empty;
                listViewLabelEmptyVisible = false;
            }

            OnPropertyChanged("IsListViewLabelEmptyVisible");
            return listViewLabelEmptyVisible;
        }
    }

    private bool listViewVisible { get; set; }

    public bool IsListViewVisible
    {
        get
        {
            if (Items == null || Items.Count == 0)
            {
                listViewVisible = false;
            }
            else
            {
                listViewVisible = true;
            }

            OnPropertyChanged("IsListViewVisible");
            return listViewVisible;
        }
    }

XAML.cs

 protected override void OnAppearing()
        {
            base.OnAppearing();


                viewModel.LoadItemsCommand.Execute(null);
        } 

I am using notify property changed. It is standard code

This is what my view model inherits for the notifyproperty changed

public class ItemsViewModel : BaseViewModel

which base viewmodel inherits from the observable object

public class BaseViewModel : ObservableObject

When you create a test xamarin project this is the way it looks.

   public class ObservableObject : INotifyPropertyChanged
    {
        /// <summary>
        /// Sets the property.
        /// </summary>
        /// <returns><c>true</c>, if property was set, <c>false</c> otherwise.</returns>
        /// <param name="backingStore">Backing store.</param>
        /// <param name="value">Value.</param>
        /// <param name="propertyName">Property name.</param>
        /// <param name="onChanged">On changed.</param>
        /// <typeparam name="T">The 1st type parameter.</typeparam>
        protected bool SetProperty<T>(
            ref T backingStore, T value,
            [CallerMemberName]string propertyName = "",
            Action onChanged = null)
        {
            if (EqualityComparer<T>.Default.Equals(backingStore, value))
                return false;

            backingStore = value;
            onChanged?.Invoke();
            OnPropertyChanged(propertyName);
            return true;
        }

        /// <summary>
        /// Occurs when property changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the property changed event.
        /// </summary>
        /// <param name="propertyName">Property name.</param>
        protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;

            changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
} 

Activity running gets set when items are loading etc. This all works on android. IOS it doesn't.

2
could you add some example code?Mike
I discovered that during view's OnAppear, any changes made to viewmodel bound properties are ignored by view (as if OnPropertyChanged not called), presumably because view is not visible, so is not prepared to respond. On Android, I was able to add an event to be triggered at beginning of first "draw". [I created a Custom Renderer, and overrode draw method.] On iOS, that technique did not work, my draw override was not called. So I start (in OnAppear) a delayed method (I've been using 1/4 second as delay) that calls OnPropertyChanged(nameof(MyProperty)). Gross hack, but it works.ToolmakerSteve

2 Answers

1
votes

Sometimes the navigation behaviours are different from iOS and Android. If OnAppearing is not working, you can try to use MessageCenter https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/messaging-center/ to trigger data loading.

0
votes

I figured this one out. The way I was handling the bindings with showing the label and hiding the list view and vice versa was interfering with the way that IOS handles requests. I removed the bindings and set the controls directly using a property changed handler on the list view. That fixed the on appearing problems for the tabs, the back button etc.