Xamarin.Forms – Mock Locations

This Project you can find at: https://github.com/officialdoniald/Xamarin.Forms.MockLocation.

Forms:

We can detect Location changing in the Forms Project: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile/MainPage.xaml.cs. If we will get a Mock Location this will detect it.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Essentials;
using Xamarin.Forms;

namespace Xamarin.Forms.MockLocation.Mobile
{
    // Learn more about making custom code visible in the Xamarin.Forms previewer
    // by visiting https://aka.ms/xamarinforms-previewer
    [DesignTimeVisible(false)]
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            Thread thread = new Thread(async () =>
            {
                while (true)
                {
                    var request = new GeolocationRequest(GeolocationAccuracy.Medium);
                    var location = await Geolocation.GetLocationAsync(request);

                    if (location != null)
                    {
                        if (location.IsFromMockProvider)
                        {
                            //Detect mocking location
                        }
                        else
                        {
                            //Detect normal location
                        }
                    }
                }
            });
                    }
    }
}

The two platforms need to be discussed separately. In Android we can make an Implementation for Mock Location, but in iOS, we have to use a script file and we have to add to the emulator, so we can’t make this programmatically.

Android:

First, we have to request permissions in the Manifest and in the MainActivity (Runtime).

Manifest: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile.Android/Properties/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.xamarin.forms.mocklocation.mobile">
  <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
  <application android:label="Xamarin.Forms.MockLocation.Mobile.Android"></application>
  
  <uses-permission android:name="android.permission.PRIORITY_HIGH_ACCURACY" />
  <uses-feature android:name="android.hardware.location" android:required="false" />
  <uses-feature android:name="android.hardware.location.gps" android:required="false" />
  <uses-feature android:name="android.hardware.location.network" android:required="false" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
  <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

MainActivity: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile.Android/MainActivity.cs

protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);

            Context = this.ApplicationContext;

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

            ActivityCompat.RequestPermissions(this, new String[] { Manifest.Permission.AccessFineLocation }, 1);
            
            LoadApplication(new App());
        }

Allright! We have to send the mock(test) location via LocationManager. Let’s create an interface in the Forms Project and call the Android implementation via DependencyService.

Forms: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Implementation/IMockLocationProvider.cs.

namespace Xamarin.Forms.MockLocation.Implementation
{
    public interface IMockLocationProvider
    {
        void SendMockLocation(MockPosition mockPosition);
    }

    public class MockPosition
    {
        public double Longitude { get; set; }

        public double Latitude { get; set; }

        /// <summary>
        /// Default value: 1.0.
        /// </summary>
        public double Altitude { get; set; } = 1.0;
    }
}

Android: https://github.com/officialdoniald/Xamarin.Forms.MockLocation/blob/master/Xamarin.Forms.MockLocation.Mobile/Xamarin.Forms.MockLocation.Mobile.Android/Implementations/MockLocationProvider.cs.

using System;
using Android.Content;
using Android.Hardware;
using Android.Locations;
using Xamarin.Forms.MockLocation.Implementation;
using Xamarin.Forms.MockLocation.Mobile.Droid.Implementations;

[assembly: Xamarin.Forms.Dependency(typeof(MockLocationProvider))]
namespace Xamarin.Forms.MockLocation.Mobile.Droid.Implementations
{
    public class MockLocationProvider : IMockLocationProvider
    {
        public void SendMockLocation(MockPosition position)
        {
            Location location = new Location(LocationManager.GpsProvider)
            {
                Latitude = position.Latitude,
                Longitude = position.Longitude,
                Altitude = position.Altitude,
                Time = DateTime.Now.Ticks,
                ElapsedRealtimeNanos = 100,
                Speed = 0.0f,
                Bearing = 0.0f,
                Accuracy = 0
            };

            LocationManager locationManager = MainActivity.Context.GetSystemService(Context.LocationService) as LocationManager;

            locationManager.AddTestProvider(LocationManager.GpsProvider, false, false, false, false, false, false, false, Power.Low, SensorStatus.AccuracyHigh);
            locationManager.SetTestProviderLocation(LocationManager.GpsProvider, location);
            locationManager.SetTestProviderEnabled(LocationManager.GpsProvider, true);
        }
    }
}

Deploy your app to the phone and close it.

One more important thing: only the System app can send mock locations, so we have to call for the operation system, that this will send mock locations. So if you haven’t already, let’s enable developer mode on your phones and go to developer options.

Select the Mock Location, and select you app. Run the app again and it will work.

iOS:

So, as above said, we can mock the Location programmatically. We have to create a script fiel and add it to the Emulator. Here is a good website for the gpx file generating: https://www.gpxgenerator.com/.

<?xml version="1.0"?>
<gpx version="1.1" creator="gpxgenerator.com">
<wpt lat="50.84633694747007" lon="4.364619255065918">
    <time>2017-02-10T10:48:50Z</time>
</wpt>
<wpt lat="50.845889847299254" lon="4.36431884765625">
    <time>2017-02-10T10:49:28Z</time>
</wpt>
<wpt lat="50.84507692692001" lon="4.363868236541748">
    <time>2017-02-10T10:50:36Z</time>
</wpt>
<wpt lat="50.84460271682405" lon="4.363675117492676">
    <time>2017-02-10T10:51:14Z</time>
</wpt>
<wpt lat="50.84374912650492" lon="4.363245964050293">
    <time>2017-02-10T10:52:25Z</time>
</wpt>
<wpt lat="50.84296326764228" lon="4.36281681060791">
    <time>2017-02-10T10:53:31Z</time>
</wpt>
<wpt lat="50.84281578200512" lon="4.36181902885437">
    <time>2017-02-10T10:54:22Z</time>
</wpt>
<wpt lat="50.84310709422066" lon="4.360542297363281">
    <time>2017-02-10T10:55:30Z</time>
</wpt>
<wpt lat="50.84268028733167" lon="4.359984397888184">
    <time>2017-02-10T10:56:13Z</time>
</wpt>
<wpt lat="50.84245672026023" lon="4.359222650527954">
    <time>2017-02-10T10:56:55Z</time>
</wpt>
<wpt lat="50.843019023277556" lon="4.35808539390564">
    <time>2017-02-10T10:58:07Z</time>
</wpt>
<wpt lat="50.84339840461739" lon="4.357527494430542">
    <time>2017-02-10T10:58:48Z</time>
</wpt>
<wpt lat="50.84356777029096" lon="4.357130527496338">
    <time>2017-02-10T10:59:12Z</time>
</wpt>
<wpt lat="50.844069089081536" lon="4.356368780136108">
    <time>2017-02-10T11:00:07Z</time>
</wpt>
<wpt lat="50.84437550047113" lon="4.3559181690216064">
    <time>2017-02-10T11:00:40Z</time>
</wpt>
<wpt lat="50.84555269015878" lon="4.354877471923828">
    <time>2017-02-10T11:02:27Z</time>
