【SwiftUI】List(PlainListStyle)配下のsheetでFormの背景が白くなる(iOS16)

SwiftUI

PlainListStyleのList配下のFormで背景が白くなる場合がありました。
iOS16から発生しています。同じコードでもiOS15では発生しませんでした。

どうやらlistStyleにplainを指定しているとsheet内のFormまで影響する様になってしまった様です。
iOS16でformStyleが追加された事が影響していると思います。

解決法

現状はsheetをList外に出すしかありません。
残念ながらFormにlistStyleやformStyleを使用しても解決しませんでした。

sheetには引数がisPresentedのものとitemのものがあります。
単一のsheetにはisPresentedが向いていますが、
ForEachなどでパラメータが違うsheetが複数ある場合はitemの方が向いています。
この2つを使い分けてList外にsheetを配置して行きます。

問題のコード

List配下にButtonを置いて、sheetを使ってFormを表示しています。
単純にButtonにsheetをつけたものとForEachを使ったものを用意しています。
ForEachでは子ViewにBool型の変数を持たせてisPresented引数のものを使っています。

struct ContentView: View {
    
    @State private var sheet = false
    
    var body: some View {
        NavigationView {
            List{
                ForEach(0..<5){ item in
                    ListRow(text: "Row \(item)")
                }
                
                Button("Test"){
                    sheet.toggle()
                }
                .sheet(isPresented: $sheet) {
                    Form{
                        Text("Test")
                    }
                }
            }
            .listStyle(.plain)
        }
    }
}


struct ListRow:View{
    
    let text:String
    @State private var sheet = false
    
    var body: some View{
        Button(text){
            sheet.toggle()
        }
        .sheet(isPresented: $sheet) {
            Form{
                Text(text)
            }
        }
    }
}

この様に背景が真っ白になってしまいます。
PlainListStyleが影響してFromに適用されている様です。

これをListの外に出してPlainListStyleが影響しないようにします。
単体の方は簡単ですがForEachを使っている方はそのままでは出来ません。

正常に動作するコード

Button単体の方はそのままsheetを外に出しました。

ForEachの方はitem引数の方にsheetを使っています。
こちらであればForEachの外に配置しても同じ様に動作させる事が可能です。

struct ListItem:Identifiable{
    let id:Int
}

struct FixedView: View {
    
    @State private var sheetItem:ListItem?
    @State private var sheet = false
    
    private let items = [ListItem(id: 0), ListItem(id: 1), ListItem(id: 2), ListItem(id: 3), ListItem(id: 4)]
    
    var body: some View {
        NavigationView {
            List{
                ForEach(items){ item in
                    FixedRow(item: item, sheetItem: $sheetItem)
                }
                
                Button("Test"){
                    sheet.toggle()
                }
            }
            .listStyle(.plain)
            .sheet(isPresented: $sheet) {
                Form{
                    Text("Test")
                }
            }
            .sheet(item:$sheetItem) { item in
                Form{
                    Text("Row \(item.id)")
                }
            }
        }
    }
}

struct FixedRow:View{
    
    let item:ListItem
    @Binding var sheetItem:ListItem?
    
    var body: some View{
        Button("Row \(item.id)"){
            sheetItem = item
        }
    }
}

FormがPlainListStyleの影響下から離れたので背景がお馴染みの色で表示される様になりました。

sheetのitem引数はBinding<Item>なので@Stateでマークした変数を用意します。
またこのItemはIdentifiableでなければいけない為、ListItem構造体を用意しました。
このsheetItemがnilでない場合に該当のデータを使用してsheetが表示されます。

今回の例では子Viewに分けずとも良いのですが、実際の場合は分けることも多いと思うのでそのまま分けました。
Itemそのものとsheetの表示に使う為のsheetItemをBindingで渡しています。
Buttonが押された時にsheetItemにitemを代入すればsheetが表示されます。

最後に

今回はsheetの使い方を工夫してFormがPlainListStyleの影響を受けない様に対応しました。
sheetの使い方としても子Viewそれぞれにあるよりもまとめて1つの方が良い形だと思います。

また、SwiftUIでは機能の追加や変更があった際に既存機能に影響が出てしまう場合は多々あります。
Viewの親子関係を見て問題の切り分けをして検証できる様になると対応力が上がると思います。

コメント

タイトルとURLをコピーしました