Adding a button in UICollectionViewCell with a click handler using Xamarin.iOS - xamarin.ios

I am rewriting a screen that used a UITableView before to UICollectionView. But I am having trouble with the click handler on a button inside the cell.
collectionView.RegisterNibForCell (DocumentViewCell.Nib, docCellId);
...
public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
var docCell = (DocumentViewCell)collectionView.DequeueReusableCell (docCellId, indexPath);
docCell.BtnDelete.Hidden = !EditMode;
docCell.BtnDelete.TouchUpInside += delegate(object sender, EventArgs e) {
Logging.Debug("Crashes before if reaches here");
};
return docCell;
}
I am aware that the cell is begin reused and this most likely won't work when that happens but now it crashes immediately with just one element in the list when pressing the delete button. Everything works great until I hit the button and then I get the stacktrace below. I see no reason for that happening based on my UITableView code that is almost identical.
Have anyone done this using Nib based CollectionView cells? Any help is very appreciated!
Stacktrace:
at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication.UIApplicationMain (int,string[],intptr,intptr) <IL 0x0009f, 0xffffffff>
at MonoTouch.UIKit.UIApplication.Main (string[],string,string) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38
at SalesApp.Application.Main (string[]) [0x00000] in /MyApp/Main.cs:18
at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <IL 0x00050, 0xffffffff>
Native stacktrace:
0 SalesApp 0x0009a85c mono_handle_native_sigsegv + 284
1 SalesApp 0x0000e138 mono_sigsegv_signal_handler + 248
2 libsystem_c.dylib 0x990a78cb _sigtramp + 43
3 ??? 0xffffffff 0x0 + 4294967295
4 UIKit 0x01990258 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 61
5 UIKit 0x01a51021 -[UIControl sendAction:to:forEvent:] + 66
6 UIKit 0x01a5157f -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 578
7 UIKit 0x01a506e8 -[UIControl touchesEnded:withEvent:] + 546
8 UIKit 0x01c541d3 _UIGestureRecognizerUpdate + 7407
9 CoreFoundation 0x03ecbafe __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
Update
This code works fine
public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
var docCell = (DocumentViewCell)collectionView.DequeueReusableCell (DocumentViewCell.Key, indexPath);
docCell.BtnDelete.Hidden = !EditMode;
docCell.BtnDelete.TouchUpInside -= HandleTouchUpInside;
docCell.BtnDelete.TouchUpInside += HandleTouchUpInside;
return docCell;
}
void HandleTouchUpInside (object sender, EventArgs e)
{
Logging.Debug("No crash");
}

The issue is that there's no managed reference to the docCell instance you return from GetCell method. That means the GC can collect it whenever it wants.
When it's needed again it will be resurfaced to the managed world (using the IntPtr constructor). It's gonna be the same (re-used) native instance but a new managed instance. Your crash occurs because the event points to the old, managed, instance (that was collected).
The easy solution is to keep a cache of the created cells as along as the view exists. That will ensure the GC won't collect the (managed) cells before they are used. There's several answers showing this for UITableView.
Note: I thought we fixed (hided) this in recent version of Xamarin.iOS. Maybe it was just for UITableView !?! need to check on this...

I guess DequeueReusableCell returns null as it's the first cell being drawn.
Your code should look like this instead:
public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
var docCell = (DocumentViewCell)collectionView.DequeueReusableCell (docCellId, indexPath);
if (docCell == null)
docCell = new UICollectionViewCell (...);
docCell.BtnDelete.TouchUpInside += delegate(object sender, EventArgs e) {
Logging.Debug("Crashes before if reaches here");
};
return docCell;
}
replace the ellipses (...) by whatever creates your cell.

Related

How to enable search for a RadioGroup DVC in MonoTouch.Dialog?

