SwiftUI:matchedGeometryEffect 与嵌套视图

SwiftUI: matchedGeometryEffect With Nested Views

我面临一些困境。我喜欢将我的观点分开以便于阅读。所以例如我有这种结构

MainView -> 
--List1
----Items1
--List2 
----Items2
----DetailView
------CellView

因此 cellView 具有与 DetailsView 相同的 matchedGeometryEffect 命名空间。使从列表中的单元格项目过渡到详细视图的效果。问题是此详细信息视图仅限于 List2 屏幕/视图。

这里有一些代码可以让它更清楚

首先我有主视图

struct StartFeedView: View {

     
    var body: some View {
        ScrollView(.vertical) {
            ShortCutView()

            PopularMoviesView()
        }
    }
}

然后我有 PopularMoviesView()

struct PopularMoviesView: View {
    @Namespace var namespace
    @ObservedObject var viewModel = ViewModel()
    @State var showDetails: Bool = false
    @State var selectedMovie: Movie?

    var body: some View {
        ZStack {
            if !showDetails {
                VStack {
                    HStack {
                        Text("Popular")
                            .font(Font.itemCaption)
                            .padding()
                        Spacer()
                        Image(systemName: "arrow.forward")
                            .font(Font.title.weight(.medium))
                            .padding()

                    }
                    ScrollView(.horizontal) {
                        if let movies = viewModel.popularMovies {
                            HStack {
                                ForEach(movies.results, id: \.id) { movie in
                                    MovieCell(movie: movie, namespace: namespace, image: viewModel.imageDictionary["\(movie.id)"]!)
                                        .padding(6)
                                        .frame(width: 200, height: 300)
                                        .onTapGesture {
                                            self.selectedMovie = movie

                                            withAnimation(Animation.interpolatingSpring(stiffness: 270, damping: 15)) {
                                                showDetails.toggle()
                                            }

                                        }
                                }
                            }
                        }
                    }
                    .onAppear {
                        viewModel.getMovies()
                    }
                }


            }

            if showDetails, let movie = selectedMovie, let details = movie.details {
                MovieDetailsView(details: details, namespace: namespace, image: viewModel.imageDictionary["\(movie.id)"]!)
                    .onTapGesture {
                        withAnimation(Animation.interpolatingSpring(stiffness: 270, damping: 15)) {
                            showDetails.toggle()
                        }
                    }
            }
        }
    }
}

所以每当我点击 MovieCell.. 它都会扩展到主视图上 PopularMoviesView 边界的限制。

是否有某种方法可以使其全屏显示而不必将详细视图注入 MainView?因为那真的很脏

这里有一个方法:

  • GeometryReader获取MainView的大小,传下去
  • 在 DetailView 中使用 .overlay 可以比其父视图更大, 如果您明确指定 .frame
  • 您需要另一个内部 GeometryReader 来获取内部视图的顶部位置以进行偏移。

struct ContentView: View {
    
    var body: some View {
        // get size of overall view
        GeometryReader { geo in
            ScrollView(.vertical) {
                Text("ShortCutView()")
                
                PopularMoviesView(geo: geo)
            }
        }
    }
}


struct PopularMoviesView: View {
    
    // passed in geometry from parent view
    var geo: GeometryProxy
    // own view's top position, will be updated by GeometryReader further down
    @State var ownTop = CGFloat.zero
    
    @Namespace var namespace
    @State var showDetails: Bool = false
    @State var selectedMovie: Int?
    
    
    var body: some View {
        
        if !showDetails {
            VStack {
                HStack {
                    Text("Popular")
                        .font(.caption)
                        .padding()
                    Spacer()
                    Image(systemName: "arrow.forward")
                        .font(Font.title.weight(.medium))
                        .padding()
                    
                }
                ScrollView(.horizontal) {
                    HStack {
                        ForEach(0..<10, id: \.self) { movie in
                            Text("MovieCell \(movie)")
                                .padding()
                                .matchedGeometryEffect(id: movie, in: namespace)
                                .frame(width: 200, height: 300)
                                .background(.yellow)
                            
                                .onTapGesture {
                                    self.selectedMovie = movie
                                    
                                    withAnimation(Animation.interpolatingSpring(stiffness: 270, damping: 15)) {
                                        showDetails.toggle()
                                    }
                                }
                        }
                    }
                }
            }
        }
        
        if showDetails, let movie = selectedMovie {
            // to get own view top pos
            GeometryReader { geo in Color.clear.onAppear {
                ownTop = geo.frame(in: .global).minY
                print(ownTop)
            }}
            
            // overlay can become bigger than parent
            .overlay (
                Text("MovieDetail \(movie)")
                    .font(.largeTitle)
                    .matchedGeometryEffect(id: movie, in: namespace)
                    .frame(width: geo.size.width, height: geo.size.height)
                    .background(.gray)
                    .position(x: geo.frame(in: .global).midX, y: geo.frame(in: .global).midY - ownTop)
                
                    .onTapGesture {
                        withAnimation(Animation.interpolatingSpring(stiffness: 270, damping: 15)) {
                            showDetails.toggle()
                        }
                    }
            )
        }
    }
}