BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

WeSplit Challenge

Forums > 100 Days of SwiftUI

I'm sure this is terrible coding but this is how I achieved the Challenge, pointers are much appreciated:

struct ContentView: View { @State private var checkAmount = "" @State private var numberOfPeople = 2 @State private var tipPercentage = "" let tipPercentages = [10, 15, 20, 25, 0] var totalPerPerson: Double { let peopleCount = Double(numberOfPeople + 2) let tipSelection = Double(tipPercentage) ?? 0 let orderAmount = Double(checkAmount) ?? 0 let tipValue = orderAmount / 100 * tipSelection let grandTotal = orderAmount + tipValue let amountPerPerson = grandTotal / peopleCount

    return amountPerPerson
}
    var totalAddition:Double {
        let amountTotal = Double(checkAmount) ?? 0
        let percentageTotal = Double(tipPercentage) ?? 0
        let addPercentage = amountTotal * percentageTotal / 100
        let totalPercentage = amountTotal + addPercentage
        return totalPercentage
    }

var body: some View {
     NavigationView {
    Form {
        Section {
            TextField("Amount", text: $checkAmount)
                .keyboardType(.decimalPad)

            Picker("Number of people", selection: $numberOfPeople) {
            ForEach(2 ..< 100) {
                Text("\($0) people")
                }
            }

        }
        Section(header:Text("How much tip do you want to leave?")) {

            TextField("Tip percentage", text: $tipPercentage)
                .keyboardType(.decimalPad)

        }

        Section(header:Text("Amount per person")) {
            Text("£\(totalPerPerson, specifier:"%.2f")")

        }
        Section(header:Text("Total amount including tip")){
            Text("£\(totalAddition, specifier:"%.2f")")

        }

    }.navigationBarTitle("WeSplit")
}
}

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

1      

I did something similar:

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 totalWithTip: Double {
        let tipSelection = Double(tipPercentages[tipPercentage])
        let orderAmount = Double(checkAmount) ?? 0

        let tipValue = orderAmount / 100 * tipSelection
        let grandTotal = orderAmount + tipValue

        return grandTotal
        }

    var body: some View {
        NavigationView {
            Form {

                Section {
                    TextField("Amount", text: $checkAmount)
                        .keyboardType(.decimalPad)

                    TextField("Number of people", text: $numberOfPeople)
                        .keyboardType(.decimalPad)
                    }

                Section(header: Text("How much tip would you like 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 amount including tip")) {
                    Text("$\(totalWithTip, specifier: "%.2f")")
                }
            }
        .navigationBarTitle("WeSplit")
        }
    }
}

Did you try the third challenge?

Change the “Number of people” picker to be a text field, making sure to use the correct keyboard type.

I tried as you can see in my code above, but that sets the Amount per person at $-NaN until I enter a value into both text fields. Not sure how to fix that... Otherwise it works fine!

1      

@joforsell I solved that small annoyance of showing infinity or -NaN


import SwiftUI

struct ContentView: View {

    @State private var checkAmmount = ""
    @State private var numberOfPeople = ""
    @State private var tipPercentage = 2

    let tipPercentages = [10,20,25,0]

    var totalPerPerson:Double{
        let peopleCount = Double(numberOfPeople) ?? 1
        let tipSelection = Double(tipPercentages[tipPercentage])
        let orderAmmount = Double(checkAmmount) ?? 0

        let tipValue = orderAmmount / 100 * tipSelection
        let grandTotal = orderAmmount + tipValue
        let ammountPerPerson = grandTotal / peopleCount

        return ammountPerPerson
    }

    var body: some View {
        NavigationView{
        Form{
            Section{
            TextField("enter ammount", text: $checkAmmount)
                .keyboardType(.numberPad)
                TextField("number of people", text: $numberOfPeople)
                .keyboardType(.numberPad)
            }

            Section(header: Text("How much tip you wanna pay")){
                Picker("Tip Percentage", selection: $tipPercentage){
                    ForEach(0..<tipPercentages.count){
                        Text("\(self.tipPercentages[$0])%")
                    }
                }.pickerStyle(SegmentedPickerStyle())
            }

            Section(header: Text("Ammount per person")){
                Text("$ \(totalPerPerson > 0 ? totalPerPerson : 0 , specifier:"%.2f")")
            }

            Section(header: Text("Total ammount")){
                Text(String((Int(checkAmmount) ?? 0) + tipPercentages[tipPercentage]))
            }
        }.navigationBarTitle("WeSplit")
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

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

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() } }

1      

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.

1      

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()
    }
}

1      

You need to declare them outside of totalPerPerson. They are currently declared within the computed property instead of being properties of the struct.

1      

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

1      

Yes, it is under tipPercentage but it also needs to be an @State property, since it is mutating the struct.

1      

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

1      

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.

1      

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
    }

1      

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.

1      

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)

1      

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.

1      

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")
    }
}

}

1      

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() } }

1      

@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

1      

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.

1      

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

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.