Customising tableviewcell in Xamarin ios with MVVMCross - uitableview

I am using MVVMCross in my Xamarin application. I am able to display some data in a tableview, Now I need to customise the tableview cell to add accessory and allow user to go to next screen on selection of a row. How can I achieve it?
My code to display the tableview:
public partial class SchoolSelectionView : MvxViewController<SchoolSelectionViewModel>
{
public SchoolSelectionView() : base("SchoolSelectionView", null)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.NavigationController.SetNavigationBarHidden(true,true);
var source = new MvxStandardTableViewSource(tblSchoolSelection, "TitleText Name");
this.CreateBinding(source).To<SchoolSelectionViewModel>(vm => vm.Items).Apply();
this.CreateBinding(source).For(s => s.SelectionChangedCommand).To<SchoolSelectionViewModel>(vm => vm.ItemSelectedCommand).Apply();
tblSchoolSelection.Source = source;
//tblSchoolSelection.Source = new SchoolListTableSource(this.ViewModel.Items, this);
tblSchoolSelection.ReloadData();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
}

ustomise the tableview cell to add accessory
Create a subclass inherit from MvxTableViewSource and override the GetOrCreateCellFor method.
var source = new MyTableViewSource(tblSchoolSelection, "TitleText Name");
public class MyTableViewSource: MvxStandardTableViewSource {
override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
var cell= tableView.DequeueReusableCell(CellIdentifier);
if (cell== null)
{
//xxxx
}
return cell;
}
}
Refer to Custom MvxTableViewCell Without a NIB File
allow user to go to next screen on selection of a row
Just add ShowViewModel in ItemSelectedCommand in viewModel.
private IMvxCommand _itemSelectedCommand;
public IMvxCommand ItemSelectedCommand
{
get
{
_itemSelectedCommand= _itemSelectedCommand?? new MvxCommand(() => ShowViewModel<NextScreenModel>());
return _itemSelectedCommand;
}
}

Related

Two Button click event in single cell in TableView in iOS(Xamarin)

