Cross-platform UI Development with Xamarin.Forms
上QQ阅读APP看书,第一时间看更新

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.

Note

Code for the following is given in Chapter2/Gestures.

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.

Adding a gesture recognizer

In this example, we'll add a gesture recognizer to a label. Labels usually don't have a click event.

  1. 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
    };
  2. 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
    };
  3. 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);
    };
  4. 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.

Note

A WebView example is in Chapter2/Webviews.

The view is able to display a standard webpage or a page generated in code.

Displaying a web page

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"
};

Displaying a generated web page

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;

Displaying a web page from a file

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.

Displaying a web page from a file – iOS

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;
    }
  }
}

Displaying a web page from a file – Android

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";
    }
  }
}

Displaying a web page from a file – Windows Phone

Windows Phone is fairly similar to Android. Any images should be placed in the Assets directory:

[assembly: Dependency(typeof(BaseUrl_WinMobile))]
namespace WebViewExample.WinMobile
{
  public class BaseUrl_WinMobile : IBaseUrl
  {
    public string Get()
    {
      return "";
    }
  }
}

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.

Note

The map example is in Chapter 2/Maps.

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).

Setting up on iOS

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>

Setting up on Android

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.

Setting up on Windows Phone

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 } };

Adding a zoom facility to a map

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));
};

Sticking a pin in it

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);