|
Hi All,
Thanks to some of your help , I got inspired and solved the challenge!! Although, I see Amount per person as $-NaN as default , everything else seems ok !! I am sure there must be many ways to fine tune it !! however , below is my code !!
import SwiftUI
struct ContentView: View {
@State private var checkAmount = ""
@State private var numberOfPeople = "" // changed to take in STR now
@State private var tipPercentage = 2
let tipPercentages = [10,15,20,25,0]
var totalPerPerson: Double {
let peopleCount = Double(numberOfPeople) ?? 0 // changed to have nil coelacing of str.
let tipSelection = Double(tipPercentages[tipPercentage]) // instance to store section value from its array
let orderAmount = Double(checkAmount) ?? 0 // nil coelacing to ensure only int instead of str is taken
let tipValue = orderAmount / 100 * tipSelection // calculate tip value perctage from order amount
let grandTotal = orderAmount + tipValue // order amount + tip value to divide
let amountPerPerson = grandTotal / peopleCount //
return amountPerPerson
}
var grandTotalValue: Double {
let tipSelection1 = Double(tipPercentages[tipPercentage]) // instance to store section value from its array
let orderAmount1 = Double(checkAmount) ?? 0 // nil coelacing to ensure only int instead of str is taken
let tipValue1 = orderAmount1 / 100 * tipSelection1
let grandtotalval = tipValue1 + orderAmount1
return grandtotalval
}
var body: some View {
NavigationView {
Form {
Section {
TextField("Amount", text:$checkAmount)
.keyboardType(.decimalPad)
// There is an issue here decimal pad is not showing up
// Picker("Number of people", selection: $numberOfPeople) {
// ForEach(2 ..< 100) {
// Text("($0) people")
// }
// }
TextField("Number of people", text:$numberOfPeople) // changed picker to text.
.keyboardType(.decimalPad)
}
Section(header: Text("How much tip do you want to leave ?")) {
Picker("Tip Percentage", selection: $tipPercentage) {
ForEach(0 ..< tipPercentages.count){
Text("\(self.tipPercentages[$0])%")
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("Total Amount for the Check")) {
Text("$\(grandTotalValue, specifier: "%.2f")")
}
Section(header: Text("Amount per person")) {
Text("$\(totalPerPerson, specifier: "%.2f")")
}
}
.navigationBarTitle("WeSplit")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
|
|
Hello @pradeephoskote,
Change the following code:
let peopleCount = Double(numberOfPeople) ?? 0
to this:
let peopleCount = Double(numberOfPeople) ?? 2 // Default value for 2 people
Hope that solves your problem.
|
|
I thought users might want to know how much their tip is, so I added that section, but I get an error "cannot find tipValue in scope". same with grandTotal, even though they are declared near the beginning. Not sure what's going on.
P.S. I also fixed the -NaN issue with an if statement
import SwiftUI
struct ContentView: View {
@State private var checkAmount = ""
@State private var numberOfPeople = ""
@State private var tipPercentage = 2
let tipPercentages = [10, 15, 18, 20, 25, 0]
var totalPerPerson: Double {
let peopleCount = Double(numberOfPeople) ?? 0
let tipSelection = Double (tipPercentages[tipPercentage])
let orderAmount = Double(checkAmount) ?? 0
let tipValue = orderAmount / 100 * tipSelection <--------- Here it's declared
let grandTotal = orderAmount + tipValue <----------Here too
var amountPerPerson = grandTotal / peopleCount
if peopleCount == 0 { <-------------------------- Fixes the -NaN issue
amountPerPerson = 0
} else {
return amountPerPerson
}
return amountPerPerson
}
var body: some View {
NavigationView {
Form {
Section {
TextField ("Check Amount", text: $checkAmount)
.keyboardType(.decimalPad)
TextField("Number of People", text: $numberOfPeople)
.keyboardType(.decimalPad)
}
Section(header: Text("How much tip do you want to leave?")){
Picker("Tip Percentage", selection: $tipPercentage){
ForEach(0 ..< tipPercentages.count){
Text("\(self.tipPercentages[$0])%")
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section (header: Text("Tip Amount")){
Text("$\(tipValue, specifier: "%.2f")") <------------------------------Error
}
Section (header: Text("Total + Tip ")){
Text("$\(grandTotal, specifier: "%.2f")") <---------------------------Error
}
Section (header: Text("Amount Per Person")){
Text("$\(totalPerPerson, specifier: "%.2f")")
}
}
.navigationBarTitle("WeSplit")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
|
|
You need to declare them outside of totalPerPerson . They are currently declared within the computed property instead of being properties of the struct.
|
|
Where/How do I declare it? If I copy it under tipPercentages, I get the following error: "Cannot use instance member 'numberOfPeople' within property initializer; property initializers run before 'self' is available"
struct ContentView: View {
@State private var checkAmount = ""
@State private var numberOfPeople = ""
@State private var tipPercentage = 2
let tipPercentages = [10, 15, 18, 20, 25, 0]
let peopleCount = Double(numberOfPeople) ?? 0
let tipSelection = Double (tipPercentages[tipPercentage])
let orderAmount = Double(checkAmount) ?? 0
let tipValue = orderAmount / 100 * tipSelection
let grandTotal = orderAmount + tipValue
var totalPerPerson: Double {
let peopleCount = Double(numberOfPeople) ?? 0
let tipSelection = Double (tipPercentages[tipPercentage])
let orderAmount = Double(checkAmount) ?? 0
let tipValue = orderAmount / 100 * tipSelection
let grandTotal = orderAmount + tipValue
var amountPerPerson = grandTotal / peopleCount
|
|
Yes, it is under tipPercentage but it also needs to be an @State property, since it is mutating the struct.
|
|
I made those variables @State private var (not sure if they needed to be private var or not) but I still get the same error message. "Cannot use instance member 'numberOfPeople' within property initializer; property initializers run before 'self' is available" for all 3 variables
@State private var peopleCount = Double(numberOfPeople) ?? 0
@State private var tipSelection = Double (tipPercentages[tipPercentage])
@State private var orderAmount = Double(checkAmount) ?? 0
|
|
A couple things, now that you tried it:
1- You need to initialize them first in the declaration, then you can use them as per your first example, as part of the computed property. This will stop the error from showing within the section. (referring to the comment of where you show the declaration and error locations).
2- Alternatively you could create new computed properties:
private var grandTotal: Double {
return totalPerPerson * (Double(numberOfPeople) ?? 0)
which you can declare after totalPerPerson
3- Or if you prefer you can use functions that return a double. I personally prefer that approach as it allows me to see things clearly and perform any safety checks I want without cluttering my code... but it's not "better"... just a preference.
func orderTotal() -> Double {
guard let peopleCount = Double(numberOfPeople) else { return 0 }
return totalPerPerson * peopleCount
}
PS: I apologize for not clarifying it earlier, but the error you were getting first, is in reference to scope. Essentially, your properties were declared inside the scope of your computed property and therefore are not accessible outside of it. So when you declare properties, they can be accessed by anything within the same scope or deeper.
The use of private is to make sure that no other part of the code that is outside the scope (in this example the ContentView struct) has access to them. This is done for safety reasons.
The second error (which I am apologizing for) is due to the usage of other properties to initialize them. So grandTotal for instance, is initializing itself using another property. Since these are not yet initialized, they can not be used. Sorry for not clarifying that when I suggested you move them up one step.
I hope this explains things clearly.
|
|
I'm sorry, but I'm a beginner. I don't understand your vocabulary when you say "You need to initialize them first in the declaration" or "computed property". And sorry this is a lot to ask, but:
I've tried to put the code you provided but I keep getting errors. 1. Do I delete the @State private vars of peopleCount, tipSelection, and orderAmount? 2. If I don't, I still get the error I've describe in my earlier posts, but if I delete those then I get an error on let tipValue and grandTotal: "Cannot find 'orderAmount' in scope"
import SwiftUI
struct ContentView: View {
@State private var checkAmount = ""
@State private var numberOfPeople = ""
@State private var tipPercentage = 2
let tipPercentages = [10, 15, 18, 20, 25, 0]
@State private var peopleCount = Double(numberOfPeople) ?? 0
@State private var tipSelection = Double (tipPercentages[tipPercentage])
@State private var orderAmount = Double(checkAmount) ?? 0
let tipValue = orderAmount / 100 * tipSelection
let grandTotal = orderAmount + tipValue
var totalPerPerson: Double {
let peopleCount = Double(numberOfPeople) ?? 0
let tipSelection = Double (tipPercentages[tipPercentage])
let orderAmount = Double(checkAmount) ?? 0
let tipValue = orderAmount / 100 * tipSelection
let grandTotal = orderAmount + tipValue
var amountPerPerson = grandTotal / peopleCount
if peopleCount == 0 {
amountPerPerson = 0
} else {
return amountPerPerson
}
return amountPerPerson
}
func orderTotal() -> Double {
guard let peopleCount = Double(numberOfPeople) else { return 0 }
return totalPerPerson * peopleCount
}
|
|
There's no need to apologize, we're all here learning.
By initialize I mean similar to what you are doing with tipPercentage. You are initializing it with the value 2. Basically you are giving it some initial value.
The computed property in this example, which I recommend you go over the tutorial for that again, means it is calculated instead of having a set value... hope that explains it.
What you have in your example, is duplicates. You have the @State outside of your totalPerPerson and then you have them again inside. You can remove the ones inside now.
Then make tipValue and grandTotal @State too so you can update the UI accordingly, and remove them from inside totalPerPerson.
You also don't need the orderTotal() function if you are using grandTotal. This was a preference. You use either the function or the property.
Your code should look like this:
import SwiftUI
struct ContentView: View {
@State private var checkAmount = ""
@State private var numberOfPeople = ""
@State private var tipPercentage = 2
let tipPercentages = [10, 15, 18, 20, 25, 0]
@State private var peopleCount = Double(numberOfPeople) ?? 0
@State private var tipSelection = Double (tipPercentages[tipPercentage])
@State private var orderAmount = Double(checkAmount) ?? 0
@State let tipValue = orderAmount / 100 * tipSelection
@State let grandTotal = orderAmount + tipValue
var totalPerPerson: Double {
var amountPerPerson = grandTotal / peopleCount
if peopleCount == 0 {
amountPerPerson = 0
} else {
return amountPerPerson
}
return amountPerPerson
}
// A different way to approach totalPerPerson (Use your version or this one... not Both)
var totalPerPerson: Double {
guard peopleCount > 0 else { return 0 }
return grandTotal / peopleCount
}
}
Important Note: The only reason for placing tipValue, orderAmount and grandTotal outside of totalPerPerson is in order for you to access them for your additional feature. You could have kept everything as per the tutorial, and then use totalPerPerson as your baseline, in order to calculate the new details you want, which is why I provided the example of the function... if you notice it uses totalPerPerson, and multiplies it by peopleCount (which is declared inside the function again)... but this is only if you keep the format of the tutorial...
Sorry it takes me time to reply, it all depends on free time and memory. Let me know if you have any more questions on this.
|
|
I totally understand that your time is limited and I truly appreciate you taking the time to reply to all of my threads. I copied your code and pasted into mine and I still get the "Cannot use instance member 'tipPercentage' within property initializer; property initializers run before 'self' is available". I also get the error "Cannot find '$tipPercentage' in scope" in my picker and cannot find 'numberOfPeople' in scope" in my grandTotal var even though we declared them at the top with @State(s)
|
|
I really should stop checking code late at night. Sorry about that... I don't know what I was thinking.
That was the reason I was suggesting the use of functions. But anyway, here's what I should have pasted with comments for clarity. Again sorry.
One more thing: you cannot use @State with a computed property, but the UI will still update because as the @State properties the change the UI will refresh.
struct ContentView: View {
@State private var checkAmount = ""
@State private var numberOfPeople = ""
@State private var tipPercentage = 2
let tipPercentages = [10, 15, 18, 20, 25, 0]
//We turn this into a computed property so we can access the
//checkAmount, numberOfPeople and tipPercentage
//when these change the computed property will change too
var tipValue: Double {
let orderAmount = Double(checkAmount) ?? 0
let tipSelection = Double(tipPercentages[tipPercentage])
return orderAmount / 100 * tipSelection
}
//Same with this one.
var grandTotal: Double {
let orderAmount = Double(checkAmount) ?? 0
return orderAmount + tipValue
}
//Same here
var totalPerPerson: Double {
let peopleCount = Double(numberOfPeople) ?? 0
let amountPerPerson = grandTotal / peopleCount
//Notice how we just use a simpple if statement
if peopleCount == 0 {
return 0
} else {
return amountPerPerson
}
}
//This is where your body should be.
//Make sure you are calling the right @State in the right place
var body: some View { ... }
} // closing brace of ContentView
This was a good lesson for me too. 😉. Always check and refactor code when you are well rested, otherwise the above mistake happens.
We could have turned orderAmount into a computed property or tipSelection and even peopleCount , but there's no need, we are not repeating enough times to warrant a separate property, and this is clean enough.
|
|
This seems like it works even though it rounds up is some cases.
struct ContentView: View {
@State private var checkAmount = ""
@State private var numberOfPeople = ""
@State private var tipPercentage = 2
let tipPercentages = [10, 15, 20, 25, 0]
var totalPerPerson: Double {
let peopleCount = Double(numberOfPeople) ?? 0
let tipSelection = Double(tipPercentages[tipPercentage])
let orderAmount = Double(checkAmount) ?? 0
let tipValue = orderAmount / 100 * tipSelection
let grandTotal = orderAmount + tipValue
let amountPerPerson = grandTotal / peopleCount
return amountPerPerson
}
var peopleNumber: Double {
let personCount = Double(numberOfPeople) ?? 0
//reused peopleCount from totalPerson
return personCount
}
var body: some View {
NavigationView {
Form {
Section {
TextField("Amount", text: $checkAmount)
.keyboardType(.decimalPad)
Section(header: Text("How much tip do you want to leave?")) {
Picker("Tip Percentage", selection: $tipPercentage) {
ForEach(0 ..< tipPercentages.count) {
Text("\(self.tipPercentages[$0])%")
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section {
TextField("Number of People", text: $numberOfPeople)
.keyboardType(.numberPad)
}
}
Section(header: Text("Amount per person")) {
Text("$\(totalPerPerson, specifier: "%.2f")")
}
/*multiply totalPerPerson and peopleNumber to get result, it rounds up in certain situtaions though*/
Section(header: Text("Total of Check Plus Tip")) {
Text("$\(totalPerPerson * peopleNumber, specifier: "%.2f")")
}
}
.navigationTitle("WeSplit")
}
}
}
|
|
Try this:
import SwiftUI
struct ContentView: View {
@State private var checkAmount = ""
@State private var numberOfPeople = ""
@State private var tipPercentage = 2
let tipPercentages = [10, 15, 20, 25, 0]
var totalPerPerson : Double {
let peopleCount = Double(numberOfPeople) ?? 2
let tipSelection = Double(tipPercentages[tipPercentage])
let orderAmount = Double(checkAmount) ?? 0
let tipValue = orderAmount / 100 * tipSelection
let grandTotal = orderAmount + tipValue
let amountPerPerson = grandTotal / peopleCount
return amountPerPerson
}
var totalAmountBill : Double {
let tipSelection = Double(tipPercentages[tipPercentage])
let orderAmount = Double(checkAmount) ?? 0
let tipValue = orderAmount / 100 * tipSelection
let grandAmount = orderAmount + tipValue
return grandAmount
}
var body: some View {
NavigationView {
Form {
Section {
TextField("Amount", text: $checkAmount)
.keyboardType(.decimalPad)
TextField("Number of people", text: $numberOfPeople)
.keyboardType(.numberPad)
}
Section(header: Text("How much tip do you want to leave?")) {
Picker("Tip percentage", selection: $tipPercentage) {
ForEach(0 ..< tipPercentages.count) {
Text("\(self.tipPercentages[$0])%")
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("Amount per person")) {
Text("$\(totalPerPerson, specifier: "%.2f")")
}
Section(header: Text("Total amound")) {
Text("$\(totalAmountBill, specifier: "%.2f")")
}
}
.navigationBarTitle("WeSplit")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
|
|
@MarcusKay
I think your code is working. The only thing is, there is a large space at the top and so users have to scroll down a tiny bit to see the amount per person textfield instead of everything viewable on one page. Is there anything that can be done about that? thanks
|
|
That space on top has to do with body If you emebedded your view inside a NavigationView without giving it a title, that can appear as a white space. You could make the title displayMode .inline
I can't really know why your code is doing that... check the layout.
|