Search code examples
swiftuimodifier

In SwiftUI recurring Stacks simplifying


For MacOS I use the following code many times to create forms. I really like to create a modifier or extension to make it more simplified.

HStack{
    VStack (alignment: .leading, spacing: 5) {
         Text("Account Number").font(.headline)
         TextField("", text: $selectedBankAccount.accountNumber, prompt: Text("AccountNumber"))
    }
}
.frame(width: 450 )

I would love to see something like:

struct FormStackModifier: ViewModifier {
    var width : CGFloat
    func body(content: Content) -> some View {
        HStack {
            VStack(alignment: .leading, spacing: 5) {
                content
            }
        }
        .frame(width: width)
    }
}

// and use it like
ModifiedContent( 
   content: VStack { Text("Account Name").font(.headline)
                            TextField("", text: $selectedBankAccount.accountName, prompt: Text("AccountName"))},
   modifier: FormStackModifier(width: 450)
)

But I need stil the extra VStack in content.

Is there a to solve this and a way to make this neat?


Solution

  • Group is a view that does nothing but groups views together, allowing you to use multiple views as a single view. Unlike VStack, it doesn't enforce a particular layout on the views or do anything like that.

    You can replace the VStack with Group.

    ModifiedContent( 
       content: 
           Group { 
               Text("Account Name").font(.headline)
               TextField("", text: $selectedBankAccount.accountName, prompt: Text("AccountName"))
           },
       modifier: FormStackModifier(width: 450)
    )
    

    Using ModifiedContent like this is rather inconvenient. I recommend adding an extension on View that applies the FormStackModifier.

    extension View {
        func formStack(width: CGFloat) -> some View {
            modifier(FormStackModifier(width: width))
        }
    }
    
    // usage:
    
    Group { 
        Text("Account Name").font(.headline)
        TextField("", text: $selectedBankAccount.accountName, prompt: Text("AccountName"))
    }
    .formStack(width: 450)
    

    Alternatively, instead of a ViewModifier, you can make your own View called FormStack:

    struct FormStack<Content: View>: View {
        let width : CGFloat
        let content: Content
        
        init(width: CGFloat, @ViewBuilder content: () -> Content) {
            self.width = width
            self.content = content()
        }
        
        var body: some View {
            HStack {
                VStack(alignment: .leading, spacing: 5) {
                    content
                }
            }
            .frame(width: width)
        }
    }
    
    // usage:
    
    FormStack(width: 450) { 
        Text("Account Name").font(.headline)
        TextField("", text: $selectedBankAccount.accountName, prompt: Text("AccountName"))
    }