I need to implement a simple UIPageViewController using Xamarin in Visual Studio 2017 for mac.
As the documentation says in https://developer.xamarin.com/api/type/MonoTouch.UIKit.UIPageViewController/ I need to connect the previous and next views, using UIPageViewControllerDataSource.GetNextViewController and UIPageViewControllerDataSource.GetPreviousViewController from the UIPageViewControllerDataSource delegate, but I'm not clear where should I do that, nor I see any where to do that using the storyboard designer.
The idea to use the UIPageViewController is to add a quick tutorial of 4 pages before advancing to another View, at the last view of the UIPageViewController there would be a button that calls the segway to the next page.
You can add a UIPageViewController in the Storyboard. Configure the style in the Properties window => Widget(Navigation, Transition Style, Spine Location).
Then you can add the DataSource in the corresponding CS file. Add the firstly shown UIViewController using: SetViewControllers(new UIViewController[] { viewController }, UIPageViewControllerNavigationDirection.Forward, true, null);. Please notice that when you set the Spine Location to Mid, this array should contain at least two UIViewControllers. If not, add just one.
but I'm not clear where should I do that, nor I see any where to do
that using the storyboard designer.
When you want to navigate to the next page, the event GetNextViewController() will fire(i.e. first page - second page). And you should return which Controller will show in this event. Also GetPreviousViewController() means which Controller you want to show when navigate back. Here is my sample:
public class MyPageViewDataSource : UIPageViewControllerDataSource
{
List<UIViewController> pageList;
public MyPageViewDataSource(List<UIViewController> pages)
{
pageList = pages;
}
public override UIViewController GetNextViewController(UIPageViewController pageViewController, UIViewController referenceViewController)
{
var index = pageList.IndexOf(referenceViewController);
if (index < pageList.Count - 1)
{
return pageList[++index];
}
else
{
//when navigate to the last page, return null to disable navigate to next.
return null;
}
}
public override UIViewController GetPreviousViewController(UIPageViewController pageViewController, UIViewController referenceViewController)
{
var index = pageList.IndexOf(referenceViewController);
if (index == 0)
{
//when navigate to the first page, return null to disable navigate to previous.
return null;
}
else
{
return pageList[index - 1];
}
}
}
At last set your UIPageViewController's DataSource to this:DataSource = new MyPageViewDataSource(pageList);
Related
I'm building my first full-scale Xamarin.Forms app and trying to figure out how to keep user input between navigation. After doing some searching online I've read that the default behavior is to completely reload pages each time you navigate, but you can change the default behavior by setting the NavigationCacheMode to true or required, but I've tried to set this attribute in both xaml and C# with no success - it seems like the property is not recognized.
Is there a simple way to make it so that user input does not disappear when navigating between pages? If anyone can show me how to set the NavigationCacheMode that would be great, but I'm also open to any reasonable solution that will keep the user input from disappearing during navigation.
Additional details: My app has a UWP and Android project. I am using a master detail page for navigation. Here is my MenuList_ItemSelected event handler:
private void MenuList_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = (MenuItem)e.SelectedItem;
var title = item.Title;
var page = (Page)Activator.CreateInstance(item.TargetType);
Detail = new NavigationPage(page); //TODO: when menu item is clicked and you're already on that page, the menu should just slide back. (currently it does nothing and stays out).
IsPresented = false;
}
Finally was able to solve this! I adapted this code from a related post which implements a Dictionary that keeps track of the navigation stack:
In the constructor for my Master Detail Page:
public partial class MenuPage : MasterDetailPage
{
Dictionary<Type, Page> menuCache = new Dictionary<Type, Page>();
}
Then in the ItemSelected method:
private void MenuList_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (menuCache.Count == 0)
menuCache.Add(typeof(AttendancePage), Detail);
var item = (MenuItem)e.SelectedItem;
if (item != null)
{
if (menuCache.ContainsKey(item.TargetType))
{ Detail = menuCache[item.TargetType]; }
else
{
Detail = new NavigationPage((Page)Activator.CreateInstance(item.TargetType));
menuCache.Add(item.TargetType, Detail);
}
menuList.SelectedItem = null; //solves issue with nav drawer not hiding when same item is selected twice
IsPresented = false;
}
}
I'm having trouble with navigation outside of the typical use cases for navigation controllers and tabbar controllers. Here's a drawing of a simplified version. (real version has a TabBarController that feeds into 9 NavControllers)
In my Home Nav Controller, there's a table view where the user could select a row and that should navigate them to a particular Events Detail View Controller. At that point, the user should be able to navigate back to the Events TableView Controller to see the list of events or use the TabBar to navigate to another section.
So far with my code I can push the correct detail view controller and select the correct index of the tabbar but it only works the first time:
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
tableView.DeselectRow(indexPath, true);
var eventsDetailView = new EventsDetailController(eventPosts[indexPath.Row]);
//loop through ViewControllers array in case the user has set a custom tabbar order via the More Tab
foreach (UIViewController viewC in tbController.ViewControllers)
{
UINavigationController navController = viewC as UINavigationController;
if (navController.TabBarItem.Tag.Equals(9))
{
navController.PushViewController(eventsDetailView, false);
tbController.SelectedIndex = 9;
}
}
}
After the user has navigated to the events detail view controller, if they then:
travel to the Home ViewController via the TabBar
select a new TableRow to navigate to a different Events detail view controller
... then the same previous events detail view shows up. It doesn't matter which row the user selects in the Home View Controller. The initial detail view controller will always be the same one to be presented.
Any help pointing me in the right direction would be greatly appreciated!
Solution:
In my understanding, what you want is :
HomePageController --> EventDetailPage --> EvevtTableViewControll
So in the first step, you could push to EventDetailPage in HomePageController:
this.NavigationController.PushViewController(new HomeDetailUIViewController(), true);
Then, in your EventDetailPage, you can customize a back button, for example:
UIBarButtonItem backBtn = new UIBarButtonItem();
backBtn.Title = "back";
backBtn.Style = UIBarButtonItemStyle.Plain;
this.NavigationItem.LeftBarButtonItem = backBtn;
backBtn.Clicked += (sender, e) =>
{
// 1 here is an example, it should be the index of your EvevtTableViewControll
NavigationController.TabBarController.SelectedIndex = 1;
NavigationController.PopToRootViewController(true);
};
Set the NavigationController.TabBarController.SelectedIndex=1 first to make sure EvevtTableViewControll will show after you back from EvevtDetailViewControll, then your PopToRootViewController to back to the EvevtTableViewControll.
Try it and let me know if you have any problem.
I am building a UWP app. On one of my Pages, I keep running into the exception listed in the title.
This is the scenario. The App loads. I navigate to my Transit Page (the one that is causing the issue) and it loads fine. Then I navigate away to another page fine, but when I try to go back to the Transit Page, I hit that unhandled exception in the App.g.i.cs file.
The only message in the exception is: "The parameter is incorrect.\r\n"
I have no idea what is causing this. If I put a break in the code on my OnNavigatedTo event in the Transit code behind, it loads the ViewModel, and I hit the error right after this:
public ICommand TransitButtonCommand
{
get
{
return _TransitButtonCommand ??
new RelayCommand(() =>
{
_navigationService.Navigate(typeof(TransitView));
});
}
}
The only way around this is to put: NavigationCacheMode="Required" In the Transit view XAML file, but this is not optimal, because this page shows train/bus schedules, and I need it to be updated every time it is loaded.
Any one have any ideas why this is happening? I am happy to provide anymore code from the project to help with this issue.
My Transit page consists of a SplitView where I load a another XAML page into it based on what menu item is selected.
I created a small project and put it up on GitHub to demonstrate this error, you can find it here:
https://github.com/marekt77/SplitViewBindingTest
Run the App then navigate to the SplitView Page, then away, and then back again, and you will hit the error.
The problem is in the SplitViewPageViewModel. The PanelPage property is returning a Page. When you first navigate to SplitViewPage, it will bind it to the content properly, but on the next time, you're binding it to a new visual tree. The PanelPage is still stuck in memory as the child of the first SplitViewPage you created. This will throw an exception because that page is a child of another element.
You need to avoid having any UI elements in your view models. They get stuck in memory.
If you have no other option and you just wanna get this thing working, here's a workaround:
public class SplitViewPageViewModel : ViewModelBase
{
private INavigationService _navigationService;
public SplitViewPageViewModel(INavigationService navService)
{
_navigationService = navService;
// construction removed here
}
private ICommand _OtherPageCommand;
public ICommand OtherPageCommand
{
get
{
return _OtherPageCommand ??
new RelayCommand(() =>
{
_PanelPage = null; // <-- Added this
_navigationService.Navigate(typeof(OtherPage));
});
}
}
private ICommand _HomePageCommand;
public ICommand HomePageCommand
{
get
{
return _HomePageCommand ??
new RelayCommand(() =>
{
_PanelPage = null; // <-- Added this
_navigationService.Navigate(typeof(HomePage));
});
}
}
private Page _PanelPage;
public Page PanelPage
{
get
{
// build page on demand
return _PanelPage ?? (_PanelPage = new Views.PanelPage());
}
set
{
_PanelPage = value;
RaisePropertyChanged("PanelPage");
}
}
}
The panel page is now created and disposed on demand.
i start using xamarin to create ios software.
in mainstoryboard, i navigate PageMain view controller to PageTo view controller.
I want navigate from PageMain view controller to PageTo view controller. i use this code and did not auto navigate:
var storyBoard = UIStoryboard.FromName ("MainStoryboard", null);
storyBoard.InstantiateViewController ("PageTo");
tried this one too but also not auto navigate :
PageMainViewController *viewController = segue. PageToViewController;
viewController.delegate = self;
tried this one too but also not auto navigate :
UIViewController pageto = new PageTo ();
pageto.Transition (PageMain, PageTo);
i know it, it easier use button to create push seque to PageTo view controller, but i did not want it.
please help me.
Another thing that you can do is push to another view controller...
AssignCaseController assignCaseController = this.Storyboard.InstantiateViewController("AssignCaseController") as AssignCaseController;
if (assignCaseController != null)
{
assignCaseController.CaseID = GetCurrentCaseID();
this.NavigationController.PushViewController(assignCaseController, true);
}
I hope this helps, I just had to do the same.
// Clear your notifications and other things
// Show the controller
// I am assuming your are using storyboards based in your use of Handle. I am also assuming the identifier in your storyboard for this is set to "WebViewController and its a custom subclass of UIViewController named WebViewController.
UIStoryboard Storyboard = UIStoryboard.FromName ("MainStoryboard", null); // Assume the name of your file is "MainStoryboard.storyboard"
var webController = Storyboard.InstantiateViewController("WebViewController") as WebViewController;
// ... set any data in webController, etc.
// Make it the root controller for example (the first line adds a paramater to it)
webController.CaseID = int.Parse(notification.UserInfo["ID"].ToString());
this.Window.RootViewController = webController;
// Note* the "Window" should already be set and created because the app is running. No need to remake it.
I am attempting to populate a grid in my MainPage with web browser items which are added in the code behind. For some reason, I cannot get the grid to remember which elements have been added already. I am unsure of how to store this information so that when the MainPage is navigated from, and then back to, the grid knows which values were previously added. To better explain, what I have is as follows:
MainPage.xaml
<Grid x:Name="BrowserHost" Grid.Row="1">
//Items will be added in code behind
</Grid>
MainPage.xaml.cs
private void ShowTab(int index)
{
this.currentIndex = index;
int count = Settings.BrowserList.Value.Count;
if (count <= 0)
{
MessageBox.Show("count <= 0");
}
if (currentIndex <= (count - 1)) //in correct range
{
// BrowserHost does not remember previously added children elements added previously when navigated back to this page and performing this check
if(!BrowserHost.Children.Contains(Settings.BrowserList.Value[currentIndex].Browser))
//Attempt to add browser if it was already added previously throws InvalidOperationException
BrowserHost.Children.Add(Settings.BrowserList.Value[currentIndex].Browser);
}
if (currentIndex > (count - 1))
{
MessageBox.Show("currentIndex > (count - 1)");
}
for (int i = 0; i < Settings.BrowserList.Value.Count; i++)
{
if (Settings.BrowserList.Value.ElementAt(i) != null)
{
Settings.BrowserList.Value.ElementAt(i).Browser.Visibility = i == this.currentIndex ? Visibility.Visible : Visibility.Collapsed;
}
}
}
Essentially, the ShowTab method is called with a specific index value everytime the MainPage loads. A check is performed to see if Settings.BrowserList.Value[currentIndex].Browser (a collection with web browser instances) exists at the currentIndex in the list (these are added in another page). If the BrowserHost grid on MainPage already contains the child element (a browser) with a particular index, then it should skip over the check and continue to show that browser instance on the screen. This is supposed to basically implement a tabbed browsing scenario for a custom web browser control I have created. My issue is, I can consecutively add new browsers to BrowserHost and show them on the screen, but when I try to call a previously added webbrowser at certain index in my collection, I get an InvalidOperationException. How would I fix this? My debugging shows that in ShowTab when I check to see if a browser has been added, regardless of it has or not, it jumps into attempting to add that browser to BrowserHost child element. If the browser has been created previously, thats when the InvalidOperationException occurs.
I don't think what you are doing is a good idea. However, the issue seems to be that the control is already parented by a different control and so you cannot add it again. Try doing the following:
var browser = Settings.BrowserList.Value[currentIndex].Browser;
if(!BrowserHost.Children.Contains(browser))
{
var grid = (Grid)browser.Parent;
if (grid != null)
{
grid.Children.Remove(browser);
}
BrowserHost.Children.Add(browser);
}
You also need to make sure that the Browser is placed in a Grid in your other form (if it's not directly inside a Grid, just wrap it in one).
This may work, but I strongly suggest you find a different way of doing it.