Xamarin UITableView RowSelection - iphone

I have a simple UITableView and I am loading the data by implementing UITableViewSource. I also have a label on the same page. I would like to display the selected row from tableview on label.
This is my MainViewController:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
List<string> lstTableSource = new List<string> ();
for (int intCounter=0; intCounter<5; intCounter++)
{
lstTableSource.Add ("Row "+intCounter);
}
tblTest.Source = new TableSource (lstTableSource.ToArray());
}
public void Test(string strSelected)
{
lblTest.Text = strSelected;
}
This is my row selection method in TableSource.cs
public override void RowSelected (UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath)
{
MainViewControllerObj.Test (tableItems[indexPath.Row]);
}
When i tap on the row system is throwing NullReferenceException on
lblTest.Text = strSelected;
Program works fine if I print the value of the variable strSelected on a Console.

Related

How to call a method AFTER a view has rendered in MAUI - OnAppearing fires too soon

I have some animations that I want to trigger once the view has loaded.
Some of them rely on position values of other views on the page but at the time that OnAppearing fires, the X and Y values for these controls have not been set.
Others can just run by themselves but because they start in OnAppearing, the first few frames aren't rendered.
Adding a Task.Delay into the start of the methods solves the problem but is obviously not great.
Is there any way to create such a method or maybe a way to do it with behaviours? They need to trigger automatically, not in response to some control event like TextChanged etc.
Thanks!
You can do it from the native side, In Ios, you can override the ViewDidLoad method in custom renderer like:
public class MyPageRenderer : PageRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
//call before ViewWillAppear and only called once
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
}
}
and android, override the OnAttachedToWindow method:
public class MyPageRenderer : PageRenderer
{
public MyPageRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
}
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
}
}
`VisualElement` is the base class for most `Microsoft.Maui.Control`s:
public partial class NameOfThePage : ContentPage
{
public NameOfThePage()
{
InitializeComponent();
this.Loaded += NameOfThePage_Loaded;
}
private void NameOfThePage_Loaded(object sender, EventArgs e)
{
/* animations you want to trigger */
}
}
Unfortunately no OnLoad() to override.

Modifying a class to display specific output

I am new to Swift and am having a bit of trouble with this bit in particular. Attached is some code I need to run as part of a project. Details are at the end.
class Screen:DataSource {
var items:[String]=[]
func run(){
let lv = TableView()
items = ["John","Paul","George","Ringo"]
let ds = self
lv.dataSource=ds
lv.drawList()
}
}
class TableView {
// This class displays a list given a DataSource
var dataSource:DataSource?
init (){
self.dataSource=nil
}
func drawList(){
for(var i=0;i<dataSource!.getSize();i++) {
print("\(dataSource!.getItem(at:i))")
print("--------------------------")
}
}
}
protocol DataSource {
func getSize()->Int
func getItem(at pos:Int)->String
}
let screen = Screen()
screen.run()
Without changing the "run" function/method, I need to have it print this:
John
--------------------------
Paul
--------------------------
George
--------------------------
Ringo
--------------------------
I'm not sure what to modify in my Screen class. This is what I have so far:
class Screen: DataSource {
var items: [String]=[]   
func run() {       
let lv = TableView()       
items = ["John","Paul","George","Ringo"]       
let ds = self       
lv.dataSource=ds       
lv.drawList()   
}
//NEED HELP HERE
}
Any tips would be appreciated.
Use an extension to make Screen conform to DataSource:
class Screen {
var items:[String]=[]
func run(){
let lv = TableView()
items = ["John","Paul","George","Ringo"]
let ds = self
lv.dataSource=ds
lv.drawList()
}
}
extension Screen: DataSource {
func getSize() -> Int { return items.count }
func getItem(at index:Int) -> String { return items[index] }
}
You could also put the DataSource conformance and methods on the class itself, but doing it in an extension is a common style for organizing your class's members.

