How to push onto Navigation Page's stack within a Tabbed Page - xamarin.forms

I am using MvvmCross and Xamarin Forms. My app's main page is a MvxTabbedPage with four tabs. Each of the tabs are Navigation Pages that contain their own Content Pages. No matter which tab is current, any Navigate call to IMvxNavigationService pushes the page onto the first tab.
A printout of Application.Current.Hierarchy() looks like this:
Application Root
HomePage(Tabbed)
[0]: MvxNavigationPage(Navigation)
[0]: FirstPage(Content)
[1]: MvxNavigationPage(Navigation)
[0]: SecondPage(Content)
[2]: MvxNavigationPage(Navigation)
[0]: ThirdPage(Content)
[3]: MvxNavigationPage(Navigation)
[0]: FourthPage(Content)
Initiating a Navigate to DetailsPage from the second tab results in this hierarchy:
Application Root
HomePage(Tabbed)
[0]: MvxNavigationPage(Navigation)
[0]: FirstPage(Content)
[1]: DetailsPage(Content)
[1]: MvxNavigationPage(Navigation)
[0]: SecondPage(Content)
[2]: MvxNavigationPage(Navigation)
[0]: ThirdPage(Content)
[3]: MvxNavigationPage(Navigation)
[0]: FourthPage(Content)
In an attempt to debug, I attached a handler to the CurrentPageChanged event on my MvxTabbedPage and I can confirm that the CurrentPage is updated correctly. I've also tried settings WrapInNavigationPage = true for the TabbedPage, but it resulted in undesirable navigation behavior.
HomePage.xaml (no Code-behind):
<?xml version="1.0" encoding="UTF-8"?>
<views:MvxTabbedPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
xmlns:mvx="clr-namespace:MvvmCross.Forms.Bindings;assembly=MvvmCross.Forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Ketchup.Core.Views"
xmlns:viewModels="clr-namespace:Ketchup.Core.ViewModels;assembly=Ketchup.Core"
x:Class="Ketchup.Core.Views.HomePage"
x:TypeArguments="viewModels:HomeViewModel"
ItemTemplate="{mvx:MvxBind DataTemplateSelector}"
ItemsSource="{mvx:MvxBind TabViewModels}"
xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
android:TabbedPage.ToolbarPlacement="Bottom"
>
</views:MvxTabbedPage>
HomePageViewModel.cs
public class HomeViewModel : MvxViewModel
{
// Bound Property
List<IMvxViewModel> _tabViewModels;
public List<IMvxViewModel> TabViewModels
{
get
{
return _tabViewModels;
}
set
{
_tabViewModels = value;
RaisePropertyChanged(() => TabViewModels);
}
}
// Bound Property
public TabDataTemplateSelector DataTemplateSelector
{
get;
set;
}
// ...
public class TabDataTemplateSelector : DataTemplateSelector
{
private IMvxViewsContainer _viewsContainer;
protected IMvxViewsContainer ViewsContainer
{
get
{
if (_viewsContainer == null)
_viewsContainer = Mvx.IoCProvider.Resolve<IMvxViewsContainer>();
return _viewsContainer;
}
}
Dictionary<IMvxViewModel, DataTemplate> Cache;
public TabDataTemplateSelector()
{
Cache = new Dictionary<IMvxViewModel, DataTemplate>();
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
Console.WriteLine("Get Data Template for Object {0}", item);
DataTemplate _dataTemplate = null;
IMvxViewModel viewModel = item as IMvxViewModel;
Type viewType;
if (item != null)
{
if (Cache.ContainsKey(viewModel))
{
Console.WriteLine("Tab in cache, returning cached data.");
_dataTemplate = Cache[viewModel];
}
else
{
viewType = ViewsContainer.GetViewType(viewModel.GetType());
Console.WriteLine("View Type {0}, ViewModel {1}", viewType, viewModel);
_dataTemplate = new DataTemplate(() =>
{
IMvxView view = (IMvxView)Activator.CreateInstance(viewType);
view.DataContext = viewModel;
Page page = (Page)view;
MvxNavigationPage navigationPage = new MvxNavigationPage(page);
navigationPage.Title = page.Title;
navigationPage.Icon = page.Icon;
return navigationPage;
});
// Cache DataTemplate
Cache[viewModel] = _dataTemplate;
}
}
return _dataTemplate;
}
}
// ... End of HomeViewModel class
Any help or pointers are greatly appreciated!
Other notes:
The tabbed page has these attributes: [MvxTabbedPagePresentation(TabbedPosition.Root, NoHistory = true, WrapInNavigationPage = false)]
The ItemSource property is a List of MvxViewModel objects.
The ItemTemplate property is a DataTemplate that instantiates the corresponding Page and wraps it in a NavigationPage.
MvvmCross 6.2.2, Xamarin Forms 3.4

Related

Xamarin Forms check is page is Modal

So basically I'm try to to find out if a page was pushed modally.
Here is the code I have for my extension method:
public static bool IsModal(this Page page)
{
return page.Navigation.ModalStack.Any(p => page == p);
}
The issue is; p never equals page due to the fact p changes to NavigationPage during runtime although intellisense reports it as a type of Page at compile time.
I've tried casting p to a Page but the type does not change at runtime and intellisense just moans that the cast is redundant.
I call this extension by using CurrentPage.IsModal in my View Model. CurrentPage is a type of Page at compile time but then changes to NavigationPage at runtime.
The confusing thing is that during debugging, p has properties such as CurrentPage and RootPage which show in the debugger, but these are not accessible by using p.CurrentPage as the compiler complains they don't exist !?! I was going to try an compare these but I can't access them but can view them in the debugger.
You need to check the type of page first, a page without navigationbar can also be pushed modally:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private async void Button_Clicked(object sender, EventArgs e)
{
Page1 p = new Page1();
await this.Navigation.PushModalAsync(p, true);
bool b = PageExtensions.IsModal(p);
Console.WriteLine(b);
}
}
public static class PageExtensions
{
public static bool IsModal(this Page page)
{
if (page.GetType() == typeof(NavigationPage))
{
return page.Navigation.ModalStack.Any(p => ((NavigationPage)p).CurrentPage.Equals(page));
}
else
{
return page.Navigation.ModalStack.Any(p => p.Equals(page));
}
}
}
So this code works:
public static class PageExtensions
{
public static bool IsModal(this Page page)
{
return page.Navigation.ModalStack.Any(p=> ((NavigationPage) p).CurrentPage.Equals(page));
}
}
I'm concerned that is not safe as it assumes p is a Type of NavigationPage.
Can you try this, there could be typos, I wrote this freehand
public static bool IsModal(this Page page)
{
if (page.Navigation.ModalStack.Count > 0)
{
foreach (var thisPage in page.Navigation.ModalStack)
{
if (thisPage.Equals(page))
return true;
}
return false;
}
else
return false;
}
This is what I made to check the last pushed modal. Hope it helps to someone.
public async Task NewModalPagePushAsync(Page pageToOpen)
{
var lastModalPage = Application.Current.MainPage.Navigation.ModalStack;
if (lastModalPage.Count >= 1)
{
if (lastModalPage.Last().GetType().Name == pageToOpen.GetType().Name)
return;
}
await Application.Current.MainPage.Navigation.PushModalAsync(pageToOpen);
}

