在ViewController中绑定嵌套自定义视图属性失效问题排查
咱们来拆解下你遇到的这个绑定失效问题,核心原因是自定义视图没有满足MvvmCross绑定系统的核心要求,具体有这几个关键点你忽略了:
1. 自定义视图没实现INotifyPropertyChanged接口
MvvmCross的绑定系统本质是靠属性变更通知来同步数据的——不管是ViewModel把值推给UI,还是UI把用户输入同步回ViewModel,都需要绑定的目标对象(也就是你的CustomViewA、CustomViewB)实现INotifyPropertyChanged接口。
你直接绑定UITextField能生效,是因为苹果的原生UI控件(比如UITextField)内部已经实现了值变化的通知逻辑,MvvmCross对这些原生控件有内置的绑定支持。但你的自定义视图只是继承了UIView,完全没有这个通知机制,绑定系统根本不知道属性什么时候变了。
2. 自定义Text属性没触发变更通知
就算你给CustomViewA和CustomViewB加了Text属性,也只是简单封装了子视图的属性,没有在setter里触发PropertyChanged事件。举个例子:
- 当ViewModel的
Text更新时,MvvmCross会把值设给CustomViewA.Text,但因为没有通知,绑定系统不知道要把这个值往下传给CustomViewB和UITextField; - 反过来,用户在
UITextField里输入内容时,CustomViewB.Text变了,但也没通知CustomViewA,更没法同步回ViewModel。
怎么修复?
要让绑定正常工作,得给自定义视图补上变更通知的逻辑,这里有两种实用方式:
方式一:手动实现INotifyPropertyChanged
修改CustomViewB:
public class CustomViewB : UIView, INotifyPropertyChanged { private UITextField _textField; public event PropertyChangedEventHandler PropertyChanged; public string Text { get => _textField.Text; set { if (_textField.Text != value) { _textField.Text = value; RaisePropertyChanged(nameof(Text)); } } } private void RaisePropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public CustomViewB() { _textField = new UITextField(); // 要双向绑定的话,得监听输入变化,触发属性变更 _textField.EditingChanged += (sender, e) => RaisePropertyChanged(nameof(Text)); // 其他初始化代码... } }
修改CustomViewA:
public class CustomViewA : UIView, INotifyPropertyChanged { private CustomViewB _customViewB; public event PropertyChangedEventHandler PropertyChanged; public string Text { get => _customViewB.Text; set { if (_customViewB.Text != value) { _customViewB.Text = value; RaisePropertyChanged(nameof(Text)); } } } private void RaisePropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public CustomViewA() { _customViewB = new CustomViewB(); // 监听子视图的属性变化,同步到当前视图的Text属性(双向绑定需要) _customViewB.PropertyChanged += (sender, e) => { if (e.PropertyName == nameof(CustomViewB.Text)) RaisePropertyChanged(nameof(Text)); }; // 其他初始化代码... } }
方式二:继承MvvmCross的MvxView
MvvmCross提供了MvxView基类,它已经帮你实现了INotifyPropertyChanged接口,还封装了RaisePropertyChanged方法,用它可以省掉手动实现接口的代码:
public class CustomViewB : MvxView { private UITextField _textField; public string Text { get => _textField.Text; set { if (_textField.Text != value) { _textField.Text = value; RaisePropertyChanged(() => Text); } } } // 初始化逻辑同上... }
这样修改后,你原来的绑定代码bindingSet.Bind(customViewA).For(v => v.Text).To(vm => vm.Text);就能正常工作了,不管是ViewModel推值到UI,还是用户输入同步回ViewModel都没问题。
内容的提问来源于stack exchange,提问作者user525192

