SwiftUI

[SwiftUI] リストアイテム を常に 移動 可能 にする

SwiftUI

SwiftUI において、リスト に登録した リストアイテム を 移動 する View を実装したかったのですが、 toolbar の EditButton を用いる実装では都度 Edit ボタン を クリック する必要があり、その手間を省けないかと思いました。 実装してみれば簡単なのですが、あまり サンプル も見つからなかったた事もあり、本記事で整理してみます。

Xcode: 12.4
Swift: 5

はじめに SwiftUI で リスト を実装

まずは、単純なリストを実装するところから始めます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<span class="hljs-keyword">import</span> SwiftUI
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView</span>: <span class="hljs-title">View</span> </span>{
<span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
<span class="hljs-type">SampleList</span>()
}
}
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView_Previews</span>: <span class="hljs-title">PreviewProvider</span> </span>{
<span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> previews: some <span class="hljs-type">View</span> {
<span class="hljs-type">ContentView</span>()
}
}
<span class="hljs-keyword">import</span> SwiftUI <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView</span>: <span class="hljs-title">View</span> </span>{ <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> { <span class="hljs-type">SampleList</span>() } } <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView_Previews</span>: <span class="hljs-title">PreviewProvider</span> </span>{ <span class="hljs-keyword">static</span> <span class="hljs-keyword">var</span> previews: some <span class="hljs-type">View</span> { <span class="hljs-type">ContentView</span>() } }
import SwiftUI

struct ContentView: View {
    var body: some View {
        SampleList()
    }
}

struct ContentView_Previews: PreviewProvider {

    static var previews: some View {
        ContentView()
    }
}

ContentView.swift

単純に5つの アイテム を生成し、 リスト に表示する シンプル な リスト です。

SampleList.swift

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<span class="hljs-keyword">struct</span> SampleList: View {
<span class="hljs-keyword">struct</span> Item: Identifiable {
<span class="hljs-keyword">let</span> id = <span class="hljs-constructor">UUID()</span>
<span class="hljs-keyword">let</span> title: String
}
@State <span class="hljs-keyword">private</span> var items: <span class="hljs-literal">[I<span class="hljs-identifier">tem</span>]</span> = (<span class="hljs-number">0.</span>.<<span class="hljs-number">5</span>).map { <span class="hljs-constructor">Item(<span class="hljs-params">title</span>: <span class="hljs-string">"Item #\($0)"</span>)</span> }
var body: some View {
NavigationView {
List {
<span class="hljs-constructor">ForEach(<span class="hljs-params">items</span>)</span> { item <span class="hljs-keyword">in</span>
<span class="hljs-constructor">Text(<span class="hljs-params">item</span>.<span class="hljs-params">title</span>)</span>
}
}
}
}
}
<span class="hljs-keyword">struct</span> SampleList: View { <span class="hljs-keyword">struct</span> Item: Identifiable { <span class="hljs-keyword">let</span> id = <span class="hljs-constructor">UUID()</span> <span class="hljs-keyword">let</span> title: String } @State <span class="hljs-keyword">private</span> var items: <span class="hljs-literal">[I<span class="hljs-identifier">tem</span>]</span> = (<span class="hljs-number">0.</span>.<<span class="hljs-number">5</span>).map { <span class="hljs-constructor">Item(<span class="hljs-params">title</span>: <span class="hljs-string">"Item #\($0)"</span>)</span> } var body: some View { NavigationView { List { <span class="hljs-constructor">ForEach(<span class="hljs-params">items</span>)</span> { item <span class="hljs-keyword">in</span> <span class="hljs-constructor">Text(<span class="hljs-params">item</span>.<span class="hljs-params">title</span>)</span> } } } } }
struct SampleList: View {

    struct Item: Identifiable {
        let id = UUID()
        let title: String
    }

    @State private var items: [Item] = (0..<5).map { Item(title: "Item #\($0)") }
    
    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in
                    Text(item.title)
                }
            }
        }
    }
}

EditButton を用いて、 リストアイテム を 移動 する

まずは、EditButton を用いた実装を紹介します。 なお、ここで NavigationView を追加していますが、追加せずとも動作するはずです。

以下の サイト を参考に、下記を追記しています。

  • move function を定義
  • リストアイテム の onMove の Perform アクション の Closure として指定
  • リストの Modifier に toolbar を追加し、要素として EditButton を追加
EditButton | Apple Developer Documentation
A button that toggles the edit mode environment value.

