Using MapFragment inside ScrollView

|
I have an app where I need to put a Map inside a ScrollView, doing so will make the ScrollView intercept all the touch events. So the solution is to call RequestDisallowInterceptTouchEvent(true) whenever you need the Map to scroll around, pinching it etc. The problem is that MapFragment or its properties do not expose any touch events, so it makes it a bit harder to find out when it is touched.

I found a nice little code snippet, Lorensius Londa posted on his blog, for Java Android, which overlays the MapFragment with a FrameLayout and using the FrameLayout touch event. Using this exact technique help me achieve my goal as well. Here is the code I used.

public class TouchableMapFragment : MapFragment
{
public event EventHandler TouchDown;
public event EventHandler TouchUp;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var root = base.OnCreateView(inflater, container, savedInstanceState);
var wrapper = new TouchableWrapper(Activity);
wrapper.SetBackgroundColor(Resources.GetColor(Android.Resource.Color.Transparent));
((ViewGroup) root).AddView(wrapper,
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent));
wrapper.TouchUp = () =>
{
if (TouchUp != null)
TouchUp(this, EventArgs.Empty);
};
wrapper.TouchDown = () =>
{
if (TouchDown != null)
TouchDown(this, EventArgs.Empty);
};
return root;
}
class TouchableWrapper : FrameLayout
{
public Action TouchDown;
public Action TouchUp;
#region ctors
protected TouchableWrapper(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer) {}
public TouchableWrapper(Context context)
: this(context, null) {}
public TouchableWrapper(Context context, IAttributeSet attrs)
: this(context, attrs, 0) {}
public TouchableWrapper(Context context, IAttributeSet attrs, int defStyle)
: base(context, attrs, defStyle) { }
#endregion
public override bool DispatchTouchEvent(MotionEvent e)
{
switch (e.Action)
{
case MotionEventActions.Down:
if (TouchDown != null)
TouchDown();
break;
case MotionEventActions.Cancel:
case MotionEventActions.Up:
if (TouchUp != null)
TouchUp();
break;
}
return base.DispatchTouchEvent(e);
}
}
}
So as you see in the code there is a TouchableWrapper which simply fires some Actions whenever it was touched or the touch was released. This is grabbed by the TouchableMapFragment which subsequently fires the equivalent events.
The Fragment is used as follows in a AXML layout.
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/scroll">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<fragment
android:id="@+id/map"
android:layout_width="600dp"
android:layout_height="match_parent"
android:name="my.awesome.namespace.TouchableMapFragment" />
</LinearLayout>
</HorizontalScrollView>
view raw view.axml hosted with ❤ by GitHub
Then you can use the events in your Activity like so.
public class MyActivity : Activity
{
private GoogleMap _map;
private HorizontalScrollView _hsv;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.rtc);
_hsv = FindViewById<HorizontalScrollView>(Resource.Id.scroll);
SetupMapIfNeeded();
}
private void SetupMapIfNeeded()
{
if (null != _map) return;
var frag = FragmentManager.FindFragmentById<TouchableMapFragment>(Resource.Id.map);
if (frag != null)
{
frag.TouchUp += (sender, args) => _hsv.RequestDisallowInterceptTouchEvent(false);
frag.TouchDown += (sender, args) => _hsv.RequestDisallowInterceptTouchEvent(true);
_map = frag.Map;
if (_map == null) return; // will probably not happen
// do stuff to _map here, such as adding overlays etc.
}
}
}
view raw Activity.cs hosted with ❤ by GitHub
Now you should be able to navigate your Map when it is nested in a ScrollView.

Some experiences with the new Support packages on NuGet

|
I spent some time moving over to the new Support packages Xamarin released on NuGet, instead of using the versions from the Component store.

Jonathan Dick made a nice post about what makes the packages on NuGet a bit more smarter than they were previously, which I find very interesting. However, it is not all happy times as there seems to be some caveats by using these packages.

Hence, here are some findings that you might encounter when using them.

