iOSの写真アプリの画像や動画のPHAssetがどのアプリから保存されたのかを調べる
Photos Frameworkを利用して、写真アプリに保存された画像や動画のPHAssetを取得し、iOSの写真アプリのように画像をリスト表示する機能を作る事がよくあります
自作した画像リストでも、iOSの写真アプリにある「〇〇から保存」という表示を実現できないか調べてみました
iOS 15.1で動作確認しています
(手元で動作確認しただけで、互換性など含めて諸々調べきれていないため、実際に使う場合はご注意ください🙇)
xattr を使って保存したアプリを調べる
Photos Framework には、PHAssetを保存したアプリの情報を提供する機能は見当たりませんでした
しかし、保存されたファイルのExtended Attributesの com.apple.assetsd.creatorBundleID
に保存したアプリのBundle IDが設定されていました
そこで、xattrを使って、Extended Attributesを取得して、保存したアプリを調べます
xattr を使って Extended Attributes を取得する
listxattr
とgetxattr
をSwiftから使いやすいようにWrapperを作成しました
public struct XattrError: Error {
private let str: String? = String(utf8String: strerror(errno))
public var localizedDescription: String { str ?? "unknown" }
}
public enum xattr {
public static func list(from url: URL) throws -> [String] {
let path = url.path
let len = listxattr(path, nil, 0, 0)
guard len > 0 else { throw XattrError() }
let namebuff = UnsafeMutablePointer<CChar>.allocate(capacity: len)
defer { namebuff.deallocate() }
let res = listxattr(path, namebuff, len, 0)
guard res > 0 else { throw XattrError() }
guard let str = NSString(bytes: namebuff, length: len, encoding: String.Encoding.utf8.rawValue) else {
throw XattrError()
}
var strs = str.components(separatedBy: "\0")
strs.removeLast()
return strs
}
public static func get(from url: URL, name: String) throws -> Data {
let path = url.path
let len = getxattr(path, name, nil, 0, 0, 0)
guard len > 0 else { throw XattrError() }
let value = UnsafeMutableRawPointer.allocate(byteCount: len, alignment: MemoryLayout<UnsafeMutableRawPointer.Pointee>.alignment)
defer { value.deallocate() }
let res = getxattr(path, name, value, len, 0, 0)
guard res > 0 else { throw XattrError() }
let data = Data(bytes: value, count: len)
return data
}
}
この xattrを使って、指定したURLのファイルのExtended Attributesに com.apple.assetsd.creatorBundleID
が含まれていた場合は、Dataを取得して文字列として表示します
let url: URL
let list = try! xattr.list(from: url)
guard list.contains("com.apple.assetsd.creatorBundleID") else { return }
let creatorBundleID = try! xattr.get(from: url, name: "com.apple.assetsd.creatorBundleID")
print("creatorBundleID: \(String(data: creatorBundleID, encoding: .utf8)!)")
PHAsset のファイルの URL を取得する
前項のxattrはファイルのURLを対象としているため、PHAssetのファイルのURLを取得します
適当にファイルのURLを取得して、Extended Attributesを表示させます
let options = PHFetchOptions()
options.sortDescriptors = [.init(key: "creationDate", ascending: false)]
let assets = PHAsset.fetchAssets(with: options)
assets.enumerateObjects { (obj: PHAsset, idx: Int, stopPtr: UnsafeMutablePointer<ObjCBool>) in
getUrl(asset: obj) { (url: URL) in
print("obj: \(obj)")
print("url: \(url)")
let list = try! xattr.list(from: url)
print("list: \(list)")
guard list.contains("com.apple.assetsd.creatorBundleID") else { return }
let creatorBundleID = try! xattr.get(from: url, name: "com.apple.assetsd.creatorBundleID")
print("creatorBundleID: \(String(data: creatorBundleID, encoding: .utf8)!)")
}
}
func getUrl(asset: PHAsset, completion: @escaping (URL) -> Void) {
switch asset.mediaType {
case .image:
let options = PHContentEditingInputRequestOptions()
asset.requestContentEditingInput(with: options) { (contentEditingInput: PHContentEditingInput?, info: [AnyHashable : Any]) in
completion(contentEditingInput!.fullSizeImageURL!)
}
case .video:
let options = PHVideoRequestOptions()
PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { (asset: AVAsset?, audioMix: AVAudioMix?, info: [AnyHashable : Any]?) in
let urlAsset = asset as! AVURLAsset
completion(urlAsset.url)
}
default:
break
}
}
結果
PHAssetのURLとExtended Attributesのリストと、com.apple.assetsd.creatorBundleID
のプロパティを取得できました
url: file:///var/mobile/Media/DCIM/XXXAPPLE/IMG_XXX.MP4
list: ["com.apple.assetsd.UUID", "com.apple.assetsd.addedDate", "com.apple.assetsd.assetType", "com.apple.assetsd.avalanche.type", "com.apple.assetsd.cloudAsset.UUID", "com.apple.assetsd.creatorBundleID", "com.apple.assetsd.customCreationDate", "com.apple.assetsd.dbRebuildUuid", "com.apple.assetsd.deferredProcessing", "com.apple.assetsd.favorite", "com.apple.assetsd.hidden", "com.apple.assetsd.importedBy", "com.apple.assetsd.importedByDisplayName", "com.apple.assetsd.originalFilename", "com.apple.assetsd.publicGlobalUUID", "com.apple.assetsd.sceneAnalysisIsFromPreivew", "com.apple.assetsd.syndicationHistory", "com.apple.assetsd.timeZoneName", "com.apple.assetsd.timeZoneOffset", "com.apple.assetsd.trashed", "com.apple.assetsd.videoComplementVisibility"]
creatorBundleID: hoge.fuga.piyo.bundle.identifier
まとめ
iOSの写真アプリの画像や動画のPHAssetがどのアプリから保存されたのかを調べる方法を調査してみました
最初は、Photos.sqlite を直接読み込むのも検討していましたが、結果的に比較的シンプルに画像を保存したアプリを調べる事ができました
最初にも書きましたが、互換性など含めて諸々調べきれていないので、実際に使う場合はご注意頂けると幸いです