GetViewForAnnotation is never called - xamarin

I am creating a MapView where I want to display some custom Annotations.
So I think usually what you do is add some IMKAnnotation to the MKMapView using the AddAnnotation method. I make sure to invoke that on the main thread like:
new NSObject().InvokeOnMainThread(() => {
_mapView.AddAnnotation(myNewAnnotation);
});
After these I are added I do see that the MKMapView now contains all the annotations I added in the Annotations property when inspecting with the debugger.
However, the problem is that GetViewForAnnotation is never invoked, no matter how I do it.
I've tried:
_mapView.GetViewForAnnotation += ViewForAnnotation;
private MKAnnotationView ViewForAnnotation(MKMapView mapView, IMKAnnotation annotation) {
// do stuff here
}
I've tried implementing my own delegate:
public class MyMapViewDelegate : MKMapViewDelegate
{
public override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation) {
// do stuff
}
}
_delegate = new MyMapViewDelegate();
_mapView.Delegate = _delegate;
I've tried using WeakDelegate:
public class MapView : ViewController, IMKMapViewDelegate
{
private MKMapView _mapView;
public override void ViewDidLoad() {
_mapView = new MKMapView();
_mapView.WeakDelegate = this;
}
[Export("mapView:viewForAnnotation:")]
public MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation) {
// do stuff
}
}
Nothing seems to trigger the GetViewForAnnotation method. Any ideas what I do wrong?
EDIT:
A bit more details what I have now.
[Register("MapView")]
public class MapView : MvxViewController<MapViewModel>
{
private MKMapView _mapView;
private NMTAnnotationManager _annotationManager;
public override void ViewDidLoad()
{
base.ViewDidLoad();
_mapView = new MKMapView();
_mapView.GetViewForAnnotation += GetViewForAnnotation;
_annotationManager = new NMTAnnotationManager(_mapView);
var bindSet = this.CreateBindingSet<MapView, MapViewModel>();
bindSet.Bind(_annotationManager).For(a => a.ItemsSource).To(vm => vm.Locations).OneWay();
bindSet.Apply();
Add(_mapView);
View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
View.AddConstraints(
_mapView.AtTopOf(View),
_mapView.AtLeftOf(View),
_mapView.AtRightOf(View),
_mapView.AtBottomOf(View));
}
private MKAnnotationView GetViewForAnnotation(MKMapView mapview, IMKAnnotation annotation)
{
return null;
}
}
The NMTAnnotationManager simply weak subscribes to the INotifyCollectionChanged event the ObservableCollection which is used as ItemsSource in the binding. When the collection changes it simply adds and removes the Annotations from the MKMapView, nothing magical happens here. I have verified that it does indeed add, in this case 13, different IMKAnnotation instances to the MKMapView and they can be inspected in the Annotations property of it.
So as #Philip suggested in his answer, GetViewForAnnotation does get set before Annotations are added to the MapView. But if I put a breakpoint or some trace in the method it never gets hit.
The same code above, just with a simple MKMapViewDelegate like so:
public class MyMapViewDelegate : MKMapViewDelegate
{
public override void MapLoaded(MKMapView mapView)
{
Mvx.Trace("MapLoaded");
}
public override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
Mvx.Trace("GetViewForAnnotation");
return null;
}
}
Does not work either. Although, the MapLoaded event gets hit for each time the map is rendered, but why does GetViewForAnnotation not get hit?

The way I'm doing it is:
Storyboard with a ViewController which contains a MKMapView
MKMapView Delegate to the ViewController is set in IB in the Connections Inspector
In ViewDidLoad I make sure I set this first:
mapView.GetViewForAnnotation += GetViewForAnnotation;
I had problems with it never beeing called too, and I fixed it with making sure that I set the event for GetViewForAnnotation before I added some annotations.

OK, so apparently the problem was PEBKAC.
The annotation indeed got added to the MKMapView. However, none of them had their Coordinate property set. So obviously the map did not know where to present them and never called GetViewForAnnotation.

Related

Binding string with Button

I am trying to bind a string to a Button in pure C# (no XAML), but apparently I am doing it wrong, as the result of my code is that the button disappears.
I am defining my property as follows:
public string selectionString { get; set; }
And this is how I am binding the string to the button:
selectionString = "Hello";
selectionButton = new Button
{
TextColor = Color.Black
};
selectionButton.SetBinding(Button.TextProperty, "selectionString");
Children.Add(selectionButton);
I have tried to use BindingMode.TwoWay, but it doesn't work.
Of course, setting the text and removing the binding makes the button appear and work.
My need is just this: the button text should be the selectionString, and if this changes by an external event, so the button's text should change accordingly.
Am I missing something in how the binding works?
Bindings work against public properties on the view's binding context, and respond to INotifyPropertyChanged events firing. Hopefully this demonstrates for you.
public class MyViewModel : INotifyPropertyChanged
{
// Fire RaisePropertyChanged in the setter, I use Fody to weave this in
public string SelectionString {get;set;}
}
public class MyView : Page
{
protected override void OnBindingContextChanged()
{
if (BindingContext is MyViewModel)
{
this.SetBinding(Button.TextProperty, "SelectionString");
}
}
}

