폴더(디렉토리) 표시용 모델 생성
import Foundation
struct Folder: Codable, Equatable, Hashable, Identifiable, FileSystem {
static func == (lhs: Folder, rhs: Folder) -> Bool {
lhs.id == rhs.id && lhs.fileURL == rhs.fileURL
}
var id: UUID
var fileURL: URL
var fileName: String
var subfolder: [Folder]?
}
subfolder는 반드시 옵셔널 형태의 배열 타입을 지정해야 합니다.- 밑에서
List > children기능을 이용할 때 배열 원소 개수가 0이라도 하위 목록 열기 토글이 표시되며, - 배열 변수 자체가
nil이여야만 하위 목록 열기 토글이 표시되지 않습니다.
- 밑에서

트리 표시 뷰 생성
import SwiftUI
struct SelectFolderView: View {
@StateObject private var viewModel = SelectFolderViewModel()
var body: some View {
NavigationStack {
List(viewModel.directoryList, children: \.subfolder) { directory in
Label(directory.fileName, systemImage: "folder.fill")
}
.onTapGesture {
}
.navigationTitle("이동할 폴더 선택")
}
}
}
#Preview {
SelectFolderView()
}
List의children기능을 사용하면 접이식 메뉴를 사용할 수 있어서 목록 셀을 펼치고 접을 수 있습니다.
뷰모델 생성
import Foundation
class SelectFolderViewModel: ObservableObject {
@Published private(set) var directoryList: [Folder] = []
init() {
guard let documentDirectoryURL = FileManager.documentDirectoryURL else {
return
}
var rootFolder: Folder = .init(id: .init(), fileURL: documentDirectoryURL, fileName: "Root", subfolder: [])
directoryListRecursively(parentFolder: &rootFolder, nextURL: documentDirectoryURL)
if let documents = rootFolder.subfolder?.first {
directoryList = [documents]
}
}
func directoryContents(at targetDirectoryURL: URL) throws -> [URL] {
return try FileManager.default.contentsOfDirectory(
at: targetDirectoryURL,
includingPropertiesForKeys: nil
).filter {
$0.hasDirectoryPath
}
}
func directoryListRecursively(parentFolder: inout Folder, nextURL: URL) {
guard let directoryContents = try? directoryContents(at: nextURL) else {
return
}
guard parentFolder.subfolder != nil else {
return
}
// guard let을 쓰지 않고 강제 언래핑 하는 이유: 배열에 직접 접근해서 값을 바꾸려고
if directoryContents.isEmpty {
parentFolder.subfolder!.append(.init(id: .init(), fileURL: nextURL, fileName: nextURL.lastPathComponent, subfolder: nil))
return
}
parentFolder.subfolder!.append(.init(id: .init(), fileURL: nextURL, fileName: nextURL.lastPathComponent, subfolder: []))
let lastCount = parentFolder.subfolder!.count - 1
for directoryContent in directoryContents {
directoryListRecursively(parentFolder: &parentFolder.subfolder![lastCount], nextURL: directoryContent.absoluteURL)
}
}
}
- directoryContents(at:)
-
- 현재 위치에서 하위 폴더 목록을 가져옵니다.
- 하나도 없다면
Optional의 빈 배열을 가져오고, 아니라면 옵셔널의 URL 배열을 리턴 (nil이 되는 경우는 없음)
- directoryListRecursively(parentFolder:nextURL:)
- 하위 디렉토리(들)가 있다면
nextURL이 들어있는Folder오브젝트를 새로 생성한 뒤 부모 폴더의subfolder에 삽입하고- 그 디렉토리들을 순회하면서 재귀적으로(recursively) 함수를 반복 호출합니다.
- 하위 디렉토리가 없다면
nextURL이 들어있는Folder오브젝트를 부모Folder의subfolder배열에 넣고 종료합니다.
- 하위 디렉토리(들)가 있다면
- init()
- 문서 디렉토리
URL을 가져온 후 시작 폴더(root folder)를 생성하고nextURL를 문서URL로 시작합니다. - 문서 디렉토리의 URL이 먼저 담기므로
rootFolder.subfolder?.first를 List에서 사용할@Published배열에 넣습니다.
- 문서 디렉토리

자세한 설명은 나중에…
🚧 이 글은 작성중입니다.

빠른 시일 내에 포스트 작성을 완료하겠습니다.





0개의 댓글