😊

【SwiftUI】スクロール可能なViewに対してはoverlayではなく、safeAreaInsetでボタン配置を行うといい

2024/03/06に公開

overlayではなくsafeAreaInsetを用いる理由

ScrollView,List,Formなどを使っている時、右下にボタンを配置したいということはよくある話だと思います。(右下にボタンを配置するサンプルコードは最後にあります。)

そんな時、overlayを使えば、右下にボタンを実装することができますが、コンテンツとボタンが被ってしまうという問題があります。(最下部までスクロールしても最後の行(No. 19)が表示されない。)

struct ContentView: View {
    var body: some View {
        List(0..<20) { num in
            Text("No. \(num)")
        }
        .overlay(alignment: .bottom) {
            Text("Hello, world.")
                .foregroundStyle(.white)
                .font(.largeTitle)
                .frame(maxWidth: .infinity)
                .padding()
                .background(.blue)
        }
    }
}

これを解決してくれるのがsafeAreaInsetです。overlayは言うなれば上塗りですが、safeAreaInsetは差し込みなので、最下部の行(No. 19)と重なってしまうことはありません。

※ iOS15.2以降でScrollViewだけでなく、List, Formも対応しました。

struct ContentView: View {
    var body: some View {
        List(0..<20) { num in
            Text("No. \(num)")
        }
        .safeAreaInset(edge: .bottom) {
            Text("Hello, world.")
                .foregroundStyle(.white)
                .font(.largeTitle)
                .frame(maxWidth: .infinity)
                .padding()
                .background(.blue)
        }
    }
}

サンプルコード

右下にボタンを配置したい場合は、以下の通りです。

struct ContentView: View {
    var body: some View {
        List(0..<20) { num in
            Text("No. \(num)")
        }
        .safeAreaInset(edge: .bottom, alignment: .trailing) {
            Button("action", action: {})
                .buttonStyle(.borderedProminent)
                .buttonBorderShape(.capsule)
                .padding(.trailing)
        }
    }
}

複数のボタンを配置することもできます。

struct ContentView: View {
    var body: some View {
        List(0..<20) { num in
            Text("No. \(num)")
        }
        .safeAreaInset(edge: .bottom, alignment: .trailing) {
            Button("action1", action: {})
                .tint(.orange)
                .buttonStyle(.borderedProminent)
                .buttonBorderShape(.capsule)
                .padding(.trailing)
        }
        .safeAreaInset(edge: .bottom, alignment: .trailing) {
            Button("action2", action: {})
                .tint(.green)
                .buttonStyle(.borderedProminent)
                .buttonBorderShape(.capsule)
                .padding(.trailing)
        }
    }
}

公式ドキュメント

https://developer.apple.com/documentation/swiftui/view/safeareainset(edge:alignment:spacing:content:)-6gwby

Discussion