Xamarin - UITableView reload data for array - ios

I implemented UITableView on Xamarin using a new class for TableSource. I am initializing TableSource from MainView and pass an array. Then I am trying to update that array from MainView and I am getting error:
Object reference not sent to an instance
The code in MainVC:
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
//Console.WriteLine("Event 1");
lVM = new ListViewModel();
lVM.dataUpdated += this.updateArray;
lVM.dataUpdatedNetwork += this.networkUpdatedSQL;
lVM.getInitialData(100);
tableSource = new ListTableSource(this);
tableV.Source = tableSource;
}
public void updateArray(object sender, EventArgs e) {
InvokeOnMainThread(delegate
{
//Console.WriteLine("Update array event on VC for SQL count for array " + lVM.ordersArray.Count);
tableSource.updateArray(lVM.ordersArray);
tableV.ReloadData();
});
}
The code in TableSource:
public class ListTableSource : UITableViewSource
{
ListViewController listVCRef;
ArrayList tableItems;
string cellIdentifier = "TableCell";
public ListTableSource(ListViewController listVC)
{
tableItems = new ArrayList();
listVCRef = listVC;
}
public void updateArray(ArrayList items) {
tableItems = new ArrayList(items);
Console.WriteLine("Items " + tableItems.Count);
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
new UIAlertView("Alert", "You touched: " + tableItems[indexPath.Row], null, "OK", null).Show();
tableView.DeselectRow(indexPath, true);
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell(cellIdentifier);
if (cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Subtitle, cellIdentifier);
}
Order currentOrder = tableItems[indexPath.Row] as Order;
cell.TextLabel.Text = currentOrder.comment;
cell.DetailTextLabel.Text = currentOrder.orderDate.ToString();
return cell;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return tableItems.Count;
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return 80;
}
}
Is my approach wrong? How I am supposed to update the data in TableSource.

My feeling is that updateArray is executed before tableSource = new ListTableSource(this); and because of this tableSource is null. lVM.getInitialData(100); is likely triggering updateArray.

This worker for my:
button.TouchUpInside += (s, e) => {
tableItems.Add(textfield1.Text); //(1)
table = new UITableView(View.Bounds);
table.Source = new TableView(tableItems.ToArray()); //(2)
table.Frame = new CoreGraphics.CGRect(0, 350, this.View.Frame.Width, 300); //(3)
Add(table);
};
in (1) the list tableItems add new value of uiTextField
in (2) Actually the datas in Source of your table
in (3) create visually the table

Related

Not able find UITableViewCellDeleteConfirmationView in ios 11 layoutsubview subview in UITableviewcell?

