Skip to content

Commit

Permalink
- Added non KVO dependent iOS specific, extendable ICreatesObservable…
Browse files Browse the repository at this point in the history
…ForProperty implementation supporting some core UIKit controls

- Added MonoTouch Linker XML definition file that should be added to MonoTouch projects in release configuration. Otherwise the Monotouch Linker might optimize away properties referenced soley through ReactiveUI (reflection-driven) data-binding
  • Loading branch information
Oliver Weichhold committed May 25, 2013
1 parent c948458 commit e4590ab
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 2 deletions.
40 changes: 40 additions & 0 deletions ReactiveUI.Platforms/Cocoa/Buildsupport/Linker.xml
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" ?>
<linker>
<assembly fullname="monotouch">
<type fullname="MonoTouch.UIKit.UIControl" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UITextField" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UITextView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UILabel" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UISlider" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UISearchBar" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIButton" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UITableView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIActionSheet" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIDatePicker" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIProgressView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UISwitch" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIActivityIndicatorView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIAlertView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIBarButtonItem" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIBarItem" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UICollectionView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIScrollView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIImage" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIImageView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UINavigationBar" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UINavigationItem" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIPageControl" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIPickerView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIRefreshControl" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UISegmentedControl" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIStepper" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UITabBar" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UITabBarItem" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UITableViewCell" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIToolbar" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UITouch" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIWebView" preserve="properties"></type>
<type fullname="MonoTouch.UIKit.UIWindow" preserve="properties"></type>
</assembly>
</linker>
37 changes: 37 additions & 0 deletions ReactiveUI.Platforms/Cocoa/UIKitObservableForProperty.cs
@@ -0,0 +1,37 @@
using System;
using ReactiveUI;
using System.Collections.Generic;
using MonoTouch.UIKit;
using System.Linq;
using MonoTouch.Foundation;
using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Reactive.Disposables;

namespace ReactiveUI.Cocoa
{
public class UIKitObservableForProperty : UIKitObservableForPropertyBase
{
public UIKitObservableForProperty ()
{
Register(typeof(UIControl), "Value", 9, (s, p)=> ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged));

Register(typeof(UITextField), "Text", 10, (s, p) => ObservableFromNotification(s, p, UITextField.TextFieldTextDidChangeNotification));

Register(typeof(UIDatePicker), "Date",10, (s, p)=> ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged));

Register(typeof(UISegmentedControl), "SelectedSegment", 10, (s, p)=> ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged));

Register(typeof(UISwitch), "On", 10, (s, p)=> ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged));

Register(typeof(UISegmentedControl), "SelectedSegment", 10, (s, p)=> ObservableFromUIControlEvent(s, p, UIControlEvent.ValueChanged));

// Warning: This will stomp the Control's delegate
Register(typeof(UITabBar), "SelectedItem", 10, (s, p) => ObservableFromEvent(s, p, "ItemSelected"));

// Warning: This will stomp the Control's delegate
Register(typeof(UISearchBar), "Text", 10, (s, p) => ObservableFromEvent(s, p, "TextChanged"));
}
}
}

155 changes: 155 additions & 0 deletions ReactiveUI.Platforms/Cocoa/UIKitObservableForPropertyBase.cs
@@ -0,0 +1,155 @@
using System;
using ReactiveUI;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using System.Reactive.Disposables;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;