</wpt>
<wpt lat="50.84620302096338" lon="4.354770183563232">
    <time>2017-02-10T11:03:18Z</time>
</wpt>
<wpt lat="50.8467178597545" lon="4.354383945465088">
    <time>2017-02-10T11:04:03Z</time>
</wpt>
<wpt lat="50.84665428412764" lon="4.3536436557769775">
    <time>2017-02-10T11:04:40Z</time>
</wpt>
<wpt lat="50.84662380036855" lon="4.353042840957642">
    <time>2017-02-10T11:05:10Z</time>
</wpt>
<wpt lat="50.846430736098434" lon="4.3524473905563354">
    <time>2017-02-10T11:05:43Z</time>
</wpt>
<wpt lat="50.846213961280625" lon="4.351991415023804">
    <time>2017-02-10T11:06:11Z</time>
</wpt>
<wpt lat="50.84603783095749" lon="4.3516212701797485">
    <time>2017-02-10T11:06:34Z</time>
</wpt>
<wpt lat="50.845837989977895" lon="4.351256489753723">
    <time>2017-02-10T11:06:58Z</time>
</wpt>
<wpt lat="50.84563476098514" lon="4.350859522819519">
    <time>2017-02-10T11:07:23Z</time>
</wpt>
<wpt lat="50.84547556432245" lon="4.350618124008179">
    <time>2017-02-10T11:07:40Z</time>
</wpt>
<wpt lat="50.84531975429677" lon="4.350387454032898">
    <time>2017-02-10T11:07:56Z</time>
</wpt>
<wpt lat="50.845167330942075" lon="4.350119233131409">
    <time>2017-02-10T11:08:14Z</time>
</wpt>
<wpt lat="50.84499536286211" lon="4.3499743938446045">
    <time>2017-02-10T11:08:29Z</time>
</wpt>
</gpx>

If we get a gpx file, we have to script the Emulator. Open the AppleScript App on the Mac and create a new script:

on replace_chars(this_text, search_string, replacement_string)
    set AppleScript's text item delimiters to the search_string
    set the item_list to every text item of this_text
    set AppleScript's text item delimiters to the replacement_string
    set this_text to the item_list as string
    set AppleScript's text item delimiters to ""
    return this_text
end replace_chars
 
on run arg
--> arg is a collection of the command line parameters
    
    set xmlPathParam to item 1 of arg --> first param is the location of the gpx file
    set sleepParam to item 2 of arg --> second param is the time (seconds) to sleep between each iteration
 
    tell application "System Events"
        tell XML file xmlPathParam
            tell XML element "gpx"
                set wptElements to every XML element whose name = "wpt" -->put wpt elements in a collection for later use
            end tell
    end tell
    
    tell process "Simulator"
        set frontmost to true
            repeat with c from 1 to count of wptElements
                set wptElement to item c of wptElements
                tell wptElement
                    set lon to value of XML attribute "lon" of wptElement --> putting the lon value of the wpt element in a variable for later use
                    set lat to value of XML attribute "lat" of wptElement --> putting the lan value of the wpt element in a variable for later use
                end tell
                    
                click menu item "Custom Location…" of menu of menu item "Location" of menu "Debug" of menu bar 1
                set popup to window "Custom Location"
                set value of text field 1 of popup to my replace_chars(lat, ".", ",")
                set value of text field 2 of popup to my replace_chars(lon, ".", ",")
                click button "OK" of popup
                    
                delay sleepParam --> sleep to simulate 'natural' movement
            end repeat
        end tell
    end tell
end run

With this script this will open our gpx file and processes it.

iOS Blog Source: https://blog.pieeatingninjas.be/2017/02/10/custom-location-and-movement-on-xamarin-remoted-ios-simulator-for-windows/.

Another way: you can create a Xamarin UI Test Project and you can mock the location too. Just call the SetLocation function. (On Android it is still required that change the Mock Location app and request permissions.)

Xamarin.Forms Twilio implementation

This project you can find at: https://github.com/officialdoniald/Xamarin.Forms.Twilio

Twilio created just for Android and iOS libs and not for Xamarin. So we need to create Bindings for Xamarin.Forms and native.

