
【SwiftUI】タップでセルがセクション間を移動するリストを実装したメモ
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
概要
大阪オフィスの山田です。絶賛SwiftUI勉強中なので、ObservableObjectやCombineを使いつつ、セクション間を移動できるリストを実装してみました。試行錯誤しながら実装したので、もっと良い方法があるかと思いますが、現時点で学習したことの整理も含め記事にしています。
開発環境
- Xcode: 11.7
- macOS: 10.15.4
作るもの
セルにチェックボックスがあり、チェックすると下のセクションに、チェックを外すと上のセクションに移動するリストです。

モデルを作成
まずモデルを作成します。
class Item: Identifiable, ObservableObject {
let id = UUID()
let title: String
@Published var check: Bool
init(title: String, check: Bool) {
self.title = title
self.check = check
}
// 表示確認用データを作成
static func make() -> [Item] {
return [
Item(title: "(´・ω・`)", check: true),
Item(title: "( ᐛ?)パァ", check: true),
Item(title: "( ᐛ?)ニャア", check: false),
Item(title: "٩( ᐛ )و.", check: false),
Item(title: "( ´・ω・`)人(´・ω・` )", check: false)
]
}
}
Identifiableを継承しているのは、ForEachで使うためです。ObservableObjectを継承しているのは、checkプロパティの変更を検出するためです。makeメソッドは検証に使うテストデータを作るメソッドです。
セルのViewとViewModelを作成
class ItemCellViewModel: ObservableObject {
@Published var item: Item
@Published var stateSystemName: String = ""
private var bag = [AnyCancellable]()
init(item: Item) {
self.item = item
item.$check.map {
$0 ? "checkmark.square" : "square"
}
.assign(to: \.stateSystemName , on: self)
.store(in: &bag)
}
func switchItem() {
item.check.toggle()
}
}
struct ItemCell: View {
@ObservedObject var vm: ItemCellViewModel
var body: some View {
HStack {
Image(systemName: vm.stateSystemName)
.onTapGesture {
self.vm.switchItem()
}
Text(vm.item.title)
}
}
}
ItemCellViewModelはモデルItemを生成時に受け取ります。stateSystemNameは、セルに表示するチェックボックスの画像名です。itemのcheckプロパティに変更があった場合に、stateSystemNameを適切なものに変更しています。mapによりBoolをStringに変換して、assignメソッドを使ってstateSystemName変数に代入しています。ItemCellはセル内の画像をタップした際に、ViewModelのswitchItemを実行してcheckのON/OFFを切り替えています。
ContentViewとContentViewModelの作成
struct ContentView: View {
@ObservedObject var vm = ContentViewModel()
var body: some View {
List {
Section(header: Text("Check False")) {
ForEach(vm.items.filter { $0.check == false }) { item in
ItemCell(vm: ItemCellViewModel(item: item))
}
}
Section(header: Text("Check True")) {
ForEach(vm.items.filter { $0.check == true }) { item in
ItemCell(vm: ItemCellViewModel(item: item))
}
}
}
}
}
class ContentViewModel: ObservableObject {
let items = Item.make()
private var bag = [AnyCancellable]()
init() {
items.forEach { [weak self] item in
guard let strongSelf = self else { return }
item.objectWillChange.sink {
strongSelf.objectWillChange.send()
}.store(in: &strongSelf.bag)
}
}
}
ContentViewModelはItemの配列を持ちます。ContentViewではcheckのon/offによってI
Itemをどのセクションに表示するか分けています。ContentViewModelの初期化処理で、配列のItemのうち、どれかに変更があった場合、ContentViewModelでobjectWillChenge.send()を実行し、変更があったことを通知するようにしています。この処理を入れないと、ContentViewに変更が通知されないため、チェックボックスのON/OFFを切り替えても、チェックボックスの画像は切り替わりますが、セルがSection間を移動しません。
終わり
今回、この記事を書ききるまで、結構、試行錯誤しました。
ModelをObservableObjectを適用するかどうかを考えたのですが、CoreDataで使うNSManagedObjectもObservableObjectが適用されているので、それに倣いました。
EnvironmentObjectを使うことも考えましたが、Viewの階層も深くないし、まだ僕自身の理解も浅いので、現時点では使用を見送りました。
まだまだ勉強不足だなぁ。実装たのしー。










