Turning Entry’s Placeholder into Title in Xamarin Forms

I really hope that this title is not misleading. I’m not sure ‘turning’ is the right word, but I don’t know better word to describe what I want to discuss in this post. As we know together, in Android Native app, we can have Entry component whose placeholder will turn into title with a little ‘going up’ animation when user start interact with it. And because you can only have it in Android, not in other platform, Xamarin Forms doesn’t support it, at least not yet. Unfortunately, in my current project I’m already required to create such a thing. So, this is how I do the work around, not perfect yet but not too bad either.

Custom Entry with Title

The first thing I do is creating a custom content view with a stack layout in it. The stack layout contains an Entry and a Label that will serve as Entry’s title. Why stack layout, not Grid? It’s not just for simplicity’s sake. Remember the ‘going up’ animation I discuss earlier? I want to create an effect like that, not as good as the original, but I think it’s good enough. When you hide something in stack layout and something trigger it to appear, it will create a little animation, up or down depend on the position. It also works the other way around, when you hide something, you’ll get similar animation. So, this how I put the Entry and Label on the xaml file.

TitledEntryCode1

Pay the on the Entry. It has event handler for Focused and Unfocused event, that where we gonna do the trick. And the code below is how the code behind looks like.

public partial class TitledEntry : ContentView
{
    string placeholder = string.Empty;

    public TitledEntry()
    {
        InitializeComponent();

        if (EntryContent.Text == null || EntryContent.Text.Length == 0)
        {
            LabelTitle.IsVisible = false;
        }

        EntryContent.BindingContext = this;
        LabelTitle.BindingContext = this;
    }

    public static BindableProperty PlaceholderProperty =
        BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(TitledEntry), null, BindingMode.TwoWay);

    public string Placeholder
    {
        get => (string)GetValue(PlaceholderProperty);
        set => SetValue(PlaceholderProperty, value);
    }

    void Handle_Focused(object sender, FocusEventArgs e)
    {
        LabelTitle.IsVisible = true;

        if (EntryContent.Text == null || EntryContent.Text.Length == 0)
        {
            placeholder = EntryContent.Placeholder;
            EntryContent.Placeholder = string.Empty;
        }

    }

    void Handle_Unfocused(object sender, FocusEventArgs e)
    {
        if (EntryContent.Text == null || EntryContent.Text.Length == 0)
        {
            EntryContent.Placeholder = placeholder;
            LabelTitle.IsVisible = false;
        }

    }
}

So all I do is just giving condition about Entry’s text length and stack layout will do the rest. If there’s one thing you need to remember is setting the binding context of the Entry and Label to current class like I do in the constructor or you won’t be able to bind them from view model.

Let’s use it

Now that we already have our custom entry ready, let see it in action. I create a view model contains 2 strings called Username and Email and I set a view model as the binding context for the class whose xaml file you can see below.

TitledEntryCode2

And if try to run the solution, you’ll have Entries like the real Android’s Entry.

 

Update : Making Enum as Bindable Property

Sample Code is available in my Github repo

 

Get to know about iOS Web Kit Web View

Recently I’ve been informed that I want to display Web View on iOS application, I actually have 3 options. The first, the most common one, using UI Web View. I’ve been using it in all my project whenever I need¬† to display a web view, whether Swift project or Xamarin. Second one is Web Kit Web View or WK Web View in short. WK Web View has similar functionalities and methods like UI Web View but it designed to overcome UI Web View weakness when compiling Java Script. Third is Safari View Controller. It can be said that you put Safari Browser inside your app. The purpose is your user doesn’t need to navigate outside your app when you want to display a web. It’s about maintaining user engagement to your app. I already posted twice about UI Web View and not so much I can discuss about Safari, so in this post I want to discuss about WK Web View and how to migrate from UI Web View to WK Web View in Xamarin Forms.

Hybrid Web View