First step: we need to create Android Binding Library(https://github.com/officialdoniald/Xamarin.Forms.Twilio/tree/master/Xamarin.Forms.Twilio.PoC/Xamarin.Android.Twilio.Client) and iOS Binding Library(https://github.com/officialdoniald/Xamarin.Forms.Twilio/tree/master/Xamarin.Forms.Twilio.PoC/Xamarin.iOS.Twilio.Client) in an Emptys Solution. We have to add the .aar/.jar file(to Jars folder to Android) and .a files (as native reference to iOS) to the Projects. But if you want to just using the completed DLL, just download theese Project from my GitHub, Clean and Rebuild and use the builded DLLs or just added theese Projects to your Application Project as Reference.

After this we need to create a Xamarin.Forms Application(https://github.com/officialdoniald/Xamarin.Forms.Twilio/tree/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC).

We need to add the DLLs or the Projects as Reference to the various Platforms Projects.

Super! We can use the Twilio lib from the Android and iOS Projects. But if we want to use theese from the Forms Application we need to create some Platform Specific implementations and we will call theese throught DependencyService.

Let’s create an Interface in the Forms Application: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC/Implementation/Twilio/ITwilioClient.cs

Now we have to create some events. We will implement the MakeOutBoundCall, Mute and HangOut functions from Twilio. https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC/Helper/GlobalEvents.cs

Great! Now just implement theese in the various Platforms and we will talk about what have to do, when we want to use theese functions without crash.

Android:

Let’s create the interface imaplementation: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.Android/Implementation/Twilio/TwilioClient.cs

Open the MainActivity.cs and create the Twilio object.: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.Android/MainActivity.cs

Very important: we need to change our AndroidManifest.xml file: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.Android/Properties/AndroidManifest.xml. We need to add some uses and service.

Done!

iOS:

https://github.com/officialdoniald/Xamarin.Forms.Twilio/tree/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.iOS

Don’t worry, if there will be some errors, that’s a Visual Studio bug. The project will be built.

Let’s create the interface imaplementation: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.iOS/Implementation/Twilio/TwilioClient.cs

Open the AppDelegate.cs file: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.iOS/AppDelegate.cs

And here we will use the microphone we have to change the Info.plist file: https://github.com/officialdoniald/Xamarin.Forms.Twilio/blob/master/Xamarin.Forms.Twilio.PoC/Xamarin.Forms.Twilio.PoC.iOS/Info.plist

    <key>NSMicrophoneUsageDescription</key>
    <string>YOUR_APP_NAME will use your microphone, when you call someone.</string>

When you make an OutBoundCall you can use a lots of combination of the parameters. See some of theese at: https://www.twilio.com/docs/voice/client/tutorials/outgoing-calls.

Xamarin.Forms save and open PDF file

This project you can find at: https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF.

It could be a real situation that your app need to save or open a pdf file. How can it possible? Throught DependencyService. There are many companies, who deal with generationg PDF Document in Xamarin.Forms. We need to create a PDF Document first and than save it. But if you want to open an exists document, just read this blog post cointinue :D. Companies, whom you can download NuGet Packages to create PDF Document:

First thing we need to do, that create an interface in the StandardLibrary. https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF/IPDFSaveAndOpen.cs

using System;
using System.IO;
using System.Threading.Tasks;

namespace Xamarin.Forms.Save.Open.PDF
{
    public interface IPDFSaveAndOpen
    {
        Task SaveAndView(string fileName, String contentType, MemoryStream stream, PDFOpenContext context);
    }

    /// <summary>
    /// Where should the PDF file open. In the app or out of the app.
    /// </summary>
    public enum PDFOpenContext
    {
        InApp,
        ChooseApp
    }
}

Implement it on the various platforms!

Android

https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF.Android/PDFSaveAndOpen.cs

using Android;
using Android.Content;
using Android.Content.PM;
using Android.Support.V4.App;
using Android.Support.V4.Content;
using Android.Webkit;
using Java.IO;
using System;
using System.IO;
using System.Threading.Tasks;
using Android.App;
using Xamarin.Forms;
using Xamarin.Forms.Save.Open.PDF;
using Droid.Implementations;

[assembly: Dependency(typeof(PDFSaveAndOpen))]
namespace Droid.Implementations
{
    public class PDFSaveAndOpen : IPDFSaveAndOpen
    {
        public async Task SaveAndView(string fileName, String contentType, MemoryStream stream, PDFOpenContext context)
        {
            string exception = string.Empty;
            string root = null;

            if (ContextCompat.CheckSelfPermission(Forms.Context, Manifest.Permission.WriteExternalStorage) != Permission.Granted)
            {
                ActivityCompat.RequestPermissions((Activity)Forms.Context, new String[] { Manifest.Permission.WriteExternalStorage }, 1);
            }
            
            if (Android.OS.Environment.IsExternalStorageEmulated)
            {
                root = Android.OS.Environment.ExternalStorageDirectory.ToString();
            }
            else
                root = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

            Java.IO.File myDir = new Java.IO.File(root + "/PDFFiles");
            myDir.Mkdir();

            Java.IO.File file = new Java.IO.File(myDir, fileName);

            if (file.Exists()) file.Delete();

            try
            {
                FileOutputStream outs = new FileOutputStream(file);
                outs.Write(stream.ToArray());

                outs.Flush();
                outs.Close();
            }
            catch (Exception e)
            {
                exception = e.ToString();
            }

            if (file.Exists() && contentType != "application/html")
            {
                string extension = MimeTypeMap.GetFileExtensionFromUrl(Android.Net.Uri.FromFile(file).ToString());
                string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
                Intent intent = new Intent(Intent.ActionView);
                intent.SetFlags(ActivityFlags.ClearTop | ActivityFlags.NewTask);
                Android.Net.Uri path = FileProvider.GetUriForFile(Forms.Context, Android.App.Application.Context.PackageName + ".provider", file);
                intent.SetDataAndType(path, mimeType);
                intent.AddFlags(ActivityFlags.GrantReadUriPermission);

                switch (context)
                {
                    case PDFOpenContext.InApp:
                        Forms.Context.StartActivity(intent);
                        break;
                    case PDFOpenContext.ChooseApp:
                        Forms.Context.StartActivity(Intent.CreateChooser(intent, "Choose App"));
                        break;
                    default:
                        break;
                }
            }
        }
    }
}

iOS

We need to create a PreviewController and that we have to show the PDF on this Controller.

https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF.iOS/PreviewControllerDS.cs

using Foundation;
using QuickLook;
using System;
using System.IO;

namespace Xamarin.Forms.Save.Open.PDF.iOS
{
    public class PreviewControllerDS : QLPreviewControllerDataSource
    {
        //Document cache
        private QLPreviewItem _item;

        //Setting the document
        public PreviewControllerDS(QLPreviewItem item)
        {
            _item = item;
        }

        //Setting document count to 1
        public override nint PreviewItemCount(QLPreviewController controller)
        {
            return 1;
        }

        //Return the document
        public override IQLPreviewItem GetPreviewItem(QLPreviewController controller, nint index)
        {
            return _item;
        }
    }

    public class QLPreviewItemFileSystem : QLPreviewItem
    {

        string _fileName, _filePath;

        //Setting file name and path
        public QLPreviewItemFileSystem(string fileName, string filePath)
        {
            _fileName = fileName;
            _filePath = filePath;
        }

        //Return file name
        public override string ItemTitle
        {
            get
            {
                return _fileName;
            }
        }

        //Retun file path as NSUrl
        public override NSUrl ItemUrl
        {
            get
            {
                return NSUrl.FromFilename(_filePath);
            }
        }
    }

    public class QLPreviewItemBundle : QLPreviewItem
    {
        string _fileName, _filePath;

        //Setting file name and path
        public QLPreviewItemBundle(string fileName, string filePath)
        {
            _fileName = fileName;
            _filePath = filePath;
        }

        //Return file name
        public override string ItemTitle
        {
            get
            {
                return _fileName;
            }
        }

        //Retun file path as NSUrl
        public override NSUrl ItemUrl
        {
            get
            {
                var documents = NSBundle.MainBundle.BundlePath;
                var lib = Path.Combine(documents, _filePath);
                var url = NSUrl.FromFilename(lib);
                return url;
            }
        }
    }
}

Implementation:

https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF.iOS/PDFSaveAndOpen.cs

using System;
using System.Threading.Tasks;
using System.IO;
using Xamarin.Forms;
using UIKit;
using QuickLook;
using Xamarin.Forms.Save.Open.PDF.iOS;

[assembly: Dependency(typeof(PDFSaveAndOpen))]
namespace Xamarin.Forms.Save.Open.PDF.iOS
{
    public class PDFSaveAndOpen : IPDFSaveAndOpen
    {
        //Method to save document as a file and view the saved document
        public async Task SaveAndView(string filename, string contentType, MemoryStream stream, PDFOpenContext context)
        {
            //Get the root path in iOS device.
            string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
            string filePath = Path.Combine(path, filename);

            //Create a file and write the stream into it.
            FileStream fileStream = File.Open(filePath, FileMode.Create);
            stream.Position = 0;
            stream.CopyTo(fileStream);
            fileStream.Flush();
            fileStream.Close();

            //Invoke the saved document for viewing
            UIViewController currentController = UIApplication.SharedApplication.KeyWindow.RootViewController;
            while (currentController.PresentedViewController != null)
                currentController = currentController.PresentedViewController;
            UIView currentView = currentController.View;

            QLPreviewController qlPreview = new QLPreviewController();
            QLPreviewItem item = new QLPreviewItemBundle(filename, filePath);
            qlPreview.DataSource = new PreviewControllerDS(item);

            currentController.PresentViewController(qlPreview, true, null);
        }
    }
}

Let’s just create a Page and try it: https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF/MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Xamarin.Forms.Save.Open.PDF.MainPage">
    <StackLayout>
        <Button HeightRequest="100" Text="Save and open PDF in app" Clicked="Button_Clicked"/>
        <Button HeightRequest="100" Text="Save and open PDF with choose app" Clicked="Button_Clicked_1"/>
    </StackLayout>
</ContentPage>

https://github.com/officialdoniald/Xamarin.Forms.Save.Open.PDF/blob/master/Xamarin.Forms.Save.Open.PDF/Xamarin.Forms.Save.Open.PDF/MainPage.xaml.cs

using System;
using System.IO;

namespace Xamarin.Forms.Save.Open.PDF
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Save and open the PDF file in the app.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void Button_Clicked(object sender, EventArgs e)
        {
            MemoryStream stream = new MemoryStream();

            await Xamarin.Forms.DependencyService.Get<IPDFSaveAndOpen>().SaveAndView(Guid.NewGuid() + ".pdf", "application / pdf", stream, PDFOpenContext.InApp);
        }

        /// <summary>
        /// Save and open the PDF file with "choose app".
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void Button_Clicked_1(object sender, EventArgs e)
        {
            MemoryStream stream = new MemoryStream();

            await Xamarin.Forms.DependencyService.Get<IPDFSaveAndOpen>().SaveAndView(Guid.NewGuid() + ".pdf", "application / pdf", stream, PDFOpenContext.ChooseApp);
        }
    }
}