Xamarin.Forms Google Services AdMob

I'm trying to implement AdMob in my Xamarin.Forms app (Android version for now). Here is what I have done so far:
Created a custom control, AdViewControl, in my shared project:
public class AdControlView : Xamarin.Forms.View
{
}
In my page in which to show the ad, I added the custom control in xaml:
xmlns:ads="clr-namespace:MyFeelingBuddyTwo.Views"
<ads:AdControlView BackgroundColor="Red"/>
In the Android project (AndroidManifest.xml), within :
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-myappid"/>
<activity android:name="com.google.android.gms.ads.AdActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"/>
In the Android project still, I created an AdViewRenderer:
[assembly: ExportRenderer(typeof(MyFeelingBuddyTwo.Views.AdControlView), typeof(AdViewRenderer))]
namespace MyFeelingBuddyTwo.Droid
{
class AdViewRenderer : ViewRenderer<Views.AdControlView, AdView>
{
string adUnitId = "myadunitid";
AdSize adSize = AdSize.SmartBanner;
AdView adView;
AdView CreateAdView()
{
if (adView != null)
return adView;
adView = new AdView(Forms.Context);
adView.AdSize = adSize;
adView.AdUnitId = adUnitId;
var arParams = new LinearLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
adView.LayoutParameters = arParams;
adView.LoadAd(new AdRequest.Builder().Build());
return adView;
}
protected override void OnElementChanged(ElementChangedEventArgs<AdControlView> e)
{
base.OnElementChanged(e);
if(Control == null)
{
CreateAdView();
SetNativeControl(adView);
}
}
}
}
In MainActivity, intialize MobileAds just before loading the app:
MobileAds.Initialize(ApplicationContext, "ca-app-pub-appid");
When I run, I get the red background but no ads are loaded. Any ideas?
In the AdControlView class, I added :
public static readonly BindableProperty AdUnitIdProperty = BindableProperty.Create("AdUnitId", typeof(string), typeof(AdControlView));
public string AdUnitId
{
get { return (string)GetValue(AdUnitIdProperty); }
set { SetValue(AdUnitIdProperty, value); }
}
Now I can see "Test Ad" in the banner placeholder.

Getting navigation bar height in dependency service - Xamarin Forms

