Home > Blockchain >  How can I create a View type that can accept a String or a View producing closure for it's init
How can I create a View type that can accept a String or a View producing closure for it's init

Time:01-17

In SwiftUI, the Button struct declaration is this:

struct Button<Label> where Label : View

Two of Button's init's read as follows:

init<S>(_ title: S, action: @escaping () -> Void) where S : StringProtocol

and

init(action: @escaping () -> Void, label: () -> Label)

I'm trying to create a similar struct that can take either creates a Label : View from a String passed in the init, or passes a closure that produces a Label in the init.

Here's what I've tried:

import SwiftUI

struct ContentView: View {
    var body: some View {
        HelpView("Learn more about locked and unlocked tracks",
                 url: "https://example.com/help")
    }
}

struct HelpView<Label>: View where Label : View {
    let url: String
    var label: () -> Label
    
    init<S>(_ text: S, url: String) where S : StringProtocol {
        self.url = url
        self.label = { Text(text) }
    }
    
    init(url: String, label: @escaping () -> Label) {
        self.url = url
        self.label = label
    }
    @State private var showWebView = false
    var body: some View {
        Button {
            showWebView = true
        } label: {
            label()
        }
        .sheet(isPresented: $showWebView) {
            //WebView(url: URL(string: url)!)
        }
    }
}

This doesn't compile as I get

Generic parameter 'Label' could not be inferred

at the call site and

Cannot assign value of type 'Text' to type 'Label'

in the StringProtocol init.

How can I make this work?

CodePudding user response:

Just put init with string into separated extension with specifier (same as Apple does for Button)

Here is fixed code. Tested with Xcode 13.2 / iOS 15.2

struct HelpView<Label>: View where Label : View {
    let url: String
    var label: () -> Label

    init(url: String, label: @escaping () -> Label) {
        self.url = url
        self.label = label
    }

    @State private var showWebView = false
    var body: some View {
        Button {
            showWebView = true
        } label: {
            label()
        }
        .sheet(isPresented: $showWebView) {
            //WebView(url: URL(string: url)!)
        }
    }
}

extension HelpView where Label == Text {
    init<S>(_ text: S, url: String) where S : StringProtocol {
        self.url = url
        self.label = { Text(text) }
    }
}

CodePudding user response:

Here is a way for you:

struct ContentView: View {
    var body: some View {
        
        VStack(spacing: 20.0) {
            
            HelpView("Learn more about", url: "https://example.com/help")
            
            HelpView("https://Apple.com/help", label: { Text("find my iPhone") })
            
            HelpView("https://google.com/help", label: { Image(systemName: "globe") })
            
        }

    }
}

struct HelpView<Content>: View where Content: View {
    
    let url: String
    var label: () -> Content
    @State private var showWebView = false

    init(_ url: String, label: @escaping () -> Content) {
        self.url = url
        self.label = label
    }
    
    init(_ text: String, url: String) where Content == Text {
        
        self.init(url, label: { Text(text) })
    }

    var body: some View {
        
        Button { showWebView.toggle() } label: { label() }
            .sheet(isPresented: $showWebView) {
                webView
            }
        
    }
    
    var webView: some View {
        return Text(url)
    }
}
  •  Tags:  
  • Related