Why is just a MemoryStream stream = new MemoryStream(); ?

Because I don’t have a pdf in stream. 😀 This is your task to create a stream (upload a pdf on your phone and create a Stream: pdf files are opened in the same method like other file types) and open it with this method.

Xamarin.Forms – CustomMasterDetailPage – change navigation icon(back button and “hamburger” menu button) – Android

This project, you can find at: https://github.com/officialdoniald/Xamarin.Forms.CustomControls

We have to create some platforms specific code (custom NavigationPage Renderer), because in the base Xamarin.Forms code, we can’t find this implementation. So first step, we have to create a class in the .NET Standard/PCL project: CustomMasterDetailPage( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/MasterDetailPage/CustomMasterDetailPage.cs ).

using Xamarin.Forms; 
using XamarinForms.CustomControls.Enums;
namespace XamarinForms.CustomControls.MasterDetailPage
{
public class CustomMasterDetailPage : Xamarin.Forms.MasterDetailPage
{
public CustomMasterDetailPage() : base() { }
}
}

In Xamarin.Android, we can say that: ok, let’s change the back button and the hamburger menu button image forever, so we have to write an algorythm, that will change our icons. We have to watch the NavigationStack, and it’s count is equal to 1, we have to change the icon to a hamburger menu icon, otherwise we have to change the icon to a back button icon. Let’s do it.

So we have to write a MasterDetailPageRenderer class in the Android Project and we have to write the algorythm there.

 using Android.Content;
 using Android.Graphics;
 using Android.Graphics.Drawables;
 using Android.Runtime;
 using Android.Widget;
 using Plugin.CurrentActivity;
 using System;
 using Xamarin.Forms;
 using Xamarin.Forms.Platform.Android.AppCompat;
 using XamarinForms.CustomControls.Droid.CustomRenderer;
 using XamarinForms.CustomControls.MasterDetailPage;

[assembly: ExportRenderer(typeof(CustomMasterDetailPage), typeof(MasterNavigationPageRenderer))]
 namespace XamarinForms.CustomControls.Droid.CustomRenderer
 { 
pragma warning disable CS0618
public class MasterNavigationPageRenderer : MasterDetailPageRenderer
{
    private static Android.Support.V7.Widget.Toolbar GetToolbar() => (CrossCurrentActivity.Current?.Activity as MainActivity)?.FindViewById(Resource.Id.toolbar);

    private Android.Support.V7.Widget.Toolbar toolbar;

    public MasterNavigationPageRenderer() : base() { }

    public MasterNavigationPageRenderer(Context context) : base(context) { }

    public MasterNavigationPageRenderer(IntPtr a, JniHandleOwnership b) : base() { }

    protected override void OnLayout(bool changed, int l, int t, int r, int b)
    {
        base.OnLayout(changed, l, t, r, b);
        toolbar = GetToolbar();

        if (toolbar != null)
        {
            if (GlobalVariables.NavigationStackCount == 1)
            {
                SetNavigationButton(Resource.Drawable.menu);
            }
            else
            {
                SetNavigationButton(Resource.Drawable.back);
            }
        }
    }

    private void SetNavigationButton(int resourceID)
    {
        var icon = Forms.Context.GetDrawable(resourceID);
        using (var drawable = ((BitmapDrawable)icon).Bitmap)
        using (var bitmap = Bitmap.CreateScaledBitmap(drawable, 80, 80, false))
        using (var newDrawable = new BitmapDrawable(Resources, bitmap))
        {
            toolbar.NavigationIcon = newDrawable;
        }
    }
}
pragma warning restore CS0618
}
GlobalVariables.NavigationStackCount is public static variable. You can defined it in the .NET Standard/PCL project.

Next step, we have to create a platform specific NavigationPageRenderer. So let’s create a class in the .NET Standard/PCL project, and call it CustomNavigationPage ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/NavigationPage/CustomNavigationPage.cs )

