80642


モーダル的に振る舞うボトムシートを作成しています。  
ボトムシートを開くときと閉じるときはアニメーションしたいです。  
下記のようにオフセットを使ったコードを書きました。  
このオフセットに自分自身のViewのサイズを渡せるとちょうどいい具合の位置に引っ込んで見えなくなると思うのですが、取得方法はわかるのですが、オフセットに渡す良い方法がわかりません。（ということで現在は適当に大きな値として`500`を与えています）なにか良い方法はありますか？

（自分自身のサイズと言っていますが、SwiftUIのモディファイアは新しいViewを入れ子にしていくようなイメージ？なので、本来は自分自身という表現は違うような気もするのですが、良い言葉がわかりません。コードコメント箇所を見ていただけると、やりたいことは伝わると思っています。）

コード：

[code]

    import SwiftUI
    
    struct ContentView: View {
      @State private var isShow = false
    
      var body: some View {
        ZStack {
          Button(
            "Show Sheet",
            action: {
              withAnimation {
                self.isShow.toggle()
              }
            }
          )
          .zIndex(0)
          .allowsHitTesting(!isShow)
    
          BottomSheet(
            isShow: self.$isShow,
            content: {
              VStack {
                Text("A")
                Text("B")
                Text("C")
              }
              .frame(
                maxWidth: .infinity
              )
              .background(Color(.yellow))
            }
          )
          .zIndex(1)
        }
      }
    }
    
    struct ScrimView: View {
      var body: some View {
        VStack {}.frame(
          maxWidth: .infinity,
          maxHeight: .infinity,
          alignment: .bottom
        )
        .background(
          Color(.gray)
        )
      }
    }
    
    struct BottomSheet<Content: View>: View {
      private let content: () -> Content
      @Binding var isShow: Bool
    
      init(
        isShow: Binding<Bool>,
        content: @escaping () -> Content
      ) {
        self._isShow = isShow
        self.content = content
      }
    
      var body: some View {
        ZStack(alignment: .bottom) {
          ScrimView().zIndex(
            0
          )
          .opacity(
            self.isShow ? 0.5 : 0
          )
    
          VStack {
            Button(
              "X",
              action: {
                withAnimation {
                  self.isShow = false
                }
              }
            )
            self.content()
          }
          .zIndex(1)
          .background(Color(.white))
          .cornerRadius(10)
          .offset(
            x: 0,
            y: self.isShow ? 0 : 500 // <-- 取得した高さを指定したい
          ).background(
            GeometryReader { g in
              let _ = print(g.size.height) // <-- ここで高さを取得できる
              Color(.clear)
            }
          )
        }
      }
    }
    
[/code]

メイン側コード：

[code]

    import SwiftUI
    
    @main
    struct FooApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    
[/code]

ほとんど、関係ないと思いますが、制作過程で行った別の質問も念の為参考として記載しておきます。  
[ios - How to prevent the ZStack lower level View from reacting? - Stack
Overflow](https://stackoverflow.com/questions/68573525/how-to-prevent-the-
zstack-lower-level-view-from-reacting)

この質問を書いたあとに別アプローチとして下記の質問もしたので、念の為追記しておきます。  
[ios - Why isn't one of the animations active? - Stack
Overflow](https://stackoverflow.com/questions/68577762/why-isnt-one-of-the-
animations-active)


CC BY-SA 4.0
2021-07-29T12:00:25.957
80466
2021-08-07T03:16:14.890
2021-07-29T15:17:36.423
9008
9008
question
swift ios swiftui
自分自身のサイズをオフセットに渡したい


サイズを取得して与えることは下記を参考にすることでできました。

[SwiftUI - Get size of
child?](https://stackoverflow.com/questions/56573373/swiftui-get-size-of-
child)

全体のコード：

[code]

    import SwiftUI
    
    struct ContentView: View {
      @State private var isShow = false
    
      var body: some View {
        ZStack {
          Button(
            "Show Sheet",
            action: {
              withAnimation {
                self.isShow.toggle()
              }
            }
          )
          .zIndex(0)
          .allowsHitTesting(!isShow)
    
          BottomSheet(
            isShow: self.$isShow,
            content: {
              VStack {
                Text("A")
                Text("B")
                Text("C")
              }
              .frame(
                maxWidth: .infinity
              )
              .background(Color(.yellow))
            }
          )
          .zIndex(1)
        }
      }
    }
    
    struct ScrimView: View {
      var body: some View {
        VStack {}.frame(
          maxWidth: .infinity,
          maxHeight: .infinity,
          alignment: .bottom
        )
        .background(
          Color(.gray)
        )
      }
    }
    
    struct BottomSheet<Content: View>: View {
      private let content: () -> Content
      @Binding var isShow: Bool
      @State var size: CGSize = .zero
    
      init(
        isShow: Binding<Bool>,
        content: @escaping () -> Content
      ) {
        self._isShow = isShow
        self.content = content
      }
    
      var body: some View {
        ZStack(alignment: .bottom) {
          ScrimView().zIndex(
            0
          )
          .opacity(
            self.isShow ? 0.5 : 0
          )
    
          ChildSizeReader(size: $size) {
            VStack {
              Button(
                "X",
                action: {
                  withAnimation {
                    self.isShow = false
                  }
                }
              )
              self.content()
            }
            .zIndex(1)
            .background(Color(.white))
            .cornerRadius(10)
          }
          .offset(
            x: 0,
            y: self.isShow ? 0 : size.height
          )
        }
      }
    }
    
    struct ChildSizeReader<Content: View>: View {
      @Binding var size: CGSize
    
      let content: () -> Content
      var body: some View {
        ZStack {
          content().background(
            GeometryReader { proxy in
              Color.clear.preference(
                key: SizePreferenceKey.self,
                value: proxy.size
              )
            }
          )
        }
        .onPreferenceChange(SizePreferenceKey.self) { preferences in
          self.size = preferences
        }
      }
    }
    
    struct SizePreferenceKey: PreferenceKey {
      typealias Value = CGSize
      static var defaultValue: Value = .zero
    
      static func reduce(value _: inout Value, nextValue: () -> Value) {
        _ = nextValue()
      }
    }
    
[/code]

ただ、セーフエリアの下側を考慮できていなかったのか、オフセットの距離は足りなかったようです。


CC BY-SA 4.0
2021-08-06T12:40:08.940
80642
2021-08-07T03:16:14.890
2021-08-07T03:16:14.890
9008
9008
80466
answer