Sponsor Link
環境&対象
- macOS Monterey 12.2
- Xcode 13.2.1
- iOS 15.2
OutlineGroup
SwiftUI で アウトライン表示をしようと考えた時に、最初に候補にあがるのが、OutlineGroup です。
Apple のドキュメントは、こちら。
OutlineGroup 使い方
Apple のドキュメントにもありますが、シンプルに使うのは、非常に簡単です。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2022/01/27
// © 2022 SmallDeskSoftware
//
import SwiftUI
struct FileItem: Hashable, Identifiable, CustomStringConvertible {
var id: Self { self }
var name: String
var children: [FileItem]? = nil
var description: String {
switch children {
case nil:
return "📄 \(name)"
case .some(let children):
return children.isEmpty ? "📂 \(name)" : "📁 \(name)"
}
}
}
let data = FileItem(name: "users", children:
[FileItem(name: "user1234", children:
[FileItem(name: "Photos", children:
[FileItem(name: "photo001.jpg"),
FileItem(name: "photo002.jpg")]),
FileItem(name: "Movies", children:
[FileItem(name: "movie001.mp4")]),
FileItem(name: "Documents1", children: [])
]),
FileItem(name: "newuser", children:
[FileItem(name: "Documents2", children: [])
])
])
struct ContentView: View {
@State private var selection: FileItem?
var body: some View {
VStack {
OutlineGroup(data, children: \.children) { item in
Text("\(item.description)")
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
OutlineGroupの難しい点
シンプルに使う時には、直感的でわかりやすいのですが、以下のことをしようとすると、徐々に難しくなっていきます。
・要素の選択
・Drag&Drop
OutlineGroup での要素選択
Apple のサンプルのままでは、要素を選択できるようになりません。
OutlineGroup には、selection を引数として持つ initializer が存在しないのです。
以下のように OutlineGroup を List に内包するようにすると、selection できるようになります。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2022/01/27
// © 2022 SmallDeskSoftware
//
import SwiftUI
struct FileItem: Hashable, Identifiable, CustomStringConvertible {
var id: Self { self }
var name: String
var children: [FileItem]? = nil
var description: String {
switch children {
case nil:
return "📄 \(name)"
case .some(let children):
return children.isEmpty ? "📂 \(name)" : "📁 \(name)"
}
}
}
let data = FileItem(name: "users", children:
[FileItem(name: "user1234", children:
[FileItem(name: "Photos", children:
[FileItem(name: "photo001.jpg"),
FileItem(name: "photo002.jpg")]),
FileItem(name: "Movies", children:
[FileItem(name: "movie001.mp4")]),
FileItem(name: "Documents1", children: [])
]),
FileItem(name: "newuser", children:
[FileItem(name: "Documents2", children: [])
])
])
struct ContentView: View {
@State private var selection: FileItem?
var body: some View {
VStack {
List(selection: $selection) {
OutlineGroup(data, children: \.children) { item in
Text("\(item.description)")
}
}
}
.environment(\.editMode, .constant(.active))
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
OutlineGroup の root 要素選択
スクリーンショットでも気づくのですが、一番 root に存在する要素 (users) を選択することができません。
OutlineGroup では、root 要素は選択対象にできないようです。
今回の例で言うと、users は、root 要素ですので、選択対象となりません。
以下のように、root 要素を [FileItem] とすることで、選択対象とすることができます。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2022/01/27
// © 2022 SmallDeskSoftware
//
import SwiftUI
struct FileItem: Hashable, Identifiable, CustomStringConvertible {
var id: Self { self }
var name: String
var children: [FileItem]? = nil
var description: String {
switch children {
case nil:
return "📄 \(name)"
case .some(let children):
return children.isEmpty ? "📂 \(name)" : "📁 \(name)"
}
}
}
let data = [ // 👈 最初の FileItem も配列に入れるのがポイント
FileItem(name: "users", children:
[FileItem(name: "user1234", children:
[FileItem(name: "Photos", children:
[FileItem(name: "photo001.jpg"),
FileItem(name: "photo002.jpg")]),
FileItem(name: "Movies", children:
[FileItem(name: "movie001.mp4")]),
FileItem(name: "Documents1", children: [])
]),
FileItem(name: "newuser", children:
[FileItem(name: "Documents2", children: [])
])
])]
struct ContentView: View {
@State private var selection: FileItem?
var body: some View {
VStack {
List(selection: $selection) {
OutlineGroup(data, children: \.children) { item in
Text("\(item.description)")
}
}
}
.environment(\.editMode, .constant(.active))
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
OutlineGroup の ドラッグ&ドロップ
OutlineGroup の子要素として作成する View に .onDrag を設定することで、ドラッグは可能になるのですが、ドロップ可能になる方法がわかりません・・・
通常は、onInsert を ForEach に設定することで対応しますが、OutlineGroup の場合は、対応する ForEach は存在しません。
もちろん、子 View に設定することはできませんし、List に設定することもできません。
既存の OutlineGroup を使うことでは、ドラッグ&ドロップ操作は実装できないようです。
つまり、自分で onInsert や onDrag を使って実装する必要があります。
このような実装は、OutlineGroup のままではできないので、ForEach に分解する必要があります。
まとめ
OutlineGroup を使う時に役立ちそうなことをまとめてきました。
- List に内包させることで、selection はできる
- root 要素も、Array に入れることで selection 可能になる
- Drag&Drop は、自分で”1から”実装する必要がある
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link