在ContentView
中,我们使用.constant
的true
和false
来完成切换,如果我们要实现点击不同卡片视图都进行切换,那么我们需要声明一个变量数组来记录每一个卡片视图的切换状态。
@State private var showContents: [Bool] = Array(repeating: false, count: sampleModels.count)
我们声明了一个开放的变量showContents
数组,它存储每一个sampleModels
数组中的状态变量,因为我们默认是展示摘要视图,所以这里都用false
。
由于我们的ContentView
结构体中需要展示摘要视图,又要展示完整的内容视图。因此我们需要知道当前处于什么模式,我们这里使用计算属性来计算视图的当前模式。
enum ContentMode { case list case content } private var contentMode: ContentMode { self.showContents.contains(true) ? .content : .list }
上述代码中,我们定义了一个ContentMode
枚举,它有两种状态:list
摘要列表视图、content
完整内容视图。
然后我们声明了一个开放变量contentMode
,遵循ContentMode
协议,它根据我们之前声明的showContents
变量,判断是处于摘要列表模式,还是完整内容模式。
我们要将声明好的变量绑定到CardView
卡片视图中,并且我们再需要为CardView
卡片视图添加一个点击事件,用于切换状态。
运行预览下,我们可以看到我们完成了点击卡片,卡片从摘要视图切换到完整视图了。
但我们发现了一些问题,首先是从摘要视图切换到完整内容视图时,完整内容视图它没有铺满全屏,然后是切换到完整视图时,内容的部分看不到了。
我们一点点来完善它。
首先是摘要视图切换到完整视图时,应该铺满全屏。
.padding(.horizontal, self.showContents[index] ? 0 : 20) .opacity(self.contentMode == .list || self.contentMode == .content && self.showContents[index] ? 1 : 0)
我们设置边距padding
,它根据showContents
的状态进行切换,如果是完整内容视图情况下,我们的padding
为0
。
我们还增加了一个opacity
修饰符,用来展示我们选中的卡片浮在最上层,所以我们隐藏
非选中的卡片内容。
不错的样子。
我们完整内容模式下还需要隐藏顶部导航栏,我们也需要给顶部导航栏添加判断条件,根据当前模式contentMode判断是否隐藏。
.opacity(self.contentMode == .content ? 0 : 1)
额?上面还留有一段空白?
这是因为我们设置了GeometryReader
几何容器,而且我们之前还定义了整个卡片视图的高度不得超过500
,那我们这里如果要调整卡片视图的位置,我们就需要再加一个GeometryReader
几何容器包裹住整个滚动视图。
然后再重新根据屏幕高度设置卡片视图的显示区域。
.frame(height: self.showContents[index] ? fullView.size.height + fullView.safeAreaInsets.top + fullView.safeAreaInsets.bottom : min(sampleModels[index].image.size.height/3, 500))
上述代码中,我们在最外层的GeometryReader
几何容器中使用了参数fullView
,它允许我们访问屏幕的大小,因为最外层的GeometryReader
几何容器是获得屏幕的大小。
然后我们调整卡片视图的尺寸为展示的区域加上顶部和底部的安全区域,这样我们就获得了全屏展示的效果。
额?全屏时全屏了,可上面还是有空白的区域?
这是因为卡片视图确实会延长其高度,但不会改变它位置,所以就不会覆盖到上面的屏幕。
那么我们如何将整个视图移动上去呢?
移动,是个好办法。
我们在内层的GeometryReader
几何容器中增加一个offset
修饰符,用来移动位置,移动多少位置好呢?移动到整个内层的GeometryReader
几何容器的Y轴为0
就行了。
.offset(y: self.showContents[index] ? -inner.frame(in: .global).minY : 0)
不错!恭喜你,完成了本章节的所有内容。
本章代码
struct ContentView: View { @State private var showContents: [Bool] = Array(repeating: false, count: sampleModels.count) enum ContentMode { case list case content } private var contentMode: ContentMode { showContents.contains(true) ? .content : .list } var body: some View { GeometryReader { fullView in ScrollView { VStack(spacing: 40) { TopBarView() .padding(.horizontal, 20) .opacity(self.contentMode == .content ? 0 : 1) ForEach(sampleModels.indices, id: \.self) { index in GeometryReader { inner in CardView(category: sampleModels[index].category, headline: sampleModels[index].headline, subHeadline: sampleModels[index].subHeadline, image: sampleModels[index].image, content: sampleModels[index].content , isShowContent: self.$showContents[index]) .offset(y: self.showContents[index] ? -inner.frame(in: .global).minY : 0) .padding(.horizontal, self.showContents[index] ? 0 : 20) .opacity( self.contentMode == .list || self.contentMode == .content && self.showContents[index] ? 1 : 0) .onTapGesture { self.showContents[index] = true } } .frame(height: self.showContents[index] ? fullView.size.height + fullView.safeAreaInsets.top + fullView.safeAreaInsets.bottom : min(sampleModels[index].image.size.height / 3, 500)) } } } } } }
快来动手试试吧!