*** Assertion failure in -[Scrapboom.iPhone.NewsFeedTableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-2903.2/UITableView.m:1076

I am using below code in UITablViewSource
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
UIImage scaledImage=null;
string cellIdentifier = "NewsFeedCell";
var newsFeedCellItem = NewsFeedCellItemList [indexPath.Row];
var newsFeedCell = new NewsFeedCell (NewsFeedScreenInstance, newsFeedCellItem, cellIdentifier, indexPath);
if (newsFeedCell != null) {
if (!String.IsNullOrWhiteSpace (newsFeedCellItem.FeedItem.Picture.PreviewUrl)) {
var image = ImageStore.Get (newsFeedCellItem.FeedItem.Picture.PreviewUrl);
if(image != null)
{
newsFeedCellItem.FeedItem.Picture.Image = image;
scaledImage = ImageHelper.Scale(image, new SizeF (528, 528));
}
if (scaledImage != null) {
newsFeedCell.ScrapImage = scaledImage;
} else {
BeginDownloadImage (tableView, indexPath);
}
}
}
return newsFeedCell;
}
#endregion
#region PRIVATE METHODS
private void BeginDownloadImage (UITableView tableView, NSIndexPath indexPath)
{
Action successAction = () => {
this.BeginInvokeOnMainThread (() => {
tableView.BeginUpdates ();
tableView.ReloadRows (new NSIndexPath[] { indexPath }, UITableViewRowAnimation.Fade);
tableView.EndUpdates ();
});
};
ImageStore.BeginDownloadImage(NewsFeedCellItemList [indexPath.Row].FeedItem.Picture.PreviewUrl, successAction);
}
#endregion
*Description:*But below part of code is giving exception as
* Assertion failure in -[Scrapboom.iPhone.NewsFeedTableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-2903.2/UITableView.m:1076 and application is
get hanged or sometimes crashing.
tableView.ReloadRows (new NSIndexPath[] { indexPath }, UITableViewRowAnimation.Fade);
Your GetCell implementation looks wrong. You should try to Dequeue a cell, then create one if it fails (not even required in iOS6+ with Register*ForCellReuse:
public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
string cellIdentifier = "NewsFeedCell";
var newsFeedCell = tableView.DequeueReusableCell (cellIdentifier) as NewsFeedCell;
//only required if you haven't used Register*ForCellReuse
if (newsFeedCell == null)
newsFeedCell = new NewsFeedCell (..., cellIdentifier,...);
//update your cell image and components here.
}
To know more about this, read the tutorials.
If you look around, you'll also find proven working patterns to load images in table cells lazily. Not that yours looks wrong at first sight, but it's uncommon.

MVVMCross iOS: How to trigger RowSelected on a Custom TableViewSource using CreateBindingSet?

My question is for MVVMCross in iOS. I have a custom TableView and a detailView. How can I bind my "showDetailCommand", so when user click on one of the rows on TableView with trigger RowSelected to switch to detailViewModel?
Here is my code structure:
public class SearchResultsViewModel: MvxViewModel
{
private MvxCommand _showDetailCommand;
public IMvxCommand ShowDetailCommand
{
get
{
_showDetailCommand = _showDetailCommand ?? new MvxCommand(ShowDetailCommandHandler);
return _showMapCommand;
}
}
private void ShowDetailCommandHandler()
{
ShowViewModel<ResultDetailViewModel>(new
{
city = _filter.City,
state = _filter.State,
interstate = _filter.Interstate,
travelPlaza = _filter.SearchTravelPlaza,
hasCatScale = _filter.HasCatScale,
hasWashoutExpress = _filter.HasWashoutExpress,
hasUndercarriage = _filter.HasUndercarriage,
nearest = _nearest
});
}
}
[Register("SearchResults")]
public class SearchResultsView : MvxTableViewController
{
public override void ViewDidLoad()
{
Title = "List";
base.ViewDidLoad();
var source = new TableViewSource(TableView);
var bindings = this.CreateBindingSet<SearchResultsView, SearchResultsViewModel>();
bindings.Bind(source).To(vm => vm.Items);
bindings.Apply();
TableView.BackgroundColor = UIColor.Clear;
TableView.ShowsVerticalScrollIndicator = false;
TableView.ScrollEnabled = true;
TableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
TableView.Source = source;
TableView.ReloadData();
}
public class TableViewSource : MvxTableViewSource
{
public TableViewSource(UITableView tableView)
: base(tableView)
{
tableView.RegisterClassForCellReuse(typeof(TableViewCell), TableViewCell.CellIdentifier);
}
public override float GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
var item = GetItemAt(indexPath);
return TableViewCell.CellHeight(item);
}
protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
var cell = tableView.DequeueReusableCell(TableViewCell.CellIdentifier) ?? new TableViewCell();
return cell;
}
}
}
[Register("TableViewCell")]
public class TableViewCell : MvxTableViewCell
{
public static readonly NSString CellIdentifier = new NSString("TableViewCell");
public TableViewCell()
: base()
{
Initialise();
}
public TableViewCell(IntPtr handle)
: base(handle)
{
Initialise();
}
private void Initialise()
{
var titleLabel = new UILabel(new RectangleF(10, 2, 200, 25));
Add(titleLabel);
this.DelayBind(() =>
{
var bindings = this.CreateBindingSet<TableViewCell, SearchResultsItemViewModel>();
bindings.Bind(titleLabel).For(x => x.Text).To(vm => vm.Name);
bindings.Bind(??).For(x => x.SelectionChangeCommand).To(vm => vm.showDetailCommand); // what should be in this line ??
bindings.Apply();
});
}
}
You can bind a view model command to the SelectionChangedCommand on the table source.
This technique is demonstrated in several samples - e.g
N=27 http://www.youtube.com/watch?v=h0Eww89c9DM&feature=youtu.be&t=47m30s
https://github.com/slodge/MvvmCross-Tutorials/tree/master/DailyDilbert
// command
new Cirrious.MvvmCross.ViewModels.MvxCommand<DilbertItem>((dilbertItem) =>
{
// note that we can only pass an id here - do *not* serialiase the whole dilbertItem
ShowViewModel<DilbertDetailViewModel>(new { id = dilbertItem.Id });
});
// binding
set.Bind(source)
.For(s => s.SelectionChangedCommand)
.To(s => s.ShowDetailCommand);
Alternatively, you can also put commands inside your list items and can then bind within each cell to a SelectedCommand (or some other custom event within your cell).
For examples of this, look at some of the samples which have menus - e.g. the ValueConversion sample

