iOS, Swift, SwiftUI

[SwiftUI] GeometryReaderでメソッド/関数を実行したい!

実行環境

  • Xcode 12.5

GeometryReaderでViewのサイズを取得する

[SwiftUI] GeometryReaderでViewのサイズを知るで解説したとおり、SwiftUIでViewのサイズを取得するにはGeometryReaderを使います。

GeometryReaderのハンドラーから渡されるGeometryProxyのインスタンスでViewのサイズがわかります。

struct LocalVariableView: View {
    var body: some View {
        GeometryReader { proxy in
            Text("\(proxy.frame(in: .local).debugDescription)")
        }
    }
}

struct LocalVariableView_Previews: PreviewProvider {
    static var previews: some View {
        LocalVariableView()
            .previewLayout(.fixed(width: 200, height: 100))
    }
}

実行結果はこの通り。

上記のコードではTextViewにViewの座標を表示していましたが、ローカル変数として計算してメソッドを実行したい場合があるでしょう。

しかしGeometryReaderのハンドラー内は@ViewBuilderの記法が適応されているので、一文ずつViewプロトコルに適合したインスタンスを返す必要があります。
これではVoidを返す関数やメソッドが実行できません。

試しにprint関数を実行してみましょう。

var body: some View {
    GeometryReader { proxy in
        let frame = proxy.frame(in: .local).debugDescription
        print(frame) // Voidを返す関数
        Text("\(proxy.frame(in: .local).debugDescription)")
    }
}

するとType '()' cannot conform to 'View'というコンパイルエラーが表示されます。

----------2021-06-29-9.53.17-1

このエラーを避けるため、GeometryReaderのハンドラーに明確に返すViewの型を指定する方法があります。

GeometryReader { proxy -> Text in
    let frame = proxy.frame(in: .local).debugDescription
    print(frame)
    return Text("\(proxy.frame(in: .local).debugDescription)")
}

proxy -> Textとすることで返す型を指定してreturnTextを返します。
するとコンパイルが通ります。

Viewサイズ取得してVoid関数を実行する

ここからは私が実際体験した話ですが、Image Viewのサイズを取得したい場合がありました。
取得したサイズは計算のみに使うので特定のViewを表示する必要はありません。
.overlay修飾子とEmptyViewを使って以下のようなコードを実装しました。

Image("people")
    .resizable()
    .aspectRatio(contentMode: .fit)
    .overlay(
        GeometryReader { proxy -> EmptyView in
            let frame = proxy.frame(in: .local).debugDescription
            print(frame)
            return EmptyView()
        }
    )

ところが、コードを実行してもprint(frame)が呼ばれませんでした。
完全に想像ですが、EmptyViewはViewが描写されないのでSwiftUIの方で実行しないように最適化しているのでしょうか?

AnyViewを返すようにするとprint関数が呼ばれました。

Image("people")
    .resizable()
    .aspectRatio(contentMode: .fit)
    .overlay(
        GeometryReader { geo -> AnyView in
            print(geo.frame(in: .local).debugDescription)
            return AnyView(EmptyView())
        }
    )

まとめ

GeometryReaderのハンドラー内でメソッドや関数を実行したい場合は

  • @ViewBuilderの記法から抜け出すために、ハンドラーに明確に返す型を指定して、returnでViewを返す
  • Viewを表示しないからといってEmptyViewを返すとそもそも実行されないことがある。
  • AnyViewで返す

宣伝

インプレスR&D社より、「1人でアプリを作る人を支えるSwiftUI開発レシピ」発売中です。
「SwiftUIでアプリを作る!」をコンセプトにSwiftUI自体の解説とそれを組み合わせた豊富なサンプルアプリでどんな風にアプリ実装すればいいかが理解できる本となっています。
iOS 14対応、Widgetの作成も一章まるまるハンズオンで解説しています。
SwiftUIを学びたい方、ぜひこちらのリンクをチェックしてください!




https://nextpublishing.jp/book/12491.html

Author image

About Sato Takeshi

  • Tokyo, Japan