Macでファイルオープン/保存ダイアログを実装する

Macでよくみるファイルオープン/保存のダイアログについての用法と実装方法についてです。

# Document modalとApp modal
ダイアログにはいくつか種類があり、これらはファイルオープン/保存をするときのシーンによって使い分けるのが良いです。

## Document modal (Sheets)
ドキュメント保存時によく出てくるこのダイアログは、特徴としてウィンドウにくっついているので、保存するものとウィンドウの関連が視覚化できます。

![sheet_example_2x.png](https://qiita-image-store.s3.amazonaws.com/0/61792/fe4b5446-8689-94e6-49e5-07d8473e1895.png)


用いられるシーンとして、

* ファイル保存, ファイル添付, 印刷などの操作をするとき
* シングルウィンドウアプリケーションの確認時
* アプリ終了時確認
* エラー表示

などがあります。

## App modal
ファイルを開くときによく出てくるこのダイアログは他のウィンドウから独立しています。ドキュメントやウィンドウに対して表示するのではなく、アプリケーションに対して表示するイメージです。

![nondocument_open_dialog_2x.png](https://qiita-image-store.s3.amazonaws.com/0/61792/622b9ec1-eb25-9e79-b176-13f98ec7ce8e.png)


用いられるシーンとして、

* ファイルオープン
* アプリ終了時確認
* エラー表示

などがあります。Document modalと重複するのもがありますが、ポイントは**アプリケーションに対して表示する**点かと。


# 実装方法
## Document modalを表示する
以下はDocument modalを使って特定の画像ファイル（jpg, png, gif）を保存先パスを表示するサンプルです。

```swift:filesave.swift
    let savePanel = NSSavePanel()
    savePanel.message = "保存するで"         // ダイアログに表記される説明
    savePanel.directoryURL = NSURL(fileURLWithPath: "\(NSHomeDirectory())/Desktop)")    // デフォルトで表示されるフォルダ
    savePanel.allowedFileTypes = ["gif","jpg","png"]

    // jpg,png,gifのみ保存可能にする（イレギュラーな拡張子の場合はgifになる）
    savePanel.beginSheetModalForWindow(self.window!, completionHandler: { (num: Int) -> Void in
        if num == NSModalResponseOK {
            let filePath = savePanel.URL!.absoluteString
            let decoadPath :String! = filePath.stringByRemovingPercentEncoding  // 日本語ファイル対応
            NSLog("\(decoadPath)")
        } else {
            NSLog("Canceled")
        }
    })
```
## App modalを表示する
以下はApp modalを使って特定の画像ファイル（jpg, png, gif）をローカルから選択してファイル名を取得するためのサンプルです。

```swift:fileopen.swift
    let openPanel :NSOpenPanel = NSOpenPanel()
    openPanel.canChooseFiles = true             // ファイル選択を許可する
    openPanel.canChooseDirectories = false      // フォルダ選択は禁止する
    openPanel.allowsMultipleSelection = true    // 複数選択を許可する
    openPanel.message = "開くで"                 // ダイアログに表記される説明

    // jpg,png,gifのみ選択可能にする
    openPanel.allowedFileTypes = ["gif","jpg","png"]
    openPanel.beginWithCompletionHandler({ (num) -> Void in
        if num == NSModalResponseOK {
            for fileURL in openPanel.URLs {
                let filePath :String! = fileURL.resourceSpecifier
                let decoadPath :String! = filePath.stringByRemovingPercentEncoding  // 日本語ファイルの場合
                NSLog("\(decoadPath)")
            }
        } else if num == NSModalResponseCancel {
            NSLog("Canceled")
        }
   })
```

## Accessory Viewを表示する
Panel上で何かユーザーに操作させたい場合、AccessoryViewを使ってダイアログを拡張します。
以下の例はMac標準のテキストエディットで文書を保存するときに表示されるものです。

<img width="713" alt="スクリーンショット 2016-01-08 12.23.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/61792/4ff9c554-138d-0bf2-828d-0f83bfba4edf.png">


以下は、ファイル保存を選択するとポップアップメニューで選択した拡張子を表示するサンプルです。

```swift:accesory.swift
    // Viewの設定
    let accessoryView = NSView(frame: CGRectMake(0, 0, 270, 50))
    let popup = NSPopUpButton(frame: CGRectMake(130, 10, 120, 25))
    let label = NSTextField(frame: CGRectMake(20, 15, 100, 18))
    
    popup.addItemsWithTitles(["JPEG", "PNG", "GIF"])
        
    label.stringValue = "拡張子 : "
    label.alignment = NSTextAlignment.Right
    label.bordered = false
    label.selectable = false
    label.editable = false
    label.backgroundColor = NSColor.clearColor()
        
    accessoryView.addSubview(popup)
    accessoryView.addSubview(label)
        
    let savePanel = NSSavePanel()
    savePanel.message = "保存するで"         // ダイアログに表記される説明
    savePanel.directoryURL = NSURL(fileURLWithPath: "\(NSHomeDirectory())/Desktop)")    // デフォルトで表示されるフォルダ
    savePanel.allowedFileTypes = ["gif","jpg","png"]
    savePanel.accessoryView = accessoryView
    
    savePanel.beginSheetModalForWindow(self.window!, completionHandler: { (num: Int) -> Void in
        if num == NSModalResponseOK {
            NSLog("\(popup.selectedItem?.title)")
        } else {
            NSLog("Canceled")
        }
    })
```

### 表示結果
<img width="712" alt="スクリーンショット 2016-01-08 13.57.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/61792/06e2c343-3eb2-4448-2ecc-3839ca805e9c.png">



# 参考
[Dialogs - OS X Human Interface Guidelines](https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/WindowDialogs.html#//apple_ref/doc/uid/20000957-CH43-SW7)
[セーブパネルの使い方とシートダイアログの呼び出し方 - Repeating Motif Wonderland](http://www15.plala.or.jp/NovemberKou/programming/columnHome/column/howToUseSavePanel.html)