I am trying to set binding between my viewmodel and tableviewsource. But method GetOrCreateCellFor in tableviewsource is never called.
Here is my code:
ViewController:
public partial class MainView : MvxViewController
{
public MainView() : base("MainView", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var source = new TableViewDataSource(FloorTableView);
this.CreateBinding(source).To((MainViewModel vm) => vm.Floors).Apply();
FloorTableView.Source = source;
FloorTableView.ReloadData();
}
}
ViewModel:
public class MainViewModel : MvxViewModel
{
DataService DataService;
ObservableCollection<Category> _Floors = new ObservableCollection<Category>();
public ObservableCollection<Category> Floors
{
get
{
LoadFloors();
return _Floors;
}
set
{
_Floors = value; RaisePropertyChanged(() => Floors);
}
}
void LoadFloors()
{
_Floors.Add(new Category {
Name = "Test"
});
}
}
TableViewSource:
public class TableViewDataSource : MvxTableViewSource
{
private static string CellId = "FloorCell";
public TableViewDataSource(UITableView tableView) : base(tableView)
{
tableView.RegisterNibForCellReuse(FloorCell.Nib, CellId);
}
public override System.nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return 50;
}
protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
//this method never called
return tableView.DequeueReusableCell(CellId, indexPath);
}
}
FloorCell is simple empty tableview cell which extends MvxTableViewCell.
Where is the problem?
You need to implement the RowsInSection method otherwise the method won't get called.
You also need to make sure that the UITableView actually has a Frame set up either one you set yourself or through auto layout constraints. If the height or width is 0 the method won't get called either.
This might also happen if height of your table view is lower than at least on item including footer / header size. So be sure you calculate height appropriately (with one item available it will add scrolling to your table view, but GetOrCreateCell will be called).
Related
I'm trying to make an app for people of my student dancing association to find a dance partner. It's been a fun project during corona. Everything is done in ASP Core 5 and Xamarin Forms, and it's almost ready for testing. However, I'm having some trouble with the chat UI. When I use a CollectionView in Forms, it loads very slowly, due to rendering performance (at least on Android, iOS is generally much faster). To solve that I tried creating it as a view and use native renderers for iOS and Android. With Android, I simply used a NuGet package (XamarinLibrary.Xamarin.AndroidX.ChatKit), but for iOS I could find no such package. There's a native SwiftUI package (MessageKit), though I don't know how to wrap that (and the documentation is a lot). So I started to instead write it in Xamarin iOS. My test loads really quickly, although I'm now struggling with the layout of the Collection and the ViewCell. Could someone show me how to create message cells that fill the horizontal space?
Thanks in advance! :)
This is what it looks like right now:
Screenshot
[UPDATE] I partially solved the issue by adding this (though it does not work in the constructor):
Screenshot 2
public override bool ShouldInvalidateLayoutForBoundsChange(CGRect newBounds) {
MinimumLineSpacing = 10;
MinimumInteritemSpacing = 10;
SectionInset = new UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10);
ItemSize = new CGSize(width: (newBounds.Width - 20), height: 100);
return true;
}
My code currently (the data source isn't correct yet):
public class MessageListLayout : UICollectionViewFlowLayout {
public MessageListLayout() {
ItemSize = new CGSize(UIScreen.MainScreen.Bounds.Size.Width, 50);
EstimatedItemSize = AutomaticSize;
}
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);
}
}
public class MessageListDataSource : UICollectionViewDataSource {
private string CellId { get; set; }
#region Computed Properties
public UICollectionView CollectionView { get; set; }
public List<int> Numbers { get; set; } = new List<int>();
#endregion
#region Constructors
public MessageListDataSource(UICollectionView collectionView, string cellId) {
// Initialize
CollectionView = collectionView;
CellId = cellId;
// Init numbers collection
for (int n = 0; n < 100; ++n) {
Numbers.Add(n);
}
}
#endregion
#region Override Methods
public override nint NumberOfSections(UICollectionView collectionView) {
// We only have one section
return 1;
}
public override nint GetItemsCount(UICollectionView collectionView, nint section) {
// Return the number of items
return Numbers.Count;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath) {
// Get a reusable cell and set {~~it's~>its~~} title from the item
var cell = collectionView.DequeueReusableCell(CellId, indexPath) as MessageListViewCell;
cell.Text = Numbers[(int)indexPath.Item].ToString();
return cell;
}
public override bool CanMoveItem(UICollectionView collectionView, NSIndexPath indexPath) {
// We can always move items
return true;
}
public override void MoveItem(UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath) {
// Reorder our list of items
var item = Numbers[(int)sourceIndexPath.Item];
Numbers.RemoveAt((int)sourceIndexPath.Item);
Numbers.Insert((int)destinationIndexPath.Item, item);
}
#endregion
}
So this should be the base message bubble.
public class MessageListViewCell : UICollectionViewCell {
UIImageView imageView;
public string Text { get; set; }
[Export("initWithFrame:")]
public MessageListViewCell(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(1, 1);
ContentView.AutoresizingMask = UIViewAutoresizing.FlexibleWidth;
imageView = new UIImageView(UIImage.FromBundle("placeholder.png"));
imageView.Center = ContentView.Center;
imageView.Transform = CGAffineTransform.MakeScale(0.7f, 0.7f);
ContentView.AddSubview(imageView);
}
public UIImage Image {
set {
imageView.Image = value;
}
}
[Export("custom")]
public void Custom() {
// Put all your custom menu behavior code here
Console.WriteLine("custom in the cell");
}
public override bool CanPerform(Selector action, NSObject withSender) {
if (action == new Selector("custom"))
return true;
else
return false;
}
}
[assembly: ExportRenderer(typeof(MessagesListControl), typeof(MessagesListControlRenderer))]
namespace Vinder.iOS.UI.Renderers {
public class MessagesListControlRenderer : ViewRenderer<MessagesListControl, UICollectionView> {
static readonly NSString messageListViewCell = new("MessageListViewCell");
private UICollectionView collectionView { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<MessagesListControl> e) {
base.OnElementChanged(e);
if (e.OldElement != null) {
// Unsubscribe to events
}
if (e.NewElement != null) {
if (Control == null) {
collectionView = new UICollectionView(new CGRect(0, 0, UIScreen.MainScreen.Bounds.Size.Width, 300), new MessageListLayout());
collectionView.RegisterClassForCell(typeof(MessageListViewCell), messageListViewCell);
collectionView.BackgroundColor = UIColor.Blue; // blue is easy for seeing the correct bounds
collectionView.DataSource = new MessageListDataSource(collectionView, messageListViewCell);
SetNativeControl(collectionView);
}
// Subscribe to events
}
}
}
}
I need the UIMenuItem to call a method inside the UITableViewCell. Something similar is possible in swift as explained here: https://stackoverflow.com/a/31358474/202179 .
At the moment I am only able to call the method inside the UIViewController from the UIMenuItem if I add an Export attribute to the method.
There is some trick in the export of this method in the cell, but I have failed to find it till now. I would expect this code to work, but it doesn't:
[Register("CustomCell")]
public class CustomCell : UITableViewCell
{
public CustomCell(IntPtr intPtr) : base(intPtr)
{
var menuItem = new UIMenuItem("Menu Item", new ObjCRuntime.Selector("CustomCell.SeekSelectedText"));
UIMenuController.SharedMenuController.MenuItems = new UIMenuItem[] { menuItem };
UIMenuController.SharedMenuController.Update();
}
[Export("SeekSelectedText")]
void SeekSelectedText()
{
//do something
}
}
As said just quoting a different selector inside the UIViewController works, so the problem is that this selector doesn't appear to be recognized whatever I try.
Here is my test and the custom UIMenuItem works fine.
First, define a custom cell class which inherits UITableViewCell
class MyCell: UITableViewCell
{
public MyCell(UITableViewCellStyle style, string reuseIdentifier):base(style, reuseIdentifier)
{
}
[Export("custom")]
void Custom()
{
//do something
Console.WriteLine("custom pop up");
}
public override bool CanPerform(Selector action, NSObject withSender)
{
if (action == new Selector("custom"))
return true;
else
return false;
}
}
Second, set the source of your UITableView
class MyTableViewSource : UITableViewSource
{
int i = 0;
// create custom cell
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
string cellStr = "cell";
MyCell cell = (MyCell)tableView.DequeueReusableCell(cellStr);
if (cell == null)
{
cell = new MyCell(UITableViewCellStyle.Subtitle, cellStr);
cell.TextLabel.Text = $"User Name {i}";
cell.DetailTextLabel.Text = $"details here... {i++}";
}
return cell;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return 10;
}
}
Then override the realted methods(e.g., ShouldShowMenu, CanPerformAction).
class MyTableViewDelegate : UITableViewDelegate
{
public override bool ShouldShowMenu(UITableView tableView, NSIndexPath rowAtindexPath)
{
return true;
}
public override bool CanPerformAction(UITableView tableView, Selector action, NSIndexPath indexPath, NSObject sender)
{
return true;
}
public override void PerformAction(UITableView tableView, Selector action, NSIndexPath indexPath, NSObject sender)
{
}
public override bool ShouldHighlightRow(UITableView tableView, NSIndexPath rowIndexPath)
{
return true;
}
}
Last, don't foget to add custom MenuItem to your UITableViewCell.
UIMenuController.SharedMenuController.MenuItems = new UIMenuItem[] {
new UIMenuItem ("Custom", new Selector ("custom"))
};
Since UICollectionView has similar functions to UITableVIew, you can directly refer to the sample SimpleCollectionView.
I am making an app for iOS using Xamarin I had some trouble with UITableViews and found this tutorial: https://learn.microsoft.com/en-us/xamarin/ios/user-interface/controls/tables/populating-a-table-with-data
I created a new project and followed what they did, but it doesn't work for me. I must be doing something wrong, but I can't figure out what.
Could someone take a look and see why the TableView doesn't show up?
BasicViewController:
public class BasicTableViewController : UIViewController
{
private UIView view;
public override void ViewDidLoad()
{
base.ViewDidLoad();
var table = new UITableView(View.Bounds);
table.Source = new BasicTableSource("aaaaaaaaa", "bbbbb", "cccccccc", "ddddddddddddd", "eeeee");
Add(table);
}
public override void LoadView()
{
view = new UIView()
{
BackgroundColor = UIColor.White,
};
View = view;
}
}
BasicTableViewSource:
public class BasicTableSource : UITableViewSource
{
private readonly string[] items;
public BasicTableSource(params string[] items)
{
this.items = items;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell("cell");
if (cell == null)
cell = new UITableViewCell(UITableViewCellStyle.Default, "cell");
cell.TextLabel.Text = items[indexPath.Row];
return cell;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return items.Length;
}
}
AppDelegate:
[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
public override UIWindow Window { get; set; }
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
Window.RootViewController = new BasicTableViewController();
Window.MakeKeyAndVisible();
return true;
}
}
What the simulator looks like when running this project:
What the simulator looks like
Cause:
When you override the method LoadView.It seems that you forgot to set the Frame of view.So the size of view will with Height as 0 and Width as 0.
Solution:
Set the frame of view as the size of screen.
public override void LoadView()
{
view = new UIView()
{
Frame = UIScreen.MainScreen.Bounds,
BackgroundColor = UIColor.White,
};
View = view;
}
I have two UITableView in the same UIViewController.
tblView1 & tblView2.
Both TableViews are bringing data dynamically.
Now my problem is: I want to load data in tblView2 on the basis of SelectedRow from tblView1.
For Example, when I select a company name from tblView1, then tblView2 needs to populate the Cars made by that company.
I have rowIndex of my first table (tblView1), but i can't bind my tblView2 from the RowSelected Method of tblView1.
I need help in binding my tblView2. How can i do this?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using CoreGraphics;
using Foundation;
using UIKit;
namespace Tables
{
public partial class ViewController : UIViewController
{
protected ViewController(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public ViewController()
{
//tblView2.Source = new CompanyModelsSource();
// Note: this .ctor should not contain any initialization logic.
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
if (tblView1 != null)
{
tblView1.Source = new CarModelsSource();
}
// Perform any additional setup after loading the view, typically from a nib.
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
}
class CompanyModelsSource : UITableViewSource
{
private List<Companies> _Companies;
NSString _cellID = new NSString("TableCell");
public CompanyModelsSource()
{
_Companies = CompanyTableSource.AllCompany();
}
public override nint RowsInSection(UITableView tableview, nint section)
{
// tell the TableView how many rows to create
return _Companies.Count;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
//I can't access my tblView2 in this method. So where i can bind my other UITableView?
UILabel lblTotalFacilities = new UILabel();
lblTotalFacilities.Layer.BackgroundColor = UIColor.Blue.CGColor;
lblTotalFacilities.Frame = new CGRect(305, 200, 1024, 400);
ViewController vc = new ViewController();
vc.View.AddSubview(lblTotalFacilities);
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
Company currentCompany = _Companies[indexPath.Row];
var cell = tableView.DequeueReusableCell(_cellID) as CompanyTableCell;
if (cell == null) { cell = new CompanyTableCell(_cellID); }
cell.UpdateCellControlsWithAuditData(currentCompany.Name);
return cell;
}
}
public class CompanyTableSource
{
public CompanyTableSource()
{
}
public static List<Companies> AllCompany()
{
CompanyCollection CC = null;
string path1 = path + "Companies.xml";
XmlSerializer serializer = new XmlSerializer(typeof(CompanyCollection));
StreamReader reader = new StreamReader(path1);
CC = (CompanyCollection)serializer.Deserialize(reader);
reader.Close();
List<Companies> comp = CC.CompanyCollection.Cast<Companies>().ToList();
return comp;
}
}
public class CompanyTableCell : UITableViewCell
{
UILabel nameLabel;
public CompanyTableCell(NSString cellId) : base(UITableViewCellStyle.Default, cellId)
{
SelectionStyle = UITableViewCellSelectionStyle.Gray;
ContentView.BackgroundColor = UIColor.FromRGB(54, 194, 36);
var newFont = UIFont.SystemFontOfSize(12);
nameLabel = new UILabel();
nameLabel.Font = newFont;
ContentView.AddSubviews(new UIView[] { nameLabel });
}
public void UpdateCellControlsWithAuditData(string Name)
{
nameLabel.Text = Name;
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
nameLabel.Frame = new CGRect(10, 0, 350, 33);
}
}
}
Hope this helps. Created everything in a single file for simplicity.
public partial class ViewController : UIViewController
{
public ViewController() : base("ViewController", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
var companySource = new CompanyTableSource();
tblView1.Source = companySource;
companySource.ItemSelected += OnCompanyItemSelected;
}
//this will handle the ItemSelected event
//here you will retrieve the data for the second UITableView
void OnCompanyItemSelected(object sender, Company e)
{
//you can do it this way
tblView2.Source = new CarTableSource(e.Id);
List<Car> cars = GetCarsByCompanyId(e.Id);
//or this whay
tblView2.Source = new CarTableSource(cars);
}
List<Car> GetCarsByCompanyId(int id)
{
//Your logic to get the data goes here
throw new NotImplementedException();
}
}
public class CompanyTableSource : UITableViewSource
{
// You can use the whole object or just an integer (companyId)
public event EventHandler<Company> ItemSelected;
public IList<Company> Items { get; private set; } = new List<Company>();
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
var item = Items[indexPath.Row];
ItemSelected?.Invoke(this, item);
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
//Do your implementation
throw new NotImplementedException();
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return Items.Count;
}
}
public class CarTableSource : UITableViewSource
{
public IList<Car> Items { get; private set; } = new List<Car>();
public CarTableSource(int companyId)
{
//Get your data based on the companyId
}
//Or pass in the data already retreived. (I preffer this method)
public CarTableSource(IList<Car> cars)
{
Items = cars;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
//Do your implementation
throw new NotImplementedException();
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return Items.Count;
}
}
I have a TableView with a custom UITableViewCell. In each cell I have multiple buttons, when any of the buttons is click after scrolling down and up it calls itself the many times I scroll down and up.
I have read and research for a solution and I haven't found a solution.
I know the problem is the cell are being reuse so that's why the buttons are being call multiple times but I can't find a way to prevent it.
I added console write line statements through the code and the else part in MoveToWindow is never call. Could that be the reason why?
Research material for solution:
my code is calling twice the btndelete method in uitableview
UIButton click event getting called multiple times inside custom UITableViewCell
My Code:
namespace Class.iOS
{
public partial class CustomCell : UITableViewCell
{
public static readonly NSString Key = new NSString ("CustomCell");
public static readonly UINib Nib;
public int Row { get; set; }
public event EventHandler LikeButtonPressed;
private void OnLikeButtonPressed()
{
if (LikeButtonPressed != null)
{
LikeButtonPressed(this, EventArgs.Empty);
}
}
public override void MovedToWindow()
{
if (Window != null)
{
btnAdd.TouchUpInside += HandleLikeButtonPressed;
}
else
{
btnAdd.TouchUpInside -= HandleLikeButtonPressed;
}
}
private void HandleLikeButtonPressed(object sender, EventArgs e)
{
OnLikeButtonPressed();
}
static CustomCell ()
{
Nib = UINib.FromName ("CustomCell", NSBundle.MainBundle);
}
public CustomCell ()
{
}
public CustomCell (IntPtr handle) : base (handle)
{
}
public void UpdateCell (string Name, int number)
{
// some code
}
public class TableSource : UITableViewSource
{
public override nint RowsInSection (UITableView tableview, nint section)
{
return 8;
}
private void HandleLikeButtonPressed(object sender, EventArgs e)
{
var cell = (CustomCell)sender;
var row = cell.Row;
switch (row)
{
case 0:
cell.label.Text = ""
break;
case 1:
cell.label.Text = ""
break;
}
}
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (CustomCell.Key) as CustomCell;
cell.Row = indexPath.Row;
if (cell == null)
{
cell = new CustomCell ();
var views = NSBundle.MainBundle.LoadNib("CustomCell", cell, null);
cell.LikeButtonPressed += HandleLikeButtonPressed;
cell = Runtime.GetNSObject( views.ValueAt(0) ) as CustomCell;
}
cell.UpdateCell
(
// some code
);
return cell;
}
}
}
}
The cells are reused in iOS, so you need to make sure you properly unhook handlers and reset state when a cell is reused. You can do something like this:
public partial class CustomCell : UITableViewCell {
EventHandler _likeButtonHandler;
public static readonly NSString Key = new NSString (typeof(CustomCell).Name);
public static readonly UINib Nib = UINib.FromName (typeof(CustomCell).Name, NSBundle.MainBundle);
public CustomCell ()
{
}
public CustomCell (IntPtr handle) : base (handle)
{
}
public override void PrepareForReuse ()
{
LikeButton.TouchUpInside -= _likeButtonHandler;
_likeButtonHandler = null;
base.PrepareForReuse ();
}
public void SetupCell (int row, string Name, EventHandler likeBtnHandler)
{
_likeButtonHandler = likeBtnHandler;
LikeButton.TouchUpInside += _likeButtonHandler;
LikeButton.Tag = row;
NameLabel.Text = Name;
RowLabel.Text = row.ToString ();
}
Notice that I unhook the event handler in the PrepareForReuse override. This is the correct place to do clean up and reset a cell for reuse. You should NOT be using MovedToWindow().
Then your GetCell method will look like this:
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell (CustomCell.Key) as CustomCell ?? new CustomCell ();
cell.SetupCell (indexPath.Row, _fruits [indexPath.Row], _likeButtonHandler);
return cell;
}
The _likeButtonHandler is a simple EventHandler.