#UIScope annotation not respected for spring view?

I am facing an issue with the Vaadin spring annotation #UIScope, defined as follows:
#SpringComponent
#SpringView(name = AdminView.VIEW_NAME)
#UIScope
public class AdminView extends NavigatingView {
...
}
The view is created every time the navigation is opening the view. I would expect that it is created only once, on first time access.
However, if I replace #UIScope with #Scope(UIScopeImpl.VAADIN_UI_SCOPE_NAME) then it works as expected. Did I miss something?
It's related to the order of the #SpringView and #UIScope annotations, as the tutorial and the older wiki page briefly suggest:
// Pay attention to the order of annotations
It's probably related to how and when the annotations are processed. I did not dig that deep into the Vaadin code, but as per the the #SpringView javadoc it puts the view into a view-scope by default. Furthermore, I don't think you require the #SpringComponent annotation because you're already using #SpringView to register it a spring component.
Annotation to be placed on View-classes that should be handled by the SpringViewProvider.
This annotation is also a stereotype annotation, so Spring will automatically detect the annotated classes. By default, this annotation also puts the view into the view scope. You can override this by using another scope annotation, such as the UI scope, on your view class. However, the singleton scope will not work!
In the sample below, you'll find 2 views, the first one with the annotations in the correct order, and the second one with them swapped:
#SpringUI
#SpringViewDisplay
public class MyVaadinUI extends UI implements ViewDisplay {
/* UI */
private Panel springViewDisplay;
#Override
protected void init(VaadinRequest request) {
VerticalLayout mainLayout = new VerticalLayout();
HorizontalLayout buttonLayout = new HorizontalLayout();
springViewDisplay = new Panel();
buttonLayout.addComponent(new Button("1", event -> getNavigator().navigateTo(FirstView.VIEW_NAME)));
buttonLayout.addComponent(new Button("2", event -> getNavigator().navigateTo(SecondView.VIEW_NAME)));
mainLayout.addComponents(buttonLayout, springViewDisplay);
setContent(mainLayout);
}
#Override
public void showView(View view) {
springViewDisplay.setContent((Component) view);
}
/* VIEWS */
#UIScope
#SpringView(name = FirstView.VIEW_NAME)
public static class FirstView extends HorizontalLayout implements View {
public static final String VIEW_NAME = "";
#PostConstruct
private void init() {
System.out.println("Created first view");
addComponent(new Label("First view - " + LocalDateTime.now()));
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// no-op
}
}
#SpringView(name = SecondView.VIEW_NAME)
#UIScope
public static class SecondView extends HorizontalLayout implements View {
public static final String VIEW_NAME = "secondView";
#PostConstruct
private void init() {
System.out.println("Created second view");
addComponent(new Label("Second view - " + LocalDateTime.now()));
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// no-op
}
}
}
As you'll notice in the animation below, when navigating to the second view a new instance is always created, while navigating to the first one will reuse the initial instance:

OnNavigatedTo() method is not triggered in Xamarin.Forms

I have inherited my view model class from INavigateAware interface as below,
public class ViewModel : INavigationAware
{
public ViewModel()
{
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
// some codes
}
}
And called that view model in the associated view(the page I have been navigated to)
public partial class Page1 : ContentPage
{
ViewModel viewModel;
public Page1()
{
InitializeComponent();
viewModel = new ViewModel();
this.Content = myview; //myview is my control like grid
}
}
Now my problem is when I navigate to this page(Page1), OnNavigateTo() method in ViewModel is not triggered. Please someone helps me how to make trigger OnNavigateTo() method.
Thanks in advance.
First thing first, check if you have AutowireViewModel parameter in your page class set to True.
Second, you should not assign view model yourself, prism will do that for you, when you call PushViewModel
Third there is well known limitation in prism:
https://github.com/PrismLibrary/Prism/issues/563
There is also workaround suggested:
Create interface:
public interface IPageNavigationAware
{
void OnAppearing();
void OnDisappearing();
}
Derive your ViewModel class from this interface.
In the Views code behind:
protected override void OnAppearing()
{
(BindingContext as IPageNavigationAware)?.OnAppearing();
}
protected override void OnDisappearing()
{
(BindingContext as IPageNavigationAware)?.OnDisappearing();
}
The problem with that is that OnAppearing/OnDissapparing are not reliable navigation methods and do not accept parameters, but rather page lifecycle methods. They do not indicate when a page has been navigated to or from. You can have instances where a parent page can be appearing at the same time as multiple child pages are appearing. This will be addressed when Xamarin provides a proper navigation API.

