Working with the Attachment Manager
The attachment manager greatly simplifies working with attachments. It provides a convenient, coarse-grained API to retrieve, add, and delete attachments. An attachment manager builds on the functionality provided by a feature layer to manage attachments and does all the heavy-lifting for you behind the scenes.
Getting an attachment manager
An attachment manager is associated with a single feature belonging to a feature layer. All attachments belonging to this feature can be managed through the attachment manager. To get a handle to an attachment manager for a feature, you need to invoke the attachmentManagerForFeature: method on AGSFeatureLayer.
//The feature layer from which we will get an attachment manager
AGSFeatureLayer* featureLayer = ...;
//The ID of the feature whose attachment manager we want
int fid = ...;
AGSGraphic* feature = [featureLayer lookupFeatureWithObjectId:fid];
AGSAttachmentManager* attMgr = [featureLayer attachmentManagerForFeature: feature];
//clear all attachment managers
[featureLayer clearAttachmentManagers];
//or clear a specific one
[featureLayer clearAttachmentManagerForFeatureWithObjectId:fid];
When an attachment manager is cleared, any local changes it is holding on to are lost. You can invoke hasLocalEdits on an AGSAttachmentManager to check if it has any local changes before clearing it.
Retrieving attachments
Once you have a handle to a feature's attachment manager, you can use it to retrieve attachments belonging to that feature. Retrieving attachments is a 2 step process.
1. Retrieving attachment metadata
You first need to retrieve metadata about the feature's attachments by invoking downloadAttachmentInfos on AGSAttachmentManager.
[attMgr downloadAttachmentInfos];
Metadata contains information about how many attachments a feature has (if any), the IDs of those attachments, name, etc. This metadata is captured in AGSAttachmentInfo objects.
The attachment manager's delegate is notified via the attachmentManager:didDownloadAttachmentInfos: method when the operation completes.
If metadata is retrieved successfully , AGSAttachmentInfo objects containing the metadata are passed into this method. Also, the attachments property on AGSAttachmentManager is also populated with empty AGSAttachment objects containing only metadata.
If an error is encountered, the downloadAttachmentInfosError property on AGSAttachmentManager is populated providing more information about the error. A delegate should always inspect this property and handle errors appropriately.
2. Retrieving attachment data
For each attachment whose data you want to retrieve, you need to invoke downloadAttachmentDataForId: on AGSAttachmentManager. You need to pass in the ID of the attachment. The ID is available in the attachment metadata.
//The attachment whose data to download
AGSAttachment* att = ...;
[attMgr downloadAttachmentDataForId:att.attachmentInfo.attachmentId];
The attachment manager's delegate is notified via the attachmentManager:didDownloadDataForAttachment: method when the operation completes.
If data is retrieved successfully, you can invoke the data method on individual AGSAttachment objects to get their raw data. In order to conserve memory, an attachment manager persists the data for each attachment to temporary files on disk. These files are automatically cleaned up when the attachment manager is cleared.
If an error is encountered, the networkError property on individual AGSAttachment objects is populated providing more information about the error. A delegate should always inspect this property and handle errors appropriately.
To cancel any outstanding download operations, be it to retrieve attachment metadata or the actual attachment data, you can invoke cancelAllDownloadDataOperations , cancelDownloadDataForId: , or cancleDownloadAttachmentInfos methods on AGSAttachmentManager
Adding attachments
To add a new attachment for a feature, you need to invoke the appropriate addAttachment... method on AGSAttachmentManager.
//Adding image
UIImage* image = ...;
[attMgr addAttachmentAsJpgWithImage:image name:@"My House"];
//Adding any binary data, for instance, pdf
NSData* data = ...;
[attMgr addAttachmentWithData:data name:@"Deed" contentType:@"application/pdf"];
When the method completes, an AGSAttachment object representing the attachment is returned. The object is also added to the attachment manager's attachments list. The object's editState property is set to AGSAttachmentEditStateAdded.
Note, at this point, the attachment exists only on the device. If the application is killed or the device powers down, the attachment will be lost. To save the new attachment on the server, you need to post it as described in the Committing changes to the server section below. After the attachment is saved on the server, the attachment object's editState property is set to AGSAttachmentEditStateOriginal.
Deleting attachments
To delete an existing attachment for a feature, you need to invoke markAttachment:forDeletion: on AGSAttachmentManager.
//attachment to be deleted
AGSAttachment* att = ...;
[attMgr markAttachment:att forDeletion:YES];
When this method completes, the attachment is only marked for deletion - it is not actually deleted. The attachment object's editState property is set to AGSAttachmentEditStateMarkedForDeletion.
To completely delete the attachment, you need to post the edits to the server as described in the Committing changes to the server section below. When the changes are successfully committed, the attachment is deleted both on the server and locally, and the attachment object's editState property is set to AGSAttachmentEditStateDeletedFromServer.
Commiting changes to the server
When you add attachments or mark them for deletion, the attachment manager keeps track of the changes you make and holds on to them locally. If the application is killed or the device powers down, the changes will be lost. If you want to persist the changes on the server, you need to invoke postLocalEditsToServer on AGSAttachmentManager.
//Only sync if the attachment manager contains local edits
if([attMgr hasLocalEdits])
[attMgr postLocalEditsToServer];
The postingLocalEdits property on AGSAttachmentManager is set while changes are being committed to the server. You can attach a key-value observer to this property to show an activity indicator to the user.
When the operation completes, the attachment manager's delegate is notified via the attachmentManager:didPostLocalEditsToServer: method.
If an error is encountered while posting the edits, the networkError or editResultError properties on individual AGSAttachment objects are set depending on whether a network error prevented the operation from succeeding, or if an underlying database error occurred on the server. A delegate should always inspect these properties and handle errors appropriately.
-(void)attachmentManager:(AGSAttachmentManager *)attachmentManager didPostLocalEditsToServer:(NSArray *)attachmentsPosted{
//loop through all attachments looking for failures
BOOL _anyFailure = NO;
for (AGSAttachment* attachment in attachmentsPosted) {
if(attachment.networkError!=nil || attachment.editResultError!=nil){
_anyFailure = YES;
NSString* reason;
if(attachment.networkError!=nil)
reason = [attachment.networkError localizedDescription];
else if(attachment.editResultError !=nil)
reason = attachment.editResultError.errorDescription;
NSLog(@"Attachment '%@' could not be synced with server because %@",attachment.attachmentInfo.name,reason);
}
}
if(_anyFailure){
//warn the user
}
}
If you want to cancel an operation in progress, you can invoke cancelPostLocalEditsToServer. Note, some edits may already have been committed to the server when you call this method. Those will not be undone. Only the uncommitted edits will be cancelled.
If you want to undo any local edits to the attachments that have not yet been posted, you can invoke cancelLocalEdits . This will revert the attachments to their original state before any attachments were added or marked for deletion.
See also
Feature Layer Editing Sample using popups.