swiftswiftui

'buildExpression' is unavailable when creating a custom dot indicator


I'm trying to create a custom dot indicator (like the one under TabView). This is the code:

import SwiftUI

public struct CustomDotIndicatorView: View {
    private let indicatorCount: Int
    private let height: CGFloat
    private let spacing: CGFloat
    
    @State private var _activeIndex: Int = 0
    
    private let activeImage: some View = Image( "dotindicator-active")
        .renderingMode(.original)
        .colorMultiply(.green)
        
    private let inactiveImage: some View = Image( "dotindicator-inactive")
    
    public var activeIndex: Int {
        get { return self._activeIndex }
        set {
            guard newValue >= 0 && newValue < indicatorCount else { return }
            self._activeIndex = newValue
        }
    }
    
    init(indicatorCount: Int,
         default activeIndex: Int = 0,
         height: CGFloat = 6,
         spacing: CGFloat = 12) {
        
        let actualCount = indicatorCount > 1 ? indicatorCount : 1
        
        self.indicatorCount = actualCount
        self.height = height
        self.spacing = spacing
        
        if activeIndex < 0 {
            self.activeIndex = 0
        } else if activeIndex >= actualCount {
            self.activeIndex = actualCount - 1
        } else {
            self.activeIndex = activeIndex
        }
    }
    
    public var body: some View {
        HStack(alignment: .center, spacing: spacing) {
            ForEach(0..<indicatorCount) { index in
                index == self._activeIndex ? self.activeImage : self.inactiveImage
            }
        }
    }
}

However it has this error:

'buildExpression' is unavailable: this expression does not conform to 'View'
'buildExpression' has been explicitly marked unavailable here (SwiftUICore.ViewBuilder.buildExpression)

Can anybody help me? Thank you


Solution

  • Use a simple if else, such as:

    if index == self._activeIndex {
        self.activeImage
    } else {
        self.inactiveImage
    }
    

    instead of using a ternary expression that returns two different View types, which is not allowed in ForEach. This is because ViewBuilder does not handle the ternary case if the return types are not the same. For example:

    you could also use

      index == self._activeIndex ? AnyView(self.activeImage) : AnyView(self.inactiveImage)
    

    But I would not recommend using that approach.

    Note, you should not mutate @State properties via computed properties like activeIndex.