100 Days of SwiftUI Day 50

100 Days of SwiftUI – Day 50

We’ve arrived at day 50 of the 100 Days of SwiftUI! We’ve officially reached the halfway point and are well on our way to becoming a real software developer. Yesterday, we started our 10th project: Cupcake Corner. Today, we’re starting the implementation of this app. Let’s dive in!

Taking basic order details

We start by creating a basic order class in a new Swift file. This contains all data related to the order itself.

In our ContentView, we set up the layout of our app. I’ve added comments to the code so it’s clear what’s going on and why.

//
//  Order.swift
//  CupcakeCorner
//


import SwiftUI

class Order: ObservableObject {
    static let types = ["Vanilla", "Strawberry", "Chocolate", "Rainbow"]
    
    @Published var type = 0 // Vanilla by default, as it's the first entry in the types array
    @Published var quantity = 3 // Default quantity that is ordered
    
    @Published var specialRequestEnabled = false { // Has the user requested a special request? False by default.
        didSet {
            if specialRequestEnabled == false { // If specialRequestEnabled is false, the two extra options are false by default as well
                extraFrosting = false
                addSprinkles = false
            }
        }
    }
    @Published var extraFrosting = false // Has the user requested extra frosting? False by default.
    @Published var addSprinkles = false // Has the user requested sprinkles? False by default.
    
}
//
//  ContentView.swift
//  CupcakeCorner


import SwiftUI

struct ContentView: View {
    
    @StateObject var order = Order() // This order we create here will be shared among all other views in the app.
    
    var body: some View {
        NavigationView {
            Form {
                Section {
                    Picker("Select your cake type", selection: $order.type) {
                        ForEach(Order.types.indices) { // Has the user requested extra frosting? False by default.
                            Text(Order.types[$0])
                        }
                    }
                    
                    Stepper("Number of cakes: \(order.quantity)", value: $order.quantity, in: 3...20)
                }
                
                Section {
                    Toggle("Any special requests?", isOn: $order.specialRequestEnabled.animation()) // A toggle to enable special requests
                    
                    if order.specialRequestEnabled { // If special requests are toggled on (if true), then...
                        Toggle("Add extra frosting", isOn: $order.extraFrosting) // Shows a toggle for extraFrosting
                        Toggle("Add extra sprinkles", isOn: $order.addSprinkles) // Shows a toggle for addSprinkles
                    }
                }
                Section {
                    NavigationLink {
                        AddressView(order: order) // Navigation links to our AddressView, passing in the order we've created
                    } label: {
                        Text("Delivery details")
                    }
                }
            }
            .navigationTitle("Cupcake Corner") // Title
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
//
//  AddressView.swift
//  CupcakeCorner
//

import SwiftUI

struct AddressView: View {
    
    @ObservedObject var order: Order
    
    var body: some View {
        Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
    }
}

struct AddressView_Previews: PreviewProvider {
    static var previews: some View {
        AddressView(order: Order())
    }
}

As you’ll have undoubtedly noticed, the AddressView is still quite empty. We’ve created it already so that our NavigationLink in the ContentView could be set up. We’ll be completing it next.

Checking for a valid address in SwiftUI

It would be amazing if we were already implementing some sort of API that allows us to validate addresses, but we’re not quite there yet with our skills to do that. Instead, we’ll be implementing a simple check to ensure none of the address fields are empty.

// This is added to the Order.Swift code block above

    @Published var name = ""
    @Published var streetAddress = ""
    @Published var city = ""
    @Published var zip = ""
    
    var hasValidAddress: Bool { // Used to validate that none of the address fields are empty
        if name.isEmpty || streetAddress.isEmpty || city.isEmpty || zip.isEmpty {
            return false
        }
        return true
    }
//
//  AddressView.swift
//  CupcakeCorner
//

import SwiftUI

struct AddressView: View {
    
    @ObservedObject var order: Order
    
    var body: some View {
        Form {
            Section {
                TextField("Name", text: $order.name)
                TextField("Street address", text: $order.streetAddress)
                TextField("City", text: $order.city)
                TextField("Zipcode", text: $order.zip)
            }
            
            Section {
                NavigationLink {
                    CheckoutView(order: order)
                } label: {
                    Text("Check out")
                }
            }
            .disabled(order.hasValidAddress == false) // If any of the fields is empty, the check out link will be disabled
        }
        .navigationTitle("Delivery details")
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct AddressView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView { // Used to preview the navigationTitle
            AddressView(order: Order())
        }
    }
}

Preparing for checkout

The final screen of the app is the CheckoutView. The first half of this view is the basic user interface and the second half is more advanced. It includes all the data from the order class, which needs to be encoded to JSON. We’ll be looking at the advanced part tomorrow, but for now, we’ll focus on the user interface.

The user interface for the CheckoutView consists of an image we load from the internet as seen yesterday during day 49, along with the total order amount to be paid and a button to place the order.

// This is added to the Order.Swift code blocks above

var cost: Double {
        
        // 2$ per cake
        var cost = Double(quantity) * 2
        
        // complicated cakes cost more
        cost += (Double(type) / 2)
        
        // 1$ per cake for extra frosting
        
        if extraFrosting {
            cost += Double(quantity)
        }
        
        // $0.50 per cake for sprinkles
        if addSprinkles {
            cost += Double(quantity) / 2
        }
        
        return cost
        
        // Side note: A Double is not ideal for currency, but we have not yet learned better ways in this point in time (Day 50 of the 100 Days of SwiftUI). We will in the future, though!
    }
//
//  CheckoutView.swift
//  CupcakeCorner
//


import SwiftUI

struct CheckoutView: View {
    
    @ObservedObject var order: Order
    
    var body: some View {
        ScrollView {
            VStack {
                // Load an image from the internet
                AsyncImage(url: URL(string: "https://hws.dev/img/cupcakes@3x.jpg"), scale: 3) {  image in
                    image
                        .resizable()
                        .scaledToFit()
                } placeholder: {
                    ProgressView()
                }
                .frame(height: 233)
                
                Text("Your total is \(order.cost, format: .currency(code: "USD"))")
                    .font(.title)
                
                Button("Place Order", action: {})
                    .padding()
            }
        }
        .navigationTitle("Checkout")
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct CheckoutView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            CheckoutView(order: Order())
        }
    }
}

Wrap up

And that’s it for day 50! Tomorrow, we’ll finish the implementation of our appn as we learn how to encode an ObservableObject class and how to send and receive orders over the internet. Stay tuned for that!

Darryl

Hi! My name is Darryl and this is my personal blog where I write about my journey as I learn programming! You'll also find articles about other things that interest me including games, tech and anime.

Post navigation

Leave a Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

100 Days of SwiftUI – Day 95

100 Days of SwiftUI – Day 13 – Protocols

100 Days of SwiftUI – Day 70

100 Days of SwiftUI – Day 97