Xamarin iOS: Replace View Controller within the current Tab Bar item - xamarin

I am just starting up in Xamarin iOS (native) development, so bear with me as I try to explain what we're trying to do.
I have a single storyboard Main.storyboard, where I have designed and built a single Tab Bar controller, and several custom View Controllers, some of which are linked up as tabs. On the first tab, I have a login screen if they are not logged in. However, if they ARE logged in, I want to show the view defined in a different View Controller (Account) that exists within the same storyboard.
In the ViewDidLoad() override for the LoginController, I have the following code that I thought would show the "logged in" Account View Controller:
this.PresentViewController(new AccountController(), true, null);
This line of code executes without error, however the Login view is still shown. The ViewDidLoad() override in the AccountController does NOT fire.
Question 1: Is this the right method to call in order to replace your current view controller with another?
Now, if I place this exact same line of code in an async button handler method (particularly, the Facebook SDK Login Button's Completed event), it actually hits the ViewDidLoad() override in the AccountController, but I receive a NullReferenceException on a line that is simply referencing a button that is defined within the storyboard/view.
Question 2: Why would the same exact call from an event handler behave so differently?
Lastly, if I set the AccountController up as a tab bar tab instead, the code in ViewDidLoad() that is referencing the button works as expected.
Up to this point, all of my loading and unloading of different views was being handled automagically by the UITabBarController, but now that I'm trying to do some manual transitions, I am having a tough time finding appropriate documentation that isn't outdated and applies to my specific setup.

Generally , we can use PresentViewController to navigate to another page in Navigation Controller , this should be a safe and easy way.
Push :
Pop :
However , PresentModalViewController is a model ViewController , although it seems like the same as PresentViewController . It not needs to be in the stack of Navigation Controller ,and always used in a single view needs to show temporarily , soon will dismiss it.
If scene needs to reset RootViewControllerwhen runtime , this can be thought as the third way to navigate .
var storyboard = UIStoryboard.FromName("Main", null);
var anotherController = storyboard.InstantiateViewController("AnotherViewController") as AnotherViewController;
UIWindow window = UIApplication.SharedApplication.KeyWindow;
if(window != null)
{
window.RootViewController = anotherController;
}
In addittion , this will be un-safe way to use , better used them in current ViewController dismiss method :
DismissViewController(true, () => {
// Set Root View Controller
});

While Junior's answer pointed me in the right direction, the ultimate solution was to adjust the first tab to be a new NavigationController, and use (manual) Segues to navigate between the views.
The segues were added to the storyboard, and the View Controllers ultimately make calls to PerformSegue('namedSegue', this) to navigate to the next view.

Related

Xamarin Shell: Redirect to different page and remove current page from nav stack

I imagine this is pretty straightforward, but I'm struggling to make sense of how to do it via the documentation...
I've got a tab page, that is opened from a flyout item.
The tabs for this page are dynamically loaded from saved data in the code behind, and all works exactly as expected.
However, in the scenario when there is no saved data (and thus, no tabs) I want to redirect the user to a different page, and crucially, not be able to navigate back to this tab page (but allow going back to the page they were initially on BEFORE they navigated to the tabbed page)
I've got a method in initalize for the tabbed page that, if it doesn't have any saved data, attempts to do this. I started with:
Shell.Current.Navigation.PopAsync();
Shell.Current.GoToAsync(nameof(AddNewDataPage));
which navigated to the add page, but pressing the back button just resulted in the add page being shown again, over and over.
I then tried:
Shell.Current.Navigation.PopToRootAsync();
Shell.Current.GoToAsync(nameof(AddNewDataPage));
which did the same.
So next I went with trying to navigate backwards:
Shell.Current.GoToAsync("../" + nameof(AddNewDataPage));
which showed the right page again, but now the back button doesn't work
Next, I went with trying an absolute route:
Shell.Current.GoToAsync("//HomePage/" + nameof(AddNewDataPage));
which worked... sort of.
The first time the user clicks the flyout for the tabbed page, it all works great. back button takes you to the home page etc... but the second and subsequent times the user clicked the flyout, they navigate to the tabbed page and my LoadData method isn't called.
I assumed this is because the tabbed page is still loaded, so I added:
protected override void OnAppearing()
{
LoadData();
}
Now, when the user clicks the flyout for the second and subsequent times, they navigate to the HomePage page instead of the AddNewDataPage page (an improvement, I guess?)
So, now I'm at a loss.. it seems like this should be really simple, but I can't figure it out.
You can try this.
//route The route hierarchy will be searched for the specified route, upwards from the current position. The matching page will replace the navigation stack.
https://learn.microsoft.com/nl-nl/xamarin/xamarin-forms/app-fundamentals/shell/navigation?WT.mc_id=friends-0000-jamont#relative-routes
await Shell.Current.GoToAsync($"//{nameof(AddNewDataPage)}");
https://www.youtube.com/watch?v=ylbgWHB_gMI&t=703s&ab_channel=JamesMontemagno
An explanation about relative routes.
May I don't understand your problem clearly. But I created a simple with the flyout shell app.
At first, it seems that the construction method of the content page will jut call once which the page shows first time. So I put the following code into it. Such as:
public ItemsPage()
{
InitializeComponent();
BindingContext = _viewModel = new ItemsViewModel();
Shell.Current.Navigation.PopAsync();
Shell.Current.GoToAsync(nameof(NewItemPage));
}
And then when the app got into the ItemsPage first time, it will get into the NewItemPage. User can add new item here. After that, when I clicked the back button, I will get into the ItemsPage back without it's construction method called. So you can't add the LoadData(); in it.
You can put it into the OnAppearing method. Such as:
protected override void OnAppearing()
{
//LoadData();
if (_viewModel.Items.Count < 8)
{
Shell.Current.Navigation.PopAsync();
Shell.Current.GoToAsync(nameof(NewItemPage));
// then add two item in the NewItemPage
}
else {
base.OnAppearing();
_viewModel.OnAppearing();
}
}
This will get the same effect as above. I also try to move the Shell.Current.Navigation.PopAsync();. The result is same. It will get back to the ItemsPage when you clicked the back button in the NewItemPage after you add two item.
which navigated to the add page, but pressing the back button just resulted in the add page being shown again, over and over.
You may just need a if() to judge the condition the app need to go to the AddNewDataPage with skipping the tab page.

How do you navigate between views in a Cocoa application using Swift?

I am trying to use NSView to navigate between NSViewControllers using Swift in an OS X application.
I am not sure what are you trying to ask here. I would suggest to go through these links
Mac OS X Cocoa multiview application navigation
Easy Switching of "View Controllers" in Mac Apps (similar to iOS)
Hope this helps,cheers.
Edit:
If you are new to swift i would suggest to go through https://developer.apple.com/swift/ first. Your methods will look like the following in swift
func switchSubViews(newSubview : NSView) {
//Implementation
}
func prepareViews(){
//Implementation
}
First create a custom view in your xib file. This will be where the various view controllers will put their own views. In your the code which will navigate between the view controllers create a variable to reference the custom view in your xib file;
var masterView : NSView!
Back in interface builder, connect the custom view to your variable. masterView now refers to the custom view and gives you the means to change it.
You can replace that view with the view of the view controller that you want to be active at any given time.
For this example, assume that I have three view controllers (as examples);
var pDemog : patientDemographicsController!
var pExams : patientExamsListController!
var pReports : patentReportsViewController!
The first view controller can be set up as follows;
masterView.addSubview(pDemog.view)
Later when needed you can replace the view with the view of a different view controller
masterView.replaceSubview(pDemog!.view with: pExams!.view)
and back again;
masterView.replaceSubview(pExams!.view with: pDemog!.view)
or to another;
masterView.replaceSubview(pExams!.view with: pReports!.view)
That's the mechanism for changing the view. You can add animations as referenced above, but it's not necessary. Also, you should prepare the views in the view controllers as follows; (this is just one example, you need it for each view controller)
pDemog.view.setFrameOrigin(NSPoint(x:0, y:0))
pDemog.view.setFrameSize(masterView.frame.size)
do this before you start setting the views in the masterView.

Change UILabel from appDelegate

I want to do some stuff from the appDelegate in Xcode. One of these things are to change a UILabel.
ViewController *viewController = [[UIStoryboard storyboardWithName:#"Main_iPhone" bundle:nil] instantiateViewControllerWithIdentifier:#"id"];
viewController.label.text = #"heeej";
I was told this would do the trick. Doesn't work. Label doesn't change. Does anyone know what the problem is?
There are several problems:
Don't do anything in the AppDelegate except for loading the window an the initial view controllers (if needed).
You are instantiating a new view controller in the first line. I assume you already have a view controller and you want to change the label in that view controller. Than you need a reference to that view controller and use this reference to send messages to it.
The label should not be a property of the view controller. You should try to follow the design pattern Model-View-Controller or the pattern Model-View-ViewModel. Ask you preferred search engine what those are if you don't know.
id as an identifier for anything in Objective-C is a bad idea because this is used as an object type.
Edit: You don't need a reference to change a property in the view controller. Use notifications to update the label. The system sends a notification with the name UIApplicationWillEnterForgroundNotification' (see [here][1]). Add the view controller as an observer to the[NSNotificationCenter defaultCenter]` for this name and react on the notification. Read the Apple documentation if you don't know what I am talking about.

reference between app controller and window controllers

at the risk of a public flogging, I was hoping someone could clarify my understanding of communicating between the app controller and a window controller. Here is the scenario using xcode4.
using the default AppDelegate.h and .m as the "controller" (which is also a delegate to MainMenu.xib). Have an ivar called int counter.
Created page.xib and PageController.h and .m (subclass NSWindowController). Imported it into AppDelegate.m
Used IBAction to create and view the page object. Like this:
if (!page1) {
PageController *page1 = [[Page
if (!page1) {
page1 = [[PageControoer alloc] initWithWindowNibName:#"page"];
}
[page1 showWindow:sender];
So the new window pops and we can press buttons, etc. The code for the new window is all in PageController.h and .m. and things basically work.
That is the context, here is where I'm confused.
a) question: let's say I want to access that original ivar in AppDelegate.h called counter from PageController. Either retrieving or updating the variable. What approach would I take?
b) confirm: let's say I'm back in the AppDelegate and want to get access to a selector from page1. I believe I can do this as so: [page1 runaction]; or [[page1 variable] setStringValue:#"hello"];
(this complies but I'm not sure it really works because I can't get the changes into the xib view.)
ok and the stumper. Say another view was created with another view controller call it Page2Controller.h and .m.
c) how should data flow between page and page2 -> via the AppDelegate or directly? what would the syntax look like to connect them together?
I've been following tutorials, but they don't really cover this back and forth messaging. Thanks for all the help!
a) Generally, if you want to have data that is accessed by your controllers, it should be in a model which they are given access to in some way. You can access things in the app delegate using this method:
AppDelegate* appDelegate = [[NSApplication sharedApplication] delegate];
[appDelegate <some method>];
b) I don't understand what you're asking. If the app delegate has a pointer to page1, then yes, you can call it directly.
c) Again, you should have a data model of some sort. The controllers should update the data model when the user changes the view. You can use notifications, IBActions, and direct calls to do the updating, for example. You should look up the Model, View, Controller design pattern.

Prism 4: Unloading view from Region?

How do I unload a view from a Prism Region?
I am writing a WPF Prism app with a Ribbon control in the Shell. The Ribbon's Home tab contains a region, RibbonHomeTabRegion, into which one of my modules (call it ModuleA) loads a RibbonGroup. That works fine.
When the user navigates away from ModuleA, the RibbonGroup needs to be unloaded from the RibbonHomeTabRegion. I am not replacing the RibbonGroup with another view--the region should be empty.
EDIT: I have rewritten this part of the question:
When I try to remove the view, I get an error message that "The region does not contain the specified view." So, I wrote the following code to delete whatever view is in the region:
// Get the regions views
var regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
var ribbonHomeTabRegion = regionManager.Regions["RibbonHomeTabRegion"];
var views = ribbonHomeTabRegion.Views;
// Unload the views
foreach (var view in views)
{
ribbonHomeTabRegion.Remove(view);
}
I am still getting the same error, which tells me there is something pretty basic that I am doing incorrectly.
Can anyone point me in the right direction? Thanks for your help.
I found my answer, although I can't say I fully understand it. I had used IRegionManager.RequestNavigate() to inject the RibbonGroup into the Ribbon's Home tab, like this:
// Load RibbonGroup into Navigator pane
var noteListNavigator = new Uri("NoteListRibbonGroup", UriKind.Relative);
regionManager.RequestNavigate("RibbonHomeTabRegion", noteListNavigator);
I changed the code to inject the view by Registering it with the region, like this:
// Load Ribbon Group into Home tab
regionManager.RegisterViewWithRegion("RibbonHomeTabRegion", typeof(NoteListRibbonGroup));
Now I can remove the RibbonGroup using this code:
if(ribbonHomeTabRegion.Views.Contains(this))
{
ribbonHomeTabRegion.Remove(this);
}
So, how you inject the view apparently matters. If you want to be able to remove the view, inject by registration with the Region Manager
StockTraderRI Example Project by Microsoft contains the following example of removing views from region in ViewModel.
private void RemoveOrdersView()
{
IRegion region = this._regionManager.Regions[RegionNames.ActionRegion];
object ordersView = region.GetView("OrdersView");
if (ordersView != null)
{
region.Remove(ordersView);
}
}
Is it possible you have a RegionAdapter that is wrapping the view inside another view before adding it? The ribbonHomeTabRegion should have a property with the collection of views - is there anything inside it?

Resources