listswiftuiswiftui-zstack

SwiftUI List - some part of row content is overlapped by next row


When I am trying to add a plus button, which is overlapped on two rows of the list. The code works fine for cases: 1) When view will appear, 2) Scrolling from top to bottom (Every time). However, the code is not working properly when I scroll from bottom of the list to top. After scrolling from bottom to top, the next row is overlapped some part of plus image.

Moreover,I have tried to refresh the plus image by changing foreground color of the image when view appears on the screen. But, it didn't work. Here, I am toggling needRefresh variable of the Card model to refresh the plus image.

Requirement

struct Card: Identifiable {
    var cardNum = 0
    var id = UUID()
    var needRefresh: Bool = false

    init(cardNum: Int) {
        self.cardNum = cardNum
    }
}

class CardModel: ObservableObject {
    @Published var cards = [Card]()

    init() {
        for val in (1...10) {
            let card = Card(cardNum: val)
            cards.append(card)
        }
    }
}

struct ContentView: View {
    @StateObject var cardModel = CardModel()
    @State var refreshStatus = false

    var body: some View {
        NavigationView {
            List($cardModel.cards) { $card in
                CardViewRow(card: $card)
            }
            .navigationTitle("List")
            .navigationBarTitleDisplayMode(.inline)
        }
    }
}

struct CardViewRow: View {
    @Binding var card: Card
    var body: some View {
        ZStack(alignment: .bottom){
            CardView()

            Image(systemName: "plus.app.fill")
                .font(.system(size: 40.0))
                .offset(y: 25)
                .foregroundColor(card.needRefresh ? .blue : .red)
                .onAppear {
                    card.needRefresh.toggle()
                }
        }
    }
}

struct CardView: View {
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 25, style: .continuous)
                .fill(.white)
                .shadow(radius: 10)

            VStack {
                Text("Question")
                    .font(.largeTitle)
                    .foregroundColor(.black)

                Text("Answer")
                    .font(.title)
                    .foregroundColor(.gray)
            }
            .padding(20)
            .multilineTextAlignment(.center)
        }
        .frame(width: UIScreen.main.bounds.size.width - 40, height: 150)
    }
}

Solution

  • I think it will be tricky with a list, as it "crops" the list cells. But it would work like this with a ScrollView and LazyVStack:

    struct ContentView: View {
        @ObservedObject var cardVM = CardVM()
        @State var refreshStatus = false
    
        var body: some View {
            NavigationView {
                ScrollView {
                    LazyVStack(spacing: -15) {
                        ForEach(cardVM.cardList, id: \.id) { card in
                            CardViewRow(card: card)
                        }
                    }
                }
                .listStyle(.plain)
                .navigationTitle("List")
                .navigationBarTitleDisplayMode(.inline)
            }
        }
    }
    
    struct CardViewRow: View {
        @ObservedObject var card: Card
        var body: some View {
            
            CardView()
            
            Image(systemName: "plus.app.fill")
                .font(.system(size: 40.0))
                .offset(y: 0)
                .foregroundColor(card.needRefresh ? .blue : .red)
                .zIndex(1)
            
                .onAppear {
                    card.needRefresh.toggle()
                }
        }
    }