I use Xamarin iOS designer to design custom TableViewCell class for my TableView.
But all cell subviews properties (outlets) return null except cell itself.
My custom cell class:
partial class VehiclesTableCell : UITableViewCell
{
public VehiclesTableCell(IntPtr handle) : base(handle) { }
public void UpdateCell(string licensePlate) {
licensePlateLabel.Text = licensePlate; //hit the null reference exception for licensePlateLabel
}
}
Generated partial class:
[Register ("VehiclesTableCell")]
partial class VehiclesTableCell
{
[Outlet]
[GeneratedCode ("iOS Designer", "1.0")]
UILabel licensePlateLabel { get; set; }
void ReleaseDesignerOutlets ()
{
if (licensePlateLabel != null) {
licensePlateLabel.Dispose ();
licensePlateLabel = null;
}
}
}
And GetCell of my Table Source class:
public class VehiclesTableSource : UITableViewSource
{
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) {
// Dequeueing the reuse cell
var cell = (VehiclesTableCell)tableView.DequeueReusableCell(new NSString(typeof(VehiclesTableCell).Name));
// Set cell properties
cell.UpdateCell(LicensePlate);
// Return the cell with populated data
return cell;
}
}
As we see genereted code has an outlet for licensePlate so why is its property null?
Is storyboard not supposed to instantiate all of its subviews automatically?
At least its happening in all other situations.
I was having the same problem with my custom TableViewCell. I found out the issue was with TableView.RegisterClassForCellReuse (typeof(MyCell), MyCellId). I had to change it to TableView.RegisterNibForCellReuse(UINib.FromName("MyCell", NSBundle.MainBundle), MyCellId) in order to get my .xib loaded.
I came across this issue myself. This is what I did to resolve it:
Instantiate the components of the subview in the constructor of the custom cell. In your case, something like:
public VehiclesTableCell(IntPtr handle) : base(handle) {
licensePlateLabel = new UILabel();
}
Override the method LayoutSubviews:
public override void LayoutSubviews () {
base.LayoutSubviews ();
licensePlateLabel.Frame = new RectangleF(63, 5, 33, 33); // Your layout here
}
The info on this guide got me this far, but ideally there would be a way to accomplish this without instantiating and laying out the subviews components manually as they're already defined in the IOS designer. Hopefully someone can chime in with a better way to do this...
EDIT: I came across this answer which explained why I wasn't able to bind the custom table cell I created in the designer. TLDR: Ensure that Identity -> Class and Table View Cell -> Identifier are both set to the custom table view cell class name in the inspector window.
I had this same issue, and as far as I have found out, if you are using storyboard you can just enter your custom class name in the properties of your table cell in the iOS designer and it should work. Calling RegisterClassForCellReuse() is what causes the null sub views issue. Once I removed that method call it seemed to work
Related
In the app I'm working on there's a need for custom UITableView section headers and footers. For this I'd like to create a custom control that works with binding and our logic.
For that I've created a XIB and added a backing class that looks like the following:
public partial class HeaderFooterView : MvxTableViewHeaderFooterView
{
public static readonly NSString Key = new NSString("HeaderFooterView");
public static readonly UINib Nib = UINib.FromName("HeaderFooterView", NSBundle.MainBundle);
public HeaderFooterView(IntPtr handle) : base(handle)
{
}
public override void AwakeFromNib()
{
base.AwakeFromNib();
//var binding = this.CreateBindingSet<HeaderFooterView, TableViewSection>();
//binding.Apply();
}
}
The MvxTableViewHeaderFooterView is actually a pretty simple class, combining the stock UITableViewHeaderFooterView with IMvxBindable. Nothing fancy.
However for some reason, even though I register it properly within the TableViewSource constructor:
tableView.RegisterNibForHeaderFooterViewReuse(HeaderFooterView.Nib, HeaderFooterView.Key);
And do the proper way of returning the Header itself only:
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
return tableView.DequeueReusableHeaderFooterView(HeaderFooterView.Key);
}
The app dies with the following error:
2017-07-12 16:56:40.517 MyAppiOS[3833:58706] *** Assertion failure in -[UITableView _dequeueReusableViewOfType:withIdentifier:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.7.47/UITableView.m:6696
2017-07-12 16:56:40.528 MyAppiOS[3833:58706] WARNING: GoogleAnalytics 3.17 void GAIUncaughtExceptionHandler(NSException *) (GAIUncaughtExceptionHandler.m:48): Uncaught exception: invalid nib registered for identifier (HeaderFooterView) - nib must contain exactly one top level object which must be a UITableViewHeaderFooterView instance
My NIB actually contains a single root object, the root view itself, that is set to the HeaderFooterView class (which derives from MvxTableViewHeaderFooterView which in turn derives from UITableViewHeaderFooterView). Yet it claims there's no UITableViewHeaderFooterView instance.
Why isn't it working as it's supposed to?
It's because return tableView.DequeueReusableHeaderFooterView(HeaderFooterView.Key); can return null if there are no HeaderFooterViews to reuse. In that case you have to create your own:
var view = tableView.DequeueReusableHeaderFooterView(HeaderFooterView.Key);
if (view == null){
//instantiate the nib here and set view
}
return view;
I would suggest structuring the backing class as follows:
public partial class HeaderFooterView : MvxTableViewHeaderFooterView
{
public static readonly NSString Key = new NSString("HeaderFooterView");
public static readonly UINib Nib = UINib.FromName("HeaderFooterView", NSBundle.MainBundle);
static HeaderFooterView()
{
//Adding this alone should allow your tableview to properly instantiate the view.
Nib = UINib.FromName("HeaderFooterView", NSBundle.MainBundle);
}
public static HeaderFooterView Create()
{
// However you can add this static method and create and return the view yourself.
var arr = NSBundle.MainBundle.LoadNib(nameof(HeaderFooterView ), null, null);
var v = Runtime.GetNSObject<HeaderFooterView >(arr.ValueAt(0));
return v;
}
public HeaderFooterView(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public override void AwakeFromNib()
{
base.AwakeFromNib();
}
}
Adding the static constructor by itself should be enough to allow your table view to properly instantiate the Nib. However if you still end up having problems like that you can use the static method 'Create' to instantiate the nib yourself as so:
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
HeaderFooterView theView = HeaderFooterView.Create()
return theView;
}
Try those suggestions, one or both should work for you.
Okay, found the issue.
While my initial XIB was correct, for some reason the root object's type was erased, and Interface Builder refused to accept mine.
However using VS2017 for Mac, I was able to set the proper root view class, and now everything works fine.
I am trying to use a custom UITableViewCell. I am able to get the cell to instantiate, so that it is not null. However, all of the UILabel outlets in the cell are null. Thus, I am getting a null pointer exception.
This is similar to iOS custom TableViewCell class subviews return null and Label in custom cell is null when populating UITableViewController. Nevertheless, neither solutions have solved my problem. They suggest to make sure the identifier matches, which it does. They also tell me to register the cells for reuse, which I also do.
I have made sure that my identifier in the Xib file of the cell matches what I am using.
In my Source class:
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
tableView.RegisterClassForCellReuse(typeof(BoardListCell), new NSString(BoardListCell.Key));
var cell = (BoardListCell) tableView.DequeueReusableCell(BoardListCell.Key);
if (cell == null)
{
var nib = UINib.FromName(BoardListCell.Key, NSBundle.MainBundle);
cell = (BoardListCell)nib.Instantiate (null, null) [0];
}
cell.setData("top", "not top"); //Sample data
return cell;
}
In my BoardListCell class:
public partial class BoardListCell : UITableViewCell
{
public static readonly UINib Nib = UINib.FromName ("BoardListCell", NSBundle.MainBundle);
public static readonly NSString Key = new NSString ("BoardListCell");
public BoardListCell() : base()
{
}
public BoardListCell (IntPtr handle) : base (handle)
{
}
public void setData(String top, String bottom)
{
exp.Text = top;
playTime.Text = bottom;
}
}
When I debug, the cell is being created. However, its outlets are null. If I instantiate them in the constructor, I don't get the error, but this obviously defeats the purpose of making prototypes in xcode as it wipes out the layout, etc.
I was having the same issue described above. The solution for me was to remove the call to TableView.RegisterClassForCellReuse(typeof(YourCustomeTableCell), "YourTableCellName");
If you created the Cell in the Xcode designer, there is no need to register the Cell.
Hope this helps anyone still having this issue.
I've searched around the xamarin tutorials and over the various posts about UITableView in xamarin but I couldn't found whatever about UITableView with static cells.
What I'm trying to achieve is a simple detail screen like twitterific app, but without Nib or storyboards (using mvvmcross, storyboards aren't available and Nib files prevent from using static UITableView, at least I couldn't find any way to do it)
Furthermore, after trying differents solutions I've ended up with something like this:
UITableViewController
public override int NumberOfSections(UITableView tableView)
{
return 1;
}
public override int RowsInSection(UITableView tableview, int section)
{
return 1;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell("test");
if (cell == null)
{
cell = new SejourInfoViewPatientCell();
//cell = new UITableViewCell(UITableViewCellStyle.Subtitle, "test");
}
//cell.TextLabel.Text = "test";
return cell;
}
But now the mvvmcross binding doesn't works. If I take the exact same binding and use it on a non static UITableViewController everything works fine
If someone could point me to a direction I'll be glad
I've been struggling with the same problem. I was able to get around the binding issue by having my view inherit from MvxViewController then in ViewDidLoad:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
UITableView table = new UITableView();
Action<UITableViewCell> initializers = new Action<UITableViewCell>[] {
(cell) => {
this.CreateBinding(cell.TextLabel)
.For(c => c.Text)
.To(x => x.FirstName)
.Apply();
},
(cell) => {
this.CreateBinding(cell.TextLabel)
.For(c => c.Text)
.To(x => x.LastName)
.Apply();
}
};
StubDataSource source = new StubDataSource(table, initializers);
table.Source = source;
}
StubDataSource inherits from MvxStandardTableViewSource (though depending on your needs you'll probably want to inherit off UITableViewSource (overriding GetCell instead).
public class StubDataSource : MvxStandardTableViewSource
{
private readonly Action<UITablleViewCell>[] _inits;
public StubDataSource(
UITableView tableView,
Action<UITablleViewCell>[] inits)
: base(tableView)
{
_inits = inits;
}
public override int NumberOfSections(UITableView tableView)
{
return 1;
}
public override int RowsInSection(UITableView tableview, int section)
{
return 2;
}
protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
var reuse = base.CreateDefaultBindableCell(tableView, indexPath, item);
switch(indexPath.Row)
{
case 0:
_inits[0](reuse);
case 1:
_inits[1](reuse);
}
return reuse;
}
}
Two points to note,
This is a bit of hack (thanks Closures), but its the only way to bind to static tables in ios that I can think of. Hopefully someone will suggest a better way.
The second is when you create custom cells with subviews (e.g. UITextFields etc) you should create a custom cell type and have those subviews as a class variable, otherwise the GC will collect them you'll get SIGSEGV exceptions see
I'm not sure what a static TableView or Cell are, but I've just posted a complete sample using "custom cells" on https://github.com/slodge/ListApp in answer to MvxTableViewSource DequeueReusableCell issue when scrolling
The key parts were:
ensure the custom cell includes an IntPtr constructor
use RegisterClassForCellReuse on the custom cell type
override GetOrCreateCellFor in your table source - and use DequeueReusableCell to create the cell
For variable height cells, you'll also need to override GetHeightForRow within the table source
Is it possible to create and use 'Custom' prototype table cells in xamarin ios (monotouch) using storyboard?
I could only find this by Stuart Lodge explaining a method using xib/nibs:
http://www.youtube.com/watch?feature=player_embedded&v=Vd1p2Gz8jfY
Let's answer this question! I was also looking for this one. :)
1) Open Storyboard where you have your ViewController with TableView:
Add prototype cell (if there is no cell added before):
Customize cell as you want (in my case there is custom UIImage and Label):
Remember to set height of the cell. To do it select your whole TableView and from the Properties window select "Layout" tab. On the top of the properties window you should see "row height" - put the appropriate value:
Now select prototype cell once again. In the Properties window type the name of the class (it will create code-behind class for it). In my case this is "FriendsCustomTableViewCell". After that provide "Identifier" for your cell. As you can see my is "FriendCell". Last thing to set is "Style" property set to custom. "Name" field should be empty. Once you click "enter" after typing "Class" code-behind file will be automatically created:
Now code behind for the cell should look like below:
public partial class FriendsCustomTableViewCell : UITableViewCell
{
public FriendsCustomTableViewCell (IntPtr handle) : base (handle)
{
}
public FriendsCustomTableViewCell(NSString cellId, string friendName, UIImage friendPhoto) : base (UITableViewCellStyle.Default, cellId)
{
FriendNameLabel.Text = friendName;
FriendPhotoImageView.Image = friendPhoto;
}
//This methods is to update cell data when reuse:
public void UpdateCellData(string friendName, UIImage friendPhoto)
{
FriendNameLabel.Text = friendName;
FriendPhotoImageView.Image = friendPhoto;
}
}
In UITableViewSource you have to declare cellIdentifier at the top of the class (in my case it is "FriendCell") and in "GetCell" method you have to cast cells and set data for them:
string cellIdentifier = "FriendCell";
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
FriendsCustomTableViewCell cell = (FriendsCustomTableViewCell) tableView.DequeueReusableCell(cellIdentifier);
Friend friend = _friends[indexPath.Row];
//---- if there are no cells to reuse, create a new one
if (cell == null)
{ cell = new FriendsCustomTableViewCell(new NSString(cellIdentifier), friend.FriendName, new UIImage(NSData.FromArray(friend.FriendPhoto))); }
cell.UpdateCellData(friend.UserName, new UIImage(NSData.FromArray(friend.FriendPhoto)));
return cell;
}
That's it, now you can use your custom cells. I hope that it will help.
I am trying to add cells to a static table that I created using storyboard. I want to be able to add cell to one section of it dynamically from code, I implemented a custom UITableViewSource but it keeps overwriting my static cells. How should I implement GetCell() method so it will work with static cells too? Looks like in ObjectiveC it can be done by following:
return [super tableView:tableView numberOfRowsInSection:section]
But how to implement it in Xamarin iOS?
I was able to get it to work using this code:
public partial class StaticTableController : UITableViewController
{
public StaticTableController (IntPtr handle) : base (handle)
{
}
[Export("tableView:cellForRowAtIndexPath:")]
public UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var selector = new Selector("tableView:cellForRowAtIndexPath:");
//WTF?
var cell = new UITableViewCell(Messaging.IntPtr_objc_msgSendSuper_IntPtr_IntPtr(SuperHandle, selector.Handle, tableView.Handle, indexPath.Handle));
if (indexPath.Row == 2)
{
cell.TextLabel.Text = "Dynamic thingy";
}
return cell;
}
}
Looks a bit weird, you might be better off using a dynamic table instead. One thing I tried that doesn't work is modifying the number of cells in a section -- this just seems to directly crash with the Obj-C version of an "index out of range" inside UITableView.