I am making custom cell in xamarin iOS. In the cell I have two Button which is display in the below figure.
Figure :
Two Button are :
Create Appointment
View Detail
I want to create this two different Button click Event in My Source class so that I can send data to different ViewController for my purpose.
Code :
TableCell class :
public partial class CaseHistoryTableCell : UITableViewCell
{
public static readonly NSString Key = new NSString("CaseHistoryTableCell");
public static readonly UINib Nib;
static CaseHistoryTableCell()
{
Nib = UINib.FromName("CaseHistoryTableCell", NSBundle.MainBundle);
}
public CaseHistoryTableCell(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public static CaseHistoryTableCell Create()
{
return (CaseHistoryTableCell)Nib.Instantiate(null, null)[0];
}
public void BindData(string hospitalLabel, string addressLabel, string drLabel, string patientLabel)
{
this.lbl_hospitalName.Text = hospitalLabel;
this.lbl_address.Text = addressLabel;
this.lbl_drName.Text = drLabel;
this.lbl_patientName.Text = patientLabel;
this.lbl_address.TextColor = UIColor.Clear.FromHexString("#000000", 0.54f);
this.lbl_patientName.TextColor = UIColor.Clear.FromHexString("#000000", 0.54f);
this.lbl_caseDate.TextColor = UIColor.Clear.FromHexString("#000000", 0.54f);
this.lbl_scheDate.TextColor = UIColor.Clear.FromHexString("#000000", 0.54f);
this.lbl_hospitalName.TextColor = UIColor.Clear.FromHexString("#000000", 0.87f);
this.lbl_drName.TextColor = UIColor.Clear.FromHexString("#000000", 0.87f);
this.btn_createAppointment.SetTitleColor(UIColor.Clear.FromHexString("#0072BA", 1.0f), UIControlState.Normal);
this.btn_viewDetail.SetTitleColor(UIColor.Clear.FromHexString("#0072BA", 1.0f), UIControlState.Normal);
}
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
value.Y += 4;
value.Height -= 2 * 4;
base.Frame = value;
}
}
}
Source Class :
public class CaseHistorySourceClass : UITableViewSource
{
private List<CaseSearchItem> caseSearchItems;
public CaseSearchItem caseSearchItem;
public static event EventHandler RowClicked;
public CaseHistorySourceClass(List<CaseSearchItem> caseSearchItems)
{
this.caseSearchItems = caseSearchItems;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
CaseHistoryTableCell cell = tableView.DequeueReusableCell(CaseHistoryTableCell.Key) as CaseHistoryTableCell ?? CaseHistoryTableCell.Create();
var item = caseSearchItems[indexPath.Row];
cell.BindData(item.Organization, item.Address, item.Doctor, item.UserName);
cell.Layer.MasksToBounds = false;
cell.Layer.CornerRadius = 10.0f;
cell.BackgroundColor = UIColor.White;
cell.SetNeedsLayout();
cell.LayoutIfNeeded();
return cell;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return caseSearchItems.Count;
}
}
My Question :
It is possible to Create two different Button click event in a single Cell.
If yes then How ?
and If No then what is alternative to Perform this type of operation.
Note : I doesn't want to require RowSelected. I only require how to
perform this two different Button click Event.
Don't do such operation on RowSelected
Setting Target with selector will help you out .
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
CaseHistoryTableCell cell = tableView.DequeueReusableCell(CaseHistoryTableCell.Key) as CaseHistoryTableCell ?? CaseHistoryTableCell.Create();
var item = caseSearchItems[indexPath.Row];
// setTag to button to identify in which row button is pressed
cell.btnCreateAppointment.tag=indexPath.Row;
cell.btnViewDetail.tag=indexPath.row;
// set Target to a method
cell.btnCreateAppointment.TouchUpInside += createAppointment;
ell.btnViewDetail.TouchUpInside +=viewDetail;
}
These Method will be called when Press you button
public void createAppointment(object sender, EventArgs e)
{
var row=sender.tag;
}
Second button Clicked event
public void viewDetail(object sender, EventArgs e)
{
var row=sender.tag;
}
i hope this work.
You can do this by adding target action to button from your GetCell method:
first add following in your cell class to make buttons accessible:
public UIButton btnCreateAppointment {
get
{
return this.btn_createAppointment;
}
}
public UIButton btnViewDetail
{
get
{
return this.btn_viewDetail;
}
}
Now from modify your GetCell method to add action target
cell.btnCreateAppointment.tag = indexPath.Row;
cell.btnViewDetail.tag = indexPath.row;
//assign action
cell.btnCreateAppointment.TouchUpInside += (sender, e) =>
{
var row = ((UIButton)sender).Tag;
var item = caseSearchItems[row];
};
cell.btnViewDetail.TouchUpInside += (sender, e) =>
{
var row = ((UIButton)sender).Tag;
var item = caseSearchItems[row];
};

Xamarin: UICollection Image reorder Issue