SampleList.swift

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
struct SampleList: <span class="hljs-keyword">View</span> {
struct Item: Identifiable {
let id = <span class="hljs-type">UUID</span>()
let title: String
}
@State private var items: [Item] = (<span class="hljs-number">0.</span>.<<span class="hljs-number">5</span>).map { Item(title: "Item #\($0)") }
var body: <span class="hljs-keyword">some</span> <span class="hljs-keyword">View</span> {
NavigationView {
List {
<span class="hljs-keyword">ForEach</span>(items) { item <span class="hljs-keyword">in</span>
Text(item.title)
}
.onMove(<span class="hljs-keyword">perform</span>: <span class="hljs-keyword">move</span>)
}
.navigationTitle("Navigation Title")
.toolbar {
EditButton()
}
}
}
private func <span class="hljs-keyword">move</span>(source: IndexSet, destination: <span class="hljs-type">Int</span>) {
items.<span class="hljs-keyword">move</span>(fromOffsets: source, toOffset: destination)
}
}
struct SampleList: <span class="hljs-keyword">View</span> { struct Item: Identifiable { let id = <span class="hljs-type">UUID</span>() let title: String } @State private var items: [Item] = (<span class="hljs-number">0.</span>.<<span class="hljs-number">5</span>).map { Item(title: "Item #\($0)") } var body: <span class="hljs-keyword">some</span> <span class="hljs-keyword">View</span> { NavigationView { List { <span class="hljs-keyword">ForEach</span>(items) { item <span class="hljs-keyword">in</span> Text(item.title) } .onMove(<span class="hljs-keyword">perform</span>: <span class="hljs-keyword">move</span>) } .navigationTitle("Navigation Title") .toolbar { EditButton() } } } private func <span class="hljs-keyword">move</span>(source: IndexSet, destination: <span class="hljs-type">Int</span>) { items.<span class="hljs-keyword">move</span>(fromOffsets: source, toOffset: destination) } }
struct SampleList: View {

    struct Item: Identifiable {
        let id = UUID()
        let title: String
    }

    @State private var items: [Item] = (0..<5).map { Item(title: "Item #\($0)") }
    
    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in
                    Text(item.title)
                }
                .onMove(perform: move)
            }
            .navigationTitle("Navigation Title")
            .toolbar {
                EditButton()
            }
        }
    }
    
    private func move(source: IndexSet, destination: Int) {
        items.move(fromOffsets: source, toOffset: destination)
    }
}

Editボタン を クリック することで、編集 モード になり、 リスト が移動できるようになります。


リストアイテム を常に 移動 できるようにする

UI によっては、都度 Edit ボタン を クリック するのが煩雑な場合もあるかと思います。
次に紹介するのは、 リスト の アイテム が常に移動できるようになる サンプル です。

以下の サイト を参考に、以下を実装します。

  • リスト の environment Modifier の中で、 EditMode.active を指定

https://developer.apple.com/documentation/swiftui/environmentvalues
https://developer.apple.com/documentation/swiftui/editmode

SampleList.swift

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
struct SampleList: <span class="hljs-keyword">View</span> {
struct Item: Identifiable {
let id = <span class="hljs-type">UUID</span>()
let title: String
}
@State private var items: [Item] = (<span class="hljs-number">0.</span>.<<span class="hljs-number">5</span>).map { Item(title: "Item #\($0)") }
var body: <span class="hljs-keyword">some</span> <span class="hljs-keyword">View</span> {
NavigationView {
List {
<span class="hljs-keyword">ForEach</span>(items) { item <span class="hljs-keyword">in</span>
Text(item.title)
}
.onMove(<span class="hljs-keyword">perform</span>: <span class="hljs-keyword">move</span>)
}
.navigationTitle("Navigation Title")
.environment(\.editMode, .<span class="hljs-keyword">constant</span>(EditMode.active))
}
}
private func <span class="hljs-keyword">move</span>(source: IndexSet, destination: <span class="hljs-type">Int</span>) {
items.<span class="hljs-keyword">move</span>(fromOffsets: source, toOffset: destination)
}
}
struct SampleList: <span class="hljs-keyword">View</span> { struct Item: Identifiable { let id = <span class="hljs-type">UUID</span>() let title: String } @State private var items: [Item] = (<span class="hljs-number">0.</span>.<<span class="hljs-number">5</span>).map { Item(title: "Item #\($0)") } var body: <span class="hljs-keyword">some</span> <span class="hljs-keyword">View</span> { NavigationView { List { <span class="hljs-keyword">ForEach</span>(items) { item <span class="hljs-keyword">in</span> Text(item.title) } .onMove(<span class="hljs-keyword">perform</span>: <span class="hljs-keyword">move</span>) } .navigationTitle("Navigation Title") .environment(\.editMode, .<span class="hljs-keyword">constant</span>(EditMode.active)) } } private func <span class="hljs-keyword">move</span>(source: IndexSet, destination: <span class="hljs-type">Int</span>) { items.<span class="hljs-keyword">move</span>(fromOffsets: source, toOffset: destination) } }
struct SampleList: View {

    struct Item: Identifiable {
        let id = UUID()
        let title: String
    }

    @State private var items: [Item] = (0..<5).map { Item(title: "Item #\($0)") }
    
    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in
                    Text(item.title)
                }
                .onMove(perform: move)
            }
            .navigationTitle("Navigation Title")
            .environment(\.editMode, .constant(EditMode.active))
        }
    }
    
    private func move(source: IndexSet, destination: Int) {
        items.move(fromOffsets: source, toOffset: destination)
    }
}

このように、 Edit ボタン を クリック することなく、 リスト の アイテム が常に移動可能となります。


まとめ

  • List の onMove モディファイア で move 部分を実装
  • Navigation View の environment モディファイア で \.editMode を指定することで 常に リスト を編集状態にすることが可能

関連記事

その他参考にしたサイト

How to let users move rows in a list - a free SwiftUI by Example tutorial
Learn Swift coding for iOS with these free tutorials
Ads