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

Cant Bind UIButton and UITextField in ViewModel with Xamarin-iOS and ReactiveUI

我有 LoginViewController class

// 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 class

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 中键入一个字符时,我必须启用该按钮,而每当用户删除整个文本字段并且如果字符串为空时,我必须再次禁用该按钮,因此我需要 UiButton 来更改来自 ViewModel 的背景颜色,但 UIButtonProperty 始终 returns null.

问题出在哪里?

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

谢谢。

几点建议:

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

  • 使用ReactiveCommands。他们自动处理 enabling/disabling 按钮。如果您查看此文档 link,您会发现本质上完全相同的示例 code/scenario.

  • 使用 ReactiveViewController 的 generic version 这样您就不必担心自己实现 ViewModel 属性(您的版本应该使用 RaiseAndSetIfChanged,正如您将在 link).

...

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, along with the corresponding sample project to help you get headed in the right direction. And I highly recommend the book, "You, I, and ReactiveUI"。希望这对您有所帮助。

好的朋友,如果我没理解错的话,你可以这样做:

在您的 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));
        });
    }

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

希望对您有所帮助。