[SwiftUI] Outline 表示の方法

SwiftUI

iOS14 になってやっと、アウトライン表示ができるようになりました。その方法を説明します。

アウトラインビュー

List を使って、アウトラインを表示することができるように拡張されました。

Outline() みたいなビューが用意されるかと思っていたら、なんと List でできるようになりました。
Mac の AppKit を使っていて、OutlineView と TableView を置き換えるのが面倒に思うことがあったので、設定だけで切り替えられるのは便利だと思います。

List を使います

ということで、拡張された List ビューを使います。

アウトライン表示に使う List のコンストラクタ
List for Outline

Apple のドキュメントは、こちら

children: に対して、子要素を RandomAccessCollection に準拠するタイプで返すようなキーパスを与えます。

表示するときに、List ビューがこのキーパスを使用します。

なお、各要素のキーパス値が、nil であれば、下位層を表示するためのシンボルを表示しません。空の配列等が返されるのであれば、現時点では子要素がないけれども、持つかもしれないということで、下位層を表示するためのシンボルは表示されます。

難しく見えるかもしれませんが以下のように使えます。

List でアウトラインするときの例

List(data, children: \.children) { item in
  // item を使ったビュー
}

階層構造を持つデータを表示できます

当たり前ですが、階層的にアクセスできるような構造を持つ要素が必要になります。

以下のような struct を作ってみました。

OutlineItem

struct OutlineItem: Identifiable, Hashable, CustomStringConvertible {
    var id: Self { self }
    var name: String
    enum ItemType {
        case Folder
        case Symbol
        case Default
    }
    var type: ItemType = .Default
    var children:[OutlineItem]? = nil
    
    var description: String {
        switch type {
        case .Folder:
            return "\(name) ( \(children?.count ?? 0) )"
        case .Symbol:
            return "\(name)"
        case .Default:
            return "\(name)"
        }
    }
}

Children に、子要素を配列として持つようにしています。

具体的には、以下のようなサンプルデータを作りました。

サンプルデータ

let data = [OutlineItem(name: "Symbols", type: .Folder, children: [
                OutlineItem(name: "Transportation", type: .Folder, children: [
                OutlineItem(name: "bicycle", type: .Symbol, children: nil),
                OutlineItem(name: "car", type: .Symbol, children: nil)
            ]),
            OutlineItem(name: "Empty", type: .Folder, children: []),
            OutlineItem(name: "Weather", type: .Folder, children: [
                OutlineItem(name: "sun.min", type: .Symbol, children: nil),
                OutlineItem(name: "cloud", type: .Symbol, children: nil),
                OutlineItem(name: "moon", type: .Symbol, children: nil)
            ])]),
            OutlineItem(name: "Symbols.Fill", type: .Folder, children: [
                OutlineItem(name: "clock.fill", type: .Symbol, children: nil)]),
            OutlineItem(name: "sum", type: .Symbol, children: nil)]

OutlineItem が階層的に設定されています。

アウトライン表示

ビュー側では以下のように使用することになります。

アウトライン表示する List

struct ContentView: View {
    var body: some View {
        List(data, children: \.children) { item in
            OutlineItemView(item: item)
        }
        .padding()
    }
}

OutlineItem を表示するためのビューを別途作りました。

OutlineItemView

struct OutlineItemView : View {
    let item: OutlineItem
    var body: some View {
        switch item.type {
        case .Folder:
            HStack {
                Image(systemName: "folder")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 30, height: 30)
                Text("\(item.description)")
            }
        case .Symbol:
            Image(systemName: item.name)
                .resizable()
                .scaledToFit()
                .frame(width: 30, height: 30)
        default:
            Text("\(item.description)")
        }
    }
}

できあがったアウトラインビュー

List を使った アウトライン表示
OutlineByList

インデント、アイテムの開閉 など

特に設定せずともアウトライン表示であれば、以下のような表示・動作になりました。

  • 子要素が親要素よりも少し右に寄って表示される
  • 子要素を持つ要素には、右側に " > " が表示されて、開閉することができる

まとめ: List を使うと、アウトライン表示が簡単にできます

List を使ったアウトライン表示
  • List ビューに children: 指定することで、アウトライン表示される
  • 親子関係の表示位置調整は自動で行われる
  • 開閉のシンボル表示やタップによる表示切り替えはデフォルトで実装されている

説明は以上です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です