我对iOS开发领域还比较陌生。但是已经使用了大约2个月,但是我觉得我设置的内容可能存在缺陷或缺少某些东西。
基本上,我设置的是一个占位符视图控件,任何人都可以在其XIB中添加到其UITableViewCell原型中。
您可能会问这是什么目的? UI组成基本不错。我们的解决方案分为业务领域,我们专注于UI组合以保持分离。
例如,“客户”和“订单”是两个独立的业务领域。
假设我们有一个名为Order的对象,Order通过其ID(客户ID)对客户的引用。
当我们在UITableView中显示订单列表时。您可能会看到订单号,订单的下达日期,以及订单的收件人(即客户)的姓名。
从桌面的角度(特别是WPF)的角度来看,我们在UI构成方面所做的就是拥有一个ContentPresenter,它是一个占位符,它将在运行时被其各自的视图替换。
同样,我们希望在iOS上执行相同的操作。
我已经建立了一个简单的框架来尝试实现这一目标,但是在iOS的世界中,我不确定是否违反或缺少任何东西来实现我想要的目标。
其工作方式是从UIView继承的名为PlaceholderView的自定义视图。
PlaceholderView包含两个属性-PlaceholderViewTypeKey,PlaceholderViewData
PlaceholderViewTypeKey是一个字符串,是用于解析特定视图的唯一键。
PlaceholderViewData作为某种参数传递给相应的View Registry查找,以解析视图。
回到订购示例。
“客户”项目库将提供“客户名称”的替代视图。
其中包含以下文件:
我认为代码可以更好地说明这一点。
using System;
using System.ComponentModel;
using System.Drawing;
using CoreGraphics;
using Foundation;
using UIKit;
namespace SharedViews
{
[Register("PlaceholderView"), DesignTimeVisible(true)]
public class PlaceholderView : UIView
{
private object _placeholderViewData;
private string _placeholderViewTypeKey;
private CGSize _customIntrinsicSize = new CGSize();
[Export("PlaceholderViewTypeKey"), Browsable(true)]
public string PlaceholderViewTypeKey
{
get => _placeholderViewTypeKey;
set
{
_placeholderViewTypeKey = value;
RefreshView();
}
}
private void RefreshView()
{
ClearSubViews();
GetViewAndBind();
}
public object PlaceholderViewData
{
get => _placeholderViewData;
set
{
_placeholderViewData = value;
RefreshView();
}
}
public PlaceholderView(IntPtr handle) : base(handle) { }
public PlaceholderView()
{
Initialize();
}
public PlaceholderView(CGRect bounds) : base(bounds)
{
// Called when created from code.
Initialize();
}
public override void AwakeFromNib()
{
// Called when loaded from xib or storyboard.
Initialize();
}
void Initialize()
{
}
[Export("CustomIntrinsicSize"), Browsable(true)]
public CGSize CustomIntrinsicSize
{
get => _customIntrinsicSize;
set
{
_customIntrinsicSize = value;
InvalidateIntrinsicContentSize();
}
}
public override CGSize IntrinsicContentSize
{
get
{
return CustomIntrinsicSize;
}
}
void ClearSubViews()
{
foreach (var subView in Subviews)
{
subView.RemoveFromSuperview();
foreach (var constraint in subView.Constraints)
{
subView.RemoveConstraint(constraint);
}
}
}
void GetViewAndBind()
{
if (PlaceholderViewTypeKey == null || PlaceholderViewData == null)
return;
var placeholder = PlaceholderViewRegistry.Instance.ResolvePlaceholderView(PlaceholderViewTypeKey);
if (placeholder?.GetView(PlaceholderViewData) is UIViewController controller)
{
var view = controller.View;
view.TranslatesAutoresizingMaskIntoConstraints = false;
AddSubview(view);
view.LeadingAnchor.ConstraintEqualTo(this.LeadingAnchor).Active = true;
view.TopAnchor.ConstraintEqualTo(this.TopAnchor).Active = true;
view.TrailingAnchor.ConstraintEqualTo(this.TrailingAnchor).Active = true;
view.BottomAnchor.ConstraintEqualTo(this.BottomAnchor).Active = true;
}
}
}
}
然后我们有了PlaceholderViewRegistry,它实际上是一个简单的单例注册表,业务区域可以在其中注册其占位符视图。
internal class PlaceholderViewRegistry
{
private readonly Dictionary<string, Type> _mapping = new Dictionary<string, Type>();
private IServiceProvider _serviceProvider;
static PlaceholderViewRegistry()
{
Instance = new PlaceholderViewRegistry();
}
public static readonly PlaceholderViewRegistry Instance;
public void RegisterDescriptorType<T>(string key)
{
if (!_mapping.ContainsKey(key))
{
_mapping.Add(descriptorKey, typeof(T));
}
else
{
Logger.LogWarning($"Duplicate placeholder registered for key {key}. Ignoring it");
}
}
public IPlaceholderView ResolvePlaceholderView(string key)
{
if (!_mapping.ContainsKey(key))
{
Logger.LogWarning($"No placeholder registered for key {key}");
return null;
}
var type = _mapping[key];
return (IDescriptor)_serviceProvider.GetService(type);
}
public void SetServiceProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
}
CustomerNamePlaceholderViewResolver.cs:
public class CustomerNamePlaceholderViewResolver: IPlaceholderView
{
private Dictionary<string, MvxViewModel> _viewModelCache = new Dictionary<string,string>();
public object GetView(object data)
{
if (!(data is string customerId))
{
return null;
}
var vmRequest = MvxViewModelRequest.GetDefaultRequest(typeof(T));
var view = new MvxViewController().CreateViewControllerFor(typeof(CustomerNameView), vmRequest);
if (view == null)
{
throw new NullReferenceException(nameof(view));
}
view.OnViewCreate(() => GetViewModel(entityId));
return view;
}
private MvxViewModel GetViewModel(customerId)
{
if(!_viewModelCache.ContainsKey(customerId))
{
_viewModelCache.Add(customerId, new CustomerNameViewModel());
}
return _viewModelCache[customerId];
}
}
所以这或多或少有效,即我们有一个Orders列表,并且该订单由OrderTableViewCell.xib表示
它具有2个UILabel,用于订购号和订购日期。它还具有PlaceholderView自定义视图。在设计时,XIB内将PlaceholderView的Type键设置为“ CustomerNamePlaceholderViewKey”。
与此相关的MvvmCross绑定如下所示:
protected OrderTableViewCell(IntPtr handle) : base(handle)
{
this.DelayBind(() =>
{
var set = this.CreateBindingSet<OrderTableViewCell, OrderViewModel>();
set.Bind(OrderNumberLabel).To(vm => vm.OrderNumber);
set.Bind(OrderDateLabel).To(vm => vm.OrderDate);
set.Bind(CustomerNamePlaceholderView).For(v=>v.PlaceholderViewData).To(vm => vm.CustomerId);
set.Apply();
});
}
此方法可以在运行时正确替换视图。
但是我们注意到的是:
由于每当解析CustomerNamePlaceholderView时,它都会查看ViewModel是否被缓存,如果它被缓存,它将尝试重用它。
之所以要缓存视图模型,是为了提高效率,即John Smith仅有一个CustomerNameViewModel实例,任何想要使用它的视图都可以重用相同的ViewModel。
但是我们注意到的是,即使我们更改了此缓存的ViewModel的CustomerName,绑定的Label上似乎也没有任何更新,有时它可以工作,有时不能完全确定这是否可以做使用MvvmCross。
此外,如果您查看PlaceholderView类(我添加解析视图的方法),只需调用AddSubView。我知道存在UIViewController包含的概念,您可以在其中添加以调用addViewController等,但是在这种情况下是否有必要?