Deployment iOS not binding XAML properties and commands from ViewModel

mall1312mall1312 Member ✭✭
edited August 2018 in Xamarin.iOS

I am using Xamarin Forms in VS 2017.

When I try to test my application in iPhone Simulator or real Device the pages are not binding the properties and commands from the view model.

I am packaging with the Linker in "Link All" and the target framework for iOS 11.4.

This is my code:

XAML

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyApp;assembly=MyApp"
             xmlns:xfx="clr-namespace:Xfx;assembly=Xfx.Controls"
             x:Class="MyApp.LoginPage"
             NavigationPage.HasNavigationBar="false" 
             Title="Login" BackgroundColor="#8ab1d3">
    <ContentPage.Content>
        <ScrollView>
            <AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
                <StackLayout Orientation="Vertical" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1">
                    <StackLayout Orientation="Vertical" HorizontalOptions="Center">
                        <StackLayout Orientation="Vertical" VerticalOptions="Center"  Opacity="0.8">
                            <Image x:Name="imgLogo" HeightRequest="180" WidthRequest="170"></Image>
                        </StackLayout>
                    </StackLayout>
                    <StackLayout  Orientation="Vertical">
                        <StackLayout Orientation="Vertical" HorizontalOptions="Center">
                            <StackLayout>
                                <Label Text="{Binding Message, Mode=OneWay}" TextColor="#FFF" FontSize="18" FontAttributes="Bold" HorizontalOptions="CenterAndExpand"/>
                            </StackLayout>
                            <StackLayout Padding="5" Margin="0,-15,0,0">
                                <StackLayout Orientation="Horizontal">
                                    <Label Text="{x:Static local:Helpers.FontAwesome.FAUser}" FontSize="30" FontFamily="FontAwesome" HorizontalOptions="Start"  Margin="0,10,0,0" />
                                    <xfx:XfxEntry Text="{Binding User.Email, Mode=TwoWay}" x:Name="EmailEntry" WidthRequest="270"  HorizontalOptions="Fill" TextColor="{Binding TextColor}" ActivePlaceholderColor="{Binding ActivatePHColor}" PlaceholderColor="{Binding PHColor}" Placeholder="Usuario" Keyboard="Email" FontSize="18" HorizontalTextAlignment="Start" />
                                </StackLayout>
                                <StackLayout Orientation="Horizontal">
                                    <Label Text="{x:Static local:Helpers.FontAwesome.FALock}" FontSize="30" FontFamily="FontAwesome" HorizontalOptions="Start" Margin="0,10,0,0" />
                                    <xfx:XfxEntry Text="{Binding User.Password, Mode=TwoWay}" x:Name="PasswordEntry" WidthRequest="270"  HorizontalOptions="Fill" TextColor="{Binding TextColor}" ActivePlaceholderColor="{Binding ActivatePHColor}" PlaceholderColor="{Binding PHColor}" Placeholder="Contraseña" IsPassword="True" FontSize="18" HorizontalTextAlignment="Start"/>
                                </StackLayout>
                            </StackLayout>
                            <StackLayout Orientation="Vertical" Padding="0,0,50,0">
                                <StackLayout Orientation="Horizontal" HorizontalOptions="EndAndExpand" Margin="0,-20,0,0">
                                    <Switch x:Name="switchRemember" ></Switch>
                                    <Label Text="Recordar" FontSize="17"></Label>
                                </StackLayout>
                            </StackLayout>
                            <StackLayout Orientation="Vertical" >
                                <Button x:Name="btnSignIn" Text="Iniciar" BackgroundColor="#0072BD" TextColor="White" FontSize="20" FontAttributes="Bold" WidthRequest="300" HorizontalOptions="CenterAndExpand" Command="{Binding LoginCommand}"/>
                            </StackLayout>
                            <StackLayout Orientation="Vertical">
                                <Button Text="¿Olvidaste tu contraseña?" TextColor="#FFF" BackgroundColor="#8ab1d3" BorderWidth="0" WidthRequest="250" HorizontalOptions="CenterAndExpand" Command="{Binding ForgotPasswordCommand}" />
                            </StackLayout>
                        </StackLayout>
                    </StackLayout>
                    <StackLayout  Orientation="Vertical">
                        <StackLayout Orientation="Vertical">
                            <Button HorizontalOptions="CenterAndExpand" Text="Registrate" BackgroundColor="#0063AB" TextColor="#FFF" FontSize="14" WidthRequest="150" Command="{Binding SignUpCommand}"></Button>
                        </StackLayout>
                    </StackLayout>
                </StackLayout>
                <StackLayout IsVisible="{Binding IsBusy}" Padding="12" AbsoluteLayout.LayoutFlags="PositionProportional" AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1">
                    <Frame x:Name="frameLayer" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" BackgroundColor="#404040" Opacity="0.8" HorizontalOptions="CenterAndExpand" IsVisible="{Binding IsBusy}"  VerticalOptions="CenterAndExpand">
                        <StackLayout>
                            <ActivityIndicator x:Name="indicatorLoader" WidthRequest="85" HeightRequest="85" BackgroundColor="Transparent" IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}" VerticalOptions="CenterAndExpand" Color="#FFF" />
                            <Label HorizontalTextAlignment="Center" Text="Procesando..." FontSize="22" TextColor="#FFF"  XAlign="Center" />
                        </StackLayout>
                    </Frame>
                </StackLayout>
            </AbsoluteLayout>
        </ScrollView>
    </ContentPage.Content>
