最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - Scrolling more than one item in SwiftUI ScrollView - Stack Overflow

programmeradmin0浏览0评论

I have the following scrollview that displays a certain number of cards. I want the scrollview to display only 2 cards at a time with slight peeking on the right to indicate more cards. The scrollview should also be scrollable by 2 cards at a time. For example, show card 1 & 2 with card 3 peeking, then on scroll show card 3 & 4 with card 5 peeking and then on scroll show card 5. If the number of cards are odd, the last card should display by itself but with leading alignment in the scrollview. I'd also like each card to be sized such that the 2 card display takes up most of the space such that each card is sized equally and with a spacing of 15 points between the cards and the edges (horizontally & vertically). How can I achieve this?

Here's my code:

struct CardGallery: View {
    @State var cards: [Int] = [1,2,3,4,5]
    
    var body: some View {
        GeometryReader { geometry in
            VStack {
                
                let width = geometry.size.width
                let height = geometry.size.height / 1.5
                let cardWidth = width / 2.2
                let cardHeight = height / 1.2
                
                ZStack {
                    ScrollView(.horizontal, showsIndicators: false) {
                        LazyHStack(spacing: 20) {
                            ForEach(cards, id: \.self) { level in
                                CardView(level: level)
                                    .frame(width: cardWidth,height: cardHeight)
                            }
                        }
                        .padding(.horizontal)
                    }
                    .scrollTargetBehavior(.paging)
                    .background {
                        Color.yellow
                    }
                    .clipShape(RoundedRectangle(cornerRadius: 15))
                    .overlay {
                        RoundedRectangle(cornerRadius: 15)
                            .strokeBorder(Color.black, lineWidth: 5)
                    }
                }
                .frame(width: width, height: height)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
        .background {
            Color.green.ignoresSafeArea(.all)
        }
    }
}

struct CardView: View {
    let level: Int
    
    var body: some View {
        VStack(alignment: .leading) {
            RoundedRectangle(cornerRadius: 15)
                .fill(Color.blue)
        }
        .padding()
    }
}

I have the following scrollview that displays a certain number of cards. I want the scrollview to display only 2 cards at a time with slight peeking on the right to indicate more cards. The scrollview should also be scrollable by 2 cards at a time. For example, show card 1 & 2 with card 3 peeking, then on scroll show card 3 & 4 with card 5 peeking and then on scroll show card 5. If the number of cards are odd, the last card should display by itself but with leading alignment in the scrollview. I'd also like each card to be sized such that the 2 card display takes up most of the space such that each card is sized equally and with a spacing of 15 points between the cards and the edges (horizontally & vertically). How can I achieve this?

Here's my code:

struct CardGallery: View {
    @State var cards: [Int] = [1,2,3,4,5]
    
    var body: some View {
        GeometryReader { geometry in
            VStack {
                
                let width = geometry.size.width
                let height = geometry.size.height / 1.5
                let cardWidth = width / 2.2
                let cardHeight = height / 1.2
                
                ZStack {
                    ScrollView(.horizontal, showsIndicators: false) {
                        LazyHStack(spacing: 20) {
                            ForEach(cards, id: \.self) { level in
                                CardView(level: level)
                                    .frame(width: cardWidth,height: cardHeight)
                            }
                        }
                        .padding(.horizontal)
                    }
                    .scrollTargetBehavior(.paging)
                    .background {
                        Color.yellow
                    }
                    .clipShape(RoundedRectangle(cornerRadius: 15))
                    .overlay {
                        RoundedRectangle(cornerRadius: 15)
                            .strokeBorder(Color.black, lineWidth: 5)
                    }
                }
                .frame(width: width, height: height)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
        .background {
            Color.green.ignoresSafeArea(.all)
        }
    }
}

struct CardView: View {
    let level: Int
    
    var body: some View {
        VStack(alignment: .leading) {
            RoundedRectangle(cornerRadius: 15)
                .fill(Color.blue)
        }
        .padding()
    }
}

Share Improve this question asked Jan 18 at 15:08 batmanbatman 2,4522 gold badges27 silver badges51 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 1

I would suggest showing the cards in pairs, using an HStack to combine each pair. Then use ViewAlignedScrollTargetBehavior to align the pairs to the left side of the scroll view.

Ways of iterating over an array in pairs are shown in the post Iterate over collection two at a time in Swift.

Other changes:

  • Add .scrollTargetLayout to the LazyHStack.

  • Add leading and trailing padding to the LazyHStack to give a space before the first card and a space after the last card, also adding the space needed to show the peeking card.

  • If the last pair only contains one card (in other words, if the number of cards is odd), use a placeholder to fill the space.

  • Remove the padding from CardView and manage the spacing in the main view instead.

Here is the updated example to show it working:

struct CardGallery: View {
    @State var cards: [Int] = [1,2,3,4,5]
    let gapSize: CGFloat = 15
    let peekSize: CGFloat = 40

    private var cardsInPairs: [(firstCard: Int, secondCard: Int?)] {
        stride(from: 0, to: cards.endIndex, by: 2).map {
            (cards[$0], $0 < cards.index(before: cards.endIndex) ? cards[$0.advanced(by: 1)] : nil)
        }
    }

    var body: some View {
        GeometryReader { geometry in
            let width = geometry.size.width
            let height = geometry.size.height / 1.5
            let cardWidth = (width - (3 * gapSize) - peekSize) / 2
            let cardHeight = height - (2 * gapSize)
            ZStack {
                ScrollView(.horizontal, showsIndicators: false) {
                    LazyHStack(spacing: gapSize) {
                        ForEach(Array(cardsInPairs.enumerated()), id: \.offset) { offset, cardPair in
                            HStack(spacing: gapSize) {
                                CardView(level: cardPair.firstCard)
                                    .frame(width: cardWidth, height: cardHeight)
                                if let secondCard = cardPair.secondCard {
                                    CardView(level: secondCard)
                                        .frame(width: cardWidth, height: cardHeight)
                                } else {
                                    Color.clear
                                        .frame(width: cardWidth, height: cardHeight)
                                }
                            }
                        }
                    }
                    .scrollTargetLayout()
                    .padding(.leading, gapSize)
                    .padding(.trailing, gapSize + peekSize)
                }
                .scrollTargetBehavior(.viewAligned)
                .background {
                    Color.yellow
                }
                .clipShape(RoundedRectangle(cornerRadius: 15))
                .overlay {
                    RoundedRectangle(cornerRadius: 15)
                        .strokeBorder(.black, lineWidth: 5)
                }
            }
            .frame(width: width, height: height)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
        .background(.green)
    }
}

struct CardView: View {
    let level: Int

    var body: some View {
        RoundedRectangle(cornerRadius: 15)
            .fill(.blue)
            .overlay {
                Text("\(level)")
                    .font(.title)
                    .foregroundStyle(.white)
            }
    }
}

发布评论

评论列表(0)

  1. 暂无评论