- Görkem Güray/
- SwiftUI in 100 Days Notes/
- Day 52 - SwiftUI Project 10 (Cupcake Corner) Challange and Solutions/
Day 52 - SwiftUI Project 10 (Cupcake Corner) Challange and Solutions
Table of Contents
Hopefully this project has shown you how to use the skills you already know (SwiftUI Picker, Stepper and Navigation) and turn them into an app that sends all the user’s data to a server and processes the response.
You may not realize it yet, but the skills you learned in the project are important skills for the vast majority of iOS developers: receiving user data, sending it to a server, and processing the response is probably half of the non-trivial apps that exist. Yes, what data to send and how to use it to update the UI varies greatly, but the concepts are the same.
Challange #
Here are 3 ways we should try to improve this practice;
- Our address fields are currently considered valid if they contain anything, even just spaces. Improve validation to make sure that a string containing only spaces is invalid.
- Show an informative warning for the user if our
placeOrder()
call fails - for example if there is no internet connection. To test this, you can comment out the linerequest.httpMethod = "POST"
in your code, this will force the request to fail. - For a more challenging task, try updating the
Order
class so that we can save data like the user’s delivery address inUserDeafults
. This requires some thought, because@AppStorage
doesn’t work here and you will find that getters and setters cause problems withCodable
support. Can you find a middle ground?
Challange Solutions #
-
To solve this problem we can write an extension on
String
. In this extension we can use theallSatisfy()
method to check each element of the String. Since each element of String isCharacter
, we can use.isWhitespace
, a method defined onCharacter
. With the following code we will add a new method to String and return true if the string is whitespace.extension String { var isBlank: Bool { allSatisfy({$0.isWhitespace}) } }
After writing the code above, in our
Order
class, we can changevar hasValidAddress: Bool {
we can change it as follows;if name.isEmpty || name.isBlank || streetAddress.isEmpty || streetAddress.isBlank || city.isEmpty || city.isBlank || zip.isEmpty || zip.isBlank {
-
To show a warning when there is a problem with the internet connection, let’s first create a variable in the CheckoutView;
@State private var showingNoInternet = false
Then let’s create another one under the
alert
we created before;.alert("Oops!", isPresented: $showingNoInternet) { Button("OK", role: .cancel) { } } message: { Text("Please check your internet connection.") }
Finally, let’s add the following code to the
catch
block in ourplaceOrder()
method;showingNoInternet = true
-
To solve this problem, first of all we need to decide where to write the data to
UserDefaults
. The best place for this is theNavigationLink
section inAddressView
where we push theCheckoutView
. So change the code as follows;Section{ NavigationLink("Check out") { CheckoutView(order: order) .onAppear { let addressItems = [order.name, order.streetAddress, order.city, order.zip] if let encoded = try? JSONEncoder().encode(addressItems) { UserDefaults.standard.set(encoded, forKey: "addressItems") print("Address Items kaydedildi.") } } } //NavigationLink } //Section-2
The problem now is where to read the data we saved in
UserDefaults
. The best place for this is theinit()
function of theOrder
class. In this function we readUserDefaults
and if there is any saved data we assign it to the corresponding values when the class object is created, if there is no data we assign an empty string. To do this, change where we define the following variables in the Order class;var name: String var streetAddress: String var city: String var zip: String
Then write the
init()
function as follows;init() { if let dataName = UserDefaults.standard.data(forKey: "addressItems") { if let decodedName = try? JSONDecoder().decode([String].self, from: dataName) { name = decodedName[0] streetAddress = decodedName[1] city = decodedName[2] zip = decodedName[3] return } } name = "" streetAddress = "" city = "" zip = "" }
You can access the completed project via the Github link below;
You can also read this article in Turkish.
Bu yazıyı Türkçe olarak da okuyabilirsiniz.