Binding problems with using Selector and Messaging in Monotouch - binding

I've been trying to reproduce this Animate a view zoom-bouncing in?
in Monotouch but I hit an exception and I don't know what to do...
these statics methods are in a utility class.
An exception is raised on the sending the message to the first selector.
MonoTouch.Foundation.MonoTouchException: Objective-C exception thrown. Name: NSInvalidArgumentException Reason: -[MainScreen bounce1AnimationStopped]: unrecognized selector sent to instance 0x13908570
at at (wrapper managed-to-native) MonoTouch.ObjCRuntime.Messaging:void_objc_msgSend_IntPtr_IntPtr (intptr,intptr,intptr,intptr)
at AnimationManager.BounceAppear (MonoTouch.UIKit.UIViewController container, MonoTouch.UIKit.UIView view, Double duration) [0x0003a]
public static void BounceAppear(UIViewController container, UIView view, double duration = 0.5)
{
view.Transform = CGAffineTransform.MakeScale(0.001f, 0.001f);
UIView.BeginAnimations(null);
UIView.SetAnimationDuration(0.3f/1.5f);
UIView.SetAnimationDelegate(container);
var selector = new MonoTouch.ObjCRuntime.Selector("bounce1AnimationStopped");
Messaging.void_objc_msgSend_IntPtr_IntPtr(container.Handle, selector.Handle, container.Handle, view.Handle);
UIView.SetAnimationDidStopSelector(selector);
view.Transform = CGAffineTransform.MakeScale(1.1f, 1.1f);
UIView.CommitAnimations();
}
[Export("bounce1AnimationStopped:forView")]
public static void Bounce1AnimationStopped(UIViewController container,UIView view)
{
Console.WriteLine("Bounce1AnimationStopped");
UIView.BeginAnimations(null);
UIView.SetAnimationDuration(0.3f/2.0f);
UIView.SetAnimationDelegate(container);
var selector = new MonoTouch.ObjCRuntime.Selector("bounce2AnimationStopped");
Messaging.void_objc_msgSend_IntPtr_IntPtr(container.Handle, selector.Handle, container.Handle, view.Handle);
view.Transform = CGAffineTransform.MakeScale(0.9f,0.9f);
UIView.CommitAnimations();
}
[Export("bounce2AnimationStopped:forView")]
public static void Bounce2AnimationStopped(UIViewController container, UIView view)
{
Console.WriteLine("Bounce2AnimationStopped");
UIView.BeginAnimations(null);
UIView.SetAnimationDuration(0.3f/2.0f);
view.Transform = CGAffineTransform.MakeIdentity();
UIView.CommitAnimations();
}

Ok I forgot to register my class and have its reference pass to the messaging function
private static IntPtr class_ptr = Class.GetHandle("animationManager");
and then
Messaging.void_objc_msgSend_IntPtr_IntPtr(class_ptr, selector.Handle, container.Handle, view.Handle);

Related

iOS: Camera does not recognize QR Code on first load of view controller