Than we have to create the platform spec. implementation in the Android project (https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/CustomRenderer/CustonNavigationPageRenderer.cs) :

 using System;
 using System.ComponentModel;
 using System.Threading.Tasks;
 using Android.Content;
 using Android.Graphics;
 using Android.Graphics.Drawables;
 using Android.Runtime;
 using Android.Support.V7.Graphics.Drawable;
 using Android.Util;
 using Android.Widget;
 using Plugin.CurrentActivity;
 using Xamarin.Forms;
 using Xamarin.Forms.Platform.Android;
 using XamarinForms.CustomControls.Droid.CustomRenderer;
 using XamarinForms.CustomControls.Enums;
 using XamarinForms.CustomControls.NavigationPage;
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustonNavigationPageRenderer))] 
namespace XamarinForms.CustomControls.Droid.CustomRenderer
 { 
pragma warning disable CS0618
public class CustonNavigationPageRenderer : Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer
{
    #region Properties

    private static Android.Support.V7.Widget.Toolbar GetToolbar() => (CrossCurrentActivity.Current?.Activity as MainActivity)?.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);

    private Android.Support.V7.Widget.Toolbar toolbar;

    private Page view;

    #endregion

    #region Kontruktor

    public CustonNavigationPageRenderer() : base() { }

    public CustonNavigationPageRenderer(Context context) : base(context) { }

    public CustonNavigationPageRenderer(IntPtr a, JniHandleOwnership b) : base() { }

    #endregion

 protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        var page = (CustomNavigationPage)sender;
    }

    protected override void OnLayout(bool changed, int l, int t, int r, int b)
    {
        base.OnLayout(changed, l, t, r, b);

        //You can define special pages, where you can change the rule.
        //if (view is LoginPage || view is PasswordResetPage || view is RegisterPage || view is SMSPage)
        //{
        //    SetIcon();
        //}
    }

    protected override Task<bool> OnPushAsync(Page view, bool animated)
    {
        var retVal = base.OnPushAsync(view, animated);

        GlobalVariables.NavigationStackCount = view.Navigation.NavigationStack.Count;

        this.view = view;

        return retVal;
    }

    protected override Task<bool> OnPopToRootAsync(Page page, bool animated)
    {
        var retVal = base.OnPopToRootAsync(page, animated);

        GlobalVariables.NavigationStackCount = page.Navigation.NavigationStack.Count - 1;

        view = page;

        return retVal;
    }

    protected override Task<bool> OnPopViewAsync(Page page, bool animated)
    {
        var retVal = base.OnPopViewAsync(page, animated);

        GlobalVariables.NavigationStackCount = page.Navigation.NavigationStack.Count - 1;

        view = page;

        return retVal;
    }

    private void SetIcon()
    {
        toolbar = GetToolbar();

        if (toolbar != null)
        {
            if (GlobalVariables.NavigationStackCount == 1)
            {
                SetNavigationButton(Resource.Drawable.menu);
            }
            else
            {
                SetNavigationButton(Resource.Drawable.back);
            }
        }
    }

    private void SetNavigationButton(int resourceID)
    {
        var icon = Forms.Context.GetDrawable(resourceID);
        using (var drawable = ((BitmapDrawable)icon).Bitmap)
        using (var bitmap = Bitmap.CreateScaledBitmap(drawable, 80, 80, false))
        using (var newDrawable = new BitmapDrawable(Resources, bitmap))
        {
            toolbar.NavigationIcon = newDrawable;
        }
    }
}
pragma warning restore CS0618
}

Here the most important thing is to store the GlobalVariables.NavigationStackCount. Here can change the icons too.

Xamarin.Forms – change cursor’s color – Android

This code (https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/CustomRenderer/CustomEntryRenderer.cs ) :

IntPtr IntPtrtextViewClass = JNIEnv.FindClass(typeof(TextView));

IntPtr mCursorDrawableResProperty = JNIEnv.GetFieldID(IntPtrtextViewClass, “mCursorDrawableRes”, “I”);

will change the cursor’s color. If you want to really change the color of the cursor, you have to create in the Resources/Drawable folder a my_cursor.xml file: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/Resources/drawable/my_cursor.xml

<shape xmlns:android=”http://schemas.android.com/apk/res/android” android:shape=”rectangle”>
<solid android:color=”@color/colorCursors”></solid>
<size android:width=”2dp” />
</shape>

@color/colorCursors is a color from the Resources/values/color.xml. If you don’t have this file, just create it: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/Resources/values/colors.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<resources>
<color name=”launcher_background”>#FFFFFF</color>
<color name=”colorPrimary”>#3F51B5</color>
<color name=”colorPrimaryDark”>#303F9F</color>
<color name=”colorAccent”>#FF4081</color>
<color name=”colorCursors”>#000000</color>
</resources>

<color name=”colorCursors”>#000000</color> here can you gain the color of the cursor.

Xamarin.Forms – CustomSearchBar

This project, you can find at: https://github.com/officialdoniald/Xamarin.Forms.CustomControls

We have to create some platforms specific code (custom NavigationPage Renderer), because in the base Xamarin.Forms code, we can’t find this implementation. So first step, we have to create a class in the .NET Standard/PCL project: CustomSearchBar ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/SearchBar/CustomSearchBar.cs ).

using Xamarin.Forms;
namespace XamarinForms.CustomControls.SearchBar
{
public class CustomSearchBar : Xamarin.Forms.SearchBar
{
public static readonly BindableProperty BorderColorProperty =
BindableProperty.Create(nameof(BorderColor), typeof(Color), typeof(CustomSearchBar));
public static readonly BindableProperty BorderWidthProperty =
BindableProperty.Create(nameof(BorderWidth), typeof(int), typeof(CustomSearchBar));
/// <summary>
/// Set the Border Color of the SearchBar.
/// </summary>
public Color BorderColor
{
set { SetValue(BorderColorProperty, value); }
get { return (Color)GetValue(BorderColorProperty); }
}
/// <summary>
/// Set the Border Width of the SearchBar.
/// </summary>
public int BorderWidth
{
set { SetValue(BorderWidthProperty, value); }
get { return (int)GetValue(BorderWidthProperty); }
}
}
}

Now, we have to implement to the various platforms. Let’s try with the Android part: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/CustomRenderer/CustomSearchBarRenderer.cs

With this code, you can change the search icon of the searchbar:

int searchIconId = Context.Resources.GetIdentifier(“android:id/search_mag_icon”, null, null);
var icon = searchView.FindViewById(searchIconId);
(icon as ImageView).SetImageResource(Resource.Drawable.searchbaricon);

You have to create an image in the Resources/Drawable folder with this name: searchbaricon.png (you can change it, but don’t forget to rename Resource.Drawable.your_drawable).

iOS part:

using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using XamarinForms.CustomControls.iOS.CustomRenderer;
using XamarinForms.CustomControls.SearchBar;
[assembly: ExportRenderer(typeof(CustomSearchBar), typeof(CustomSearchBarRenderer))]
namespace XamarinForms.CustomControls.iOS.CustomRenderer
{
public class CustomSearchBarRenderer : SearchBarRenderer
{
#region Properties
private UIColor BorderColor = UIColor.Black;
private int BorderWidth = 1;
#endregion
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.SearchBar> e)
{
base.OnElementChanged(e);
var newElement = ((CustomSearchBar)e.NewElement);
BorderColor = newElement.BorderColor.ToUIColor();
if (newElement.BorderWidth != 0)
{
BorderWidth = newElement.BorderWidth;
}
var searchbar = (UISearchBar)Control;
if (e.NewElement != null)
{
//Foundation.NSString _searchField = new Foundation.NSString(“searchField”);
//var textFieldInsideSearchBar = (UITextField)searchbar.ValueForKey(_searchField);
//textFieldInsideSearchBar.BackgroundColor = UIColor.FromRGB(0, 0, 12);
//textFieldInsideSearchBar.TextColor = UIColor.White;
// searchbar.Layer.BackgroundColor = UIColor.Blue.CGColor;
//searchbar.TintColor = UIColor.White;
//searchbar.BarTintColor = UIColor.White;
searchbar.Layer.CornerRadius = 0;
searchbar.Layer.BorderWidth = BorderWidth;
searchbar.Layer.BorderColor = BorderColor.CGColor;
//searchbar.ShowsCancelButton = false;
}
}
}
}