If you look up about Hybrid Web View on internet, the first link you get will directly navigate you to Xamarin documentation about how to use WK Web View. For starter I will follow through the example that documentation provides. So, here my code for my Hybrid WK Web View.

public class HybridWebKitWebView : View
{
    Action action;
    public static readonly BindableProperty UriProperty = BindableProperty.Create(
      propertyName: "Uri",
      returnType: typeof(string),
      declaringType: typeof(HybridWebKitWebView),
      defaultValue: default(string));

    public string Uri
    {
        get { return (string)GetValue(UriProperty); }
        set { SetValue(UriProperty, value); }
    }

    public void RegisterAction(Action callback)
    {
        action = callback;
    }

    public void Cleanup()
    {
        action = null;
    }

    public void InvokeAction(string data)
    {
        if (action == null || data == null)
        {
            return;
        }
       action.Invoke(data);
    }
}

Renderer and Delegate

Still from documentation, here’s the code for WK Web View Renderer, It’s pretty much the same, I just get rid some codes because I wanna load from internet not local content.

public class HybridWKWebViewRenderer: ViewRenderer
{
    WKUserContentController userController;

    protected override void OnElementChanged(ElementChangedEventArgs e)
    {
        base.OnElementChanged(e);

        if (Control == null)
        {
            userController = new WKUserContentController();

            var config = new WKWebViewConfiguration { UserContentController = userController };
            var webView = new WKWebView(Frame, config);
            webView.NavigationDelegate = new HybridWKWebViewDelegate(this);
            SetNativeControl(webView);
        }
        if (e.OldElement != null)
        {
            var hybridWebView = e.OldElement as HybridWebKitWebView;
            hybridWebView.Cleanup();
        }
        if (e.NewElement != null)
        {
            Control.LoadRequest(new NSUrlRequest(new NSUrl(Element.Uri)));
        }
    }
}

Next is the Delegate class. The delegate class of WK Web View is actually pretty much the same with Delegate class for UI Web View. So, if you want to migrate from UI Web View to WK Web View, it won’t be too difficult. It just has different class names. Here’s some important methods and their ‘new name’ in WK Web View

  • LoadStarted became DidStartProvisionalNavigation
  • LoadFailed became DidFailProvisionalNavigation
  • LoadingFinished became DidFinishNavigation
  • ShouldStartLoad became DecidePolicy

They don’t just have different names but different parameters as well, so here’s the example of empty Delegate Class of WK Web View.

public class HybridWKWebViewDelegate: WKNavigationDelegate
{
    HybridWKWebViewRenderer hybridWebViewRenderer;
    public HybridWKWebViewDelegate(HybridWKWebViewRenderer _webViewRenderer = null)
    {
        hybridWebViewRenderer = _webViewRenderer ?? new HybridWKWebViewRenderer();
    }

    [Export("webView:didStartProvisionalNavigation:")]
    public override void DidStartProvisionalNavigation(WKWebView webView, WKNavigation navigation)
    {

    }

    [Export("webView:didFailProvisionalNavigation:")]
    public override void DidFailProvisionalNavigation(WKWebView webView, WKNavigation navigation, NSError error)
    {

    }

    [Export("webView:didFinishNavigation:")]
    public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
    {

    }

    [Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
    public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler)
    {

    }
}

Decide Policy

Let’s dive little bit deeper about DecidePolicy Method because it’s works little bit different compare to ShouldStartLoad method. When you load a web page in WK Web View, this method is the first one who get triggered. In this method, you can decide whether you want to allow the web view to load the web page or not.

But to handle that kind of event, first you need to know what Navigation Type that the web page using. There’re six of them and you need to handle the navigation activities based on them. Here’s the simple sample of it.