I have a small app, that does read QR-Codes for a login and alternatively offers the possibility to hand-type the code and login.
The app starts and heads directly to the login (View). When I try to scan a qr code that does not work - the delegate is never called/the event never raised.
I adapted the approach from Larry OBrien http://www.knowing.net/index.php/2013/10/09/natively-recognize-barcodesqr-codes-in-ios-7-with-xamarin-ios/
And created my own ScannerView class for that use:
public sealed partial class ScannerView : UIView
{
private readonly AVCaptureVideoPreviewLayer _layer;
public AVCaptureSession Session { get; }
private readonly AVCaptureMetadataOutput _metadataOutput;
public event EventHandler<AVMetadataMachineReadableCodeObject> MetadataFound = delegate { };
public ScannerView (IntPtr handle) : base (handle)
{
Session = new AVCaptureSession();
var camera = AVCaptureDevice.DefaultDeviceWithMediaType(AVMediaType.Video);
var input = AVCaptureDeviceInput.FromDevice(camera);
Session.AddInput(input);
//Add the metadata output channel
_metadataOutput = new AVCaptureMetadataOutput {RectOfInterest = Bounds};
var metadataDelegate = new MetadataOutputDelegate();
var dispatchQueue = new DispatchQueue("scannerQueue");
_metadataOutput.SetDelegate(metadataDelegate, dispatchQueue);
Session.AddOutput(_metadataOutput);
_layer = new AVCaptureVideoPreviewLayer(Session)
{
MasksToBounds = true,
VideoGravity = AVLayerVideoGravity.ResizeAspectFill,
Frame = Bounds
};
Layer.AddSublayer(_layer);
// Hand event over to subscriber
metadataDelegate.MetadataFound += (s, e) => MetadataFound(s, e);
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
_layer.Frame = Bounds;
_metadataOutput.RectOfInterest = Bounds;
}
public void SetMetadataType(AVMetadataObjectType type)
{
//Confusing! *After* adding to session, tell output what to recognize...
_metadataOutput.MetadataObjectTypes = type;
}
}
And in my LoginView I do the following:
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
// Manipulate navigation stack
NavigationController.SetViewControllers(
NavigationController.ViewControllers.Where(
viewController => viewController is LoginView).ToArray(), false);
ScannerView.MetadataFound += (s, e) =>
{
Console.WriteLine($"Found: [{e.Type.ToString()}] {e.StringValue}");
LoginViewModel.BarCode = e.StringValue;
if (LoginViewModel.DoneCommand.CanExecute())
{
ScannerView.Session.StopRunning();
LoginViewModel.DoneCommand.Execute();
}
};
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
ScannerView.Session.StartRunning();
ScannerView.SetMetadataType(AVMetadataObjectType.QRCode | AVMetadataObjectType.EAN13Code);
}
Funny thing is, that this works once I logged in with the manual input and logged out again, so I'm on the same screen again (possibly not the same but a new instance of it as the GC may destroy the view as it is removed from the navigation stack?)
I have put the scannerview as a subview on the LoginView in the storyboard. For navigation I use MVVMCross. (just for info)
So: What am I doing wrong? What do I need to do to make it work on the first load? (I got it to do that once - with the same code... maybe it is a timing issue?)
Obviously this is a timing issue.
I solved it by adding a "Tap to scan" paradigm.
When tapping I execute the following code:
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
Console.WriteLine($"Current types to scan: {this.MetadataOutput.MetadataObjectTypes}");
this.SetMetadataType(this.MetadataObjectType);
Console.WriteLine($"New types to scan: {this.MetadataOutput.MetadataObjectTypes}");
}
public void SetMetadataType(AVMetadataObjectType type)
{
//Confusing! *After* adding to session, tell output what to recognize...
this.Session.BeginConfiguration();
this.MetadataOutput.MetadataObjectTypes = type;
this.Session.CommitConfiguration();
}
Where MetadataObjectType is set to the codes we're looking for before.
And that solves the problem - the scanning now works every time.
I think the magic part is the Begin- and CommitConfiguration call, as this also works, if I do not use the touch to scan paradigm.

How to return value to Objective-C using DLLImport (hiding UIWebView inputAccessoryView)

How to return nil value to Objective-C using DLLImport and MonoPInvokeCallback.
I'm trying to implement this solution iOS 7 UIWebView keyboard issue in Xamarin.iOs
I'm able to get callback using this approach How to port "method_getImplementation" and "method_setImplementation" to MonoTouch?
I'm trying something like this. Delegate is called but IntPtr.Zero value isn't returned to Objective-c.
delegate IntPtr CaptureDelegate(IntPtr block, IntPtr self, IntPtr uiView);
[MonoPInvokeCallback(typeof (CaptureDelegate))]
static IntPtr MyCapture(IntPtr block, IntPtr self, IntPtr uiView)
{
return IntPtr.Zero;
}
public void SetImplementation(UIView subview)
{
var method = class_getInstanceMethod(subview.ClassHandle, new Selector("inputAccessoryView").Handle);
var block_value = new BlockLiteral();
block_value.SetupBlock(MyCapture, null);
class_addMethod(subview.ClassHandle, new Selector("inputAccessoryView").Handle, imp_implementationWithBlock(ref block_value), IntPtr.Zero);
}
How to return nil to obj-c?

Out of Bounds Error when Deselecting Segment

