Reputation: 307
I have customized my table view cell accessory with button. Everything went fine until event handler.
Here is my code. [EDIT]
public class ProductTableSource: UITableViewSource
{
protected List<Product> tableItems = new List<Product> ();
protected string cellIdentifier = "producdetailscell";
private RootViewController controller;
protected Transaction transaction;
public UIButton addItemButton;
public UIImage Image;
public ProductTableSource (List<Product> items, RootViewController controller)
{
this.tableItems = items;
this.controller = controller;
Image = UIImage.FromFile ("Images/plus.png");
addItemButton = UIButton.FromType(UIButtonType.Custom);
var buttonFrame = new RectangleF (0f, 0f, Image.Size.Width, Image.Size.Height);
addItemButton.Frame = buttonFrame;
addItemButton.SetImage(Image, UIControlState.Normal);
addItemButton.TouchUpInside += delegate {
Console.WriteLine ("Touched");
};
}
public void setTableItem (List<Product> tableItems)
{
this.tableItems = tableItems;
}
/// <summary>
/// Called by the TableView to determine how many cells to create for that particular section.
/// </summary>
public override int RowsInSection (UITableView tableview, int section)
{
return tableItems.Count;
}
/// <summary>
/// Called when a row is touched
/// </summary>
public override void AccessoryButtonTapped (UITableView tableView, NSIndexPath indexPath)
{
var prod = controller.Productdetails[indexPath.Row];
Double amount = 1*prod.SellingPriceA;
transaction = new Transaction();
transaction.SetTransaction(controller.CustomerID, prod.ID, prod.Description1,1, prod.SellingPriceA,0.0,amount);
controller.UpdateOrderedItemsTableView();
}
/// <summary>
/// Called by the TableView to get the actual UITableViewCell to render for the particular row
/// </summary>
public override UITableViewCell GetCell (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
// request a recycled cell to save memory
UITableViewCell cell = tableView.DequeueReusableCell (cellIdentifier);
// if there are no cells to reuse, crate a new one
if (cell == null) {
cell = new UITableViewCell (UITableViewCellStyle.Default, cellIdentifier);
}
var cust = controller.Productdetails [indexPath.Row];
cell.TextLabel.Text = cust.Description1;
cell.AccessoryView = this.controller.addItemButton;
return cell;
}
}
Here is Error I am getting. Not sure what is going on. Perhaps I used inappropriate functions. Please suggestion me!!!
**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 SalesOrder.Application.Main (string[]) [0x00000] in /Users/Mac/Projects/SalesOrderPolarisnet/SalesOrder/Main.cs:17
at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <IL 0x00050, 0xffffffff>
Native stacktrace:
0 SalesOrder 0x000908cc mono_handle_native_sigsegv + 284
1 SalesOrder 0x000056f8 mono_sigsegv_signal_handler + 248
2 libsystem_c.dylib 0x9613559b _sigtramp + 43
3 ??? 0xffffffff 0x0 + 4294967295
4 UIKit 0x022620e6 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 61
5 UIKit 0x02308ade -[UIControl sendAction:to:forEvent:] + 66
6 UIKit 0x02308fa7 -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 503
7 UIKit 0x02307d8a -[UIControl touchesBegan:withEvent:] + 264
8 UIKit 0x02523a1a _UIGestureRecognizerUpdate + 6725
9 CoreFoundation 0x011b099e __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
10 CoreFoundation 0x01147640 __CFRunLoopDoObservers + 384
11 CoreFoundation 0x011134c6 __CFRunLoopRun + 1174
12 CoreFoundation 0x01112d84 CFRunLoopRunSpecific + 212
13 CoreFoundation 0x01112c9b CFRunLoopRunInMode + 123
14 GraphicsServices 0x048307d8 GSEventRunModal + 190
15 GraphicsServices 0x0483088a GSEventRun + 103
16 UIKit 0x0225f626 UIApplicationMain + 1163
17 ??? 0x0d4d41f4 0x0 + 223166964
18 ??? 0x0d4d2f50 0x0 + 223162192
19 ??? 0x0d4d27c0 0x0 + 223160256
20 ??? 0x0d4d284f 0x0 + 223160399
21 SalesOrder 0x00009ab2 mono_jit_runtime_invoke + 722
22 SalesOrder 0x0016b34e mono_runtime_invoke + 126
23 SalesOrder 0x0016f4d4 mono_runtime_exec_main + 420
24 SalesOrder 0x001748c5 mono_runtime_run_main + 725
25 SalesOrder 0x00066cb5 mono_jit_exec + 149
26 SalesOrder 0x00203911 main + 2209
27 SalesOrder 0x00002ab5 start + 53
=================================================================
Got a SIGSEGV while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries
used by your application.
=================================================================**
Upvotes: 2
Views: 2021
Reputation: 43553
When your GetCell
method returns the cell
instance no longer has any managed reference. At this point the GC is able to collect it.
However the native parts of your UITableViewCell
still exists (they are referenced natively) so it will look fine - but when you use the event handler it tries to get back to managed code... and that does not exists anymore (and it will crash).
There's a few ways to deal with this. A simple one is to keep a reference to each cell
you create, e.g. add them to a static List<UITableViewCell>
. The GC won't be able to collect them so the event handler will work later.
However your code could be better is re-using the cells. Right now you're re-using the UITableViewCell
but not it's content, i.e. you're always creating UIImage
and a UIButton
. You could easily share them (at least the UIImage
) and save memory. You could also only keep a reference to the button (not the whole cell) and save memory while fixing your problem.
EDIT : This is how this should looks like this:
static UIImage shared_image = UIImage.FromFile ("Images/plus.png");
static List<UITableViewCell> cells = new List<UITableViewCell> ();
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell (cellIdentifier);
if (cell == null) {
// create a cell that will be reused (some initialization steps skipped)
cell = new UITableViewCell (UITableViewCellStyle.Default, cellIdentifier);
var addItemButton = UIButton.FromType (UIButtonType.Custom);
// the same image can be shared in all cells to save memory
addItemButton.SetImage (shared_image, UIControlState.Normal);
addItemButton.TouchUpInside += delegate {
Console.WriteLine ("Touched");
};
cell.AccessoryView = addItemButton;
// keep a reference to the cells so they won't be GC'ed
/// that will ensure you can call back (event handler) into the managed instance
cells.Add (cell);
}
// customize the cell - you should not be adding elements here
// otherwise they will be added each time the cell is shown (not created)
cell.TextLabel.Text = indexPath.Row.ToString ();
return cell;
}
Upvotes: 4
Reputation: 2572
It is possible that Mono's GC collect and dispose your UIButtons since they're declared as local variables. I recommend you to subclass UITableViewCell, include UIButton to it and then use that class in GetCell.
BTW, it's better to use TouchUpInside instead of TouchDown event.
Upvotes: 2