I order to apply some navigationBar properties (like as the background image) for different page, I think to have a condition on my custom NavigationRenderer.
My idea is to have some condition like (in my working code)
public class CustomNavigationRenderer : NavigationRenderer
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
if (pagePushed is 1)
{
NavigationBar.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
NavigationBar.ShadowImage = new UIImage();
}
else (ahother page){
var img = UIImage.FromBundle("MyImage");
NavigationBar.SetBackgroundImage(img, UIBarMetrics.Default);
}
}
}
that allows me to have at least a condition to apply a different navigation properties. Another way is to have 2 Navigationrenderer class but I think is not possible.
Any idea to how do that?
If you look at the source code for NavigationRenderer here, you will notice there are quite a few methods and callbacks you can take advantage of.
I would suggest you can do something like this:
1) Code for your custom NavigationRenderer (iOS project, you will have to do something similar on Android):
using System.Threading.Tasks;
using MyProject.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavRenderer))]
namespace MyProject.iOS
{
public class NavRenderer : NavigationRenderer
{
protected override async Task<bool> OnPushAsync(Page page, bool animated)
{
var result = await base.OnPushAsync(page, animated);
if(result)
{
if (page is IMyPageType1)
{
NavigationBar.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
NavigationBar.ShadowImage = new UIImage();
}
else if(page is IMyPageType2)
{
var img = UIImage.FromBundle("MyImage");
NavigationBar.SetBackgroundImage(img, UIBarMetrics.Default);
}
}
return result;
}
}
}
2) Based on the code above, you need to add two interfaces. These should be located in the same project / dll where your Pages are located (all your Xamarin.Forms UI):
public interface IMyPageType1
{
}
public interface IMyPageType2
{
}
3) Now everything that's remaining is implement the interfaces on the pages where you need it. For example:
public partial class MyPage1 : ContentPage, IMyPageType1
{
//...
}
From here, possibilities are endless! You can add for example a method to IMyPageType1 that would return a color, and then inside your renderer, once you know the page being pushed is implementing IMyPageType1, you can call the method and get the color to use.
Related
Please let me re-stress, I am on MvvmCross 6.4.2. I'm currently upgrading a very old project.
I'm getting the error "Trying to show a page without a PageViewController, this is not possible!". According to the source, this is because my PageViewController is null.
I'm not sure why this is, because I've set my pager views and viewmodels up like in the playground sample (the big change in 6.4.2. appears to add attribute support for PageViewControllers).
The PageViewController I want to show (embedded in another ViewController via Container View, for clarity):
[MvxFromStoryboard("Main")]
//[MvxRootPresentation(WrapInNavigationController = true)]
public partial class MyPageViewController : MvxPageViewController<MyPagingViewModel>
And my first page:
[MvxPagePresentation(WrapInNavigationController = false)]
public partial class Page1View : MvxViewController<Page1ViewModel>
{
public Page1View() : base()
{
}
public Page1View(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
UIButton myButton = new UIButton(UIButtonType.System);
myButton.Frame = new CGRect(25, 25, 300, 150);
myButton.SetTitle("Hello, World!", UIControlState.Normal);
}
}
And my pages ViewModel:
public class Page1ViewModel : MvxNavigationViewModel
{
public Page1ViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
{
}
}
I override ShowPageViewController in my custom MvxIosViewPresenter to see what variables are passed in:
protected override Task<bool> ShowPageViewController(UIViewController viewController, MvxPagePresentationAttribute attribute, MvxViewModelRequest request)
The viewController is of type Page1View. Shouldn't it be MyPageViewController though if the method is called ShowPageViewController?
I use a command to navigate to the pages, just like in the playground:
private Task ShowInitialViewModels()
{
var tasks = new List<Task>();
tasks.Add(NavigationService.Navigate<Page1ViewModel>());
return Task.WhenAll(tasks);
}
So what could be going wrong?
This is my code
ToolbarItems.Add(new ToolbarItem("User", "userAvatar.png", async () => {
await Navigation.PopToRootAsync();
}));
It's not working. It's place a masked single color image instead a png in colors.
I'm trying to archive something like this...
Any clue ?
I was going mad about this issue once, too (my situation was a bit more subtle, I had a plain and a colored verion of the icon and was wondering why the heck the colored icon would not be loaded) and unfortunately it's not that easy to overcome.
The icons being monochrome is the default behavior for iOS apps and Xamarin.Forms implements this behavior. According to this post you'll need a custom renderer to show colored icons in the navigation bar.
Edit
According to this post, you'll have to set the UIImageRenderingMode for the respective images in your custom renderer
image = image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
using the renderer implementation from this answer, it should be something in the line of
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyProject.iOS.Renderers.IosMainMenuRenderer))]
namespace MyProject.iOS.Renderers {
public class IosMainMenuRenderer : TabbedRenderer {
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
var items = TabBar.Items;
for (var i = 0; i < items.Length; i++) {
items[i].Image = items[i].ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
items[i].SelectedImage = items[i].SelectedImage.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
}
}
}
}
but I have not tested this!
For a color logo on the right on all my navigation pages I used this custom renderer:
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationRenderer))]
namespace App.iOS
{
public class CustomNavigationRenderer : NavigationRenderer
{
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
var logo = NavigationBar.TopItem.RightBarButtonItem.Image;
if (logo == null) return;
if (logo.RenderingMode == UIImageRenderingMode.AlwaysOriginal)
{
return;
}
NavigationBar.TopItem.RightBarButtonItem.Image = logo.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
}
}
}
The easiest way is to not use a toolbar, instead, set the type of this page as a Modal when navigating to it(Using Navigation.PushModal()). And add a horizontal LinearLayout that will act as the toolbar.
I'm trying to use a library that doesn't has a .Net SDK, but as I want to use it only to return a string, I thought I could use it's JS SDK by creating a custom WebView that returns strings (https://xamarinhelp.com/xamarin-forms-webview-executing-javascript/).
The first problem that I faced was that a CustomRenderer is not called in Xamarin.Forms until the View is added to a Page (or at least I couldn't make it be called). To fix this I added a call to Platform.CreateRenderer in each platform.
It did the trick and the CustomRenderer executed. But when I tried to call a JS function to retrieve a string, the app just hung and stayed that way.
I didn't try to insert the WebView in a Page because I want it to be independent of the page that the app is current on, and as I want a "code-only" html, I don't see the point of adding it somewhere.
My classes:
JSEvaluator
namespace MyNamespace.Views
{
public class JSEvaluator : WebView
{
public static BindableProperty EvaluateJavascriptProperty = BindableProperty.Create(nameof(EvaluateJavascript), typeof(Func<string, Task<string>>), typeof(JSEvaluator), null, BindingMode.OneWayToSource);
public Func<string, Task<string>> EvaluateJavascript
{
get { return (Func<string, Task<string>>)GetValue(EvaluateJavascriptProperty); }
set { SetValue(EvaluateJavascriptProperty, value); }
}
public JSEvaluator()
{
}
}
}
UWP Renderer
[assembly: ExportRenderer(typeof(JSEvaluator), typeof(JSEvaluatorRenderer))]
namespace MyNamespace.UWP.Renderers
{
public class JSEvaluatorRenderer : WebViewRenderer
{
public JSEvaluatorRenderer() { }
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
var webView = e.NewElement as JSEvaluator;
if (webView != null)
webView.EvaluateJavascript = async (js) =>
{
return await Control.InvokeScriptAsync("eval", new[] { js });
};
}
}
}
Creation and use
if (jsEvaluator == null)
{
jsEvaluator = new JSEvaluator { Source = new HtmlWebViewSource { Html = HTML.html } };
#if __ANDROID__
Xamarin.Forms.Platform.Android.Platform.CreateRenderer(jsEvaluator);
#elif __IOS__
Xamarin.Forms.Platform.iOS.Platform.CreateRenderer(jsEvaluator);
#elif WINDOWS_UWP
Xamarin.Forms.Platform.UWP.Platform.CreateRenderer(jsEvaluator);
#endif
}
Thanks for the help :)
I had to add the WebView to a page, as #SushiHangover said in the comment. With this done, it worked as expected.
The script used to be:
function OnMouseEnter()
{
renderer.material.color = Color.grey;
}
But using that is now obsolete after an update and I have no idea what the current syntax is or how one would go about finding it out. I've searched everywhere and couldn't find an answer.
Since Unity 4.6 there is a new way of handling input events. One have to use interfaces from UnityEngine.EventSystems namespace. Look at this example:
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems; // dont forget this
public class SomeController : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
private bool hovered = false;
// from IPointerEnterHandler
public void OnPointerEnter(PointerEventData eventData)
{
hovered = true;
}
// from IPointerExitHandler
public void OnPointerExit(PointerEventData eventData)
{
hovered = false;
}
// from IPointerClickHandler
public void OnPointerClick(PointerEventData eventData)
{
// send some event
}
}
Still, you have to add collider component to your object.
I have a Xamarin.Forms.ListView that contains events that are grouped by date. There are events that occur in future and events that occur in the past.
Users would like to have their screen load with a future event closest to the current date in view so that they do not need to manually scroll down to view it.
What options do I have with a Xamarin.Forms.ListView to accomplish this for iOS and Android users?
I have made some progress. I am able to accomplish my goal in iOS by creating a CustomListView and an iOS render to support it.
In Xamarin.Forms you create a CustomListView then after you have loaded the list you an call ScrollToRow(item,section) to manually scroll to the row you need.
In iOS the renderer maps the method to UITableView message ScrollToRow(...);
For Android I still need to create the renderer but I do know that I need to map to the calls getListView().setSelection(...); or getListView().smoothScrollToPosition(...);
I am sure there is a more elegant way to do this but for now it is getting the job done
Source For: Common.CustomListView
using System;
using Xamarin.Forms;
namespace Common {
public class CustomListView : ListView {
public Action<int, int, bool> ScrollToRowDelegate { get; set; }
public void ScrollToRow(int itemIndex, int sectionIndex = 0, bool animated = false) {
if (ScrollToRowDelegate != null) {
ScrollToRowDelegate (itemIndex, sectionIndex, animated);
}
}
}
}
iOS Renderer Source:YourApplication.iOS.Renderers.CustomListViewRenderer
using System;
using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;
using Common;
using MonoTouch.UIKit;
using MonoTouch.Foundation;
using YourApplication.iOS.Renderers;
[assembly: ExportRenderer (typeof(CustomListView), typeof(CustomListViewRenderer))]
namespace YourApplication.iOS.Renderers
{
public class CustomListViewRenderer : ListViewRenderer
{
protected override void OnModelSet (VisualElement view) {
base.OnModelSet (view);
var listView = view as CustomListView;
listView.ScrollToRowDelegate = (itemIndex, sectionIndex, animated) => {
ScrollToRow(itemIndex, sectionIndex, animated);
};
}
private void ScrollToRow(int itemIndex, int sectionIndex, bool animated) {
var tableView = this.Control as UITableView;
var indexPath = NSIndexPath.FromItemSection (itemIndex, sectionIndex);
tableView.ScrollToRow (indexPath, UITableViewScrollPosition.Top, animated);
}
}
}