I’m currently in the process of learning SwiftUI, and I’m running through Hacking With Swift’s 100 Days of SwiftUI. I’ll be taking notes along the way and compiling them here.
Day 1
Swift is a type-safe language which means that every variable must be of one specific type.
When creating a variable with a large number as the value, you can use underscores instead of commas to make your code more readable. Example below:
var population = 8_000_000
// Value of population is 8000000
Day 2
Ways of storing data:
- Arrays – can have duplicate values
- Sets – can’t have duplicates, usually for very large data sets
- Tuples – type-specific, number of values specific
- Dictionaries – key: value pairs, has default values for non-existent keys when looking them up.
Here’s what the above types look like when empty:
- Array – var results = [Int]()
- Set – var words = Set<String>()
- Dictionary – var teams = [String: String]()
Sets are basically unordered arrays. They are useful for when you want to look up if an item exists in a large group of data. In a Set() that decision is super fast, in an array the compiler has to run through the array one item at a time. Also, sets cannot have duplicate values, arrays can. Set() syntax is below:
var readings = Set([894, 345, 234, 145])
Dictionaries are pretty much the same as arrays, but they follow a key: value syntax. Arrays are just all values.
Day 4
When looping through a for loop, you can use an underscore in place of a constant if the constant doesn’t matter or add any context. Example below:
for _ in 1...3 {
print("output")
}
When inside nested loops, if you want to break outside of multiple loops you’ll need a keyword after break. That keyword will also need to be placed before the parent loop you plan to break out of. Example below:
outerLoop: for _ in 1...3 {
while i < 4 {
print("output")
i++
break outerLoop
}
}
Day 5
Swift lets us build new functions out of existing functions, which is a technique called function composition.
In Objective C, the first parameter to a function was always left unnamed, and so when you use those frameworks in Swift you’ll see lots of functions that have underscores for their first parameter label to preserve interoperability with Objective-C.
Day 7
If the last parameter to your function is a closure, you can use trailing closure syntax.
Day 8
Properties let us attach information to structs, and Swift gives us two variations: stored properties, where a value is stashed away in some memory to be used later, and computed properties, where a value is recomputed every time it’s called.
Constants cannot be computed properties.
Functions inside structs are called methods, but they still use the same func
keyword.
If you want to use a function inside of a struct to modify a variable within that same struct, you must precede the func definition with mutating. This allows you to change values of variables within the same struct that the function is in.
Day 9
When you create your own custom initializer for a struct (example below) you can no longer call the struct as you would normally. Instead, you first have to create an instance of the struct, and then add in the values for its properties in a second line of code.
struct Employee {
var name: String
var yearsActive = 0
init() {
self.name = "Anonymous"
print("Creating an anonymous employee…")
}
}
let roslin = Employee(name: "Laura Roslin")
// The above is not valid, instead you have to do it like the below code
let roslin = Employee()
roslin.name = "Roslin"
Day 10
Classes don’t have member-wise initializers like structs do.
Copies of structs are always unique, whereas copies of classes actually point to the same shared data.
You can create a class that is based off of another class but adds on to it. The original class is called the superclass (Or parent class) and the new class is called the child class.
Using the override keyword in a subclass, you can change the implementation of a func (referred to as a method) that was defined in a superclass.
Using the keyword final in front of a class means you cannot make subclasses of that class.
Contrary to a constant struct, in a constant class with a variable property, that property can be changed.
Day 11
Protocols are a way of describing what properties and methods something must have. An example protocol is defined below:
protocol Identifiable {
var id: String { get set }
}
Properties cannot only have the value { set }. They can either be { get set } or { get }
Extensions may not add new stored properties, only computed properties.
Day 12
Add a ? after a type declaration to make it optional.
Swift won’t let us use optionals without unwrapping them first.
func getUsername() -> String? {
"Taylor"
}
if let username = getUsername() {
print("Username is \(username)")
} else {
print("No username")
}
You can force unwrap optionals by adding a ! after the definition. You should never force unwrap something that won’t succeed 100% of the time though.
You can create an implicitly unwrapped optional by adding a ! after the type declaration. The biggest difference between these and regular optionals are that implicitly unwrapped optionals don’t need to be unwrapped to be used. So as long as you know they will have a value before they are used, you’re good.
Nil coalescing (??) allows you to set a default value if a variable actually does end up being nil. Example below:
let lightsaberColor: String? = "green"
let color = lightsaberColor ?? "blue"
Optional chaining lets us dig through several layers of optionals in a single line of code, and if any one of those layers is nil then the whole line becomes nil. In the example below, if the first item in the array was nil then uppercase would stop when it encounters first? and set uppercase equal to nil.
let names = ["Vincent", "Pablo", "Claude"]
let uppercase = names.first?.uppercased()
Day 16
var body: some View means that some specific type of view must be sent back from this property. This is the only thing that’s required within a view.
Option + CMD + P is the shortcut to resume the Preview editor. (But I changed mined to CMD + P)
You are limited to 10 child views within a parent view. If you want to add more than 10, wrap a set of 10 or less elements within a Group{}. Group{} doesn’t have a visual change on the UI – if you do want to group child views within a visible section use Section{}.
struct ContentView: View {
var body: some View {
Form {
Group {
Text("Hello, world!")
Text("Hello, world!")
Text("Hello, world!")
}
Group {
Text("Hello, world!")
Text("Hello, world!")
Text("Hello, world!")
}
}
}
}
@State is best used for simple properties stored in one view.
When you see a $ before a property name, remember that that creates a two-way binding. The value can be read and also written.
Day 17
When adding a TextField to a form with SwiftUI the first text property will be the placeholder text. In the example below the placeholder text will be “Amount”. You can also choose which keyboard will be pulled up when the field is edited. For example, the below code will bring up the number pad keyboard instead of the regular keyboard.
TextField("Amount", text: $checkoutAmount)
.keyboardType(.decimalPad)
With the following code, you can convert a string into a Double. Take into consideration that this will be an optional, because there’s a chance that the string value won’t be able to be converted into a double, in which case it’ll be nil.
let stringValue = "0.5"
let doubleValue = Double(stringValue)
// You can use the following code to set doubleValue to 0 if Swift is not able to convert it to a Double
let orderAmount = Double(checkoutAmount) ?? 0
To change a Double’s value to only two decimal places use a specifier like the code below.
Text("$\(totalPerPerson, specifier: "%.2f")")
Day 20
When creating an alert, for the isPresented property, make sure to have a 2-way-binding variable (@State private var showingAlert = false) beacause Swift will automatically change that value to false when the alert is closed.
@State private var showingAlert = false
var body: some View {
Button("Show Alert") {
self.showingAlert = true
}
.alert(isPresented: $showingAlert) {
Alert(title: Text("Hello SwiftUI"), message:
Text("This is some detail message"), dismissButton:
.default(Text("OK")))
}
}
Day 21
Add .shuffled() after declaring an array to shuffle the order of the elements within the array. Example below:
var names = ["Joe", "Sears", "Reedom", "Chop"].shuffled()
When adding a shadow with .shadow(), if you skip adding the X and Y values Swift will automatically assume those values as 0.
Day 23
.frame(maxWidth: .infinity, maxHeight: infinity)
Imagine that SwiftUI renders your view each time after each modifier that has been added to the view. (.frame, .background)
You can stack .padding (Among other modifiers) multiple times to create multiple borders around an element.
Day 26
All iPhones come with a technology called Core ML built right in, which allows us to write code that makes predictions about new data based on previous data it has seen.
When using a specifier for a Text label, you can use %g to remove any insignificant zeroes. An example of it in use is below:
Text("\(sleepAmount, specifier: "%g") hours")
Day 29
You can change the style of List() you are using with .listStyle(GroupedListStyle()). That will get you the iOS 14 style list view.
Instead of putting a ForEach inside of a List(), since this is so common the syntax for it it List(0..<5) { //code here }
When creating a dynamic list with the code above, you need to make sure that each item in the list has a unique identifier. An example of that is found in the below code block:
let people = ["Finn", "Leia", "Luke", "Rey"]
var body: some View {
List(people, id: \.self) {
Text(($0))
}
}
if let fileURL = Bundle.main.url(forResource: "some-file", withExtension: "txt") {
// we found the file in our bundle
if let fileContents = try? String(contentsOf: fileURL) {
// we loaded the file into a string
}
}
The below code will split up the first string and separate it whenever there is a space.
let input = "a b c"
let letters = input.components(separatedBy: " ")
You can use .randomElement() to pick a random string out of another string such as input above. And also you can use ?.trimmingCharacters(in: .whiteSpaceAndNewLines) to remove the leading and trailing white space in another string.
Day 32
Just by adding the .animation(.default) modifier to an element, you can ensure smooth animations instead of choppy ones.
Day 33
As said previously, the order in which you add modifiers to views matters. Think like this: Swift redraws the view every time you add a new modifier. Each individual view gets stacked on top of each other to form the final view.
You can add more than one .animation() modifier to a view. Again the order in which you place these matter. You can also use .animation(nil) to set sort of an “animation breakpoint” in which everything above it animates, but not using the properties in the inevitable second .animation() modifier added later.
You can use .transition() to animate effects that happen when transitioning between views.
Day 35
Remember, in SwiftUI all our views – and all our animations – must be a function of our state. That means we don’t tell the button to spin, but instead attach the button’s spin amount to some state, then modify that state as appropriate.
Day 36
When using a class instead of struct to store data, you’ll need to use @ObservableObject instead of @State. A simple example with Textfields to edit the data is shown below:
class User: ObservableObject {
@Published var firstName = "Bilbo"
@Published var lastName = "Baggins"
}
struct ContentView: View {
@ObservedObject private var user = User()
var body: some View {
Text("Hello, \(user.firstName) \(user.lastName)!")
TextField("First Name", text: $user.firstName)
TextField("Last Name", text: $user.lastName)
}
}
The onDelete() modifier only works when you have items within a ForEach().
Aim to store no more than 512KB in the UserDefaults.
Day 37
@Published
publishes change announcements automatically.@ObservedObject
watches for those announcements and refreshes any views using the object.sheet()
watches a condition we specify and shows or hides a view automatically.Codable
can convert Swift objects into JSON and back with almost no code from us.UserDefaults
can read and write data so that we can save settings and more instantly.
Day 39
There’s two basic ways to add images. The first way is below:
Image("apollo-capsule")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 300, height: 300)
.clipped()
And the second (more advanced way) is by using a Geometry Reader:
GeometryReader { geo in
Image("apollo-capsule")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geo.size.width)
}
And lastly here’s another way to do it (Found in Apple’s developer documentation)
Image("ProfilePicture")
.resizable()
.aspectRatio(contentMode: .fit)
When creating a ScrollView, add the .frame(maxWidth: .infinity) modifier to allow the Scrollview to be scrolled through by scrolling anywhere on the screen, not just within the ScrollView frame itself. Example below:
ScrollView(.vertical) {
VStack {
ForEach(0..<100) {
Text("Item \($0)")
.font(.title)
}
}
.frame(maxWidth: .infinity)
}
Here’s a simple bare-bones example to create a linked view with NavigationLink:
NavigationView {
VStack {
NavigationLink(destination: Text("Detail View")) {
Text("Hello World")
}
}
.navigationBarTitle("SwiftUI")
}
And here’s an example of doing the same thing, but with a list of 100 unique rows instead:
NavigationView {
List(0..<100) { row in
NavigationLink(destination: Text("Detail \(row)")) {
Text("Row \(row)")
}
}
.navigationBarTitle("SwiftUI")
}
Day 44
In this order, “Make it work, Make it right, Make it fast”.
If you have overlapping paths, try filling them with the below code. It can produce some very interesting results.
.fill(Color.red, style: FillStyle(eoFill: true))
Day 45
A quick shortcut to apply a color overlay on and image is as follows:
Image("angles-orange")
.colorMultiply(.blue)
You can also set the blend mode of an element with the .blendMode(.screen) modifier.
To animate a value that is being passed in, try the below code where insetAmount is the value that will be changing. In this case it’s on tap of an element.
struct Trapezoid: Shape {
  var insetAmount: CGFloat
  Â
  var animatableData: CGFloat {
    get { insetAmount }
    set { self.insetAmount = newValue }
  }
}
@State private var insetAmount: CGFloat = 50
  Â
var body: some View {
Trapezoid(insetAmount: insetAmount)
.onTapGesture {
withAnimation {
    self.insetAmount = CGFloat.random(in: 10...90)
    }
}
}
Day 57
If you don’t have a unique identifier, you can often use \.self
@joekotlan on X