ObservableCollection only updating on Map Zoom and Move events not adding to Collection! - windows-phone-7

I have an issue with a collection that I have bound. I have a manual refresh button that pulls some moving pushpins from the server. The server is moving the pins itself. After processing I delete the existing collection and re add it to the Observable Collection. This code works and I have verififed that the contents have been update however the pins only "update" (move on the map) if a Zoom or move of the map has happened!
My class is as follows...
public class MapData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisedPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private GeoCoordinate mapCenter = new GeoCoordinate(50, -1);
public GeoCoordinate MapCenter
{
get { return this.mapCenter; }
set
{
if (this.mapCenter == value) return;
this.mapCenter = value;
this.RaisedPropertyChanged("MapCenter");
}
}
private double zoom = 7.0;
public double Zoom
{
get { return this.zoom; }
set
{
if (this.zoom == value) return;
this.zoom = value;
this.RaisedPropertyChanged("Zoom");
}
}
public ObservableCollection<Plane> pins = new ObservableCollection<Plane>() {
};
public ObservableCollection<Plane> Pins
{
get { return pins; }
}
public void RemovePoints()
{
for (int i = 0; i < pins.Count; i++)
{
pins.RemoveAt(i);
}
pins.Clear();
this.RaisedPropertyChanged("Location");
}
public void AddPoints(List<Plane> Planelist)
{
for (int i = 0; i < Planelist.Count; i++)
{
pins.Add(Planelist[i]);
}
}
private Plane selectedPin;
public Plane SelectedPin
{
get {
return this.selectedPin;
}
set
{
if (this.selectedPin == value) return;
this.selectedPin = value;
this.RaisedPropertyChanged("SelectedPin");
}
}
private LocationCollection routePoints = new LocationCollection();
public LocationCollection RoutePoints
{
get { return routePoints; }
}
}
And it is bound using the following...
<my:MapItemsControl ItemsSource="{Binding Pins}" ItemTemplate="{StaticResource PushedMe}"/>

After speaking with Microsoft it appears that the device will cache URL's and that is actually my issue! Forced no-cache on the server side and issue fixed!

Related

Code Error. The modifier public is not valid. Why?