I have to override the height swipe delete button in Tableviewcell i used the below code it's work fine ios 10 but in ios11 i can't able find the UITableViewCellDeleteConfirmationView in layoutsubview class
foreach (var views in this.Subviews)
{
if (views.Class.Name.ToString() == new NSString("UITableViewCellDeleteConfirmationView"))
{
CGRect newFrame = views.Frame;
CGRect newframe1 = new CGRect(newFrame.X, 6, newFrame.Size.Width, 59);
views.Frame = newframe1;
foreach (var getButtonviews in views.Subviews)
{
Console.WriteLine("x:"+getButtonviews.Frame.X);
Console.WriteLine("W:"+getButtonviews.Frame.Width);
if (getButtonviews.Class.Name.ToString() == "_UITableViewCellActionButton")
{
UIImage image = UIImage.FromBundle("img_line");
UIButton button = (UIButton)getButtonviews;
UIImageView imageview = new UIImageView();
imageview.Frame = new CGRect(getButtonviews.Frame.X + 120, 0, 1, getButtonviews.Frame.Height);
imageview.Image = image;
button.AddSubview(imageview);
foreach (var getButton in getButtonviews.Subviews)
{
if (getButton.Class.Name.ToString() == "UIButtonLabel")
{
UILabel label = (UILabel)getButton;
label.Font = UIFont.FromName("ProximaNova-Regular", 13);
}
}
}
}
}
}
The view hierarchy inside tableview has been changed after iOS10.
iOS8 - iOS10
UITableView -> UITableViewCell -> UITableViewCellDeleteConfirmationView -> _UITableViewCellActionButton
iOS11
work with Xcode8
UITableView -> UITableViewWrapperView -> UISwipeActionPullView -> UISwipeActionStandardButton
work with Xcode9
UITableView -> UISwipeActionPullView -> UISwipeActionStandardButton
Solution:
I make the code work both at iOS8 - iOS11, and I put all the code at ViewWillLayoutSubviews in ViewController , but first , we need to know which cell we are selecting.
public class TableDelegate : UITableViewDelegate
{
YourViewController owner;
public TableDelegate(YourViewController vc){
owner = vc;
}
public override UITableViewRowAction[] EditActionsForRow(UITableView tableView, NSIndexPath indexPath)
{
UITableViewRowAction hiButton = UITableViewRowAction.Create(
UITableViewRowActionStyle.Default,
"Hi",
delegate {
Console.WriteLine("Hello World!");
});
return new UITableViewRowAction[] { hiButton };
}
public override void WillBeginEditing(UITableView tableView, NSIndexPath indexPath)
{
owner.selectIndexPath = indexPath;
owner.View.SetNeedsLayout();
}
public override void DidEndEditing(UITableView tableView, NSIndexPath indexPath)
{
owner.selectIndexPath = null;
}
}
public class TableSource : UITableViewSource
{
string[] TableItems;
string CellIdentifier = "TableCell";
public TableSource(string[] items)
{
TableItems = items;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return TableItems.Length;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell(CellIdentifier);
string item = TableItems[indexPath.Row];
//---- if there are no cells to reuse, create a new one
if (cell == null)
{ cell = new UITableViewCell(UITableViewCellStyle.Default, CellIdentifier); }
cell.TextLabel.Text = item;
return cell;
}
}
public partial class ViewController : UIViewController
{
protected ViewController(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public NSIndexPath selectIndexPath { get; set; }
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
string[] tableItems = new string[] { "Vegetables", "Fruits", "Flower Buds", "Legumes", "Bulbs", "Tubers" };
tableview.Source = new TableSource(tableItems);
tableview.Delegate = new TableDelegate(this);
}
public override void ViewWillLayoutSubviews()
{
base.ViewWillLayoutSubviews();
if (this.selectIndexPath != null)
{
if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
{
// Code that uses features from iOS 11.0 and later
foreach (UIView subview in tableview.Subviews)
{
if (subview.Class.Name.ToString() == "UISwipeActionPullView")
{
foreach (var buttonViews in subview.Subviews)
{
if (buttonViews.Class.Name.ToString() == "UISwipeActionStandardButton")
{
//operate what you want.
}
}
}
}
}
else
{
// Code to support earlier iOS versions
UITableViewCell cell = tableview.CellAt(this.selectIndexPath);
foreach (UIView subview in cell.Subviews)
{
if (subview.Class.Name.ToString() == "UITableViewCellDeleteConfirmationView")
{
foreach (var buttonViews in subview.Subviews)
{
if (buttonViews.Class.Name.ToString() == "_UITableViewCellActionButton")
{
//operate what you want.
}
}
}
}
}
}
}
}

iOS Navigating to New View on Table Cell Click using a Segue