I am using UICollectionView to store images and I can reorder them by overriding CanMove And MoveItem.
But the items inside the UICollection only reorder when cell size is large like if cell size is around 106 height and width, then they can be reordered if they are smaller in size, they are cannot be reordered.
View:
public override void ViewDidLoad()
{
base.ViewDidLoad();
//ImageCv is the name of UiCollectionView
var collectionLayout = new PostImageFlowLayout(3, 0.85f);
var allCollectionSource = new PostImageColectionSource(ImageCv, (ViewModel as NewPostDetailViewModel));
ImageCv.RegisterNibForCell(PostImageCell.Nib, PostImageCell.Key);
ImageCv.RegisterClassForSupplementaryView(typeof(CollectionHeader), UICollectionElementKindSection.Header, new NSString("headerId"));
ImageCv.BackgroundColor = UIColor.Clear;
ImageCv.Hidden = false;
ImageCv.DataSource = allCollectionSource;
ImageCv.Delegate = collectionLayout;
var longPressGesture = new UILongPressGestureRecognizer(gesture =>
{
// Take action based on state
switch (gesture.State)
{
case UIGestureRecognizerState.Began:
var selectedIndexPath = ImageCv.IndexPathForItemAtPoint(gesture.LocationInView(View));
if (selectedIndexPath != null)
ImageCv.BeginInteractiveMovementForItem(selectedIndexPath);
Debug.WriteLine("Gesture Recognition: Activated");
break;
case UIGestureRecognizerState.Changed:
ImageCv.UpdateInteractiveMovement(gesture.LocationInView(View));
Debug.WriteLine("Gesture activated: Item location is changed");
break;
case UIGestureRecognizerState.Ended:
ImageCv.EndInteractiveMovement();
Debug.WriteLine("Gesture activation: complete");
break;
default:
ImageCv.CancelInteractiveMovement();
Debug.WriteLine("Gesture activation: Terminate");
break;
}
});
// Add the custom recognizer to the collection view
ImageCv.AddGestureRecognizer(longPressGesture);
}
UICollectionViewDelegateFlowLayout
using System;
using System.Windows.Input;
using CoreGraphics;
using UIKit;
namespace Sources.CollectionSources
{
public class PostImageFlowLayout : UICollectionViewDelegateFlowLayout
{
private float headerHeight;
private int noOfItems;
private bool isLoading;
public PostImageFlowLayout(int noOfItems, float headerHeight = 0f)
{
this.noOfItems = noOfItems;
this.headerHeight = headerHeight;
}
public override CGSize GetSizeForItem(UICollectionView collectionView, UICollectionViewLayout layout, Foundation.NSIndexPath indexPath)
{
return GetPostCellSize();
}
public override CGSize GetReferenceSizeForHeader(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
return new CGSize(collectionView.Frame.Width, headerHeight);
}
public override UIEdgeInsets GetInsetForSection(UICollectionView collectionView, UICollectionViewLayout layout, nint section)
{
return new UIEdgeInsets(0, 0, 0, 0);
}
private CGSize GetPostCellSize()
{
var relativeWidth = (UIScreen.MainScreen.Bounds.Width - 2) / this.noOfItems;
return new CGSize(relativeWidth, relativeWidth);
//return new CGSize(55, 55);
}
}
}
Source
public class PostImageColectionSource : MvxCollectionViewSource
{
private NewPostDetailViewModel newPostDetailViewModel;
private string type;
static NSString animalCellId = new NSString("PostImageCell");
static NSString headerId = new NSString("Header");
List<IAnimal> animals;
protected override NSString DefaultCellIdentifier
{
get
{
return PostImageCell.Key;
}
}
public override System.Collections.IEnumerable ItemsSource
{
get
{
return base.ItemsSource;
}
set
{
base.ItemsSource = value;
CollectionView.ReloadData();
}
}
public PostImageColectionSource(UICollectionView collectionView, NewPostDetailViewModel newPostDetailViewModel) : base(collectionView)
{
this.newPostDetailViewModel = newPostDetailViewModel;
animals = new List<IAnimal>();
for (int i = 0; i < 20; i++)
{
animals.Add(new Monkey(i));
}
}
public override nint NumberOfSections(UICollectionView collectionView)
{
return 1;
}
public override nint GetItemsCount(UICollectionView collectionView, nint section)
{
return 5;// animals.Count;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = (PostImageCell)collectionView.DequeueReusableCell(animalCellId, indexPath);
var animal = animals[indexPath.Row];
cell.Result(indexPath.Row);
return cell;
}
public override bool CanMoveItem(UICollectionView collectionView, NSIndexPath indexPath)
{
Debug.WriteLine("Ready to move images");
//System.Diagnostics.Debug.WriteLine("Checking if it can move the item");
return true;
}
public override void MoveItem(UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath)
{
//base.MoveItem(collectionView, sourceIndexPath, destinationIndexPath);
Debug.WriteLine("Start moving images to reorder");
var item = animals[(int)sourceIndexPath.Item];
animals.RemoveAt((int)sourceIndexPath.Item);
animals.Insert((int)destinationIndexPath.Item, item);
}
}
When the GetPostCellSize in PostImageFlowLayout has width and height of around 100, the CanMove and MoveItem in PostImageColectionSource are being called and items are being reordered. But if the GetPostCellSize has width and height of around 50 or 70, even though the gestures are activated, CanMove and MoveItem in PostImageColectionSource are not being called hence cannot be moved.
Can anyone hope me with reordering the images in UICollectionView when the cell size is small like around width and height of 70.
Thank you.
I am tagging swift and objective-C as this issue is related to IOS in general and not xamarin specific
Main issue here is that you need to pass in the collection view to the gesture.LocationInView(View) call instead of the main View. In ViewDidLoad in the UILongPressGestureRecognizer change:
var selectedIndexPath = ImageCv.IndexPathForItemAtPoint(gesture.LocationInView(View));
and
ImageCv.UpdateInteractiveMovement(gesture.LocationInView(View));
to
var selectedIndexPath = ImageCv.IndexPathForItemAtPoint(gesture.LocationInView(ImageCv)); // <-- pass in ImageCV instead of View. (where ImageCV is the collection view)
and
ImageCv.UpdateInteractiveMovement(gesture.LocationInView(ImageCv)); // <-- pass in ImageCV instead of View.
Another thing to note, but not a huge deal, is that PostImageColectionSource is ultimately derived from UICollectionViewSource, which is a combo of UICollectionViewDelegate and UICollectionViewDataSource in one class, but is being assigned to the DataSource property of the collection view. All this means is that though you can implement methods for UICollectionViewDelegate in PostImageColectionSource the delegate methods will not be called on that class since the Delegate property of the collection view is set to the PostImageFlowLayout, which derives ultimately from UICollectionViewDelegate via UICollectionViewDelegateFlowLayout.

