Only the last UISwitch in UITableViewCell fires - xamarin.ios

I am trying to add a UISwitch in every cell (this works). When I tap to toggle any switch other than the last one, they all give the last switches state until I change the last switches state
`public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) {
//---- declare vars
UITableViewCell cell = tableView.DequeueReusableCell (this._cellIdentifier);
//---- if there are no cells to reuse, create a new one
if (cell == null) {
// This changes the style of the UITableViewCell to the Default style
cell = new UITableViewCell (UITableViewCellStyle.Default, this._cellIdentifier);
// Instantiate a cell accessory here
uiSwitch = new UISwitch (new RectangleF (0f, 0f, 20f, 20f));
uiSwitch.Tag = indexPath.Row;
uiSwitch.ValueChanged += (object sender, EventArgs e) => {
Console.WriteLine ("Cell Switch value is now {0}", uiSwitch.On);
};
_vRMs.View.AddSubview (uiSwitch);
// keep a reference to each cell you create,
// e.g. add them to a static List<UITableViewCell>.
// The GC won't be able to collect them so the event handler will work later.
cells.Add (cell);
}
//---- create a shortcut to our item
TableViewItem item = this._TableViewItemGroupList[indexPath.Section].Items[indexPath.Row];
cell.TextLabel.Text = item.Name;
cell.Accessory = UITableViewCellAccessory.DisclosureIndicator;
cell.AccessoryView = uiSwitch;
// cell.DetailTextLabel.Text = item.SubHeading;
return cell;
}`
I wanted to know if all this code was necessary to create a table with the UISwitches - being new to the iPhone dev world, I'm not sure.
I'm hoping this update will help in my cause.
`using System;
using System.Drawing;
using System.Collections.Generic;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace eOneRaw {
public partial class vRMs : UIViewController {
#region FIELDS
private string _ViewTitle = "RMs";
private UITableView _TableView;
private TableViewDataSource _TableViewDataSource;
private List<TableViewItemGroup> _TableViewItemGroupList;
private static vRMs _vRMs;
#endregion
#region PROPERTIES
#endregion
#region ViewDidLoad
public override void ViewDidLoad () {
base.ViewDidLoad ();
// Title the Controller
Title = _ViewTitle;
#region UITableView Setup
// Set up the table and data
this.CreateTableItems ();
// Create the UITableView
_TableView = new UITableView() {
Delegate = new TableViewDelegate(_TableViewItemGroupList),
DataSource = _TableViewDataSource,
AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth,
};
_TableView.SizeToFit();
// Reposition and resize the receiver
_TableView.Frame = new RectangleF (0, 0, this.View.Frame.Width, this.View.Frame.Height);
// Add the table view as a subview
this.View.AddSubview(_TableView);
#endregion
#region Define the Look of the View
var _barbtnCancel = new UIBarButtonItem(UIBarButtonSystemItem.Done);
NavigationItem.LeftBarButtonItem = _barbtnCancel;
_barbtnCancel.Clicked += (s, e) => {
this.NavigationController.PopViewControllerAnimated(true);
};
#endregion
} // end ViewDidLoad
#endregion
#region METHODS
public vRMs () {
// Shouldn't ever happen
_vRMs = this;
Console.WriteLine (Environment.StackTrace);
}
public override void DidReceiveMemoryWarning () {
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
#endregion
#region ALL TABLE FUNCTIONALITY
#region CreateTableItems
//========================================================================
/// <summary>
/// Creates a set of table items.
/// </summary>
// This is where you define your table
protected void CreateTableItems () {
_TableViewItemGroupList = new List<TableViewItemGroup> ();
//---- declare vars
TableViewItemGroup tGroup;
tGroup = new TableViewItemGroup() { Name = "Regional Managers" };
tGroup.Items.Add (new TableViewItem() { Name = "Chris" });
tGroup.Items.Add (new TableViewItem() { Name = "Mike" });
tGroup.Items.Add (new TableViewItem() { Name = "Dan" });
tGroup.Items.Add (new TableViewItem() { Name = "Steve" });
_TableViewItemGroupList.Add (tGroup);
this._TableViewDataSource = new TableViewDataSource(_TableViewItemGroupList);
}
#endregion
#region CLASS TableViewDelegate
// The delegate manages the transitions from view-to-view
private class TableViewDelegate : UITableViewDelegate {
private List<TableViewItemGroup> _TableViewItemGroupList;
public TableViewDelegate(List<TableViewItemGroup> pList) {
this._TableViewItemGroupList = pList;
}
public override void RowSelected (UITableView tableView, NSIndexPath indexPath) {
return;
}
}
#endregion
#region CLASS TableViewDataSource
public class TableViewDataSource : UITableViewDataSource {
protected List<TableViewItemGroup> _TableViewItemGroupList;
string _cellIdentifier = "TableViewCell";
private static UISwitch uiSwitch;
static List<UITableViewCell> cells = new List<UITableViewCell> ();
public TableViewDataSource (List<TableViewItemGroup> items) {
this._TableViewItemGroupList = items;
}
/// <summary>
/// Called by the TableView to determine how many sections(groups) there are.
/// </summary>
public override int NumberOfSections (UITableView tableView) {
return this._TableViewItemGroupList.Count;
}
/// <summary>
/// Called by the TableView to determine how many cells to create for that particular section.
/// </summary>
public override int RowsInSection (UITableView tableview, int section) {
return this._TableViewItemGroupList[section].Items.Count;
}
/// <summary>
/// Called by the TableView to retrieve the header text for the particular section(group)
/// </summary>
public override string TitleForHeader (UITableView tableView, int section) {
return this._TableViewItemGroupList[section].Name;
}
/// <summary>
/// Called by the TableView to retrieve the footer text for the particular section(group)
/// </summary>
public override string TitleForFooter (UITableView tableView, int section) {
return this._TableViewItemGroupList[section].Footer;
}
#region UITableViewCell
/// <summary>
/// Called by the TableView to get the actual UITableViewCell to render for the particular section and row
/// </summary>
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath) {
//---- declare vars
UITableViewCell cell = tableView.DequeueReusableCell (this._cellIdentifier);
//---- if there are no cells to reuse, create a new one
if (cell == null) {
// This changes the style of the UITableViewCell to the Default style
cell = new UITableViewCell (UITableViewCellStyle.Default, this._cellIdentifier);
// This changes the style of the UITableViewCell to the Subtitle style,
// which displays a second line of text within the cell.
// cell = new UITableViewCell (UITableViewCellStyle.Subtitle, this._cellIdentifier);
// Instantiate a cell accessory here
uiSwitch = new UISwitch (new RectangleF (0f, 0f, 20f, 20f));
uiSwitch.Tag = indexPath.Row;
uiSwitch.ValueChanged += (object sender, EventArgs e) => {
Console.WriteLine ("Cell Switch value is now {0}", uiSwitch.On);
};
_vRMs.View.AddSubview (uiSwitch);
// keep a reference to each cell you create,
// e.g. add them to a static List<UITableViewCell>.
// The GC won't be able to collect them so the event handler will work later.
cells.Add (cell);
}
//---- create a shortcut to our item
TableViewItem item = this._TableViewItemGroupList[indexPath.Section].Items[indexPath.Row];
cell.TextLabel.Text = item.Name;
cell.Accessory = UITableViewCellAccessory.DisclosureIndicator;
cell.AccessoryView = uiSwitch;
// cell.DetailTextLabel.Text = item.SubHeading;
// Add an image if needed
/*
if(!string.IsNullOrEmpty(item.ImageName))
{
cell.ImageView.Image = UIImage.FromFile("Images/" + item.ImageName );
}
*/
return cell;
}
#endregion
} // end TableViewDataSource Class
#endregion
#region CLASS TableViewItemGroup
//========================================================================
/// <summary>
/// A group that contains table items
/// </summary>
public class TableViewItemGroup {
public string Name { get; set; }
public string Footer { get; set; }
public List<TableViewItem> Items {
get { return this._items; }
set { this._items = value; }
}
protected List<TableViewItem> _items = new List<TableViewItem>();
public TableViewItemGroup () {
}
}
#endregion
#region CLASS TableViewItem
//========================================================================
/// <summary>
/// Represents our item in the table
/// </summary>
public class TableViewItem {
public string Name { get; set; }
public string SubHeading { get; set; }
public string ImageName { get; set; }
public TableViewItem () {
}
}
#endregion
#endregion
#region OBSOLETE methods
// ***************************** OBSOLETE
// ***************************** OBSOLETE
// ***************************** OBSOLETE
[Obsolete]
public override void ViewDidUnload () {
base.ViewDidUnload ();
// Clear any references to subviews of the main view in order to
// allow the Garbage Collector to collect them sooner.
//
// e.g. myOutlet.Dispose (); myOutlet = null;
ReleaseDesignerOutlets ();
}
[Obsolete]
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation) {
// Return true for supported orientations
return (toInterfaceOrientation != UIInterfaceOrientation.PortraitUpsideDown);
}
#endregion
}
}`