Upon clicking a table cell I would like to navigate to a new page using a segue. However whenever I click the cell nothing happens.
When I place a breakpoint on the PrepareForSegue method it is never hit when I click a cell on the DayTableView.
Main.storyboard
DayTableViewController
namespace HHGoals2
{
public partial class DayTableViewController : UITableViewController
{
HHGoals2DataService dataService = new HHGoals2DataService();
public DayTableViewController(IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
var allDays = dataService.GetAllDays();
var dataSource = new GoalsDataSource(allDays, this);
TableView.Source = dataSource;
this.NavigationItem.Title = "Completed Goals";
}
public override void PrepareForSegue (UIStoryboardSegue segue, NSObject sender)
{
base.PrepareForSegue(segue, sender);
if(segue.Identifier == "GoalDetailSegue")
{
var goalDetailViewController = segue.DestinationViewController
as GoalDetailViewController;
if(goalDetailViewController != null)
{
var source = TableView.Source as GoalsDataSource;
var rowPath = TableView.IndexPathForSelectedRow;
var item = source.GetItem(rowPath.Row);
goalDetailViewController.SelectedDay = item;
}
}
}
}
}
GoalsDataSource
using System;
using Foundation;
using UIKit;
using System.Collections.Generic;
using HHGoals2.Core.Model;
using HHGoals2.Cells;
namespace HHGoals2.DataSources
{
public class GoalsDataSource: UITableViewSource
{
private List<Day> allDays;
NSString cellIdentifier = new NSString("DayCell");
DayTableViewController callingController;
public GoalsDataSource(List<Day> allDays, DayTableViewController callingController)
{
this.allDays = allDays;
this.callingController = callingController;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
DayListCell cell = tableView.DequeueReusableCell(cellIdentifier) as DayListCell;
if (cell == null)
{
cell = new DayListCell(cellIdentifier);
}
cell.UpdateCell(allDays[indexPath.Row].Number.ToString(),
allDays[indexPath.Row].SuccessRate.ToString(),
allDays[indexPath.Row].FailRate.ToString());
return cell;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return allDays.Count;
}
public Day GetItem(int id)
{
return allDays[id];
}
}
}
Do I need to add a TouchUpInside event of the DayCell? Any suggestions on how to get this to work would be great.
I think you just missed out to connect the delegate and datasource to tableview in uitableviewcontroller. So connect it and it will work fine.

UICollectionViews GetSizeForItem() crashes on call of collectionView.DequeueReusableCell ()

