I am new to Xamarin development and I am trying to build simple IOS app.
I have a situation with 2 ViewControllers with specific classes ( FirstViewController, SecondViewController) and segue set between them.
When I try to pass data from the first one to the second in this code, everything is ok :
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
base.PrepareForSegue(segue, sender);
var viewController = (SecondViewController)segue.DestinationViewController;
viewController.Number = 1;
}
But when I set an identifier to segue, I get a NullReferenceException. There is no SecondViewController in DestinationViewController, but UIViewController.
What is even weirder is that when i remove the identifier, the exception is still there. Any Ideas ?
The problem was that when you change the namespace of a ViewController it gets detached from the Storyboard View.
Related
I have a Xamarin.iOS application which includes a Navigation Controller where the root in a View Controller ViewController.cs, inside the UIViewController (the default one created in a single view application) is a Table View which has a segue to a new View Controller.
Depicted in Storyboard below.
The Table view is controlled by the StationsTableViewSource.cs code which inherits from UITableViewSource. The source for the Table View is set inside the ViewController.cs.
StationsTable.Source = new StationsTableViewSource(StationPlayer.StationsTitleList)
When I press a cell in the table view I want to segue to the new view controller but as I see it:
I have no access to the Parent/Root/Containing View Controller from the Table View
I have no access to the New View Controller from the Table View.
My question is this, with a set up as above, how can I perform a segue from a cell inside a tableview nested in a View Controller to another View Controller?
Please correct the terminology - not too hot on Xamarin/iOS lingo.
Thanks all.
First of all, we need to figure out which segue have you created?
1.From your tableViewCell to a new ViewController.
This means: tap down on the Cell + ctrl and drag to a new ViewContoller. In this way, there's no need to execute PerformSegue() manually. When you click the Cell, it will push to a new Controller.
2.From your ViewContoller to a new ViewController
This means: tap down on the bottom bar of the ViewContoller + ctrl and drag to a new ViewController. In this way, we need to click the segue we created above then set the Identifier. When we click the Cell, the event below will be triggered:
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
//use this method we can push to a new viewController
//the first parameter is the identifier we set above
parentVC.PerformSegue("NewPush", indexPath);
}
I have no access to the Parent/Root/Containing View Controller from
the Table View
When you construct this Source you can pass your "Parent" ViewController like:
ViewController parentVC;
//You can add other parameters you want in this initial method
public MyTableViewCellSource(ViewController viewController, ...)
{
parentVC = viewController;
}
Moreover both segues will fire PrepareForSegue() in the parent ViewController. In this method you can pass parameters to the new ViewController:
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
if (segue.Identifier == "NewPush")
{
SecondVC secondVC = segue.DestinationViewController as SecondVC;
...//do some configuration
}
}
About how to use segue, you can read this official documentation for more details.
I set up a segue with a navigation controller in Xamarin.iOS. The first screen is just a ViewController that has a List of phoneNumbers, and adds to the list of phone numbers when a button is clicked. When a different button is clicked, I want to go to the CallHistory2 screen and display the list of phone numbers. However, I'm having trouble passing the List object to the second screen.
This is a method in the ViewController.cs class (the first screen)
public override void PrepareForSegue (UIStoryboardSegue segue, NSObject sender)
{
base.PrepareForSegue (segue, sender);
// set the View Controller that’s powering the screen we’re
// transitioning to
var callHistoryContoller = segue.DestinationViewController as CallHistory2;
//set the Table View Controller’s list of phone numbers to the
// list of dialed phone numbers
if (callHistoryContoller != null) {
callHistoryContoller.PhoneNumbers = PhoneNumbers;
}
}
I get an error at this line
var callHistoryContoller = segue.DestinationViewController as CallHistory2;
Cannot convert type 'UIKit.UIViewController' to 'PortableAppTest.iOS.CallHistory2' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion PortableAppTest.iOS
When I change CallHistory2 to UITableViewController the error goes away, but then callHistoryController won't contain a reference to my CallHistory2 class (implements UITableViewController), but rather to a generic UITableViewController class.
How do I work around this issue? Any help is appreciated!
try following, It will work .
if ( segue.DestinationViewController.GetType() == typeof(CallHistory2))
{
CallHistory2 callHistoryContoller = (CallHistory2)segue.DestinationViewController
callHistoryContoller.PhoneNumbers = PhoneNumbers;
}
I hope this helps someone else as I ran into the same issue!
This is from Xamarins Multiscreen guide
https://developer.xamarin.com/guides/ios/getting_started/hello,_iOS_multiscreen/hello,_iOS_multiscreen_quickstart/
And the issue for me was that the app is called "PhoneWord" but the code for this Viewcontroller was using "Phoneword" as the namespace (notice the lower case W)
As soon as I set it correctly it worked as expected.
I carry a uitableview with a collection of objects.
And when I click on a UITableViewCell open another UIView.
But I wanted to send the object that is in UITableViewCell for this new UIView and there to show their details.
I followed to answer this question to load another view: ViewController Segue Xamarin
I am very grateful if someone help
There are a few solutions, first one is to use
prepareForSegue(_:sender:) method:
Discussion
The default implementation of this method does nothing; you
can override it to pass relevant data to the new view controller or
window controller, based on the context of the segue. The segue object
describes the transition and includes references to both controllers
involved in the segue.
Segues can be triggered from multiple sources, so use the information
in the segue and sender parameters to disambiguate between different
logical paths in your app. For example, if the segue originated from a
table view, the sender parameter would identify the cell that the user
clicked. You could use that information to set the data on the
destination view controller.
In thi case you need to "save" an object that you want to pass in didSelectRow:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.currentObjectToPass = .. some object from array or somewhere else..
}
And then set it to next vc:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(UIButton*)sender {
if ([segue.identifier isEqualToString:#"bookingDetailsSegue"]) {
self.nextVC = segue.destinationViewController;
self.nextVC.objectToPass = self.currentObjectToPass;
}
}
The other way is to refuse of using of segues and get vc by its storyboardID.
This way you need
instantiateViewController(withIdentifier:) method:
Return Value The view controller corresponding to the specified
identifier string. If no view controller is associated with the
string, this method throws an exception.
Discussion You use this method to create view controller objects that
you want to manipulate and present programmatically in your
application. Before you can use this method to retrieve a view
controller, you must explicitly tag it with an appropriate identifier
string in Interface Builder.
This method creates a new instance of the specified view controller
each time you call it.
Then your code will look like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.nextVC = [self.storyboard instantiateViewControllerWithIdentifier:#"nextVCID"];
self.nextVC.objectToPass = = .. some object from array or somewhere else..
[self.navigationController pushViewController:self.nextVC animated:YES];
}
And don't forget to set vc identifier (Storyboard ID) in IB here:
override PrepareForSegue in your ViewController to set parameters on the destination controller. You will need to define public methods or properties on the destination controller that allow you to pass parameters:
public override void PrepareForSegue (UIStoryboardSegue segue, NSObject sender)
{
if (segue.Identifier == "TaskSegue") { // set in Storyboard
var navctlr = segue.DestinationViewController as TaskDetailViewController;
if (navctlr != null) {
// some public method you create in your destination controller
navctlr.SetTask (this, item);
}
}
}
I am developing a WinRT 8.1 application and I have a MenuFlyout within my custom control. Essentially, when a user clicks an Item within the MenuFlyout, the user is navigated to a different page. My dilemma is that I cannot access the Page element within my user control. Is there any work-around for this? I have looked at many similar SO questions, but none of them worked for me.
public sealed partial class BottomAppBar : UserControl {
public BottomAppBar() {
this.InitializeComponent();
//we are forced to manually add items as flyout does not support command
foreach (Vault v in User.Instance.Vaults) {
MenuFlyoutItem vault = new MenuFlyoutItem();
vault.Text = v.Name;
vault.Click += switchUser;
flyoutVault.Items.Add(vault);
}
}
private void switchUser(object sender, object e) {
//This line results in an error
this.Frame.Navigate(typeof(LoginPage));
/** Does not work as well
var parent = VisualTreeHelper.GetParent(this);
while (!(parent is Page)) {
parent = VisualTreeHelper.GetParent(parent);
}
(parent as Page).Frame.Navigate(typeof(LoginPage));
*/
}
The design-patterned solution is to create a navigation service passing the app frame to it and then use something like dependency injection to pass the navigation service to whomever might need it.
The simple solution is to store the reference to the Frame in your App class and access it through the app object/static property.
I followed the https://github.com/jamesmontemagno/Xam.NavDrawer example and Im able to successfully use the drawer layout with fragments(infact nested fragments with view pager). I have one issue, when I click the back button the navigation drawer menu item on the left side is not synced with the fragment that is shown.
This is how I navigate to other fragments
SupportFragmentManager.BeginTransaction().Replace(Resource.Id.content, fragment).AddToBackStack(fragment.Name).Commit();
I tried the OnAttachFragment method, but its not called on back stack. I also tried the SupportFragmentManager BackStackChanged method, but I could not get the current fragment that is in the view to update the navigation drawer menu title.
I had the same issue and couldn't find any solution as well. Although this question is kinda old, my solution may help someone. I'm not sure if it's the best solution but it works for me.
So first you need to add variable to store ID of previously checked item:
private int previousItemChecked;
set it initially to your default checked item:
if (savedInstanceState == null) {
selectItem(0);
previousItemChecked = 0;
}
then edit the fragment transaction so that the transaction title in backstack contains position of the previously checked item converted to string and after the transaction is done set the variable previousItemChecked to the currently checked item id:
fragmentManager.beginTransaction().replace(R.id.content_frame, selectedFragment).addToBackStack(""+previousItemChecked).commit();
previousItemChecked = mDrawerList.getCheckedItemPosition();
and finally in method onBackPressed you need to get the string previously assigned to fragment transaction, parse it to int and update the drawer according to the obtained id:
#Override
public void onBackPressed() {
if(fragmentManager.getBackStackEntryCount() > 0) {
String title = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount()-1).getName();
int pos = Integer.parseInt(title);
previousItemChecked = pos;
mDrawerList.setItemChecked(pos, true);
}
super.onBackPressed();
}
I took the code from my app created in Android Studio so it isn't for Xamarin but if you update the code it should work with it too. What's important here is the idea how it's done. I hope the answer is understandable and will help someone.
I had the same issue and I solved like this:
Inside selectItem we are passing the position Item;
So if position is 0 (or whatever is fragment we want it appears as first indipendently from it's position on the menu) we have to avoid to save the first transaction. So...
private void selectItem(position){
//...
if (position != 0)
{
SupportFragmentManager.BeginTransaction()
.Replace(Resource.Id.content_frame, fragment)
.AddToBackStack(fragment.Name)
.Commit();
}
else
{
SupportFragmentManager.BeginTransaction()
.Replace(Resource.Id.content_frame, fragment)
.Commit();
}
}
Managed to achieve this by injecting a DestinationChangedListener into the NavController like this:
NavController navController.addOnDestinationChangedListener(this);
and then:
#Override
public void onDestinationChanged(#NonNull NavController controller,
#NonNull NavDestination destination,
#Nullable Bundle arguments) {
NavigationView navigationView = findViewById(R.id.nav_view);
if(navigationView != null){
navigationView.setCheckedItem(destination.getId());
}
}