There are a few problems with your code.
Firstly, I think your code sample misses a vital part?
What is the scope of the UISwitch variable? It seems like it's a class level reference?
Secondly, I think your code doesn't really handle cell reuse very well - the line uiSwitch.Tag = indexPath.Row; isn't called in the case of reuse.
Thirdly, I'm not too sure that your approach is really very scalable - although it would work for small lists
In all honestly, you are probably better off just creating a custom cell for this switch - and then using the values from those custom cells... To create a custom cell, see:
using code in http://docs.xamarin.com/ios/Guides/User_Interface/Tables/Part_3_-_Customizing_a_Table%27s_Appearance
using XIBs - http://www.alexyork.net/blog/2011/07/18/creating-custom-uitableviewcells-with-monotouch-the-correct-way/ and http://slodge.blogspot.co.uk/2013/01/uitableviewcell-using-xib-editor.html

In addition to the problems pointed out by Stuart, the other problem is that you are recycling the cells, so you might end up with a recycled cell that is pointing to another state, nto what you wanted.
For a case like this, you could just use a unique key for each UISlider that you create to ensure that you never reuse a UISlider for two different variables. Alternatively, you need to change your code so that the handler tells the different based on the IndexPath that acted on it.

Related

UICollectionView - Curious flow layout

I'm experiencing with the UICollectionView(Controller) for the first time. Actually it should be as simple as working with TableViews, it's not though.
Rather than showing all images in a flow (several rows) just the top row is displayed. All the other images are somewhere... scrolling is enabled but nothing happens, no bouncing, no scrolling, ...
And after an orientation change (and back) some more images are visible but they appear randomly. After every orientation change they appear at an other location.
My example should have 7 images.
My properties in IB:
First time:
After rotating (and back):
And my source code to implement the photo gallery.
using System;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using System.Collections.Generic;
using Xamarin.Media;
using MonoTouch.AssetsLibrary;
using MonoTouch.CoreGraphics;
using System.Diagnostics;
using System.Linq;
using System.Drawing;
namespace B2.Device.iOS
{
public partial class TagesRapportDetailRegieBilderCollectionViewController : UICollectionViewController
{
private const string Album = "Rapport";
public TagesRapportDetailRegieBilderSource Source { get; private set; }
private TagesRapportDetailRegieBilderDelegate _delegate;
public TagesRapportDetailRegieBilderCollectionViewController (IntPtr handle) : base (handle)
{
Source = new TagesRapportDetailRegieBilderSource(this);
_delegate = new TagesRapportDetailRegieBilderDelegate(this);
// Delegate - Muss im konstruktor sein. ViewDidLoad geht nicht!
CollectionView.Delegate = _delegate;
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Cell Klasse registrieren
CollectionView.RegisterClassForCell (typeof(ImageCell), new NSString("imageCell"));
// DataSource
CollectionView.Source = Source;
// Bilder laden
LoadImages();
}
private void LoadImages()
{
Source.Images.Clear();
var assetsLibrary = new ALAssetsLibrary();
assetsLibrary.Enumerate(ALAssetsGroupType.Album, GroupsEnumeration, GroupsEnumerationFailure);
}
private void GroupsEnumeration(ALAssetsGroup group, ref bool stop)
{
if (group != null && group.Name == Album)
{
//notifies the library to keep retrieving groups
stop = false;
//set here what types of assets we want,
//photos, videos or both
group.SetAssetsFilter(ALAssetsFilter.AllPhotos);
//start the asset enumeration
//with ALAssetsGroup's Enumerate method
group.Enumerate(AssetsEnumeration);
CollectionView.ReloadData();
}
}
private void AssetsEnumeration(ALAsset asset, int index, ref bool stop)
{
if (asset != null)
{
//notifies the group to keep retrieving assets
stop = false;
//use the asset here
var image = new UIImage(asset.AspectRatioThumbnail());
Source.Images.Add(image);
}
}
private void GroupsEnumerationFailure(NSError error)
{
if (error != null)
{
new UIAlertView("Zugriff verweigert", error.LocalizedDescription, null, "Schliessen", null).Show();
}
}
}
public class TagesRapportDetailRegieBilderDelegate : UICollectionViewDelegateFlowLayout
{
private TagesRapportDetailRegieBilderCollectionViewController _controller;
public TagesRapportDetailRegieBilderDelegate(TagesRapportDetailRegieBilderCollectionViewController controller)
{
_controller = controller;
}
public override System.Drawing.SizeF GetSizeForItem(UICollectionView collectionView, UICollectionViewLayout layout, NSIndexPath indexPath)
{
var size = _controller.Source.Images[indexPath.Row].Size.Width > 0
? _controller.Source.Images[indexPath.Row].Size : new SizeF(100, 100);
size.Width /= 3;
size.Height /= 3;
return size;
}
public override UIEdgeInsets GetInsetForSection(UICollectionView collectionView, UICollectionViewLayout layout, int section)
{
return new UIEdgeInsets(50, 20, 50, 20);
}
}
public class TagesRapportDetailRegieBilderSource : UICollectionViewSource
{
private TagesRapportDetailRegieBilderCollectionViewController _controller;
public List<UIImage> Images { get; set; }
public TagesRapportDetailRegieBilderSource(TagesRapportDetailRegieBilderCollectionViewController controller)
{
_controller = controller;
Images = new List<UIImage>();
}
public override int NumberOfSections(UICollectionView collectionView)
{
return 1;
}
public override int GetItemsCount(UICollectionView collectionView, int section)
{
return Images.Count;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = collectionView.DequeueReusableCell(new NSString("imageCell"), indexPath) as ImageCell;
cell.Image = Images[indexPath.Row];
return cell;
}
}
public class ImageCell : UICollectionViewCell
{
UIImageView imageView;
[Export ("initWithFrame:")]
public ImageCell (System.Drawing.RectangleF frame) : base (frame)
{
imageView = new UIImageView(frame);
imageView.AutoresizingMask = ~UIViewAutoresizing.None;
ContentView.AddSubview (imageView);
}
public UIImage Image
{
set
{
imageView.Image = value;
}
}
}
}
If all you want to do is displaying the cells in an uniform grid, overriding GetSizeForItem shouldn't be necessary in the first place. Just configure the cellsize property of your flow layout either in IB or programatically during ViewDidLoad and be done with it.
There's another problem with your code:
group.Enumerate(AssetsEnumeration)
This will run asynchronously. That means:
CollectionView.ReloadData();
Will only cover a small subset of your images. It would be better to issue a ReloadData() when group == null, which indicates that the enumeration is complete.
You could also avoid ReloadData alltogether and call CollectionView.InsertItem() everytime you've added an image. This has the benefit of your items become immediately visible instead of all of them at once after everything has been enumerated - which may take some time (on the device). The downside is that you've got to be careful to not run into this.