I have a strange behaviour on my Custom UICollectionView.
Everytime i call
KeyWordsFieldsCell _dummyCellForRendering = (KeyWordsFieldsCell)collectionView.DequeueReusableCell (KeyWordsFieldsCell.CellId, indexPath);
in
public override CGSize GetSizeForItem (UICollectionView collectionView, UICollectionViewLayout layout, NSIndexPath indexPath)
my Code crashes without Error or Stacktrace.
In
public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
however the call of
KeyWordsFieldsCell _dummyCellForRendering = (KeyWordsFieldsCell)collectionView.DequeueReusableCell (KeyWordsFieldsCell.CellId, indexPath);
just works fine.
Here is the complete Code of my UICollectionViews DataSource and Delegate.
namespace KeyWordFieldsView
{
#region CollectionViewDataSource
public class KeyWordsFieldDataSource : UICollectionViewDataSource
{
private readonly UICollectionView keyWordsCollectionView;
public KeyWordsFieldDataSource (UICollectionView keyWordsCollectionView)
{
this.keyWordsCollectionView = keyWordsCollectionView;
}
public event EventHandler ContentChangedEvent;
private List<String> data = new List<String> ();
public List<String> Data
{
get
{
return data;
}
set
{
data = value;
}
}
public override nint GetItemsCount (UICollectionView collectionView, nint section)
{
return data.Count;
}
public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
{
var textCell = (KeyWordsFieldsCell)collectionView.DequeueReusableCell (KeyWordsFieldsCell.CellId, indexPath);
textCell.initCell ();
textCell.Text = Data [indexPath.Row];
textCell.DeleteButtonPressedEvent += HandleDeleteButtonPressedEvent;
return textCell;
}
public void HandleDeleteButtonPressedEvent (object sender, EventArgs a)
{
if (sender.GetType () == typeof (KeyWordsFieldsCell))
{
var cell = sender as KeyWordsFieldsCell;
NSIndexPath [] pathsToDelete = { keyWordsCollectionView.IndexPathForCell (cell) };
if (pathsToDelete [0] != null)
{
cell.DeleteButtonPressedEvent -= HandleDeleteButtonPressedEvent;
Data.RemoveAt (pathsToDelete [0].Row);
keyWordsCollectionView.DeleteItems (pathsToDelete);
}
OnContentChanged (sender, a);
}
}
public void OnContentChanged (object sender, EventArgs ea)
{
if (ContentChangedEvent != null)
{
ContentChangedEvent (this, ea);
}
}
}
#endregion
#region CollectionViewDelegate
class KeyWordsFieldDelegate : UICollectionViewDelegateFlowLayout
{
public override CGSize GetSizeForItem (UICollectionView collectionView, UICollectionViewLayout layout, NSIndexPath indexPath)
{
List<String> data = ((KeyWordsFieldDataSource)collectionView.DataSource).Data;
KeyWordsFieldsCell _dummyCellForRendering = (KeyWordsFieldsCell)collectionView.DequeueReusableCell (KeyWordsFieldsCell.CellId, indexPath);
_dummyCellForRendering.Text = data [indexPath.Row];
_dummyCellForRendering.keyWordContainerView.SetNeedsLayout ();
_dummyCellForRendering.keyWordContainerView.LayoutIfNeeded ();
double height = Math.Max (_dummyCellForRendering.keyWordLabel.Frame.Height, _dummyCellForRendering.keyWordFieldDeleteButton.Frame.Height);
double width = Math.Min (_dummyCellForRendering.keyWordContainerView.Frame.Width, collectionView.Bounds.Width);
_dummyCellForRendering = null;
return new CGSize (width, height);;
}
public override void ItemSelected (UICollectionView collectionView, NSIndexPath indexPath)
{
}
public override bool ShouldSelectItem (UICollectionView collectionView, NSIndexPath indexPath)
{
return true;
}
public override void CellDisplayingEnded (UICollectionView collectionView, UICollectionViewCell cell, NSIndexPath indexPath)
{
var keyWordCell = cell as KeyWordsFieldsCell;
keyWordCell.DeleteButtonPressedEvent -= ((KeyWordsFieldDataSource)collectionView.DataSource).HandleDeleteButtonPressedEvent;
}
}
#endregion
#region left justified cells
class LeftAlignedCollectionViewFlowLayout : UICollectionViewFlowLayout
{
nfloat maxCellSpacing = 4;
public override UICollectionViewLayoutAttributes [] LayoutAttributesForElementsInRect (CGRect rect)
{
var attributesForElementsInRect = base.LayoutAttributesForElementsInRect (rect);
UICollectionViewLayoutAttributes [] newAttributesForElementsInRect = new UICollectionViewLayoutAttributes [attributesForElementsInRect.Count ()];
var leftMargin = this.SectionInset.Left;
for (int i = 0; i < attributesForElementsInRect.Count (); i++)
{
var attributes = attributesForElementsInRect [i];
//if Element is first in new Line and already leftaligned or if element is in new line
if (attributes.Frame.X == leftMargin || attributes.Frame.Y > attributesForElementsInRect[i > 0 ? i-1 : i].Frame.Y)
{
leftMargin = this.SectionInset.Left; //reset the leftMargin to left sectionInset.
}
CGRect newLeftAlignedFrame = attributes.Frame;
newLeftAlignedFrame.X = leftMargin;
attributes.Frame = newLeftAlignedFrame;
leftMargin += attributes.Size.Width + maxCellSpacing;
newAttributesForElementsInRect [i] = attributes;
}
return newAttributesForElementsInRect;
}
}
#endregion
}
and here is the code of my UICollectionViewCell
namespace KeyWordFieldsView
{
public partial class KeyWordsFieldsCell : UICollectionViewCell
{
protected KeyWordsFieldsCell (IntPtr handle) : base (handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public string Text
{
get
{
return keyWordLabel.Text;
}
set
{
initCell ();
keyWordLabel.Text = value;
keyWordLabel.SizeToFit ();
SetNeedsDisplay ();
}
}
public UILabel keyWordLabel;
public UIButton keyWordFieldDeleteButton;
public UIView keyWordContainerView;
public static readonly NSString CellId = new NSString ("KeyWordsFieldsCell");
public event EventHandler DeleteButtonPressedEvent;
public void initCell () {
UIColor chipGrey = UIColor.FromRGBA (153, 153, 153, 51);
ContentView.BackgroundColor = chipGrey;
ContentView.Layer.CornerRadius = 16;
if (keyWordContainerView == null)
{
keyWordContainerView = new UIView (new CGRect (0, 0, 0, 32));
keyWordContainerView.TranslatesAutoresizingMaskIntoConstraints = false;
keyWordContainerView.BackgroundColor = UIColor.Clear;
ContentView.AddSubview (keyWordContainerView);
}
if (keyWordLabel == null)
{
keyWordLabel = new UILabel (new CGRect (0, 0, 0, 32));
keyWordLabel.BackgroundColor = UIColor.Clear;
UIFont labelFont = UIFont.SystemFontOfSize (14f);
keyWordLabel.Font = labelFont;
keyWordLabel.TranslatesAutoresizingMaskIntoConstraints = false;
keyWordLabel.LineBreakMode = UILineBreakMode.MiddleTruncation;
keyWordContainerView.AddSubview (keyWordLabel);
}
if (keyWordFieldDeleteButton == null)
{
keyWordFieldDeleteButton = UIButton.FromType (UIButtonType.Custom);
keyWordFieldDeleteButton.Frame = new CGRect (0, 0, 32, 32);
keyWordFieldDeleteButton.SetImage (UIImage.FromBundle ("remove-icon"), UIControlState.Normal);
keyWordFieldDeleteButton.BackgroundColor = UIColor.Clear;
keyWordFieldDeleteButton.TouchUpInside += DeleteButtonPressed;
keyWordFieldDeleteButton.TranslatesAutoresizingMaskIntoConstraints = false;
keyWordContainerView.AddSubview (keyWordFieldDeleteButton);
}
else {
//Add ButtonEvent in Case of Reuse
keyWordFieldDeleteButton.TouchUpInside -= DeleteButtonPressed;
keyWordFieldDeleteButton.TouchUpInside += DeleteButtonPressed;
}
var cvDictionary = NSDictionary.FromObjectsAndKeys (new NSObject [] { keyWordContainerView }, new NSObject [] { new NSString ("kwcv") });
ContentView.AddConstraints (NSLayoutConstraint.FromVisualFormat ("H:|[kwcv]|", 0, new NSDictionary (), cvDictionary));
ContentView.AddConstraints (NSLayoutConstraint.FromVisualFormat ("V:|[kwcv]|", 0, new NSDictionary (), cvDictionary));
keyWordContainerView.SetContentHuggingPriority (249, UILayoutConstraintAxis.Vertical);
keyWordContainerView.SetContentCompressionResistancePriority (749, UILayoutConstraintAxis.Vertical);
var viewsDictionary = NSDictionary.FromObjectsAndKeys (new NSObject [] { keyWordLabel, keyWordFieldDeleteButton }, new NSObject [] { new NSString ("kwlbl"), new NSString ("kwbtn") });
keyWordContainerView.AddConstraints (NSLayoutConstraint.FromVisualFormat ("H:|-[kwlbl][kwbtn(==32)]|", 0, new NSDictionary (), viewsDictionary));
keyWordContainerView.AddConstraints (NSLayoutConstraint.FromVisualFormat ("V:|[kwbtn(==32)]|", 0, new NSDictionary (), viewsDictionary));
keyWordContainerView.AddConstraints (NSLayoutConstraint.FromVisualFormat ("V:|[kwlbl]|", 0, new NSDictionary (), viewsDictionary));
keyWordFieldDeleteButton.SetContentHuggingPriority (249, UILayoutConstraintAxis.Vertical);
keyWordFieldDeleteButton.SetContentCompressionResistancePriority (751, UILayoutConstraintAxis.Vertical);
keyWordLabel.SetContentHuggingPriority (249, UILayoutConstraintAxis.Vertical);
keyWordLabel.SetContentCompressionResistancePriority (749, UILayoutConstraintAxis.Vertical);
}
//[Export ("initWithFrame:")]
//public KeyWordsFieldsCell (CGRect frame) : base (frame)
//{
// initCell ();
//}
public override void LayoutSubviews ()
{
base.LayoutSubviews ();
}
public void DeleteButtonPressed (object sender, EventArgs ea)
{
((UIButton)sender).TouchUpInside -= DeleteButtonPressed;
OnDeleteButtonPressed (sender, ea);
}
void OnDeleteButtonPressed (object sender, EventArgs ea)
{
if (DeleteButtonPressedEvent != null)
{
DeleteButtonPressedEvent (this, ea);
}
}
}
}
This is where the UICollectionView gets initialized:
if (CollectionView != null && CollectionView.DataSource == null)
{
CollectionView.RegisterClassForCell (typeof (KeyWordsFieldsCell), KeyWordsFieldsCell.CellId);
CollectionView.TranslatesAutoresizingMaskIntoConstraints = false;
CollectionView.SetCollectionViewLayout (new LeftAlignedCollectionViewFlowLayout (), false);
KeyWordsFieldDataSource Source = new KeyWordsFieldDataSource (CollectionView);
if (data != null)
{
Source.Data = data;
}
CollectionView.DataSource = Source;
KeyWordsFieldDelegate keyWordsDelegate = new KeyWordsFieldDelegate ();
CollectionView.Delegate = keyWordsDelegate;
(CollectionView.CollectionViewLayout as UICollectionViewFlowLayout).MinimumLineSpacing = 4;
(CollectionView.CollectionViewLayout as UICollectionViewFlowLayout).MinimumInteritemSpacing = 2;
//CollectionViewHeightConstraint.Constant = CollectionView.CollectionViewLayout.CollectionViewContentSize.Height;
}
Hope that someone can help be, because this is a rather frustrating problem.
Greets,
Mav
So for anyone who stumbles across the same Problem.
Frankly speaking there is just a different behaviour between UITableView and UICollectionView.
Where in UITableView it is totally ok to call dequeueReusableCellWithReuseIdentifier() in getHeightForRow() to get a cell for height calculation, calling it in sizeForItemAtIndexPath in a UICollectionView will cause an indefinite loop and thus crash the app.
Thx to #Markus Rautopuro for point me in the right direction with his Answer
I'm now calculating the height of the cell, by calculating the size of the components in the cell. That works quite well and needs less resources, since i don't need to build a complete cell, but only the items, that add up to the height.

Button Clicked Event was triggered Twice or More in iOS

I added a button on a section and wrote clicked event
var refreshCacheSettings = new CustomButtonSection ("GeneralSettingsView_RefreshCache_Button_Title".t());
refreshCacheSettings.ButtonClicked += async (sender, e) => {
Console.WriteLine ("Clicked...");
var btn = (UIButton)sender;
btn.Enabled=false;
btn.UserInteractionEnabled =false;
var answer= await AlertUtil.CreateMessageYesNo("GeneralSettingsView_RefreshCache_Question_Title".t(),"GeneralSettingsView_RefreshCache_Question_Message".t());
if(!answer)
{
ShowLoadingScreen();
await Task.Run(()=> RefreshFormFieldSettings());
}
btn.Enabled=true;
btn.UserInteractionEnabled =true;
};
and here is custom button section class
public class CustomButtonSection : RootElement
{
string _btnText;
public event EventHandler ButtonClicked;
public CustomButtonSection (string btnText):base ("")
{
base.MakeViewController ();
_btnText = btnText;
}
protected override UIViewController MakeViewController ()
{
var vc = (UITableViewController) base.MakeViewController();
vc.TableView.BackgroundView = null;
vc.View.BackgroundColor = UIColor.White; //or whatever color you like
return vc;
}
public override void Selected (DialogViewController dvc, UITableView tableView, NSIndexPath path)
{
base.Deselected (dvc, tableView, path);
}
public override UITableViewCell GetCell (UITableView tv)
{
var cell = tv.DequeueReusableCell ("CustomCell") as CustomButtonCell;
if (cell == null)
cell = new CustomButtonCell (new NSString("CustomCell"));
cell.Accessory = UITableViewCellAccessory.None;
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
cell.UpdateCell (_btnText);
cell.btn.TouchUpInside += (sender, e) => {
if (ButtonClicked != null)
ButtonClicked (sender, e);
};
return cell;
}
private class CustomButtonCell: UITableViewCell
{
public UIButton btn;
public CustomButtonCell(NSString cellId )
: base(UITableViewCellStyle.Default, cellId)
{
SelectionStyle = UITableViewCellSelectionStyle.None;
Accessory = UITableViewCellAccessory.DisclosureIndicator;
ContentView.BackgroundColor = UIColor.White;
btn = ImagewareMobile.iOS.UI.Common.Buttons.ElementsButton("", FitpulseTheme.SharedTheme.BlueButtonImage);
ContentView.AddSubviews(new UIView[] {btn});
}
public void UpdateCell (string caption )
{
btn.SetTitle(caption,UIControlState.Normal);
}
public override void LayoutSubviews ()
{
base.LayoutSubviews ();
btn.SetTitleColor(UIColor.White, UIControlState.Normal);
btn.Frame = new CGRect (5,5,300,35);
}
}
}
Sometimes I got the alert message twice or more times. It doesnt happen all time but sometimes happens and it is boring..
in javascript there is preventDefault method, but what about for ios?
I am using xamarin.ios with c# but I can handle code of objective C or swift.
The reason for this behavior is that you apply the event every time the cell will be reused.
public override UITableViewCell GetCell (UITableView tv)
{
var cell = tv.DequeueReusableCell ("CustomCell") as CustomButtonCell;
if (cell == null)
cell = new CustomButtonCell (new NSString("CustomCell"));
cell.Accessory = UITableViewCellAccessory.None;
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
cell.UpdateCell (_btnText);
// this is the reason for multiple events fired
cell.btn.TouchUpInside += (sender, e) => {
if (ButtonClicked != null)
ButtonClicked (sender, e);
};
return cell;
}
It would be better to change your code to
public override UITableViewCell GetCell (UITableView tv)
{
var cell = tv.DequeueReusableCell ("CustomCell") as CustomButtonCell;
if (cell == null)
{
cell = new CustomButtonCell (new NSString("CustomCell"));
cell.btn.TouchUpInside += (sender, e) => {
if (ButtonClicked != null)
ButtonClicked (sender, e);
};
}
cell.Accessory = UITableViewCellAccessory.None;
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
cell.UpdateCell (_btnText);
return cell;
}
That way the event will be attached only if the cell will be newly created.

Xamarin UITableViewCell Memory Leak

I'm using Xamarin and classes that inherit UITableViewController, UITableViewSource, and UITableViewCell to provide a simple list of items.
I find that once garbage collection has run, the UITableViewController and UITableViewSource get collected, but the UITableViewCell's never get disposed and collected properly.
Here's my example code.
public class Test1ButtonCell : UITableViewCell
{
public static readonly NSString Key = new NSString("Test1ButtonCell");
public Test1ButtonCell () : base (UITableViewCellStyle.Default, Key)
{
TextLabel.TextColor = UIColor.Black;
TextLabel.TextAlignment = UITextAlignment.Left;
}
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
}
}
public class Test1Controller : UITableViewController
{
private WeakReference<Test1Source> source = null;
public Test1Controller () : base (UITableViewStyle.Grouped)
{
}
public override void DidReceiveMemoryWarning ()
{
base.DidReceiveMemoryWarning ();
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
List<String> testdata = new List<String>( );
for( int i = 0; i < 30; i++ )
{
testdata.Add( i.ToString( ) );
}
source = new WeakReference<Test1Source>(new Test1Source( this,testdata ));
testdata = null;
this.NavigationItem.LeftBarButtonItem = new UIBarButtonItem ("Back", UIBarButtonItemStyle.Plain, null);
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear (animated);
this.NavigationItem.Title="Test1";
TableView.Source = source.target;
this.NavigationItem.LeftBarButtonItem.Clicked += HandleBackButtonTouch;
}
public override void ViewWillDisappear (bool animated)
{
this.NavigationItem.LeftBarButtonItem.Clicked -= HandleBackButtonTouch;
base.ViewWillDisappear (animated);
TableView.Source = null;
}
private void HandleBackButtonTouch(object sender, EventArgs e)
{
NavigationController.PopToRootViewController( true );
}
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
}
}
public class Test1Source : UITableViewSource
{
private WeakReference<Test1Controller> controller;
private List<String> testdata = new List<string>();
public Test1Source (Test1Controller controller, List<String> testData)
{
testdata = testData;
this.controller = new WeakReference<Test1Controller>( controller );
}
public override nint NumberOfSections (UITableView tableView)
{
return 1;
}
public override nint RowsInSection (UITableView tableview, nint section)
{
if( section == 0 )
{
return testdata.Count;
}
else
{
return 0;
}
}
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (Test1ButtonCell.Key) as Test1ButtonCell;
if( cell == null )
{
cell = new Test1ButtonCell( );
}
if( indexPath.Section == 0 )
{
cell.TextLabel.Text = testdata[ indexPath.Row ];
}
return cell;
}
}
The UITableView caches (and reuses) UITableViewCells.
They'll go away once the UITableView is freed.
I can confirm the problem. It works properly on the simulator and not on the device.
The only thing I can think of is to keep somewhere list of all created custom cells (by adding them to the list in the constructor of the cell) and dispose them when the table is disposed. Kind of ugly, but it works.

Resources