Please note this is a duplicate of a forum question over at Xamarin Forums.
I have a UITableView of which the source is set to a UITableViewSource sub class. I have multiple methods in use at the moment, amongst the obvious ones; NumberOfSections, GetCells, RowsInSection, RowsSelected etc.
This morning whilst implementing a refresh view at the top of the table I tried to implement the scrollViewDidScroll (Scroll()) method, however it is not being called when the table view scrolls. There is no exception being thrown, nor is it hitting the breakpoint - but there is an assertion failed which appears in the console occasionally when I scroll the table view (not every time though):
Dec 18 12:50:02 iMac assertiond[26037]: assertion failed: 15B42 13C75: assertiond + 12188 [8CF1968D-3466-38B7-B225-3F6F5B64C552]: 0x1
The table view source is set in the ViewController like so:
journeyTableView.Source = dts = new DashboardTableSource ();
And within the TableViewSource code is as follows:
using Foundation;
using System;
using System.CodeDom.Compiler;
using UIKit;
using System.Collections.Generic;
using CoreGraphics;
using VideoToolbox;
namespace AppName
{
public class DashboardTableSource : UITableViewSource
{
public const string CellIndentifer = "DriveCell";
#region Refresh View Related
public override void Scrolled (UIScrollView scrollView)
{
// Globals.refreshView.DidScroll ();
Console.WriteLine ("TV Scrolled!");
}
#endregion
public override nint NumberOfSections (UITableView tableView)
{
if(!dataLoaded)
{
var rect = new CGRect (0, 0, tableView.Bounds.Width, tableView.Bounds.Height);
UIImageView iv = new UIImageView(tableView.Bounds);
iv.Image = UIImage.FromBundle ("NoData");
iv.ContentMode = UIViewContentMode.Center;
tableView.BackgroundView = iv;
return 0;
}
else
{
UIView.Animate (0.5, ()=>{
if(tableView.BackgroundView != null){
tableView.BackgroundView.Alpha = 0.0f;
tableView.BackgroundView = null;
}
});
return 1;
}
}
public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (CellIndentifer) as DashboardTableCell ??
new UITableViewCell (UITableViewCellStyle.Default, CellIndentifer) as DashboardTableCell;
if (cell != null) {
cell.SetupCell (dataLoaded, indexPath);
return cell;
} else {
return null;
}
}
public override nint RowsInSection (UITableView tableview, nint section)
{
if (!dataLoaded)
return 0;
else
return obj.Count;
}
public override bool CanEditRow (UITableView tableView, NSIndexPath indexPath)
{
return true;
}
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.CellAt (indexPath) as DashboardTableCell;
cell.SetSelectedState (true, true);
SelectedRow = indexPath.Row;
}
public override void RowDeselected (UITableView tableView, NSIndexPath indexPath)
{
}
}
}
There are no other events which are listening for a table view scroll within the application, so I cannot really understand as to why this event is not being fired. Any help would be appreciated!
Thanks.
your code seems to be fine except for one thing. The problem is in you GetCell method actually.
First of all I assume that DashboardTableCell is a subclass of UITableVIewCell. If that is the case, in this line
new UITableViewCell (UITableViewCellStyle.Default, CellIndentifer) as DashboardTableCell you are trying to cast base class to sub class which can (and in my testing did) return null.
as operator returns null if cast was not successful instead of throwing exception
Quote from https://msdn.microsoft.com/en-us/library/cscsdfbt.aspx
The as operator is like a cast operation. However, if the conversion isn't possible, as returns null instead of raising an exception.
And then if cell is null you return null. Returning null caused Assertion failure for me too.
So you could simply replace UITableViewCell with DashboardTableCell and remove as like that: new DashboardTableCell (UITableViewCellStyle.Default, CellIndentifer);
but this way looks cleaner to me:
public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (CellIndentifer) as DashboardTableCell;
if (cell == null) {
// Notice that we are creating an instance of DashboardTableCell
// instead of UITableViewCell
cell = new DashboardTableCell (UITableViewCellStyle.Default, CellIndentifer);
}
// cell is definitely not null now. You can update and return
cell.SetupCell (dataLoaded, indexPath);
return cell;
}
If this won't solve your problem I will look into your code once again :)
Related
I writing xamarin.ios app and using TableView.
I want to Create a custom cell.
So in the designer, I created it with this properties
I try to use it then in TableSource
Like this
public class ExperienceSource : UITableViewSource
{
Experience[] TableItems;
//NSString cellIdentifier = new NSString("TableCell");
ExperienceController owner;
public ExperienceSource(Experience[] items, ExperienceController owner)
{
TableItems = items;
this.owner = owner;
}
public override nint NumberOfSections(UITableView tableView)
{
return 1;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return TableItems.Length;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell("cell_id", indexPath) as ExperienceCell;
//Experience item = TableItems[indexPath.Row];
/*if (cell == null)
{
cell = new ExperienceCell(cellIdentifier);
}*/
cell.UpdateCell(TableItems[indexPath.Row].title, TableItems[indexPath.Row].price);
//---- if there are no cells to reuse, create a new one
/*if (cell == null)
{
cell = new ()(CellIdentifier);
}*/
return cell;
}
}
And Here is class for ExperienceCell
public partial class ExperienceCell : UITableViewCell
{
public ExperienceCell (IntPtr handle) : base (handle)
{
}
internal void UpdateCell(string title, string price)
{
ExperienceTitle.Text = title;
ExperincePrice.Text = price;
}
}
When I run app, I got this error
unable to dequeue a cell with identifier cell_id - must register a nib
or a class for the identifier or connect a prototype cell in a
storyboard
How I can fix this?
Thank's for help.
You are missing the registration of the UITableViewCell in the UITableView.
Please add something like this in your ViewController (the important part is the RegisterNibForCellReuse sentence):
public override void ViewDidLoad()
{
base.ViewDidLoad();
MyTableView.RegisterNibForCellReuse(UINib.FromName(nameof(ExperienceCell), NSBundle.MainBundle), "cell_id");
MyTableView.Source = new ExperienceSource(..., ...);
// .. your code
}
This is a necessary step when you declare your UITableViewCells outside the UITableView.
Consider this simple example:
public partial class TableViewController : UITableViewController
{
public TableViewController (IntPtr handle) : base (handle)
{
}
protected override void Dispose (bool disposing)
{
Console.WriteLine (String.Format ("{0} controller disposed - {1}", this.GetType (), this.GetHashCode ()));
base.Dispose (disposing);
}
public override void ViewDidLoad ()
{
//TableView.Source = new TableSource(this);
TableView.Source = new TableSource();
}
}
public class TableSource : UITableViewSource {
private TableViewController controller;
string CellIdentifier = "TableCell";
public TableSource ()
{
}
public TableSource (TableViewController controller)
{
this.controller = controller;
}
public override nint RowsInSection (UITableView tableview, nint section)
{
return 1;
}
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell (CellIdentifier);
//if there are no cells to reuse, create a new one
if (cell == null){
cell = new UITableViewCell (UITableViewCellStyle.Default, CellIdentifier);
}
cell.TextLabel.Text = "test";
return cell;
}
}
I've noticed that the view controller (TableViewController) is never released. The table view controller has a reference to the data source, but the data source also has a reference to the table view controller.
With TableView.Source = new TableSource(); the view controller gets released, with TableView.Source = new TableSource(this); it's not.
How should this reference cycle be broken so that everything get released?
Edit:
Now I tried the WeakReference:
Through using a WeakReference the Dispose method is called, when the view controller is popped off the navigation stack.
In ViewDidLoad:
TableView.Source = new TableSource(new WeakReference<TableViewController> (this));
In the datasource:
private WeakReference<TableViewController> controller;
public TableSource (WeakReference<TableViewController> controller)
{
this.controller = controller;
}
I built this into my real project, but how can I access my controller? I get the message
Type 'System.WeakReference' does not contain a definition for 'xxx' and no extension method 'xxx' of type 'System.WeakReference' could be found. Are you missing an assembly reference?
You work with Xamarin, as I see? Have you tried WeakReference?
https://msdn.microsoft.com/en-us/library/system.weakreference(v=vs.110).aspx
PS:
private WeakReference weakController;
to set:
this.weakController = new WeakReference(controller);
to get:
if (weakController.isAlive)
{
TableViewController controller = weakController.Target as TableViewController;
}
change
public partial class TableViewController : UITableViewController
to
public partial class TableViewController : UITableViewController, UITableViewSource
and in ViewDidLoad just do
self.TableView.Source = self;
the source property is a weak reference internally already so your have no problem of managing that. It is a convenience property to make the tbaleviewcontroller the delegate and datasource altogether. (Just like you would in native iOS)
You can move the methods into the controller itself which would be less of a hassle than WeakReference. Then mark them with export attribute which then allows you to set the UITableView.WeakDataSource property to the controller itself.
[Export("tableView:numberOfRowsInSection:")]
public nint RowsInSection (UITableView tableview, nint section)
[Export("tableView:cellForRowAtIndexPath:")]
public UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
Once they are moved you can attach the datasource:
public override void ViewDidLoad ()
{
TableView.WeakDataSource = this;
}
I have a UITAbleView on my main ViewController. Tableview is subclasses as seen below. When a user selects a row I want to switch to a new view by calling a routine on the main ViewController. However, i'm not able to access my main viewcontroller from the subclass. How should I go about this?
public class TableSource : UITableViewSource
{
string[] tableItems;
string cellIdentifier = "TableCell";
public TableSource(string[] items)
{
tableItems = items;
}
public override int RowsInSection(UITableView tableview, int section)
{
return tableItems.Length;
}
public override UITableViewCell GetCell(UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell(cellIdentifier);
if (cell == null)
cell = new UITableViewCell(UITableViewCellStyle.Default, cellIdentifier);
cell.TextLabel.Text = tableItems[indexPath.Row];
return cell;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
new UIAlertView("Row Selected", tableItems[indexPath.Row], null, "OK", null).Show();
tableView.DeselectRow(indexPath, true);
//Call routine in the main view controller to switch to a new view
}
}
I happened to come across this post after finding and commenting on a similar post:
Xamarin UITableView RowSelection
While passing an instance of your UIViewController to your TableSource is one way to solve this problem, it does have its drawbacks. Mainly that you've tightly coupled your TableSource to that particular type of UIViewController.
What I would suggest instead is to create an EventHandler in your UITableViewSource like so:
public event EventHandler ItemSelected;
I would also set up a getter property for the selected item:
private selectedItem
public MyObjectType SelectedItem{
get{
return selectedItem;
}
}
I would then update the RowSelected method like so:
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
selectedItem = tableItems[indexPath.Row];
ItemSelected(this, EventArgs.Empty);
}
Your UIViewController could then listen to the ItemSelected event and do whatever it needs. This allows you to reuse your UITableViewSource for multiple Views and ViewControllers.
Add it to your .ctor, e.g.
public TableSource(string[] items)
becomes:
public TableSource(string[] items, UIViewController ctl)
then keep a reference to it:
public TableSource(string[] items, UIViewController ctl)
{
tableItems = items;
controller = ctl;
}
and use it in your RowSelected call:
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
new UIAlertView("Row Selected", tableItems[indexPath.Row], null, "OK", null).Show();
tableView.DeselectRow(indexPath, true);
controller.DoWhatYouNeedWithIt ();
}
NSNotifications might be another option.
So in the RowSelected you post a notification:
NSNotificationCenter.DefaultCenter.PostNotificationName("RowSelected", indexPath);
And in the view controller you add an observer for the notification:
public override void ViewDidLoad()
{
base.ViewDidLoad();
NSNotificationCenter.DefaultCenter.AddObserver(new NSString("RowSelected"), RowSelected);
}
void RowSelected(NSNotification notification)
{
var indexPath = notification.Object as NSIndexPath;
// Do Something
}
I have a navigation controller. Inside it i have a view controller that has a button. When the button is clicked a popover shows with a tableview of items.
When an item is selected i want to either navigate to a different view controller or open up a view controller on top of the current one.
I hope i've made my objectives clear.
Below is my current code:
private UITableView tableView;
private List list;
private class TableViewDelegate : UITableViewDelegate
{
private List<string> list;
public TableViewDelegate(List<string> list)
{
this.list = list;
}
public override void RowSelected (
UITableView tableView, NSIndexPath indexPath)
{
//THIS IS WHERE I AM CURRENTLY PUTTING THE NAVIGATION CONTROLLER PUSHVIEWCONTROLLER. BUT ITS NOT WORKING !!!
}
}
private class TableViewDataSource : UITableViewDataSource
{
static NSString kCellIdentifier =
new NSString ("MyIdentifier");
private List<string> list;
public TableViewDataSource (List<string> list)
{
this.list = list;
}
public override int RowsInSection (
UITableView tableview, int section)
{
return list.Count;
}
public override UITableViewCell GetCell (
UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell =
tableView.DequeueReusableCell (
kCellIdentifier);
if (cell == null)
{
cell = new UITableViewCell (
UITableViewCellStyle.Default,
kCellIdentifier);
}
cell.TextLabel.AdjustsFontSizeToFitWidth = true;
cell.TextLabel.Font = UIFont.FromName("Helvetica", 14.0f);
cell.TextLabel.Text = list[indexPath.Row];
return cell;
}
}
in monotouch you should use
class TableDelegate : UITableViewDelegate
{
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
this.YourNavigationController.PushViewController(yourViewController,true);
}
}
also i recommend you to use monotouch.dialog
in your table source declare an event like this:
public class TableSourceAnimales : UITableViewSource
{
public event RowSelectedEventHandler RowSelectedEvent;
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
if (this.RowSelectedEvent != null) {
RowSelectedEvent (indexPath);
}
}
}
and in your uiviewcontroller like this
namespace yournamespace
public delegate void RowSelectedEventHandler(MonoTouch.Foundation.NSIndexPath indexpath);
public partial class yourclass : UIViewController
{
public override void ViewDidLoad ()
{
TableViewDataSource tablelist = new TableViewDataSource (yourlist);
table.Source = tablelist;
table.ReloadData ();
table.RowSelectedEvent += tablelist_RowSelectedEvent;
}
public void tablelist_RowSelectedEvent (NSIndexPath indexpath)
{
this.YourNavigationController.PushViewController(yourViewController,true);
//or what you want to do
}
}
Check which row is clicked in this function:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
And then show your view controller.
This will do the trick:
- (void)tableView:(UITableView *)tblView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
YourViewController *vc = [[[YourViewController alloc] initWithNibName:#"YourViewController" bundle:[NSBundle mainBundle]] autorelease];
[self.navigationController pushViewController:vc animated:YES];
}
Something like this in mono:
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
var DetailViewController = new DetailViewController ();
// Pass the selected object to the new view controller.
controller.NavigationController.PushViewController(DetailViewController, true);
}
actually its like in Obj-C only the syntax is a bit different. Try to do it yourself next time, its easy.
I understand there are several similar questions already asked and I went through all of them, but none of them solved my problem.
I have a simple UIViewController which contains a UISearchBar at top and a UITableView. This is the basic code:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
searchBar.TextChanged += delegate {
tableView.DataSource = new CodeSearchTableViewDataSource(searchBar.Text);
tableView.ReloadData();
};
tableView.DataSource = new CodeSearchTableViewDataSource(searchBar.Text);
}
public class CodeSearchTableViewDataSource : UITableViewDataSource
{
static NSString CELL_ID = new NSString("MYID");
public List<CodeItem> CodesFound { get; set; }
public CodeSearchTableViewDataSource()
{
}
public CodeSearchTableViewDataSource(string searchText)
{
CodesFound = CodeSearch.Instance.Find(searchText);
}
public override int RowsInSection (UITableView tableView, int section)
{
return CodesFound.Count;
}
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell(CELL_ID);
if (cell == null)
cell = new UITableViewCell(UITableViewCellStyle.Default, CELL_ID);
cell.TextLabel.Text = CodesFound[indexPath.Row].Id + " " + CodesFound[indexPath.Row].Desc;
return cell;
}
}
I don't have custom delegate. What I don't understand is that when this view loads up the first time, the table is populated with a valid list. However, after I enter a character in search box, a new list is sent to the table, and it crashes.
There's no (managed) reference to the cell you are creating in GetCell once it returns from the method. As such the Garbage Collector (GC) can collect it whenever it wants to.
For more details see my answer (and its links) for the question on: Monotouch - UITableview Cell null reference exception