【问题标题】:UIAlertController binding in mvvmcrossmvvmcross 中的 UIAlertController 绑定
【发布时间】:2014-10-23 17:33:46
【问题描述】:

我正在尝试将自定义命令绑定到 UIAlertController (ios 8)。我在导航栏中有一个按钮,并附有一个操作表。当用户单击导航栏按钮时,将出现操作表。当用户单击操作表按钮时,他必须被重定向到另一个视图/视图模型。

我的视图模型代码:

public ICommand AddPhonecallCommand
{
    get
    {
        return new MvxCommand(() => ShowViewModel<AddPhonecallViewModel>();
    }
}

public ICommand AddMeetingCommand
{
    get
    {
        return new MvxCommand(() => ShowViewModel<AddMeetingViewModel>();
    }
}

我的查看代码:

var actionSheet = UIAlertController.Create("Add a new...", null, UIAlertControllerStyle.ActionSheet);

actionSheet.AddAction(UIAlertAction.Create("Phone call", UIAlertActionStyle.Default, null));
actionSheet.AddAction(UIAlertAction.Create("Meeting", UIAlertActionStyle.Default, null));

var rightNavButton = new UIBarButtonItem("Add", UIBarButtonItemStyle.Plain, (s, e) =>
            {
                this.PresentViewController(actionSheet, true, null);
            });

因此,操作表中的每个按钮都应重定向到特定的视图模型。但似乎操作表中的按钮不是 UIButtons。所以我需要以某种方式将 UIAlertAction 绑定到 ICommand。

set.Bind(...).For(...).To(vm => vm.AddPhonecallCommand);

我应该用什么代替点?


我在视图中没有视图模型的实例。我正在使用这种语法:

var set = this.CreateBindingSet<MainView, MainViewModel>();
set.Bind(source).To(vm => vm.Events); // binding IEnumerable
set.Apply();

没有直接实例化视图模型。该框架为我做了所有肮脏的工作。所以,如果我在写

set.Bind(rightNavButton).To(vm => vm.AddPhonecallCommand);  // binding the Clicked event

一切正常。但是如果我想写这样的东西

var actionSheetButton = UIAlertAction.Create("Phone call", UIAlertActionStyle.Default, null));
...
set.Bind(actionSheetButton).To(vm => vm.AddPhonecallCommand);  // attempt to bind

什么都没有发生。可能是因为我们在UIAlertAction 中根本没有合适的活动。

【问题讨论】:

  • 我不确定我是否理解您想要实现的“绑定”。您能否编辑问题以解释您尝试绑定的 ViewModel 属性和/或命令的类型。 (我真的不确定您是在尝试绑定导航栏按钮、操作表按钮还是其他东西 - 抱歉!)
  • 我已经编辑了问题,希望对您有所帮助。

标签: ios binding mvvmcross


【解决方案1】:

如果您只想执行这些命令,那么一个简单的方法就是使用:

 actionSheet.AddAction(UIAlertAction.Create("Phone call", UIAlertActionStyle.Default, (s,e) => {
     MyViewModel.AddPhoneCallCommand.Execute(null);
 }));

如果您想要“完全绑定” - 包括观察视图模型更改和观察 CanExecute 更改,那么也可以这样做 - 您需要持久化 UIAlertAction 实例并将它们上的属性绑定到 ViewModel。

【讨论】:

  • 很明显,它可以工作,但它仍然是一个 hack。我必须以某种方式实例化视图模型(我想避免)。 UIAlertAction 的主要问题 - 根据Xamarin docs,它不公开任何事件。所以也许我必须把这个类包起来,自己实现一个事件......
  • 如果您在视图中调用它,那么您可以访问 ViewModel - 您不必实例化一个新的?也许我误解了代码。如果你需要,这种事情也可以在 Prism 风格的 Interaction 中完成 - 就像在 Droid 示例中一样 github.com/slodge/BindingTalk/blob/master/BindingTalk.Droid/… (抱歉,想不出我在任何地方发布了 iOS 示例)
  • 我添加了一些额外的解释。我会看看你的例子。感谢您的帮助!
  • 我不相信有任何属性或方法可用于绑定UIAlertAction - 请参阅iosapi.xamarin.com/?link=T%3aUIKit.UIAlertAction%2f* - 一旦创建,我以后看不到更改操作吗?
【解决方案2】:

我找到了合适的解决方案。我为 UIAlertAction 创建了一个代理类:

public class UIAlertActionBindable : UIAlertAction
{
    public UIAlertAction AlertAction;

    public UIAlertActionBindable(string title, UIAlertActionStyle style)
    {
        AlertAction = UIAlertAction.Create(title, style, action =>
            {
                if(Clicked != null)
                {
                    Clicked(this, null);
                }
            });
    }

    public event EventHandler Clicked;
}

然后我创建了一个自定义绑定类

public class UIAlertActionBinding : MvxTargetBinding
{
    private readonly UIAlertActionBindable _view;
    private IMvxCommand _command;

    public UIAlertActionBinding(UIAlertActionBindable view) 
        : base(view)
    {
        _view = view;
        _view.Clicked += OnClicked;
    }

    void OnClicked(object sender, EventArgs e)
    {
        if (_command != null)
        {
            _command.Execute();
        }
    }

    public override void SetValue(object value)
    {
        _command = (IMvxCommand)value;
    }

    protected override void Dispose(bool isDisposing)
    {
        if (isDisposing)
        {
            _view.Clicked -= OnClicked;
        }
        base.Dispose(isDisposing);   
    }

    public override Type TargetType
    {
        get
        {
            return typeof(IMvxCommand);
        }
    }

    public override Cirrious.MvvmCross.Binding.MvxBindingMode DefaultMode
    {
        get
        {
            return MvxBindingMode.OneWay;
        }
    }
}

并修改了 Setup.cs:

protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
    {
        base.FillTargetFactories(registry);

        registry.RegisterFactory(
            new MvxCustomBindingFactory<UIAlertActionBindable>("Click", aab => new UIAlertActionBinding(aab)));
    }

视图中的代码如下:

var actionSheet = UIAlertController.Create("Add a new...", null, UIAlertControllerStyle.ActionSheet);
var meetingActionButton = new UIAlertActionBindable("Meeting", UIAlertActionStyle.Default);
actionSheet.AddAction(meetingActionButton.AlertAction);
...
set.Bind(meetingActionButton).For("Click").To(vm => vm.AddMeetingCommand);

一切都像魅力一样。

【讨论】:

  • 你用 UIAlertActionBinding 的一个 (AlertAction = UIAlertAction.Create(...)) 包装了 UIAlertAction 的实例,但是为什么还要继承 UIAlertAction 呢?
【解决方案3】:

为什么不创建一个跨平台服务来处理这个问题?这样,您也可以更轻松地从视图模型中调用窗口,并且可以在 android、winphone 和 iOS 中使用它。

public class Option {
    public string Text { get; set; }
    public Action Action { get; set; }

    public Option(string text, Action action) {
        Text = text;
        Action = action;
    }
}

public interface IDialogService {
    void ActionSheet(string title, params Option[] option);
}

public class iOSDialogService : IDialogService {

    public void ActionSheet(string title, params Option[] options) {
        var sheet = UIAlertController.Create(title ?? String.Empty, String.Empty, UIAlertControllerStyle.ActionSheet);
        foreach (var opt in options) {
            sheet.AddAction(UIAlertAction.Create(opt.Text, UIAlertActionStyle.Default, x => {
                opt.Action();
            }))
        );

    }


    private void Present(UIAlertController controller) {
        this.Dispatch(() =>  {
            var top = GetTopViewController();
            var po = controller.PopoverPresentationController;
            if (po != null) {
                po.SourceView = top.View;
                var h = (top.View.Frame.Height / 2) - 400;
                var v = (top.View.Frame.Width / 2) - 300;
                po.SourceRect = new RectangleF(v, h, 0, 0);
                po.PermittedArrowDirections = UIPopoverArrowDirection.Any;
            }
            top.PresentViewController(controller, true, null);
        });
     }


     private static UIViewController GetTopViewController() {
        var root = UIApplication
            .SharedApplication
            .Windows
            .Reverse()
            .FirstOrDefault(x => 
                x.WindowLevel == UIWindowLevel.Normal && 
                !x.Hidden
            )
            .RootViewController;

        var tabs = root as UITabBarController;
        if (tabs != null)
            return tabs.SelectedViewController;

        var nav = root as UINavigationController;
        if (nav != null)
            return nav.VisibleViewController;

        if (root.PresentedViewController != null)
            return root.PresentedViewController;

        return root;
     }
}

现在调用它,只需将此服务注入您的视图模型:

public IMvxCommand Choice {
    get {
        return new MvxCommand(() => dialogService.ActionSheet(
            "Action Sheet",
            new Option("Button1", () => { .. do something here }),
            new Option("Button2", () => { .. do something else here })   
        ));
    }
}

如需更完整的解决方案,请在 nuget 上搜索“MvvmCross User Dialogs”或去看看here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-18
    • 2013-10-17
    • 2014-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多