Xamarin.Forms – CustomEntry (BorderWidth, BorderColor and TextPadding)

This project, you can find at: https://github.com/officialdoniald/Xamarin.Forms.CustomControls

We have to create some platforms specific code (custom NavigationPage Renderer), because in the base Xamarin.Forms code, we can’t find this implementation. So first step, we have to create a class in the .NET Standard/PCL project: CustomEntry ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/Entry/CustomEntry.cs ).

using Xamarin.Forms;
namespace XamarinForms.CustomControls.Entry
{
public class CustomEntry : Xamarin.Forms.Entry
{
public static readonly BindableProperty BorderColorProperty =
BindableProperty.Create(nameof(BorderColor), typeof(Color), typeof(CustomEntry));
public static readonly BindableProperty BorderWidthProperty =
BindableProperty.Create(nameof(BorderWidth), typeof(int), typeof(CustomEntry));
public static readonly BindableProperty TextPaddingProperty =
BindableProperty.Create(nameof(TextPadding), typeof(Thickness), typeof(CustomEntry));
/// <summary>
/// Set the Border Color of the Entry.
/// </summary>
public Color BorderColor
{
set { SetValue(BorderColorProperty, value); }
get { return (Color)GetValue(BorderColorProperty); }
}
/// <summary>
/// Set the Border Width of the Entry.
/// </summary>
public int BorderWidth
{
set { SetValue(BorderWidthProperty, value); }
get { return (int)GetValue(BorderWidthProperty); }
}
/// <summary>
/// Set the Text Padding of the Entry.
/// </summary>
public Thickness TextPadding
{
set { SetValue(TextPaddingProperty, value); }
get { return (Thickness)GetValue(TextPaddingProperty); }
}
}
}

Now, we have to implement to the various platforms. Let’s begin with Android: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/CustomRenderer/CustomEntryRenderer.cs

using System;
using System.ComponentModel;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using XamarinForms.CustomControls.Droid.CustomRenderer;
using XamarinForms.CustomControls.Entry;
[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
namespace XamarinForms.CustomControls.Droid.CustomRenderer
{
#pragma warning disable CS0618
public class CustomEntryRenderer : EntryRenderer
{
private Android.Graphics.Color BorderColor = Android.Graphics.Color.Black;
private int BorderWidth = 1;
private Thickness TextPadding = new Thickness(0);
private CustomEntry CustomEntry;
#endregion
#region Konstruktor
public CustomEntryRenderer() : base() { }
public CustomEntryRenderer(Context context) : base(context) { }
public CustomEntryRenderer(IntPtr a, JniHandleOwnership b) : base() { }
#endregion
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
BorderColor = CustomEntry.BorderColor.ToAndroid();
if (CustomEntry.BorderWidth != 0)
{
BorderWidth = CustomEntry.BorderWidth;
}
TextPadding = CustomEntry.TextPadding;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Entry> e)
{
base.OnElementChanged(e);
CustomEntry = e.NewElement as CustomEntry;
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
if (CustomEntry != null)
{
ChangeCustomEntryProperties(CustomEntry);
Control.Gravity = GravityFlags.CenterVertical;
int left = Convert.ToInt32(TextPadding.Left);
int right = Convert.ToInt32(TextPadding.Right);
int top = Convert.ToInt32(TextPadding.Top);
int bottom = Convert.ToInt32(TextPadding.Bottom);
Control.SetPadding(left, top, right, bottom);
IntPtr IntPtrtextViewClass = JNIEnv.FindClass(typeof(TextView));
IntPtr mCursorDrawableResProperty = JNIEnv.GetFieldID(IntPtrtextViewClass, “mCursorDrawableRes”, “I”);
JNIEnv.SetField(Control.Handle, mCursorDrawableResProperty, Resource.Drawable.my_cursor);
}
}
private void ChangeCustomEntryProperties(CustomEntry entry)
{
var customEntry = entry;
var nativeEditText = (EditText)Control;
var shape = new ShapeDrawable(new Android.Graphics.Drawables.Shapes.RectShape());
shape.Paint.Color = BorderColor;
shape.Paint.SetStyle(Paint.Style.Stroke);
nativeEditText.Background = shape;
GradientDrawable gd = new GradientDrawable();
gd.SetColor(Android.Graphics.Color.White);
gd.SetStroke(BorderWidth, BorderColor);
nativeEditText.SetBackground(gd);
}
}
#pragma warning restore CS0618
}

This code:

IntPtr IntPtrtextViewClass = JNIEnv.FindClass(typeof(TextView));

IntPtr mCursorDrawableResProperty = JNIEnv.GetFieldID(IntPtrtextViewClass, “mCursorDrawableRes”, “I”);

will change the cursor’s color. If you want to really change the color of the cursor, you have to create in the Resources/Drawable folder a my_cursor.xml file:

<shape xmlns:android=”http://schemas.android.com/apk/res/android” android:shape=”rectangle”>
<solid android:color=”@color/colorCursors”></solid>
<size android:width=”2dp” />
</shape>

@color/colorCursors is a color from the Resources/values/color.xml. If you don’t have this file, just create it:

<?xml version=”1.0″ encoding=”utf-8″?>
<resources>
<color name=”launcher_background”>#FFFFFF</color>
<color name=”colorPrimary”>#3F51B5</color>
<color name=”colorPrimaryDark”>#303F9F</color>
<color name=”colorAccent”>#FF4081</color>
<color name=”colorCursors”>#000000</color>
</resources>

<color name=”colorCursors”>#000000</color> here can you gain the color of the cursor.

If you don’t want to change, just skip this code, comment in it.

Now continue with the iOS site: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.iOS/CustomRenderer/CustomEntryRenderer.cs

using System.ComponentModel;
using CoreGraphics;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using XamarinForms.CustomControls.Entry;
using XamarinForms.CustomControls.iOS.CustomRenderer;
[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
namespace XamarinForms.CustomControls.iOS.CustomRenderer
{
public class CustomEntryRenderer : EntryRenderer
{
#region Properties
private UIColor BorderColor = UIColor.Black;
private int BorderWidth = 1;
private Thickness TextPadding = new Thickness(0);
#endregion
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var element = ((CustomEntry)sender);
BorderColor = element.BorderColor.ToUIColor();
BorderWidth = element.BorderWidth;
TextPadding = element.TextPadding;
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Layer.BorderWidth = BorderWidth;
Control.VerticalAlignment = UIControlContentVerticalAlignment.Center;
Control.LeftView = new UIView(new CGRect(TextPadding.Left, TextPadding.Top, TextPadding.Right, TextPadding.Bottom));
Control.LeftViewMode = UITextFieldViewMode.Always;
Control.TintColor = UIColor.Black;
Control.Layer.BorderColor = BorderColor.CGColor;
//Control.RightView = new UIView(new CGRect(0, 0, 15, 0));
//Control.RightViewMode = UITextFieldViewMode.Always;
//// Use tint color to change the cursor’s color
//Control.TintColor = UIColor.White;
}
}
}
}