overriding virtual event

I've got the following code
public delegate void NotificacaoScanner(NotifScanner e);
// interface
public interface IScanner
{
event NotificacaoScanner onFinalLeitura;
}
// abstract class that implements the interface
public abstract class ScannerGCPerif : IScanner
{
public virtual event NotificacaoScanner onFinalLeitura;
{
add { throw new NotImplementedException("Event not available for this service"); }
remove { throw new NotImplementedException("Event not available for this service"); }
}
}
// concrete class that implements the abstract class
public class ScannerBurroughs : ScannerGCPerif
{
public override event NotificacaoScanner onFinalLeitura;
}
Why when I subscribe the onFinalLeitura event of a ScannerBurroughs instance, it insists on execute the event declaration of the base class (ScannerGCPerif), where the exception is?
I ran your code and I did not get an exception. Let me explain what happens:
You override the event in your concrete class, but you do not provide implementation for adding and removing event handlers so the compiler generates the following code:
public class ScannerBurroughs : ScannerGCPerif
{
private NotificacaoScanner _onFinalLeitura; // Declare a private delegate
public override event NotificacaoScanner onFinalLeitura
{
add { _onFinalLeitura += value; }
remove { _onFinalLeitura -= value; }
}
}
Behind the scenes it adds a private delegate and autoimplements the add / remove event accessors. The base implementation never gets called when you subscribe. Try explicitly implementing the accessors, put some breakpoints in your code and see what happens.

BlackBerry - Fun with FieldManagers

I am trying to make a View class that provides a Horizontal or Vertical layout depending on how it is created. I'm using a delegate to achieve this.
class View extends Manager {
private Manager mDelegate;
public View(Manager inDelegate) {
mDelegate = inDelegate;
// the delegate is the only child of "this" manager.
super.add(mDelegate);
}
public void add(Field f) {
// all other children go into the delegate.
mDelegate.add(f);
}
// other methods that also delegate
}
When I instantiate a View object I pass in a Horizontal or Vertical field manager and then delegate calls to that. This is kinda what the Screen class does in blackberry.
Actually I am looking at the blackberry docs for Screen to see what calls it delegates (so I can emulate that) and I notice calls like this in Screen...
protected boolean keyChar(char c, int status, int time)
Delegates key generation event to the controlled field with focus.
This method invokes Manager.keyChar(char, int, int) on this screen's delegate manager.
So then it immediately dawns on me, how in the world are they calling a protected method on the screen's delegate? Or are the docs wrong and this method isn't delegated?
Anyone know how they accomplish this?
Reminding myself what protected means:
A protected method can be called by
any subclass within its class, but not
by unrelated classes.
This doesn't directly answer your question, but could you extend Screen (API here) instead of Manager and then call super(mDelegate) in your constructor? Then presumably whatever magic is necessary will just work?
Aside from that I would just suggest you try it and see if you can override the supposedly protected method!
I managed to work out a solution to this problem with help from some other SO questions.
My solution is to create an interface that provides the public access points for the protected methods and then subclass the Manager class and mix in that interface. The public method will then call its super's protected method.
Then the View class is then passed one of these Manager subclasses.
public interface ManagerDelegate {
Manager asManager();
// Provide public access points to any protected methods needed.
void doProtectedMethod();
}
public HorizontalDelegate extends HorizontalFieldManager implements ManagerDelegate {
public Manager asManager() {
return this;
}
public void doProtectedMethod() {
// call the Manager's protected method.
protectedMethod();
}
}
public VerticalDelegate extends VerticalFieldManager implements ManagerDelegate {
public Manager asManager() {
return this;
}
public void doProtectedMethod() {
// call the Manager's protected method.
protectedMethod();
}
}
public class View extends Manager {
private final ManagerDelegate mDelegate;
public View(ManagerDelegate inDelegate) {
mDelegate = inDelegate;
}
protected void protectedMethod() {
// Call into our delegate's public method to access its protected method.
mDelegate.doProtectedMethod();
}
public void publicMethod() {
// For public delegated methods I can just get the Manager instance from
// the delegate and call directly.
mDelegate.asManager().publicMethod();
}
}

Resources