SwiftUI - 像锁屏上的通知一样堆叠视图

SwiftUI - stacking views like the notifications on the Lockscreen

我正在尝试堆叠我的应用程序的视图,例如锁屏上的通知。 (见附件)我希望它们能够通过单击按钮堆叠并在单击视图时展开。我在 VStack 上尝试了很多东西并设置了其他视图的偏移量但没有成功。

是否有一种标准化且简单的方法可以做到这一点? 其他人已经尝试过并且对我编程有提示吗?

谢谢

Notifications stacked Notifications expanded

编辑:

这是我试过的一些代码:

VStack{
            ToDoItemView(toDoItem: ToDoItemModel.toDo1)
                .background(Color.green)
                .cornerRadius(20.0)
                .padding(.horizontal, 8)
                .padding(.bottom, -1)
                .zIndex(2.0)
            
            ToDoItemView(toDoItem: ToDoItemModel.toDo2)
                .background(Color.blue)
                .cornerRadius(20.0)
                .padding(.horizontal, 8)
                .padding(.bottom, -1)
                .zIndex(1.0)
                .offset(x: 0.0, y: offset)
                
            ToDoItemView(toDoItem: ToDoItemModel.toDo3)
                .background(Color.yellow)
                .cornerRadius(20.0)
                .padding(.horizontal, 8)
                .padding(.bottom, -1)
                .offset(x: 0.0, y: offset1)
            
            ToDoItemView(toDoItem: ToDoItemModel.toDo3)
                .background(Color.gray)
                .cornerRadius(20.0)
                .padding(.horizontal, 8)
                .padding(.bottom, -1)
                .offset(x: 0.0, y: offset1)
            
            ToDoItemView(toDoItem: ToDoItemModel.toDo3)
                .background(Color.pink)
                .cornerRadius(20.0)
                .padding(.horizontal, 8)
                .padding(.bottom, -1)
                .offset(x: 0.0, y: offset1)
        }
        
        
        Button(action: {
            if(!stacked){
                stacked.toggle()
                
                withAnimation(.easeOut(duration: 0.5)) { self.offset = -100.0 }
                
                withAnimation(.easeOut(duration: 0.5)) { self.offset1 = -202.0 }
            }
            else {
                stacked.toggle()
                
                withAnimation(.easeOut(duration: 0.5)) { self.offset = 0 }
                
                withAnimation(.easeOut(duration: 0.5)) { self.offset1 = 0 }
            }
        }, label: {
            Text("Button")
        })

所以第一个问题是视图 4 和 5 没有堆叠在视图 3 后面的 z 上。 第二个问题是按钮没有进入第 5 个视图。 (浏览量frame/VStack还是原来的尺寸)

Views stacked vertical Views stacked on z

我发现使用 GeometryReader 比使用 ZStack 并尝试使用 offsets/positions.fiddle 更容易获得精确定位。

这段代码中有一些数字是硬编码的,您可能希望使其更加动态,但它确实起作用了。它使用了很多您已经在做的想法(zIndexoffset 等):

struct ToDoItemModel {
    var id = UUID()
    var color : Color
}

struct ToDoItemView : View {
    var body: some View {
        RoundedRectangle(cornerRadius: 20).fill(Color.clear)
            .frame(height: 100)
    }
}


struct ContentView : View {
    @State private var stacked = true
    
    var todos : [ToDoItemModel] = [.init(color: .green),.init(color: .pink),.init(color: .orange),.init(color: .blue)]
    
    func offsetForIndex(_ index : Int) -> CGFloat {
        CGFloat((todos.count - index - 1) * (stacked ? 4 : 104) )
    }
    
    func horizontalPaddingForIndex(_ index : Int) -> CGFloat {
        stacked ? CGFloat(todos.count - index) * 4 : 4
    }
    
    var body: some View {
        VStack {
            GeometryReader { reader in
                ForEach(Array(todos.reversed().enumerated()), id: \.1.id) { (index, item) in
                    ToDoItemView()
                        .background(item.color)
                        .cornerRadius(20)
                        .padding(.horizontal, horizontalPaddingForIndex(index))
                        .offset(x: 0, y: offsetForIndex(index))
                        .zIndex(Double(index))
                }
            }
            .frame(height:
                        stacked ?  CGFloat(100 + todos.count * 4) : CGFloat(todos.count * 104)
            )
            .border(Color.red)
            Button(action: {
                withAnimation {
                    stacked.toggle()
                }
            }) {
                Text("Unstack")
            }
            Spacer()
        }
    }
}

如果您感兴趣,可以使用 ZStack 的另一种方式。

注-:

我已经在 view 中全局声明了 变量 ,如果您愿意,您可以创建自己的 model

    struct ContentViewsss: View {
    
    @State var isExpanded = false
    
    var color :[Color] = [Color.green,Color.gray,Color.blue,Color.red]
    var opacitys :[Double] = [1.0,0.7,0.5,0.3]
    var height:CGFloat
    var width:CGFloat
    var offsetUp:CGFloat
    var offsetDown:CGFloat
    
    init(height:CGFloat  = 100.0,width:CGFloat = 400.0,offsetUp:CGFloat = 10.0,offsetDown:CGFloat =  10.0) {
        self.height = height
        self.width = width
        self.offsetUp = offsetUp
        self.offsetDown = offsetDown
    }
    
    var body: some View {
        
            ZStack{
                ForEach((0..<color.count).reversed(), id: \.self){ index in
                    
                    Text("\(index)")
                        .frame(width: width , height:height)
                        .padding(.horizontal,isExpanded ? 0.0 : CGFloat(-index * 4))
                        .background(color[index])
                        .cornerRadius(height/2)
                        .opacity(isExpanded ? 1.0 : opacitys[index])
                        .offset(x: 0.0, y: isExpanded ? (height + (CGFloat(index) * (height + offsetDown))) : (offsetUp * CGFloat(index)))
                }
                
                Button(action:{
                    withAnimation {
                        if isExpanded{
                            isExpanded = false
                        }else{
                            isExpanded = true
                        }
                        
                    }
                    
                } , label: {
                    Text(isExpanded ? "Stack":"Expand")
                }).offset(x: 0.0, y: isExpanded ? (height + (CGFloat(color.count) * (height + offsetDown))) : offsetDown * CGFloat(color.count * 3))
                
            }.padding()
            
            Spacer().frame(height: 550)
        
    }
}