i have this issue wherein i need to get the navigation bar height in my Dependency Service.
Currently I am stuck on what to follow here. I tried everything i find in stackoverflow and google but no one works for me.
Heres my code:
[assembly: Dependency(typeof(DeviceInfo))]
namespace Wicket.App.Mobile.iOS.Framework
{
public class DeviceInfo : IDeviceInfo
{
public float StatusBarHeight => (float)UIApplication.SharedApplication.StatusBarFrame.Size.Height;
public float NavigationBarHeight => GetNavigationBarHeight();
public static UINavigationController NavigationController { get; set; }
public float GetNavigationBarHeight()
{
//Get navigation bar height
return 0;
}
}
}
I already completed the android part and it works good. The only problem now is in iOS. I have tried getting the instance of navigationcontroller in AppDelegate so that I can just get the bar frame like this NavigationBar.Bounds.Height;
I think this should work:
var navheight = GetTopViewController().NavigationController.NavigationBar.Frame.Height;
public static UIViewController GetTopViewController()
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
vc = vc.PresentedViewController;
if (vc is UINavigationController navController)
vc = navController.ViewControllers.Last();
return vc;
}
Solution:
How about pass an instance of viewController as parameter in the function inside the IDeviceInfo?
Try this:
public void getNaviHeight(ContentPage vc)
{
var renderer = Platform.GetRenderer(vc);
if (renderer == null)
{
renderer = RendererFactory.GetRenderer(vc);
Platform.SetRenderer(vc, renderer);
}
var viewController = renderer.ViewController;
var h = viewController.NavigationController?.NavigationBar.Frame.Height;
}
And use the dependency:
public MainPage ()
{
DependencyService.Get<IDeviceInfo>().getNaviHeight(this);
}
this worked to me:
var navigationBar = UIApplication.SharedApplication.KeyWindow.RootViewController.View.Subviews[0].Subviews.OfType<UINavigationBar>().FirstOrDefault();
if(navigationBar != null)
{
// continue here...
}

How to make MarkupExtension visible in Xaml

I am working on a project using Xamarin.Forms.
I have a MarkupExtension for text translations defined in PCL which is
public (so should be visible outside the PCL) and
there is also a static class for PCL initialization and there is a call to its Init method in App contructor
Extension code:
[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
readonly CultureInfo ci;
const string ResourceId = "GymHeroViews.Resources.AppResources";
public TranslateExtension()
{
ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo();
}
public string Text { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Text == null)
return "";
ResourceManager resmgr = new ResourceManager(ResourceId
, typeof(TranslateExtension).GetTypeInfo().Assembly);
var translation = resmgr.GetString(Text, ci);
if (translation == null)
{
#if DEBUG
throw new ArgumentException(
String.Format("Key '{0}' was not found in resources '{1}' for culture '{2}'.", Text, ResourceId, ci.Name),
"Text");
#else
translation = Text;
#endif
}
return translation;
}
}
Extension is included in GymHero.Common namespace. And output dll's name is GymHero.Common.dll.
In Xaml file I have defined a namespace:
xmlns:t="clr-namespace:GymHero.Common;assembly:GymHero.Common"
Then I have defined a Button:
<Button x:Name="Excersise1GridButton" Text="{t:Translate Excersise1GridButton}" Grid.Row="0" Grid.Column="1" />
For me, everything looks as it should but I get the exception:
Xamarin.Forms.Xaml.XamlParseException: Type TranslateExtension not
found in xmlns clr-namespace:GymHero.Common;assembly:GymHero.Common
I would appreciate any help. Thanks in advance.
Tomasz

Binding with Map API Extensions for Windows Phone 8

I'm trying to use databinding with the map api extension of windows phone toolkit. I'm doing :
<maps:Map x:Name="Map" Center="47.6, -122.3" ZoomLevel="12">
<maptk:MapExtensions.Children>
<maptk:MapItemsControl ItemsSource="{Binding PositionList}">
<maptk:MapItemsControl.ItemTemplate>
<DataTemplate>
<maptk:Pushpin GeoCoordinate="{Binding}" />
</DataTemplate>
</maptk:MapItemsControl.ItemTemplate>
</maptk:MapItemsControl>
</maptk:MapExtensions.Children>
</maps:Map>
with my code behind :
public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string name= null)
{
if (object.Equals(variable, valeur)) return false;
variable = valeur;
NotifyPropertyChanged(name);
return true;
}
private IEnumerable<GeoCoordinate> positionList;
public IEnumerable<GeoCoordinate> PositionList
{
get { return positionList; }
set { NotifyPropertyChanged(ref positionList, value); }
}
public MainPage()
{
InitializeComponent();
PositionList = new List<GeoCoordinate>
{
new GeoCoordinate(47.6050338745117, -122.334243774414),
new GeoCoordinate(47.6045697927475, -122.329885661602),
new GeoCoordinate(47.605712890625, -122.330268859863),
new GeoCoordinate(47.6015319824219, -122.335113525391),
new GeoCoordinate(47.6056594848633, -122.334243774414)
};
DataContext = this;
}
}
But I can't see any pushpin on the map :(
What am I doing wrong ?
Note that If I use this in the code-behind file, it's working
MapExtensions.GetChildren(Map).OfType<MapItemsControl>().First().ItemsSource = PositionList;
Thanks in advance for your help,
Best regards
MapItemsControl derives from DependencyObject, not FrameworkElement so the DataContext does not propagate. Long story long... you can't data bind MapItemsControl from XAML unless you have some way to set the Source property of the Binding.
If the FindAncestor mode of RelativeSource worked on the phone, it might be possible to work around this but it apparently does not. This leaves us with either creating the binding in code or (more realistically) setting the ItemsSource in code.

Resources