Edited at

【Swift】SwiftUIのListでダイナミックな高さを実現する方法

SwiftUIでListを使用していて

UITableViewCellのように各行をダイナミックな高さで表示をしようとする際に

layoutPriorityを使うと簡単に実現することができます。

https://developer.apple.com/documentation/swiftui/view/3278584-layoutpriority

2019/8/9現在

Screenshotは貼れないのでイメージ図で表現します🙇🏻‍♂️

Betaでなくなったら貼りたいと思います。

実際に行っているのはQiitaAPIから取得したデータをリストに表示しています。

左側にユーザのアイコン

真ん中に記事のタイトルとユーザ名

右側に静的ないいねマークの画像といいねの数

を表示します。

まずはlayoutPriorityがない状態です。


struct QiitaView: View {
let item: QiitaItemViewModel
var body: some View {
HStack {
ImageLoadingView(loader: ImagePath(path: item.profileImageURL))
.frame(width: 44, height: 44)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)

VStack(alignment: .leading) {
Text(item.title)
.lineLimit(nil)
.padding([.bottom], 12)

Text(item.userName)
.lineLimit(nil)
}
.padding([.leading, .trailing], 12)

Spacer()

VStack(alignment: .center) {
Image(systemName: "faceid")
.resizable()
.frame(width: 44, height: 44)

Text("\(item.likesCount)")
}
.frame(width: 60)
}
}
}

これを表示すると下記のようになります。

スクリーンショット 2019-08-08 14.14.53.png

真ん中のラベルの横幅は想定よりも狭くなってしまっています。

これをlayoutPriorityを用いて改善していきます。

まずは真ん中のVStacklayoutPriorityを設定します。

layoutPriorityはデフォルトが0で

1を設定することで優先度を他よりも高くすることができます。


struct QiitaView: View {
let item: QiitaItemViewModel
var body: some View {
HStack {
ImageLoadingView(loader: ImagePath(path: item.profileImageURL))
.frame(width: 44, height: 44)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)

VStack(alignment: .leading) {
Text(item.title)
.lineLimit(nil)
.padding([.bottom], 12)

Text(item.userName)
.lineLimit(nil)
}
.padding([.leading, .trailing], 12)
.layoutPriority(1) // ← ここに追加

Spacer()

VStack(alignment: .center) {
Image(systemName: "faceid")
.resizable()
.frame(width: 44, height: 44)

Text("\(item.likesCount)")
}
.frame(width: 60)
}
}
}

すると下記のイメージのような状態になります。

スクリーンショット 2019-08-08 14.14.59.png

横幅は改善されました。

しかし、まだタイトルが切れてしまっている状態です。

そこでTextにもlayoutPriorityを設定します。


struct QiitaView: View {
let item: QiitaItemViewModel
var body: some View {
HStack {
ImageLoadingView(loader: ImagePath(path: item.profileImageURL))
.frame(width: 44, height: 44)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)

VStack(alignment: .leading) {
Text(item.title)
.lineLimit(nil)
.padding([.bottom], 12)
.layoutPriority(1) // ← ここに追加

Text(item.userName)
.lineLimit(nil)
}
.padding([.leading, .trailing], 12)
.layoutPriority(1)

Spacer()

VStack(alignment: .center) {
Image(systemName: "faceid")
.resizable()
.frame(width: 44, height: 44)

Text("\(item.likesCount)")
}
.frame(width: 60)
}
}
}

すると下記のような形になります。

スクリーンショット 2019-08-08 14.14.45.png

イメージだけで大変恐縮ですが

もし何か参考になりましたら幸いです。

逆に間違いや他に良い方法があれば

教えていただけると嬉しいです🙇🏻‍♂️