MonoTouch NullReferenceException on tableview scroll

In my app i simply trying to add a checkmark to the selected row. I have around 20 items in my row and want to show a checkmark for the selected row. When i scroll to the bottom of the page and select a row it is throwing NullReferenceException. I have checkmark code reference from here
using System;
using System.Drawing;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using MonoTouch.ObjCRuntime;
using System.Collections.Generic;
namespace SampleApp
{
public partial class FirstViewController : UITableViewController
{
DataSource dataSource;
public FirstViewController () : base ("FirstViewController", null)
{
Title = NSBundle.MainBundle.LocalizedString ("First", "First");
TabBarItem.Image = UIImage.FromBundle ("first");
}
public override void DidReceiveMemoryWarning ()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Perform any additional setup after loading the view, typically from a nib.
// Add Table
TableView.Source = dataSource = new DataSource (this);
}
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
// Return true for supported orientations
return (toInterfaceOrientation != UIInterfaceOrientation.PortraitUpsideDown);
}
class DataSource : UITableViewSource
{
static readonly NSString CellIdentifier = new NSString ("DataSourceCell");
FirstViewController controller;
private List<String> _listData = new List<String> ();
private NSIndexPath _previousRow;
public DataSource (FirstViewController controller)
{
this.controller = controller;
// POPULATE DISPOSITION LIST
PopulateDatabase();
_previousRow = NSIndexPath.FromRowSection(Settings.SelectedIndex,0);
}
void PopulateDatabase()
{
_listData.Add("val1");
_listData.Add("val2");
_listData.Add("val3");
_listData.Add("val4");
_listData.Add("val5");
_listData.Add("val6");
_listData.Add("val7");
_listData.Add("val8");
_listData.Add("val9");
_listData.Add("val10");
_listData.Add("val11");
_listData.Add("val12");
_listData.Add("val13");
_listData.Add("val14");
_listData.Add("val15");
}
// Customize the number of sections in the table view.
public override int NumberOfSections (UITableView tableView)
{
return 1;
}
public override int RowsInSection (UITableView tableview, int section)
{
return _listData.Count;
}
// Customize the appearance of table view cells.
public override UITableViewCell GetCell (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (CellIdentifier);
if (cell == null) {
cell = new UITableViewCell (UITableViewCellStyle.Default, CellIdentifier);
//cell.Accessory = UITableViewCellAccessory.DisclosureIndicator;
}
cell.TextLabel.Text = _listData[indexPath.Row];
cell.TextLabel.Font = UIFont.SystemFontOfSize(18);
return cell;
}
// Row Select
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
// Uncheck the previous row
if (_previousRow != null)
tableView.CellAt(_previousRow).Accessory = UITableViewCellAccessory.None;
// Do something with the row
var row = indexPath.Row;
Settings.SelectedIndex = row;
tableView.CellAt(indexPath).Accessory = UITableViewCellAccessory.Checkmark;
//Console.WriteLine("{0} selected",_controller.Items[row]);
_previousRow = indexPath;
// This is what the Settings does under Settings>Mail>Show on an iPhone
tableView.DeselectRow(indexPath,false);
}
// Set row height
public override float GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return 50f;
}
}
public class Settings
{
public static int SelectedIndex = 0;
}
}
}
I get the exception at line
tableView.CellAt(_previousRow).Accessory =UITableViewCellAccessory.None;
When i debug i found that Accessory
tableView.CellAt(_previousRow).Accessory Unknown member: Accessory
Any idea what is going wrong???
Got the anser
// Row Select
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
// Uncheck the previous row
if (_previousRow != null)
{
NSIndexPath [] temp = tableView.IndexPathsForVisibleRows;
if (temp.Contains(preIndex))
{
tableView.CellAt(_previousRow).Accessory = UITableViewCellAccessory.None;
}
}
// Do something with the row
var row = indexPath.Row;
Settings.SelectedIndex = row;
tableView.CellAt(indexPath).Accessory = UITableViewCellAccessory.Checkmark;
//Console.WriteLine("{0} selected",_controller.Items[row]);
_previousRow = indexPath;
// This is what the Settings does under Settings>Mail>Show on an iPhone
tableView.DeselectRow(indexPath,false);
}