Xamarin.Forms – ListView – CustomTextCell

This project, you can find at: https://github.com/officialdoniald/Xamarin.Forms.CustomControls

We have to create some platforms specific code (custom NavigationPage Renderer), because in the base Xamarin.Forms code, we can’t find this implementation. So first step, we have to create a class in the .NET Standard/PCL project: CustomTextCell ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/ListView/CustomTextCell.cs ).

using Xamarin.Forms;
namespace XamarinForms.CustomControls.ListView
{
public class CustomTextCell : TextCell
{
public static readonly BindableProperty TextFontSizeProperty =
BindableProperty.Create(“TextFontSize”, typeof(double), typeof(CustomTextCell), default(double));
public static readonly BindableProperty DetailFontSizeProperty =
BindableProperty.Create(“DetailFontSize”, typeof(double), typeof(CustomTextCell), default(double));
public static readonly BindableProperty TextFontAttributesProperty =
BindableProperty.Create(“TextFontAttributes”, typeof(Enums.FontAttributes), typeof(CustomTextCell), default(Enums.FontAttributes));
public static readonly BindableProperty TextFontFamilyProperty =
BindableProperty.Create(“TextFontFamily”, typeof(string), typeof(CustomTextCell), default(string));
public static readonly BindableProperty DetailFontAttributesProperty =
BindableProperty.Create(“DetailFontAttributes”, typeof(Enums.FontAttributes), typeof(CustomTextCell), default(Enums.FontAttributes));
public static readonly BindableProperty DetailFontFamilyProperty =
BindableProperty.Create(“DetailFontFamily”, typeof(string), typeof(CustomTextCell), default(string));
/// <summary>
/// Set the Text font size.
/// </summary>
public double TextFontSize
{
get { return (double)GetValue(TextFontSizeProperty); }
set { SetValue(TextFontSizeProperty, value); }
}
/// <summary>
/// Set the Detail font size.
/// </summary>
public double DetailFontSize
{
get { return (double)GetValue(DetailFontSizeProperty); }
set { SetValue(DetailFontSizeProperty, value); }
}
/// <summary>
/// Set the Font Attributes of the Text.
/// </summary>
public Enums.FontAttributes TextFontAttributes
{
get { return (Enums.FontAttributes)GetValue(TextFontAttributesProperty); }
set { SetValue(TextFontAttributesProperty, value); }
}
/// <summary>
/// Set the Font Family of the Text.
/// </summary>
public string TextFontFamily
{
get { return (string)GetValue(TextFontFamilyProperty); }
set { SetValue(TextFontFamilyProperty, value); }
}
/// <summary>
/// Set the Font Attributes of the Detail.
/// </summary>
public Enums.FontAttributes DetailFontAttributes
{
get { return (Enums.FontAttributes)GetValue(DetailFontAttributesProperty); }
set { SetValue(DetailFontAttributesProperty, value); }
}
/// <summary>
/// Set the Font Family of the Detail.
/// </summary>
public string DetailFontFamily
{
get { return (string)GetValue(DetailFontFamilyProperty); }
set { SetValue(DetailFontFamilyProperty, value); }
}
}
}

Enums.FontAttributes is a custtom enum class, we can create it, but we can skip it too. You can find it at: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/Enums/HelperEnums.cs

Now we have to implement the platform spec. code at the various platforms.

Let’s begin with the Android site ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/CustomRenderer/CustomTextCellRenderer.cs ):

First, we have to implement the base class’s contructor:

public CustomTextCellRenderer() : base() { }
public CustomTextCellRenderer(System.IntPtr a, Android.Runtime.JniHandleOwnership b) : base() { }

Then we have to override the GetCellCore base function:

protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, ViewGroup parent, Android.Content.Context context)
{
var view = (CustomTextCell)item;
if (convertView == null)
{
convertView = new BaseCellView(context, item);
}
if (convertView is BaseCellView cellView)
{
cellView.SetImageVisible(false);
cellView.MainText = view.Text;
cellView.DetailText = view.Detail;
cellView.SetMainTextColor(view.TextColor);
cellView.SetDetailTextColor(view.DetailColor);
var control = ((LinearLayout)convertView);
if (control.GetChildAt(1) is LinearLayout linearLayout)
{
var mainTextView = (TextView)linearLayout.GetChildAt(0);
var detailTextView = (TextView)linearLayout.GetChildAt(1);
mainTextView.TextSize = (float)view.TextFontSize;
detailTextView.TextSize = (float)view.DetailFontSize;
var titleTypeface = Typeface.Create(view.TextFontFamily, ConvertFontAttributesToTypefaceStyle(view.TextFontAttributes));
var detailTypeface = Typeface.Create(view.DetailFontFamily, ConvertFontAttributesToTypefaceStyle(view.DetailFontAttributes));
mainTextView.Typeface = titleTypeface;
detailTextView.Typeface = detailTypeface;
}
}
return _cellCore = convertView;
}

If we implement the custom FontAttribute enum, we have to create the ConvertFontAttributesToTypefaceStyle() function:

private TypefaceStyle ConvertFontAttributesToTypefaceStyle(Enums.FontAttributes fontAttributes)
{
if (fontAttributes == Enums.FontAttributes.Bold)
{
return Android.Graphics.TypefaceStyle.Bold;
}
else if (fontAttributes == Enums.FontAttributes.BoldItalic)
{
return Android.Graphics.TypefaceStyle.BoldItalic;
}
else if (fontAttributes == Enums.FontAttributes.Italic)
{
return Android.Graphics.TypefaceStyle.Italic;
}
else return Android.Graphics.TypefaceStyle.Normal;
}

Let’s continue with the iOS site ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.iOS/CustomRenderer/CustomTextCellRenderer.cs ):

The mechanism is the same, but we don’t need to implement the base class’s constructors, but we need to override the GetCell() function:

