swiftbindingoptional-parameters

SwiftUI View with optional bound parameter


I am trying to modify Stewart Lynch's Rating View to support box a static representation and a "control" to set the rating. To do that, I need some optional properties to address the two different conditions. So currentRating is the current rating, to create a view of that number of symbols. maxRating is the maximum possible rating & mutableRating is the bound current rating, so I can display the cornet value as filled symbols, with the remainder of symbols up to maxRating unfilled, as well as an unfilled and struck through symbol to set the rating to 0.

Where I am having problems is making that bound parameter also optional. I found this that suggests it should be possible, but that is also rather old. In any case, I have tried various permutations as seen in the commented lines, trying to get Initialization to work, with various different failures.

struct RatingView: View {
    var currentRating: Int?
    
    var maxRating: Int?
    @Binding var mutableRating: Int?
    
    var width:Int
    var color: UIColor
    var sfSymbol: String
    
    public init(
        currentRating: Int? = nil,
        maxRating: Int? = nil,
        mutableRating: Binding<Int?>,
//        mutableRating: Binding<Int>?,
//        mutableRating: (Binding<Int>)?,
        width: Int = 20,
        color: UIColor = .systemYellow,
        sfSymbol: String = "star"
    ) {
        self.currentRating = currentRating
        self.maxRating = maxRating
        self._mutableRating = mutableRating
//        self._mutableRating = mutableRating ?? Binding.constant(nil)
        self.width = width
        self.color = color
        self.sfSymbol = sfSymbol
    }
    
    public var body: some View {
        Text("")
    }
}

#Preview ("mutating") {
    struct PreviewWrapper: View {
        @State var rating: Int? = 3
        
        var body: some View {
            RatingView(
                maxRating: 5,
                mutableRating: $rating,
                width: 30,
                color: .red,
                sfSymbol: "heart"
            )
        }
    }
    return PreviewWrapper()
}

#Preview ("non mutating") {
    struct PreviewWrapper: View {
        
        var body: some View {
            RatingView(
                currentRating: 3,
                width: 20,
                color: .red,
                sfSymbol: "heart"
            )
        }
    }
    return PreviewWrapper()
}

So, my first question is, can I still do this in iOS 17 and if so how? And second, I am curious about the details in what is the difference between these three approaches to Init arguments?

mutableRating: Binding<Int?>
mutableRating: Binding<Int>?
mutableRating: (Binding<Int>)?

EDIT: Clarification of failures When I use mutableRating: Binding<Int?> & self._mutableRating = mutableRating I get Missing argument for parameter 'mutableRating' in call in the non mutating preview. Which I assume means that the parameter is not actually optional, which makes sense given the differences mentioned. If I change the argument line to mutableRating: Binding<Int>? to make the parameter itself optional then I get Cannot assign value of type 'Binding<Int>' to type 'Binding<Int?>' on the initialization line. One of the fix options offered suggests that this might work, self._mutableRating = mutableRating ?? nilbut that producesCannot assign value of type 'Binding?' to type 'Binding<Int?>'` which brings us back around to the change in the argument.


Solution

  • From my perspective, I believe you're looking for:

    public init(
        ...
        mutableRating: Binding<Int?> = .constant(nil),
        ...
    ) { }
    

    With the code above, both mutating and non mutating previews are valid.