From my answer to http://stackoverflow.com/questions/17816684/using-mvvmcross-how-can-i-bind-a-list-of-annotations-to-a-mapview/17834923#17834923
Subscribing to changing collections is one of the corner-stones of Data-Binding and relies on a little knowledge of the
Within the MvvmCross source, there are a few example classes which show how to subscribe to collections and their change notifications - e.g. MvxViewGroupExtensions.cs in Droid and MvxTableViewSource.cs in Touch
The core to the technique is to create an
The same type of approach applies for maps with multiple - but markers - although we don't yet have any helper classes for this.
Without actually having a Mac or iOS device to hand, here are roughly the steps I'd take to create a wrapper...
Assuming I had a Model object like:
Inside a ViewModel like:
With this done, then in the View we can create an annotation class for each House - e.g. something like:
We could then create a
To do this, we would give the manager methods to:
Overall, this might look something like: https://gist.github.com/slodge/6070386
Disclaimer: this code hasn't been compiled let alone run, but the approach is basically correct (I think)
The same basic approach should also work in Android - although in Android you'll also have to fight a battle against the setup - Ant, Google Play v2 and all that jazz.
If you wanted to do further map manipulations - e.g. changing the map center and zoom when a house is added, then this can obviously be done from within overrides of methods such as AddAnnotation within your manager.
The full gist is:
Subscribing to changing collections is one of the corner-stones of Data-Binding and relies on a little knowledge of the
INotifyCollectionChanged
interface.Within the MvvmCross source, there are a few example classes which show how to subscribe to collections and their change notifications - e.g. MvxViewGroupExtensions.cs in Droid and MvxTableViewSource.cs in Touch
The core to the technique is to create an
Adapter
or Source
object which listens for changes either in the whole list or to parts of the list and which takes action accordingly.The same type of approach applies for maps with multiple - but markers - although we don't yet have any helper classes for this.
Without actually having a Mac or iOS device to hand, here are roughly the steps I'd take to create a wrapper...
Assuming I had a Model object like:
public class House
{
public double Lat { get; set; }
public double Lng { get; set; }
public string Name { get; set; }
}
Inside a ViewModel like:
public class FirstViewModel : MvxViewModel
{
public ObservableCollection HouseList { get; set; }
}
With this done, then in the View we can create an annotation class for each House - e.g. something like:
public class HouseAnnotation : MKAnnotation
{
public HouseAnnotation(House house)
{
// Todo - the details of actually using the house here.
// in theory you could also data-bind to the house too (e.g. if it's location were to move...)
}
public override CLLocationCoordinate2D Coordinate { get; set; }
}
We could then create a
HouseAnnotationManager
who's responsibility would be to manage the translation of changes in the HouseList
being mapped to changes in the annotations being displayed on the map.To do this, we would give the manager methods to:
- Create a single annotation:
private MKAnnotation CreateAnnotation(House house) { return new HouseAnnotation(house); }
- Add an annotation to the map (and to a local lookup table)
private void AddAnnotationFor(House house) { var annotation = CreateAnnotation(house); _annotations[house] = annotation; _mapView.AddAnnotation(annotation); }
- Remove an annotation from the map (and from a local lookup table)
private void RemoveAnnotationFor(House house) { var annotation = _annotations[house]; _mapView.RemoveAnnotation(annotation); _annotations.Remove(house); }
- Do the same actions for lists:
private void AddAnnotations(IList newItems) { foreach (House house in newItems) { AddAnnotationFor(house); } } private void RemoveAnnotations(IList oldItems) { foreach (House house in oldItems) { RemoveAnnotationFor(house); } }
- Respond to
INotifyCollection
changes:
private void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: AddAnnotations(e.NewItems); break; case NotifyCollectionChangedAction.Remove: RemoveAnnotations(e.OldItems); break; case NotifyCollectionChangedAction.Replace: RemoveAnnotations(e.OldItems); AddAnnotations(e.NewItems); break; case NotifyCollectionChangedAction.Move: // not interested in this break; case NotifyCollectionChangedAction.Reset: ReloadAllAnnotations(); break; default: throw new ArgumentOutOfRangeException(); } }
- Respond to whole list changes:
// MvxSetToNullAfterBinding isn't strictly needed any more // - but it's nice to have for when binding is torn down [MvxSetToNullAfterBinding] public virtual IEnumerable
ItemsSource { get { return _itemsSource; } set { SetItemsSource(value); } } protected virtual void SetItemsSource(IEnumerable value) { if (_itemsSource == value) return; if (_subscription != null) { _subscription.Dispose(); _subscription = null; } _itemsSource = value; if (_itemsSource != null && !(_itemsSource is IList)) MvxBindingTrace.Trace(MvxTraceLevel.Warning, "Binding to IEnumerable rather than IList - this can be inefficient, especially for large lists"); ReloadAllAnnotations(); var newObservable = _itemsSource as INotifyCollectionChanged; if (newObservable != null) { _subscription = newObservable.WeakSubscribe(OnItemsSourceCollectionChanged); } }
_manager
field and can create and data-bind it as: _manager = new HouseAnnotationManager(myMapView);
var set = this.CreateBindingSet();
set.Bind(_manager).To(vm => vm.HouseList);
set.Apply();
Overall, this might look something like: https://gist.github.com/slodge/6070386
Disclaimer: this code hasn't been compiled let alone run, but the approach is basically correct (I think)
The same basic approach should also work in Android - although in Android you'll also have to fight a battle against the setup - Ant, Google Play v2 and all that jazz.
If you wanted to do further map manipulations - e.g. changing the map center and zoom when a house is added, then this can obviously be done from within overrides of methods such as AddAnnotation within your manager.
The full gist is:
No comments:
Post a Comment