[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler)
{
    switch (navigationAction.NavigationType)
    {
        case WKNavigationType.BackForward:
            decisionHandler(WKNavigationActionPolicy.Allow);
            break;
        case WKNavigationType.FormResubmitted:
            decisionHandler(WKNavigationActionPolicy.Cancel);
            break;
        case WKNavigationType.FormSubmitted:
            decisionHandler(WKNavigationActionPolicy.Allow);
            break;
        case WKNavigationType.LinkActivated:
            decisionHandler(WKNavigationActionPolicy.Cancel);
            break;
        case WKNavigationType.Other:
            decisionHandler(WKNavigationActionPolicy.Allow);
            break;
        case WKNavigationType.Reload:
            decisionHandler(WKNavigationActionPolicy.Cancel);
            break;
    }
}

From the names, I believe you can guess what kind Navigation Type are them. The only one which little bit unclear maybe the Other type. Well, so far I only meet this type twice. First, when the web view load the url that I set for the first time. So, everything will be started from this type before the web view navigate to other page. The second one I will discuss in the next section.

Handle Button Click Event

For example how to use Delegate class in WK Web View, I will use example of what I’ve done in UI Web View. First is about handling button click event. Here’s how I did it using WK Web View Delegate class. The navigation type I used is Other.

[Export("webView:didFinishNavigation:")]
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
    // find the particular button
    string funcurl = string.Empty;
    funcurl = "var btn1 =  document.getElementsByClassName('btn-primary')[1]; if(btn1 != null){btn1.addEventListener('click', function() { window.location = \"dosomething\"; }); }" +
              "var btn2 =  document.getElementsByClassName('btn-primary')[2]; if(btn2 != null){reject.addEventListener('click', function() { window.location = \"dosomethingelse\"; }); }";

    webView.EvaluateJavaScript("javascript: r(function(){" + funcurl + "});", null);
}

[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler)
{
    switch (navigationAction.NavigationType)
    {

        case WKNavigationType.Other:

            var request = navigationAction.Request;

            if (request.Url.ToString().Contains("dosomething"))
            {
                // do something here
                decisionHandler(WKNavigationActionPolicy.Cancel);
            }
            else if (request.Url.ToString().Contains("dosomethingelse"))
            {
                // do something else here
                decisionHandler(WKNavigationActionPolicy.Cancel);
            }
            break;
        default:
            decisionHandler(WKNavigationActionPolicy.Allow);
            break;
    }
}

Adding Authorization Header

Second example is about Adding Authorization Header. I think adding this header suppose to in every type of navigation. But, in this example I will just add it in two most common navigation types, Other and LinkActivated. Link Activated is navigation type when user clink a link in a web page. So, here’s how it’s done.

[Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler)
{
    switch (navigationAction.NavigationType)
    {
        case WKNavigationType.LinkActivated:
            var request = navigationAction.Request;

            if (!request.Headers.ContainsKey(new NSString("Authorization")))
            {
                var copy = request.MutableCopy() as NSMutableUrlRequest;
                var token = "";

                NSMutableDictionary dic = new NSMutableDictionary();
                dic.Add(new NSString("Authorization"), new NSString("Bearer " + token));
                copy.Headers = dic;

                string currentUrl = request.Url.ToString();

                webView.LoadRequest(copy);
                decisionHandler(WKNavigationActionPolicy.Cancel);
            }
            else
            {
                decisionHandler(WKNavigationActionPolicy.Allow);
            }

            break;

        case WKNavigationType.Other:

           request = navigationAction.Request;

            if (!request.Headers.ContainsKey(new NSString("Authorization")))
            {
                var copy = request.MutableCopy() as NSMutableUrlRequest;
                var token = "";

                NSMutableDictionary dic = new NSMutableDictionary();
                dic.Add(new NSString("Authorization"), new NSString("Bearer " + token));
                copy.Headers = dic;

                string currentUrl = request.Url.ToString();

                webView.LoadRequest(copy);
                decisionHandler(WKNavigationActionPolicy.Cancel);
            }
            else
            {
                decisionHandler(WKNavigationActionPolicy.Allow);
            }

             break;
        default:
            decisionHandler(WKNavigationActionPolicy.Allow);
            break;
    }

}