问题描述
我正在尝试从 Appstore 的今天"标签中复制卡片:
Im trying to replicate the cards from the Appstore's today tab:
这是我目前所拥有的:
struct ContentView: View {
@State var showDetail = false
@State var selectedForDetail : Post?
var posts = [...] // Just sample data: Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor...") etc.
var body: some View {
ZStack {
ScrollView{
ForEach(self.posts){ current in
PostView(post: current, isDetailed: self.$showDetail).onTapGesture {
self.selectedForDetail = current
withAnimation(.spring()){
self.showDetail.toggle()
}
}
}
}
if showDetail {
PostView(post: selectedForDetail!, isDetailed: self.$showDetail).onTapGesture {
withAnimation(.spring()){
self.showDetail.toggle()
}
}
}
}
}
}
struct PostView : View {
var post : Post
@Binding var isDetailed : Bool
var body : some View {
VStack(alignment: .leading){
HStack(alignment: .top){
Text(post.subtitle)
Spacer()
}.padding([.top, .horizontal])
Text(post.title).padding([.horizontal, .bottom])
if isDetailed {
Text(post.extra).padding([.horizontal, .bottom])
Spacer()
}
}
.background(isDetailed ? Color.green : Color.white)
.cornerRadius(isDetailed ? 0 : 16)
.shadow(radius: isDetailed ? 0 : 12)
.padding(isDetailed ? [] : [.top, .horizontal])
.edgesIgnoringSafeArea(.all)
}
}
struct Post : Identifiable {
var id = UUID()
var subtitle : String
var title : String
var extra : String
}
到目前为止,按下 PostView 可以全屏显示详细的 PostView.但动画看起来很遥远.我也尝试遵循这些视频教程:
It works so far that pressing a PostView shows a detailed PostView in fullscreen. But the animation looks way off. I also tried to follow these video tutorials:
https://www.youtube.com/watch?v=wOQWAzsKi4U
https://www.youtube.com/watch?v=8gDtf22TwW0
但这些只适用于静态内容(没有 ScrollView.使用一个结果会导致重叠的 PostView)或者看起来不正确..
But these only worked with static content (and no ScrollView. Using one results in overlapped PostViews) or didn't look right..
所以我的问题是如何改进我的代码以尽可能接近 Appstore 中的今天标签?我的方法可行吗?
So my question is how can i improve my code to get as close as possible to the todays tab in die Appstore? Is my approach even feasible?
提前致谢
推荐答案
请在下面找到已修改的代码以满足您的需求
Please find below your code modified to fit your needs
import SwiftUI
struct ContentView: View {
@State var selectedForDetail : Post?
@State var showDetails: Bool = false
// Posts need to be @State so changes can be observed
@State var posts = [
Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor..."),
Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor..."),
Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor..."),
Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor..."),
Post(subtitle: "test1", title: "title1", extra: "Lorem ipsum dolor...")
]
var body: some View {
ScrollView {
VStack {
ForEach(self.posts.indices) { index in
GeometryReader { reader in
PostView(post: self.$posts[index], isDetailed: self.$showDetails)
.offset(y: self.posts[index].showDetails ? -reader.frame(in: .global).minY : 0)
.onTapGesture {
if !self.posts[index].showDetails {
self.posts[index].showDetails.toggle()
self.showDetails.toggle()
}
}
// Change this animation to what you please, or change the numbers around. It's just a preference.
.animation(.spring(response: 0.6, dampingFraction: 0.6, blendDuration: 0))
// If there is one view expanded then hide all other views that are not
.opacity(self.showDetails ? (self.posts[index].showDetails ? 1 : 0) : 1)
}
.frame(height: self.posts[index].showDetails ? UIScreen.main.bounds.height : 100, alignment: .center)
.simultaneousGesture(
// 500 will disable ScrollView effect
DragGesture(minimumDistance: self.posts[index].showDetails ? 0 : 500)
)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct PostView : View {
@Binding var post : Post
@Binding var isDetailed : Bool
var body : some View {
VStack(alignment: .leading){
HStack(alignment: .top){
Text(post.subtitle)
Spacer()
// Only show close button if page is showing in full screen
if(self.isDetailed) {
// Close Button
Button(action: {
self.post.showDetails.toggle()
self.isDetailed.toggle()
}) {
Text("X")
.frame(width: 48, height: 48, alignment: .center)
.background(Color.white)
.clipShape(Circle())
}.buttonStyle(PlainButtonStyle())
}
}.padding([.top, .horizontal])
Text(post.title).padding([.horizontal, .bottom])
if isDetailed {
Text(post.extra).padding([.horizontal, .bottom])
Spacer()
}
}
.background(isDetailed ? Color.green : Color.white)
.cornerRadius(isDetailed ? 0 : 16)
.shadow(radius: isDetailed ? 0 : 12)
.padding(isDetailed ? [] : [.top, .horizontal])
.edgesIgnoringSafeArea(.all)
}
}
struct Post : Identifiable {
var id = UUID()
var subtitle : String
var title : String
var extra : String
var showDetails: Bool = false // We need this variable to control each cell individually
}
如果需要任何解释,请告诉我.
If any explanation is needed please let me know.
注意:我在您的 Post
模型中添加了一个 showDetails
属性,这是控制单个单元格所必需的.请记住,最佳做法是将其分离到不同的数组中,以处理可见的和不可见的,但现在可以这样做.
Note: I added a showDetails
property to your Post
model, this is needed to control individual cells. Keep in mind best practice is to separate that into a different array to take care of whats visible and what not but this will do for now.
另请注意,我们正在遍历数组的索引而不是对象,这样我们就可以灵活地选择要显示的内容.
Also note we are looping through indices of our array and not the objects, this way we have flexibility of choosing what to show.
这篇关于动画视图到全屏(卡片到细节)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!