Created
September 9, 2012 17:55
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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?