SwiftUI List の高さを、項目の数と高さに合わせてちょうどフィットさせる

サンプルは GitHub にある。




List の高さが項目に対して、ちょうどフィットされていないと以下の課題が発生する

  • List の高さが項目表示に足りない -> List 内スクロールが発生する
  • List の高さが項目表示より大きい -> List が大きすぎて、見た目が悪い

これを解決したいため、SwiftUI の List の高さを、項目に応じて、ちょうどフィットする高さに調整する方法を調べた。


サンプルの動作は、上のようになる。

コードを抜粋すると以下だ。

struct RowView: View {
    var name: String
    
    var body: some View {
        HStack {
            Spacer().frame(width: 10)
            
            Image(systemName: name).foregroundColor(.white)
            
            Text(name).foregroundColor(.white)
            
            Spacer()
        }.listRowBackground(Color.blue)
    }
}

struct ContentView: View {
    let rows = [
        "sun.min",
        "sun.min.fill",
        "sun.max",
        "sun.max.fill",
        "sunrise",
        "sunrise.fill",
        "sunset",
        "sunset.fill",
        "sun.dust",
        "sun.dust.fill",
        "sun.haze",
        "sun.haze.fill",
        "moon",
        "moon.fill",
        "moon.circle",
        "moon.circle.fill",
    ]
    static let rowHeight: CGFloat = 50
    static let rowMargin: CGFloat = 0.5 // I don't want to use fixed value. But i don't know right way.
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: true) {
            VStack {
                Text("Weather Symbols").font(.largeTitle)

                Spacer().frame(height: 10)
                
                List {
                    ForEach(0..<rows.count) { (i) in
                        RowView(name: self.rows[i]).frame(height: ContentView.rowHeight)
                    }
                    .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))  // Important!
                }
                .frame(height: CGFloat(rows.count) * (ContentView.rowHeight + ContentView.rowMargin))
                
                Spacer()
            }
        }
        .padding()
    }
}


重要なのは以下だ。

まず、リスト内の項目の Insets(マージン)を無しにする。

List {
    ForEach(0..<rows.count) { (i) in
        // ...
    }.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}


そして List 自体の高さを明示的に設定する。
この時、重要なのが項目に対して 0.5 をプラスしている箇所。
これが無いと List の高さが足りないためか、 List 内のスクロールが発生してしまう。

ここは 0.5 と固定で設定しているが、本来は SwiftUI から取得した値を使いたい。
しかし、それに当たるものを見つけることができなかった。
おそらく List の項目間にある Divider (仕切り線)の高さに相当するものだと思うだが・・・。

List {
}.frame(height: 項目の数 * (項目の高さ + 0.5))


まあ、とりあえず、こんな感じで List の高さを項目の数・高さに対して、ピッタリ設定することはできた。