Xamarin binding Events WeakDelegate issue - events

I have following
iOS code:
#protocol TestDelegate
- (void)onSuccess:(NSString*)token;
#end
#interface Utility : NSObject
#property (nullable, weak, getter = getTestDelegate, setter = setTestDelegate:) id<TestDelegate> delegate;
#end
and Sharpie generated code with delegate to event mapping:
[Protocol, Model]
public interface TestDelegate
{
[Export ("onSuccess:")]
void OnSuccess (string token);
}
[BaseType(typeof(NSObject),
Delegates = new string[] { "WeakDelegate" },
Events = new Type[] { typeof(TestDelegate) })
public interface Utility
{
[Wrap ("WeakDelegate")]
[NullAllowed]
TestDelegate Delegate { [Bind ("getTestDelegate")] get; [Bind ("setTestDelegate:")] set; }
[NullAllowed, Export ("delegate", ArgumentSemantic.Weak)]
NSObject WeakDelegate { [Bind ("getTestDelegate")] get; [Bind ("getTestDelegate:")] set; }
}
When I'm attaching only to the events and not using delegate property then events are not fired.
Correct me if I’m wrong but when using only events then nothing is referring to the internally created _XDelegate so it will be garbage collected right away.
In my case I had to store internal _XDelegate after attaching to the events to make them work.
public class TestClass
{
private readonly Delegate _del;
public TestClass()
{
iOS.SingletonInstance().OnSuccess += HandleOnSuccess;
_del = iOS.SingletonInstance().Delegate; //store Delegate instance to make events work
}
}

The issue was somewhere else - SingletonInstance() is a method instead of property so nothing is referring to the returned object so it's being GCed.

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");
}
}
}

Xamarin exception when framework callback passes an interface

I'm trying to bind a framework written in Swift to my Xamarin application. The framework has a delegate with the following method:
onError(error: IError)
IError is a protocol in Swift (4):
#objc public protocol IError {
var message: String { get }
}
It's binding, as generated by sharpie and modified by me, looks like this:
// #protocol IError
[BaseType(typeof(NSObject))] // <- Added by me. Won't compile without it.
[Protocol, Model]
abstract class IError
{
// #required #property (readonly, copy, nonatomic) NSString * _Nonnull message;
[Abstract]
[Export("message")]
string Message { get; }
}
When the callback is being called, I get the following error in my application:
System.MemberAccessException has been thrown
Cannot create an instance of IError because it is an abstract class
Why is my application trying to instantiate IError? isn't the framework supposed to do this? Does it have to do with [BaseType(typeof(NSObject))]?
Finally found the solution here. You need to declare a bare interface with the same name but starting with 'I' in your binding and then it magically creates a wrapper for the interface which you receive instead of the real object.
The binding now looks like this:
//bare interface
interface IIError {}
// #protocol IError
[BaseType(typeof(NSObject))] // <- Added by me. Won't compile without
it.
[Protocol, Model]
abstract class IError
{
// #required #property (readonly, copy, nonatomic) NSString *
_Nonnull message;
[Abstract]
[Export("message")]
string Message { get; }
}
The binding for the method then looks like this:
[Abstract]
[Export("onError:")]
void OnError(IIError error);
EDIT:
It seems to also be documented here. Missed it somehow.

Xamarin iOS delegate BaseType usage

I'm just wondering why delegate in binding iOS project has to use BaseType(typeof(NSObject)) attribute when it's iOS counterpart does not use NSObject
iOS code:
#protocol TestDelegate
- (void)onSuccess:(NSString*)token;
#end
#interface Utility : NSObject
#property (nullable, weak, getter = getTestDelegate, setter = setTestDelegate:) id<TestDelegate> delegate;
#end
Sharpie code with delegate to event mapping added:
[Protocol, Model]
public interface TestDelegate
{
[Export ("onSuccess:")]
void OnSuccess (string token);
}
[BaseType(typeof(NSObject),
Delegates = new string[] { "WeakDelegate" },
Events = new Type[] { typeof(TestDelegate) })
public interface Utility
{
[Wrap ("WeakDelegate")]
[NullAllowed]
TestDelegate Delegate { [Bind ("getTestDelegate")] get; [Bind ("setTestDelegate:")] set; }
[NullAllowed, Export ("delegate", ArgumentSemantic.Weak)]
NSObject WeakDelegate { [Bind ("getTestDelegate")] get; [Bind ("getTestDelegate:")] set; }
}
BaseType attribute was not generated on TestDelegate by Sharpie because iOS native code was not using <NSObject> in its protocol.
This fails with "The type or namespace name TestDelegate' does not exist in the namespaceTest'. Are you missing an assembly reference? (CS0234) (Test.iOS)".
When I add [BaseType(typeof(NSObject))] on top of the TestDelegate it works like a charm.
The question is why this is needed?

GetViewForAnnotation is never called

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.

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.

Resources