I have the following code in iOS renderer:
public class TabbedPageRenderer : TabbedRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
try
{
var tabbarController = (UITabBarController)this.ViewController;
if (null != tabbarController)
{
tabbarController.ViewControllerSelected += OnTabBarReselected;
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
void OnTabBarReselected(object sender, UITabBarSelectionEventArgs e)
{
var tabs = Element as TabbedPage;
var playTab = tabs.Children[4];
if (TabBar.SelectedItem.Title == "Play")
{
if (tabs != null)
{
playTab.Title = "Pause";
playTab.Icon = "ionicons_2_0_1_pause_outline_22.png";
}
App.pauseCard = false;
}
else
{
if (tabs != null)
{
playTab.Title = "Play";
playTab.Icon = "ionicons_2_0_1_play_outline_25.png";
}
App.pauseCard = true;
}
}
}
What this do is let the user pause/play a timer running in a page. When opening the app, Home tab will be open so the Play icon is displayed. But when switching to the Play tab, by default the timer is running so the Pause title and icon is displayed.
The code above works perfectly for iOS. But I am still lost in Android. I have tried the following code for Android:
public class MyTabbedPageRenderer: TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
var tabs = Element as TabbedPage;
var playTab = tabs.Children[4];
var selectedPosition = tab.Position;
if(selectedPosition == 4)
{
if (playTab.Title == "Play")
{
if (tabs != null)
{
playTab.Title = "Pause";
playTab.Icon = "ionicons_2_0_1_pause_outline_22.png";
}
App.pauseCard = false;
}
else
{
if (tabs != null)
{
playTab.Title = "Play";
playTab.Icon = "ionicons_2_0_1_play_outline_25.png";
}
App.pauseCard = true;
}
}
}
}
Obviously this would only work when reselecting the tab. Would really appreciate if someone could point me to the right direction here.
Like #G.hakim, you also need add TabLayout.IOnTabSelectedListener.OnTabSelected method, and it will be same as OnTabReselected:
void TabLayout.IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab)
{
var tabs = Element as TabbedPage;
var playTab = tabs.Children[4];
var selectedPosition = tab.Position;
if(selectedPosition == 4)
{
if (playTab.Title == "Play")
{
if (tabs != null)
{
playTab.Title = "Pause";
playTab.Icon = "ionicons_2_0_1_pause_outline_22.png";
}
App.pauseCard = false;
}
else
{
if (tabs != null)
{
playTab.Title = "Play";
playTab.Icon = "ionicons_2_0_1_play_outline_25.png";
}
App.pauseCard = true;
}
}
}
Related
I'm having a Xamarin Forms application where I could successfully add banner ads and native ads for the android with custom renderers using Google AdMob NuGet. What I'm trying to achieve here is to make them work on IOS, I managed to do that (I suppose as I'm receiving test ads) with banner but when it comes to native, I get unified native ad and it never renders...
I'm using it inside of a custom listview data template which I thought it might cause an issue, so I tried using it in the page itself but no luck, same issue.
Here is my IOS renderer so far:
[assembly: ExportRenderer(typeof(NativeAdMobUnit), typeof(NativeAdMobUnitRenderer))]
namespace Example.iOS.Components
{
public class NativeAdMobUnitRenderer : ViewRenderer<NativeAdMobUnit, NativeExpressAdView>
{
protected override void OnElementChanged(ElementChangedEventArgs<NativeAdMobUnit> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
return;
if (e.OldElement == null)
{
CreateAdView(this, e.NewElement.AdUnitId);
}
}
private void CreateAdView(NativeAdMobUnitRenderer renderer,String AdUnitId)
{
if (Element == null) return;
var loader = new AdLoader(
AdUnitId,
GetVisibleViewController(),
new AdLoaderAdType[] { AdLoaderAdType.UnifiedNative },
new AdLoaderOptions[] {
new NativeAdViewAdOptions {PreferredAdChoicesPosition = AdChoicesPosition.TopRightCorner}
});
var request = Request.GetDefaultRequest();
request.TestDevices = new string[] { Request.SimulatorId };
try
{
loader.Delegate = new MyAdLoaderDelegate(renderer);
Device.BeginInvokeOnMainThread(() => {
loader.LoadRequest(Request.GetDefaultRequest());
});
}
catch(Exception ex)
{
}
}
private UIViewController GetVisibleViewController()
{
var windows = UIApplication.SharedApplication.Windows;
foreach (var window in windows)
{
if (window.RootViewController != null)
{
return window.RootViewController;
}
}
return null;
}
private class MyAdLoaderDelegate : NSObject, IUnifiedNativeAdLoaderDelegate
{
private readonly NativeAdMobUnitRenderer _renderer;
public MyAdLoaderDelegate(NativeAdMobUnitRenderer renderer)
{
_renderer = renderer;
}
public void DidReceiveUnifiedNativeAd(AdLoader adLoader, UnifiedNativeAd nativeAd)
{
Debug.WriteLine("DidReceiveUnifiedNativeAd");
}
public void DidFailToReceiveAd(AdLoader adLoader, RequestError error)
{
Debug.WriteLine("DidFailToReceiveAd");
}
public void DidFinishLoading(AdLoader adLoader)
{
Debug.WriteLine("DidFinishLoading");
}
}
}
}
And that's the same but for Android which is working as expected:
[assembly: ExportRenderer(typeof(NativeAdMobUnit), typeof(NativeAdMobUnitRenderer))]
namespace Example.Droid.Renderers
{
public class NativeAdMobUnitRenderer : ViewRenderer
{
public NativeAdMobUnitRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
if (Control == null)
{
NativeAdMobUnit NativeAdUnit = (NativeAdMobUnit)Element;
var adLoader = new AdLoader.Builder(Context, NativeAdUnit.AdUnitId);
var listener = new UnifiedNativeAdLoadedListener();
listener.OnNativeAdLoaded += (s, ad) =>
{
try
{
var root = new UnifiedNativeAdView(Context);
var inflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
var adView = (UnifiedNativeAdView)inflater.Inflate(Resource.Layout.ad_unified, root);
populateUnifiedNativeAdView(ad, adView);
SetNativeControl(adView);
}
catch
{
}
};
adLoader.ForUnifiedNativeAd(listener);
var requestBuilder = new AdRequest.Builder();
adLoader.Build().LoadAd(requestBuilder.Build());
}
}
private void populateUnifiedNativeAdView(UnifiedNativeAd nativeAd, UnifiedNativeAdView adView)
{
adView.MediaView = adView.FindViewById<MediaView>(Resource.Id.ad_media);
// Set other ad assets.
adView.HeadlineView = adView.FindViewById<TextView>(Resource.Id.ad_headline);
adView.BodyView = adView.FindViewById<TextView>(Resource.Id.ad_body);
adView.CallToActionView = adView.FindViewById<TextView>(Resource.Id.ad_call_to_action);
adView.IconView = adView.FindViewById<ImageView>(Resource.Id.ad_app_icon);
adView.PriceView = adView.FindViewById<TextView>(Resource.Id.ad_price);
adView.StarRatingView = adView.FindViewById<RatingBar>(Resource.Id.ad_stars);
adView.StoreView = adView.FindViewById<TextView>(Resource.Id.ad_store);
adView.AdvertiserView = adView.FindViewById<TextView>(Resource.Id.ad_advertiser);
// The headline and mediaContent are guaranteed to be in every UnifiedNativeAd.
((TextView)adView.HeadlineView).Text = nativeAd.Headline;
// These assets aren't guaranteed to be in every UnifiedNativeAd, so it's important to
// check before trying to display them.
if (nativeAd.Body == null)
{
adView.BodyView.Visibility = ViewStates.Invisible;
}
else
{
adView.BodyView.Visibility = ViewStates.Visible;
((TextView)adView.BodyView).Text = nativeAd.Body;
}
if (nativeAd.CallToAction == null)
{
adView.CallToActionView.Visibility = ViewStates.Invisible;
}
else
{
adView.CallToActionView.Visibility = ViewStates.Visible;
((Android.Widget.Button)adView.CallToActionView).Text = nativeAd.CallToAction;
}
if (nativeAd.Icon == null)
{
adView.IconView.Visibility = ViewStates.Gone;
}
else
{
((ImageView)adView.IconView).SetImageDrawable(nativeAd.Icon.Drawable);
adView.IconView.Visibility = ViewStates.Visible;
}
if (string.IsNullOrEmpty(nativeAd.Price))
{
adView.PriceView.Visibility = ViewStates.Gone;
}
else
{
adView.PriceView.Visibility = ViewStates.Visible;
((TextView)adView.PriceView).Text = nativeAd.Price;
}
if (nativeAd.Store == null)
{
adView.StoreView.Visibility = ViewStates.Invisible;
}
else
{
adView.StoreView.Visibility = ViewStates.Visible;
((TextView)adView.StoreView).Text = nativeAd.Store;
}
if (nativeAd.StarRating == null)
{
adView.StarRatingView.Visibility = ViewStates.Invisible;
}
else
{
((RatingBar)adView.StarRatingView).Rating = nativeAd.StarRating.FloatValue();
adView.StarRatingView.Visibility = ViewStates.Visible;
}
if (nativeAd.Advertiser == null)
{
adView.AdvertiserView.Visibility = ViewStates.Invisible;
}
else
{
((TextView)adView.AdvertiserView).Text = nativeAd.Advertiser;
adView.AdvertiserView.Visibility = ViewStates.Visible;
}
adView.SetNativeAd(nativeAd);
}
}
public class UnifiedNativeAdLoadedListener : AdListener, UnifiedNativeAd.IOnUnifiedNativeAdLoadedListener
{
public void OnUnifiedNativeAdLoaded(UnifiedNativeAd ad)
{
OnNativeAdLoaded?.Invoke(this, ad);
}
public EventHandler<UnifiedNativeAd> OnNativeAdLoaded { get; set; }
}
}
Is there any way to make a popup with a few questions + a few answers to choose from after pressing the added pin on the map?
How to do that? What I mean most is how to combine the popup with the pin (in xamarin.forms)
I apologize for my weak language
My pin code for adding pins:
private async void OnButton(object sender, EventArgs e)
{
Pin pin = new Pin
{
Label = "Nazwa",
Address = "adres",
Type = PinType.SavedPin,
Position = new Position(Convert.ToDouble(szerokosc.Text), Convert.ToDouble(dlugosc.Text))
};
positions.Add(new Position(Convert.ToDouble(szerokosc.Text), Convert.ToDouble(dlugosc.Text)));
Polyline polyline = new Polyline
{
StrokeColor = Color.Blue,
StrokeWidth = 6
};
foreach (Position pos in positions)
{
polyline.Geopath.Add(pos);
}
maps.Pins.Add(pin);
maps.MapElements.Add(polyline);
var json = JsonConvert.SerializeObject(new { X = pin.Position.Latitude, Y = pin.Position.Longitude });
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpClient client = new HttpClient();
var result = await client.PostAsync("URL", content);
if (result.StatusCode == HttpStatusCode.Created)
{
await DisplayAlert("Komunikat", "Dodanie puntku przebiegło pomyślnie", "Anuluj");
}
}
You could use CustomRenderer to define your Map,and create a custom info window base on your needs.For example,use a listview to display your questions and answers.
You could refer to custom map pin and custom info window
Update (i could only give you some snippets,you should fill your content and layout base on your needs):
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace CustomRenderer.Droid
{
public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter
{
List<CustomPin> customPins;
public CustomMapRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
NativeMap.InfoWindowClick -= OnInfoWindowClick;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins;
}
}
protected override void OnMapReady(GoogleMap map)
{
base.OnMapReady(map);
NativeMap.InfoWindowClick += OnInfoWindowClick;
NativeMap.SetInfoWindowAdapter(this);
}
protected override MarkerOptions CreateMarker(Pin pin)
{
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
marker.SetTitle(pin.Label);
marker.SetSnippet(pin.Address);
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));
return marker;
}
void OnInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)
{
var customPin = GetCustomPin(e.Marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
if (!string.IsNullOrWhiteSpace(customPin.Url))
{
var url = Android.Net.Uri.Parse(customPin.Url);
var intent = new Intent(Intent.ActionView, url);
intent.AddFlags(ActivityFlags.NewTask);
Android.App.Application.Context.StartActivity(intent);
}
}
// you could custom your view with a listview here,fill the answers and question
public Android.Views.View GetInfoContents(Marker marker)
{
var inflater = Android.App.Application.Context.GetSystemService(Context.LayoutInflaterService) as Android.Views.LayoutInflater;
if (inflater != null)
{
Android.Views.View view;
var customPin = GetCustomPin(marker);
if (customPin == null)
{
throw new Exception("Custom pin not found");
}
//inflate your custom layout
if (customPin.Name.Equals("Xamarin"))
{
view = inflater.Inflate(Resource.Layout.XamarinMapInfoWindow, null);
}
else
{
view = inflater.Inflate(Resource.Layout.MapInfoWindow, null);
}
var infoTitle = view.FindViewById<TextView>(Resource.Id.InfoWindowTitle);
var infoSubtitle = view.FindViewById<TextView>(Resource.Id.InfoWindowSubtitle);
if (infoTitle != null)
{
infoTitle.Text = marker.Title;
}
if (infoSubtitle != null)
{
infoSubtitle.Text = marker.Snippet;
}
return view;
}
return null;
}
public Android.Views.View GetInfoWindow(Marker marker)
{
return null;
}
CustomPin GetCustomPin(Marker annotation)
{
var position = new Position(annotation.Position.Latitude, annotation.Position.Longitude);
foreach (var pin in customPins)
{
if (pin.Position == position)
{
return pin;
}
}
return null;
}
}
}
im trying to draw a path on map which user walked. It works fine on android but ios, just draw at start little line. After "OnElementPropertyChanged" triggered not draw anything.
RouteCoordinates are created on CustomMap based Map class as BindableProperties. I think my property changed method can not reach right display layer or thread.
My custom renderer for IOS:
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace PawsApp.iOS.Renderers
{
public class CustomMapRenderer : MapRenderer
{
MKPolylineRenderer polylineRenderer;
CustomMap formsMap;
MKMapView nativeMap;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
if (nativeMap != null)
{
nativeMap.RemoveOverlays(nativeMap.Overlays);
nativeMap.OverlayRenderer = null;
polylineRenderer = null;
}
}
if (e.NewElement != null)
{
formsMap = (CustomMap)e.NewElement;
nativeMap = Control as MKMapView;
UpdatePolyLine();
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (this.Element == null || this.Control == null)
return;
//if (e.PropertyName == CustomMap.RouteCoordinatesProperty.PropertyName)
if ((e.PropertyName == "RouteCoordinates" || e.PropertyName == "VisibleRegion"))
{
formsMap = (CustomMap)sender;
nativeMap = Control as MKMapView;
UpdatePolyLine();
}
}
private void UpdatePolyLine()
{
//var nativeMap = Control as MKMapView;
nativeMap.OverlayRenderer = GetOverlayRenderer;
CLLocationCoordinate2D[] coords = new CLLocationCoordinate2D[formsMap.RouteCoordinates.Count];
int index = 0;
foreach (var position in formsMap.RouteCoordinates)
{
coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude);
index++;
}
var routeOverlay = MKPolyline.FromCoordinates(coords);
nativeMap.AddOverlay(routeOverlay);
}
//[Foundation.Export("mapView:rendererForOverlay:")]
MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper)
{
if (polylineRenderer == null)
{
var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay;
//var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as MKPolyline;
polylineRenderer = new MKPolylineRenderer(overlay as MKPolyline)
{
FillColor = UIColor.Yellow,
StrokeColor = UIColor.Red,
LineWidth = 3,
Alpha = 0.4f
};
}
return polylineRenderer;
}
}
}
After trying it on my side, I find if you want to refresh the polyLine in the Map in iOS, you have to:
remove the old polyline;
use a new nativeMap.OverlayRenderer everytime.
I changed the customer renderer code like this:
public class CustomMapRenderer : MapRenderer
{
MKPolylineRenderer polylineRenderer;
CustomMap formsMap;
MKMapView nativeMap;
MKPolyline polyline;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
if (nativeMap != null)
{
nativeMap.RemoveOverlays(nativeMap.Overlays);
nativeMap.OverlayRenderer = null;
polylineRenderer = null;
}
}
if (e.NewElement != null)
{
formsMap = (CustomMap)e.NewElement;
nativeMap = Control as MKMapView;
UpdatePolyLine();
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (this.Element == null || this.Control == null)
return;
//if (e.PropertyName == CustomMap.RouteCoordinatesProperty.PropertyName)
if ((e.PropertyName == "RouteCoordinates" || e.PropertyName == "VisibleRegion"))
{
formsMap = (CustomMap)sender;
nativeMap = Control as MKMapView;
UpdatePolyLine();
}
}
private void UpdatePolyLine()
{
//var nativeMap = Control as MKMapView;
if (polyline != null)
{
nativeMap.RemoveOverlay(polyline);
polyline.Dispose();
}
nativeMap.OverlayRenderer = GetOverlayRenderer;
CLLocationCoordinate2D[] coords = new CLLocationCoordinate2D[formsMap.RouteCoordinates.Count];
int index = 0;
foreach (var position in formsMap.RouteCoordinates)
{
coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude);
index++;
}
var routeOverlay = MKPolyline.FromCoordinates(coords);
nativeMap.AddOverlay(routeOverlay);
polyline = routeOverlay;
}
//[Foundation.Export("mapView:rendererForOverlay:")]
MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper)
{
if (polylineRenderer != null)
{
polylineRenderer = null;
}
var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay;
//var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as MKPolyline;
polylineRenderer = new MKPolylineRenderer(overlay as MKPolyline)
{
FillColor = UIColor.Yellow,
StrokeColor = UIColor.Red,
LineWidth = 3,
Alpha = 0.4f
};
return polylineRenderer;
}
}
And in the xamarin.forms porject, reset the customMap.RouteCoordinates every time:
Device.StartTimer(TimeSpan.FromSeconds(3), () =>
{
Device.BeginInvokeOnMainThread(() =>
{
a -= 0.000321;
b += 0.000222;
customMap.RouteCoordinates = new List<Position>
{
new Position (37.797534, -122.401827),
new Position(37.797510, -122.402060),
new Position(37.790269, -122.400589),
new Position(37.790265, -122.400474),
new Position(37.790228, -122.400391),
new Position(37.790126, -122.400360),
new Position(37.789250, -122.401451),
new Position(a, b)
};
});
return true;
});
I also upload my demo here and you can check it. Let me know if it works.
When I try to run my Xamarin.forms maps application I get the following error: Event registration is overwriting existing delegate. Either just use events or your own delegate: MyApp.iOS.CustomRenderer.MapDelegate MapKit.MKMapView+_MKMapViewDelegate
I attached my CustomMapRender file to this question
... C#
using ICCHMapClusterControllerDelegate = MapClustering.ICCHMapClusterControllerDelegate;
using ICCHMapClusterer = MapClustering.ICCHMapClusterer;
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace MyApp.iOS.CustomRenderer
{
public class CustomMapRenderer : MapRenderer, ICCHMapClusterControllerDelegate//IMKMapViewDelegate
{
MKMapView mapView;
CustomMap customMap;
CCHMapClusterController mapClusterController;
ICCHMapClusterer mapClusterer;
List<MKPointAnnotation> annotations;
CLLocationManager LocationManager;
protected override void Dispose(bool disposing)
{
mapView.ShowsUserLocation = false;
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
mapView = Control as MKMapView;
customMap = e.NewElement as CustomMap;
customMap.LocationsLoaded += customMap_LocationsLoaded;
customMap.CenterToMyLocationButtonClicked += CenterToMyLocation;
customMap.OpenCallout += OpenCallout;
LocationManager = new CLLocationManager();
LocationManager.AuthorizationChanged += (object locationmanager, CLAuthorizationChangedEventArgs eventargs) =>
{
if (eventargs.Status == CLAuthorizationStatus.AuthorizedWhenInUse)
{
mapView.ShowsUserLocation = true;
}
else
{
mapView.ShowsUserLocation = false;
}
};
if (CLLocationManager.Status == CLAuthorizationStatus.AuthorizedWhenInUse || CLLocationManager.Status == CLAuthorizationStatus.AuthorizedAlways
|| CLLocationManager.Status == CLAuthorizationStatus.Authorized)
{
mapView.ShowsUserLocation = true;
}
WMSTileOverlay WMSOverlay = new WMSTileOverlay(NetworkConstants.WmsOvermaasLayerUrl, "3")
{
CanReplaceMapContent = false
};
WMSProvincieTileOverlay ProvinceOverLay = new WMSProvincieTileOverlay
{
CanReplaceMapContent = false
};
WMSTileOverlay WPMOverlay2 = new WMSTileOverlay(NetworkConstants.WmsWpmLayerUrl, "2")
{
CanReplaceMapContent = false
};
WMSTileOverlay WPMOverlay3 = new WMSTileOverlay(NetworkConstants.WmsWpmLayerUrl, "3")
{
CanReplaceMapContent = false
};
WMSTileOverlay WPMOverlay4 = new WMSTileOverlay(NetworkConstants.WmsWpmLayerUrl, "4")
{
CanReplaceMapContent = false
};
mapView.AddOverlay(WMSOverlay, MKOverlayLevel.AboveLabels);
mapView.AddOverlay(ProvinceOverLay, MKOverlayLevel.AboveLabels);
mapView.AddOverlay(WPMOverlay2, MKOverlayLevel.AboveLabels);
mapView.AddOverlay(WPMOverlay3, MKOverlayLevel.AboveLabels);
mapView.AddOverlay(WPMOverlay4, MKOverlayLevel.AboveLabels);
Debug.WriteLine("TESTING");
mapClusterController = new CCHMapClusterController(mapView);
mapClusterController.Delegate = new CCHMapClusterControllerDelegate();
mapClusterController.WeakDelegate = this;
// /new CCHMapDelegate();//new CCHMapDelegate();
//new CCHMapClusterControllerDelegate(); //new CCHMapDelegate ();
}
}
private void customMap_LocationsLoaded(object sender, List<Location> locations)
{
RemoveAnnotations();
if (locations == null || locations.Count == 0)
{
return;
}
annotations = new List<MKPointAnnotation>();
foreach (Location location in locations)
{
MKPointAnnotation anno = new MKPointAnnotation();
CLLocationCoordinate2D coord = new CLLocationCoordinate2D(location.Latitude, location.Longitude);
anno.Coordinate = coord;
anno.Title = location.WaterName;
anno.Subtitle = location.City + " " + location.ExternalReference;
annotations.Add(anno);
}
mapClusterController.AddAnnotations(annotations.ToArray(), null);
mapView.Delegate = new MapDelegate(locations, customMap);
mapClusterController.ReuseExistingClusterAnnotations = false;
}
private void RemoveAnnotations()
{
if (annotations != null && annotations.Count != 0)
{
mapClusterController.RemoveAnnotations(annotations.ToArray(), () =>
{
});
}
}
private void CenterToMyLocation(object sender, string e)
{
if (CLLocationManager.Status == CLAuthorizationStatus.AuthorizedWhenInUse || CLLocationManager.Status == CLAuthorizationStatus.AuthorizedAlways
|| CLLocationManager.Status == CLAuthorizationStatus.Authorized)
{
MKCoordinateRegion mapRegion;
mapRegion.Center.Latitude = mapView.UserLocation.Coordinate.Latitude;
mapRegion.Center.Longitude = mapView.UserLocation.Coordinate.Longitude;
mapRegion.Span.LatitudeDelta = 0.2;
mapRegion.Span.LongitudeDelta = 0.2;
mapView.SetRegion(mapRegion, true);
}
if (CLLocationManager.Status == CLAuthorizationStatus.Denied)
{
//check if ios 7/8 this is ios 8
UIAlertController alertController = UIAlertController.Create(title: "Locatie", message: "Om uw locatie te kunnen gebruiken in de app hebben we uw toestemming nodig. " +
"dit kunt u in het instellingen menu geven.", preferredStyle: UIAlertControllerStyle.Alert);
alertController.AddAction(UIAlertAction.Create(title: "Annuleren", style: UIAlertActionStyle.Cancel, handler: null));
alertController.AddAction(UIAlertAction.Create(title: "Instellingen", style: UIAlertActionStyle.Default, handler =>
{
NSUrl settingsurl = new NSUrl(UIApplication.OpenSettingsUrlString);
mapView.ShowsUserLocation = true;
UIApplication.SharedApplication.OpenUrl(settingsurl);
}));
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alertController, true, null);
//Old Obsolete
//UIAlertView alert = new UIAlertView("Locatie", "Om uw locatie te kunnen gebruiken in de app hebben we uw toestemming nodig. " +
// "dit kunt u in het instellingen menu geven.", null, "Annuleren", "Instellingen");
//alert.Clicked += (object alertsender, UIButtonEventArgs eventargs) => {
// if (eventargs.ButtonIndex != 0)
// {
// NSUrl settingsurl = new NSUrl(UIApplication.OpenSettingsUrlString);
// mapView.ShowsUserLocation = true;
// UIApplication.SharedApplication.OpenUrl(settingsurl);
// }
//};
//alert.Show();
}
if (CLLocationManager.Status == CLAuthorizationStatus.NotDetermined)
{
LocationManager.RequestWhenInUseAuthorization();
}
}
private void OpenCallout(object sender, Location e)
{
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
Thread.Sleep(1000); // delay execution for 1 s
Device.BeginInvokeOnMainThread(() =>
{
var anno = this.annotations.Where(a => a.Coordinate.Latitude == e.Latitude && a.Coordinate.Longitude == e.Longitude).FirstOrDefault();
if (anno != null)
{
this.mapClusterController.SelectAnnotation(anno, e.Latitude, e.Longitude);
}
});
});
}
[Export("mapClusterController:titleForMapClusterAnnotation:")]
public string TitleForMapClusterAnnotation(CCHMapClusterController mapClusterController, CCHMapClusterAnnotation mapClusterAnnotation)
{
var annotationsCount = mapClusterAnnotation.Annotations.Count;
if (annotationsCount != 1) return "";
MKPointAnnotation annot = (MKPointAnnotation)mapClusterAnnotation.Annotations.First();
return annot.Title;
}
[Export("mapClusterController:subtitleForMapClusterAnnotation:")]
public string SubtitleForMapClusterAnnotation(CCHMapClusterController mapClusterController, CCHMapClusterAnnotation mapClusterAnnotation)
{
if (mapClusterAnnotation.Annotations.Count != 1) return "";
MKPointAnnotation annot = (MKPointAnnotation)mapClusterAnnotation.Annotations.First();
return annot.Subtitle;
}
[Export("mapClusterController:willReuseMapClusterAnnotation:")]
public void WillReuseMapClusterAnnotation(CCHMapClusterController mapClusterController, CCHMapClusterAnnotation mapClusterAnnotation)
{
}
}
internal class MapDelegate : MKMapViewDelegate
{
public MapDelegate(List<Location> locations, CustomMap CustomMap)
: base()
{
Locations = locations;
customMap = CustomMap;
conv = new LevelStatusToColorValueConverter();
defaultColor = Color.FromHex(ColorConstants.WaterLevelBadgeGreen).ToUIColor();
}
static NSObject Invoker = new NSObject();
List<Location> Locations;
string clusterPinId = "cluster";
string nonClusterPinId = "nonCluster";
CustomMap customMap;
LevelStatusToColorValueConverter conv;
UIColor defaultColor;
public override void RegionChanged(MKMapView mapView, bool animated)
{
if (mapView.UserLocationVisible)
{
customMap.ChangeStatusOfLocationImageOnMap(true);
}
else
{
customMap.ChangeStatusOfLocationImageOnMap(false);
}
}
public override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation anno)
{
var annotation = ObjCRuntime.Runtime.GetNSObject(anno.Handle) as CCHMapClusterAnnotation; // this is required to get the underlying annotation object
string useId = nonClusterPinId;
if (anno is MKUserLocation)
return null;
if (annotation == null)
{
return null;
}
string imageName = "";
string imageURL = string.Empty;
string value = string.Empty;
UIColor color = defaultColor;
if (annotation.IsCluster)
{
useId = clusterPinId;
if (Locations != null)
{
List<Location> locations = new List<Location>();
foreach (MKPointAnnotation clusterAnno in annotation.Annotations)
{
string title = clusterAnno.Subtitle;
var location = Locations.Where(l => (l.City + " " + l.ExternalReference) == title).ToList();
if (location != null && location.Count != 0)
{
locations.Add(location.First());
}
}
if (locations.Any())
{
imageName = MapUtil.GetCorrectImageNameForLocations(locations);
}
}
}
else
{
if (Locations != null)
{
string title = annotation.Subtitle;
var location = Locations.Where(l => (l.City + " " + l.ExternalReference) == title).ToList();
if (location != null && location.Count != 0)
{
var currentLocation = location.First();
value = currentLocation.CurrentValueAsString;
var xColor = (Color)conv.Convert(currentLocation.WaterlevelStatus, null, null, null);
color = xColor.ToUIColor();
imageName = MapUtil.GetCorrectImageNameForLocation(currentLocation);
if (currentLocation.HasImage)
{
imageURL = currentLocation.SmallImageURL();
}
else
{
imageURL = NetworkConstants.NoImageSmall;
}
}
}
}
MKAnnotationView pinView = (MKAnnotationView)mapView.DequeueReusableAnnotation(useId);
if (pinView == null)
pinView = new MKAnnotationView(annotation, useId);
if (!annotation.IsCluster)
{
pinView.CanShowCallout = true;
double testWidth = value.Length < 11 ? 65 : 90;
var button = new UIButton(new CGRect(testWidth, 5, 10, 20));
button.SetImage(UIImage.FromBundle("disclosure"), UIControlState.Normal);
JSBadgeView badge = new JSBadgeView(button, JSBadgeView.Alignment.CenterLeft)
{
BadgeBackgroundColor = color,
BadgeStrokeColor = UIColor.Red,
BadgeTextColor = Color.FromHex(ColorConstants.DarkGreyDetailText).ToUIColor(),
BadgeTextFont = UIFont.FromName("AvenirNext-Regular", 11),
BadgeAlignment = JSBadgeView.Alignment.CenterLeft,
BadgeText = value
};
UIView view = new UIView(new CGRect(0, 0, testWidth + 10, 30));
view.ClipsToBounds = false;
view.AddSubview(button);
pinView.RightCalloutAccessoryView = view;
if (string.IsNullOrEmpty(imageURL))
{
var imageView = new UIImageView(new CGRect(0, 0, 42, 42));
imageView.Image = UIImage.FromBundle("notavailable");
imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
pinView.LeftCalloutAccessoryView = imageView;
}
else
{
FromUrl(imageURL, pinView);
}
}
pinView.Image = UIImage.FromBundle((string.IsNullOrEmpty(imageName)) ? "flowgreenpin" : imageName);
if (annotation.IsCluster)
{
pinView.CanShowCallout = false;
JSBadgeView badge = new JSBadgeView(pinView, JSBadgeView.Alignment.TopCenter)
{
BadgeBackgroundColor = UIColor.FromRGB(0.451f, 0.847f, 0.988f),
BadgeStrokeColor = UIColor.Red,
BadgeTextColor = UIColor.White,
BadgeTextFont = UIFont.FromName("AvenirNext-Regular", 11),
BadgeAlignment = JSBadgeView.Alignment.TopCenter,
BadgeText = annotation.Annotations.Count.ToString()
};
}
return pinView;
}
public static void InvokedOnMainThread(Action Action)
{
if (NSThread.Current.IsMainThread)
Action();
else
Invoker.BeginInvokeOnMainThread(() => Action());
}
static async Task FromUrl(string uri, MKAnnotationView pinView)
{
using (var httpClient = new HttpClient())
{
Task<byte[]> contentsTask = httpClient.GetByteArrayAsync(uri);
// await! control returns to the caller and the task continues to run on another thread
var contents = await contentsTask;
// load from bytes
var image = UIImage.LoadFromData(NSData.FromArray(contents));
var imageView = new UIImageView(new CGRect(0, 0, 42, 42));
imageView.Image = image;
imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
InvokedOnMainThread(delegate
{
pinView.LeftCalloutAccessoryView = imageView;
});
}
}
public override void DidSelectAnnotationView(MKMapView mapView, MKAnnotationView view)
{
var annotation = ObjCRuntime.Runtime.GetNSObject(view.Annotation.Handle) as CCHMapClusterAnnotation;
if (annotation != null)
{
if (!annotation.IsCluster)
{
UITapGestureRecognizer tap = new UITapGestureRecognizer(OnTap);
view.AddGestureRecognizer(tap);
return;
}
MKMapRect mapRect = annotation.MapRect;
UIEdgeInsets edgeInsets = new UIEdgeInsets(20, 20, 20, 20);
mapView.SetVisibleMapRect(mapRect, edgeInsets, true);
}
mapView.DeselectAnnotation(view.Annotation, true);
}
public override void DidDeselectAnnotationView(MKMapView mapView, MKAnnotationView view)
{
view.GestureRecognizers = null;
}
public override void CalloutAccessoryControlTapped(MKMapView mapView, MKAnnotationView view, UIControl control)
{
if (view != null)
{
var clusterPin = ObjCRuntime.Runtime.GetNSObject(view.Annotation.Handle) as CCHMapClusterAnnotation;
var location = Locations.Where(l => (l.City + " " + l.ExternalReference) == clusterPin.Subtitle).ToList();
if (location != null && location.Count != 0)
{
customMap.ClickedCallout(location.First());
}
}
}
private void OnTap(UIGestureRecognizer gesture)
{
var pinView = gesture.View as MKAnnotationView;
if (pinView != null)
{
var clusterPin = ObjCRuntime.Runtime.GetNSObject(pinView.Annotation.Handle) as CCHMapClusterAnnotation;
var location = Locations.Where(l => (l.City + " " + l.ExternalReference) == clusterPin.Subtitle).ToList();
if (location != null && location.Count != 0)
{
customMap.ClickedCallout(location.First());
}
}
}
public override MKOverlayRenderer OverlayRenderer(MKMapView mapView, IMKOverlay overlay)
{
var osmOverlay = overlay as WMSTileOverlay;
if (osmOverlay != null)
{
return new MKTileOverlayRenderer(new WMSTileOverlay(osmOverlay.BaseUrl, osmOverlay.Layer) { CanReplaceMapContent = false });
}
return new MKTileOverlayRenderer(new WMSProvincieTileOverlay() { CanReplaceMapContent = false });
}
}
internal class CCHMapDelegate : UIViewController, ICCHMapClusterControllerDelegate//CCHMapClusterControllerDelegate
{
[Export("mapClusterController:titleForMapClusterAnnotation:")]
public string TitleForMapClusterAnnotation(CCHMapClusterController mapClusterController, CCHMapClusterAnnotation mapClusterAnnotation)
{
var annotationsCount = mapClusterAnnotation.Annotations.Count;
if (annotationsCount != 1) return "";
MKPointAnnotation annot = (MKPointAnnotation)mapClusterAnnotation.Annotations.First();
return annot.Title;
}
//public override string MapClusterTitleForAnnotation (CCHMapClusterController mapClusterController, CCHMapClusterAnnotation mapClusterAnnotation)
//{
// if (mapClusterAnnotation.Annotations.Count == 1) {
// MKPointAnnotation annot = (MKPointAnnotation)mapClusterAnnotation.Annotations.First ();
// return annot.Title;
// }
// return "";
//}
[Export("mapClusterController:subtitleForMapClusterAnnotation:")]
public string SubtitleForMapClusterAnnotation(CCHMapClusterController mapClusterController, CCHMapClusterAnnotation mapClusterAnnotation)
{
if (mapClusterAnnotation.Annotations.Count != 1) return "";
MKPointAnnotation annot = (MKPointAnnotation)mapClusterAnnotation.Annotations.First();
return annot.Subtitle;
}
//public override string MapClusterSubtitleForAnnotation (CCHMapClusterController mapClusterController, CCHMapClusterAnnotation mapClusterAnnotation)
//{
// if (mapClusterAnnotation.Annotations.Count == 1) {
// MKPointAnnotation annot = (MKPointAnnotation)mapClusterAnnotation.Annotations.First ();
// return annot.Subtitle;
// }
// return "";
//}
[Export("mapClusterController:willReuseMapClusterAnnotation:")]
public void WillReuseMapClusterAnnotation(CCHMapClusterController mapClusterController, CCHMapClusterAnnotation mapClusterAnnotation)
{
}
//public override void MapClusterWillReuseAnnotation (CCHMapClusterController mapClusterController, CCHMapClusterAnnotation mapClusterAnnotation)
//{
//}
}
}
...
I’m using the BottomBarPage package. I’m changing the Tab on the fly programmatically, however the icon at the bottom doesn’t get highlighted for Android when there’s a tab change. It works well on iOS on the hand.
I tried James Montemagno’s code, I couldn’t get it to work with the BottomBarPage package.
This is the link to his code
https://montemagno.com/dynamically-changing-xamarin-forms-tab-icons-when-select/
How do I get the Icon highlighted when there is a Tab Page happening programmatically for BottomBarPage?
Below is my code.
public AndroidMainPage(int indexPage)
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
CurrentPageChanged += MainPage_CurrentPageChanged;
CurrentPage = Children[indexPage];
CurrentPage.Appearing += (s, a) => Children[indexPage].Icon = new FileImageSource() { File = "quizzes_selected.png" };
CurrentPage.Disappearing += (s, a) => Children[indexPage].Icon = new FileImageSource() { File = "quizzes.png" };
_currentPage = CurrentPage;
}
private void MainPage_CurrentPageChanged(object sender, EventArgs e)
{
IIconChange currentBinding;
if (_currentPage != null)
{
currentBinding = ((NavigationPage)_currentPage).CurrentPage.BindingContext as IIconChange;
if (currentBinding != null)
currentBinding.IsSelected = false;
}
_currentPage = CurrentPage;
currentBinding = ((NavigationPage)_currentPage).CurrentPage.BindingContext as IIconChange;
if (currentBinding != null)
currentBinding.IsSelected = true;
UpdateIcons?.Invoke(this, EventArgs.Empty);
}
Android BottomBarPage Renderer:
[assembly: ExportRenderer(typeof(AndroidMainPage), typeof(MyTabbedPageRenderer))]
namespace TabPageDemo.Android.Renderers
{
public class MyTabbedPageRenderer : BottomBarPageRenderer
{
bool setup;
TabLayout layout;
public MyTabbedPageRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<BottomBarPage> e)
{
if (e != null) base.OnElementChanged(e);
if (Element != null)
{
((AndroidMainPage)Element).UpdateIcons += Handle_UpdateIcons;
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (layout == null && e.PropertyName == "Renderer")
{
layout = (TabLayout)ViewGroup.GetChildAt(1);
}
}
void Handle_UpdateIcons(object sender, EventArgs e)
{
TabLayout tabs = layout;
if (tabs == null)
return;
for (var i = 0; i < Element.Children.Count; i++)
{
var child = Element.Children[i].BindingContext as IIconChange;
if (child == null) continue;
var icon = child.CurrentIcon;
if (string.IsNullOrEmpty(icon))
continue;
TabLayout.Tab tab = tabs.GetTabAt(i);
SetCurrentTabIcon(tab, icon);
}
}
void SetCurrentTabIcon(TabLayout.Tab tab, string icon)
{
tab.SetIcon(IdFromTitle(icon, ResourceManager.DrawableClass));
}
int IdFromTitle(string title, Type type)
{
string name = Path.GetFileNameWithoutExtension(title);
int id = GetId(type, name);
return id;
}
int GetId(Type type, string memberName)
{
object value = type.GetFields().FirstOrDefault(p => p.Name == memberName)?.GetValue(type)
?? type.GetProperties().FirstOrDefault(p => p.Name == memberName)?.GetValue(type);
if (value is int)
return (int)value;
return 0;
}
}
}
Using the Renderer, the tab variable is always null from this TabLayout.Tab tab = tabs.GetTabAt(i);
Any suggestion?