UIViewController not garbage collected when started modal

I’m having a memory issue in my iPhone app. I'm using MonoTouch. I have hunted down the problem, by using a static instance counters. The problem has something to do with modal view controllers. When I navigate from a root-viewcontroller to a first-level-viewcontroller and back, I find that the first-level-viewcontroller is garbage-collected. But when I make the first-level-viewcontroller modal by calling PresentModalViewController, and I return by calling DismissModalViewControllerAnimated, I find that the first-level-viewcontroller is not garbage collected. Not even when I call GC.Collect().
Why not? Am I doing something wrong?
What is the best practice for ensuring release of view controllers?
partial class RootViewController : UITableViewController
{
static int instanceCount;
static int nextId;
int instanceId;
public RootViewController (IntPtr handle) : base(handle)
{
instanceCount++;
instanceId = nextId++;
Console.WriteLine(string.Format("RootViewController #{0} Count={1}", instanceId, instanceCount));
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
Title = "Root";
NavigationItem.RightBarButtonItem = new UIBarButtonItem("ModalVC", UIBarButtonItemStyle.Plain,
delegate
{
var firstlevelVc = new FirstLevelViewController();
PresentModalViewController(new UINavigationController(firstlevelVc), true);
});
NavigationItem.LeftBarButtonItem = new UIBarButtonItem("PushVC", UIBarButtonItemStyle.Plain,
delegate
{
var firstlevelVc = new FirstLevelViewController();
NavigationController.PushViewController(firstlevelVc, true);
});
}
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
GC.Collect();
}
~RootViewController()
{
instanceCount--;
Console.WriteLine(string.Format("RootViewController #{0} Count={1}", instanceId, instanceCount));
}
}
public partial class FirstLevelViewController : UIViewController
{
static int instanceCount;
static int nextId;
int instanceId;
public FirstLevelViewController (IntPtr handle) : base(handle)
{
Initialize ();
}
[Export("initWithCoder:")]
public FirstLevelViewController (NSCoder coder) : base(coder)
{
Initialize ();
}
public FirstLevelViewController () : base("FirstLevelViewController", null)
{
Initialize ();
}
void Initialize ()
{
instanceCount++;
instanceId = nextId++;
Console.WriteLine(string.Format("FirstLevelViewController #{0} Count={1}", instanceId, instanceCount));
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
Title = "1. level";
NavigationItem.RightBarButtonItem = new UIBarButtonItem("Dismiss modal",
UIBarButtonItemStyle.Plain,
delegate { ParentViewController.DismissModalViewControllerAnimated(true); });
}
~FirstLevelViewController()
{
instanceCount--;
Console.WriteLine(string.Format("FirstLevelViewController #{0} Count={1}", instanceId, instanceCount));
}
}
Finalizer (~method) is being called when reference counter on an object comes to 0, are you sure you do not have a reference somewhere to the UIViewController you use?
To be sure I do not have a reference and ensure GC removal, I implement IDisposable and Dispose method, call it and do the necessary cleaning there (remove all objects from inner collections and such) and I do set the original variable to null. So it looks like:
MyUIViewController cntrl = new MyUIViewController();
... do some stuff
// when done
cntrl.Dispose();
cntrl = null;
And that is all.
BTW. I believe you should not call GC.Collect by yourself that frequently, as it stops all threads and does only the cleanup, all work is at that time stopped and is quite heavy on resources.
Hope it helps.
If you want to dispose the controller manually, you can call Dispose on it.