Some time back, someone asked on the Embarcadero forums about using ALAssetsLibrary from the Assets Library framework in iOS with Delphi. I was curious because I thought I may be interested in using it myself.

It seemed relatively simple enough: create an Assets Library instance (TALAssetsLibrary from the iOSapi.AssetsLibrary unit), enumerate the groups, and for each group, enumerate the assets. That is until I discovered it wasn’t possible to move past 1st base: calling enumerateGroupsWithTypes was (at the time) causing a crash.

Recently I decided to revisit the issue, and this time the problem was an Invalid Typecast error. After a little digging, I came across someone in a similar situation in this post on stack overflow. He solved the problem by changing the definition for the “block” so that the method parameters all have pointer types.

I changed the method parameters to pointer types myself, and voila! I was able to call enumerateGroupsWithTypes without an error: made it past first base. Next step was to see what I actually have in those pointers. I figured the ALAssetsGroup parameter reference might be an objective-c object id, so I Wrap’d it using TALAssetsGroup, and I was able to successfully use the valueForProperty method. Success! It also turns out that the PBoolean really is one: it just can’t be passed via the block that way; it needs to be typecast inside the method.

The next problem is that similar action needs to be taken for ALAssetsGroup and ALAsset, both of which use block parameters.

Now for the really bad news: ALAssetsLibrary is deprecated (since iOS 9). The preferred method is to use Photos Framework, for which there is no translation available (at least out of the box) for Delphi. I have this task on my to-do list, however it is moving ever further downwards as other tasks are becoming more pressing.

Regardless, here’s some example code fragments that will give those who are also looking to use it, to give them a “leg up”:

  iOSapi.AssetsLibrary, iOSapi.Foundation, Macapi.ObjectiveC;

  // A copy of what's in iOSapi.AssetsLibrary, but changed to Pointer parameters
  TALAssetsLibraryGroupsEnumerationResultsBlock = procedure(group: Pointer; stop: Pointer) of object;
  TALAssetsLibraryAccessFailureBlock = procedure(error: Pointer) of object;

  ALAssetsLibrary = interface(NSObject)
    procedure enumerateGroupsWithTypes(types: ALAssetsGroupType; usingBlock: TALAssetsLibraryGroupsEnumerationResultsBlock; failureBlock: TALAssetsLibraryAccessFailureBlock); cdecl;
    procedure assetForURL(assetURL: NSURL; resultBlock: TALAssetsLibraryAssetForURLResultBlock; failureBlock: TALAssetsLibraryAccessFailureBlock); cdecl;
    procedure groupForURL(groupURL: NSURL; resultBlock: TALAssetsLibraryGroupResultBlock; failureBlock: TALAssetsLibraryAccessFailureBlock); cdecl;
    procedure addAssetsGroupAlbumWithName(name: NSString; resultBlock: TALAssetsLibraryGroupResultBlock; failureBlock: TALAssetsLibraryAccessFailureBlock); cdecl;
    procedure writeImageToSavedPhotosAlbum(imageRef: CGImageRef; orientation: ALAssetOrientation; completionBlock: TALAssetsLibraryWriteImageCompletionBlock); cdecl; overload;
    procedure writeImageToSavedPhotosAlbum(imageRef: CGImageRef; metadata: NSDictionary; completionBlock: TALAssetsLibraryWriteImageCompletionBlock); cdecl; overload;
    procedure writeImageDataToSavedPhotosAlbum(imageData: NSData; metadata: NSDictionary; completionBlock: TALAssetsLibraryWriteImageCompletionBlock); cdecl;
    procedure writeVideoAtPathToSavedPhotosAlbum(videoPathURL: NSURL; completionBlock: TALAssetsLibraryWriteVideoCompletionBlock); cdecl;
    function videoAtPathIsCompatibleWithSavedPhotosAlbum(videoPathURL: NSURL): Boolean; cdecl;
  TALAssetsLibrary = class(TOCGenericImport<ALAssetsLibraryClass, ALAssetsLibrary>)end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    FAL: ALAssetsLibrary;
    procedure GroupsEnumerationResults(group: Pointer; stop: Pointer);
    procedure AccessFailure(error: Pointer);
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

  Form1: TForm1;


{$R *.fmx}


{ TForm1 }

constructor TForm1.Create(AOwner: TComponent);
  FAL := TALAssetsLibrary.Create;

destructor TForm1.Destroy;

procedure TForm1.AccessFailure(error: Pointer);

procedure TForm1.GroupsEnumerationResults(group: Pointer; stop: Pointer);
  LGroup: ALAssetsGroup;
  LName: NSString;
  LNameString: string;
  LStop: Boolean;
  LStop := PBoolean(stop)^;
  if not LStop then   // I put a break point here to check the value of LStop
  LGroup := TALAssetsGroup.Wrap(group);
  LName := TNSString.Wrap(LGroup.valueForProperty(CocoaNSStringConst(libAssetsLibrary, 'ALAssetsGroupPropertyName')));
  LNameString := NSStrtoStr(LName);
  if LNameString = '' then  // I put a break point here too to check LNameString

procedure TForm1.Button1Click(Sender: TObject);
  FAL.enumerateGroupsWithTypes(ALAssetsGroupAll, GroupsEnumerationResults, AccessFailure);