TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: Initializing a View with a Property

Forums > SwiftUI

I am an expeienced professional iOS developer but very inexperienced with SwiftUI, just started using it last week.

I have a so-far-simple app that has a SwiftUI wrapper around a WebView and a footer view (named WebNavigationView) with navigation buttons. I have been following an example app at https://github.com/yamin335/SwiftUIWebView

As in the example app, I am using a PassthroughSubject to publish messages to the WebView.

I want to use the navigation buttons to send back and forward navigation commands to the webView. For proper code modularization, I want to pass the ContentView's viewModel to the WebNavigationView so i can call its PassthroughSubject from the buttons.

ContentView

struct ContentView: View {
   @ObservedObject var viewModel = ViewModel()

   var body: some View {
      VStack(spacing: 0) {
         WebView(currentURL: storeURL.urlString, viewModel: viewModel).overlay (
            RoundedRectangle(cornerRadius: 4, style: .circular)
               .stroke(Color.gray, lineWidth: 0.5)
         )
         WebNavigationView().frame(minWidth: 0, maxWidth: .infinity, minHeight: 64, maxHeight: 64).background(Color.white)
      }

   }
}

struct ContentView_Previews: PreviewProvider {
   static var previews: some View {
      ContentView()
   }
}

WebNavigationView so far is:

struct WebNavigationView: View {
   @State var viewModel: ViewModel

   var body: some View {
      HStack(alignment: .center, spacing: 32, content: {
         Button(action: goBack) {
            Image(systemName: "chevron.backward").resizable().aspectRatio(contentMode: .fit)
         }.frame(width: 38, height: 38, alignment: .center).padding(.leading, 64)
         Button(action: goForward) {
            Image(systemName: "chevron.forward").resizable().aspectRatio(contentMode: .fit)
         }.frame(width: 38, height: 38, alignment: .center)
         Spacer()
      })
   }

   func goBack() {

   }

   func goForward() {

   }

}

I think this is the proper pattern, is it correct? It works, but I want to write idiomatic SwiftUI so we don't have to go through in six months and redo all the stuff we did when learning.

2      

Actually, after doing more reading on @State, is it necessary to mark the viewModel property with the @State property wrapper here? The ViewModel struct is never mutated anywhere in the app.

2      

To pass an observableObject to a childView, you should use a binding. Replace this in your WebNavigationView:

@State var viewModel: ViewModel

with this:

@Binding var viewModel: ViewModel

And update your ContentView to pass in the viewModel to your WebNavigationView:

WebNavigationView(viewModel: $viewModel).frame(minWidth: 0, maxWidth: .infinity, minHeight: 64, maxHeight: 64).background(Color.white)

Now you have full access to your viewModel in the WebNavigationView.

More info: https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-binding-property-wrapper

2      

@joosttk Thanks a bunch! Unfortunately that begets the build-time error Cannot convert value of type 'ObservedObject<ViewModel>.Wrapper' to expected argument type 'Binding<ViewModel>'

If I change the ContentView's viewModel property to be wrapped as @State, the code works, but still not sure if that's correct.

2      

Make sure you use the $-sign when passing in your viewModel to your childview:

WebNavigationView(viewModel: $viewModel).frame(minWidth: 0, maxWidth: .infinity, minHeight: 64, maxHeight: 64).background(Color.white)

3      

@joosttk I did! I promise. :) If I leave the ContentView's viewModel property wrapped with @ObservedObject, I get that error.

Copy and paste for verification:

struct ContentView: View {
   @ObservedObject var viewModel = ViewModel()

   var body: some View {
      VStack(spacing: 0) {
         WebView(viewModel: viewModel).overlay (
            RoundedRectangle(cornerRadius: 4, style: .circular)
               .stroke(Color.gray, lineWidth: 0.5)
         )
         WebNavigationView(viewModel: $viewModel).frame(minWidth: 0, maxWidth: .infinity, minHeight: 64, maxHeight: 64).background(Color.white)

and :

struct WebNavigationView: View {
      @Binding var viewModel: ViewModel

      var body: some View {

2      

Ah yes.. makes sense, I was a bit confused.

I believe you can just pass it as a normal variable. No need to use State or a Binding.

struct ContentView: View {
   @ObservedObject var viewModel = ViewModel()

   var body: some View {
      VStack(spacing: 0) {
         WebView(viewModel: viewModel).overlay (
            RoundedRectangle(cornerRadius: 4, style: .circular)
               .stroke(Color.gray, lineWidth: 0.5)
         )
         WebNavigationView(viewModel: viewModel).frame(minWidth: 0, maxWidth: .infinity, minHeight: 64, maxHeight: 64).background(Color.white)

And

struct WebNavigationView: View {
      var viewModel: ViewModel

      var body: some View {

3      

@joosttk Thanks!

2      

Hacking with Swift is sponsored by String Catalog.

SPONSORED Get accurate app localizations in minutes using AI. Choose your languages & receive translations for 40+ markets!

Localize My App

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.