0

I am currently using tab views to navigate between 4 views in my project, and the initial view is my profileView, where I want the user to input their information and goals, such as age, height etc. **However I am really struggling to find good examples and instructions on how to get user input and set my environment object to that user input. I was thinking of just using forms, but it seems unnecessary and im not too sure how to change the environment object to what the user has set. Does anyone have any good documentation they can refer me to, or show me an example of how to get user input and set my environment object to that data. (I am also not sure if this is necessary, as I will also later be storing it in userdefaults or coredata.

This is my model class which is my class data for the user. Ignore the set values at the bottom for now, they will all be set to empty when the project is in later development.

class UserInfoModel: ObservableObject {
    
    
    struct UserInfo: Identifiable {
        var id = UUID()
        var firstName: String
        var lastName: String
        var height: Int
        var weight: Int
        var gender: String
        var age: Int
        
    }
    
    struct DailyCalorieGoals: Identifiable{
        var id = UUID()
        var calorieGoal: Int
        var fatGoal: Int
        var proteinGoal: Int
        var carbGoal: Int

    }
    
    struct CurrentCalorieProgress: Identifiable{
        var id = UUID()
        var calorieProgress: Int
        var fatProgress: Int
        var carbProgress: Int
    }
    
    @Published var personUserInfo = UserInfo.init(firstName: "", lastName:"", height: 0, weight: 0, gender: "", age: 0)
    @Published var personDailyCalorieGoals = DailyCalorieGoals.init(calorieGoal: 2400, fatGoal: 0, proteinGoal: 0, carbGoal: 0)
    @Published var personCurrentCalorieProgress = CurrentCalorieProgress.init(calorieProgress: 0, fatProgress: 0, carbProgress: 0)
    
   
}

So when I am in my profile view how do i get the user input and set my environment object to that data?

The above has been solved, thanks to the help provides so far, however using the method linked below I get the following, but I am not sure how to change the value of my double environment object to the fatInputString:

New code to get input from user

Text("Fat: \(self.person.personDailyCalorieGoals.fatGoal, specifier: "%.0f")g")
                            }
                            TextField("Enter new fat goal (g)", text: $fatInputString ).keyboardType(.numberPad)
                                .onReceive(Just(fatInputString)) { newValue in
                                                let filtered = newValue.filter { "0123456789".contains($0) }
                                                if filtered != newValue {
                                                    self.fatInputString = filtered
                                                }

But how do i set my environment object

self.person.personDailyCalorieGoals.fatGoal 

equal to

self.inputString 

from the above code }

Matthew
  • 121
  • 1
  • 9
  • See the [Apple SwiftUI tutorial Handling User Input](https://developer.apple.com/tutorials/swiftui/handling-user-input) – lorem ipsum Feb 16 '21 at 13:05

1 Answers1

2

Using a Form isn't required -- it just makes the formatting of your user input elements a little more standardized on iOS/macOS.

Regardless of whether you use Form or not, you'll be using various user input elements and assigning them Bindings on your UserInfoModel. SwiftUI/Combine makes this really easy to do with the $ operator.

Here's an example of how to get input to a couple of your fields. You can see that when the field info changes, the value is reflected in the EnvironmentObject which ContentView owns -- it displays the value at the top above the EditorView

struct ContentView : View {
    @StateObject var userInfo = UserInfoModel()
    
    var body: some View {
        VStack {
            Text("Name: \(userInfo.personUserInfo.firstName)")
            Text("Weight: \(userInfo.personUserInfo.weight)")
            Text("Carb: \(userInfo.personCurrentCalorieProgress.carbProgress)")
            EditorView().environmentObject(userInfo)
        }
    }
}

struct EditorView : View {
    @EnvironmentObject private var userInfo : UserInfoModel
    
    var body: some View {
        Form {
            TextField("First name", text: $userInfo.personUserInfo.firstName)
            Stepper("Weight", value: $userInfo.personUserInfo.weight, in: 0...500)
            Stepper("Carb progress", value: $userInfo.personCurrentCalorieProgress.carbProgress, in: 0...1000, step: 10)
        }
    }
}

Update based on comments: Edit to show your request about numerical text input:

struct EditorView : View {
    @EnvironmentObject private var userInfo : UserInfoModel
    @State var fatInputString = "" //<-- Here
    
    var body: some View {
        Form {
            TextField("First name", text: $userInfo.personUserInfo.firstName)
            Stepper("Weight", value: $userInfo.personUserInfo.weight, in: 0...500)
            Stepper("Carb progress", value: $userInfo.personCurrentCalorieProgress.carbProgress, in: 0...1000, step: 10)
            Text("Fat: \(userInfo.personDailyCalorieGoals.fatGoal)g") //<-- Here
            TextField("Enter new fat goal (g)", text: $fatInputString ).keyboardType(.numberPad)
                .onReceive(Just(fatInputString)) { newValue in
                    let filtered = newValue.filter { "0123456789".contains($0) }
                    if filtered != newValue {
                        self.fatInputString = filtered
                    }
                    if let goal = Int(fatInputString), goal != userInfo.personDailyCalorieGoals.fatGoal {  //<-- Here
                        userInfo.personDailyCalorieGoals.fatGoal = goal
                    }
                }
        }
        
    }
}

Note that in your updated code you had a bug where the fat amount would always be displayed as 0. Make sure to look at each line with //<-- Here marked

jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • This is a really awesome answer, thank you very much! However, I have one last issue. How do I let the user use an edit text or something similar to adjust the int value $userInfo.personUserInfo.weight? Thanks! – Matthew Feb 17 '21 at 10:01
  • There are some really good answers for accepting Int in TextField (which requires a bit of fiddling) here: https://stackoverflow.com/questions/58733003/swiftui-how-to-create-textfield-that-only-accepts-numbers/58736068#58736068 Let me know if you need clarification on it for your specific situation. – jnpdx Feb 17 '21 at 14:01
  • Ok, so I am able to get the input of a user, however I get the following error when using the following: let fatInput = String(self.person.personDailyCalorieGoals.fatGoal) and then trying to set TextField("Enter Fat", text: fatInput ) – Matthew Feb 18 '21 at 18:16
  • You didn't actually put the error down, but I'm assuming it's telling you that you have to use a Binding, not just a string in a TextField. Look at the answer I linked you to and how it uses a @State variable for this, and check out the `$` operator that it uses. If you need to, you could edit this question to show your code so I could give more specific advice. – jnpdx Feb 18 '21 at 18:49
  • Just edited my answer, kept the original contents incase someone else needed it, thank you so much for the help so far! – Matthew Feb 19 '21 at 09:50
  • I've updated to reflect your additional question. Please consider accepting the answer if this works for you. – jnpdx Feb 19 '21 at 16:05