Home > Blockchain >  Make a view generic and usable with TYPE as well as Binding<TYPE>
Make a view generic and usable with TYPE as well as Binding<TYPE>

Time:11-09

I just had a situation where I wanted to use a view which, takes a String, with a Binding:

struct MyButton: View {
    
    var text: String = ""
    
    var body: some View {
        Button{
            HStack {
                Text(text)
            }   
        }
    }
}

I couldn't get it to work in an acceptable time period, so I just created a second view:

struct MyBoundedButton: View {
    
    @Binding var text: String
    
    var body: some View {
        Button{
            HStack {
                Text(text)
            }   
        }
    }
}

This obviously works, but doesn't seem to be the most elegant solution. I tried to figure out how the SwiftUI framework does it, since there are plenty of examples of views which have very different initialisers (see the SwiftUI Button for instance). I still want to figure out how this might work, so since after work I'm trying to play around with generic view, but can't really get it to work. The following code doesn't work and is just to demonstrate the line of thinking I'm following:

struct MyButton<T>: View {
        
        var text: T
        
        var body: some View {
            Button{
                HStack {
                    Text(text)
                }   
            }
        }
    }

extension MyButton where text == Binding<String>{
    init(text: Binding<String>)
}

extension MyButton where text == String{
    init(text: String)
}

Does someone have an idea how this could be implemented or how Apple does this in their SwiftUI library?

Cheers, Philip

CodePudding user response:

This could be done different ways... your approach with generics seems more elegant, but I believed that you are seeking a more understandable code, simpler:

import SwiftUI

struct MyButton: View {
    
    private var text: Binding<String>?
    private var pureText: String?
    private var action: () -> Void
    
    init(text: Binding<String>, action: @escaping () -> Void) {
        self.action = action
        self.text = text
    }
    
    init(text: String, action: @escaping () -> Void) {
        self.action = action
        self.pureText = text
    }
    
    var body: some View {
        Button(action: action) {
            if let text = text {
                Text(text.wrappedValue)
            }
            if let text = pureText {
                Text(text)
            }
        }
    }
}

struct ContentView: View {
    
    @State private var text = "Press me"
    
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundColor(.accentColor)
            Text("Hello, world!")
            
            MyButton(text: $text) {
                print("Button pressed.")
            }
        }
    }
}

Although the binding in this case doesn't make much sense... binding is when you are changing the value of the variable and want to pass UP to the ParentView, a binding inside a Text(text) that will never change doesn't make sense to me, but this is how you done with multiple initializers.

  • Related