using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using XamarinForms.CustomControls.iOS.CustomRenderer;
using XamarinForms.CustomControls.ListView;
[assembly: ExportRenderer(typeof(CustomTextCell), typeof(CustomTextCellRenderer))]
namespace XamarinForms.CustomControls.iOS.CustomRenderer
{
public class CustomTextCellRenderer : TextCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var view = (CustomTextCell)item;
var cell = base.GetCell(item, reusableCell, tv);
//cell.SelectedBackgroundView = new UIView { BackgroundColor = UIColor.Red };
var TextLabel = cell.TextLabel;
var DetailTextLabel = cell.DetailTextLabel;
TextLabel.Font = UIFont.FromName(view.TextFontFamily, (int)view.TextFontSize);
DetailTextLabel.Font = UIFont.FromName(view.DetailFontFamily, (int)view.DetailFontSize);
TextLabel.AttributedText = ConvertAttributes(TextLabel.Text, view.TextFontAttributes, (int)view.TextFontSize);
DetailTextLabel.AttributedText = ConvertAttributes(DetailTextLabel.Text, view.DetailFontAttributes, (int)view.DetailFontSize);
return cell;
}
private NSMutableAttributedString ConvertAttributes(string text, Enums.FontAttributes attr, int size)
{
if (attr == Enums.FontAttributes.Bold)
{
return new NSMutableAttributedString(
str: text,
font: UIFont.BoldSystemFontOfSize(size)
);
}
else if (attr == Enums.FontAttributes.Italic)
{
return new NSMutableAttributedString(
str: text,
font: UIFont.ItalicSystemFontOfSize(size)
);
}
//else if (attr == Enums.FontAttributes.BoldItalic)
//{
// var textattr = new NSMutableAttributedString(
// str: text,
// font: UIFont.BoldSystemFontOfSize(size)
// );
// textattr.Append(new NSMutableAttributedString(
// str: text,
// font: UIFont.ItalicSystemFontOfSize(size)
// ));
// return textattr;
//}
else
{
return new NSMutableAttributedString(
str: text,
font: UIFont.SystemFontOfSize(size)
);
}
}
}
}

Xamarin.Forms – NavigationPage Horizontal Alignment Center Title on Android

This project, you can find at: https://github.com/officialdoniald/Xamarin.Forms.CustomControls

We have to create some platforms specific code (custom NavigationPage Renderer), because in the base Xamarin.Forms code, we can’t find this implementation. So first step, we have to create a class in the .NET Standard/PCL project: CustomNavigationPage ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/NavigationPage/CustomNavigationPage.cs ).

using Xamarin.Forms;
using XamarinForms.CustomControls.Enums;
namespace XamarinForms.CustomControls.NavigationPage
{
public class CustomNavigationPage : Xamarin.Forms.NavigationPage
{
public CustomNavigationPage() : base() { }
public CustomNavigationPage(Xamarin.Forms.Page page) : base(page) { }
public static readonly BindableProperty TitleFontFamilyProperty =
BindableProperty.Create(“TitleFontFamily”, typeof(string), typeof(Xamarin.Forms.NavigationPage), default(string));
public static readonly BindableProperty TitleFontSizeProperty =
BindableProperty.Create(“TitleFontSize”, typeof(float), typeof(Xamarin.Forms.NavigationPage), default(float));
public static readonly BindableProperty TitleHorizontalAlignmentProperty =
BindableProperty.Create(“TitleHorizontalAlignment”, typeof(HorizontalAlignment), typeof(Xamarin.Forms.NavigationPage), default(HorizontalAlignment));
public static readonly BindableProperty TitleFontAttributesProperty =
BindableProperty.Create(“TitleFontAttributes”, typeof(Enums.FontAttributes), typeof(Xamarin.Forms.NavigationPage), default(Enums.FontAttributes));
/// <summary>
/// Set the Horizontal Alignment of the Title.
/// </summary>
public HorizontalAlignment TitleHorizontalAlignment
{
get { return (HorizontalAlignment)GetValue(TitleHorizontalAlignmentProperty); }
set { SetValue(TitleHorizontalAlignmentProperty, value); }
}
/// <summary>
/// Set the Font Size of the Title.
/// </summary>
public float TitleFontSize
{
get { return (float)GetValue(TitleFontSizeProperty); }
set { SetValue(TitleFontSizeProperty, value); }
}
/// <summary>
/// Set the Font Family of the Title.
/// </summary>
public string TitleFontFamily
{
get { return (string)GetValue(TitleFontFamilyProperty); }
set { SetValue(TitleFontFamilyProperty, value); }
}
/// <summary>
/// Set the Font Attributes of the Title.
/// </summary>
public Enums.FontAttributes TitleFontAttributes
{
get { return (Enums.FontAttributes)GetValue(TitleFontAttributesProperty); }
set { SetValue(TitleFontAttributesProperty, value); }
}
}
}

Enums.FontAttributes is a custom enum, we can skip it, but here is the code: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls/Enums/HelperEnums.cs

Second step: we have to implement in the Android code. Create a new class in the Droid Porject, call it: CustomNavigationPageRenderer ( https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.Android/CustomRenderer/CustonNavigationPageRenderer.cs).

Implement the base class’s contructors, because it may not working:

public CustonNavigationPageRenderer() : base() { }
public CustonNavigationPageRenderer(Context context) : base(context) {}
public CustonNavigationPageRenderer(IntPtr a, JniHandleOwnership b) : base() { }

We have to override to base function:

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var page = (CustomNavigationPage)sender;
FontFamily = page.TitleFontFamily;
TitleHorizontalAlignment = page.TitleHorizontalAlignment;
TitleFontAttributes = page.TitleFontAttributes;
TitleFontSize = page.TitleFontSize;
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
SetTitle();
//if (view is LoginPage || view is PasswordResetPage || view is RegisterPage || view is SMSPage)
//{
// SetIcon();
//}
}

And create the SetTitle function:

private void SetTitle()
{
toolbar = GetToolbar();
if (toolbar != null)
{
for (var i = 0; i < toolbar.ChildCount; i++)
{
var Title = toolbar.GetChildAt(i);
if (Title is TextView)
{
var title = toolbar.GetChildAt(i) as TextView;
if (TitleHorizontalAlignment == HorizontalAlignment.Center)
{
//Title in center
float toolbarCenter = toolbar.Width / 2;
float titleCenter = title.Width / 2;
title.SetX(toolbarCenter – titleCenter);
}
//Title Font “HelveticaNeueLTStd”
if (!string.IsNullOrEmpty(FontFamily))
{
var typeface = Typeface.Create(FontFamily, ConvertFontAttributesToTypefaceStyle(TitleFontAttributes));
title.Typeface = typeface;
}
if (TitleFontSize != 0)
{
title.TextSize = TitleFontSize;
}
}
}
}
}

The default position of the Title on Android is Left, so we have to check just that, if the TitleHorizontalAlignment Property is Center. Or if we don’t create an enum, we just have to write without the if condition:

float toolbarCenter = toolbar.Width / 2;

float titleCenter = title.Width / 2;

title.SetX(toolbarCenter – titleCenter);

You can find the iOS implementation too: https://github.com/officialdoniald/Xamarin.Forms.CustomControls/blob/master/XamarinForms.CustomControls/XamarinForms.CustomControls.iOS/CustomRenderer/CustomNavigationPageRenderer.cs

XamarinClearCookies

With this Package you can clear all applicaion’s cookies. So if you are already logged in with your social media account you can simply log out with this.

NOTE: This NuGetPackage will create 2 other folders: iOS and Droid. These two folders you have to move to the each Platform’s project like this: Droid/ClearCookies folder -> <your_project_name>.Droid iOS/ClearCookies folder -> <your_project_name>.iOS

Usage:

bool success = DependencyService.Get<IClearCookies.IClearCookies>().LetsClearCookies();