</ContentPage>

**
XAML.cs**

public partial class LoginPage : ContentPage
    {
        private LoginPageViewModel viewModel;
        public LoginPage()
        {
            InitializeComponent();
            imgLogo.Source = ImageSource.FromFile("cloudlogo.png");
            BindingContext = viewModel = new LoginPageViewModel();

        }
}

LoginPageViewModel.cs

 public class LoginPageViewModel : ViewModelBase
    {
        #region Commands
        public ICommand LoginCommand { get; set; }
        public ICommand SignUpCommand { get; set; }
        public ICommand ForgotPasswordCommand { get; set; }
        public ICommand RememberCommand { get; set; }
        #endregion

 #region Properties
        private UserModel _user = new UserModel();
        private bool _rememberchecked = false;
        private string _iconchecked = "";

        public UserModel User
        {
            get { return _user; }
            set { SetProperty(ref _user, value); }
        }

        public bool RememberChecked
        {
            get { return _rememberchecked; }
            set { SetProperty(ref _rememberchecked, value); }
        }

        public string IconChecked
        {
            get { return _iconchecked; }
            set { SetProperty(ref _iconchecked, value); }
        }

        #endregion

 public LoginPageViewModel()
        {
            RememberChecked = false;
            IconChecked = "&#xf046;";
            LoginCommand = new Command(Login);
            SignUpCommand = new Command(SignUp);
            ForgotPasswordCommand = new Command(ForgotPwd);
            RememberCommand = new Command(CheckRemember);
            IsBusy = false;
        }

  public async void ForgotPwd()
        {
            await Navigation.PushAsync(new ForgotPasswordPage());
        }
        public async void SignUp()
        {
            await Navigation.PushAsync(new SignUpPage());
        }
   public async void Login()
        {
            ....//Complementary code
        }
}

ViewModelBase.cs

     public class ViewModelBase : INotifyPropertyChanged
        {

            public INavigation Navigation { get; set; }

            bool isBusy;

            /// <summary>
            /// Gets or sets a value indicating whether this instance is busy.
            /// </summary>
            /// <value><c>true</c> if this instance is busy; otherwise, <c>false</c>.</value>
            public bool IsBusy
            {
                get { return isBusy; }
                set
                {
                    SetProperty(ref isBusy, value);
                }
            }

            string message = string.Empty;

            /// <summary>
            /// Gets or sets the title.
            /// </summary>
            /// <value>The title.</value>
            public string Message
            {
                get { return message; }
                set { SetProperty(ref message, value); }
            }

            string _textcolor = "#0E394E";
            string _phcolor = "#14516D";
            string _activatephcolor = "#FFF";

            public string TextColor
            {
                get { return _textcolor; }
            }

            public string PHColor
            {
                get { return _phcolor; }
            }

            public string ActivatePHColor
            {
                get { return _activatephcolor; }
            }

            public CloudComun.Models.UserModel CurrentUser
            {
                get; set;
            }


            public RestService api;

            public ViewModelBase()
            {
                api = new RestService();
            }

            public ViewModelBase(string title)
            {
                Title = title;
                api = new RestService();
            }

             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;
            }

            public event PropertyChangedEventHandler PropertyChanged;

            protected void OnPropertyChanged([CallerMemberName]string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        }

