SwiftUI: matchedGeometryEffect с вложенными представлениями

Я столкнулся с некоторой дилеммой. Мне нравится разделять взгляды для удобства чтения. так, например, у меня такая структура

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

таким образом, cellView имеет то же пространство имен для matchedGeometryEffect, что и DetailsView. сделать эффект перехода к подробному просмотру из элемента ячейки в списке. проблема в том, что это подробное представление ограничено экраном List2/View.

Вот некоторый код, чтобы сделать его более понятным

Сначала у меня есть основной вид

      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? Потому что это было бы очень грязно

1 ответ

Вот подход:

  • Получите размер 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()
                        }
                    }
            )
        }
    }
}
Другие вопросы по тегам