import UIKit
import Photos
/// UITableViewCellにプロパティーを追加。IBでコネクトする。
class MyTableViewCell: UITableViewCell {
@IBOutlet var thumbnail: UIImageView?
@IBOutlet var fileName: UILabel?
@IBOutlet var creationDate: UILabel?
@IBOutlet var resolution: UILabel?
@IBOutlet var duration: UILabel?
}
class MyAssetListViewController: UITableViewController {
// MyRootListViewControllerから画面遷移する時にセットするインスタンス変数
var assetsFetchResults: PHFetchResult<PHAsset>?
var albumTitle: String?
var assetToFileName = [PHAsset: String]()
let reuseIdentifier = "Cell"
let imageManager = PHCachingImageManager()
let dateFormatter = DateFormatter()
let timeFormatter = DateComponentsFormatter()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = albumTitle
// Formatterの設定
dateFormatter.timeZone = TimeZone.current
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
timeFormatter.allowedUnits = [NSCalendar.Unit.minute, NSCalendar.Unit.second]
timeFormatter.zeroFormattingBehavior = .pad
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let assets = self.assetsFetchResults else { return 0 }
return assets.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyTableViewCell
guard let assets = self.assetsFetchResults else { return cell }
let asset = assets[indexPath.item]
cell.tag = asset.localIdentifier.hash
//iCloudへアクセスするようにOptionを設定
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = true
self.imageManager.requestImage(for: asset,
targetSize: CGSize(width:80, height:80),
contentMode: PHImageContentMode.aspectFit,
options: options,
resultHandler: {(result, info)->Void in
//非同期処理ためcellの再利用により異なるassetの内容を表示している場合がある。
//localIdentifierが異なる場合は何もしない。
guard cell.tag == asset.localIdentifier.hash,
let result = result else { return }
cell.thumbnail!.image = result
})
// assetから取得できるデータをセット
cell.fileName!.text = nil
cell.creationDate!.text = asset.creationDate == nil ? "(unknown)" : dateFormatter.string(from:asset.creationDate!)
cell.resolution!.text = "\(asset.pixelWidth) x \(asset.pixelHeight)"
cell.duration!.text = timeFormatter.string(from: asset.duration)
// ファイル名セット
// 既にassetResourcesがキャッシュされている場合はそれからファイル名を取得する。
// iOS10時点では、LivePhotoの場合にassetResourcesにJPG、MOVの対がセットされている。
let assetResources = PHAssetResource.assetResources(for: asset)
if setFileName(from: assetResources, to: cell) == false {
// assetResourcesが空の場合はfalseが返される。
// LivePhoto以外のimage、videoの場合は空。LivePhotoでもキャッシュされていない場合は空。
// assetResourcesがない場合はrequestContentEditingInputを取得する。
setFileNameFromContentEditingInput(with: asset, to: cell)
// AVURLAssetを使う場合はこちらに変更。
//setFileNameFromAVAsset(with: asset, to: cell)
}
return cell
}
/// assetResource配列の各要素のoriginalFilenameを結合してlabelにセットする。
/// assetResourcesが空の場合は"(searching)"をセットする。
/// - parameter from: PHAssetResource.assetResources(asset)
/// - parameter cell: MyTableViewCell
func setFileName(from assetResources:[PHAssetResource], to cell: MyTableViewCell) -> Bool {
let label = cell.fileName!
if assetResources.count > 0 {
label.text = nil
for assetResource in assetResources {
let fileName = assetResource.originalFilename
if label.text == nil {
label.text = fileName
} else {
//assetResourcesが複数あるので同じfileNameが重複する可能性がある。
label.text = label.text! + ", " + fileName
}
}
return true
} else {
//assetResourcesが空の場合は"(searching)"をセット。
label.text = "(searching)"
return false
}
}
/// assetからContentEditingInputを得て、livePhoto、image、videoの種類に応じてfileNameをセットする。
/// - parameter with: PHAsset
/// - parameter to: MyTableViewCell
func setFileNameFromContentEditingInput(with asset: PHAsset, to cell: MyTableViewCell) {
let options = PHContentEditingInputRequestOptions()
options.isNetworkAccessAllowed = true
asset.requestContentEditingInput(with: options,
completionHandler: { (contentEditingInput, info) -> Void in
guard cell.tag == asset.localIdentifier.hash else { return }
if let livePhoto = contentEditingInput?.livePhoto {
// live photoの場合はassetResourcesを取得してファイル名をセットする。
let _ = self.setFileName(from: PHAssetResource.assetResources(for: livePhoto), to: cell)
} else if let url = contentEditingInput?.fullSizeImageURL {
// imageの場合
cell.fileName!.text = url.path.components(separatedBy: "/").last
} else if let urlAsset = contentEditingInput?.audiovisualAsset as? AVURLAsset {
// videoの場合
cell.fileName!.text = urlAsset.url.path.components(separatedBy: "/").last
cell.duration!.text = self.timeFormatter.string(from: asset.duration)
} else {
cell.fileName!.text = "(not found)"
}
})
}
}
-----------------------------
Live Photoの場合にJPG、MOVの対を取得するにはContentEditingInputを使わないとダメそうですが、MOVだけでよければ次のようなコードでAVURLAssetからファイル名を取得することができます。
上のコードのsetFileNameFromContentEditingInput呼び出しをこれの呼び出しに変更します。
func setFileNameFromAVAsset(with asset: PHAsset, to cell: MyTableViewCell) {
if asset.mediaType == PHAssetMediaType.video {
//iCloudへアクセスするようにOptionを設定
let options = PHVideoRequestOptions()
options.isNetworkAccessAllowed = true
//PHAssetに対応するAVPlayerItemを取得
let _ = self.imageManager.requestAVAsset(forVideo: asset,
options: options,
resultHandler: { (avAsset, avAudioMix, info) -> Void in
guard let urlAsset = avAsset as? AVURLAsset,
let fileName = urlAsset.url.path.components(separatedBy: "/").last
else { return; }
cell.fileName!.text = fileName
})
}
}
-----------------------------
PHAssetからvalue(for: "filename")でファイル名を取得する方法もありました。
ビデオの場合も拡張子JPGのファイル名が返されますが、用途によってはこれで十分な場合もあるでしょう。
なお、PHAssetの公開仕様ではないので、今後のバージョンで同じ方法が使い続けれらるかはわかりません。
cell.fileName!.text = asset.value(forKey: "filename") as? String ?? "<no filename>"