1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

iOS15で画面サイズに応じて画像をトリミングする際にはGeometryReader × frameではなく、Rectangleを使った方が良さそう→シンプルな方法がありました。

Last updated at Posted at 2023-05-03

はじめに

GeometryReaderLazyVStackclippedを使いトリミングした画像を画面の横幅いっぱいに広げる画面を実装したところ、レイアウトで詰まったので備忘録として解決方法を残します。

環境

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を使用せず、画像をトリミング、並べることができました。

image.png

追記(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 の構造にしたら、高さのレイアウトがおかしくなった

Twitter

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?