#ios #iphone #uiimageview #alassetslibrary #ios8
#iOS #iPhone #uiimageview #alassetslibrary #ios8
Вопрос:
Я рассматриваю возможность замены ALAssetsLibrary
Photos
рамки в моем приложении.
Я могу просто отлично извлекать фотографии, коллекции и источники ресурсов (даже записывать их обратно), но нигде не вижу доступа к метаданным фотографий (словари, такие как {Exif}, {TIFF}, {GPS} и т. Д.).
ALAssetsLibrary
есть способ. UIImagePickerController
есть способ. Photos
тоже должен быть способ.
Я вижу, что PHAsset
у location
этого есть свойство, которое подойдет для словаря GPS, но я хочу получить доступ ко всем метаданным, которые включают лица, ориентацию, экспозицию, ISO и многое другое.
В настоящее время Apple находится на стадии бета-версии 2. Возможно, появятся новые API?
Обновить
Официального способа сделать это, используя только API-интерфейсы Photos, не существует.
Однако вы можете прочитать метаданные после загрузки данных изображения. Есть несколько способов сделать это, используя либо PHImageManager
или PHContentEditingInput
.
Этот PHContentEditingInput
метод требует меньше кода и не требует импорта ImageIO
. Я поместил его в категорию PHAsset.
Комментарии:
1. Выяснили ли вы, есть ли способ сделать это без загрузки данных изображения?
2. Я проверил вашу категорию, но requestMetadataWithCompletionBlock не возвращает метаданные для видео. Есть ли какой-либо другой способ получить метаданные видео без загрузки видео
Ответ №1:
Если вы запрашиваете ввод для редактирования содержимого, вы можете получить полное изображение в виде CIImage
, и CIImage
имеет свойство под названием properties
, которое является словарем, содержащим метаданные изображения.
Пример кода Swift:
let options = PHContentEditingInputRequestOptions()
options.networkAccessAllowed = true //download asset metadata from iCloud if needed
asset.requestContentEditingInputWithOptions(options) { (contentEditingInput: PHContentEditingInput?, _) -> Void in
let fullImage = CIImage(contentsOfURL: contentEditingInput!.fullSizeImageURL)
print(fullImage.properties)
}
Пример кода Objective-C.:
PHContentEditingInputRequestOptions *options = [[PHContentEditingInputRequestOptions alloc] init];
options.networkAccessAllowed = YES; //download asset metadata from iCloud if needed
[asset requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
CIImage *fullImage = [CIImage imageWithContentsOfURL:contentEditingInput.fullSizeImageURL];
NSLog(@"%@", fullImage.properties.description);
}];
Вы получите нужные словари {Exif}, {TIFF}, {GPS} и т. Д.
Комментарии:
1. Очень круто. Я могу читать свойства, как вы показали. Попробую записать их тоже.
2. Если вы сможете их выписать, пожалуйста, дайте мне знать!
3. По-видимому, это самый быстрый способ выполнить работу. Это также намного меньше кода, чем в моем примере (загрузка данных изображения), и эта версия не зависит от ImageIO. Принятый ответ
4. Спасибо тебе, @Joey! Я создал gist на основе вашего кода, чтобы поделиться им со своей командой. Я также хотел бы поделиться им здесь: swift-фотографии-метаданные
5. @Benjohn Это решение требует загрузки полного изображения, если оно еще не доступно локально, т.Е. Хранится в iCloud
Ответ №2:
Я подумал, что поделюсь некоторым кодом для чтения метаданных с использованием фреймворка ImageIO в сочетании с фреймворком Photos. Вы должны запросить данные изображения с помощью PHCachingImageManager.
@property (strong) PHCachingImageManager *imageManager;
Запросите изображение и используйте его данные для создания словаря метаданных
-(void)metadataReader{
PHFetchResult *result = [PHAsset fetchAssetsInAssetCollection:self.myAssetCollection options:nil];
[result enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndex:myIndex] options:NSEnumerationConcurrent usingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
[self.imageManager requestImageDataForAsset:asset options:nil resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
NSDictionary *metadata = [self metadataFromImageData:imageData];
NSLog(@"Metadata: %@", metadata.description);
NSDictionary *gpsDictionary = metadata[(NSString*)kCGImagePropertyGPSDictionary];
if(gpsDictionary){
NSLog(@"GPS: %@", gpsDictionary.description);
}
NSDictionary *exifDictionary = metadata[(NSString*)kCGImagePropertyExifDictionary];
if(exifDictionary){
NSLog(@"EXIF: %@", exifDictionary.description);
}
UIImage *image = [UIImage imageWithData:imageData scale:[UIScreen mainScreen].scale];
// assign image where ever you need...
}];
}];
}
Преобразование NSData в метаданные
-(NSDictionary*)metadataFromImageData:(NSData*)imageData{
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL);
if (imageSource) {
NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:NO]};
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
if (imageProperties) {
NSDictionary *metadata = (__bridge NSDictionary *)imageProperties;
CFRelease(imageProperties);
CFRelease(imageSource);
return metadata;
}
CFRelease(imageSource);
}
NSLog(@"Can't read metadata");
return nil;
}
Это связано с накладными расходами на захват изображения, поэтому это происходит не так быстро, как перечисление ваших ресурсов или коллекций, но, по крайней мере, это что-то.
Комментарии:
1. В этом примере кода произошла утечка памяти; если
imageProperties
значение не равно нулю, метод вернется до его вызоваCFRelease(imageSource)
.
Ответ №3:
Я предпочитаю не решение CIImage, а решение ImageIO:
func imageAndMetadataFromImageData(data: NSData)-> (UIImage?,[String: Any]?) {
let options = [kCGImageSourceShouldCache as String: kCFBooleanFalse]
if let imgSrc = CGImageSourceCreateWithData(data, options as CFDictionary) {
let metadata = CGImageSourceCopyPropertiesAtIndex(imgSrc, 0, options as CFDictionary) as! [String: Any]
//print(metadata)
// let image = UIImage(cgImage: imgSrc as! CGImage)
let image = UIImage(data: data as Data)
return (image, metadata)
}
return (nil, nil)
}
ниже приведен код для получения данных из PHAseet
func getImageAndMeta(asset: PHAsset){
let options = PHImageRequestOptions()
options.isSynchronous = true
options.resizeMode = .none
options.isNetworkAccessAllowed = false
options.version = .current
var image: UIImage? = nil
var meta:[String:Any]? = nil
_ = PHCachingImageManager().requestImageData(for: asset, options: options) { (imageData, dataUTI, orientation, info) in
if let data = imageData {
(image, meta) = imageAndMetadataFromImageData(data: data as NSData)
//image = UIImage(data: data)
}
}
// here to return image and meta
}
Комментарии:
1. Интересно. Спасибо за совместное использование. Проверю это.
Ответ №4:
PhotoKit ограничивает доступ к метаданным свойствами PHAsset (местоположение, дата создания, избранное, скрытое, дата изменения, ширина пикселя, высота пикселя …). Причина (я подозреваю) заключается в том, что из-за внедрения iCloud PhotoLibrary изображений может не быть на устройстве. Поэтому все метаданные недоступны. Единственный способ получить полные метаданные EXIF / IPTC — это сначала загрузить исходное изображение (если оно недоступно) из iCloud, а затем использовать ImageIO для извлечения его метаданных.
Комментарии:
1. Да, это все, что можно сделать прямо сейчас. Я отправил сообщение об ошибке (фактически запрос функции) в Apple. Они ответили мне: «Спасибо, мы всегда ищем новые идеи». Я был бы удивлен, увидев это в 8.0. Жаль, однако. Это может обеспечить некоторую мощную фильтрацию. Они уже извлекают некоторые из них (например, местоположение). Они могли бы извлечь весь словарь и представить его как .metadata, как в ALAsset.defaultRepresentation , даже если он был доступен только для чтения.
2. Мне кажется, что PhotoKit все еще находится на самой ранней стадии. К сожалению, все еще существует много лазеек в функциональности. Новая концепция выборки, например, является мощной, но свойства, которые мы можем извлекать, очень ограничены.
Ответ №5:
Лучшее решение, которое я нашел и которое хорошо сработало для меня, это:
[[PHImageManager defaultManager] requestImageDataForAsset:photoAsset
options:reqOptions
resultHandler:
^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
CIImage* ciImage = [CIImage imageWithData:imageData];
DLog(@"Metadata : %@", ciImage.properties);
}];
Комментарии:
1. Вы нашли способ записать изменения метаданных обратно в исходный источник?
2. Учитывая производительность и использование памяти, ImageIO по-прежнему предпочтительнее.
3. Можно ли перевести этот код на Swift? Я не могу.
Ответ №6:
Вы можете изменить PHAsset (например, добавить метаданные о местоположении), используя Photos Framework и метод UIImagePickerControllerDelegate. Нет накладных расходов из сторонних библиотек, не создаются дубликаты фотографий. Работает для iOS 8.0
В методе делегирования didFinishPickingMediaWithInfo вызовите UIImageWriteToSavedPhotosAlbum, чтобы сначала сохранить изображение. Это также создаст PHAsset, данные EXIF GPS которого мы будем изменять:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if let myImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
UIImageWriteToSavedPhotosAlbum(myImage, self, Selector("image:didFinishSavingWithError:contextInfo:"), nil)
}
}
Функция выбора завершения будет запущена после завершения сохранения или сбоя с ошибкой. В обратном вызове извлеките только что созданный PHAsset. Затем создайте запрос PHAssetChangeRequest для изменения метаданных местоположения.
func image(image: UIImage, didFinishSavingWithError: NSErrorPointer, contextInfo:UnsafePointer<Void>) {
if (didFinishSavingWithError != nil) {
print("Error saving photo: (didFinishSavingWithError)")
} else {
print("Successfully saved photo, will make request to update asset metadata")
// fetch the most recent image asset:
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
let fetchResult = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: fetchOptions)
// get the asset we want to modify from results:
let lastImageAsset = fetchResult.lastObject as! PHAsset
// create CLLocation from lat/long coords:
// (could fetch from LocationManager if needed)
let coordinate = CLLocationCoordinate2DMake(myLatitude, myLongitude)
let nowDate = NSDate()
// I add some defaults for time/altitude/accuracies:
let myLocation = CLLocation(coordinate: coordinate, altitude: 0.0, horizontalAccuracy: 1.0, verticalAccuracy: 1.0, timestamp: nowDate)
// make change request:
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
// modify existing asset:
let assetChangeRequest = PHAssetChangeRequest(forAsset: lastImageAsset)
assetChangeRequest.location = myLocation
}, completionHandler: {
(success:Bool, error:NSError?) -> Void in
if (success) {
print("Succesfully saved metadata to asset")
print("location metadata = (myLocation)")
} else {
print("Failed to save metadata to asset with error: (error!)")
}
Комментарии:
1. Да, вы можете. Я поделился категорией в своем оригинальном сообщении, в которой есть удобный способ сделать это (похожий на ваш). Его блок вызывается с обновленным набором PHAsset. Никаких дубликатов. [местоположение обновления активов:дата создания местоположения: nil AssetBlock:(^AssetBlock)];