Skip to content

Instantly share code, notes, and snippets.

@pragmatrix
Created September 9, 2012 17:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pragmatrix/3686110 to your computer and use it in GitHub Desktop.
Save pragmatrix/3686110 to your computer and use it in GitHub Desktop.
ALAssetsLibrary: export to group for MonoTouch, tested on iOS 5.1 - iOS 8.0.2
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using MonoTouch.UIKit;
using MonoTouch.AssetsLibrary;
using MonoTouch.Foundation;
namespace LookHere.Exporting
{
public struct PhotoExportResult
{
public PhotoExportResult(NSError error)
{
Error_ = error.ToString();
}
public PhotoExportResult(string error)
{
Error_ = error;
}
public readonly string Error_;
}
public sealed class PhotoExportService
{
readonly ALAssetsLibrary _library = new ALAssetsLibrary();
public event Action<PhotoExportResult> Error;
public static void asyncExportToGroup(UIImage image, string group, Action<PhotoExportResult> result)
{
var exportSession = new PhotoExportService();
exportSession.Error += result;
exportSession.asyncExportToGroup(image, group, () => result(new PhotoExportResult()));
}
void asyncExportToGroup(UIImage image, string group, Action done)
{
ensureGroupExists(group,
groupUrl => writeImageToSavedPhotos(image,
imageUrl => copyToGroup(imageUrl, groupUrl, done)));
}
void writeImageToSavedPhotos(UIImage image, Action<NSUrl> result)
{
_library.WriteImageToSavedPhotosAlbum(image.CGImage,
(ALAssetOrientation) image.Orientation,
(url, error) =>
{
track("WriteImageToSavedPhotosAlbum Callback");
if (error != null)
{
asyncNotifyResult(error);
return;
}
On.MainThread(() => result(url));
});
}
void ensureGroupExists(string group, Action<NSUrl> callback)
{
tryGetGroupURL(group,
url_ =>
{
if (url_ != null)
callback(url_);
else
createGroup(group, () => tryGetGroupURL(@group, callback));
});
}
// callback with null string: group does not exist.
void tryGetGroupURL(string group, Action<NSUrl> callback)
{
Debug.Assert(NSThread.Current.IsMainThread);
ALAssetsGroup foundGroup_ = null;
_library.Enumerate(ALAssetsGroupType.Album,
(ALAssetsGroup g, ref bool stop) =>
{
track("Enumerate Callback");
if (g == null)
{
if (foundGroup_ == null)
On.MainThread(() => callback(null));
stop = true;
return;
}
if (g.Name != group)
{
stop = false;
return;
}
foundGroup_ = g;
On.MainThread(() => callback(g.PropertyUrl));
stop = true;
},
asyncNotifyResult);
}
void createGroup(string group, Action done)
{
_library.AddAssetsGroupAlbum(group, g => On.MainThread(done), asyncNotifyResult);
}
void copyToGroup(NSUrl imageAsset, NSUrl groupAsset, Action done)
{
Debug.Assert(NSThread.Current.IsMainThread);
var collector = new AssetCollector(_library);
collector.addAsset(imageAsset);
collector.addGroup(groupAsset);
// should work with 1 retry only, but just to be safe.
collector.tryCallWithValidAssets(4,
table =>
{
Debug.Assert(NSThread.Current.IsMainThread);
// yeah, ugly, but must be done!
collector.Dispose();
if (table == null)
{
asyncNotifyResult(new PhotoExportResult("failed to resolve assets"));
return;
}
var group = table.groupForUrl(groupAsset);
var asset = table.assetForUrl(imageAsset);
if (!group.AddAsset(asset))
{
asyncNotifyResult(new PhotoExportResult("failed to add asset to group"));
return;
}
done();
});
}
void asyncNotifyResult(NSError error)
{
asyncNotifyResult(new PhotoExportResult(error));
}
void asyncNotifyResult(PhotoExportResult result)
{
On.MainThread(() =>
{
Debug.Assert(NSThread.Current.IsMainThread);
if (Error != null)
Error(result);
});
}
static void track(string where)
{
Console.WriteLine(where + " thread: " + Thread.CurrentThread.ManagedThreadId);
}
}
sealed class AssetCollector : IDisposable
{
readonly ALAssetsLibrary _library;
readonly IDisposable _notification;
readonly object _syncRoot = new object();
uint _libraryVersion;
public AssetCollector(ALAssetsLibrary library)
{
_library = library;
_notification = ALAssetsLibrary.Notifications.ObserveChanged(libraryChanged);
}
public void Dispose()
{
_notification.Dispose();
}
void libraryChanged(object _, NSNotificationEventArgs __)
{
lock (_syncRoot)
++_libraryVersion;
debug("library changed");
}
enum AssetType
{
Asset,
Group
}
readonly Dictionary<NSUrl, AssetType> _assets = new Dictionary<NSUrl, AssetType>();
public void addAsset(NSUrl url)
{
_assets.Add(url, AssetType.Asset);
}
public void addGroup(NSUrl url)
{
_assets.Add(url, AssetType.Group);
}
public void tryCallWithValidAssets(uint retries, Action<AssetLookupTable> callback)
{
uint version;
lock (_syncRoot)
version = _libraryVersion;
tryCallWithValidAssets(retries, version, _assets.ToDictionary(kv => kv.Key, kv => (object)null), callback);
}
public void tryCallWithValidAssets(uint retries, uint version, Dictionary<NSUrl, object> table, Action<AssetLookupTable> callback)
{
Debug.Assert(NSThread.Current.IsMainThread);
foreach (var kv in table)
{
var url = kv.Key;
if (kv.Value != null)
continue;
switch (_assets[kv.Key])
{
case AssetType.Asset:
_library.AssetForUrl(kv.Key, asset =>
{
table[url] = asset;
On.MainThread(() => tryCallWithValidAssets(retries, version, table, callback));
},
error => On.MainThread(() => callback(null)));
break;
case AssetType.Group:
_library.GroupForUrl(kv.Key, asset =>
{
table[url] = asset;
On.MainThread(() => tryCallWithValidAssets(retries, version, table, callback));
},
error => On.MainThread(() => callback(null)));
break;
}
return;
}
bool libraryValidNow;
lock (_syncRoot)
libraryValidNow = _libraryVersion == version;
if (!libraryValidNow)
{
if (retries == 0)
{
debug("library invalid, no more retries, failed");
callback(null);
}
else
{
debug("library invalid, retrying");
tryCallWithValidAssets(retries - 1, callback);
}
return;
}
callback(new AssetLookupTable(table));
}
[Conditional("DEBUG")]
void debug(string what)
{
Console.WriteLine(GetType().Name + ": " + what);
}
}
sealed class AssetLookupTable
{
readonly Dictionary<NSUrl, object> _table = new Dictionary<NSUrl, object>();
public AssetLookupTable(Dictionary<NSUrl, object> table)
{
_table = table;
}
public ALAsset assetForUrl(NSUrl url)
{
return (ALAsset)_table[url];
}
public ALAssetsGroup groupForUrl(NSUrl url)
{
return (ALAssetsGroup) _table[url];
}
}
static class On
{
public static void MainThread(Action action)
{
if (NSThread.Current.IsMainThread)
action();
else
NSThread.Current.InvokeOnMainThread(new NSAction(action));
}
}
}
@dmalikyar
Copy link

This class nicely encapsulates and simplifies the creation of a new album as well as saving photos to it. The only issue I'm having is retrieving the photo I just took. Since the process is asynchronous, I assumed that if the result callback is successful, then the image was successfully saved in the album. This isn't always the case. Sometimes I get the newly saved image back and other times I don't. Is there a reliable way of getting the just saved image back?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment