2017年10月15日日曜日

Could not load the "imageName.png" image referenced from a nib in the bundle with identifier ...

Could not load the "imageName.png" image referenced from a nib in the bundle with identifier ...

pngをプロヘクトに追加するとInterfaceBuilderではUIにimageを設定できるようになるが、それだけではビルド時にbundleにコピーされないため、実行時にリソースが見つけられず発生する。

プロジェクトビュー > Target > BuildPhases > Copy Bundle Resources
エラーが発生している画像ファイルを追加する。

2017年9月12日火曜日

VisualStudio 2017 Community & WiX tools & WiX Edit によるWindowsアプリ インストーラ(msi)作成

Windowsインストーラ(msi)を無料で作成できる方法を探したところ、MS発祥のオープンソースプロジェクトのWiX toolsetがあり、3.11ではVisualStudio 2017 Communityでインストーラを作成することができるようになっていた。また、VisualStudioだと多言語でのインストーラを一気に作成することもできる。

VisualStudioでいきなりXMLに取り組むのはしんどいので、もうひとつ別のオープンソースのWiX Editを使い、XMLを作成した。

以下、その手順です。

ダウンロードインストール

WiX toolset 3.11、WiX Toolset Visual Studio 2017 Extensionをダウンロード、インストール。
ダウンロードページ:http://wixtoolset.org/releases/
同ページからWix Toolset Visual Studio 2017 Extensionへ移動しExtensionをダウンロード。

WiX Editををダウンロード、インストール。
WiX Edit ページ:http://wixedit.sourceforge.net/
ダウンロードページ:https://sourceforge.net/projects/wixedit/files/wixedit/


WiX Editで簡単サンプル作成

Toru Takahashi さんの「Windows上でWixおよびWixEditを使ったインストーラ作成
」に従ってサンプルを作る。

サンプル拡張

次の二点に対応するため、サンプルを作り直す。
・exeと多言語対応のresources.dllを含める。
・デスクトップショートカットとスタートメニューを追加する。

以下、Toru Takahashi さんのチューアルと共通する手順は割愛。
斜体部分は実際の対象の名前で置き換える。

このチュートリアルでは後半の方で紹介される「WiXで指定ディレクトリ以下をまとめてインポート」する方法を新規作成時に行う。

File > New > Next で表示されるダイアログで、PFilesを右クリック。

ImportFolderを選択。

exeとresoucesが含まれるReleaseフォルダを選択。
フォルダ内の全ファイルがインポートされるので、不要なファイルを削除する。

削除後はこんな感じ。

このチュートリアルにはショートカット追加が含まれていないので、ウィザードでの新規作成の際にフィーチャーを追加。次のものにチェックを入れる。
Add User Interface
Desktop application shortcut
Startmenu application shortcut
Startmenu uninstall shortcut

”Add a userinterface”に"WixUI_InstallDir"が含まれていないので、仮設定しておき、あとで修正する。

デスクトップのショートカットを設定する。
下段はショートカットアイコンの下に表示される名前。

スタートメニューのショートカットを設定する。
中段はスタートメニューのアプリ起動ショートカットに表示される名前(XMLに反映されないようだが)。
exeを起動するだけのショートカットなら下段は空欄のままにしておく。

スタートメニューのアンインストールショートカットを設定する。

適宜情報を変更してウィザード終了。
VS2017でculture指定で日本語版msiを作る場合は、Languageの変更、Codepageの追加はしなくてもよい。

左ペインFiles > 中ペインPFiles > Release を選択。
ReleaseはImportしたフォルダ名。
右ペインのId=Nameの値をProgramFilesFolderの下に作るフォルダ名(通常はアプリケーション名と同じ)に変更する。
私はIdもAppNameAppFolderに変更したが、その場合はFeaturesでこのIdを参照している項目も同時に変更する必要がある。

左ペインGlobal > 中ペインUIRefを選択。
右ペインのIdの値を"WixUI_InstallDir"に変更する。

左ペインProperties > 中ペイン右クリック > Add Newで表示されるダイアログで"WIXUI_INSTALLDIR"を追加し、その値にアプリケーションフォルダのIdを入力する。この例では”Release"、あるいは変更した場合は"AppNameAppFolder"。
これを行わない場合、ビルドはされるがインストーラ実行時にエラーが発生する。(下記error code 2819参照)

VisualStudioのソリューションにSetupプロジェクト追加

詳しくは(といってもとてもシンプルな)http://wixtoolset.org/のVisualStudioへのプロジェクト追加手順のページを参照してください。

WiX toolsetがインストールされると、新規プロジェクト追加の際のプロジェクトタイプに”WiX Toolset”が追加される。3.11をインストールしたので"Setup Project for WiX v3"を選択。

wixtoolset.orgの手順では独立したソリューションを作り、既存アプリを参照に追加しているが、私はもとのアプリのソリューションにSetupプロジェクトを追加してみた。
プロジェクト追加ダイアログ左下の選択肢で"ソリューションに追加"を選択。
名前は"Setup”とした。

Setup > References に次のdllを追加する。
C:\Program Files (x86)\WiX Toolset v3.11\bin\WixUIExtension.dll

この参照がないとビルド時に次のエラーが発生する。
Unresolved reference to symbol 'WixUI:WixUI_InstallDir' ...

Setup > Product.wxs にWiX Editで作成したwxsをコピー/ペースト。

wxsのReleaseフォルダへのパスをソリューションに合わせて変更。
..\AppName\bin\Release\

ビルド構成マネージャーで構成変更・追加。(一例)
Debug、ReleaseのビルドからプロジェクトSetupを除く(チェックをはずす)。
新規構成”Setup"を追加。Release、Setupをビルドに追加。

ビルド構成Setupでビルドすると構成に従った出力フォルダ(この例では...\Setup\bin\Release\)内にSetup.msiが出力される。

多言語対応

プロジェクトSetupのプロパティー画面 > Build > General > Cultures to buildに対象カルチャーを追加。en-US;ja-JPのように複数のときはセミコロンで句切る。

ビルドすると構成に従った出力フォルダ(この例では...\Setup\bin\Release\)内に対象カルチャー毎のサブフォルダが作られ、その中にSetup.msiが出力される。

UIの画像入れ替え

次のようなタグ名=WixVariable、id=WixUIDialogBmpの要素を追加すると、最初のダイアログの背景を入れ替えることができる。
画像はbmp形式、解像度498x312
<WixVariable Id="WixUIDialogBmp" Value="WavCutterBK.bmp" />

次のようなタグ名=WixVariable、id=WixUIDialogBmpの要素を追加すると、ふたつめ以降のダイアログのバナーを入れ替えることができる。
画像はbmp形式、解像度498x58
<WixVariable Id="WixUIDialogBmp" Value="WavCutterBanner.bmp" />


Upgradeインストール

以上の手順で作成したインストーラの場合、同じバージョン番号でもリビルドしたインストーラで再度インストールを行うと「別のバージョンの製品が既にインストールされています」の警告が出て、先にアンインストールを行わないとインストールできない。

Product.wxsのProductタグのIDを"*"に変更し、MajorUpgradeタグを追加する。

<Product Id="*" ...>
      <MajorUpgrade AllowSameVersionUpgrades="yes" 
        DowngradeErrorMessage="A newer version of [ProductName] is already installed." />

ProductのIdが同じ値の場合、AllowSameVersionUpgrades="yes" でも上の警告が出る。バージョンごとに独立したGUIDを設定すればよいが、ProductのId="*"とするとビルドの度に新規GUIDで置き換わるので、なんどもビルドする場合は楽。

DowngradeErrorMessageはAllowDowngrades="yes"が設定されていない場合は必須。

メッセージを日本語にする場合は次項の「メッセージ多言語対応」にするか、Product タグにCodepage="932"を追加し、プロジェクトのカルチャーを"Cultures to build"にja-JPのみを設定する。

メッセージ多言語対応

プロジェクトSetupを右クリック > 追加 > 新しいい項目 > Localization File でファイル追加。

日本語、英語のふたつを用意する場合は次のようになる。

Strings_ja-JP.wxl、Strings_en-US.wxlのようにカルチャーを付けたファイルを各々に追加する。

自動で追加されるファイルのWixLocalizationタグのxmlnsは"http://wixtoolset.org/schemas/v4/wxl"となっているが、"http://schemas.microsoft.com/wix/2006/localization"に変更する。

次の要領で各々ファイルにメッセージを追加する。

Strings_en-US.wxl
  <String Id="DowngradeErrorMessage">A later  virsion of [ProductName] is already installed. Setup will now exit.</String>

Strings_ja-JP.wxl
  <String Id="DowngradeErrorMessage">[ProductName]の新しいバージョンが既にインストールされています。</String>

Product.wxsのメッセージを上記のStringの参照に置き換える。

<MajorUpgrade AllowSameVersionUpgrades="yes"
                        DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" />

プロジェクトSetupの” Cultures to build”に対象カルチャーをen-US;ja-JPとする。(「多言語対応」参照)

TRANSFORMによる多言語メッセージ対応