I am attempting to programmatically deselect a UISegmentedControl from within Xamarin whenever the user re-taps the selected segment. I have created an inherited class that will determine if this is required, but whenever I try to set SelectedSegment to -1, the application bombs out, throwing an out of bounds exception.
public class UIDeselectableSegmentedControl : UISegmentedControl
{
private int previouslySelectedIndex = -1;
private bool isIOS7
{
get
{
return UIDevice.CurrentDevice.CheckSystemVersion(7, 0);
}
}
public UIDeselectableSegmentedControl()
{
this.ValueChanged += UIDeselectableSegmentedControl_ValueChanged;
}
void UIDeselectableSegmentedControl_ValueChanged(object sender, EventArgs e)
{
if (previouslySelectedIndex == this.SelectedSegment)
{
this.SelectedSegment = -1;
}
previouslySelectedIndex = this.SelectedSegment;
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
int initialIndex = this.SelectedSegment;
base.TouchesBegan(touches, evt);
//Pre-iOS7 Segment Changes are present in Touches Began
if (!isIOS7) CheckSelectedSegment(initialIndex, this.SelectedSegment);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
int initialIndex = this.SelectedSegment;
base.TouchesEnded(touches, evt);
//Post-iOS7 Segment Changes are present in Touches Ended
if (isIOS7) CheckSelectedSegment(initialIndex, this.SelectedSegment);
}
/// <summary>
/// Compare the SelectedSegment with the Current Index value. If it is the same, raise the Value Changed event.
/// </summary>
/// <param name="index">Currently Selected Segment</param>
private void CheckSelectedSegment(int oldIndex, int newIndex)
{
if (oldIndex == newIndex)
{
this.SendActionForControlEvents(UIControlEvent.ValueChanged);
}
}
}
If I change it to this.SelectedSegment = 0 then the control will successfully force the SegementedControl to the first item whenever you re-tap another selected item, but if I have it to set it to -1, then it throws the following exception:
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 4294967295 beyond bounds [0 .. 1]'
Is there a different way I should be deselecting the segment? According to this answer this is the correct way in Objective-C, and the Metadata comment on SelectedSegment indicates this is it correct:
//
// Summary:
// The index of the selected segment.
//
// Remarks:
// Set to -1 to turn off the currently selected segment. If MonoTouch.UIKit.UISegmentedControl.Momentary
// == true the SelectedSegment property is ignored.
In typical form, I think I've solved my own issue. I'm not sure why (perhaps someone can clarify this), but it seems to be something to do with trying to change the SelectedSegment within the ValueChanged event. Possibly some sort of mutation exception essentially.
I've reworked my code, so that the method called within TouchesEnded/Began just makes the change itself directly, rather than forcing ValueChanged to be called and changing it itself. In a way, that makes more sense, since ValueChanged is reacting to a change - it probably shouldn't be responsible for making the change:
public class UIDeselectableSegmentedControl : UISegmentedControl
{
private bool isIOS7
{
get
{
return UIDevice.CurrentDevice.CheckSystemVersion(7, 0);
}
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
int initialIndex = this.SelectedSegment;
base.TouchesBegan(touches, evt);
//Pre-iOS7 Segment Changes are present in Touches Began
if (!isIOS7) DeselectSegmentIfApplicable(initialIndex, this.SelectedSegment);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
int initialIndex = this.SelectedSegment;
base.TouchesEnded(touches, evt);
//Post-iOS7 Segment Changes are present in Touches Ended
if (isIOS7) DeselectSegmentIfApplicable(initialIndex, this.SelectedSegment);
}
/// <summary>
/// Compare the SelectedSegment with the Current Index value. If it is the same, raise the Deselect the Segment.
/// </summary>
/// <param name="oldIndex">the previously selected segment</param>
/// <param name="newIndex">The newly selected segment</param>
private void DeselectSegmentIfApplicable(int oldIndex, int newIndex)
{
if (oldIndex == newIndex)
{
this.SelectedSegment = -1;
}
}
}

ASIHTTPRequest Monotouch Binding Callback/Event

I am trying to wrap ASIHTTPRequest objective-c library to monotouch and I am stuck on .
The objective-c header file contains
#property (assign) SEL requestDidFinishSelector;
and when I use monotouch binding generator it generates
[Export ("requestDidStartSelector")]
Selector RequestDidStartSelector { get; set; }
This compiles successfully but it does not get called. I want to be able to do
networkQueue.RequestDidFinish += HandleRequestFinish;
from my C# code.
Does anyone already have ASIHTTPRequest monotouch binding or guide me on how to hook up callback events?
Thanks.
The selector will only return the handle to the "selector" that you can use.
What you need to do is write your event handler more or less like this:
class MyCallbacker {
public MyCallbacker (Action t) { this.t = t; }
[Preserve (Conditional=true)]
[Export ("MyCallBack")]
void Callback () {
t ();
}
}
event RequestDidFinish {
add {
requestDidFinishSelector = new Selector ("MyCallback");
new MyCallbacker (() => value);
}
}

Printing in Monomac

How do I print in monomac? This is as far as I can get, but I can't seem to get a reference to the graphics context in the NSView. If I add a control to the PrintDoc that's fine, but I want to draw.
//Print Function
void Print(){
PrintDoc NewDoc = new PrintDoc ();
NewDoc.SetFrameSize(new SizeF(600,1000));
NSPrintOperation P = NSPrintOperation.FromView (NewDoc);
P.RunOperation ();
}
//NSView to be printed
class PrintDoc:NSView
{
public PrintDoc ()
{
}
public override void DrawRect (System.Drawing.RectangleF dirtyRect)
{
//NSPrintOperation.CurrentOperation.Context !! this is null
//NSGraphicsContext.CurrentContext !! this hangs
}
}
I've managed to get it working by getting the context manually, instead of using NSGraphicsContext.CurrentContext:
https://github.com/picoe/Eto/blob/feature/printing/Source/Eto.Platform.Mac/Forms/Printing/PrintDocumentHandler.cs#L39
Snippet:
static IntPtr selCurrentContext = Selector.GetHandle ("currentContext");
static IntPtr classNSGraphicsContext = Class.GetHandle ("NSGraphicsContext");
public override void DrawRect (System.Drawing.RectangleF dirtyRect)
{
var operation = NSPrintOperation.CurrentOperation;
var context = new NSGraphicsContext(Messaging.IntPtr_objc_msgSend (classNSGraphicsContext, selCurrentContext));
// this causes monomac to hang for some reason:
//var context = NSGraphicsContext.CurrentContext;
}

Resources