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.
Related
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 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
I use Xamarin Studio to create a new custom table cell in a MonoTouch project (File -> New -> File... -> iOS -> iPhone Table View Cell). It creates MyViewCell subclass of UITableViewCell. The class already contains a static Create() method.
Then I use this class in table's GetCell method this way:
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell(MyViewCell.Key) as MyViewCell;
if (cell == null)
{
cell = MyViewCell.Create();
}
// my own code to update the cell content
cell.Update(someData);
return cell;
}
In Update method I use some subview transformations. The problem is that when the cell is first time created (Create method is called) the transformations are not applied.
But if I schedule the update to the next run loop iteration everything works fine:
NSTimer.CreateScheduledTimer(TimeSpan.Zero, () =>
{
cell.Update(someData);
});
What is the problem here? Can I solve the problem without using this hack?
the first time a cell has not been added in the table, when you called Update
for cell subview transformation override PrepareForReuse method or LayoutSubviews method on the cell class, depending on what you need.
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.