Learn Courses My Dashboard

Binding a var in view code to a var in a model class? Swift UI

Hi Guys,

I’ll use Chris’ Recipe List App as an example as his code is neater than mine!

I would like to bind the ‘selectedServingSize’ var to the ‘servings’ var in the model, so that the picker starts on the suggested serving size and in my case allows the picker to update the original
‘servings’ var so that the updated number is used in other calculations on the app.

View Code;

struct RecipeDetailView: View {

var recipe:Recipe

**@State var selectedServingSize = 2**

var body: some View {
    
    ScrollView {
    
        VStack (alignment: .leading) {
            
            // MARK: Recipe Image
            Image(recipe.image)
                .resizable()
                .scaledToFill()
            
            // MARK: Recipe title
            Text(recipe.name)
                .bold()
                .padding(.top, 20)
                .padding(.leading)
                .font(Font.custom("Avenir Heavy", size: 24))
            
            
            // MARK: Serving size picker
            VStack (alignment: .leading) {
                Text("Select your serving size:")
                    .font(Font.custom("Avenir", size: 15))
                Picker("", selection: $selectedServingSize) {
                    Text("2").tag(2)
                    Text("4").tag(4)
                    Text("6").tag(6)
                    Text("8").tag(8)
                }
                .font(Font.custom("Avenir", size: 15))
                .pickerStyle(SegmentedPickerStyle())
                .frame(width:160)
            }
            .padding()
            
            // MARK: Ingredients
            VStack(alignment: .leading) {
                Text("Ingredients")
                    .font(Font.custom("Avenir Heavy", size: 16))
                    .padding([.bottom, .top], 5)
                
                ForEach (recipe.ingredients) { item in
                    
                    Text("• " + RecipeModel.getPortion(ingredient: item, recipeServings: recipe.servings, targetServings: selectedServingSize) + " " + item.name.lowercased())
                        .font(Font.custom("Avenir", size: 15))
                }
            }
            .padding(.horizontal)
            
            // MARK: Divider
            Divider()
            
            // MARK: Directions
            VStack(alignment: .leading) {
                Text("Directions")
                    .font(Font.custom("Avenir Heavy", size: 16))
                    .padding([.bottom, .top], 5)
                
                ForEach(0..<recipe.directions.count, id: \.self) { index in
                    
                    Text(String(index+1) + ". " + recipe.directions[index])
                        .padding(.bottom, 5)
                        .font(Font.custom("Avenir", size: 15))
                }
            }
            .padding(.horizontal)
        }
        
    }
}

}

Model Code:

class Recipe: Identifiable, Decodable {

var id:UUID?

var name:String

var featured:Bool

var image:String

var description:String

var prepTime:String

var cookTime:String

var totalTime:String

var servings:Int

var highlights:[String]

var ingredients:[Ingredient]

var directions:[String]

}

class Ingredient: Identifiable, Decodable {

var id:UUID?

var name:String

var num:Int?

var denom:Int?

var unit:String?

}

you might need to consider on working with databases to pass the data around… or you can take a look at some sharedinstance samples

Try this:

            // MARK: Directions
            VStack(alignment: .leading) {
                Text("Directions")
                    .font(Font.custom("Avenir Heavy", size: 16))
                    .padding([.bottom, .top], 5)
                
                ForEach(0..<recipe.directions.count, id: \.self) { index in
                    
                    Text(String(index+1) + ". " + recipe.directions[index])
                        .padding(.bottom, 5)
                        .font(Font.custom("Avenir", size: 15))
                }
            }
            .padding(.horizontal)
            // Add the following .onAppear to set the selectedServingSize.
            .onAppear {
                viewModel.selectedServingSize = recipe.servings
            }

Thanks both. @Chris_Parker the .onAppear solution you suggested works for getting the picker to start with the ‘suggested serving size’ ie. equal to recipe.serving, but does not work for updating the recipe.servings value based on the picker selection. This element is quite important to me as I have further calculations based on the users selection form the picker.

@Francis_Fuerte I have tried to re-imagine my design using the database, however this would involve a lot of calls to the server to update the database every time the user selects a new value from a picker and then recall the new value for calculations running in the app. Mayne this is a compromise I just need to accept, but it could become expensive and wouldd make the app unable to run off-line.

The app I am working on is very similar in behaviour to a ‘calorie counter’ app, does anyone have any advice on the type of data architecture the twould be used in this sort of app or know where I could look to find out?

Thanks,

James

You will need to monitor that selectedServingSize value and use an .onChange modifier to update the underlying value when you change the picker value.

Something like this:

            // MARK: Serving size picker
            VStack (alignment: .leading) {
                Text("Select your serving size:")
                    .font(Font.custom("Avenir", size: 15))
                Picker("", selection: $selectedServingSize) {
                    Text("2").tag(2)
                    Text("4").tag(4)
                    Text("6").tag(6)
                    Text("8").tag(8)
                }
                .font(Font.custom("Avenir", size: 15))
                .pickerStyle(SegmentedPickerStyle())
                .frame(width:160)
            }
            .padding()
            .onChange(of: selectedServingSize) {
                updateServingSize()
            }

Add your function to perform the update

func updateServingSize() {
    //  Your code goes here
}

Thank Chris the .onChange, works perfectly