「メッセージ多言語対応」ではCulture別に独立したmsiが作られる。
バージョン間の差分を取り出し、それをインストーラに適用する機能を利用してUIを切り替えることができる。

torch.exeで、ベースmsiと言語対応するmsiの差分を作り、EmbedTransform.exeでmsiに組み込む。

EmbedTransform.exeはWiX toolset ドキュメント: Morphing Installers のページのリンク(EmbedTransform tool)からダウンロードする。

Setプロジェクトのプロパティー > Build Events > Post-build Event Command Line
に次のようなバッチ処理を設定する。

set BIN=C:\Program Files (x86)\WiX Toolset v3.11\bin
copy en-US\WavCutterSetUp.msi .
"%BIN%\torch.exe" -p -t language WavCutterSetUp.msi ja-JP\WavCutterSetUp.msi -out ja-JP.mst
"%BIN%\EmbedTransform.exe" WavCutterSetUp.msi ja-JP.mst

EmbedTransform.exeはWiX toolsetインストールフォルダのbinに保存した。
この例ではCulture別のフォルダのうちen-USのmsiをひとつ上のフォルダにコピーし、それをベースのmsiとして、ja-JPと差分をja-JP.mstに出力、それをベースにmsiに組み込んでいる。

torch.exeは異なるバージョン間の差異を作るのが本来の目的のようで、多言語対応のための差分つくりではバージョンが同じであるため、AllowSameVersionUpgrades="yes"が設定されていると次の警告が発生する。

warning LGHT1076: ICE61: This product should remove only older versions of itself. The Maximum version is not less than the current product. (1.0.1 1.0.1)

この警告が出ても処理は続行され、msiが作られる。警告を出さないようにしたい場合は
Setupプロジェクトのプロパティー > Tool Settings > Suppress specific ICE ValidationにICE61をセットする。

msiを直接実行した場合は英語版、次のコマンドで実行した場合は日本語版となる。msiexec /i folderPath\WavCutterSetUp.msi TRANSFORMS=":ja-JP.mst"

エラーと対策

warning CNDL1113 : Because it is an advertised shortcut, the target of shortcut 'desktopShortcut' will be the keypath of component 'APPNAME.EXE' rather than parent file 'APPNAME.EXE'. To eliminate this warning, you can (1) make the Shortcut element a child of the File element that is the keypath of component 'APPNAME.EXE', (2) make file 'APPNAME.EXE' the keypath of component 'APPNAME.EXE', or (3) remove the @Advertise attribute so the shortcut is a non-advertised shortcut.

この警告が出ても正常に動作するmsiが作られる。
WiX Editは次のようなタグ構造を作る。
<Component Id="APPNAME.EXE” ...>
    <File Id="APPNAME.EXE"  ...>
        <Shortcut Id="desktopShortcut" ... />
        <Shortcut Id="ExeShortcut" ... />
    </File>
</Component>
これを、Shortcut要素がComponent要素の直接の子要素になるように変更する。
<Component Id="APPNAME.EXE” ...>
    <File Id="APPNAME.EXE"  ...></File>
    <Shortcut Id="desktopShortcut" ... />
    <Shortcut Id="ExeShortcut" ... />
</Component>
WiX EditのUIでは変更できないので、Tools > Launch External Editorでwxsを開き、編集する。
WiX EditがComponent とFileに同じIdを振るため、メッセージが分かりにくなっている。
WiX toolsetのマニュアルの例ではこの場合のComponent のIdをMainExecutableとしている。このIdを変更する場合はFeaturesのComponentRefでの参照も変更すること。(次項のエラー参照)

error LGHT0094 : Unresolved reference to symbol 'Component:APPNAME.EXE' in section 'Product:GUID'.

上記警告でComponent要素のIdを変更したが、それを参照しているRefのIdを一致させていない場合に発生する。当該要素のIdを変更する。

error CNDL0006 : The Directory/@Name attribute's value cannot be an empty string.  If you want the value to be null or empty, simply remove the entire attribute.

値が必須の要素で、値がブランクになっている。
上記手順で作成した場合、Files > ProgramMenuFolder > Dictionary のNameの値が(ウィザードで入力したはずだが?)ブランクになっている。スタートメニューのアプリ起動ボタンに表示される名前を入力する。

error LGHT0267 : Found orphaned Component 'SomeCompnent'.  If this is a Product, every Component must have at least one parent Feature.  To include a Component in a Module, you must include it directly as a Component element of the Module element or indirectly via ComponentRef, ComponentGroup, or ComponentGroupRef elements.

