
Gestures, maps, and WebViews
Gestures, maps, and WebViews are very commonly used user-interface facilities. While they are all in Xamarin Forms, we will see that they are not as extensive as if they were native versions.
Gestures
All mobile devices have some form of a sweep system. The iOS is especially rich when it comes to having a multi-touch user interface. As with everything to do with Xamarin.Forms
, it only covers the basic type of touch, essentially the only one supported on all platforms.
Gestures are not enabled on all types of gadgets (for example, gestures are not enabled for dragging pins on a map). This is because only tap detection is currently supported.
In this example, we'll add a gesture recognizer to a label. Labels usually don't have a click event.
- Let's create the label:
var count = 0; var label = new Label() { Text = string.Format("You have clicked me {0} times", count), TextColor = Color.Red, BackgroundColor = Color.White };
- Next is the creation of the gesture recognizer. One of the nice things about the gesture recognizer is that the number of taps required to activate the tap can be defined. If it is set to more than one, there is a finite time between clicks. If the tap is too slow, then the event is ignored:
var tapGesture = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
- The gesture recognizer reacts to the
Tapped
event:tapGesture.Tapped += (s, e) => { count++; if (count % 2 == 0) { label.BackgroundColor = Color.Red; label.TextColor = Color.White; } else { label.TextColor = Color.Red; label.BackgroundColor = Color.White; } label.Text = string.Format("You have clicked me {0} times", count); };
- Finally, the
tapGesture
has to be added to the label itself:label.GestureRecognizers.Add(tapGesture);
That is all there is to adding a gesture recognizer to a gadget.
WebViews
The WebView UI is handled by the platform's native browser. This is simple enough for Windows Phone, and iOS, but Android is currently moving over to using Chrome. Whichever you have selected will be used. If you are using this on Android, ensure that the INTERNET
permission is set.
The view is able to display a standard webpage or a page generated in code.
The browser is called into life using the following line of code:
var myBrowser = new WebView();
To point the browser to a website, set the Source
property to point to the website:
var myBrowser = new Webview Source = "http://www.farmtrack.co.uk" };
To have a WebView
show a page generated in code, an HtmlWebViewSource
object has to be created. Essentially, this is a string with HTML in it. Once created, the browser Source
property is set to point at the HtmlWebViewSource
object:
var htmlSource = new HtmlWebViewSource { Html = @"<html><body><h1><center>Hello World!<center></h1></body></html>" }; myBrowser.Source = htmlSource;
There are occasions when you would want to display a webpage stored as a file within the app. The file is normally read into a string and the string is displayed. The issue is that within the PCL, a great deal of the file library is missing.
There are a couple of solutions to this. The first is to use reflection with an EmbeddedResource
, as follows:
var assembly = typeof(LoadResourceText).GetTypeInfo().Assembly; var stream = assembly.GetManifestResourceStream("myNamespace.myFileToLoad.ext"); var data = ""; using (var reader = new StreamReader(stream)) text = reader.ReadToEnd(); myBrowser.Source = text;
The next solution is to use DependencyService
. This has to be implemented on each platform before it will work on any of them. The dependency service is used in conjunction with the BaseUrl
property of WebView
. The BaseUrl
property lets WebView
know the path to use when resolving a URL. However, these will be stored on the device.
As we saw earlier, to access the platform from within the PCL, an interface is used:
public interface IBaseUrl { string GetBaseURL(); }
We then create HtmlWebViewSource
. Here, an image and a stylesheet are included. These are stored on the device itself. They are resolved by BaseUrl
being followed down to the device:
var htmlCode = new HtmlWebViewSource { BaseUrl = DependencyService.Get<IBaseUrl>().GetBaseURL(), Html = @"<html><head> <title>Test webview</title> <link rel=""stylesheet"" href=""myCSS.css""> </head> <body> <h1>Testing 123</h1> <p>This is a test</p> <img src='Images/logo.png' /> </body> </html>" }; myBrowser.Source = htmlCode;
As we are using a dependency service, the dependency interface has to be implemented on the platforms.
Store the files in Resources
, and set the build action to BundleResource
. The dependency looks like the following piece of code:
[assembly: Dependency(typeof(BaseUrl_iOS))] namespace WebViewExample.iOS { public class BaseUrl_iOS : IBaseUrl { public string GetBaseURL { return NSBundle.MainBundle.BundlePath; } } }
The code to be included should be stored in the Assets
directory and the build action set to AndroidAsset
. The dependency looks similar to the iOS version but the base URL becomes file:///android_asset/
:
[assembly: Dependency(typeof(BaseUrl_Android))] namespace WebViewExample.Android { public class BaseUrl_Android : IBaseUrl { public string GetBaseURL { return "file:///android_asset"; } } }
Maps
As with WebView
, the map system uses the native system. While it means that the users have an interface they are familiar with, from a developer's point of view, a number of hoops have to be jumped through in order to get the maps to work.
The map requires an additional library installation from NuGet: Xamarin.Forms.Maps
. Once installed, the library has to be instantiated on each platform after the main Forms.Init()
call. The library is called into being using Xamarin.FormsMaps.Init();
.
The Init()
method should be placed in FinishedLaunching
(iOS), MainActivity.cs
(Android), and MainPage.xaml.cs
(Windows Phone).
The following needs to be added to the info.plist
file if you're developing for iOS 8. iOS 7 doesn't need this, though it does no harm adding it:
<key>NSLocationAlwaysUsageDescription</key> <string>Can we use your location</string> <key>NSLocationWhenInUseUsageDescription</key> <string>We are using your location</string>
A valid Google maps v2 API key is required. This is the same as if you were developing for Android and required a map. Once you have the key, paste the key into Properties/AndroidManifest.xml
:
<meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="Key_Value_Goes_Here" />
You will also need the following permissions setting: AccessCourseLocation
, AccessFineLocation
, AccessLocationExtraCommands
, AccessMockLocation
, AccessNetworkState
, AccessWifiState
, and Internet
.
To have maps working on Windows Phone, the ID_CAP_MAP
and ID_CAP_LOCATION
capabilities flags set. These are found by clicking on the WMAppManifest.xml
file within the Properties
folder. Once you have double clicked on the XML file, select the Capabilities tab and select the two capabilities required.
Adding a map
The map control is simple to create and add onto a ContentPage
:
var theMap = new Map(MapSpan.FromCenterAndRadius(new Position(-2.962, 53.430), new Distance(0.3))) { IsShowingUser = true, HeightRequest = 100, WidthRequest = 960, VerticalOptions = LayoutOptions.FillAndExpand, MapType = MapType.Hybrid // can also be Street and Satellite }; Content = new StackLayout{ Spacing = 0, Children = { theMap } };
A simple method of adding a zoom to the map is to add in a slider
, and change the map zoom by reacting to the ValueChanged
event:
var mySlider = new Slider(1, 10, 1); // goes from 1 to 10 in steps of 1 mySlider.ValueChanged += (s,e) => { var newZoomLevel = e.NewValue; var latlongdegs = 360 / (Math.Pow(2, newZoomLevel)); theMap.MoveToRegion(new MapSpan(theMap.VisibleRegion.Center, latlongdegrees, latlongdegrees)); };
There are four types of pins: Generic
, Place
, SavedPin
, and SearchResult
. Their names describe what they are typically used for. Pins are not pullable and only have limited capabilities (though they can be extended through custom renderers).
var mapPin = new Pin { Type = PinType.Place, Position = new Position(-2.962, 53.430), Label = "Liverpool FC", Address = "Anfield, Liverpool" }; theMap.Pins.Add(mapPin);