29
votes

I'm trying to build a custom NavBar with some optional Views, like a searchbar (but only if the view needs to display it).

I need to pass some @State properties with @Binding down the views, basically. But I also need them to be Optional parameters.

Here's an example:

struct NavBar: View {
    
    var isSearchable: Bool?
    
    @Binding var searchTxt: String
    @Binding var searchIsOn: Bool
    
    var navBarTitle: String
    var navBarAction: (() -> Void)?
    var navBarImage: String?
    
    init(navBarTitle: String, navBarAction: (() -> Void)? = nil, navBarImage: String? = nil, isSearchable: Bool? = false, searchTxt: (Binding<String>)?, searchIsOn : (Binding<Bool>)?) {
        self.navBarTitle = navBarTitle
        if(navBarAction != nil) {
            self.navBarAction = navBarAction!
        }
        if(navBarImage != nil) {
            self.navBarImage = navBarImage!
        }
        self.isSearchable = isSearchable
        
        self._searchTxt = (searchTxt != nil) ? (searchTxt!).binding : nil
        self._searchIsOn = (searchIsOn != nil) ? (searchIsOn!).binding : nil
        
        assert((navBarAction != nil) ? navBarImage != nil : true)
        assert((isSearchable! == true) ? (searchTxt!.value.count > 0) : true)
    }
// var body ....

}

The properties I'm talking about are searchIsOn and searchTxt. But doing the assignment self._searchTxt = searchTxt or self._searchIsOn = searchIsOn throws a compile error:

Cannot assign value of type 'Binding?' to type 'Binding'

Do you know how could I resolve this issue?

Or is there a better way to do what I'm trying to do?

4
I posted an answer, but I wanted to add one word of constructive criticism: Swift doesn't require semi-colons at the end of a line of code. Most would say if you remove them it's more "Swiftier". :-)dfd

4 Answers

49
votes
@Binding var searchTxt: String?

init(searchTxt: Binding<String?>?) {
    self._searchTxt = searchTxt ?? Binding.constant(nil)
}

Or consider this: TextField("", text: $text ?? "default value")

https://stackoverflow.com/a/61002589/4728060

func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
    Binding(
        get: { lhs.wrappedValue ?? rhs },
        set: { lhs.wrappedValue = $0 }
    )
}
15
votes

What you want is an Optional Binding of a String, not a Binding of an Optional String. I don't think you can achieve that by using the @Binding annotation.

However, you don't need to used the annotation. You can just declare the variable as a Binding:

Your

@Binding var searchTxt: String?

then turns to this

var searchTxt: Binding<String?>

But this way the syntax lets you place the ? wherever you want. So if you move it to the end, after the Binding's closing tag, you have what you want.

var searchTxt: Binding<String>?

If you want to access the String inside your Binding, you have to use the wrappedValue property.

Text(searchTxt!.wrappedValue)
1
votes

I have a UIViewControllerRepresentable in order to use UIImagePickerController. If you've ever used this image picker, you know that you need to image returned to be an optional. So in my ContentView I declared:

@State var uiImage: UIImage?

...

if uiImage != nil {
    Image(uiImage: $uiImage)
} else {
    Rectangle()
}

And in my ImagePicker (that's my SwiftUI view) I have:

@Binding var uiImage: UIImage?

Works like a charm.

(The if statement is pretty much psuedo-code, as I'm actually using an MTKView and a CIImage, but your get the drift - you use the optional just as you would anywhere else.)

0
votes

So below is example to use optional Binding and how to access the binding and toggle to show some view:

struct NavBar: View {
    @Binding var showUser: Bool
    var showOptional: Binding<Bool>?
    
    var body: some View {
    
    VStack {
     Button(action: { showUser.toggle() },
            label: { Text("NOT OPTIONAL") })
    
     Button(action: { showSelectBuddy?.wrappedValue.toggle()) },
            label: { Text("NOT OPTIONAL") })
    
    }
    }
    }

    struct UseNavbar: View {
    
    @State var showoptional = false
    @State var show = false 
    
    var body: some View {
    Text("Optional")
    .navigationBarItems(leading:
    NavBar(showUser: show), trailing: NavBar(showUser: show, showOptional: showoptional))
    }
    }