GetViewForSupplementaryElement not getting called when trying to show heading in UICollectionView

I am trying to setup a UICollectionView within my existing UIViewController. Everything is working except for getting a title to show for each section - I can't figure out what I'm doing wrong.
My code in the UIViewController to initiate the collection view:
public partial class ViewController : UIViewController
{
//...
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
CollectionView_Outlet.RegisterClassForCell(typeof(ModifierCell), ModifierCell.CellID);
CollectionView_Outlet.RegisterClassForSupplementaryView (typeof(Header), UICollectionElementKindSection.Header, Header.HeaderId);
CollectionView_Outlet.ShowsHorizontalScrollIndicator = false;
CollectionView_Outlet.Source = new ModifiersSource(this);
CollectionView_Outlet.BackgroundColor = UIColor.White;
CollectionView_Outlet.ReloadData();
}
//...
}
Then I have created a subclass of UICollectionViewSource:
public class ModifiersSource : UICollectionViewSource
{
ViewController senderVC;
public ModifiersSource(ViewController sender)
{
senderVC = sender;
}
public override nint NumberOfSections(UICollectionView collectionView)
{
return 2;
}
public override nint GetItemsCount (UICollectionView collectionView, nint section)
{
return senderVC.modifiers.Count;
}
public override UICollectionViewCell GetCell(UICollectionView collectionView, NSIndexPath indexPath)
{
//...
}
public override UICollectionReusableView GetViewForSupplementaryElement(UICollectionView collectionView, NSString elementKind, NSIndexPath indexPath)
{
var headerView = (Header)collectionView.DequeueReusableSupplementaryView (elementKind, Header.HeaderId, indexPath);
headerView.Text = "Supplementary View";
return headerView;
}
}
And finally created:
public class Header : UICollectionReusableView
{
public static NSString HeaderId = new NSString("UserSource1");
UILabel label;
public string Text {
get {
return label.Text;
}
set {
label.Text = value;
SetNeedsDisplay ();
}
}
[Export ("initWithFrame:")]
public Header (RectangleF frame) : base (frame)
{
label = new UILabel (){
Frame = new RectangleF(0,0,300,50),
BackgroundColor = UIColor.Red};
AddSubview (label);
BackgroundColor = UIColor.White;
}
}
I've put a breakpoint on the GetViewForSupplementaryElement method but it never gets called. I've also set the following in my StoryBoard:
What am I missing?!
After many attempts of not being able to get the above to work, I manually set UICollectionViewFlowLayout whilst initiating the UIContainerView. Seems to have done the trick, but not sure why it didn't pick up the settings from my StoryBoard. Here is my working code:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
CollectionView_Outlet.RegisterClassForCell(typeof(ModifierCell), ModifierCell.CellID);
CollectionView_Outlet.RegisterClassForCell(typeof(ItemOptionCell), ItemOptionCell.CellID);
CollectionView_Outlet.RegisterClassForSupplementaryView (typeof(Header), UICollectionElementKindSection.Header, Header.HeaderId);
CollectionView_Outlet.ShowsHorizontalScrollIndicator = false;
//This is the new bit I added:
var layout = new UICollectionViewFlowLayout ();
layout.HeaderReferenceSize = new CGSize (300, 40);
CollectionView_Outlet.SetCollectionViewLayout (layout, false);
CollectionView_Outlet.Source = new ModifiersSource(this);
CollectionView_Outlet.ReloadData();
}
To get this to work in Xamarin, I had to enable Section Header under accessories (and it crashed in Xamarin, so from XCode). I had to do this even though I'm loading my header from a separate nib/xib into my collection view (but it will also show a reusable cell on the collection view that I don't think I need). Very specific but hopefully this saves someone in a similar situation some time!

