问题
Im trying to replicate the cards from the Appstore's today tab:
https://imgur.com/a/1Jd4bI5
This is what I have so far:
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
}
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
But these only worked with static content (and no ScrollView. Using one results in overlapped PostViews) or didn't look right..
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?
Thanks in advance
回答1:
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.
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.
来源:https://stackoverflow.com/questions/62331530/animate-view-to-fullscreen-card-to-detail