GMImagePicker ported to Xamarin.iOS

TL;DR
I ported GMImagePicker to C#. Code here, Nuget here. Happy coding!

This past week I was working on a Xamarin project where we need support for selecting multiple images and/or taking pictures and uploading them to a backend service. The default UIImagePicker control in iOS is ok but not very versatile. It can take a picture with the camera, or lets you select a single image from your gallery but that’s about it. Furthermore, working with the resulting images is quite cumbersome as you’re working with large UIImage objects in memory. A lot of performance and memory issues can happen if you’re not careful.

In order to deal with photos and videos more efficiently and in a much richer manner, Apple introduced the PhotoKit API in iOS 8. Mike Bluestein has written a nice introductory post on this API on the Xamarin blog, so I’m not going to repeat this.

In short, PhotoKit works with the notion of PHAsset objects, which are basically descriptors for media files on the device. There are API’s to query different photo galleries, etcetera. Only once you actually need the image for display or other purposed do you have to retrieve the image using the PHAsset descriptor. Very efficient.

Many apps, such as the Facebook app, allow users to select multiple images in a user friendly manner, and maybe even add pictures on the go by providing access to the camera while they are browsing their gallery. This is something that we also wanted to add to our app. Luckily, there are some nice open source projects around that implement just that. One of the nicest ones is the GMImagePicker component by Guillermo Muntaner Perelló, which uses the PhotoKit API under the hood. The user experience looks like this:

gmimagepickerdemo

That’s slick! You can browse through several collections, and the control is highly customizable, and even contains localized texts for labels, buttons and dialogs. Only, it’s written in Objective-C…

I had two options: bind the API using Xamarin’s Objective Sharpie or port it verbatim to C#. I chose to port it, mainly to have full control over the inner workings of the control and to not have to pull in a “foreign” language into the project. The port has complete feature parity with the Objective-C version and I tried to iron out as many issues as I could. It seems to be working pretty smoothly in my Xamarin app.

The code is up on GitHub and you can use the control by either downloading the code and including the .csproj in your project, or install the Nuget package in your Xamarin.iOS app:

[code language=”powershell”]
Install-Package GMImagePicker.Xamarin
[/code]

As I said, the GMImagePicker control is highly customizable. You can change its appearance by specifying colors for different parts of the UI, and you can provide custom titles and confirmation prompts. It’s also possible to filter and limit the types of assets you want the user to select. The whole range of options can be found in the sample app that comes with the control. Here is an overview:

[code language=”csharp”]
var picker = new GMImagePickerController {
Title = "Custom Title",
CustomDoneButtonTitle = "Finished",
CustomCancelButtonTitle = "Nope",
CustomNavigationBarPrompt = "Take a new photo or select an existing one!",
ColsInPortrait = 3,
ColsInLandscape = 5,
MinimumInteritemSpacing = 2.0f,
DisplaySelectionInfoToolbar = true,
AllowsMultipleSelection = true,
ShowCameraButton = true,
AutoSelectCameraImages = true,
ModalPresentationStyle = UIModalPresentationStyle.Popover,
MediaTypes = new [] { PHAssetMediaType.Image },
// Other customizations to play with:
//ConfirmSingleSelection = true,
//ConfirmSingleSelectionPrompt = "Do you want to select the image you have chosen?",
//PickerBackgroundColor = UIColor.Black,
//PickerTextColor = UIColor.White,
//ToolbarBarTintColor = UIColor.DarkGray,
//ToolbarTextColor = UIColor.White,
//ToolbarTintColor = UIColor.Red,
//NavigationBarBackgroundColor = UIColor.Black,
//NavigationBarTextColor = UIColor.White,
//NavigationBarTintColor = UIColor.Red,
//PickerFontName = "Verdana",
//PickerBoldFontName = "Verdana-Bold",
//PickerFontNormalSize = 14.0f,
//PickerFontHeaderSize = 17.0f,
//PickerStatusBarStyle = UIStatusBarStyle.LightContent,
//UseCustomFontForNavigationBar = true,
};

// You can limit which galleries are available to browse through
picker.CustomSmartCollections = new [] {
PHAssetCollectionSubtype.SmartAlbumUserLibrary,
PHAssetCollectionSubtype.AlbumRegular
};

// Event handling
picker.FinishedPickingAssets += PickerFinishedPickingAssets;
picker.Canceled += Picker
Canceled;

// Other events to implement in order to influence selection behavior:
// Set EventArgs::Cancel flag to true in order to prevent the action from happening
picker.ShouldDeselectAsset += (s, e) => { /* allow deselection of (mandatory) assets / };
picker.ShouldEnableAsset += (s, e) => { /
determine if a specific asset should be enabled / };
picker.ShouldHighlightAsset += (s, e) => { /
determine if a specific asset should be highlighted / };
picker.ShouldShowAsset += (s, e) => { /
determine if a specific asset should be displayed / };
picker.ShouldSelectAsset += (s, e) => { /
determine if a specific asset can be selected / };
picker.AssetSelected += (s, e) => { /
keep track of individual asset selection / };
picker.AssetDeselected += (s, e) => { /
keep track of individual asset de-selection */ };

// The GMImagePicker can be treated as a PopOver as well:
var popPC = picker.PopoverPresentationController;
popPC.PermittedArrowDirections = UIPopoverArrowDirection.Any;
popPC.SourceView = gmImagePickerButton;
popPC.SourceRect = gmImagePickerButton.Bounds;

await PresentViewControllerAsync(picker, true);
[/code]

The extensibility is very convenient. For example: if you want to set a maximum to the total size of the images you want to allow the user to select, you can handle the AssetSelected event, keep track of the total size selected, and handle ShouldSelectAsset, to prevent selection if a maximum threshold has been reached. This is exactly what we wanted to have in our app.

Once the user has finished selecting assets, you can use a PHImageManager to retrieve the actual images in whatever size you like:

[code language=”csharp”]
void FinishedPickingAssets (object s, MultiAssetEventArgs e)
{
PHImageManager imageManager = new PHImageManager();

foreach (var asset in e.Assets) {
imagePreview.Image = null;

imageManager.RequestImageForAsset (asset,
new CGSize(asset.PixelWidth, asset.PixelHeight),
PHImageContentMode.Default,
null,
(image, info) => {
// do something with the image (UIImage), e.g. upload to server
// you can get the JPEG byte[] via image.AsJPEG()
});
}
}
[/code]

Very nice, and it’s now available for Xamarin.iOS developers as well 🙂

photo-1437419764061-2473afe69fc2
Photo by Andrew Illarionov (https://unsplash.com/photos/-WW8jBak7bo)
Many thanks to Guillermo for letting me port his excellent code and publish it. I’d love to hear your feedback on the code and would love to see your PR’s for improvements.

Share this: