はじめに
GeometryReader
・LazyVStack
・clipped
を使いトリミングした画像を画面の横幅いっぱいに広げる画面を実装したところ、レイアウトで詰まったので備忘録として解決方法を残します。
環境
MacBook Pro 13 inch M1
Xcode14.2
iOS15~
実現したいこと・要件
・iOS15以上(カスタムLayout・Gridは使えない)
・オリジナルの画像から正方形にトリミングしたい(画像の比率は変えたくない)
・画像は横に2枚づつ並べたい
・画像は画面幅いっぱいに広がるように並べたい
・スクロールできるようにしたい
GeometryReaderを使ったところ問題が。。。
画像を切り取る際にはclippedが用意されておりますが、事前にframeを指定する必要があります。
GeometryReaderを使い、親Viewのサイズに応じてトリミングするframeの長さを指定する方針で実装しようと考えました。
struct ContentView: View {
let images: [Image] = .init(repeating: Image("IMG_6500"), count: 4)
private let space: CGFloat = 2
var body: some View {
GeometryReader { geometry in
// GeometryReaderの横幅を元に画像サイズを算出
let imageWidth: CGFloat = geometry.size.width / 2 - space
LazyVGrid(columns: .init(repeating: .init(.flexible(), spacing: space), count: 2), spacing: 2) {
ForEach(images) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
// GeometryReaderの横幅を元に画像をトリミング
.frame(width: imageWidth, height: imageWidth)
.clipped()
}
}
}
}
}
extension Image: Identifiable {
// TODO: プレビュー用にImageにIDを付与。実際のデータではImage型は直接利用せず、別の型にラップして使用予定
public var id: UUID { UUID() }
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
プレビュー画面
ContentViewのみプレビューすると、良さそうですが、、、
以下のようにScrollView内でContentViewを実装しようとすると
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ScrollView {
Rectangle().frame(height:100)
ContentView()
Rectangle().frame(height:400)
}
}
}
GeometryReaderを使用する際は高さを指定する必要がある。。。
GeometryReaderは親Viewのサイズ(高さ・横幅)に合わせてViewのサイズを決定するため、
ContentViewに高さ.frame(height:XXX)
を指定する必要があります。
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
GeometryReader { geometry in
ScrollView {
Rectangle().frame(height: 100)
ContentView()
.frame(height: geometry.size.width) // 高さ指定が必要(横幅と同じ高さを指定)
Rectangle().frame(height: 400)
}
}
}
}
ただし、そうなると、ContentViewを呼び出すViewも高さ.frame(height:XXX)
を指定する必要があり、堂々巡りになっていきます。。。
Rectangleを使った解決方法
画像をトリミングする方法をframe
を指定してトリミングするのではなく、
矩形Rectangle
を用意し、矩形に画像を重ね合わせ、トリミングする。
struct ContentView: View {
let images: [Image] = .init(repeating: Image("IMG_6500"), count: 4)
private let space: CGFloat = 2
var body: some View {
LazyVGrid(columns: .init(repeating: .init(.flexible(), spacing: space), count: 2), spacing: 2) {
ForEach(images) { image in
Rectangle()
.aspectRatio(1, contentMode: .fit)
.overlay {
image
.resizable()
.aspectRatio(contentMode: .fill)
}
.clipped()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ScrollView {
Rectangle().frame(height: 100)
ContentView()
Rectangle().frame(height: 400)
}
}
}
このように実装することでGeometryReaderを使用せず、画像をトリミング、並べることができました。
追記(2023/05/04)
もっとシンプルな方法を@akio0911さんに教えていただきました!
struct ContentView: View {
let images: [Image] = .init(repeating: Image("IMG_6500"), count: 4)
private let space: CGFloat = 2
var body: some View {
LazyVGrid(columns: .init(repeating: .init(.flexible(), spacing: space), count: 2), spacing: 2) {
ForEach(images) { image in
image
.resizable()
.scaledToFill()
.frame(minWidth: 0, maxWidth: .infinity)
.aspectRatio(1, contentMode: .fill)
.clipped()
}
}
}
}
上記内容に関して、改善案・簡単に実装する方法・その他の方法等ありましたらコメントに記載いただければ幸いです。よろしくお願いいたします。
参考記事
Qiita記事
SwiftUI で 親 View に ScrollView、子 View に GeometryReader の構造にしたら、高さのレイアウトがおかしくなった