Component 要素が出現したが、ComponentRef, ComponentGroup, ComponentGroupRef のいずれとも関連付けられていない。
私が作った範囲ではFiles >Features の DefaultFeatures から漏れている場合に発生。DefaultFeatures に追加する。

error LGHT0130 : The primary key 'UninstallProduct' is duplicated in table 'Shortcut'.  Please remove one of the entries or rename a part of the primary key to avoid the collision.

どういう手順で発生したか不明だが、Component Id="UninstallProduct"が二つできたときに発生。不要な方を削除する。

error code 2819
This installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code 2819.
インストーラ実行時に発生するエラー。
"UIRef"を"WixUI_InstallDir"にした場合で、"Properties"に"WIXUI_INSTALLDIR"を
追加しなった場合に発生する。
"Properties"に"WIXUI_INSTALLDIR"を追加し、その値にアプリケーションフォルダのIdを入力する。

... characters that are not available ...

A string was provided with characters that are not available in the specified database code page '1252'. Either change these characters to ones that exist in the database's code page, or update the database's code page by modifying one of the following attributes: Product/@Codepage, Module/@Codepage, Patch/@Codepage, PatchCreation/@Codepage, or WixLocalization/@Codepage.

Upgradeインストールの設定でDowngradeErrorMessageを日本語で設定し、Codepage="932"とした場合で、プロジェクトの"CUltures to build”が設定されていない、または"ja-JP"以外の言語が含まれている場合に発生する。"CUltures to build”に"ja-JP"のみ設定する。
または「メッセージ多言語対応」の要領でCulture別のwxlファイルを用意する。

2017年7月5日水曜日

メモリ使用量(使用済み/残メモリ)取得(Swift)

Swiftで使用済みメモリ、残メモリ容量を取得する方法
        
func showMemoryInfo() {
    //使用済みメモリ
    var taskInfo = mach_task_basic_info()
    var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
    var ret: kern_return_t = withUnsafeMutablePointer(to: &taskInfo) {
        $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
            task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
        }
    }
    let usedMemory = (ret == KERN_SUCCESS ? taskInfo.resident_size as UInt64 : 0)
            
    //フリーメモリ
    var size: mach_msg_type_number_t =
        UInt32(MemoryLayout<vm_statistics64_data_t>.size / MemoryLayout<integer_t>.size) as mach_msg_type_number_t
    var vmStatInfo = vm_statistics64()
    ret = withUnsafeMutablePointer(to: &vmStatInfo) {
        $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
            host_statistics64(mach_host_self(), host_flavor_t(HOST_VM_INFO64), $0, &size)
        }
    }
    let freeMemory = (ret == KERN_SUCCESS ? vmStatInfo.free_count * 
vm_kernel_page_size : 0)
            
    print("\(usedMemory), \(freeMemory)");

}

2017年6月21日水曜日

Swift エラーメッセージ - 型変換(Type Casting)

Cannot downcast from 'UIView' to a more optional type 'UITableViewHeaderFooterView?'
  • エラーが発生するコード
     if let headerView = view as! UITableViewHeaderFooterView {
       ...
     }
  • 原因:viewがOptionalでないため、UITableViewHeaderFooterViewとして扱えない。
  • 修正
    let headerView = view as! UITableViewHeaderFooterView
    ...

Initializer for conditional binding must have Optional type, not 'UITableViewCell'
  • エラーが発生するコード
    if let cell = sender as! UITableViewCell {
       ....
    }
  • 原因:senderがOptionalなのでUITableViewCellとして扱えない。
  • 修正
    if let cell = sender as! UITableViewCell? {
        ....
    }
Cannot convert value of type 'Any?' to expected argument type 'UITableViewCell'
  • エラーが発生するコード
    if let indexPath = self.tableView.indexPath(for: sender) {
        ...
    }
  • 原因:senderの型がAny?なのでキャストが必要。
  • 修正
    if let indexPath = self.tableView.indexPath(for: sender as! UITableViewCell) {
        ...
    }



2017年6月18日日曜日

swift エラーメッセージ


メッセージ:Editor placeholder in source file
Xcodeの自動修正でコードを置き換えた場合に、適切な値に置き換えるべきplaceholderがセットされることがある。
これをそのままにしてビルドした場合に発生する。

例: 
self.navigationController!.setNavigationBarHidden(true, animated: Bool)

Boolがplaceholder。次のように修正する。
self.navigationController!.setNavigationBarHidden(true, animated: true)



メッセージ:Result of call to 'someFunc()' is unused" 
someFuncの戻り値を変数にセットしてが、それを使用しない場合に発生する警告。

