Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions ReactiveUI/Cocoa/AutoLayoutViewModelViewHost.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
#if UNIFIED && UIKIT
using NSView = UIKit.UIView;
#elif UNIFIED && COCOA
Expand All @@ -16,9 +17,10 @@ namespace ReactiveUI
/// up edge constraints for you from the parent view (the target)
/// to the child subview.
/// </summary>
public class AutoLayoutViewModelViewHost : ViewModelViewHost
[Obsolete("Use ViewModelViewHost instead. This class will be removed in a future release.")]
public class AutoLayoutViewModelViewHostLegacy : ViewModelViewHostLegacy
{
public AutoLayoutViewModelViewHost(NSView targetView) : base(targetView)
public AutoLayoutViewModelViewHostLegacy(NSView targetView) : base(targetView)
{
AddAutoLayoutConstraintsToSubView = true;
}
Expand Down
152 changes: 150 additions & 2 deletions ReactiveUI/Cocoa/ViewModelViewHost.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Reactive.Linq;
using ReactiveUI;
using System.Reactive.Disposables;

#if UNIFIED && UIKIT
using UIKit;
Expand All @@ -18,6 +19,152 @@

namespace ReactiveUI
{
public class ViewModelViewHost : ReactiveViewController
{
private readonly SerialDisposable currentView;
private readonly ObservableAsPropertyHelper<string> viewContract;
private IViewLocator viewLocator;
private NSViewController defaultContent;
private IReactiveObject viewModel;
private IObservable<string> viewContractObservable;

public ViewModelViewHost()
{
this.currentView = new SerialDisposable();
this.viewContract = this
.WhenAnyObservable(x => x.ViewContractObservable)
.ToProperty(this, x => x.ViewContract, scheduler: RxApp.MainThreadScheduler);

this.Initialize();
}

public IViewLocator ViewLocator
{
get { return viewLocator; }
set { this.RaiseAndSetIfChanged(ref viewLocator, value); }
}

public NSViewController DefaultContent
{
get { return defaultContent; }
set { this.RaiseAndSetIfChanged(ref defaultContent, value); }
}

public IReactiveObject ViewModel
{
get { return viewModel; }
set { this.RaiseAndSetIfChanged(ref viewModel, value); }
}

public IObservable<string> ViewContractObservable
{
get { return viewContractObservable; }
set { this.RaiseAndSetIfChanged(ref viewContractObservable, value); }
}

public string ViewContract
{
get { return this.viewContract.Value; }
set { ViewContractObservable = Observable.Return(value); }
}

private void Initialize()
{
var viewChange = Observable
.CombineLatest(
this.WhenAnyValue(x => x.ViewModel),
this.WhenAnyObservable(x => x.ViewContractObservable).StartWith((string)null),
(vm, contract) => new { ViewModel = vm, Contract = contract })
.Where(x => x.ViewModel != null);

var defaultViewChange = Observable
.CombineLatest(
this.WhenAnyValue(x => x.ViewModel),
this.WhenAnyValue(x => x.DefaultContent),
(vm, defaultContent) => new { ViewModel = vm, DefaultContent = defaultContent })
.Where(x => x.ViewModel == null && x.DefaultContent != null)
.Select(x => x.DefaultContent);

viewChange
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(
x =>
{
var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current;
var view = viewLocator.ResolveView(x.ViewModel, x.Contract);

if (view == null)
{
var message = string.Format("Unable to resolve view for \"{0}\"", x.ViewModel.GetType());

if (x.Contract != null)
{
message += string.Format(" and contract \"{0}\"", x.Contract.GetType());
}

message += ".";
throw new Exception(message);
}

var viewController = view as NSViewController;

if (viewController == null)
{
throw new Exception(
string.Format(
"Resolved view type '{0}' is not a '{1}'.",
viewController.GetType().FullName,
typeof(NSViewController).FullName));
}

view.ViewModel = x.ViewModel;
Adopt(this, viewController);

var disposables = new CompositeDisposable();
disposables.Add(viewController);
disposables.Add(Disposable.Create(() => Disown(viewController)));
currentView.Disposable = disposables;
});

defaultViewChange
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => Adopt(this, x));
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

if (disposing)
{
this.currentView.Dispose();
this.viewContract.Dispose();
}
}

private static void Adopt(UIViewController parent, UIViewController child)
{
parent.AddChildViewController(child);
parent.View.AddSubview(child.View);

// ensure the child view fills our entire frame
child.View.Frame = parent.View.Bounds;
child.View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
child.View.TranslatesAutoresizingMaskIntoConstraints = true;

child.DidMoveToParentViewController(parent);
}

private static void Disown(UIViewController child)
{
child.WillMoveToParentViewController(null);
child.View.RemoveFromSuperview();
child.RemoveFromParentViewController();
}
}



/// <summary>
/// ViewModelViewHost is a helper class that will connect a ViewModel
/// to an arbitrary NSView and attempt to load the View for the current
Expand All @@ -26,7 +173,8 @@ namespace ReactiveUI
/// This is a bit different than the XAML's ViewModelViewHost in the sense
/// that this isn't a Control itself, it only manipulates other Views.
/// </summary>
public class ViewModelViewHost : ReactiveObject
[Obsolete("Use ViewModelViewHost instead. This class will be removed in a later release.")]
public class ViewModelViewHostLegacy : ReactiveObject
{
/// <summary>
/// Gets or sets a value indicating whether this <see cref="ReactiveUI.Cocoa.ViewModelViewHost"/>
Expand All @@ -35,7 +183,7 @@ public class ViewModelViewHost : ReactiveObject
/// <value><c>true</c> if add layout contraints to sub view; otherwise, <c>false</c>.</value>
public bool AddAutoLayoutConstraintsToSubView { get; set; }

public ViewModelViewHost(NSView targetView)
public ViewModelViewHostLegacy(NSView targetView)
{
if (targetView == null) throw new ArgumentNullException("targetView");

Expand Down
6 changes: 3 additions & 3 deletions ReactiveUI/ObservableAsPropertyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ static ObservableAsPropertyHelper<TRet> observableToProperty<TObj, TRet>(
Expression<Func<TObj, TRet>> property,
TRet initialValue = default(TRet),
IScheduler scheduler = null)
where TObj : ReactiveObject
where TObj : IReactiveObject
{
Contract.Requires(This != null);
Contract.Requires(observable != null);
Expand Down Expand Up @@ -182,7 +182,7 @@ public static ObservableAsPropertyHelper<TRet> ToProperty<TObj, TRet>(
Expression<Func<TObj, TRet>> property,
TRet initialValue = default(TRet),
IScheduler scheduler = null)
where TObj : ReactiveObject
where TObj : IReactiveObject
{
return source.observableToProperty(This, property, initialValue, scheduler);
}
Expand All @@ -208,7 +208,7 @@ public static ObservableAsPropertyHelper<TRet> ToProperty<TObj, TRet>(
out ObservableAsPropertyHelper<TRet> result,
TRet initialValue = default(TRet),
IScheduler scheduler = null)
where TObj : ReactiveObject
where TObj : IReactiveObject
{
var ret = source.observableToProperty(This, property, initialValue, scheduler);

Expand Down
13 changes: 13 additions & 0 deletions ReactiveUI/Xaml/ViewModelViewHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ public IObservable<string> ViewContractObservable {
public static readonly DependencyProperty ViewContractObservableProperty =
DependencyProperty.Register("ViewContractObservable", typeof(IObservable<string>), typeof(ViewModelViewHost), new PropertyMetadata(Observable.Return(default(string))));

private string viewContract;

public string ViewContract
{
get { return this.viewContract; }
set { ViewContractObservable = Observable.Return(value); }
}

public IViewLocator ViewLocator { get; set; }

public ViewModelViewHost()
Expand Down Expand Up @@ -103,6 +111,11 @@ public ViewModelViewHost()
Content = view;
}));
});

this
.WhenAnyObservable(x => x.ViewContractObservable)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => this.viewContract = x);
}

static void somethingChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
Expand Down