namespace ReactiveUI.Cocoa
{
public abstract class UIKitObservableForPropertyBase :
ICreatesObservableForProperty
{
#region ICreatesObservableForProperty implementation

public int GetAffinityForObject(Type type, bool beforeChanged = false)
{
if(beforeChanged)
return 0;

var info = config.Keys
.Where(x=> x.IsAssignableFrom(type))
.Where(x=> config[x].Values.Any())
.Select(x=> config[x].Values.OrderByDescending(y=> y.Affinity).FirstOrDefault())
.OrderByDescending(x=> x.Affinity)
.FirstOrDefault();

if(info == null)
return 0;

return info.Affinity;
}

public IObservable<IObservedChange<object, object>> GetNotificationForProperty(object sender, string propertyName, bool beforeChanged = false)
{
var type = sender.GetType();

if(beforeChanged)
return Observable.Never<IObservedChange<object, object>>();

Dictionary<string, ObservablePropertyInfo> typeProperties;
if(!config.TryGetValue(type, out typeProperties))
throw new NotSupportedException(string.Format("Notifications for {0} are not supported", type.Name));

ObservablePropertyInfo info;
if(!typeProperties.TryGetValue(propertyName, out info))
throw new NotSupportedException(string.Format("Notifications for {0}.{1} are not supported", type.Name, propertyName));

return info.CreateObservable((NSObject) sender, propertyName);
}

#endregion

internal class ObservablePropertyInfo
{
public int Affinity;
public Func<NSObject, string, IObservable<IObservedChange<object, object>>> CreateObservable;
}

/// <summary>
/// Configuration map
/// </summary>
readonly Dictionary<Type, Dictionary<string, ObservablePropertyInfo>> config =
new Dictionary<Type, Dictionary<string, ObservablePropertyInfo>>();

/// <summary>
/// Registers an observable factory for the specified type and property.
/// </summary>
/// <param name="type">Type.</param>
/// <param name="property">Property.</param>
/// <param name="createObservable">Create observable.</param>
protected void Register(Type type, string property, int affinity, Func<NSObject, string, IObservable<IObservedChange<object, object>>> createObservable)
{
Dictionary<string, ObservablePropertyInfo> typeProperties;
if(!config.TryGetValue(type, out typeProperties))
{
typeProperties = new Dictionary<string, ObservablePropertyInfo>();
config[type] = typeProperties;
}

var info = new ObservablePropertyInfo { Affinity = affinity, CreateObservable = createObservable };
typeProperties[property] = info;
}

/// <summary>
/// Creates an Observable for a UIControl Event
/// </summary>
/// <returns>An observable</returns>
/// <param name="sender">The sender</param>
/// <param name="propertyName">The property name </param>
/// <param name="evt">The control event to listen for</param>
protected static IObservable<IObservedChange<object, object>> ObservableFromUIControlEvent(NSObject sender, string propertyName, UIControlEvent evt)
{
return Observable.Create<IObservedChange<object, object>>(subj =>
{
var control = (UIControl) sender;
EventHandler handler = (s,e)=>
{
subj.OnNext(new ObservedChange<object, object>() { Sender = sender, PropertyName = propertyName });
};
control.AddTarget(handler, evt);
return Disposable.Create(() =>
{
control.RemoveTarget(handler, evt);
});
});
}

/// <summary>
/// Creates an Observable for a NSNotificationCenter notification
/// </summary>
/// <returns>The from notification.</returns>
/// <param name="sender">Sender.</param>
/// <param name="propertyName">Property name.</param>
/// <param name="notification">Notification.</param>
protected static IObservable<IObservedChange<object, object>> ObservableFromNotification(NSObject sender, string propertyName, NSString notification)
{
return Observable.Create<IObservedChange<object, object>>(subj =>
{
var handle = NSNotificationCenter.DefaultCenter.AddObserver (notification, (e)=>
{
subj.OnNext(new ObservedChange<object, object>() { Sender = sender, PropertyName = propertyName });
}, sender);
return Disposable.Create(() =>
{
NSNotificationCenter.DefaultCenter.RemoveObserver(handle);
});
});
}

/// <summary>
/// Creates an Observable for a NSNotificationCenter notification
/// </summary>
/// <returns>The from notification.</returns>
/// <param name="sender">Sender.</param>
/// <param name="propertyName">Property name.</param>
/// <param name="notification">Notification.</param>
protected static IObservable<IObservedChange<object, object>> ObservableFromEvent(NSObject sender, string propertyName, string eventName)
{
return Observable.Create<IObservedChange<object, object>>(subj =>
{
return Observable.FromEventPattern(sender, eventName).Subscribe((e) =>
{
subj.OnNext(new ObservedChange<object, object>() { Sender = sender, PropertyName = propertyName });
});
});
}
}
}

2 changes: 2 additions & 0 deletions ReactiveUI.Platforms/ReactiveUI.Cocoa_Monotouch.csproj
Expand Up @@ -71,6 +71,8 @@
<Compile Include="PlatformUnitTestDetector.cs" />
<Compile Include="Registrations.cs" />
<Compile Include="Cocoa\PlatformOperations.cs" />
<Compile Include="Cocoa\UIKitObservableForProperty.cs" />
<Compile Include="Cocoa\UIKitObservableForPropertyBase.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ReactiveUI\ReactiveUI_Monotouch.csproj">
Expand Down
3 changes: 2 additions & 1 deletion ReactiveUI.Platforms/Registrations.cs
Expand Up @@ -52,7 +52,8 @@ public void Register(Action<Func<object>, Type> registerFunction)
#endif

#if COCOA
registerFunction(() => new KVOObservableForProperty(), typeof(ICreatesObservableForProperty));
registerFunction(() => new UIKitObservableForProperty(), typeof(ICreatesObservableForProperty));
registerFunction(() => new KVOObservableForProperty(), typeof(ICreatesObservableForProperty));
registerFunction(() => new CocoaDefaultPropertyBinding(), typeof(IDefaultPropertyBindingProvider));
registerFunction(() => new TargetActionCommandBinder(), typeof(ICreatesCommandBinding));
#endif
Expand Down
2 changes: 1 addition & 1 deletion ReactiveUI/RxApp.cs
Expand Up @@ -41,7 +41,7 @@ public static class RxApp
{
public static void Initialize()
{
_TaskpoolScheduler = Scheduler.TaskPool;
_TaskpoolScheduler = TaskPoolScheduler.Default;

DefaultExceptionHandler = Observer.Create<Exception>(ex => {
// NB: If you're seeing this, it means that an
Expand Down

0 comments on commit e4590ab

Please sign in to comment.