例:
let val = someFunc()

valを使用しない場合に発生する。次のように変更する
let _ = someFunc()


メッセージ:Cannot use mutating member on immutable value: '...' is a 'let' constant
パラメータの変数はletで宣言されたConstant扱いなので代入不可のため発生する警告。

例:
func someFunc(someParam: SomeType) {
    someParam = someObj
}
letかvarで別の変数に代入して使用する。代入後に値を変更するかどうかでlet, varを使い分ける。名前は同じでも良いが、別オブジェクトになる。
func someFunc(someParam: SomeType) {
    var someParam = someObj
}

呼び出し元の値を変更したい場合は新規オブジェクトをreturnし、呼び出し元でオブジェクトを入れ替える。
func someFunc(someParam: SomeType) {
    var newParam = someObj
    ....
    return newParam
}

呼び出し元にあるオブジェクト (ArrayやDictionanayなど)を置換せず、そのプロパティーや要素の一部を変更したいような場合はsomeParamをinoutで宣言する。
varで宣言されていれば、someParamそのものの置換も可能。
func someFunc(someParaminout SomeType) {
    someParam.someProp = someVal
}

この場合は、呼び出し側を次のようにする。
someFunc(&someObj)



メッセージ:Unknown class SomeClass in Interface Builder file.
IBで設定したクラスが見つからない場合に発生する。
ソースコードでクラスを追加してからIBでインスタンスにそのクラウを設定していれば、このエラーは発生しない。原因は様々と思われるが、次の場合が含まれる。
  • IBで設定した後で、ソースコードでクラス名を変更した。
  • 他のプロジェクトからIBで部品のコピー/ペーストを行った。
当該の部品をIBで選択、インスペクターでクラス設定をチェックする。
前者の場合は変更前のクラス名のままになっているので、プルダウンで新しい名前のものに変更する。
後者の場合はそのクラス名しか表示されない。プルダウンで変更できないので、一旦手入力でスーパークラスの名前に書き換える。その後プルダウンを開くとそのサブクラスの一覧が表示されるので、適切なものに設定する。






2017年5月6日土曜日

PhotoLibrayにある画像、ビデオのファイルの名前、作成日時(アップロード日時)を取得する方法(iCloud共有含む)

PhotoLibrayにある画像、ビデオのファイルの名前、作成日時(アップロード日時)を取得する方法(iCloud共有含む)


iCloud共有にアップロードされたファイルをダウンロードすると、jpegの場合はExifデータから撮影日がわかるが、ビデオは撮影日時を調べるのに写真アプリで情報をチェックする必要があり、面倒だった。そこでプログラムでファイル名、CreateionDateを取得しようとすてみると、これもまた意外と面倒でした。

次のサンプルコードはPhotoLibray内のiCould、ローカル、画像、ビデオ、ライブフォトを含めてファイル名を取得するようにしたものです。

iPhoneで撮影したvideoをそのデバイスからiCloud共有にアップロードした場合は作成日時=撮影日時と考えて良いと思いますが、それ以外で撮影し、Macからアップロードした場合は作成日時=アップロード日時になるでしょう。また、その場合に拡張子がMP4以外の場合、iCloud共有からイクスポートしたファイルはもとの拡張子(M4Vなど)ですが、以下のプログラムで取得するファイル名の拡張子は全てMP4になります。

iPhoneとMacでiCloud共有の同じ写真やビデオのファイル名を比べてみると、同じものが多いものの、異なるものが結構あります。デバイス毎にファイル名が異なりますね。

MyAssetListViewControllerを呼び出す遷移元で、次の要領でPHAssetをフェッチする。


let sharedAlbums = PHAssetCollection.fetchAssetCollections(
      with: PHAssetCollectionType.album,
      subtype: PHAssetCollectionSubtype.albumCloudShared,

      options: fetchOptions)

let assetsFetchResults = PHAsset.fetchAssets(in: sharedAlbums, options: nil)


画面遷移時にMyAssetListViewControllerにassetsFetchResultsをセットする。



    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let assetGridViewController = segue.destination as! MyAssetListViewController                      assetGridViewController.assetsFetchResults = assetsFetchResults
    }


//---- MyAssetListViewController ----


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の場合にassetResourcesJPGMOVの対がセットされている。
        let assetResources = PHAssetResource.assetResources(for: asset)
        if setFileName(from: assetResources, to: cell) == false {
            // assetResourcesが空の場合はfalseが返される。
            // LivePhoto以外のimagevideoの場合は空。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を得て、livePhotoimagevideoの種類に応じて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 asAVURLAsset,
                                 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>"