SwiftUIでChildViewのFrameを取得する

# SwiftUIでChildView（子View）のFrameを取得

親Viewの`Frame`は`GeometryReader`を使うことで取得できますが、ChilViewでの取得方法がわからなかったので調べました。

## `GeometoryReander`と`background`を利用する

`background`ファンクションは`View`へ`Color`を与えるために利用してると思います。
ここで`background`ファンクションを良くみてみましょう。

実は引数は`View` protocolに準拠しているものであればなんでもOKです。

```swift
@inlinable public func background<Background>(_ background: Background, alignment: Alignment = .center) -> some View where Background : View
```

`Color`だって`View`に準拠してます。

```swift
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension Color : View {
}
```

### `GeometoryReander`

`GeometoryReander`も`View`に準拠したstructです。
GeometoryReanderを使うとこんな感じでGeometoryReanderを囲む`View`の`frame`を取得できます。

```swift
GeometryReader { proxy -> AnyView in
                    let rect = proxy.frame(in: .global)
                    print(rect
                    return AnyView(EmptyView())
                }
```

## ChildViewではどうするのか

今回は`View`と`func`の両方を使う方法を紹介します。

### `View`を使う方法

まずは`View`を使う方法

#### `ChildReader`を作りました。

```swift
struct ChildReader<Content>: View where Content: View {

    var viewFrame: Binding<CGRect>

    var content: Content

    init (viewFrame: Binding<CGRect>, @ViewBuilder content: @escaping () -> Content) {
        self.viewFrame = viewFrame
        self.content = content()
    }

    var body: some View {
        self.content
            .background(
                GeometryReader { proxy -> AnyView in
                    let rect = proxy.frame(in: .global)
                    if rect.integral != self.viewFrame.wrappedValue.integral {
                        DispatchQueue.main.async {
                            self.viewFrame.wrappedValue = rect
                            print(rect)
                        }
                    }
                    return AnyView(EmptyView())

                }
        )
    }
}
```

取得したい`View`を`ChildReader`で囲むと`childRect`に`frame`が格納されます。

```swift
struct ContentView: View {
    @State var childRect: CGRect = .zero
    var body: some View {
        ChildReader(viewFrame: $childRect) {
            Text("Hello World!")
        }
    }
}
```

### `func`を使う方法

次に`func`を使う方法を紹介します。ここでは`View`をextensionしています。

```swift
extension View {
    func frame(frame: Binding<CGRect>) -> some View {
        self.background(
                GeometryReader { proxy -> AnyView in
                    let rect = proxy.frame(in: .global)
                    if rect.integral != frame.wrappedValue.integral {
                        DispatchQueue.main.async {
                            frame.wrappedValue = rect
                            print(rect)
                        }
                    }
                    return AnyView(EmptyView())
                }
        )
    }
}
```

使い方はシンプルです。

```swift
struct ContentView: View {
    @State var childRect: CGRect = .zero
    var body: some View {
        Text("Hello World!")
        .frame(frame: $childRect)
    }
}
```

それではみなさん快適なSwiftUIライフをお送りください！

---

SwiftUIとCloud Firestoreを簡単に連携できるサンプルプログラムを公開しています。
是非参考にどうぞ！

https://github.com/1amageek/Ballcap-iOS/tree/master/examples/WithSwiftUI

---

次の記事もオススメです。

- [SwiftUIの行儀の悪いお作法。](https://qiita.com/1amageek/items/8c583a00f2679dbf38df)
- [SwiftUIのsheetを出し分ける](https://qiita.com/1amageek/items/e90e1cfb0ad497e8b27a)