Sliding one SwiftUI view out from underneath another

后端 未结 2 672
旧巷少年郎
旧巷少年郎 2021-02-09 00:17

I\'m attempting to construct an animation using SwiftUI.

Start: [ A ][ B ][ D ]
End:   [ A ][ B ][    C    ][ D ]

The key elements of the anima

相关标签:
2条回答
  • 2021-02-09 01:07

    Since I originally replied to this question, I have been investigating GeometryReader, View Preferences and Anchor Preferences. I have assembled a detailed explanation that elaborates further. You can read it at: https://swiftui-lab.com/communicating-with-the-view-tree-part-1/

    Once you get the CCCCCCCC view geometry into the textRect variable, the rest is easy. You simply use the .offset(x:) modifier and clipped().

    import SwiftUI
    
    struct RectPreferenceKey: PreferenceKey {
        static var defaultValue = CGRect()
    
        static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
            value = nextValue()
        }
    
        typealias Value = CGRect
    }
    
    struct ContentView : View {
        @State private var textRect = CGRect()
        @State private var slideOut = false
    
        var body: some View {
    
            return VStack {
                HStack(spacing: 0) {
                    Text("AAAAAA")
                        .font(.largeTitle)
                        .background(Color.yellow)
                        .zIndex(4)
    
    
                    Text("BBBB")
                        .font(.largeTitle)
                        .background(Color.red)
                        .zIndex(3)
    
                    Text("I am a very long text")
                        .zIndex(2)
                        .font(.largeTitle)
                        .background(GeometryGetter())
                        .background(Color.green)
                        .offset(x: slideOut ? 0.0 : -textRect.width)
                        .clipped()
                        .onPreferenceChange(RectPreferenceKey.self) { self.textRect = $0 }
    
                    Text("DDDDDDDDDDDDD").font(.largeTitle)
                        .zIndex(1)
                        .background(Color.blue)
                        .offset(x: slideOut ? 0.0 : -textRect.width)
    
                }.offset(x: slideOut ? 0.0 : +textRect.width / 2.0)
    
                Divider()
                Button(action: {
                    withAnimation(.basic(duration: 1.5)) {
                        self.slideOut.toggle()
                    }
                }, label: {
                    Text("Animate Me")
                })
            }
    
        }
    }
    
    struct GeometryGetter: View {
        var body: some View {
            GeometryReader { geometry in
                return Rectangle()
                    .fill(Color.clear)
                    .preference(key: RectPreferenceKey.self, value:geometry.frame(in: .global))
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-09 01:20

    It's hard to tell what exactly you're going for or what's not working. It would be easier to help you if you showed the "wrong" animation you came up with or shared your code.

    Anyway, here's a take. I think it sort of does what you specified, though it's certainly not perfect:

    Observations:

    • The animation relies on the assumptions that (A) and (B) together are wider than (C). Otherwise, parts of (C) would appear to the left of A at the start of the animation.

    • Similarly, the animation relies on the fact that there's no spacing between the views. Otherwise, (C) would be appear to the left of (B) when it's wider than (B).

      It may be possible to solve both problems by placing an opaque underlay view in the hierarchy such that it is below (A), (B), and (D), but above (C). But I haven't thought this through.

    • The HStack seems to expand a tad more quickly than (C) is sliding in, which is why a white portion appears briefly. I didn't manage to eliminate this. I tried adding the same animation(.basic()) modifier to the HStack, the transition, the withAnimation call, and the VStack, but that didn't help.

    The code:

    import SwiftUI
    
    struct ContentView: View {
      @State var thirdViewIsVisible: Bool = false
    
      var body: some View {
        VStack(alignment: .leading, spacing: 20) {
          HStack(spacing: 0) {
            Text("Lorem ").background(Color.yellow)
              .zIndex(1)
            Text("ipsum ").background(Color.red)
              .zIndex(1)
            if thirdViewIsVisible {
              Text("dolor sit ").background(Color.green)
                .zIndex(0)
                .transition(.move(edge: .leading))
            }
            Text("amet.").background(Color.blue)
              .zIndex(1)
          }
            .border(Color.red, width: 1)
          Button(action: { withAnimation { self.thirdViewIsVisible.toggle() } }) {
            Text("Animate \(thirdViewIsVisible ? "out" : "in")")
          }
        }
          .padding()
          .border(Color.green, width: 1)
      }
    }
    
    0 讨论(0)
提交回复
热议问题