UITableViewCell的自定义占位符/容器视图

时间:2019-08-28 10:25:57

标签: ios xamarin.ios uikit mvvmcross

我对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查找,以解析视图。

回到订购示例。

“客户”项目库将提供“客户名称”的替代视图。

其中包含以下文件:

  • CustomerNameView.storyboard-仅仅是一个UILabel,约束是其对Superview的CenterY约束,以及Leading和Trailing约束。
  • CustomerNameView.cs-具有简单的MvvmCross绑定代码的ViewController,用于将UILabel绑定到ViewModel的CustomerName属性。
  • CustomerNameViewModel.cs-源自MvxViewModel,具有CustomerName字符串属性。
  • CustomerNamePlaceholderViewResolver.cs-这将使用MvvmCross方法创建相应的ViewController(CustomerNameView),然后通过Resolve方法返回ViewController。
  • Initialization.cs-这几乎获取了PlaceholderViewRegistry.Instance并使用键“ CustomerNamePlaceholderViewKey”调用RegisterDescriptorType

我认为代码可以更好地说明这一点。


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等,但是在这种情况下是否有必要?

0 个答案:

没有答案