面白きことは良きことなり

拙く未熟なiOSエンジニアの備忘録と戯言

Mac AppでWindowからfileやdirectoryを選択してpathを取得するやり方

Mac Appの開発へ乗り出し

iTunesで曲を管理しているが、不便に感じていることがあるため、その辺の利便性をなんとかしたいと思い、Mac Appの開発を気ままに進めることにした。

ひとまずやりたいこと

  • directoryを参照して、その配下にあるfile(subdirectory配下も含む)を列挙

コードと自分向けへの説明

下記のWindowを表示し、そこからdirectoryを選択させたい

f:id:aryzae:20180504152845p:plain

  • NSOpenPanelのbeginメソッドで目的のWindowは表示できる
  • NSOpenPanelはPropetyによって、file選択、複数選択等を切り替えられる
@IBAction func openPanel(_ sender: NSButton) {
    let panel = NSOpenPanel()
    // fileを選択可能にするかどうか。defaultは、true 
    panel.canChooseFiles = false
    // 複数選択可能にするかどうか。defaultは、false
    panel.allowsMultipleSelection = false
    // directoryを選択可能にするかどうか。defaultは、false
    panel.canChooseDirectories = true
    // 開始
    panel.begin { response in
        // OK以外に`cancel`, `abort`, `stop`, `continue`があるが、`OK`, `cancel`以外は異常系
        guard response == .OK else { return }
        // closure内でのみ選択したdirectoryのpathが取れる
        // また、panelの持つurlは、file protocol(file://)がついている 
        self.readItems(url: panel.url)
    }
}

NSOpenPanelで選択したdirectory配下のfileを列挙する

  • 先ほどのコードでdirectoryのpathまでは取得できるようになったので、ファイルを列挙する
  • FileManagerのenumeratorメソッドで列挙可能
  • enumeratorincludingPropertiesForKeysで、列挙するfileの各種propertyも取捨できるのかと思ったが、後続のresourceValuesの時に指定したら取れているのでObj-C時代は必要だったが、今は不要?
  • enumeratoroptionsで、隠しファイルやsubdirectory配下も含めるかを指定可能
  • enumeratorのclosure部分はerror handlerなので、必要に応じて書く
func readItems(url: URL?) {
    guard let url = url else { return }
    guard let directoryPath = URL(string: url.path) else { return }
    // includingPropertiesForKeysの意味あるのか?
    let enumerator = FileManager.default.enumerator(at: directoryPath,
                                                    includingPropertiesForKeys: [.isDirectoryKey],
                                                    options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants]) { (url, error) -> Bool in
        // closuerは、errorハンドルのみ
        print(error)
        // trueならerrorでも継続、falseなら停止
        return true
    }
    // 列挙
    for element in enumerator! {
        guard let url = element as? URL else { return }
        print(url)
        let resourceValue = try! url.resourceValues(forKeys: [.isDirectoryKey, .nameKey, .isHiddenKey])
        // forKeysで指定していないものは、nilになる
        print(resourceValue.isDirectory)
        print(resourceValue.name)
        print(resourceValue.isHidden)
        print(resourceValue.isPackage)
        print(resourceValue.localizedName)
    }
}