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()
}
}
)
}
}
}
我面临一些困境。我喜欢将我的观点分开以便于阅读。所以例如我有这种结构
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()
}
}
)
}
}
}