In the image attached, you can see the ActivityIndicator is showing but the property IsBusy is FALSE in the viewmodel.
Buttons not calling the actions of the viewmodel.

In debug, I set a breakpoint in the constructor of the LoginPage(XAML.cs) but in the object I can´t see the properties inhereted from the ViewModelBase and the commands from the LoginPageViewModel

**
Android deploy is working fine.**

Best Answer

  • mall1312mall1312 ✭✭
    edited August 2018 Accepted Answer

    I found a solution to solve it.
    As I posted before, I was using "Link All" in the linker project.

    • I had to add a class to set the Preserve attributes on classes and properties( get{...} set{...} )

      [System.AttributeUsage(System.AttributeTargets.All )]
          public class PreserveAttribute : System.Attribute
          {
              public PreserveAttribute() { }
              public bool AllMembers { get; set; }
              public bool Conditional { get; set; }
          }
      
    • Add the attribute over the class or property:
      Class:

          [Preserve]
          public class LoginPageViewModel : ViewModelBase
          {
                  ...//My code
          }
      

      Properties:

            [Preserve]
                public ICommand SignUpCommand { get; set; }
      
                private UserModel _user = new UserModel();
                [Preserve]
                public UserModel User
                {
                      get { return _user; }
                      set { SetProperty(ref _user, value); }
                 }
      
                 [Preserve]
                 public bool RememberChecked
                 {
                      get { return _rememberchecked; }
                      set { SetProperty(ref _rememberchecked, value); }
                 }
      
    • Add a class called LinkerPleaseInclude in the iOS project

                [Preserve(AllMembers = true)]
                public class LinkerPleaseInclude
                {
                      public void Include(UIButton uiButton)
                      {
                          uiButton.TouchUpInside += (s, e) =>
                                                    uiButton.SetTitle(uiButton.Title(UIControlState.Normal), UIControlState.Normal);
                      }
      
                  public void Include(UIBarButtonItem barButton)
                      {
                          barButton.Clicked += (s, e) =>
                                               barButton.Title = barButton.Title + "";
                      }
      
                      public void Include(UITextField textField)
                      {
                          textField.Text = textField.Text + "";
                          textField.EditingChanged += (sender, args) =>
                          {
                              textField.Text = "";
                          };
                      }
                   }
      
    • Preserve the external references/Nuget packages

      using ObjCRuntime;
      
      [assembly: Xamarin.Forms.Dependency(typeof(iOS_Version))]
      [assembly: Preserve(typeof(Stripe.StripeCreditCardOptions), AllMembers = true)]
      [assembly: Preserve(typeof(Stripe.StripeTokenCreateOptions), AllMembers = true)]
      [assembly: Preserve(typeof(Stripe.StripeTokenService), AllMembers = true)]
      [assembly: Preserve(typeof(Stripe.StripeToken), AllMembers = true)]
      
      namespace YourNameSpace.iOS
      {
           [Preserve(AllMembers = true)]
           public class LinkerPleaseInclude
              {
              ...//The code that appears before this point
      
          }
      }
      

    @LandLu @marcnegri
    Thanks for your posts.