UITableView RowSelected not being called?

I have a UITableView and a subclassed UITableViewSource class:
table = new UITableView(window.Bounds);
table.Source = new CellSource();
public class CellSource : UITableViewSource
{
// etc etc
I'm trying to get the selected row, so implemented the RowSelected method in my source class:
public override void RowSelected(UITableView tableview, NSIndexPath indexpath) {
Console.WriteLine("User tapped");
}
However, tapping on the cells gives no response at all, not even when simply trying to write to the console.
I can post the complete classes if that will help?
Thanks.
EDIT
So, after I create my UITableView:
table = new UITableView(window.Bounds);
table.AutoresizingMask = UIViewAutoresizing.All;
table.SeparatorStyle = UITableViewCellSeparatorStyle.None;
table.BackgroundColor = UIColor.Clear;
table.Source = new CellSource();
I use my source class to parse an XML file, and populate a list:
List<Treasure> treasures = new List<Treasure>();
protected class Treasure {
public string cellTitle { get; set; }
public string cellTag { get; set; }
public string cellImage { get; set; }
public string audioFile { get; set; }
public string mainTitle { get; set; }
public string mainTag { get; set; }
public string mainBody { get; set; }
public string mainImage { get; set; }
public string mainCaption { get; set; }
}
public CellSource (/*string[] items*/)
{
Console.WriteLine("CellSource called");
string fileName = "treasuresiPhone.xml";
XDocument doc = XDocument.Load(fileName);
treasures = doc.Descendants("treasures").FirstOrDefault().Descendants("treasure").Select(p=> new Treasure() {
cellTitle = p.Element("celltitle").Value,
cellTag = p.Element("celltagline").Value,
cellImage = p.Element("cellimage").Value
}).ToList();
numCells = treasures.Count();
}
I then create a CGBitmapContext in another class, return the image, and set this as the cells image:
public UIImage DrawCell (string cellImage) {
string cellI = cellImage;
//create a new graphics context
int width = 320;
int height = 110;
CGBitmapContext ctx = new CGBitmapContext(IntPtr.Zero, width, height, 8, 4*width, CGColorSpace.CreateDeviceRGB(), CGImageAlphaInfo.PremultipliedFirst);
//load an image
var imagePath = (#cellI);
var image = UIImage.FromFile(imagePath).CGImage;
ctx.DrawImage(new RectangleF(0, 0, width, height), image);
UIImage returnedImage = new UIImage();
returnedImage = FromImage(ctx.ToImage());
return returnedImage;
}
I add some other test stuff like a label, and override the RowSelected method:
public override UITableViewCell GetCell (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath) {
UITableViewCell cell = tableView.DequeueReusableCell (cellIdentifier);
cell = new UITableViewCell (UITableViewCellStyle.Default, cellIdentifier);
cell.UserInteractionEnabled = false;
UILabel secondViewLabel = new UILabel();
//if there are no cells create a new one
if (cell == null) {
Console.WriteLine("cell == null");
} else {
//create a new cellobject - this grabs the image and returns a CGBitmapContext
CellObject _cellObject = new CellObject();
cell.ImageView.Image = _cellObject.DrawCell(treasures[indexPath.Row].cellImage);
//add text
secondViewLabel.Tag = 1;
secondViewLabel.Text = treasures[indexPath.Row].cellTitle;
secondViewLabel.TextColor = UIColor.White;
secondViewLabel.TextAlignment = UITextAlignment.Center;
secondViewLabel.Lines = 0;
secondViewLabel.LineBreakMode = UILineBreakMode.WordWrap;
secondViewLabel.Font = UIFont.FromName("Helvetica", 16);
secondViewLabel.BackgroundColor = UIColor.FromRGB(205, 54, 51);
//get the width of the text
SizeF labelSize = secondViewLabel.StringSize(secondViewLabel.Text, secondViewLabel.Font);
secondViewLabel.Frame = new RectangleF(0, 110 - (labelSize.Height + 10), labelSize.Width + 20, labelSize.Height + 10);
//add a second view
UIView secondView = new UIView();
secondView.AddSubview(secondViewLabel);
cell.ContentView.AddSubview(secondView);
}
return cell;
}
public override void RowSelected(UITableView tableview, NSIndexPath indexpath) {
Console.WriteLine("User tapped");
}
In Monotouch UITableViewSource is the combined UITableViewDataSource and UITableViewDelegate for a UITableView. So, when you extend such class you can take advantage of methods within both data source and delegate.
Said this, I would modify a little bit your code (see comments).
public override UITableViewCell GetCell (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell (cellIdentifier);
// if there are no cells create a new one
if (cell == null) {
cell = new UITableViewCell (UITableViewCellStyle.Default, cellIdentifier);
// why do you set the interaction to false? Setting it to false disable interaction for your cell
cell.UserInteractionEnabled = true;
// create label and view here. You customize the cell with additional elements once
}
// update image and label here (you need to grab a reference for your label for example through the tag)
return cell;
}
Take also a look to Working with Tables and Cells in Xamarin documentation.
Hope it helps.

UITableView navigation in monotouch

I'm using mono develop 3.1.1 to build an IOS application. I'm receiving an object reference error (see >>>) from my reference to the navigation controller that I've not declared properly.
My question is what's the best way to declare and instantiate the controller so I'm able to show another view from the point the table cell is selected.
Can someone help me with the correct syntax please?
public class TableHelper : UITableViewSource {
protected string[] tableItems;
protected string cellIdentifier = "TableCell";
public TableHelper (string[] items)
{
tableItems = items;
}
public override int RowsInSection (UITableView tableview, int section)
{
return tableItems.Length;
}
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
switch (tableItems[indexPath.Row])
{
case "one":
var DetailViewController = new SupportContactsDetailsScreen ();
UINavigationController controller = new UINavigationController();
// Pass the selected object to the new view controller.
>>>controller.NavigationController.PushViewController(DetailViewController, true);
break;
default:
//Console.WriteLine("Default case");
break;
}
}
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;
}
}
The way that I usually go about doing this is to keep a reference to the main UIViewController (the view controller that holds the UITableView) for that particular set of views and access that Navigation Controller through the NavigationController property. (Another technique taken by Xamarin in the code sample linked below is to pass the UINavigationController in directly.)
So I would alter your class by adding:
UIViewController parentViewController;
public TableHelper(string[] items, UIViewController vc)
{
tableItems = items;
parentViewController vc;
}
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
switch (tableItems[indexPath.Row])
{
case "one":
var DetailViewController = new SupportContactsDetailsScreen ();
UINavigationController controller = new UINavigationController();
// Pass the selected object to the new view controller.
parentViewController.NavigationController.PushViewController(DetailViewController, true);
break;
default:
//Console.WriteLine("Default case");
break;
}
}
Xamarin has a document on their docs site as well as some code on their Github that talks about this further. Another important note is what the type of view controller is (regular UIViewController, UITableViewController, etc.).

