Issue
I have been trying to make my previously developed map cluster friendly, and I came across some cool example using the nugget ClusterKit and everything seems to work fine, clustering declustering wise, however some of my already loaded pins are reverting to the default red pin. My Pins get added correctly and they all cluster just fine, but when my map region changes and then goes back to a previously loaded section (with loaded pins) They almost always all revert to a red iOS pin After doing some more digging, initially my annotation is passed to GetViewForAnnotation as type Xamarin.iOS.ClusterKit.CKCluster
(good) But then after that "initial load" the pin will be red, so it no longer is of type CKCluster it's now (default MKAnnotationView) This then removes all clustering ability.
Map Renderer
[assembly: ExportRenderer(typeof(CustomMap), typeof(iOSMapRenderer))]
ClusterMap _clusterMap;
MKMapView Map => Control as MKMapView;
CustomMap PCLMap => Element as CustomMap;
_testClustering = true;
protected override void OnElementChanged(ElementChangedEventArgs<CustomMap> e)
{
base.OnElementChanged(e);
if (e.OldElement != null && Map != null)
...
if (e.NewElement == null) return;
if (_testClustering)
{
_clusterMap = new ClusterMap(Map);
}
if (Map == null) return;
Map.GetViewForAnnotation = GetViewForAnnotation;
Map.DidSelectAnnotationView += OnDidSelectAnnotationView;
Map.RegionChanged += OnMapRegionChanged;
...
UpdatePins(true);
PCLMap.PropertyChanged += OnMapPropertyChanged;
}
void OnMapPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == CustomMap.Pins)
UpdatePins();
else if(e.PropertyName == CustomMap.SelectedPin)
SetSelectedPin();
else if(e.PropertyName == CustomMap.MapRegion)
UpdateMapRegion();
}
void UpdatePins(firstTime)
{
if (_testClustering)
{
_clusterMap.ClusterManager
.RemoveAnnotations(_clusterMap.ClusterManager.Annotations);
}
else
{
Map.RemoveAnnotations(Map.Annotations);
}
foreach (var i in PCLMap.Pins)
{
i.PropertyChanged -= OnPinPropertyChanged;
AddPin(i);
}
if (firstTime)
{
if (PCLMap.Pins is INotifyCollectionChanged observable)
{
observable.CollectionChanged += OnCollectionChanged;
}
}
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (CustomPin pin in e.NewItems)
{
AddPin(pin);
}
}
else if (e.Action == NotifyCollectionChangedAction.Reset)
{
IEnumerable<IMKAnnotation> annotations = _testClustering ? _clusterMap.ClusterManager.Annotations : Map.Annotations;
foreach (var annotation in annotations.OfType<MyMapAnnotation>())
{
annotation.CustomPin.PropertyChanged -= OnPinPropertyChanged;
}
UpdatePins(false);
}
}
void AddPin(CustomPin pin)
{
var annotation = new MyMapAnnotation(pin);
if (_testClustering)
{
_clusterMap.ClusterManager.AddAnnotation(annotation);
}
else
{
Map.AddAnnotation(annotation);
}
}
public virtual MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
var clusterAnnotation = annotation as CKCluster;
var createInitialClusterView = false;
MKAnnotationView annotationView = null;
MyMapAnnotation customAnnotation = null;
if (clusterAnnotation == null)
{
//for non clustering map --
//but gets called after initial load because pin is no
//Longer recognized as CKCluster
customAnnotation = annotation as MyMapAnnotation;
}
else
{
if (clusterAnnotation.Count > 1)
{
var clusterPin = PCLMap.GetClusteredPin?.Invoke(null, clusterAnnotation.Annotations.OfType<MyMapAnnotation>().Select(i => i.CustomPin));
if (clusterPin == null)
{
createInitialClusterView = true;
}
else
{
customAnnotation = new MyMapAnnotation(clusterPin);
}
}
else
{
customAnnotation = clusterAnnotation.FirstAnnotation as MyMapAnnotation;
}
}
// IF we need to create a new cluster
if (createInitialClusterView)
{
annotationView = new ClusterAnnotationView(clusterAnnotation,
else
{
if (customAnnotation == null)
{
return null;
}
if (annotationView == null)
{
annotationView = new MKAnnotationView(customAnnotation,
AnnotationIdentifier);
annotationView.Layer.AnchorPoint = new
CGPoint(customAnnotation.CustomPin.AnchorPoint.X,
}
else
{
annotationView.Annotation = customAnnotation;
}
annotationView.CanShowCallout = customAnnotation.CustomPin.ShowCallout;
SetAnnotationViewVisibility(annotationView, customAnnotation.CustomPin);
UpdateImage(annotationView, customAnnotation.CustomPin);
}
return annotationView; // returning default MKAnnotationView
}
ClusterMap
//initializing example
public class ClusterMap : CKMap
{
MKMapView _mkMapView;
public ClusterMap(MKMapView mapView)
{
_mkMapView = mapView;
}
CKClusterManager mapView;
public override CKClusterManager ClusterManager => LazyInitializer.EnsureInitialized(ref _clusterManager, () =>
{
_clusterManager = new CKClusterManager();
_clusterManager.Map = this;
_clusterManager.Algorithm = new CKNonHierarchicalDistanceBasedAlgorithm();
return _clusterManager;
});
public override MKMapRect VisibleMapRect => _mkMapView.VisibleMapRect;
public override void AddClusters(CKCluster[] clusters)
{
_mkMapView.AddAnnotations(clusters);
}
public override void DeselectCluster(CKCluster cluster, bool animated)
{
if (! _mkMapView.SelectedAnnotations.Contains(cluster)) return;
_mkMapView.DeselectAnnotation(cluster, animated);
}
public override void PerformAnimations(CKClusterAnimation[] animations, Action<bool> completion)
{
foreach (var animation in animations)
{
animation.Cluster.Coordinate = animation.From;
}
//... performs clustering animation
}
public override void RemoveClusters(CKCluster[] clusters)
{
_mkMapView.RemoveAnnotations(clusters);
}
public override void SelectCluster(CKCluster cluster, bool animated)
{
//TODO Zoom in
}
public MKMapRect MKMapRectByAddingPoint(MKMapRect rect, MKMapPoint point)
{
return MKMapRect.Union(rect, new MKMapRect() { Origin = point, Size = MKMapRect.Null.Size });
}
MyMapAnnotation
internal class MyMapAnnotation : MKAnnotation
{
CLLocationCoordinate2D _coordinate;
readonly CustomPin _formsPin;
public override string Title
{
get
{
return _formsPin.Label;
}
}
public override CLLocationCoordinate2D Coordinate
{
get { return _coordinate; }
}
public CustomPin CustomPin
{
get { return _formsPin; }
}
public override void SetCoordinate(CLLocationCoordinate2D value)
{
if (!value.IsValid()) return;
_formsPin.Position = value.ToPosition();
_coordinate = value;
}
public void SetCoordinateOriginal(CLLocationCoordinate2D value)
{
SetCoordinate(value);
}
public MyMapAnnotation(CustomPin pin)
{
_formsPin = pin;
_coordinate = pin.Position.ToLocationCoordinate();
_formsPin.PropertyChanged += FormsPinPropertyChanged;
}
public void SetCoordinateInternal(CLLocationCoordinate2D value, bool triggerObserver)
{
if (triggerObserver)
WillChangeValue("coordinate");
SetCoordinate(value);
if (triggerObserver)
DidChangeValue("coordinate");
}
System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(CustomPin.Label))
{
WillChangeValue("title");
DidChangeValue("title");
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
_formsPin.PropertyChanged -= FormsPinPropertyChanged;
}
}
Solution
Figured this out. Turns on the issue lied within my GetViewForAnnotation. My annotation was being encapsulated into a AnnotationWrapper instead of an annotation. This was then preventing me from casting into anything in my case into my CustomAnnotationView. I ended up referencing the NSObject and that referenced to a CKCluster Item (original type). Then I did some tweaks to the flow of the method but I was able to get what I needed and it all works fine now. I had some help but I will hopefully no longer need help with Xamarin now that it will be deprecated soon. Amen to that.
Answered By - uzzum07
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.