Answers

  • marcnegrimarcnegri CHMember ✭✭

    Hi @marcolerma1312,

    I don't really know why your code is not working. It seems correct.
    You can try this on your BaseViewModel :
    if you inherited viewmodels with the BaseViewModel you can add RaisePropertyChanged() in your getters.

    C#
        public bool IsBusy
            {
            get
            {
            return isBusy;
        }
        set
        {
        isBusy = value;
            RaisePropertyChanged();
        }
    }
    protected void RaisePropertyChanged([CallerMemberName]  string propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public virtual Task OnNavigated(object parameter) { return Task.Run(() => { }); }
    bool isBusy;
    
    public event PropertyChangedEventHandler PropertyChanged;
    
  • mall1312mall1312 Member ✭✭
    edited August 2018

    Hi @marcnegri

    I add the method to the view model but the result is the same.

    ViewModelBase

         bool isBusy;
    
            public bool IsBusy
            {
                get { return isBusy; }
                set
                {
                    isBusy = value;
                   // SetProperty(ref isBusy, value);
                    RaisePropertyChanged();
                }
            }
    
    protected void RaisePropertyChanged([CallerMemberName]  string propertyName = "")
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    

    I set a breakpoint but I can´t see the property **IsBusy **from ViewModelBase and the commands from LoginPageViewModel

  • LandLuLandLu Member, Xamarin Team Xamurai

    @marcolerma1312 It is because you have set the Linker behavior to Link All. According to this documentation, your code should be adjusted if you set this feature(including webservices, reflection, or serialization).
    This means it will modify your code about reflection. And binding is belong to this. So your binding in the iOS project lose its effect. It is not recommended to use this feature, for iOS simulators default feature is Don't Link and for real device default is Link Framework SDKs only.

    If you do want to set this, Skipping Assemblies must be included to avoid it modifying your code about binding.

  • mall1312mall1312 Member ✭✭
    edited August 2018 Accepted Answer

    I found a solution to solve it.
    As I posted before, I was using "Link All" in the linker project.

    • I had to add a class to set the Preserve attributes on classes and properties( get{...} set{...} )

      [System.AttributeUsage(System.AttributeTargets.All )]
          public class PreserveAttribute : System.Attribute
          {
              public PreserveAttribute() { }
              public bool AllMembers { get; set; }
              public bool Conditional { get; set; }
          }
      
    • Add the attribute over the class or property:
      Class:

          [Preserve]
          public class LoginPageViewModel : ViewModelBase
          {
                  ...//My code
          }
      

      Properties:

            [Preserve]
                public ICommand SignUpCommand { get; set; }
      
                private UserModel _user = new UserModel();
                [Preserve]
                public UserModel User
                {
                      get { return _user; }
                      set { SetProperty(ref _user, value); }
                 }
      
                 [Preserve]
                 public bool RememberChecked
                 {
                      get { return _rememberchecked; }
                      set { SetProperty(ref _rememberchecked, value); }
                 }
      
    • Add a class called LinkerPleaseInclude in the iOS project

                [Preserve(AllMembers = true)]
                public class LinkerPleaseInclude
                {
                      public void Include(UIButton uiButton)
                      {
                          uiButton.TouchUpInside += (s, e) =>
                                                    uiButton.SetTitle(uiButton.Title(UIControlState.Normal), UIControlState.Normal);
                      }
      
                  public void Include(UIBarButtonItem barButton)
                      {
                          barButton.Clicked += (s, e) =>
                                               barButton.Title = barButton.Title + "";
                      }
      
                      public void Include(UITextField textField)
                      {
                          textField.Text = textField.Text + "";
                          textField.EditingChanged += (sender, args) =>
                          {
                              textField.Text = "";
                          };
                      }
                   }
      
    • Preserve the external references/Nuget packages

      using ObjCRuntime;
      
      [assembly: Xamarin.Forms.Dependency(typeof(iOS_Version))]
      [assembly: Preserve(typeof(Stripe.StripeCreditCardOptions), AllMembers = true)]
      [assembly: Preserve(typeof(Stripe.StripeTokenCreateOptions), AllMembers = true)]
      [assembly: Preserve(typeof(Stripe.StripeTokenService), AllMembers = true)]
      [assembly: Preserve(typeof(Stripe.StripeToken), AllMembers = true)]
      
      namespace YourNameSpace.iOS
      {
           [Preserve(AllMembers = true)]
           public class LinkerPleaseInclude
              {
              ...//The code that appears before this point
      
          }
      }
      

    @LandLu @marcnegri
    Thanks for your posts.

Sign In or Register to comment.