Monotouch UITableView image "flashing" while background requests are fetched

I am in need of some help from you guys. I have a Monotouch UITableView which contains some web images. I have implemented a Task to fetch them asynchronously so the UI is responsive, an even added some animations to fade them in when they are fetched from the web.
My problems start when the user scrolls down very fast down the UITableView, so since the cells are resusable, several background tasks are queued for images. When he is at the bottom of the list, he might see the thumbnail displaying an image for another cell, then another, then another, then another, as the tasks are completed and each image replaces the other one. I am in need of some sort of checking whether the currently displayed cell corresponds to the correct image url, but not sure how to do that.
Here is the code for my TableSource class.
using System;
using MonoTouch.UIKit;
using MonoTouch.Foundation;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
{
public class ListDelegate:UITableViewDelegate
{
private UINavigationController nav;
public override float GetHeightForRow (UITableView tableView, NSIndexPath indexPath)
{
return 128;
}
public override void RowSelected (UITableView tableView, NSIndexPath indexPath)
{
DealViewController c = new DealViewController(((ListDataSource)tableView.DataSource).deals[indexPath.Row].Id,nav);
nav.PushViewController(c,true);
tableView.DeselectRow(indexPath,true);
}
public ListDelegate(UINavigationController nav)
{
this.nav = nav;
}
}
public class ListDataSource:UITableViewDataSource
{
bool toggle=true;
Dictionary<string,UIImage> images = new Dictionary<string, UIImage>();
public List<MyDeal> deals = new List<MyDeal>();
Dictionary<int,ListCellViewController> controllers = new Dictionary<int, ListCellViewController>();
public ListDataSource(List<MyDeal> deals)
{
this.deals = deals;
}
public override int RowsInSection (UITableView tableview, int section)
{
return deals.Count;
}
public override UITableViewCell GetCell (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell("cell");
ListCellViewController cellController = null;
if (cell == null || !controllers.ContainsKey(cell.Tag))
{
cellController = new ListCellViewController();
NSBundle.MainBundle.LoadNib("ListCellViewController", cellController, null);
cell = cellController.Cell;
cell.Tag = Environment.TickCount;
controllers.Add(cell.Tag, cellController);
}
else
{
cellController = controllers[cell.Tag];
}
if (toggle)
{
cell.BackgroundView = new UIImageView(UIImage.FromFile("images/bg1.jpg"));
}
else
{
cell.BackgroundView = new UIImageView(UIImage.FromFile("images/bg2.jpg"));
}
toggle = !toggle;
MyDeal d = deals[indexPath.Row];
cellController.SetValues(d.Title,d.Price,d.Value,d.DiscountPercent);
GetImage(cellController.Thumbnail,d.Thumbnail);
return cell;
}
private void GetImage(UIImageView img, string url)
{
img.Alpha = 0;
if (url != string.Empty)
{
if (images.ContainsKey(url))
{
img.Image = images[url];
img.Alpha = 1;
}
else
{
var context = TaskScheduler.FromCurrentSynchronizationContext ();
Task.Factory.StartNew (() => {
NSData imageData = NSData.FromUrl(new NSUrl(url));
var uimg = UIImage.LoadFromData(imageData);
images.Add(url,uimg);
return uimg;
}).ContinueWith (t => {
InvokeOnMainThread(()=>{
img.Image = t.Result;
RefreshImage(img);
});
}, context);
}
}
}
private void RefreshImage(UIImageView img)
{
UIView.BeginAnimations("imageThumbnailTransitionIn");
UIView.SetAnimationDuration(0.5f);
img.Alpha = 1.0f;
UIView.CommitAnimations();
}
}
}
Here is the ListCellViewController, that contains a custom cell
using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
{
public partial class ListCellViewController : UIViewController
{
#region Constructors
// The IntPtr and initWithCoder constructors are required for items that need
// to be able to be created from a xib rather than from managed code
public ListCellViewController (IntPtr handle) : base(handle)
{
Initialize ();
}
[Export("initWithCoder:")]
public ListCellViewController (NSCoder coder) : base(coder)
{
Initialize ();
}
public ListCellViewController () : base("ListCellViewController", null)
{
Initialize ();
}
void Initialize ()
{
}
public UIImageView Thumbnail
{
get{return thumbnailView;}
}
public UITableViewCell Cell
{
get {return cell;}
}
public void SetValues(string title,decimal price,decimal valuex,decimal discount,int purchases)
{
}
#endregion
}
}
All help is greatly appreciated
I have found a simple (maybe not optimal) solution. The trick I used is a Queue object to store the last n valid image urls, where n the number of words displayable in the screen. Once a cell is reused, the last image url is dequeued and when it is about to be displayed I check if it's in the queue, and if it's not I reject it.

Resources