I have some Android Library projects, each updated to the new NuGet packages. The Android Library projects seem all to have $(TargetFrameworkVersion) defined in the .csproj file, which comes from the setting in the project properties in the Application tab called Compile using Android version. This sets the $(TargetFrameworkVersion) in the project file. However in my Android Application projects, I previously had that same option set to Use Latest Platform. The problem with this, is that it does not seem to explicitly set that in the csproj file. This gives some problems when fetching the NuGet for the Application project, where NuGet might tell you that it is incompatible and simply not install it (not cool :( ).

Another problem is that, say you have an Android Library project using Google Play services, this is also a NuGet package, which additionally brings in a lot of Support packages (including v4, v7, v13 (whoa!)). Now you do all your stuff with the Play services inside of the library, so you think you can suffice with simply adding the needed Support packages in your Android Application project. Lets say you only need Fragment backwards compatibility and you just pull in Support v4. This will give you a headache and throw a bunch of errors telling you that you have duplicate managed types.
The solution seems to match the packages you added in the Android Library project. So if you are using Google Play services in your Android Library project, you need the exact same packages in your Android Application project.

Apart from these couple of things, it seems that the new packages work pretty well. It also seems that additional Support v7 packages have made it into NuGet and more are to come!

I've switched the MvvmCross "3.5" branch and my own MvxPlugins "threeandahalf" branch to use this new stuff.

Getting User Location to work in iOS8

|
There were some minor changes to how iOS8 works with core location. Previously when requesting the user's location on the MapView with ShowsUserLocation a nice pop-up would automagically show up asking the user if this was ok. Apple decided to make this a bit more flexible, allowing the developers to enter their own message. So if you compile your pre-iOS 8 app with the new SDK this might break, since you now have to manually call a method to get that pop-up to show.

So the quick fix is to do the following:

1. In your info.plist file add one of the following key/value pairs:

<key>NSLocationWhenInUseUsageDescription</key>
<string>Message to the user</string>
or
<key>NSLocationAlwaysUsageDescription</key>
<string>Message to the user</string>
view raw info.plist hosted with ❤ by GitHub
2. I then have a Extensions.cs where I keep my various extensions, for this case I've just made a couple of checks to whether the selectors are available on the platform, so the code won't crash on previous versions of iOS.

public static void TryRequestWhenInUseAuthorization(this CLLocationManager locationManager)
{
if (locationManager.RespondsToSelector(new Selector("requestWhenInUseAuthorization")))
locationManager.RequestWhenInUseAuthorization();
}
public static void TryRequestAlwaysAuthorization(this CLLocationManager locationManager)
{
if (locationManager.RespondsToSelector(new Selector("requestAlwaysAuthorization")))
locationManager.RequestAlwaysAuthorization();
}
view raw Extensions.cs hosted with ❤ by GitHub
3. Remove your previous call to mapView.ShowsUserLocation and do the following instead:
var locationManager = new CLLocationManager();
locationManager.AuthorizationChanged += LocationManagerOnAuthorizationChanged;
locationManager.TryRequestWhenInUseAuthorization();
// create your map etc.
private void LocationManagerOnAuthorizationChanged(object sender, CLAuthorizationChangedEventArgs args)
{
if (args.Status == CLAuthorizationStatus.AuthorizedWhenInUse ||
args.Status == CLAuthorizationStatus.AuthorizedAlways)
_mapView.ShowsUserLocation = true;
else
_mapView.ShowsUserLocation = false;
}

Now you respond to the AuthorizationChanged Event and set the ShosUserLocation flag accordingly.


Edit (3/10/2014 17:00):
Now that I have had the chance to play a bit more with it, I have a couple of observations that might help you. First don't let go of the CLLocationManager instance, i.e. have it as a class field otherwise the dialog will disappear pretty quickly and it won't let you get location updates.

I didn't mention that you will have to do some checks on what iOS version you run at now and only call this code when on iOS 8 and higher, to do this you can do something as follows.
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
_locationManager = new CLLocationManager();
_locationManager.AuthorizationChanged += LocationManagerOnAuthorizationChanged;
_locationManager.TryRequestWhenInUseAuthorization();
}
else
{
_mapView.ShowsUserLocation = ViewModel.LocationPermission;
}
view raw iOS8Check.cs hosted with ❤ by GitHub

Port of Floating Action Button

|
Android L has introduced something called a Floating Action Button (FAB), which is basically a circle overlayed on top your application. This FAB can be clicked to do an action. In the Google+ application this is used to persistently allow the user to create a new post.


It basically looks like in the screenshot above, where you can see a circle with a play icon at the bottom of the screen. Faiz Malkani backported this from Android L to work on JellyBean and KitKat, and I took the liberty to port it to Xamarin.Android.

You can find the repository on GitHub along with a sample application, which the screenshot above is from.

Xamarin.Forms and MvvmCross

|
As promised during the Xamarin 3 webinar, I have made a small sample showing how to use Xamarin.Forms along with MvvmCross.



What I did was look at the code Stuart Lodge already had made in another private repository, and adapted this to use it with Xamarin.Forms. What it basically does is implement custom Presenter implementations, which work with the way Xamarin.Forms navigates its Pages, which is through a NavigationPage and simple Push and Pop methods. This is wrapped in a PageNavigationProvider in MvvmCross which is in turn leveraged by the custom presenter.

Right now the sample has these presenters and MvvmCross specific classes built in, but will most likely in the future be pulled out and made into a plugin, which you can use in your own applications.

So the sample itself is a very simple application, which allows you to search TMDB for movies, it then fetches similar movies based on your search query and displays them. In turn you can then press an item in the list presenting the search results and get detailed information about the movie.

The sample actually covers a lot of MvvmCross by using:

  • ViewModels
  • Services
  • Custom Presenters
  • Bindings
  • Commands
  • Converters
  • Nuget
You can get the sample on Github