Akrambek
Akrambek

Reputation: 307

Issue with event handler in monotouch button

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

Answers (2)

poupou
poupou

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

Maxim Korobov
Maxim Korobov

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

Related Questions