I have a tableview inside a UIViewController. I also have some dynamic data, which I would like to load onto this tableview. I have attempted two approaches so far:
I created a nested private class inside this UIViewController, which inherits from UITableViewSource. I then made a new instance of this class in ViewDidLoad and assigned it to my table view's Source property.
For this option, the override functions are called. However,I'm loading the lists dynamically in the ViewWillAppear, so the data source at this point is empty.
I tried creating a new instance of the nested class in the ViewWillAppear, but the override functions aren't called when I do this. They're only called if I do it in theViewDidLoad.
The other option I tried was implementing the IUITableViewDelegate and the IUITableViewDataSource in my UIViewController class. Then in my ViewDidLoad assigned "this" to both the WeakDelegate and WeakDataSource properties of my table view.
The challenge I'm facing with this second option is : I keep getting this error:
[HomePageController tableView:numberOfRowsInSection:]: unrecognized selector sent to instance 0x185ce440
I've added this export attribute to the function, but it doesn't solve the issue:
[Export ("tableView:numberOfRowsInSection:")]
public nint RowsInSection (UITableView tableView, nint section)
{
return 1;
}
I prefer the first option, but why can't I initialize the class in the ViewWillAppear after loading my data? I need to call it here, since the ViewDidLoad is called only once.
This is what I've done:
public async override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
try {
LoadingOverlay loadingOverlay = new LoadingOverlay (UIScreen.MainScreen.Bounds, "Loading...");
this.View.AddSubview (loadingOverlay);
await FetchAllData ();
loadingOverlay.Hide ();
if (tableViewNotice != null) {
if(allData != null)
{
if(allData.Count > 0)
{
homeDataSource = new HomeDataSource(this);
tableViewNotice.Source = homeDataSource;
}
}
}
} catch (Exception ex) {
Console.WriteLine (ex.Message + ex.StackTrace);
}
}
When UITableView be loaded at draw to the layer,it will load datas just like when viewDidLoad has been called. But the tableView's datasource is set in viewWillAppear, that's too late. so you need call reloadData() after you set datasource or set datasource in viewDidLoad function.
Related
I'm developing an iOS application using Xamarin.iOS (Code only, no storyboards) and I wonder what the best way to send data back to the original uiviewcontroller when I pop from the navigationcontroller.
In android I use StartActivityForResult and then override OnResult, but I can't find a similar way for iOS.
I know there's overrides for ViewDidLoad, ViewDidAppear, etc, what I'm looking for is some kind of ViewDidGetPoppedBackTo (hope you get it).
Or is there another better way to achieve this?
NavigationController keeps track of all the ViewControllers as an array: NavigationController.ViewControllers
You can get an existing instance of the ViewController Type from this array via following code:
(You may write this method in BaseViewController if you have it.)
public T InstanceFromNavigationStack<T> () where T : UIViewController
{
return (T)NavigationController.ViewControllers.FirstOrDefault(v => v is T);
}
Then use it like :
var myVCInstance = InstanceFromNavigationStack<MyTargetViewController>();
if(myVCInstance != null)
{
//Assign a value like
myVCInstance.MyVariable = "MyValue";
//Or call a method like
myVCInstance.MethodToReloadView("MyValue")
}
//Go Back Navigation Code
//Then here write your navigation logic to go back.
This not only helps passing data in Previous ViewController, but Any ViewController in the stack. Simply pass the Type of it to get an Instance from Stack.
NOTE: This should work if your Navigation stack doesn't have multiple instance of the same ViewController Type.
Use this way
ViewController viewController = (ViewController)NavigationController.TopViewController;
viewController.SendData(myevent);
Create method SendData in your ToViewController this method is called first when navigationg back and your data send to your previous ViewController.
Another option that I've started using is EventHandler methods. Here is an example I use to populate a UITextField in the parent view controller with the selection from a UITableView (child view controller) and then close the child.
Define the EventHandler method in your parent view controller:
void LocationLookup_OnSelected(object sender, EventArgs e)
{
chosenLocation = (MKPlacemark)sender;
planLocation.Text = chosenLocation.Name;
this.ParentViewController.DismissViewController(true, null);
}
Pass the EventHandler method from the parent as a property of the child.
public partial class LocationLookupViewController : UITableViewController
{
private event EventHandler OnSelected;
public LocationLookupViewController(EventHandler OnSelected)
{
this.OnSelected = OnSelected;
}
...
}
Call the EventHandler passing in the object / data that you need in the parent
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
...
OnSelected(response?.MapItems[0].Placemark, new EventArgs());
}
Note - The above class and function are incomplete but should provide you with an idea of how this technique works.
I am trying to make a simple change to a tableview for IOS. As my project is using Xamarin forms I use a custom renderer.
The changes I want to make is to close the keyboard is the table is scrolled, simple as that.
When I make the following changes it will work for my new scrolled override event but stops item (cell) selected from triggering (some of my cells navigate away from the page) and must be tapped.
public class CustomTableRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var tableView = Control as UITableView;
TableDelegate tableDelegate;
tableDelegate = new TableDelegate();
tableView.Delegate = tableDelegate;
}
}
public class TableDelegate : UITableViewDelegate
{
#region Constructors
public TableDelegate()
{
}
public TableDelegate(IntPtr handle) : base(handle)
{
}
public TableDelegate(NSObjectFlag t) : base(t)
{
}
#endregion
#region Override Methods
public override void Scrolled(UIScrollView scrollView)
{
UIApplication.SharedApplication.KeyWindow.EndEditing(true);
//base.Scrolled(scrollView);
}
#endregion
}
I have tried changing the UITableViewDelegate to UITableViewController but seems to cause more problems.
Adding in the override for all other methods calling base also does not seem to help. Not sure what I am missing.
Any idea why this is caused?
Note: If more information is needed please comment to let me know. Same with if the question is not clear. Comments would help.
In the app I'm working on there's a need for custom UITableView section headers and footers. For this I'd like to create a custom control that works with binding and our logic.
For that I've created a XIB and added a backing class that looks like the following:
public partial class HeaderFooterView : MvxTableViewHeaderFooterView
{
public static readonly NSString Key = new NSString("HeaderFooterView");
public static readonly UINib Nib = UINib.FromName("HeaderFooterView", NSBundle.MainBundle);
public HeaderFooterView(IntPtr handle) : base(handle)
{
}
public override void AwakeFromNib()
{
base.AwakeFromNib();
//var binding = this.CreateBindingSet<HeaderFooterView, TableViewSection>();
//binding.Apply();
}
}
The MvxTableViewHeaderFooterView is actually a pretty simple class, combining the stock UITableViewHeaderFooterView with IMvxBindable. Nothing fancy.
However for some reason, even though I register it properly within the TableViewSource constructor:
tableView.RegisterNibForHeaderFooterViewReuse(HeaderFooterView.Nib, HeaderFooterView.Key);
And do the proper way of returning the Header itself only:
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
return tableView.DequeueReusableHeaderFooterView(HeaderFooterView.Key);
}
The app dies with the following error:
2017-07-12 16:56:40.517 MyAppiOS[3833:58706] *** Assertion failure in -[UITableView _dequeueReusableViewOfType:withIdentifier:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.7.47/UITableView.m:6696
2017-07-12 16:56:40.528 MyAppiOS[3833:58706] WARNING: GoogleAnalytics 3.17 void GAIUncaughtExceptionHandler(NSException *) (GAIUncaughtExceptionHandler.m:48): Uncaught exception: invalid nib registered for identifier (HeaderFooterView) - nib must contain exactly one top level object which must be a UITableViewHeaderFooterView instance
My NIB actually contains a single root object, the root view itself, that is set to the HeaderFooterView class (which derives from MvxTableViewHeaderFooterView which in turn derives from UITableViewHeaderFooterView). Yet it claims there's no UITableViewHeaderFooterView instance.
Why isn't it working as it's supposed to?
It's because return tableView.DequeueReusableHeaderFooterView(HeaderFooterView.Key); can return null if there are no HeaderFooterViews to reuse. In that case you have to create your own:
var view = tableView.DequeueReusableHeaderFooterView(HeaderFooterView.Key);
if (view == null){
//instantiate the nib here and set view
}
return view;
I would suggest structuring the backing class as follows:
public partial class HeaderFooterView : MvxTableViewHeaderFooterView
{
public static readonly NSString Key = new NSString("HeaderFooterView");
public static readonly UINib Nib = UINib.FromName("HeaderFooterView", NSBundle.MainBundle);
static HeaderFooterView()
{
//Adding this alone should allow your tableview to properly instantiate the view.
Nib = UINib.FromName("HeaderFooterView", NSBundle.MainBundle);
}
public static HeaderFooterView Create()
{
// However you can add this static method and create and return the view yourself.
var arr = NSBundle.MainBundle.LoadNib(nameof(HeaderFooterView ), null, null);
var v = Runtime.GetNSObject<HeaderFooterView >(arr.ValueAt(0));
return v;
}
public HeaderFooterView(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
}
public override void AwakeFromNib()
{
base.AwakeFromNib();
}
}
Adding the static constructor by itself should be enough to allow your table view to properly instantiate the Nib. However if you still end up having problems like that you can use the static method 'Create' to instantiate the nib yourself as so:
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
HeaderFooterView theView = HeaderFooterView.Create()
return theView;
}
Try those suggestions, one or both should work for you.
Okay, found the issue.
While my initial XIB was correct, for some reason the root object's type was erased, and Interface Builder refused to accept mine.
However using VS2017 for Mac, I was able to set the proper root view class, and now everything works fine.
I am getting started with MvvmCross in iOS.
public class MainView : MvxTabBarViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
var vm = (MainViewModel)this.ViewModel;
if (vm == null)
return;
}
}
Setting a breakpoint to the line where access the ViewModel, shows me, that ViewModel is null.
I can workaround this by calling ViewDidLoad() in the constructor. Then, ViewModel is null during the constructor call, but valid in the default ViewDidLoad call. But that looks like a workaround. can anybody help?
I'm guessing here the problem here will be specific to the way that TabBarViewController is constructed.
ViewDidLoad is a virtual method and it is called the first time the View is accessed.
In the case of TabBarViewController this happens during the iOS base View constructor - i.e. it occurs before the class itself has had its constructor called.
The only way around this I've found is to add a check against the situation in ViewDidLoad, and to make a second call to ViewDidLoad during the class constructor.
You can see this in action N-25 - https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/blob/976ede3aafd3a7c6e06717ee48a9a45f08eedcd0/N-25-Tabbed/Tabbed.Touch/Views/FirstView.cs#L17
Something like:
public class MainView : MvxTabBarViewController
{
private bool _constructed;
public MainView()
{
_constructed = true;
// need this additional call to ViewDidLoad because UIkit creates the view before the C# hierarchy has been constructed
ViewDidLoad();
}
public override void ViewDidLoad()
{
if (!_constructed)
return;
base.ViewDidLoad();
var vm = (MainViewModel)this.ViewModel;
if (vm == null)
return;
}
}
I'm having trouble getting change a property of an
Outlet in a new method, for example:
public partial class ViewApresentacao : UIViewController
{
...
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
//my ImageView
imgModelo.Image = UIImage.FromBundle ("Image/car.png");
}
//new method
public void Test(string caminho)
{
imgModelo.Image = UIImage.FromBundle (caminho);
}
....
}
At viewDidLoad functions normally, but the second
method Test did not, wanted to use it
to update when a selected row in a table, it's all
ok, but when I call the method Test, the error
"object reference not set to an instance of an object"
appears well in line of the Outlet.
Problem solved, I was in my main class with different instances, with the help Xamarin team, managed to solve my problem.