swiftswiftui

Custom page indicator cannot change the indicator index


I'm trying to create a custom dot indicator view. This is the code so far. But the problem is, when I tried this in the preview, the "activeIndex" state didn't change and the indicator didn't show as expected. It still stuck at index 0. Can anybody help? Thanks.

Here's the code:

import SwiftUI

public struct CustomDotIndicatorView: View {
    let indicatorCount: Int
    @Binding var activeIndex: Int

    let height: CGFloat
    let spacing: CGFloat

    public var body: some View {
        let actualActiveIndex = min(max(activeIndex, 0), indicatorCount - 1)
        
        return HStack(alignment: .center, spacing: spacing) {
            ForEach(0..<indicatorCount) { index in
                CloveDotItemView(
                    isActive: Binding(get: { actualActiveIndex == index },
                                      set: { _ in }))
            }
        }
        .frame(height: height)
    }
}

private struct CustomDotItemView: View {
    @Binding var isActive: Bool
    
    private let activeImage: some View = Image( "dotindicator-active", bundle: Bundle(identifier: bundleID))
        .renderingMode(.original)
        .colorMultiply(.green)
    
    private let inactiveImage: some View = Image( "dotindicator-inactive", bundle: Bundle(identifier: bundleID))
    
    var body: some View {
        ZStack {
            if isActive {
                self.activeImage
            } else {
                self.inactiveImage
            }
        }
    }
}

struct CustomDotIndicatorView_Previews: PreviewProvider {
    @State static var activeIndex: Int = 0
    static let indicatorCount: Int = 5
    
    static var previews: some View {
        ZStack {
            Color.blue.opacity(0.2).ignoresSafeArea(.all)
            VStack {
                Spacer()
                HStack {
                    Button {
                        activeIndex -= 1
                    } label: {
                        Label("", systemImage: "chevron.left")
                    }
                    .padding(.leading, 48)
                    
                    Spacer()
                    
                    CustomDotIndicatorView(indicatorCount: indicatorCount,
                                          activeIndex: $activeIndex,
                                          height: 6,
                                          spacing: 12)
                    
                    Spacer()
                    
                    Button {
                        activeIndex += 1
                    } label: {
                        Label("", systemImage: "chevron.right")
                    }
                    .padding(.trailing, 48)
                    
                }
            }
        }
    }
}


Solution

  • So, I have fixed the bug. It is a Preview problem. Because Preview is static, then it won't mutate the Views when we are changing the State. So I need to move the whole `previews` body into a local internal wrapper struct `PreviewWrapper`. So it's like this:

    struct CustomDotIndicatorView_Previews: PreviewProvider {
        static var previews: some View {
            PreviewWrapper()
        }
        
        struct PreviewWrapper: View {
            
            @State private var activeIndex: Int = 0
            private let indicatorCount: Int = 5
            
            var body: some View {
                ZStack {
                    Color.blue.opacity(0.2).ignoresSafeArea(.all)
                    VStack {
                        Spacer()
                        HStack {
                            Button {
                                activeIndex -= 1
                            } label: {
                                Label("", systemImage: "chevron.left")
                            }
                            .padding(.leading, 48)
                            
                            Spacer()
                            
                            CustomDotIndicatorView(indicatorCount: indicatorCount,
                                                  activeIndex: $activeIndex,
                                                  height: 6,
                                                  spacing: 12)
                            
                            Spacer()
                            
                            Button {
                                activeIndex += 1
                            } label: {
                                Label("", systemImage: "chevron.right")
                            }
                            .padding(.trailing, 48)
                            
                        }
                    }
                }
            }
        }
    }
    

    Credit to: ChatGPT! Thank you ChatGPT. You're a champ.