The code samples posted on this blog, and zip file downloads, are licensed under a Creative Commons Attribution 3.0 Unported License. Any work that includes code taken from this blog must provide attribution by means of a link. It is suggested that commercial applications that make use of code from this blog include a reference to the relevant blog post in their 'about' page.
My latest iOS development article has been published on Ray Wenderlich’s site, this time I look into how to develop custom iOS controls.
The article describes the creation of a ‘range’ slider control, and covers topics such as API design and using Core Graphics for rendering.
Interestingly, iOS developers have very few controls at their disposal, Apple’s UIKit is pretty sparse. This means that developer’s are far more likely to find themselves needing to create custom controls than a Windows Phone developer would be.
For the last six months or so I have been deeply immersed in a combination of iOS and HTML5 development. Now I don’t want my C#/XAML skills to get too rusty, so over the weekend I decided to write a simple Windows 8 Store App.
A couple of days ago we hosted a one-day conference, HTML5 – It Just Got Real, at the Royal Society buildings in London. I’m happy to say that the event was a great success, and that the Q&A session that followed our talks was very lively!
As promised, we have made all of our presentations available online. The first three presentations have all been created using HTML5, whilst the last uses PowerPoint.
Attention!
These presentation were designed to work with the Chrome browser, and are navigated using the left and right arrow keys. They are not intended to be a demonstration of cross-browser HTML5, on the contrary! They illustrate the fact that HTML5 has not solved the need to consider specific browsers and browser-versions when writing code.
This blog post describes the addition of a two-finger rotation and three-finger pitch gesture to the Windows Phone 8 Map control.
You can see these gesture in action below:
The WP8 release replaced the image-tile based Bing maps with a fully vector-rendered map from Nokia. Being vector-based, this map can be panned, zoomed, rotated and rendered at an angle (i.e. pitched). However, much of this new functionality is not offered to the end user!
The WP8 supports the same gestures that the Bing WP7 map did, i.e. a single-fingered pan gesture and two-fingered pinch to zoom. What about rotation and pitch? Why not allow the user to modify these via gestures?
The key here is to add some new gestures that complement the existing one. I opted for the following:
Two-finger rotate – When the user places two fingers on the map this is currently used to zoom the display via a ‘spreading’ motion. However, if the user instead rotates the two touch points around the centre, the map should be rotated.
Three-finger pitch – When the user places three fingers on the map, if they drag up or down, the map should adjust its pitch accordingly.
These gestures are enabled simply by creating them with a reference to the map:
new MapRotationGesture(map);new MapPitchGesture(map);
If you don’t care how this all works, just head over to github and grab the code. If you want to find out more, read on …
Suppressing The Existing Gestures
In order to add these new gestures to the map, there needs to be a mechanism in place to suppress the existing gestures so that they do not interfere.
The technique I used is similar to a technique I demonstrated previously for suppressing pinch and scroll in the Windows Phone Browser control. Both the Map and WebBrowser controls have a visual tree containing a number of user interface elements. The inner structure of the Map is shown below:
The technique for suppressing interactions is quite simple, just add a ManipulationDelta event handler to each one of these elements, setting the event to handled. The complete code is show below:
/// <summary>/// A base class for map gestures, which allows them to suppress the built-in map gestures./// </summary>publicclass MapGestureBase
{/// <summary>/// Gets or sets whether to suppress the existing gestures//// </summary>publicbool SuppressMapGestures {get;set;}protected Map Map {get;privateset;}public MapGestureBase(Map map){
Map = map;
map.Loaded+=(s,e)=> CrawlTree(Map);}privatevoid CrawlTree(FrameworkElement el){
el.ManipulationDelta+= MapElement_ManipulationDelta;for(int c =0; c < VisualTreeHelper.GetChildrenCount(el); c++){
CrawlTree(VisualTreeHelper.GetChild(el, c)as FrameworkElement);}}privatevoid MapElement_ManipulationDelta(object sender, ManipulationDeltaEventArgs e){if(SuppressMapGestures)
e.Handled=true;}}
If you create an instance of this class and associate it with a map you can turn gestures on and off via the SuppressMapGestures property:
var gestureBase =new MapGestureBase(map);
gestureBase.SuppressMapGestures=true;
NOTE: Unfortunately this doesn’t solve the ‘map in a pivot’ or ‘map in a panorama’ problem that many Windows Phone developers have struggled with – the gestures that are handled are not propagated to a parent control.
A Rotation Gesture
The user can rotate the map by placing two fingers on the screen then rotating them around their central point. Because two fingers are also used for the pinch-to-zoom gesture, a suitable threshold needs to be introduced. I have found that disabling rotation until the user has rotated by 10 degrees feels about right.
The code that implements the rotation is really quite simple, the MapGestureBase subclass is shown in its entirety below:
/// <summary>/// Adds a two-finger rotation gesture to a Map control./// </summary>publicclass MapRotationGesture : MapGestureBase
{/// <summary>/// Gets or sets the minimuum rotation that the user must apply in order to initiate this gesture./// </summary>publicdouble MinimumRotation {get;set;}privatedouble? _previousAngle;privatebool _isRotating;public MapRotationGesture(Map map):base(map){
MinimumRotation =10.0;
Touch.FrameReported+= Touch_FrameReported;}privatevoid Touch_FrameReported(object sender, TouchFrameEventArgs e){var touchPoints = e.GetTouchPoints(Map);if(touchPoints.Count==2){// for the initial touch, record the angle between the fingersif(!_previousAngle.HasValue){
_previousAngle = AngleBetweenPoints(touchPoints[0], touchPoints[1]);}// should we rotate?if(!_isRotating){double angle = AngleBetweenPoints(touchPoints[0], touchPoints[1]);double delta = angle - _previousAngle.Value;if(Math.Abs(delta)> MinimumRotation){
_isRotating =true;
SuppressMapGestures =true;}}// rotate meif(_isRotating){double angle = AngleBetweenPoints(touchPoints[0], touchPoints[1]);double delta = angle - _previousAngle.Value;
Map.Heading-= delta;
_previousAngle = angle;}}else{
_previousAngle =null;
_isRotating =false;
SuppressMapGestures =false;}}privatedouble AngleBetweenPoints(TouchPoint p1, TouchPoint p2){return Math.Atan2(p1.Position.Y- p2.Position.Y, p1.Position.X- p2.Position.X)*(180/ Math.PI);}}
Touch gestures are detected via the Touch.FrameReported event. When two fingers are placed on the screen the initial rotation angle is recorded. When the minimum rotation is exceeded, the Map.Heading is updated with each ‘delta’ reported. Really simple code, but a fantastic feature for the user!
A Pitch Gesture
You get a real feel for the vector-nature of the maps when you set the ‘pitch’, a style of rendering that is often used on satnavs.
I initially considered using a two finger pull-down gesture, which is similar to the one which Google Maps on Android uses, but found it very hard to coordinate the three gestures, zoom, rotate, pitch, which all use the same two-fingers! So instead, I opted for a three-finger pull-down gesture to increase the pitch of the map.
The code follows a very similar pattern to the rotate gesture:
/// <summary>/// Adds a three-finger pitch gesture to a Map control./// </summary>publicclass MapPitchGesture : MapGestureBase
{/// <summary>/// Gets or sets the sensitivity of this gesture/// </summary>publicdouble Sensitivity {get;set;}privatedouble? _initialPitchYLocation;public MapPitchGesture(Map map):base(map){
Sensitivity =0.5;
Touch.FrameReported+= Touch_FrameReported;}privatevoid Touch_FrameReported(object sender, TouchFrameEventArgs e){var touchPoints = e.GetTouchPoints(Map);
SuppressMapGestures = touchPoints.Count==3;if(touchPoints.Count==3){if(!_initialPitchYLocation.HasValue){
_initialPitchYLocation = touchPoints[0].Position.Y;}double delta = touchPoints[0].Position.Y- _initialPitchYLocation.Value;double newPitch = Math.Max(0, Math.Min(75, (Map.Pitch+ delta * Sensitivity)));
Map.Pitch= newPitch;
_initialPitchYLocation = touchPoints[0].Position.Y;}else{
_initialPitchYLocation =null;}}}
As soon as three fingers are placed on the screen, the gesture becomes active. The movement of the first finger is used to determine the delta to apply to the Pitch property. Again, nice and simple!
The sourcecode for these gesture, plus a demo app is available via github. Please let me know if you use this code in any of your apps.