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()
}
}
)
}
}
}