이번에는 이전에 작성했던 Swift로 파일 다루기_개념 편에서 다루지 않았던 실제 사용법과 관련하여 본인도 다시 실습을 하면서 이와 관련된 내용을 정리해보고자 한다.
처음에는 단순히 FileManager
를 사용하는 방법에 대해서만 작성해보려다 너무 성의가 없을 듯 하여, 간단하게 텍스트 파일을 작성하고 관리하는 작은 프로젝트를 작성하고 그 안에서 FileManager
를 활용하고자 했다. 그러다보니 다른 길로 자꾸 새서 RxDataSource
를 학습하고 있다던지 하는 경우가 잦아져 이번 포스터의 텀이 다소 길어지게 되었다.
사담은 여기까지 하고 본격적으로 FileManager
사용에 대해 정리해보고자 한다.
먼저 앞서 포스팅에서 작성했던 대로 Info.plist
에 Supports opening documents in place
, Application supports iTunes file sharing
을 추가해준다.
일단 이번 실습 단계에서는 txt 파일을 생성/삭제할 수 있고, 실행 시에 생성되어 있는 파일들을 확인하여 CollectionView
에 출력해주는 단순한 작업을 구현해보려고 했다. 이를 위해서 해당 작업들을 관리하는 객체를 따로 분리하여 구현하려 한다.
먼저 메인 화면의 CollectionView
에 그동안 작성되어 있던 텍스트 파일을 나열해주기 위해서는 해당 디렉토리 내에 있는 모든 텍스트 파일을 확인하고 이들의 URL을 가져와야 한다.
func fetchAllTextFilesURL() -> [URL]? {
guard let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first,
let fileLists = try? FileManager.default.contentsOfDirectory(at: documentURL, includingPropertiesForKeys: nil) else { return nil }
let txtFileLists = fileLists.filter { $0.lastPathComponent.hasSuffix(".txt") }
return txtFileLists
}
FileManager
의 .urls(for:, in:)
함수를 이용하여 디렉토리 영역의 URL을 받아오고, .contentsOfDirectory(at:, includingPropertiesForKeys:)
를 통해 디렉토리 영역에 있는 모든 파일들의 URL을 받아온다. 여기서 텍스트 파일들만 출력해줄 필요가 있기에 filter
를 통해 확장자가 텍스트로만 되어 있는 파일들의 URL만 걸러서 리턴해주도록 한다.
다음으로는 신규 파일을 생성하는 함수를 구현하였다. 앞서 설명한 로직과 동일한 부분은 문서 디렉토리에 대한 URL을 FileManager
로부터 받아오고 해당 URL에 텍스트 파일의 이름과 확장자명이 명시된 String 값을 path로 더해주면 파일을 생성할 주소를 확정지을 수 있다.
func createTextFile(title: String, context: String) -> URL? {
guard let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil }
var fileURL = documentURL
if #available(iOS 16.0, *) {
fileURL = documentURL.appending(path: "\(title).txt")
} else {
fileURL = documentURL.appendingPathComponent("\(title).txt")
}
do {
try context.write(to: fileURL, atomically: true, encoding: .utf8)
return fileURL
} catch(let error) {
NSLog("\(title) file can't create: \(error.localizedDescription)")
return nil
}
}
iOS 16부터는 URL에 path를 추가하는 구문이 달라져 분기 처리를 해주었다. 이렇게 파일을 생성해줄 URL을 주소를 구현한 뒤에는 내용을 담고 있는 String 값에 .write(to:, atomically:, encoding)
을 호출하여 파일을 생성해줄 수 있다.
마지막으로 생성한 파일을 삭제하는 함수를 구현해주었다. 삭제하려는 파일의 URL 값을 인자로 받아 FileManager
를 통해 삭제해주며, 삭제가 잘 진행되었는지에 대한 Bool 타입을 리턴해준다.
func removeTextFile(with fileURL: URL) -> Bool {
do {
try FileManager.default.removeItem(at: fileURL)
return true
} catch(let error) {
NSLog("This file can't remove: \(error.localizedDescription)")
return false
}
}
여기까지 로직을 구현하면 파일과 관련된 CRUD 작업은 모두 진행할 수 있는 객체를 구현한 것이다. 다만 여기서 수정과 관련된 로직이 빠진 것처럼 보이는데 FileManager
로 수정을 직접해주는 방법은 따로 없고 기존의 파일을 삭제한 뒤 신규 값을 가진 파일을 다시 추가하는 방식으로 수정을 진행하면 된다.
이제 해당 로직들을 응용하여 DataSource
와 결합시키는 작업을 진행하면 생성과 수정, 삭제를 파일단과 UI단에서 진행해줄 수 있다.
작업 화면 | UI 화면 | 실제 파일 |
---|---|---|
아까 파일들의 URL을 가져오는 로직에서 텍스트 파일만 선별해서 가져오는 구문을 추가하였는데 해당 구문이 잘 적용되고 있는 것을 출력되는 UI와 실제 존재하는 파일 화면을 비교하면 잘 알 수 있다 (실제 파일 중 하나는 csv 타입 파일).
그래서
UIDocumentPickerViewController
는 왜 얘기가 나온 것일까?
이처럼 FileManager
를 통해 파일을 로컬 디렉토리에 저장하고 실제로 접근할 수 있도록 구현하는 것이 가능한데 왜 UIDocumentPickerViewController
를 앞선 게시물 때부터 언급하고 있는 것일까.
현재의 방법으로는 본인의 iPhone 로컬에 해당 어플의 디렉토리가 생성되고 그 안에 파일이 저장되는 형태만 가능하며 iCloud나 다른 디렉토리에 저장하고 싶더라도 이를 해결할 방도가 없다. 그럴 경우 UIDocumentPickerViewController
를 활용하면 원하는 디렉토리나 iCloud에 저장하거나 해당 위치의 파일을 불러올 수 있다.
특히 앞서 info.plist
에서 추가한 키 값들을 추가하지 않아도 작업이 가능한 이점이 있다. 다만 파일들을 여러 디렉토리에 중구난방으로 넣으면 한 번에 불러오는 것이 어렵다는 게 아쉬운 점이긴 하다.
if #available(iOS 14.0, *) {
let documentPicker = UIDocumentPickerViewController(forExporting: [url], asCopy: false)
documentPicker.delegate = self
documentPicker.modalPresentationStyle = .fullScreen
self.present(documentPicker, animated: true, completion: nil)
} else {
let documentPicker = UIDocumentPickerViewController(url: url, in: .exportToService)
documentPicker.delegate = self
documentPicker.modalPresentationStyle = .fullScreen
self.present(documentPicker, animated: true, completion: nil)
}
.write
를 통해 파일을 생성한 뒤, 해당 url 값을 UIDocumentPickerViewController
에 넘겨주면 디렉토리를 탐색하여 파일을 저장할 수 있는 화면이 출력된다.
이처럼 단순히 FileManager
만을 사용할 수도 UIDocumentPickerViewController
를 같이 응용하는 방법을 사용할 수도 있다. 솔직히 해당 로직들은 문서 기반 어플이 아니면 크게 사용할 용도가 많지는 않아보이지만 또 혹시 모르지 않는가, 언젠가는 많이 활용할 때가 올 지. 해당 포스트를 쓰면서 구현한 프로젝트는 아래에 링크가 있으니, 혹여 궁금하다면 한 번 확인해보면 좋을 듯 하다.