I am making a 2D game in Unity and am trying to make my moveable character stop every time dialogue appears on screen.
I am using the Fungus extension for my dialogue as I'm a newbie to coding. Every thing I try however I run in to problems.
My current issue is that the modifier 'public' is not valid for this item.
Anyone know how this can be fixed? I have attached the code below. I assume the issue is with the public void CantMove() and public void CanMove() lines.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float moveSpeed;
public Rigidbody2D theRB;
public float jumpForce;
private bool isGrounded;
public Transform groundCheckPoint;
public LayerMask whatIsGround;
private bool canDoubleJump;
private bool canMove = true;
private Animator anim;
private SpriteRenderer theSR;
// Start is called before the first frame update
void Start()
{
anim = GetComponent<Animator>();
theSR = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void Update()
{
if(!canMove)
{
theRB.velocity = new Vector2(0, 0);
}
else
{
theRB.velocity = new Vector2(moveSpeed * Input.GetAxis("Horizontal"), theRB.velocity.y);
}
public void CantMove()
{
canMove = false;
}
public void CanMove()
{
canMove = true;
}
//theRB.velocity = new Vector2(moveSpeed * Input.GetAxis("Horizontal"), theRB.velocity.y);
isGrounded = Physics2D.OverlapCircle(groundCheckPoint.position, .2f, whatIsGround);
if(isGrounded)
{
canDoubleJump = true;
}
if(Input.GetButtonDown("Jump"))
{
if (isGrounded)
{
theRB.velocity = new Vector2(theRB.velocity.x, jumpForce);
}
else
{
if(canDoubleJump)
{
theRB.velocity = new Vector2(theRB.velocity.x, jumpForce);
canDoubleJump = false;
}
}
}
if(theRB.velocity.x > 0)
{
theSR.flipX = true;
} else if(theRB.velocity.x < 0)
{
theSR.flipX = false;
}
anim.SetFloat("moveSpeed", Mathf.Abs( theRB.velocity.x));
anim.SetBool("isGrounded", isGrounded);
}
}
'''
Your problem is that your two functions defined for CanMove and CantMove are declared inside of the Update function body... which makes them locally scoped functions which means they can never have public access and can only be called from within the Update function itself.
Move these two functions outside of the Update function body like this...
void Update() {
...
}
public void CantMove() {
canMove = false;
}
public void CanMove() {
canMove = true;
}

Scroll Index method not firing consistently

In a Xamarin.Forms and Xamarin.Android project I create a Custom Render and Adapter for a ListView.
The adapter implements BaseAdapter and ISectionIndexer. The custom render of this control is using FastScroll feature, in Android when you tap this scroll a bubble with a index letter appears. This works fine, but my idea is to have a way to catch the selected index after releasing scroll and that scroll "bubble" disappears.
I thought with the following class (in the GetSectionForPosition method) could achieve that:
public class ListViewconIndexAdapter : BaseAdapter<string>, ISectionIndexer
{
string[] items;
Activity context;
string[] sections;
Java.Lang.Object[] sectionsObjects;
Dictionary<string, int> alphaIndex;
public ListViewconIndexAdapter(Activity context, string[] items) : base()
{
this.context = context;
this.items = items;
alphaIndex = new Dictionary<string, int>();
for (int i = 0; i < items.Length; i++)
{
var key = items[i][0].ToString();
if (!alphaIndex.ContainsKey(key))
alphaIndex.Add(key, i);
}
sections = new string[alphaIndex.Keys.Count];
alphaIndex.Keys.CopyTo(sections, 0);
sectionsObjects = new Java.Lang.Object[sections.Length];
for (int i = 0; i < sections.Length; i++)
{
sectionsObjects[i] = new Java.Lang.String(sections[i]);
}
}
public override Java.Lang.Object GetItem(int position)
{
return position;
}
public override long GetItemId(int position)
{
return position;
}
public override string this[int position]
{
get { return items[position]; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);
view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = items[position];
return view;
}
//Fill in cound here, currently 0
public override int Count
{
get { return items.Length; }
}
// -- ISectionIndexer --
public int GetPositionForSection(int section)
{
return alphaIndex[sections[section]];
}
public int GetSectionForPosition(int position)
{ // this method isn't called in this example, but code is provided for completeness
int prevSection = 0;
for (int i = 0; i < sections.Length; i++)
{
if (GetPositionForSection(i) > position)
{
break;
}
prevSection = i;
}
Console.WriteLine(prevSection);
Console.WriteLine(sections[prevSection]);
//Toast.MakeText(context, sections[prevSection], ToastLength.Short).Show();
Xamarin.Forms.MessagingCenter.Send<object,string>(this, "CambioSeccion", sections[prevSection]);
return prevSection;
}
}
I put those Console.writeline for checking the index letter and that Message send is a way to send it back to PCL/NET Standard code (to show an DisplayAlert or something).
But the problem is that method firing is not consistent, for example, sometimes you fast scroll down to 'C' but Console doesn't print anything after releasing it there, but after touching it again where you leave it, it fires up. But sometimes it works like i want, it prints after release the scroll at selected index.
ListView has two different scroll listeners, AbsListView.IOnScrollListener and AbsListView.IOnScrollChangeListener (this one was added in API 23) and a touch listener (AbsListView.IOnTouchListener)
I think based upon your use-case, you are looking for the OnScrollStateChanged and when it goes into idle state and you are not touching the listview, do something (or vice versa).
Example (adjust to your needs of course):
public class MyScrollListener : Java.Lang.Object, AbsListView.IOnTouchListener, AbsListView.IOnScrollListener, AbsListView.IOnScrollChangeListener //(API23)
{
bool touching;
bool scrolling;
public void OnScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
}
public void OnScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY)
{
}
public void OnScrollStateChanged(AbsListView view, [GeneratedEnum] ScrollState scrollState)
{
switch(scrollState)
{
case ScrollState.Idle:
if (!touching)
{
scrolling = false;
GetSelection();
}
break;
default:
scrolling = true;
break;
}
}
public bool OnTouch(View v, MotionEvent e)
{
switch (e.Action)
{
case MotionEventActions.Up:
touching = false;
if (!scrolling)
GetSelection();
break;
default:
touching = true;
break;
}
return true;
}
void GetSelection()
{
// touch and srolling is done, do something
}
}
Usage:
var scrollListener = new MyScrollListener();
listView.SetOnTouchListener(scrollListener);
listView.SetOnScrollListener(scrollListener);
listView.SetOnScrollChangeListener(scrollListener); // API23

How to implement TabLayout.IOnTabSelectedListener.OnTabUnselected with TabbedPage.ToolbarPlacement="Bottom" - Xamarin Forms?

I just recently used android:TabbedPage.ToolbarPlacement="Bottom". I used to have the following code:
void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
{
var playPage = Element.CurrentPage as NavigationPage;
if (!(playPage.RootPage is PhrasesFrame))
return;
var tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
var playTab = tabLayout.GetTabAt(4);
tab.SetText("Play");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
Anyone knows how can I implement this with ToolbarPlacement="Bottom" ? I have implemented both BottomNavigationView.IOnNavigationItemSelectedListener, BottomNavigationView.IOnNavigationItemReselectedListener but can't find any reference for UnselectedTab if there is any.
Edit:
Previous custom renderer using the default tab position and implementing TabLayout:
namespace Japanese.Droid
{
public class MyTabbedPageRenderer: TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
ViewPager viewPager;
TabLayout tabLayout;
bool setup;
public MyTabbedPageRenderer(Context context): base(context){ }
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
// More codes here
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
UpdateTab(tab);
}
void TabLayout.IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab)
{
UpdateTab(tab);
}
void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
{
var playPage = Element.CurrentPage as NavigationPage;
if (!(playPage.RootPage is PhrasesFrame))
return;
var tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
var playTab = tabLayout.GetTabAt(4);
tab.SetText("Play");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
void UpdateTab(TabLayout.Tab tab)
{
// To have the logic only on he tab on position 1
if (tab == null || tab.Position != 4)
{
return;
}
if (tab.Text == "Play")
{
tab.SetText("Pause");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_22);
App.pauseCard = false;
}
else
{
tab.SetText("Play");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
}
}
}
Current custom renderer using the ToolbarPlacement="Bottom":
namespace Japanese.Droid
{
public class BottomTabPageRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemSelectedListener, BottomNavigationView.IOnNavigationItemReselectedListener
{
public BottomTabPageRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
// More codes here
}
bool BottomNavigationView.IOnNavigationItemSelectedListener.OnNavigationItemSelected(IMenuItem item)
{
base.OnNavigationItemSelected(item);
UpdateTab(item)
}
void BottomNavigationView.IOnNavigationItemReselectedListener.OnNavigationItemReselected(IMenuItem item)
{
UpdateTab(item);
}
void UpdateTab(IMenuItem item)
{
var playTabId = 4;
var title = item.TitleFormatted.ToString();
if (item == null || item.ItemId != playTabId)
{
return;
}
if (item.ItemId == playTabId)
{
if (title == "Play")
{
item.SetTitle("Pause");
item.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_22);
App.pauseCard = false;
}
else
{
item.SetTitle("Play");
item.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
}
}
}
}
So now my problem is I don't have any idea how will I implement the TabLayout.IOnTabSelectedListener.OnTabUnselected in the new custom renderer.
There is no official stuff for OnTabReselected event for TabbedPage's bottom navigation or
BottomNavigationView because It doesn't use TabLayout.Tab for a start. Many overridden methods of TabbedPageRenderer not being called like SetTabIcon. If you are using IOnTabSelectedListener interface(As your first part of code) you have three methods to use.
void OnTabReselected(Tab tab);
void OnTabSelected(Tab tab);
void OnTabUnselected(Tab tab);
But when it comes to BottomNavigationView interface you have only two methods
void OnNavigationItemReselected
bool OnNavigationItemSelected
So we don't have built in OnTabUnselected method. Here you need to write custom code to make unseleted event.
I have tried this code without using custom renderer using 4 tabs pages & the xaml of tabbed written in MailPage.xaml file. First declare List<string> in App.xaml.cs file to store Title of all tabs
public static List<string> Titles {get;set;}
Add tabs pages title in above list from MainPage.xaml.cs file's OnAppearing method
protected override void OnAppearing()
{
for (int i = 0; i < this.Children.Count; i++)
{
App.Titles.Add(this.Children[i].Title);
}
}
Now go to your MyTabbedPage class in which is available in shared project.
public class MyTabbedPage : Xamarin.Forms.TabbedPage
{
string selectedTab = string.Empty;
string unSelectedTab = string.Empty;
bool isValid;
public MyTabbedPage()
{
On<Xamarin.Forms.PlatformConfiguration.Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
this.CurrentPageChanged += delegate
{
unSelectedTab = selectedTab;
selectedTab = CurrentPage.Title;
if (App.Titles != null)
isValid = true;
else
App.Titles = new List<string>();
if (isValid)
{
MoveTitles(selectedTab);
//Pass 0 index for tab selected & 1 for tab unselected
var unSelecteTabTitle = App.Titles[1];
//TabEvents(1); here you know which tab unseleted call any method
}
};
}
//This method is for to moving selected title on top of App.Titles list & unseleted tab title automatic shifts at index 1
void MoveTitles(string selected)
{
var holdTitles = App.Titles;
if (holdTitles.Count > 0)
{
int indexSel = holdTitles.FindIndex(x => x.StartsWith(selected));
holdTitles.RemoveAt(indexSel);
holdTitles.Insert(0, selected);
}
App.Titles = holdTitles;
}
}
Or you can make swith case like this
void TabEvents(int index)
{
switch (index)
{
case 0:
//Tab selected
break;
case 1:
//Tab unselected
break;
}
}
Few things I should mention that MainPage.xaml.cs file inheriting MyTabbedPage
public partial class MainPage : MyTabbedPage
Structure of MainPage.xaml file
<?xml version="1.0" encoding="utf-8" ?>
<local:MyTabbedPage
<TabbedPage.Children>
<NavigationPage Title="Browse">
</NavigationPage>
</TabbedPage.Children>
</local:MyTabbedPage>
Answer seems long but hope it help you.
As per G.Hakim's suggestion, I was able to do what I wanted to do by capturing the tab item I wanted to work on and do the necessary actions in BottomNavigationView.IOnNavigationItemSelectedListener.OnNavigationItemSelected.
namespace Japanese.Droid
{
public class BottomTabPageRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemSelectedListener, BottomNavigationView.IOnNavigationItemReselectedListener
{
// same as above
bool BottomNavigationView.IOnNavigationItemSelectedListener.OnNavigationItemSelected(IMenuItem item)
{
base.OnNavigationItemSelected(item);
if(item.ItemId == 4 && item.TitleFormatted.ToString() == "Play")
{
item.SetTitle("Pause");
item.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_22);
App.pauseCard = false;
playTab = item;
}
if(item.ItemId !=4 && playTab.TitleFormatted.ToString() == "Pause")
{
playTab.SetTitle("Play");
playTab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
return true;
}
// same as above
}
}

xamarin binding byte array to grid labels

I want to binding a byte[] to difference label in grid view but it is weird when I change the value in a timer event,the onPropertyChanged() don't call,turns out the label didn't update with the byte[].Pls help me out where is the mistake in my code.I can binding a byte with the code I list,but seems not work with byte[].Thanks!
System.Timers.Timer testtimer;
Grid gridView;
byte[] _recvdata;
byte testbyte;
public byte[] RecvData
{
get
{
return _recvdata;
}
set
{
if (_recvdata != value)
{
_recvdata = value;
OnPropertyChanged("RecvData");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected override void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public DevicePage(IDevice currDevice)
{
device = currDevice;
this.Title = "Status";
testtimer = new System.Timers.Timer();
testtimer.Interval = 1000;
testtimer.Elapsed += OnTimedEvent;
testtimer.Enabled = true;
gridView = new Grid();
RecvData = new byte[40];
for (byte i = 0; i < 40;i++)
{
string st;
var lb = new Label ();
st = "RecvData[" + i + "]";
lb.SetBinding(Label.TextProperty, st);
lb.BindingContext = this;
gridView.Children.Add(lb, 0, i);
}
this.Content = gridView;
}
private void OnTimedEvent(object sender, System.Timers.ElapsedEventArgs e)
{
byte i;
testbyte += 1;
for (i = 0; i < 40; i++)
{
RecvData[i] = testbyte;
}
}
The setter of RecvData property is not called when you just change it using [indexer]. You have two options I see:
1) manually call OnPropertyChanged after you change the byte array using indexer:
private void OnTimedEvent(object sender, System.Timers.ElapsedEventArgs e)
{
byte i;
testbyte += 1;
for (i = 0; i < 40; i++)
{
RecvData[i] = testbyte;
}
OnPropertyChanged("RecvData");
}
2) create a wrapper for byte array which would call OnPropertyChanged automatically when you change an array using the indexer:
public class ByteArrayWrapper : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
byte[] _array;
public byte[] Array
{
get
{
return _array;
}
set
{
if (_array != value)
{
_array = value;
OnPropertyChanged("Array");
}
}
}
public byte this[int index]
{
get
{
return _array[index];
}
set
{
_array[index] = value;
OnPropertyChanged("Array");
}
}
protected void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
then instead of operating on your byte array like this:
public byte[] RecvData
{
// get/set
}
you should use an instance of above class:
public ByteArrayWrapper RecvData
{
// get/set
}
Other code should be just fine. If the binding doesn't work, maybe you'd need to bind to RecvData.Array[i] instead of RecvData[i], please let me know if that works as I'm not able to test right now.

Xamarin Forms Maps - how to refresh/update the map - CustomMap Renderer

If you are searching for a full polylines, pins, tiles, UIOptions (and 3D effects soon) renderings/implementations, you should take a loot at the public github I made at XamarinByEmixam23/..../Map.
I search a lot but I still have the same problem:
How can I update, refresh or reload the Xamarin.Forms.Maps?
In the class definition (class CustomMap : Map), there is no method to update the maps.. Maybe a MVVM logic can solves the problem, but I can't find it on the Web..
I followed this tutorial for the maps : Working with maps
To customise it, I followed this tutorial : Highlight a Route on a Map
So, after these tutorials (I made the same things, no changes), I tried with 2 RouteCoordinates which gave me a straight line... I then made an algorithm which works perfectly.
DirectionMap
public class DirectionMap
{
public Distance distance { get; set; }
public Duration duration { get; set; }
public Address address_start { get; set; }
public Address address_end { get; set; }
public List<Step> steps { get; set; }
public class Distance
{
public string text { get; set; }
public int value { get; set; }
}
public class Duration
{
public string text { get; set; }
public int value { get; set; }
}
public class Address
{
public string text { get; set; }
public Position position { get; set; }
}
public class Step
{
public Position start { get; set; }
public Position end { get; set; }
}
}
ResponseHttpParser
public static void parseDirectionGoogleMapsResponse(HttpStatusCode httpStatusCode, JObject json, Action<DirectionMap, string> callback)
{
switch (httpStatusCode)
{
case HttpStatusCode.OK:
DirectionMap directionMap = null;
string strException = null;
try
{
directionMap = new DirectionMap()
{
distance = new DirectionMap.Distance()
{
text = (json["routes"][0]["legs"][0]["distance"]["text"]).ToString(),
value = Int32.Parse((json["routes"][0]["legs"][0]["distance"]["value"]).ToString())
},
duration = new DirectionMap.Duration()
{
text = (json["routes"][0]["legs"][0]["duration"]["text"]).ToString(),
value = Int32.Parse((json["routes"][0]["legs"][0]["duration"]["value"]).ToString())
},
address_start = new DirectionMap.Address()
{
text = (json["routes"][0]["legs"][0]["start_address"]).ToString(),
position = new Position(Double.Parse((json["routes"][0]["legs"][0]["start_location"]["lat"]).ToString()), Double.Parse((json["routes"][0]["legs"][0]["start_location"]["lng"]).ToString()))
},
address_end = new DirectionMap.Address()
{
text = (json["routes"][0]["legs"][0]["end_address"]).ToString(),
position = new Position(Double.Parse((json["routes"][0]["legs"][0]["end_location"]["lat"]).ToString()), Double.Parse((json["routes"][0]["legs"][0]["end_location"]["lng"]).ToString()))
}
};
bool finished = false;
directionMap.steps = new List<Step>();
int index = 0;
while (!finished)
{
try
{
Step step = new Step()
{
start = new Position(Double.Parse((json["routes"][0]["legs"][0]["steps"][index]["start_location"]["lat"]).ToString()), Double.Parse((json["routes"][0]["legs"][0]["steps"][index]["start_location"]["lng"]).ToString())),
end = new Position(Double.Parse((json["routes"][0]["legs"][0]["steps"][index]["end_location"]["lat"]).ToString()), Double.Parse((json["routes"][0]["legs"][0]["steps"][index]["end_location"]["lng"]).ToString()))
};
directionMap.steps.Add(step);
index++;
}
catch (Exception e)
{
finished = true;
}
}
}
catch (Exception e)
{
directionMap = null;
strException = e.ToString();
}
finally
{
callback(directionMap, strException);
}
break;
default:
switch (httpStatusCode)
{
}
callback(null, json.ToString());
break;
}
}
I just get the distance and duration for some private calculs and get each step that I put into a List<>;
When everything is finished, I use my callback which bring us back to the controller (MapPage.xaml.cs the XAML Form Page (Xamarin Portable))
Now, everything becomes weird. It's like the map doesn't get that changes are made
public partial class MapPage : ContentPage
{
public MapPage()
{
InitializeComponent();
setupMap();
setupMapCustom();
}
public void setupMapCustom()
{
customMap.RouteCoordinates.Add(new Position(37.785559, -122.396728));
customMap.RouteCoordinates.Add(new Position(37.780624, -122.390541));
customMap.RouteCoordinates.Add(new Position(37.777113, -122.394983));
customMap.RouteCoordinates.Add(new Position(37.776831, -122.394627));
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(37.79752, -122.40183), Xamarin.Forms.Maps.Distance.FromMiles(1.0)));
}
public async void setupMap()
{
customMap.MapType = MapType.Satellite;
string origin = "72100 Le Mans";
string destination = "75000 Paris";
HttpRequest.getDirections(origin, destination, callbackDirections);
customMap.RouteCoordinates.Add(await MapUtilities.GetMapPointOfStreetAddress(origin));
Position position = await MapUtilities.GetMapPointOfStreetAddress(destination);
//customMap.RouteCoordinates.Add(position);
var pin = new Pin
{
Type = PinType.Place,
Position = position,
Label = "Destination !!",
};
customMap.Pins.Add(pin);
}
private async void callbackDirections(Object obj, string str)
{
if (obj != null)
{
DirectionMap directionMap = obj as DirectionMap;
foreach (Step step in directionMap.steps)
{
customMap.RouteCoordinates.Add(step.start);
System.Diagnostics.Debug.WriteLine("add step");
}
customMap.RouteCoordinates.Add(directionMap.address_end.position);
System.Diagnostics.Debug.WriteLine("add last step");
}
else
{
System.Diagnostics.Debug.WriteLine(str);
}
}
}
I run my app, everything works until it's something fast, because of the time spent by my algorithm etc, the callback is coming too late and then I need to refresh, reload or update my map... Anyway, I need to update my map in the future, so... If anyone can help, this one is welcome !
EDIT 1
I took a look at your answer ( thank a lot ! ;) ) but it doesn't works :/
I updated CustomMap as you did
public class CustomMap : Map
{
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create<CustomMap, List<Position>>(p => p.RouteCoordinates, new List<Position>());
public List<Position> RouteCoordinates
{
get { return (List<Position>)GetValue(RouteCoordinatesProperty); }
set { SetValue(RouteCoordinatesProperty, value); }
}
public CustomMap()
{
RouteCoordinates = new List<Position>();
}
}
Same for CustomMapRenderer (Droid)
public class CustomMapRenderer : MapRenderer, IOnMapReadyCallback
{
GoogleMap map;
Polyline polyline;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
{
((MapView)Control).GetMapAsync(this);
}
}
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)
{
UpdatePolyLine();
}
}
private void UpdatePolyLine()
{
if (polyline != null)
{
polyline.Remove();
polyline.Dispose();
}
var polylineOptions = new PolylineOptions();
polylineOptions.InvokeColor(0x66FF0000);
foreach (var position in ((CustomMap)this.Element).RouteCoordinates)
{
polylineOptions.Add(new LatLng(position.Latitude, position.Longitude));
}
polyline = map.AddPolyline(polylineOptions);
}
public void OnMapReady(GoogleMap googleMap)
{
map = googleMap;
UpdatePolyLine();
}
}
So, for the last change, in my MapPage.xaml.cs I made changes in the callbackDirections as you explained (I hope I did good)
private async void callbackDirections(Object obj, string str)
{
if (obj != null)
{
Device.BeginInvokeOnMainThread(() =>
{
DirectionMap directionMap = obj as DirectionMap;
var list = new List<Position>(customMap.RouteCoordinates);
foreach (Step step in directionMap.steps)
{
list.Add(directionMap.address_end.position);
System.Diagnostics.Debug.WriteLine("add step");
}
System.Diagnostics.Debug.WriteLine("last step");
customMap.RouteCoordinates = list;
System.Diagnostics.Debug.WriteLine("finished?");
});
}
else
{
System.Diagnostics.Debug.WriteLine(str);
}
}
The map is still doesn't display the polyline :/ I only made these changes, I didn't change anything else from my previous code.
I didn't tell you, but I'm not an expert in MVVM binding, so if I forget something, I'm sorry :/
EDIT 2
So after your answer and some read, read and re-read of your answer, there is my "test code" in MapPage.xaml.cs
public MapPage()
{
InitializeComponent();
//HttpRequest.getDirections(origin, destination, callbackDirections);
Device.BeginInvokeOnMainThread(() =>
{
customMap.RouteCoordinates = new List<Position>
{
new Position (37.797534, -122.401827),
new Position (37.776831, -122.394627)
};
});
//setupMap();
//setupMapCustom();
}
Because it doesn't works (for me), I took a look at my code and then, I saw that public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create<CustomMap, List<Position>>(
p => p.RouteCoordinates, new List<Position>()); was deprecated..
So I red on this post a different way to implement this binding, but it also said that this way is deprecated SEE HERE... I also saw some tutorials about binding which says that they put some code into their xaml, let me remember you mine
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:NAMESPACE;assembly=NAMESPACE"
x:Class="NAMESPACE.Controlers.MapPage">
<ContentPage.Content>
<local:CustomMap x:Name="customMap"/>
</ContentPage.Content>
</ContentPage>
I'm not using something as ItemSource="{PolylineBindable}"
The custom renderer from the example is not made for dynamic updating the path. It is just implemented for the case, where all points of the paths are known before initializing the map / drawing the path the first time. So you have this race condition, you ran into, because you are loading the directions from a web service.
So you have to do some changes:
RouteCoordinates must be a BindableProperty
public class CustomMap : Map
{
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create<CustomMap, List<Position>>(p => p.RouteCoordinates, new List<Position>());
public List<Position> RouteCoordinates
{
get { return (List<Position>)GetValue(RouteCoordinatesProperty); }
set { SetValue(RouteCoordinatesProperty, value); }
}
public CustomMap ()
{
RouteCoordinates = new List<Position>();
}
}
Update the Polyline whenever the coordinates change
Move the creation of the polyline from OnMapReady to UpdatePolyLine
call UpdatePolyLine from OnMapReady and OnElementPropertyChanged
public class CustomMapRenderer : MapRenderer, IOnMapReadyCallback
{
GoogleMap map;
Polyline polyline;
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
{
((MapView)Control).GetMapAsync(this);
}
}
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)
{
UpdatePolyLine();
}
}
private void UpdatePolyLine()
{
if (polyline != null)
{
polyline.Remove();
polyline.Dispose();
}
var polylineOptions = new PolylineOptions();
polylineOptions.InvokeColor(0x66FF0000);
foreach (var position in ((CustomMap)this.Element).RouteCoordinates)
{
polylineOptions.Add(new LatLng(position.Latitude, position.Longitude));
}
polyline = map.AddPolyline(polylineOptions);
}
public void OnMapReady(GoogleMap googleMap)
{
map = googleMap;
UpdatePolyLine();
}
}
Setting the data
Updating the positions changes a bit. Instead of adding the positions to the existing list, you have to (create a new list) and set it to RouteCoordinates. You can use Device.BeginInvokeOnMainThread to ensure, that the operation is performed on the UI thread. Else the polyline will not update.
Device.BeginInvokeOnMainThread(() =>
{
customMap.RouteCoordinates = new List<Position>
{
new Position (37.797534, -122.401827),
new Position (37.776831, -122.394627)
};
})
In your case it's something like
var list = new List<Position>(customMap.RouteCoordinates);
list.Add(directionMap.address_end.position);
customMap.RouteCoordinates = list;
Todo
On iOS you have now to implement a similar behavior (like UpdatePolyLine)
Note
That might not the most performant implementation, because you redraw everything instead of adding one point. But it's fine as long as you have no performance issues :)
I followed the tutorial available on Xamarin Docs and it worked for me with some changes based on #Sven-Michael Stübe answer
I load the coordinates from a WebService and then I create a separate List, and after this, I set the new list to the RouteCoordinates property on Custom Map.
Some changes are made on Android Renderer
I'm using MVVM.
CustomMap Class:
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create(nameof(RouteCoordinates), typeof(List<Position>), typeof(CustomMap), new List<Position>(), BindingMode.TwoWay);
public List<Position> RouteCoordinates
{
get { return (List<Position>)GetValue(RouteCoordinatesProperty); }
set { SetValue(RouteCoordinatesProperty, value); }
}
public CustomMap()
{
RouteCoordinates = new List<Position>();
}
ViewModel (Codebehind, in your case):
private async void LoadCoordinates(string oidAula, CustomMap mapa)
{
IsBusy = true;
var percurso = await ComunicacaoServidor.GetPercurso(oidAula); // Get coordinates from WebService
var pontos = percurso.Select(p => new Position(p.Latitude, p.Longitude)).ToList(); // Create coordinates list from webservice result
var latitudeMedia = percurso[percurso.Count / 2].Latitude;
var longitudeMedia = percurso[percurso.Count / 2].Longitude;
mapa.RouteCoordinates = pontos;
mapa.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(latitudeMedia, longitudeMedia), Distance.FromMiles(1.0)));
IsBusy = false;
}
XAML:
<maps:CustomMap
AbsoluteLayout.LayoutFlags = "All"
AbsoluteLayout.LayoutBounds = "0, 0, 1, 1"
VerticalOptions = "FillAndExpand"
HorizontalOptions = "FillAndExpand"
x:Name = "PercursoMapa" />
Android Renderer:
public class CustomMapRenderer : MapRenderer
{
bool isDrawn;
protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
Control.GetMapAsync(this);
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if ((e.PropertyName == "RouteCoordinates" || e.PropertyName == "VisibleRegion") && !isDrawn)
{
var polylineOptions = new PolylineOptions();
polylineOptions.InvokeColor(0x66FF0000);
var coordinates = ((CustomMap)Element).RouteCoordinates;
foreach (var position in coordinates)
polylineOptions.Add(new LatLng(position.Latitude, position.Longitude));
NativeMap.AddPolyline(polylineOptions);
isDrawn = coordinates.Count > 0;
}
}
}
This example have more than 3600 points of location and the polyline shows correctly on device:
Screenshot
Building on these answers, here is what I did to get it to work on iOS. This allows changing the route even after the map is loaded, unlike the Xamarin sample.
Firstly, custom map class as per #Sven-Michael Stübe with the update from #Emixam23:
public class CustomMap : Map
{
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create(nameof(RouteCoordinates), typeof(List<Position>), typeof(CustomMap), new List<Position>(), BindingMode.TwoWay);
public List<Position> RouteCoordinates
{
get { return (List<Position>)GetValue(RouteCoordinatesProperty); }
set { SetValue(RouteCoordinatesProperty, value); }
}
public CustomMap()
{
RouteCoordinates = new List<Position>();
}
}
Next, the iOS custom renderer:
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace KZNTR.iOS
{
public class CustomMapRenderer : MapRenderer
{
MKPolylineRenderer polylineRenderer;
CustomMap map;
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)
{
map = (CustomMap)sender;
UpdatePolyLine();
}
}
[Foundation.Export("mapView:rendererForOverlay:")]
MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlay)
{
if (polylineRenderer == null)
{
var o = ObjCRuntime.Runtime.GetNSObject(overlay.Handle) as MKPolyline;
polylineRenderer = new MKPolylineRenderer(o);
//polylineRenderer = new MKPolylineRenderer(overlay as MKPolyline);
polylineRenderer.FillColor = UIColor.Blue;
polylineRenderer.StrokeColor = UIColor.Red;
polylineRenderer.LineWidth = 3;
polylineRenderer.Alpha = 0.4f;
}
return polylineRenderer;
}
private void UpdatePolyLine()
{
var nativeMap = Control as MKMapView;
nativeMap.OverlayRenderer = GetOverlayRenderer;
CLLocationCoordinate2D[] coords = new CLLocationCoordinate2D[map.RouteCoordinates.Count];
int index = 0;
foreach (var position in map.RouteCoordinates)
{
coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude);
index++;
}
var routeOverlay = MKPolyline.FromCoordinates(coords);
nativeMap.AddOverlay(routeOverlay);
}
}
}
And finally, adding a polyline to the map:
Device.BeginInvokeOnMainThread(() =>
{
customMap.RouteCoordinates.Clear();
var plist = new List<Position>(customMap.RouteCoordinates);
foreach (var point in track.TrackPoints)
{
plist.Add(new Position(double.Parse(point.Latitude, CultureInfo.InvariantCulture), double.Parse(point.Longitude, CultureInfo.InvariantCulture)));
}
customMap.RouteCoordinates = plist;
var firstpoint = (from pt in track.TrackPoints select pt).FirstOrDefault();
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(double.Parse(firstpoint.Latitude, CultureInfo.InvariantCulture), double.Parse(firstpoint.Longitude, CultureInfo.InvariantCulture)), Distance.FromMiles(3.0)));
});
Not sure if this is the best way to do it, or the most efficient, I don't know much about renderers, but it does seem to work.
So after lot of searches and, of course, the answer of #Sven-Michael Stübe, you can have your proper maps which works on each platform "Android, iOS, WinPhone". Follow my code, then edit it following the #Sven-Michael Stübe's answer.
Once you finished everything, it could works (like for #Sven-Michael Stübe), but it also couldn't work (like for me). If it doesn't works, try to change the following code:
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create<CustomMap, List<Position>>(
p => p.RouteCoordinates, new List<Position>());
by
public static readonly BindableProperty RouteCoordinatesProperty =
BindableProperty.Create(nameof(RouteCoordinates), typeof(List<Position>), typeof(CustomMap), new List<Position>(), BindingMode.TwoWay);
See the documentation for more information about it. (Deprecated implementation)
Then the code works !
PS: You can have some troubles with the polyline at the end, which not following the road right, I'm working on it.
PS2: I'll also make a video to explain how to code your customMap to don't have to install a NuGet package, to be able to edit everything at the end ! (The first one will be in French, the second in English, this post will be edited when the video will be made)
Thank angain to #Sven-Michael Stübe !! Thank to up his answer as well :)

Resources