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.
Related
I have UIcollectionView with 8 sections. Each section has a dedicated array as the data source. I want to add an item to each section on a button press. UICollectionView scroll direction set to Horizontal because I want the sections to be vertical direction.
Everything working fine in Xcode project(Xib and Objective-C) as this screenshot(Red colored items are the items added on button press).
When I do the same implementation in Xamarin ios, the layout become a mess after press the button as bellow.
My code as bellow
public partial class GridViewController : UIViewController
{
CVSource source;
int MAX_NUMBER_OF_ROWS = 4;
public GridViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
//this.collectionView.SetNeedsDisplay();
this.collectionView.RegisterClassForCell(typeof(ItemCell), ItemCell.CellId);
source = new CVSource();
this.collectionView.DataSource = source;
}
partial void AddButtonPressed(UIButton sender)
{
Console.WriteLine("Add button pressed");
MAX_NUMBER_OF_ROWS = MAX_NUMBER_OF_ROWS + 1;
source.AddItem();
collectionView.Layer.RemoveAllAnimations();
collectionView.ReloadData();
ViewDidLayoutSubviews();
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
collectionView.Frame = new CoreGraphics.CGRect(collectionView.Frame.Location.X, collectionView.Frame.Location.Y, collectionView.Frame.Size.Width, MAX_NUMBER_OF_ROWS * 40);
btnAdd.Frame = new CoreGraphics.CGRect(btnAdd.Frame.Location.X, collectionView.Frame.Location.Y + collectionView.Frame.Size.Height + 10, btnAdd.Frame.Size.Width, btnAdd.Frame.Size.Height);
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
DataSource class
class CVSource : UICollectionViewSource
{
List<string> arr1 = new List<string> { "40", "30"};
List<string> arr2 = new List<string> { "25", "22" };
List<string> arr3 = new List<string> { "35", "67" };
List<string> arr4 = new List<string> { "26", "12" };
List<string> arr5 = new List<string> { "27", "21", };
List<string> arr6 = new List<string> { "12", "45" };
List<string> arr7 = new List<string> { "34", "67" };
List<string> arr8 = new List<string> { "21", "44", };
public override nint NumberOfSections(UICollectionView collectionView)
{
return 8;
}
public override nint GetItemsCount(UICollectionView collectionView, nint section)
{
if ((int)section == 0)
return arr1.Count;
else if ((int)section == 1)
return arr2.Count;
else if ((int)section == 2)
return arr3.Count;
else if ((int)section == 3)
return arr4.Count;
else if ((int)section == 4)
return arr5.Count;
else if ((int)section == 5)
return arr6.Count;
else if ((int)section == 6)
return arr7.Count;
else if ((int)section == 7)
return arr8.Count;
return 0;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, Foundation.NSIndexPath indexPath)
{
var itemCell = (ItemCell)collectionView.DequeueReusableCell(ItemCell.CellId, indexPath);
if (indexPath.Section == 0)
{
itemCell.SetText(arr1[indexPath.Row]);
}
else if (indexPath.Section == 1)
{
itemCell.SetText(arr2[indexPath.Row]);
}
else if (indexPath.Section == 2)
{
itemCell.SetText(arr3[indexPath.Row]);
}
else if (indexPath.Section == 3)
{
itemCell.SetText(arr4[indexPath.Row]);
}
else if (indexPath.Section == 4)
{
itemCell.SetText(arr5[indexPath.Row]);
}
else if (indexPath.Section == 5)
{
itemCell.SetText(arr6[indexPath.Row]);
}
else if (indexPath.Section == 6)
{
itemCell.SetText(arr7[indexPath.Row]);
}
else if (indexPath.Section == 7)
itemCell.SetText(arr8[(indexPath.Row)]);
return itemCell;
}
public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath)
{
Console.WriteLine("Row selected "+ indexPath.Row);
}
public void AddItem()
{
arr1.Add("0");
arr2.Add("0");
arr3.Add("0");
arr4.Add("0");
arr5.Add("0");
arr6.Add("0");
arr7.Add("0");
arr8.Add("0");
}
}
ItemCell class
public partial class ItemCell : UICollectionViewCell
{
public static readonly NSString CellId = new NSString("ItemCell");
public static UITextField txtFld;
[Export("initWithFrame:")]
public ItemCell(CGRect frame) : base(frame)
{
BackgroundView = new UIView { BackgroundColor = UIColor.Orange };
SelectedBackgroundView = new UIView { BackgroundColor = UIColor.Green };
ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
ContentView.Layer.BorderWidth = 2.0f;
ContentView.BackgroundColor = UIColor.White;
ContentView.Transform = CGAffineTransform.MakeScale(0.8f, 0.8f);
txtFld = new UITextField() { Frame = new CGRect(5.0, 5.0, 60.0, 30.0), KeyboardType = UIKeyboardType.DecimalPad };
}
}
Im not a Xamarin developer, appreciate any comments/suggestions from Xamarin experts
Maybe you miss UICollectionViewLayout to set for collectionView , you can set the GridLayout for collectionView to have a try .
this.collectionView.SetCollectionViewLayout(new GridLayout(), true); //add CollectionViewLayout
this.collectionView.RegisterClassForCell(typeof(ItemCell), ItemCell.CellId);
GridLayout inherits from UICollectionViewFlowLayout in Xamrarin iOS :
public class GridLayout : UICollectionViewFlowLayout
{
public GridLayout ()
{
}
public override bool ShouldInvalidateLayoutForBoundsChange (CGRect newBounds)
{
return true;
}
public override UICollectionViewLayoutAttributes LayoutAttributesForItem (NSIndexPath path)
{
return base.LayoutAttributesForItem (path);
}
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (CGRect rect)
{
return base.LayoutAttributesForElementsInRect (rect);
}
}
The effect :
Affter tried the shared code , the effect as follow :
The different with your codes only here :
public ViewController (IntPtr handle) : base (handle)
{
collectionView = new UICollectionView(new CGRect(0, 0, UIScreen.MainScreen.Bounds.Size.Width, 300), new GridLayout());
collectionView.RegisterClassForCell(typeof(ItemCell), ItemCell.CellId);
//collectionView.SetCollectionViewLayout(new GridLayout(), true);
collectionView.BackgroundColor = UIColor.Blue;
source = new CVSource();
collectionView.DataSource = source;
View.AddSubview(collectionView);
button = new UIButton(new CGRect(150, 400, 100, 50));
button.SetTitle("Hello", UIControlState.Normal);
button.BackgroundColor = UIColor.Red;
View.AddSubview(button);
button.TouchUpInside += Button_TouchUpInside;
}
Note : I use code to create Interface , and set GridLayout for CollectionView .Here is the sample link .
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.
}
}
}
}
}
}
}
}
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
I'm having some trouble implementing a UICollectionView in my UIViewController. It gives me an error in GetCell Failed to marshal the Objective-C object 0x137622db0 (type: ParkXApp_iOS_FamilyCollectionCell). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance (because the type 'ParkXApp_iOS.FamilyCollectionCell' does not have a constructor that takes one IntPtr argument)..
I have implemented next to the monotouch sample code: monotouch sample simple UICollectionView
this is my code:
using System;
using UIKit;
using CoreGraphics;
using System.Collections.Generic;
using ObjCRuntime;
using Foundation;
namespace ParkXApp_iOS
{
public class FamilyView : UIViewController
{
nfloat navBarHeight;
nfloat viewWidth;
nfloat viewHeight;
public FamilyView ()
{
}
public override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
Draw ();
}
public void Draw () {
// clean the screen before (re)drawing it
foreach (var subView in this.View.Subviews) {
subView.RemoveFromSuperview ();
}
this.NavigationItem.SetRightBarButtonItem(null, true);
// set background
this.View.BackgroundColor = new UIColor((nfloat)0.99, (nfloat)0.99, (nfloat)0.99, 1);
// get viewWidth and viewHeight for drawing relative to the screen
navBarHeight = this.NavigationController.NavigationBar.Frame.GetMaxY ();
viewWidth = UIScreen.MainScreen.Bounds.Width;
viewHeight = UIScreen.MainScreen.Bounds.Height - navBarHeight;
UICollectionViewFlowLayout flowLayout = new UICollectionViewFlowLayout ();
flowLayout.ItemSize = new CGSize (100, 100);
flowLayout.ScrollDirection = UICollectionViewScrollDirection.Vertical;
flowLayout.SectionInset = new UIEdgeInsets (20, 20, 20, 20);
UICollectionView collectionView = new FamilyCollectionView (flowLayout).CollectionView;
collectionView.Frame = new CGRect (0, navBarHeight, viewWidth, viewHeight);
collectionView.ContentInset = new UIEdgeInsets (50, 0, 0, 0);
this.View.AddSubview (collectionView);
}
}
public class FamilyCollectionView : UICollectionViewController
{
List<string> familyMembers;
public FamilyCollectionView (UICollectionViewLayout layout) : base (layout) {
familyMembers = new List<string> ();
for (int i = 0; i < 5; i++) {
familyMembers.Add ("tom");
}
CollectionView.RegisterClassForCell (typeof(FamilyCollectionCell), "FamilyCollectionCell");
UIMenuController.SharedMenuController.MenuItems = new UIMenuItem[] {
new UIMenuItem ("Custom", new Selector ("custom"))
};
}
public override nint NumberOfSections (UICollectionView collectionView)
{
return 1;
}
public override nint GetItemsCount (UICollectionView collectionView, nint section)
{
return familyMembers.Count;
}
public override UICollectionViewCell GetCell (UICollectionView collectionView, Foundation.NSIndexPath indexPath)
{
try {
var cell = (FamilyCollectionCell)collectionView.DequeueReusableCell ("FamilyCollectionCell", indexPath);
cell.nameLabel.Text = familyMembers [indexPath.Row];
return cell;
} catch (Exception ex) {
Console.WriteLine (ex.Message);
}
return null;
}
public override void ItemHighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = collectionView.CellForItem (indexPath);
cell.ContentView.BackgroundColor = UIColor.Yellow;
}
public override void ItemUnhighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = collectionView.CellForItem (indexPath);
cell.ContentView.BackgroundColor = UIColor.White;
}
public override bool ShouldHighlightItem (UICollectionView collectionView, NSIndexPath indexPath)
{
return true;
}
}
public class FamilyCollectionCell : UICollectionViewCell
{
public UILabel nameLabel;
public FamilyCollectionCell (CGRect frame) : base (frame)
{
BackgroundColor = UIColor.Orange;
ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
ContentView.Layer.BorderWidth = 2.0f;
ContentView.BackgroundColor = UIColor.White;
ContentView.Transform = CGAffineTransform.MakeScale (0.8f, 0.8f);
nameLabel = new UILabel ();
nameLabel.Text = "name";
nameLabel.SizeToFit ();
nameLabel.Center = ContentView.Center;
ContentView.AddSubview (nameLabel);
}
[Export("custom")]
void Custom()
{
Console.WriteLine ("some code in the cell");
}
public override bool CanPerform (Selector action, NSObject withSender)
{
if (action == new Selector ("custom"))
return true;
else
return false;
}
}
}
To test it you should be able to copy-paste the code and run it in a solution. It is completely independent.
Thanks in advance!
You need to add this:
[Export ("initWithFrame:")]
in you cell like so
public class FamilyCollectionCell : UICollectionViewCell
{
public UILabel nameLabel;
[Export ("initWithFrame:")] // ADDED HERE
public FamilyCollectionCell (CGRect frame) : base (frame)
{
BackgroundColor = UIColor.Orange;
ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
ContentView.Layer.BorderWidth = 2.0f;
ContentView.BackgroundColor = UIColor.White;
ContentView.Transform = CGAffineTransform.MakeScale (0.8f, 0.8f);
nameLabel = new UILabel ();
nameLabel.Text = "name";
nameLabel.SizeToFit ();
nameLabel.Center = ContentView.Center;
ContentView.AddSubview (nameLabel);
}
[Export("custom")]
void Custom()
{
Console.WriteLine ("some code in the cell");
}
public override bool CanPerform (Selector action, NSObject withSender)
{
if (action == new Selector ("custom"))
return true;
else
return false;
}
}
Also that's an interesting way of adding in the collectionView into a viewcontroller. Is there any need for FamilyView? It might be better to add the navbar to the collectionview controller?
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.