无法使用Xamarin-iOS和ReactiveUI在ViewModel中绑定UIButton和UITextField

时间:2018-07-04 17:54:58

标签: c# xamarin xamarin.ios reactive-programming reactiveui

我有LoginViewController类

// This file has been autogenerated from a class added in the UI designer.

using System;
using System.Collections.Generic;
using Foundation;
using UIKit;
using System.Linq;
using System.Reactive;
using ReactiveUI;
using GoBatumi.IOS.ViewModels;

namespace GoBatumi.IOS
{
    public partial class LogInViewController : ReactiveViewController,IViewFor<LoginViewModel>
    {      
        UITapGestureRecognizer SingUpGesture;
        public LogInViewController (IntPtr handle) : base (handle)
        {
            this.WhenActivated(d =>
            {
                d(this.Bind(ViewModel, vm => vm.UserName, vm => vm.userNameTextField.Text));
                d(this.Bind(ViewModel, vm => vm.Password, vm => vm.passwordTextField.Text));
                d(this.Bind(ViewModel, vm => vm.LoginButton, vm => vm.logInButton));
                d(this.Bind(ViewModel, vm => vm.PasswordTextField, vm => vm.passwordTextField));
            });
        }

        private void MakeViewModelBinding(){
        }

        public override void ViewDidLayoutSubviews(){
            base.ViewWillLayoutSubviews();   
        }

        LoginViewModel _viewModel;
        public LoginViewModel ViewModel 
        {
            get => _viewModel ?? new LoginViewModel();
            set => _viewModel = value;
        }
        object IViewFor.ViewModel 
        {
            get => ViewModel;
            set => ViewModel = (LoginViewModel)value; 
        }

        public override void ViewDidLoad(){
            base.ViewDidLoad();
        }

        private void ShouldChangeViewSettings(bool enable){
            passwordTextField.Enabled = enable;
            logInButton.Enabled = enable;         
            if (enable)
                logInButton.Alpha = 0.99f;
            else
                logInButton.Alpha = 0.4f;
        }
    }

    public class TestUser
    {
        public string UserName{
            get;
            set;
        }

        public string Password{
            get;
            set;
        }
    }
}

另外,我有LoginViewModel类

using System;
using System.Diagnostics;
using ReactiveUI;
using UIKit;

namespace GoBatumi.IOS.ViewModels
{
    public class LoginViewModel : ReactiveObject
    {
        public LoginViewModel()
        {
        }

        private string _userName;
        public string UserName
        {
            get => _userName;
            set
            {
                this.RaiseAndSetIfChanged(ref _userName, value);
                var result = string.IsNullOrEmpty(_userName);

                ShouldChangeViewSettings(result);
            }
        }


        private string _password;
        public string Password
        {
            get => _password;
            set
            {
                this.RaiseAndSetIfChanged(ref _password, value);
            }    

        }

        private UIButton _loginButton;
        public UIButton LoginButton
        {
            get => _loginButton;
            set => this.RaiseAndSetIfChanged(ref _loginButton, value);
        }

        private UITextField _passwordTextField;
        public UITextField PasswordTextField
        {
            get => _passwordTextField;
            set => this.RaiseAndSetIfChanged(ref _passwordTextField, 
        }

    }
}

我的问题是字符串用户名和密码可以绑定, usernameTextField.Texts和passwordTextField.Text

但是UIButton和UITextField始终为null,它们没有绑定。

我的任务是,每当用户在textField中键入一个字符时,我都必须启用按钮,并且每当用户删除整个Textfield且如果字符串为空时,我都必须再次禁用按钮,因此我需要UiButton来更改背景色从ViewModel中获取,但UIButtonProperty始终返回null。

问题出在哪里?

如果有人给我一些建议,我将感到非常高兴。 我对MVVM和ReactiveUi有点陌生。

谢谢。

2 个答案:

答案 0 :(得分:2)

一些建议:

  • 您的视图模型不应引用任何与平台/视图相关的内容(摆脱UITextField和UIButton成员)。视图模型旨在独立于平台,因此可以重复使用和测试。

  • 使用ReactiveCommands。他们自动处理启用/禁用按钮。如果您查看此文档链接,将会发现基本上相同的示例代码/场景。

  • 使用ReactiveViewController的generic version,因此您不必担心自己实现ViewModel属性(您的版本应该使用RaiseAndSetIfChanged,如您在链接中所见)。

...

public class LoginViewModel : ReactiveObject
{
    public LoginViewModel()
    {
        var canLogin = this.WhenAnyValue(
            x => x.UserName,
            x => x.Password,
            (userName, password) => !string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password));
        LoginCommand = ReactiveCommand.CreateFromObservable(
            LoginAsync, // A method that returns IObservable<Unit>
            canLogin);
    }

    public ReactiveCommand<Unit, Unit> LoginCommand { get; }

    private string _userName;
    public string UserName
    {
        get { return _userName; }
        set { this.RaiseAndSetIfChanged(ref _userName, value); }
    }

    private string _password;
    public string Password
    {
        get { return _password; }
        set { this.RaiseAndSetIfChanged(ref _password, value); }
    }
}

...

public partial class LogInViewController : ReactiveViewController<LoginViewModel>
{      
    UITapGestureRecognizer SingUpGesture;

    public LogInViewController (IntPtr handle) : base (handle)
    {
        this.WhenActivated(d =>
        {
            d(this.Bind(ViewModel, vm => vm.UserName, v => v.userNameTextField.Text));
            d(this.Bind(ViewModel, vm => vm.Password, v => v.passwordTextField.Text));
            d(this.BindCommand(ViewModel, vm => vm.LoginCommand, v => v.logInButton));
        });
    }
}

这里是heavily documented ViewModel,以及相应的示例项目,可帮助您朝正确的方向前进。如果您真的想精通该书,我强烈推荐这本书“ You, I, and ReactiveUI”。希望这会有所帮助。

答案 1 :(得分:1)

如果我的理解正确的话,您可以做这样的事情:

在您的ViewModel中:

public ReactiveCommand<Unit,Unit> LoginCommand { get; set; }

    public LoginViewModel()
    {
        //this operator does the magic, when UserName and Password be different than empty your button
        //will be enabled
        var canLogin = this.WhenAnyValue(x => x.UserName, x=> x.Password, 
                                        (user,password) => !string.IsNullOrWhiteSpace(user) && !string.IsNullOrWhiteSpace(password));

        LoginCommand = ReactiveCommand.CreateFromTask<Unit, Unit>(async _ =>
        {
            //Your login logic goes here..
            return Unit.Default;
        }, canLogin);

    }

在您的视图中

 public LogInViewController (IntPtr handle) : base (handle)
    {
        this.WhenActivated(d =>
        {
            d(this.Bind(ViewModel, vm => vm.UserName, vm => vm.userNameTextField.Text));
            d(this.Bind(ViewModel, vm => vm.Password, vm => vm.passwordTextField.Text));
            d(this.BindCommand(this.ViewModel,vm => vm.LoginCommand,v => v.LoginButton));
        });
    }

您的视图和视图模型通过命令绑定进行交互。

希望这对您有所帮助。