I have a RadioGroup with very many RadioElements as a Sub-DialogViewController:
Root.Add(
new Section() {
new RootElement ("Demo", new RadioGroup ("demogroup", 0)) {
new Section () {
from demoItem in bigItemList
select (Element) new RadioElement (demoItem)
}
}
}
);
I want to enable Search for this nested DVC to make picking the right RadioElement simpler. Therefor I implemented a custom RootElement which combines passing a Group and creating a DVC with EnableSearch and used it instead of the one above:
using System.Collections.Generic;
namespace MonoTouch.Dialog
{
public class SearchableRootElement : RootElement
{
public SearchableRootElement(string caption, Group group) : base(caption, group)
{
this.createOnSelected = x => {
return new DialogViewController(x) { EnableSearch = true };
};
}
}
}
Unfortunately when typing into the Searchbar of the sub DVC I get the following crash:
Unhandled Exception:
System.NullReferenceException: Object reference not set to an instance of an object
at MonoTouch.Dialog.RadioElement.GetCell (MonoTouch.UIKit.UITableView tv) [0x00019] in /Developer/MonoTouch/Source/MonoTouch.Dialog/MonoTouch.Dialog/Elements.cs:1066
at MonoTouch.Dialog.DialogViewController+Source.GetCell (MonoTouch.UIKit.UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath) [0x00029] in /Developer/MonoTouch/Source/MonoTouch.Dialog/MonoTouch.Dialog/DialogViewController.cs:341
at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38
at Demo.iOS.Application.Main (System.String[] args) [0x00001] in /Users/rodjatrappe/Projects/Claas/Dev/Apps/Demo.iOS/Main.cs:16
2013-06-22 14:15:02.296 DemoiOS[547:21b03] Unhandled managed exception: Object reference not set to an instance of an object (System.NullReferenceException)
at MonoTouch.Dialog.RadioElement.GetCell (MonoTouch.UIKit.UITableView tv) [0x00019] in /Developer/MonoTouch/Source/MonoTouch.Dialog/MonoTouch.Dialog/Elements.cs:1066
at MonoTouch.Dialog.DialogViewController+Source.GetCell (MonoTouch.UIKit.UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath) [0x00029] in /Developer/MonoTouch/Source/MonoTouch.Dialog/MonoTouch.Dialog/DialogViewController.cs:341
at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38
at Demo.iOS.Application.Main (System.String[] args) [0x00001] in /Users/rodjatrappe/Projects/Claas/Dev/Apps/Demo.iOS/Main.cs:16
Why is it crashing and how to archive the feature I described above?
I cannot give you a direct answer but you may (assuming your source is not out of sync with this one) want to have a look at Line 1066 -
https://github.com/migueldeicaza/MonoTouch.Dialog/blob/master/MonoTouch.Dialog/Elements.cs
if (!(root.group is RadioGroup))
Is root null? Consider downloading the MTD source code and debugging it, check how you are creating your DVC.
You could also replace your LINQ with a couple of hard coded sections, ensure that is not your issue.
Hope this helps
The bug report here includes a workaround for the root cause of the issue you're experiencing, but also talks about how filtering will then cause a usability issue of marking the nth element as selected even after a filter has been applied.
https://github.com/migueldeicaza/MonoTouch.Dialog/issues/203
If you don't want to update the core MTD code, you could use that same technique by putting it in your own UIBarSearchDelegate. Unfortunately, the default SearchDelegate class is internal, so you'll need to add all of the code in your delegate. I was able to do this and get it working without changing the MTD source:
public override void LoadView()
{
base.LoadView();
((UISearchBar)TableView.TableHeaderView).Delegate = new MySearchBarDelegate(this);
}
And then you use this instead of the base method:
public override void TextChanged (UISearchBar searchBar, string searchText)
{
container.PerformFilter (searchText ?? "");
foreach (var s in container.Root)
s.Parent = container.Root;
}

push a view controller from selecting a row in a table view

So I have a UITableView where I want to push to a details controller when the row is selected.
My attempt was to:
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
var controller = new BlogDetailViewController();
navController.PushViewController(controller, true);
tableView.DeselectRow (indexPath, true);
}
But I am getting the complaint that BlogDetailViewController() takes an argument.
My BlogDetailViewController:
public partial class BlogDetailViewController : UIViewController
{
public string blogTitle;
public BlogDetailViewController (IntPtr handle) : base (handle)
{
}
}
What is Intptr handle and how do I pass it in? I tried adding a zero argument constructor and pushing to that, but when I ran the app it pushed to a black screen.
If you're instantiating it from a XIB, I believe you want to use
UIStoryboard board = UIStoryboard.FromName ("name", null);
UIViewController ctrl = (UIViewController)board.InstantiateViewController ("name");
UIApplication.SharedApplication.KeyWindow.RootViewController = view;
add this line also in last

Issues with Bindings: Calling base method on binding class calls override method. Leads to infinite recursion

