Swift,Objective-Cプログラミング ~ iOS ~

Objective-C,Swift,Apple Watchなどのプログラミング

【SwiftUI】一画面で複数のモーダルを出し分けする

はじめに

画面で2つの種類のモーダルを出すときにどのように実装したらよいかを考えました。(例えば、新規追加画面と編集画面など)

アプリ起動時 f:id:fjswkun:20200720092107p:plain

addボタンタップ時 f:id:fjswkun:20200720092119p:plain

editボタンタップ時 f:id:fjswkun:20200720092131p:plain

実装例

パターン1

モーダルを起動するボタンごとにsheet修飾子でモーダル表示をする。 デザインとアクションの実装が続き、読みにくい

struct ContentView: View {
    @State var isShowAddSheet = false
    @State var isShowEditSheet = false
    
    var body: some View {
        VStack {
            Text("Hello, World!")
            Button("add") {
                self.isShowAddSheet = true
            }
            .sheet(isPresented: $isShowAddSheet) {
                Text("add").background(Color.yellow)
            }
            Button("edit") {
                self.isShowEditSheet = true
            }.sheet(isPresented: $isShowEditSheet) {
                Text("edit").background(Color.green)
            }
        }
    }
}

パターン2

デザインとアクションが分けられてソースを読みやすくなった気がするが、やりたいことを実現できないのでNG。Viewはsheetをひとつしか持てないようで、addはボタンを押してもモーダル表示されませんでした。editボタンは問題なくモーダル表示されました。

import SwiftUI

struct ContentView: View {
    @State var isShowAddSheet = false
    @State var isShowEditSheet = false
    
    var body: some View {
        VStack {
            Text("Hello, World!")
            Button("add") {
                self.isShowAddSheet = true
            }
            
            Button("edit") {
                self.isShowEditSheet = true
            }
        }
        .sheet(isPresented: $isShowAddSheet) {
            Text("add").background(Color.yellow)
        }
        .sheet(isPresented: $isShowEditSheet) {
            Text("edit").background(Color.green)
        }
    }
}

パターン3

  • モーダル表示にはitem引数を持つ方のsheet修飾子を使う
  • item引数には出し分けするモーダルを表すenumを作って使う

モーダル表示にはitem引数を持つ方のsheet修飾子を使う

こちらを使う

public func sheet<Item, Content>(item: Binding<Item?>, onDismiss: (() -> Void)? = nil, @ViewBuilder content: @escaping (Item) -> Content) -> some View where Item : Identifiable, Content : View

item引数には出し分けするモーダルを表すenumを作って使う

画面で新規追加画面と編集画面の2つを出すとします。

下記のようなenumを作成します。

enum SheetType: Int, Identifiable {
    case add
    case edit
    
    var id: Int {
        return self.rawValue
    }
}

パターン3実装

import SwiftUI

struct ContentView: View {
    @State var sheetType: SheetType? = nil
    
    var body: some View {
        VStack {
            Text("Hello, World!")
            Button("add") {
                self.sheetType = .add
            }
            Button("edit") {
                self.sheetType = .edit
            }
            
        }
        .background(Color.gray)
        .sheet(item: self.$sheetType) { (t) -> AnyView in
            self.showSheet(type: t)
        }
    }
    
    private func showSheet(type: SheetType) -> AnyView {
        switch type {
        case .add:
            return AnyView(Text("add").background(Color.yellow))
        case .edit:
            return AnyView(Text("edit").background(Color.green))
        }
    }
    
    enum SheetType: Int, Identifiable {
        case add
        case edit
        
        var id: Int {
            return self.rawValue
        }
    }
}

実行

アプリ起動時 f:id:fjswkun:20200720092107p:plain

addボタンタップ時 f:id:fjswkun:20200720092119p:plain

editボタンタップ時 f:id:fjswkun:20200720092131p:plain

結論

他にもっといい方法があるかもしれないが、ひとまずenumを作ってやっておくのが良いとして実装した。