30

Lately started learning/developing apps with SwiftUI and seems pretty easy to build the UI components. However, struggling creating a BaseView in SwiftUI. My idea is to have the common UI controls like background , navigation , etc in BaseView and just subclass other SwiftUI views to have the base components automatically.

Shantanu
  • 3,086
  • 3
  • 25
  • 32
  • 7
    SwiftUI views are structs. You can not subclass a struct. SwiftUI is using modifier to do this kind of stuff. – Marc T. Aug 28 '19 at 06:44
  • Thanks @MarcT. for pointing out that, my bad. So, i have to use modifiers to build a BaseView and then use it throughout the app ? – Shantanu Aug 28 '19 at 07:32
  • @Shantanu I had the same feeling as I used to UIKit where I can easily, for instance, subclass a UIButton. May you be more specific on you question? Maybe if we understand exactly what you are trying to do we can try to find the best way to design it on SwiftUI. – superpuccio Aug 28 '19 at 21:24
  • @superpuccio : My aim is to create a BaseView which will have common UI util functions like setting background image, adding tab bar , setting navigation bar title , alerts , loading indicators all in one place. Any other SwiftUI class which i create should inherit the properties from BaseView. Also, I have written ViewModifiers , but still in doubt if any better approach exists. – Shantanu Aug 29 '19 at 04:39
  • @Shantanu I tried to give you an overview with a simple example. Let me know if I can help you with some specific things. – superpuccio Aug 29 '19 at 13:01

2 Answers2

47

Usually you want to either have a common behaviour or a common style.

1) To have a common behaviour: composition with generics

Let's say we need to create a BgView which is a View with a full screen image as background. We want to reuse BgView whenever we want. You can design this situation this way:

struct BgView<Content>: View where Content: View {
    private let bgImage = Image.init(systemName: "m.circle.fill")
    let content: Content

    var body : some View {
        ZStack {
            bgImage
                .resizable()
                .opacity(0.2)
            content
        }
    }
}

You can use BgView wherever you need it and you can pass it all the content you want.

//1
struct ContentView: View {
    var body: some View {
        BgView(content: Text("Hello!"))
    }
}

//2
struct ContentView: View {
    var body: some View {
        BgView(content:
            VStack {
                Text("Hello!")
                Button(action: {
                    print("Clicked")
                }) {
                    Text("Click me")
                }
            }
        )
    }
}

2) To have a common behaviour: composition with @ViewBuilder closures

This is probably the Apple preferred way to do things considering all the SwiftUI APIs. Let's try to design the example above in this different way

struct BgView<Content>: View where Content: View {
    private let bgImage = Image.init(systemName: "m.circle.fill")
    private let content: Content

    public init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    var body : some View {
        ZStack {
            bgImage
                .resizable()
                .opacity(0.2)
            content
        }
    }
}

struct ContentView: View {
    var body: some View {
        BgView {
            Text("Hello!")
        }
    }
}

This way you can use BgView the same way you use a VStack or List or whatever.

3) To have a common style: create a view modifier

struct MyButtonStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.red)
            .foregroundColor(Color.white)
            .font(.largeTitle)
            .cornerRadius(10)
            .shadow(radius: 3)
    }
}

struct ContentView: View {
    var body: some View {
        VStack(spacing: 20) {
            Button(action: {
                print("Button1 clicked")
            }) {
                Text("Button 1")
            }
            .modifier(MyButtonStyle())

            Button(action: {
                print("Button2 clicked")
            }) {
                Text("Button 2")
            }
            .modifier(MyButtonStyle())

            Button(action: {
                print("Button3 clicked")
            }) {
                Text("Button 3")
            }
            .modifier(MyButtonStyle())
        }
    }
}

These are just examples but usually you'll find yourself using one of the above design styles to do things.

EDIT: a very useful link about @functionBuilder (and therefore about @ViewBuilder) https://blog.vihan.org/swift-function-builders/

superpuccio
  • 11,674
  • 8
  • 65
  • 93
  • ViewModifiers for the win! – Sajjon Aug 29 '19 at 20:46
  • Thanks @superpuccio for the details. Really appreciate. Reading this has cleared my doubts – Shantanu Aug 30 '19 at 03:22
  • @Shantanu Take a look at my edit. I posted a very useful link about function builders. It definitely made me understand how they works. – superpuccio Aug 30 '19 at 08:24
  • @superpuccio : For sure. Cheers! – Shantanu Aug 30 '19 at 08:58
  • 2
    For buttons, you may want to look at ButtonStyle instead of ViewModifier. A ButtonStyle is like a ViewModifier, except it gets applied to the button's content instead of the button itself. For instance, in the above example, the added padding would not be clickable. This blog post explains it better: https://alejandromp.com/blog/2019/06/09/playing-with-swiftui-buttons/ – chkn Oct 11 '19 at 01:19
  • Thankx @superpuccio ... but how can i pass and manipulate `@ObservedObject` or other var to `BgView` ? – Wahab Khan Jadon Jul 23 '20 at 19:39
3

I got a idea about how to create a BaseView in SwiftUI for common usage in other screen

By the way Step .1 create ViewModifier

struct BaseScene: ViewModifier {
    
    /// Scene Title
    var screenTitle: String
    
    func body(content: Content) -> some View {
        VStack {
            HStack {
                Spacer()
                Text(screenTitle)
                    .font(.title)
                    .foregroundColor(.white)
                Spacer()
            }.padding()
            .background(Color.blue.opacity(0.8))
            content
        }
    }
}

Step .2 Use that ViewModifer in View

struct BaseSceneView: View {
    
    var body: some View {
        
        VStack {
            Spacer()
            Text("Home screen")
                .font(.title)
            Spacer()
        }
        .modifier(BaseScene(screenTitle: "Screen Title"))
    }
}

struct BaseSceneView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            BaseSceneView()
        }
    }
}

Your Output be like:

BaseView screen example

Kathiresan Murugan
  • 2,783
  • 3
  • 23
  • 44