I'm working on a MonoTouch project that uses Cordova. I created a binding for Cordova (based on the one created by Scott Blomquist).
I have been able to instantiate the classes fairly well without fail. When I try to extend them however, I run into issues. For example, if I merely try extend the CDVViewController, without even overriding anything, when we get to the code that instantiates this extended class, the system quietly exits with no error messages, and the following exit lines.
The program 'Mono' has exited with code 0 (0x0).
The program '[5600] ***.vshost.exe: Managed (v4.0.30319)' has exited with code -1 (0xffffffff).
Here is the class I'm trying to instantiate
public class WebViewController : CDVViewController
{
}
And here's the binding behind it
[BaseType (typeof (UIViewController))]
interface CDVViewController {
[Export ("webView")]
UIWebView WebView { get; set; }
[Export ("pluginObjects")]
NSMutableDictionary PluginObjects { get; }
[Export ("pluginsMap")]
NSDictionary PluginsMap { get; }
[Export ("settings")]
NSDictionary Settings { get; }
[Export ("whitelist")]
CDVWhitelist Whitelist { get; }
[Export ("loadFromString")]
bool LoadFromString { get; }
[Export ("useSplashScreen")]
bool UseSplashScreen { get; set; }
[Export ("activityView")]
UIActivityIndicatorView ActivityView { get; }
[Export ("imageView")]
UIImageView ImageView { get; }
[Export ("wwwFolderName")]
string WwwFolderName { get; set; }
[Export ("startPage")]
string StartPage { get; set; }
[Export ("commandQueue")]
NSObject CommandQueue { get; set; }
[Export ("commandDelegate")]
NSObject CommandDelegate { get; set; }
[Export ("userAgent")]
string UserAgent { get; }
[Export ("printMultitaskingInfo")]
void PrintMultitaskingInfo ();
[Export ("createGapView")]
void CreateGapView ();
[Export ("newCordovaViewWithFrame:")]
UIWebView NewCordovaView(RectangleF bounds);
[Export ("javascriptAlert:")]
void JavascriptAlert (string text);
[Export ("appURLScheme")]
string AppUrlScheme ();
[Export ("parseInterfaceOrientations:")]
NSArray ParseInterfaceOrientations (NSArray orientations);
[Export ("supportsOrientation:")]
bool SupportsOrientation (UIInterfaceOrientation orientations);
[Export ("getCommandInstance:")]
NSObject GetCommandInstance (string pluginName);
[Export ("registerPlugin:withClassName:")]
void RegisterPluginWithClassName (CDVPlugin plugin, string className);
[Export ("URLisAllowed:")]
bool UrlIsAllowed (NSUrl url);
[Static] [Export ("getBundlePlist:")]
NSDictionary GetBundlePlist (string plistName);
[Static] [Export ("applicationDocumentsDirectory")]
string ApplicationDocumentsDirectory ();
// The following methods and properties come from UIWebViewDelegate, but we can't do multiple inheritance
[Export ("webView:shouldStartLoadWithRequest:navigationType:")]
bool ShouldStartLoad (UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType);
}
The CDVViewController.h that I build the binding for is available here
Any help that anyone can offer would be greatly appreciated.
Update #1
This appears to have been caused by a segmentation fault. For anyone else experiencing this, if you go onto the Mac and check the log, you will likely find the same. You can do this by opening the terminal, and typing in tail -f /var/log/system.log as apparently this is where the logs for the simulator go.
In my log, I found the following. *** is used for redacting the application name.
Mar 28 16:51:16 macmini1 com.apple.launchd.peruser.55385368[409] (UIKitApplication:com.***.***[0x5ef3][14219]): Job appears to have crashed: Segmentation fault: 11
Mar 28 16:51:16 macmini1 SpringBoard[516]: Application '***' exited abnormally with signal 11: Segmentation fault: 11
Mar 28 16:51:16 macmini1 ReportCrash[14132]: Saved crash report for ***[14219] version ??? (???) to /Users/jstarke/Library/Logs/DiagnosticReports/***_2013-03-28-165116_macmini1.crash
Mar 28 16:51:16 macmini1 ReportCrash[14132]: Removing excessive log: file://localhost/Users/jstarke/Library/Logs/DiagnosticReports/***_2013-03-28-135722_macmini1.crash
Update #2
I found the crash reports mentioned above. Here is the latest crash report information, including the crashed thread
Process: *** [29880]
Path: /Users/*/Library/Application Support/iPhone Simulator/*/***.app/***
Identifier: ***
Version: ??? (???)
Code Type: X86 (Native)
Parent Process: launchd [409]
Date/Time: 2013-03-29 11:44:48.882 -0700
OS Version: Mac OS X Server 10.7.5 (11G63)
Report Version: 9
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_PROTECTION_FAILURE at 0x00000000bf7fff7c
VM Regions Near 0xbf7fff7c:
Stack 00000000b050b000-00000000b052c000 [ 132K] rw-/rwx SM=COW
--> Stack 00000000bc000000-00000000bf800000 [ 56.0M] ---/rwx SM=NUL
Stack 00000000bf800000-00000000c0000000 [ 8192K] rw-/rwx SM=COW
Application Specific Information:
iPhone Simulator 358.4, iPhone OS 6.0 (iPhone/10A403)
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libsystem_c.dylib
0x954d458c szone_malloc_should_clear + 12
1 libsystem_c.dylib
0x954d566b szone_malloc + 24
2 libsystem_c.dylib
0x9550b962 malloc_zone_malloc + 77
3 libsystem_c.dylib
0x9550c882 malloc + 50
4 libobjc.A.dylib
0x041985c9 class_copyProtocolList + 157
5 CoreFoundation
0x03f72962 __methodDescriptionForSelector + 178
6 CoreFoundation
0x03fe65a3 -[NSObject(NSObject) methodSignatureForSelector:] + 51
7 ***
0x00254352 monotouch_trampoline + 82
8 ???
0x16c4ab0b 0 + 381987595
9 ???
0x16c7428c 0 + 382157452
10 ???
0x16c743dc 0 + 382157788
11 ***
0x0004cf22 mono_jit_runtime_invoke + 722
12 ***
0x001b03fe mono_runtime_invoke + 126
13 ***
0x00255166 monotouch_trampoline + 3686
Lines 8 - 13 repeat a large number of times (no additional information)
506 ???
0x16c4ab0b 0 + 381987595
507 ???
0x16c7428c 0 + 382157452
508 ???
0x16c743dc 0 + 382157788
509 ***
0x0004cf22 mono_jit_runtime_invoke + 722
510 ***
0x001b03fe mono_runtime_invoke + 126
511 ***
0x00255166 monotouch_trampoline + 3686
Update #3
In an effort to get some more information, I decided to override all of the methods in the CDVViewController within my local WebViewController, and then return the value from the base equivalent of each method, and set a breakpoint on the method, and put a Console.WriteLine saying that we entered and exited each method.
To my surprise, the reason for the many many duplications in Update #2 has to do with the fact that when WebViewController.ShouldAutoRotateToInterfaceOrientation is called, we call the base class CDVViewController.ShouldAutoRotateToInterfaceOrientation, which apparently in turn calls WebViewController.ShouldAutoRotateToInterfaceOrientation, and so on.
Looking at the source code for the version of Cordova I'm using (here), shouldAutorotateToInterfaceOrientation: would call supportsOrientation:, which would return from an NSArray.
I can't see any reason why CDVViewController.ShouldAutoRotateToInterfaceOrientation should ever call WebViewController.ShouldAutoRotateToInterfaceOrientation. Does anyone in the Xamarin or Cordova community have any idea?
If anyone else is experiencing a similar issue relating to Bindings, I managed to get this working thanks to the advice of Rolf Bjarne Kvinge (Binding issues: extending a class leads method calling itself?)
It turns out that a flag that I was using (without really understanding what it did) was to blame for this. When generating the DLL using btouch the -e flag Generates smaller classes that can not be subclassed.
Removing this will likely solve your problem, like it did for me.

MonoTouch.Dialog : Element Delete Event

Using Miguel de Icaza's Patterns for Creating UITableViewCells, I have created a custom UITableViewCell and turned that into a MonoTouch.Dialog Element. I'm using the elements API to create an edit form, using a few of my custom elements.
I'm trying to figure out how to respond to the deletion of the element. My custom element has a reference to the record it represents in the database. I want to respond to a deleted event in the same way I would respond to a Selected event, where I get the DialogViewController, UITableView, and NSIndexPath. Assuming such an event existed for an Element that I can respond to, I would fire a delete statement to the database with the given record id.
Based on Miguel's answer, I added a public Delete method to the sub classed Element called MyDataElement.
public class MyDataElement : Element {
static NSString key = new NSString ("myDataElement");
public MyData MyData;
public MyDataElement (MyData myData) : base (null)
{
MyData = myData;
}
public override UITableViewCell GetCell (UITableView tv)
{
var cell = tv.DequeueReusableCell (key) as MyDataCell;
if (cell == null)
cell = new MyDataCell (MyData, key);
else
cell.UpdateCell (MyData);
return cell;
}
public void Delete() {
Console.WriteLine(String.Format("Deleting record {0}", MyData.Id));
}
}
Then over on my sub classed DialogViewController, I handle the CommitEditingStyle method, cast the element as MyDataElement, then call the Delete method:
public class EntityEditingSource : DialogViewController.Source {
public EntityEditingSource(DialogViewController dvc) : base (dvc) {}
public override bool CanEditRow (UITableView tableView, NSIndexPath indexPath)
{
// Trivial implementation: we let all rows be editable, regardless of section or row
return true;
}
public override UITableViewCellEditingStyle EditingStyleForRow (UITableView tableView, NSIndexPath indexPath)
{
// trivial implementation: show a delete button always
return UITableViewCellEditingStyle.Delete;
}
public override void CommitEditingStyle (UITableView tableView, UITableViewCellEditingStyle editingStyle, NSIndexPath indexPath)
{
// In this method, we need to actually carry out the request
var section = Container.Root [indexPath.Section];
var element = section [indexPath.Row];
//Call the delete method on MyDataElement
(element as MyDataElement).Delete();
section.Remove (element);
}
}
You would have to modify the source to handle the delete event in the Source class and dispatch that message to the Element, in the same way that it is done for the other events.

JSF 2 : Is this a good approach to handle Business exceptions?

Following my previous question in Creating FacesMessage in action method outside JSF conversion/validation mechanism? , i'm trying to handle the exceptions thrown from the business layer outside my managed beans.
The strategy is to search and convert business exceptions to faces messages in the a PhaseListener,.
It is working as i expected, but im just wondering if im just reinventing the wheel, or doing it right with the wrong way ?
Here's my code sample snippet :
public class BusinessExceptionHandler implements PhaseListener {
#Override
public void afterPhase(PhaseEvent phaseEvent) {
ExceptionHandler exceptionHandler = phaseEvent.getFacesContext().getExceptionHandler();
// just debugging the handled exception, nothing here
/*
for (ExceptionQueuedEvent event : exceptionHandler.getHandledExceptionQueuedEvents()) {
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
System.out.println("handled exception : " + context.getException());
}*/
for (Iterator<ExceptionQueuedEvent> it = exceptionHandler.getUnhandledExceptionQueuedEvents().iterator();
it.hasNext();) {
ExceptionQueuedEvent event = it.next();
ExceptionQueuedEventContext eventContext = (ExceptionQueuedEventContext) event.getSource();
Throwable e = eventContext.getException();
System.out.println("unhandled exception : " + e);
// get the root cause exception
while (e.getCause() != null) {
e = e.getCause();
}
System.out.println("cause exception : " + e +
", cause exception is BE : " + (e instanceof BusinessException));
// handle BE
if (e instanceof BusinessException) {
BusinessException be = (BusinessException) e;
System.out.println("processing BE " + be);
FacesMessage message = Messages.getMessage(
"com.corejsf.errors",
be.getMessage(),
be.getParamValues()
);
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, message);
it.remove(); // remove the exception
// works fine without this block, if BE is thrown, always return to the original page
/*
NavigationHandler navigationHandler = context.getApplication().getNavigationHandler();
System.out.println("navigating to " + context.getViewRoot().getViewId());
navigationHandler.handleNavigation(context, context.getViewRoot().getViewId(), null);
*/
}
}
}
#Override
public void beforePhase(PhaseEvent phaseEvent) {
}
#Override
public PhaseId getPhaseId() {
return PhaseId.INVOKE_APPLICATION;
}
}
Thank you !
Regards,
Albert Kam
Not sure why you're going this route, creating and registering your own exception handler is really easy. Though easier will be Seam Catch ( http://seamframework.org/Seam3/CatchModule and http://docs.jboss.org/seam/3/catch/3.0.0.Alpha1/reference/en-US/html_single/ ). I don't have a JSF bridge just yet, but it will be very easy to do. Then all you have to do is write one method that will handle a specific exception, and you'll be done!
Your approach is an intermix of JSF 1.x and JSF 2.x exception handling approaches. Since you're using JSF 2.x, I'd suggest a pure JSF 2.x approach without a PhaseListener and with help of the new ExceptionHandler API.
You can find an example which treats the ViewExpiredException in page 282 of the book "JSF 2.0: The Complete Reference" which is online available here (click the 1st result link).

Resources