How can I trigger a segue when clicking a custom cell in a UICollectionView

I am trying to perform a segue (with added properties) to a detail view controller when a custom cell in my UICollectionView is highlighted. I am a little stuck on how to achieve this as I cannot use PerformSegue from the subclassed UICollectionViewSource, and I cannot seem to get the selected cell from the UICollectionView.
Here's an abridged version of what I have so far:
Collection Source:
public class ProductCollectionDataSource : UICollectionViewSource
{
public ProductCollectionDataSource()
{
Products = new List<FeedItem>();
}
public List<FeedItem> Products { get; set; }
public override void ItemUnhighlighted(UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = (MultiColumnCell)collectionView.CellForItem(indexPath);
cell.Alpha = 1.0f;
// Perform segue here, passing this cell's data...?
}
}
UIViewController:
public partial class DashboardViewController : UIViewController
{
private ProductCollectionDataSource _dataSource;
public override void ViewDidLoad()
{
_dataSource = new ProductCollectionDataSource();
CollectionView.Source = _dataSource;
GetProducts();
}
private async void GetProducts()
{
_dataSource.Products = new List<FeedItem>(await API.FeedService.Get());
CollectionView.ReloadData();
}
}
So, how can I trigger the segue in the UIViewController, based on the selected cell in the UICollectionView?
You could pass in a reference to your controller and then use that to do the Segue:
public class ProductCollectionDataSource : UICollectionViewSource
{
WeakReference<DashboardViewController> _dvc;
public List<FeedItem> Products { get; set; }
public ProductCollectionDataSource(DashboardViewController parentVc)
{
Products = new List<FeedItem>();
_dvcRef = new WeakReference<DashboardViewController>(parentVc);
}
public override void ItemUnhighlighted(UICollectionView collectionView, NSIndexPath indexPath)
{
var cell = (MultiColumnCell)collectionView.CellForItem(indexPath);
cell.Alpha = 1.0f;
if (_dvcRef.TryGetTarget(out DashboardViewController dashboardVc){
dashboardVc.PerformSegue("Identifier");
}
}
}
}
Set your segue in storyboard (drag & drop)
Open the right panel to add a segue identifier (basically a string like "CustomSegue")
Use the collectionView delegate (didSelectItemAtIndexPath:) to trigger the user tap on a cell
call [self performSegueWithIdentifier:#"CustomSegue" owner:self]
Implement the method prepareForSegue in the controller that manages the UICollectionVIew
Check if [segue.identifier isEqualToString:#"CustomSegue"]
If so, then get the segue.destinationViewController (which is supposed to be your DetailViewController
Pass any property you wish (segue.destinationViewController.property = propertyIWantToPass)
Manually Segue from your ViewController to the Detail View Controller.
SetUp the Segue in the -(void)prepareForSegue method as usual.
Then call it manually where- and whenever you see fit:
-(void)yourCall{
[self performSegueWithIdentifier:#"yourSegueIdentifier" sender:self];
}

Selecting all text in a UITextView on focus only after UIAlertView

I have a tableView that contains a few UITextView controls. When the user taps on one of these the text inside should be selected so that any keyboard input immediately replaces the original content.
I cannot get the text inside a UITextView selected using this code:
txtQuantity.SelectAll (new NSObject(NSObjectFlag.Empty));
as this code only shows the menu "Select | Select All' without the text being actually selected.
Has someone gotten this to work?
EDIT:
The code below select the text inside the txtQuantity control, BUT ONLY IF the UIAlert is show first! Why is this?
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
txtQuantity.TouchDown += txtQuantityHandleTouchDown;
txtQuantity.EditingDidBegin += delegate {
txtQuantity.ExclusiveTouch=true;
UIAlertView uv = new UIAlertView("","OK",null,"OK",null);
uv.Show ();
};
}
void txtQuantityHandleTouchDown (object sender, EventArgs e)
{
txtQuantity.SelectAll (this);
txtQuantity.Selected = true;
}
If all code within the txtQuality.EditingBegin delegate is commented out, the HandleTouchDown event does not fire.
I am not sure that this is what you are going for but I put together a quick sample.
The problem I was having is with calling SelectAll in EditingDidBegin. I had to make a call to BeginInvokeOnMainThread to get the select to work. I am not sure if it is a problem with the event not happening on the main thread or you simply need to make an async call on the main thread.
using System;
using System.Collections.Generic;
using System.Linq;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace SelectText
{
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
// class-level declarations
UIWindow window;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
// create a new window instance based on the screen size
window = new UIWindow (UIScreen.MainScreen.Bounds);
window.RootViewController = new MyTableViewController ();
// make the window visible
window.MakeKeyAndVisible ();
return true;
}
}
public class MyTableViewController : UITableViewController
{
public override void LoadView ()
{
base.LoadView ();
this.TableView.DataSource = new TableViewDataSource ();
}
private class TableViewDataSource : UITableViewDataSource
{
private class EditCell : UITableViewCell
{
UITextField _field;
public EditCell () : base (UITableViewCellStyle.Default, "mycell")
{
_field = new UITextField (this.Bounds);
_field.AutoresizingMask = UIViewAutoresizing.All;
_field.BackgroundColor = UIColor.Clear;
_field.ShouldReturn = delegate {
_field.ResignFirstResponder ();
return true;
};
_field.EditingDidBegin += delegate {
this.BeginInvokeOnMainThread ( delegate {
_field.SelectAll (this);
});
};
_field.Text = "Some Text";
this.Add (_field);
}
public override void LayoutSubviews ()
{
base.LayoutSubviews ();
_field.Frame = this.Bounds;
}
}
#region implemented abstract members of UITableViewDataSource
public override int RowsInSection (UITableView tableView, int section)
{
return 2;
}
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
UITableViewCell cell = tableView.DequeueReusableCell ("mycell");
if (cell == null)
{
cell = new EditCell ();
}
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
return cell;
}
#endregion
}
}
}

Resources