LYFA Course - Launch Your First App

Resources:

https://www.dropbox.com/sh/nh92vdepniipw2v/AAAaizYNZvE1_l5MXozzDMgCa?e=1&dl=0

Start: 07. November 2024

Module 1 - The War Card Game + Basic Syntax

Lesson 3 - UI Building

https://github.com/dmkeks1/L3-Demo

Lesson 4 - War Card Game UI

https://github.com/dmkeks1/War-Card-Game

Lesson 6 - Swift functions

```Swift
func doAdd(a:Int, b:Int) {
    
    var sum = a + b
    print(sum)
}

doAdd(5, 6)
```

  

Man kann die Funktion auch mit labels versehen was die Arbeit damit vereinfacht:

```Swift
func doAdd(firstNum a: Int, secondNum b: Int) -> Int {
    
    var sum = a + b
    return sum
}


doAdd(firstNum: 5, secondNum: 5)
```

  

Möchte man keine Labels(z.B. a: b: etc.) anzeigen, dann kann man _ bei der Deklaration verwenden:

```Swift
func doAdd(_ a: Int, _ b: Int) -> Int {
    
    var sum = a + b
    return sum
}

doAdd(2,5)
```

  

Wenn die Funktion was returnen soll →

![[99 - meta/attachements/image 44.png|image 44.png]]

![[99 - meta/attachements/image 1 2.png|image 1 2.png]]
  • Lesson 7 - Buttons and Properties

    https://github.com/dmkeks1/War-Card-Game

    Button aus image erstellen. Hier wird eine closure benutzt.

    Button {
              deal(). //action oder funktion
                } label: {
                    Image("button") //image des buttons
                }
  • Lesson 8 - War Card Game Logik - randomizer, Punkte, Auswertung, @State properties, conditional Logic (if statement)

    https://github.com/dmkeks1/War-Card-Game

    number randomizer

    Int.random(in: 2...14) // randomizes Nummer zwischen 2 und 14, inkl. 2 & 14

    @State properties

    @State ist ein property wrapper, die variable danach eine property

    @State var playerCard: String = "back" // @State ist ein property wrapper -> danach property
  • Challenge 1 - Number Climb Game

    https://github.com/dmkeks1/NumberClimbGame

Module 2 - The Menu App

  • Lesson 2 - Arrays

    var b: [Int] = [5, 10, 15, 20, 25]
     
    b[0] = 20
     
    print(b) // Ausgabe ist nun 20
  • Lesson 3 - Structures

    Structs sollten immer mit großbuchstaben beginnen da Sie sonst mit Funktionen verwechselt werden können.

    testFunktion() → funktion

    MenuItems() → struct ohne definierte vars

    structs sind wie Blueprints für einen eigens erstellten Datentypen.

    Um diese zu nutzen muss man Instances erstellen.

    functions können in einer structure definiert sein.

    firstItem.testPrint()

    variablen in einer structure nennt man property!

    functions in einer structure nennt man method!

    Wenn man in der struct Definition einen Wert zuweist gilt dieser quasi als Default Wert für alle neu erstelle Instanzen und muss bei der Instance Erstellung nicht angegeben werden.

    // Struct erstellen, vorzugsweise in eigener Datei MenuItem.swift
    struct MenuItem {
        var name: String
        var price: String
        var imageName: String
     
    }
     
    // erstellten Struct "initialisieren" 
    var menuItems: [MenuItem] = []
     
     
    // Instance erstellen
     
    var firstItem = MenuItem(name: "Sushi", price: "1,99€", imageName: "sushi")
  • Lesson 5 - Swift UI List

    https://github.com/dmkeks1/Menu

    image 2 2.png

    … {word in …
    word wird als variable deklariert

    https://github.com/dmkeks1/Menu/blob/main/Menu/MenuView.swift

    • result

      image 3 2.png

  • Lesson 5 - Challenge

    Challenge

    Build a UI with a List and a Button below it.

    Declare a property that contains an array of 5 strings. These can be any 5 words you want.

    Each time the button is tapped, choose a random word from the array and put it in the list.

    The number of items in the list should grow as you tap the button.

    https://github.com/dmkeks1/Lesson5Challenge/blob/main/Lesson5Challenge/ContentView.swift

  • Lesson 7 - Structures Part 2

    image 4 2.png

    try to organize your code logically into other files.

    image 5 2.png

  • Lesson 8 - State and Updating Views

    Outsourcing Data into another file/struct.

    File of data is called DataService here because it provides data for us.

    https://github.com/dmkeks1/Menu/blob/main/Menu/DataService.swift

    Instance muss danach in dem View indem wir die Data benötigen erstellt werden.

      @State var menuItems: [MenuItem] = [MenuItem]() // @State sagt SwiftUI, dass die variable wichtig ist fürs UI. 
      // Änderungen werden überwacht und View wird bei jeder Änderung neu gedrawed
      
      var dataService = DataService() // creating an instance of DataService
      
      ...
      List(menuItems) {item in
      ...
      }
      .onAppear { // modifier of the List
                // call for the data
                menuItems = dataService.getData()
  • Lesson 9 - Reusing your Views

    https://github.com/dmkeks1/Menu

    Views können reused werden in anderen views, z.B. ein Button.

    CustomButton()

    Es können auch parameter übergeben werden wenn diese in CustomButton definiert wurden.

    CustomButton(buttonText: “Mein Custom Button”)

  • Module 2 Challenge - Movie List

    https://github.com/dmkeks1/Module2ChallengeMovieList

Module 3 - The Restaurant App

https://github.com/dmkeks1/Restaurant

  • Lesson 2 - TabView

    TabView {
                MenuView().tabItem {
                    
                    VStack {
                        Text("Menu")
                        Image(systemName: "menucard")
                    }
                    
                    
                }
                
                AboutView().tabItem {
                    
                    VStack {
                        Text("About")
                        Image(systemName: "info.circle")
                        
                    }
                    
                }
                
                GalleryView().tabItem {
                    VStack{
                        Text("Gallery")
                        Image(systemName: "photo")
                    }
                }
            }

    image 6 2.png

  • Lesson 3 - ScrollView

    VStacks mit viel Inhalt verhalten sich komisch was Text und Images angeht da nicht genug Platz vorhanden ist. Sobald man ScrollView definiert verhält es sich wie gewünscht.

    ScrollView {}

    ScrollView {
                VStack (alignment: .leading) {
                    Text("About")
                        .font(.largeTitle)
                        .bold()
                    
                    Image("restaurant")
                        .resizable()
                        .scaledToFit()
                        .padding(.bottom)
                    
                    Text("Sukiyabashi Jiro (すきやばし次郎, Sukiyabashi Jirō) is a sushi restaurant in Ginza, Chūō, Tokyo, owned by Jiro Ono.[2] Ono previously operated as the head chef, but stepped aside in favor of his son Yoshikazu Ono in 2023 due to ill health. Sukiyabashi Jiro was the first sushi restaurant[4] to receive three stars from the Michelin Guide.[5] It was removed from the Michelin Guide in November 2019 as it does not receive reservations from the general public,[6][7] instead requiring reservations to be made through the concierge of a luxury hotel.[8]")
                    
                    Image("map")
                        .resizable()
                        .scaledToFit()
                    
                    Text("""
                         Tsukamoto Sogyo Building
                         Basement Floor 1
                         2-15, Ginza 4-chome
                         Chūō, Tokyo
                         Japan
                         """)
                    
                    
                }
            }

    You can hide the scrollbars

    ScrollView (showsIndicators: **false**``) {

    }

    You can also hide them individually for horizontal or vertically scroll.

    ScrollView(.horizontal, showsIndicators: false)

  • Lesson 4 - Dynamically Generating UI Elements using For Each

    ForEach Syntax

    ForEach(wordList) { word in
    		Text(word)
    	}
    	
    	
    	ForEach(range) { _ in // range could be for example 2..5 
    		Text("Hello")
    		}

    ForEach(wordList, id: \.``**self**``) { word **in**

    Text(word)

    }

    id: \.self → legt fest, dass der String/Int-Wert etc. sich selbst als identifier nimmt. So brauch man das “Identifiable” nicht.

  • Lesson 5 - LazyVGrids

    LazyVGrid(columns: [GridItem(), GridItem(), GridItem()]) {
     
    }

    image 7 2.png

    Beispiel:

     
    import SwiftUI
     
    struct ContentView: View {
        
        var photos = ["gallery1", "gallery2", "gallery3", "gallery4", "gallery5", "gallery6", "gallery7", "gallery8", "gallery9", "gallery10", "gallery11"]
        
        
        var body: some View {
            
            ScrollView {
                
                LazyVGrid(columns: [GridItem(), GridItem(), GridItem()]) {
                    ForEach(photos, id: \.self) { p in
                        Image(p)
                            .resizable()
                            .scaledToFit()
                    }
                }
            }
            
        }
        
    }
     
     
    \#Preview {
        ContentView()
    }

    image 8 2.png

    Ist so nicht scrollable, dafür muss es in einen ScrollView

    Beispiel wenn kein Abstand gewünscht: #LazyVGrid

    ScrollView {
                
                LazyVGrid(columns: [GridItem(spacing:0), GridItem(spacing:0), GridItem(spacing:0)], spacing: 0) {
                    ForEach(photos, id: \.self) { p in
                        Image(p)
                            .resizable()
                            .scaledToFit()
                    }
                }
            }

    image 9 2.png

  • Lesson 6 - Displaying Images in a Grid

    GeometryReader { proxy in
    	ScrollView {
    	LazyVGrid {
    	...
    	}
    	}
    	
    }

    image 10 2.png

    https://github.com/dmkeks1/Restaurant/blob/main/Restaurant/GalleryView.swift

  • Lesson 7 - Modal Views Using Sheets

    Sheets | Apple Developer Documentation
    A sheet helps people perform a scoped task that’s closely related to their current context.
    https://developer.apple.com/design/human-interface-guidelines/sheets

    Best practice ist:

    1. eigenen View anlegen, in diesem Fall ein View für ein Bild.

    2. bool variable anlegen: @State var sheetVisible = false

    3. .sheet(isPresented: $sheetVisible) { PhotoView() }

      als modifier für den VStack. $sheetVisible ist eine bool variable die bei dem Tap auf ein Bild umgeschaltet wird. Das $ Zeichen symbolisiert ein Binding. PhotoView() die erstellte View.

    4. .onTapGesture { sheetVisible.toggle() // same as sheetVisible = true }

      als modifier für den Content, in dem Fall für Image(photo)

  • Lesson 8 - Passing Data with Bindings

    @State var dataArray

    A binding is a reference to a State property that allows a two way relationship.

    Binding müssen immer in andere Views gepassed werden sollten Sie dort gebraucht werden z.B.:

    PhotoView(selectedPhoto: $selectedPhoto, sheetVisible: $sheetVisible)

    There are two common ways to use bindings:

    1. Textfield listens to changes, but also can write data in the property. (here we are in the same view)

    image 11 2.png

    1. able to pass data/variables into another views and back.

    image 12 2.png

    Für eine funktionierende Preview muss ein “FakeBinding” in einer Form einer Konstanten erstellt werden z.B. für den PhotoView View:

    #Preview { PhotoView(selectedPhoto: Binding.constant("gallery1")) }

    selectedPhoto ist die Binding property/variable und für eine funktionierende preview müssen wir quasi eine imaginäre auswahl getroffen haben. Mit einer konstanten, in dem Fall gallery1, simulieren wir eine Auswahl. Sonst kommt es zu Fehlern.

    https://github.com/dmkeks1/Restaurant/blob/main/Restaurant/PhotoView.swift

    In der GalleryView muss .sheet diese variable für den PhotoView übergeben werden:

    .sheet(isPresented: $sheetVisible) { PhotoView(selectedPhoto: $selectedPhoto) }

    und in der PhotoView natürlich definiert werden:

    @Binding **var** selectedPhoto: String

  • Lesson 9 - Swift Closures, Initializers, Access Modifiers and More

    Initializer

    An initializer is a method that gets called when a new instance is created.

    The initializer method is responsible for making sure that the instance is ready to use.

    In order for an Instance to be ready to use all of its properties have to have a value.

    You can define an init() {} method in an instance so if you create a new instance of that a piece of code runs when you create it.

    .self refers to the struct itself, gets used in initializers.

    Closures

    Closures are simply block of code without a name.

    image 13 2.png

    image 14 2.png

    XCode often changes methods into trailing closure format, so don’t worry. Just know what closures are.

    Access Modifiers

    Access modifiers allow you to change who can access the properties and methods of a structure.

    Arten von access modifiers:

    public = default

    private = can only be used inside a struct/function. Can’t be accessed outside of a structure/function.

    fileprivate = you can access outside a struct, but only inside the current swift file.

    internal = only inside the same target. this is more advanced and probably wont be used.

    by default : everything in a struct is public, you dont need to specific public.

  • Module 3 Challenge

    Habs ohne DataService und eventuell etwas unsauber gemacht was das spacing des LazyVGrids angeht.

    https://github.com/dmkeks1/Module3Challenge

Module 4 - The Guidebook App

https://github.com/dmkeks1/Guidebook

  • Lesson 03 - NavigationStack and NavigationLink

    image 15 2.png

    NavigationStack{
                
                ScrollView {
                    
                    VStack {
                        
                        ForEach(cities) { city in
                            NavigationLink {
                                AttractionView()
                            } label: {
                                Text(city.name)
                            }
                        }
                    }
                }
                .padding()
            }
            
            .onAppear() {
                cities = dataService.getData()
            }
    NavigationLink {
                            DetailView(attraction: attraction)
                        } label: {
                            Text(attraction.name)
                        }
  • Lesson 04 - Building the Guidebook UI

    https://github.com/dmkeks1/Guidebook

  • Lesson 05 - Debugging in Xcode

    Info

    image 16 2.png

    to debug variables in the console type:

    po a

    po stands for print out. A is a variable.

  • Lesson 06 - Swift Optionals

    Sometimes a variable is just empty at a certain point, maybe because the user hasnt made a selection yet. For that case we can set a variable to an Optional by adding a ? at the Datatype.

    var selectedFood: String? = nil

    nil indicates that there is no value at the moment.

    An Optional needs to get unwrapped. They way you do that is by adding a !

    Text(selectedFood!)

    image 17 2.png

    Unwrapping an optional with nil will crash the app!

    To prevent crashin you can user either

    1. if statement
    if selectedFood != nil {
    	Text(selectedFood!)
    }
     
    OR 
     
    if selectedFood == nil {
    	Text("Ice Cream")
    }
    else {
    	Text(selectedFood!)
    }
    1. optional binding
    if let food = selectedFood {
    	Text(food)
    }
     
    // if value is nil, this check just bypasses the lines -> no error
    1. nil coalescent operator
    Text(selectedFood ?? "Sushi")
     
    // if the value is valid, it will display selectedFood. If its nil
    // it will display Sushi.
     
    //can also be used for optional chaining:
     
    Text(selectedMenuItem?.name ?? "Pizza")

    image 18 2.png

    optional chaining:

    me?.walk()

    tzhe = checks if the var contains something, if yes, call method() on in. if not, do nothing.

  • Lesson 07 - Swift Dictionaries

    keys are unique!

    they have associated values

    image 19 2.png

    image 20 2.png

    import UIKit
     
    var myArray: [String] = ["Orange", "Apple","Banana"] // Array
    var myEmptyArray = [String]()
     
     
     
    var myDictionary: [String:String] = ["OR":"Orange", "AP":"Apple", "BA":"Banana"] //Dictionary
    var myEmptyDictionary = [String:String]()
     
    myArray.append("Pear")
     
    myDictionary["PR"] = "Pear" // create key value pair
    myDictionary["PR"] = "Prune" //updates value for key "PR"
     
    myDictionary["PR"] = nil // deletes value from the dictionary
     
     
    myArray[1] // read from array - ORDER MATTERS
     
    //
    // if a Dictionary entry is not there, its nil, so you need to check:
    //
     
    if myDictionary["OR"] != nil {
        myDictionary["OR"] // read from array - WILL OUTPUT ORANGE
    }
     
    print(myDictionary["JDF"] ?? "Peach")
     
     
     
     
  • Lesson 08 - Swift Loops

    image 21 2.png

    For In

    var myArray: [String] = ["Orange", "Apple","Banana"] // Array
    var myDictionary: [String:String] = ["OR":"Orange", "AP":"Apple", "BA":"Banana"] //Dictionary
     
    for item in myArray { // going through an array
        print(item)
    }
     
     
    for k in myDictionary.keys { // going through the dictionary and prints out the keys
        print(k)
    }
     
     
    for v in myDictionary.values { // going through the dictionary and prints out the values
        print(v)
    }
     
    for (k, v) in myDictionary {//  going through the dictionary and prints out the values and keys.
                                // (k,v) -> is called a tuple, a group of variables
        print(k)
        print(v)
    }

    While

    import UIKit
     
     
    var myArray: [String] = ["Orange", "Apple","Banana"] // Array
    var myDictionary: [String:String] = ["OR":"Orange", "AP":"Apple", "BA":"Banana"] //Dictionary
     
     
     
     
    var flag = false
     
    while flag == false { // checks initially and may not run even once
        print("Hello")
        
        flag = true
    }
     
     
    repeat { // run guaranteed once, and checks condition afterwards
        print("Repeat")
    } while flag == false
  • Lesson 09 - JSON Data Format

    image 22 2.png

    JSON Key always needs to be a String. The values can be:

    image 23 2.png

    to validate your JSON file:

    https://github.com/dmkeks1/Guidebook/blob/main/Guidebook/DemoData.json

  • Lesson 10 - How to Parse JSON in Swift - November 12, 2024

    Parse/Deserialize/Decode = translate into Swift

    translate Swift into JSON = Serializing/Encode

    Steps to get JSON Data into the APP:

        		// get filepath to DemoData.json
            
            // Read the file and turn into Data
            
            // parse Data into Swift Instances
            

    Code of working JSON import for the Guidebook App (DataService file, rest is in the GitHub)

    Info

    func getFileData() -> [City] {
            
            // get filepath to DemoData.json
            if let url = Bundle.main.url(forResource: "DemoData", withExtension: "json") {
                
                do {
                    // Read the file and turn into Data
                        let data = try Data(contentsOf: url) // try if it throws an error
                    
                            // parse Data into Swift Instances
                    let decoder = JSONDecoder()
                    
                    
                    
                    do {
                        let cities = try decoder.decode([City].self, from: data) //.self means that we try to create the type of City data and not an array or sth.
                        return cities
                    }
                    catch {
                        print("Couldn't parse the JSON: \(error.localizedDescription)")
                    }
                    
                    
                    
                    
                }
                catch  {
                    print("Couldn't read file: \(error.localizedDescription)")
                }
            
                
            
                
                
            }
            
            
            return [City]()
            
        }
  • Lesson 11 - Opening other Apps November 13, 2024

    Url Schemes

    image 24 2.png

    Complete List of iOS URL Schemes for Apple Apps and Services (Always-Updated)
    The most exhaustive list of Apple iOS app schemes on the web
    https://medium.com/@contact.jmeyers/complete-list-of-ios-url-schemes-for-apple-apps-and-services-always-updated-800c64f450f

    Button {
                            
                            // CReate URL Instance vbased on URL Scheme
                            if let url = URL(string: "maps://?q=\(attraction.name.replacingOccurrences(of: " ", with: "+"))&s11=\(attraction.latLong)&z=10&t=s")
                            {
                                // Test if url can be opened, is an app present?
                                if UIApplication.shared.canOpenURL(url) {
                                    // open url
                                    UIApplication.shared.open(url)
                                }
                                
                            }
                            
                            
                        } label: {
                            
                            ZStack {
                                RoundedRectangle(cornerRadius: 15)
                                    .foregroundColor(.blue)
                                    .frame(height: 40)
                                Text("Get Directions")
                                    .foregroundColor(.white)
                            }
                        }

    https://github.com/dmkeks1/Guidebook/blob/main/Guidebook/DetailView.swift

    leerzeichen müssen mit replacingOccurrences(of: " ", with: "+")ersetzt werden.

  • Challenge - Pokemon Guide November 13, 2024

    https://github.com/dmkeks1/PokemonChallenge

  • Assignment for Certificate November 13, 2024

    https://github.com/dmkeks1/PokemonTCGPacks

iOS Networking November 13, 2024

https://www.dropbox.com/scl/fo/3snpkc49at92dl1yg5lmi/h?rlkey=2o0510ofjl9cqke91wixhq364&e=1&dl=0

Module 1 - What is Networking? November 13, 2024

  • Lesson 02 - Finding and Using APIs

    image 25 2.png

    image 26 2.png

    There are OAuth and API_KEY authorizations.

    OAuth sends a login request first, then you get a token, then you can send the data request.

    With the API_KEY you can directly request data.

  • Lesson 03 - How to use API endpoints in Swift

    in this course we are using the pexels api.

    image 27 2.png

  • Lesson 04 - What is a REST API

    image 28 2.png

  • Lesson 05 - iOS Concurrency, Await and Async

    Concurrency - several Threads to work on things

    image 29 2.png

    image 30 2.png

    async keyword lets the system know, run this in the background if you need to.

    func longTask() async {
    		print("Long Task"
    }

    to call the function we need to use the Task{} block now and use the keyword await.

    Await will wait for a task to complete before continue executing.

    Task {
    	await longTask()
    }

    to check which thread is beeing used you can use

    print(Thread.current)

    UI work should always be done by the main thread. You can used the statement

    Mainactor.run {

    print(”Display UI work”

    }

  • Lesson 06 - How to make a Network request in Swift

    https://learn.codewithchris.com/courses/take/networking/lessons/47392329-lesson-06-how-to-make-a-network-request-in-swift

    image 31 2.png

     
    // the following line of code already takes care of the optinal check.
    // if the url is nil, it skips it.
    if let actualUrl = URL(string: "https://api.pexels.com/v1/search?query=nature") {
     
    	// create url request and pass in actualUrl
     
    }

    error handling:

    image 32 2.png

    Request

    ...
    .onAppear {
                
                Task {
                    await apiCall()
                }
                
            }
            ...
     
     
    func apiCall() async{
            
            // 1. URL
            if let url = URL(string: "https://api.pexels.com/v1/search?query=nature&per_page=1") {
                // 2. URL request
                var request = URLRequest(url: url)
                request.addValue("\(api_key)", forHTTPHeaderField: "Authorization") // adds key to request
                // 3. URLSession
                do { // we dont ne a task block here because the function in the code is called
                     // in a task block already
                    let (data, response) = try await URLSession.shared.data(for: request) // returns in a tuple, so we need to put the answer in a tuple aswell
                    
                    print(data) // actual requested data
                    print(response) // status code, other info
                    
                } catch {
                    print(error)
                }
            }
        }

    https://github.com/dmkeks1/M1L6HowToMakeANetworkRequest

  • Lesson 07 - Debugging Network Requests

    https://proxyman.io/

    1. install certs
    2. enabled domain
    3. remove app and reinstall

    https://learn.codewithchris.com/courses/take/networking/lessons/47857956-lesson-07-debugging-network-requests-with-proxyman

  • Lesson 08 - Parsing Network Repsonses

    Complete call with response and parsing:

    https://github.com/dmkeks1/M1L6HowToMakeANetworkRequest/blob/main/M1L6HowToMakeANetworkRequest/Model/SearchResponse.swift

    structs are created in advance from the response template of the api documentation.

    func apiCall() async{
            
            // 1. URL
            if let url = URL(string: "https://api.pexels.com/v1/search?query=nature&per_page=1") {
                // 2. URL request
                var request = URLRequest(url: url)
                request.addValue("\(api_key)", forHTTPHeaderField: "Authorization") // adds key to request
                // 3. URLSession
                do { // we dont ne a task block here because the function in the code is called
                     // in a task block already
                    let (data, response) = try await URLSession.shared.data(for: request) // returns in a tuple, so we need to put the answer in a tuple aswell
                    
                    // 4. Parse the JSON
                    
                    let decoder = JSONDecoder()
                    do {
                        let searchResponse = try decoder.decode(SearchResponse.self, from: data)
                        
                        for photo in searchResponse.photos {
                            print(photo)
                        }
                        
                    } catch {
                        print(error)
                    }
     
                } catch {
                    print(error)
                }
            }
        }

    https://github.com/dmkeks1/M1L6HowToMakeANetworkRequest/blob/main/M1L6HowToMakeANetworkRequest/ContentView.swift

  • Challenge - Jokes App

    https://github.com/dmkeks1/ChallengeJokesApp

    • Instructions

      In this challenge, I'd like you to try doing the same thing, except with a different API.

      As usual, to get the most out of this challenge, try to complete it by reviewing the training in this module before looking at the solution or asking for help.

      Getting stuck, looking things up, reviewing previous lessons.. these are all steps in the skill building process!

      **Setup:**

      • API: [https://jokeapi.dev/](https://jokeapi.dev/)
      • Authentication: None
      • Endpoint: https://v2.jokeapi.dev/joke/Any?type=single

      **Challenge:**

      • Build a UI that has a Text label in the center of the screen. At the bottom, put a button.
      • When the button is tapped, it sends the API request
      • When the response returns, parse the data and display the joke in the Text label
      • You should be able to keep tapping the button to show a different joke in the Text label
      • Make sure you use "async" and "await" as you learned in Lesson 5.

      **Getting Help:**

      • Check the project that you worked on in this module which is very similar.
      • Dedicated discussion thread for this challenge [here](https://codecrew.codewithchris.com/t/ios-networking-module-1-jokes-app-challenge/25336)``. (If you need access to the CWC+ students section of the community, fill out [this form](https://codewithchris.typeform.com/to/NlKfo8FS)``)
      • Reach out to one of our coaches with the chat widget in the lower right corner.
      • Check out the solution below.

      **Solution:**

      • Solution can be found [here](https://www.dropbox.com/scl/fi/xopy0eu436bplb2w4cp0u/Networking-Module-1-Challenge-1-Project.zip?rlkey=9ggl7pudef796v3sik5qu7id4&dl=1)``.

Module 2 - The Citysights App November 16, 2024

  • Module 01 - Source Control

    image 33 2.png

    image 34 2.png

    image 35 2.png

  • Module 05 - Adding the API key to Xcode Securely

    CleanShot_2024-11-16_at_19.05.092x 2.png

    • create config file from template in xcode

      CleanShot_2024-11-16_at_19.06.39 2.png

      target needs to be unchecked

      CleanShot_2024-11-16_at_19.07.33 2.png

      choose created config file in xcode project for it to be used

      CleanShot_2024-11-16_at_19.13.08 2.png

      add api_key to info.plist

      CleanShot_2024-11-16_at_19.15.54 2.png

      CleanShot_2024-11-16_at_19.18.57 2.png

      To test if the app can access the key you can print

      .onAppear {
                  print(Bundle.main.infoDictionary?["API_KEY"] as? String)
                  }

      create .gitignore file to ignore the config file

      navigate to the project folder an press SHIFT + COMMAND + . to reveal invis folders like .git

      open terminal and navigate to the project folder by dragging the folder into the terminal.

      in the terminal enter: touch .gitignore touch creates, . means hidden

      for creating gitignore we can use https://www.toptal.com/developers/gitignore

      enter xcode and swift and copy the content in the .gitignore file we created.

      CleanShot_2024-11-16_at_19.36.00 2.png

      also add the config.xcconfig manually!

      CleanShot_2024-11-16_at_19.36.56 2.png

      make sure to unstage config.xcconfig in xcode so it doenst commit the api_key if we created that file after settings everyhting up

      CleanShot_2024-11-16_at_19.37.35 2.png

      now we can commit.

      if we used another branch we can merge into main after and delete the used branch (chris used that in his tutorial)

  • Module 06 - making API request for yelp api

    import Foundation
     
    struct DataService  {
        
        
        let api_key = Bundle.main.infoDictionary?["API_KEY"] as? String
        
        
        func businessSearch() async {
            
            
            //check if api key exists
            guard  api_key != nil  else {
                return
            }
            
            // 1. Create URL
            if let url = URL(string: "https://api.yelp.com/v3/businesses/search?latitude=35.665517&longitude=139.770398&categories=restaurants&limit=10") {
                
                
                
                
                // 2. Create request
                var request = URLRequest(url: url)
                
                request.addValue("Bearer \(api_key!)", forHTTPHeaderField: "Authorization")
                request.addValue("application/json", forHTTPHeaderField: "accept")
                
                // 3. Send request
                
                do {
                    let (data, response) = try await URLSession.shared.data(for: request)
                    
                    print(data)
                    print(response)
                }
                catch {
                    print(error)
                }
            }

    https://github.com/dmkeks1/CitySights-App/blob/main/CitySights%20App/DataService.swift

  • Module 07 - Parsing Yelp API + CodingKeys

    https://github.com/dmkeks1/CitySights-App/blob/main/CitySights%20App/Model/Business.swift

    struct Business: Decodable, Identifiable {
     
        
        var id: String?
        var alias: String?
        var categories: [Category]?
        var coordinates: Coordinate?
        var displayPhone: String?
        var distance: Double?
        var imageUrl: String?
        var isClosed: Bool?
        var location: Location?
        var name: String?
        var phone: String?
        var price: String?
        var rating: Double?
        var reviewCount: Int?
        var url: String?
        
        enum CodingKeys: String, CodingKey {
           
            
            //properties die geändert werden müssen weil die bezeichnung nicht swift conform ist
            case displayPhone = "display_phone"
            case isClosed = "is_closed"
            case imageUrl = "image_url"
            case reviewCount = "review_count"
            
            
            //wenn properties geändert werden müssen, dann müssen trotzdem alle anderen nochmal im codingkeys enum aufgezählt werden
            case id
            case alias
            case categories
            case coordinates
            case distance
            case location
            case name
            case phone
            case price
            case rating
            case url
        }
        
    }
  • Module 09 - Styling the list

    https://github.com/dmkeks1/CitySights-App/blob/main/CitySights%20App/ContentView.swift

    struct TextHelper {
        
        static func distanceAwayText(meters: Double) -> String {
            
            if meters > 1000 {
                return "\(Int(round(meters/meters/1000))) km away"
            }
            else {
                return "\(Int(round(meters))) m away"
            }
        }
    }

    static func bedeutet, dass man keine instance erstellen muss um auf die Funktion zuzugreifen.

    ohne func müsste man erst:

    let helper = TextHelper()

    helper.distanceAwayText

    mit static func:

    TextHelper.distanceAwayText

    wird dann auch Type Method / Type function genannt.

  • Module 10 - Business Detail Using SwiftUI Sheets

    https://github.com/dmkeks1/CitySights-App/blob/main/CitySights%20App/BusinessDetailView.swift

    sheets, lists, sf symbols,

  • Challenge - Football Stats

Module 3 - Data Flow and Map November 17, 2024

  • Lesson 01 - The Observation Data Flow Pattern - MVVM

    CleanShot_2024-11-17_at_19.52.592x 2.png

    CleanShot_2024-11-17_at_19.58.252x 2.png

    CleanShot_2024-11-17_at_19.59.142x 2.png

    observation pATTERn enables how the data flows through the app

    CleanShot_2024-11-17_at_20.00.242x 2.png

  • Lesson 02 - Swift Classes November 18, 2024 and Structures Part 1

    CleanShot_2024-11-18_at_16.18.282x 2.png

    CleanShot_2024-11-18_at_16.21.552x 2.png

    CleanShot_2024-11-18_at_16.22.402x 2.png

  • Lesson 03 - Swift Classes and Structures Part 2

    Verhalten von Structs:

    creates copy and modifies copy

    CleanShot_2024-11-18_at_17.12.332x 2.png

    verhalten von class:

    referenziert nur auf instance und erstellt keine copy. ändert werte der instance

    CleanShot_2024-11-18_at_17.15.462x 2.png

    Mutability:

    // 2. Default initializers
    struct StructPerson {
        var name: String
    }
     
    var struct1 = StructPerson(name: "Dominik")
     
    class ClassPerson {
        var name: String
        
        init(name: String) {
            self.name = name
        }
    }
     
    var class1 = ClassPerson(name: "Dominik")
     
    // 3. Assignment of Structs and Classes
     
    var struct2 = struct1
    struct1.name = "Mike" // Does this statement change struct2's name property?
    print(struct2.name)
     
    var class2 = class1
    class1.name = "Mike" // Does this statement change class2's name property?
    print(class2.name)
     
     
    // 4. Mutability differences
    let struct3 = StructPerson(name: "Sara")
    let class3 = ClassPerson(name: "Sara")
     
    //struct3 = StructPerson(name: "Jen")
    //class3 = ClassPerson(name: "Jen")
     
    struct3.name = "Jen"
    class3.name = "Jen"

    CleanShot_2024-11-18_at_17.21.452x 2.png

    perform structs over classes because they perform less overhead

  • Lesson 04 - Swift Class Inheritance November 19, 2024

    CleanShot_2024-11-19_at_21.27.582x 2.png

    CleanShot_2024-11-19_at_21.28.122x 2.png

    Syntax

    class Animal {
     
    		var name: String
    		func talk() {
    		print("woop"
    		}
    init(name: String) {
      self.name = name
    		}
    }
     
    class Cat: Animal { //declaring Cat as subclass from animal
     
    }
     
    let c = Cat(name:"Bob")
    print(c.name) // has accesss to property from class Animal
    c.talk() // has accesss to method/function from class Animal

    Overriding

    class Cat: Animal { //declaring Cat as subclass from animal
     override func talk() { // overriding method from main class
       print("meow"
     }
     
    }

    CleanShot_2024-11-19_at_21.33.532x 2.png

    Super

    CleanShot_2024-11-19_at_21.36.182x 2.png

    Polymorphism

    CleanShot_2024-11-19_at_21.40.152x 2.png

  • Lesson 05 - How to implement the Observaion pattern

    Syntax Observable

    @Observable // ohter views, where this class is instanced will
    //notice when the value change
    class ViewModel { //@Observable only works with classes
      var wordDb = ["Potato", "Cat", "Sunshin", "Raindrop"]
      var currentWord = "Cactus"
      
      
      func randomizeWord() { // randomizes word and puts into currentWord
    		  currentWord = wordDb[Int.random(in: wordDb.indices)]
      }
    }

    Syntax Environment

    @main
    struct ObservationDemoApp: App {
    	@State var viewModel = ViewModel()
    	
    	var body: some Scene {
    		WindowGroup {
    			ContentView()
    				.environment(viewModel) // declaring it as an environmental
    				// will make it available for all subvies of contentview
    		
    		}
    	}
    }

    In the ContentView you need to get the reference by adding:

    @Environment(ViewModel.self) var viewModel

  • Lesson 06 - Observation Pattern For City Sights

    https://github.com/dmkeks1/CitySights-App/blob/main/CitySights%20App/BusinessModel.swift

    Important

    1. observable class needs to be created in new file.
    2. needs to be set as environment property in app entry.

    ContentView() .environment(model)

    1. needs to be declared as environment variable where it needs to appear @Environment(BusinessModel.self) var model

  • Lesson 07 - SwiftUIMaps & Picker

    Map

    https://github.com/dmkeks1/CitySights-App/blob/main/CitySights%20App/MapView.swift

    ...
    import MapKit //neede for map funcitonality
     
    ...
     
    Map() //creates map
    ...

    Picker

    https://github.com/dmkeks1/CitySights-App/blob/main/CitySights%20App/HomeView.swift

    Picker("", selection: $selectedTab) {
                    Text("List")
                        .tag(0) //legt reihenfolge fest
                    
                    Text("Map")
                        .tag(1) //legt reihenfolge fest
                }
                .pickerStyle(SegmentedPickerStyle()) // ändert den style
                // siehe screenshot

    CleanShot_2024-11-20_at_22.05.14 2.png

  • Challenge Crypto Coin November 30, 2024

    https://github.com/dmkeks1/CryptoCoin

Module 4 - Building more App Features

  • Lesson 01 - Map Markers

    https://github.com/dmkeks1/CitySights-App

    Map Markers

    To use Map markers we use Markers(). In this example we can go through our businesses with a foreach and iterate through every entry and display all the markers on the map:

    Map() {
                ForEach(model.businesses, id: \.id) { b in
                    Marker(b.name ?? "Restaurant", coordinate: CLLocationCoordinate2D(latitude: b.coordinates?.latitude ?? 0, longitude: b.coordinates?.longitude ?? 0))
                }

    Marker Selection

    To select a marker we have to set a tag with a unique ID for the selected item.

    Also we need to create a @State var to recognize changes

     
    @State var selectedBusinessId: String?
     
    Map()
    ...
     
    .tag(b.id ?? "None") //sets to None if is nil
    ...
     
    .onChange(of: selectedBusinessId) {
    ...
    }

    Displaying Business Details

    .onChange(of: selectedBusinessId) { oldValue, newValue in
                // Find matching business
                
                
                // get the first business where this is true and assign in to the constance
                let business = model.businesses.first { business in
                    business.id == selectedBusinessId
                }
                
                // if business is foujnd set it as selected one
                if business != nil {
                    model.selectedBusiness = business!
                }
  • Lesson 02 - Onboarding View

    https://github.com/dmkeks1/CitySights-App

    To create an onboarding view we can use a tabview with a special modifier and set the entry point to this view:

    TabView {
               // CODE
            }
            .tabViewStyle(.page)
            
            
            
            
     //in app entry point file:
     
     .fullScreenCover(isPresented: Binding.constant(true)) {
                        //TODO on dismiss
                    } content: {
                        OnboardingView()
                    }
  • Lesson 03 - Saving Data with AppStorage

    https://github.com/dmkeks1/CitySights-App

    Dismiss Onboarding

    There is an environment functionality for dismissing screens:

    @Environment(\.dismiss) var dismiss
     
     
    //where we want to dismiss we need to call the dismiss function
    ...
    dismiss()

    Also in the app entry we need to have a “switch” to change the ispresented

    ...
    @State var needsOnboarding = true
    ...
    .fullScreenCover(isPresented: $needsOnboarding) {
                        needsOnboarding = false
                    } content: {
                        OnboardingView()
                    }

    App Storage

    @AppStorage("onboarding") var needsOnboarding = true

    onboarding is the key which is used to store the data on the device

    if in property

    Circle()
                            .frame(width: 10)
                            .foregroundStyle(selectedViewIndex == 1 ? .white : .gray)
                            // if condition in a property
  • Lesson 04 - Location the User with CoreLocation

    CLLocationManager delegate protocol

    image 36 2.png

    To ask for permissions for location services:

    image 37 2.png

     func getUserLocation() {
            // Check if we have permission
            
            if locationManager.authorizationStatus == .authorizedWhenInUse {
                locationManager.requestLocation()
            }
            else {
            // requests permission
                locationManager.requestWhenInUseAuthorization()
            }
            
        }

    we need to set something (class) as a location manger, see

    Info

  • Lesson 06 - Downloading Images

    see:

    https://github.com/dmkeks1/CitySights-App/blob/main/CitySights%20App/ListView.swift

    if let imageUrl = b.imageUrl {
                                // Display the business image
                                AsyncImage(url: URL(string: imageUrl)!) { image in
                                    image
                                        .resizable()
                                        .frame(width:50, height:50)
                                        .scaledToFill()
                                        .clipShape(RoundedRectangle(cornerRadius: 6))
                                        .padding(.trailing, 16)
                                } placeholder: {
                                    ProgressView()
                                        .frame(width: 50, height: 50)
                                }
     
                            }
                            else {
                                Image("list-placeholder-image")
                                    .padding(.trailing, 16)
                            }

    to cache images:

  • Lesson 07 - Enhancing the Yelp Query

    https://github.com/dmkeks1/CitySights-App/tree/main

    .focused($queryBoxFocus) // only shows when textbox is focused
  • Lesson 08 - Launching other Apps URLSchemes

    https://github.com/dmkeks1/CitySights-App

    if let url = URL(string:"tel:\(business?.phone ?? "")") {
                            Link(destination: url) {
                                Text(business?.phone ?? "")
                            }
                        }
                        else {
                            Text(business?.phone ?? "")
                        }

    Settings APp:

    if model.locationAuthStatus == .denied {
                    Text("Please allow location services for this app to see sights near you")
                        .padding(.horizontal)
                    Button {
                        if let url = URL(string: UIApplication.openSettingsURLString) {
                            UIApplication.shared.open(url)
                        }
                    } label: {
                        Text("Open Privacy Settings")
                    }
                    .buttonStyle(.bordered)
     
                }

iOS Databases and Basic Figma December 2, 2024

https://www.dropbox.com/scl/fo/0y5bxp06mvnoysq8cyfjd/h?rlkey=zlhlx0iqp3ihg519kg39evl9m&dl=0

  • Lesson 01 - Databases Overview

    Local Database

    image 38 2.png

    Remote Databases

    image 39 2.png

    image 40 2.png

  • Lesson 02 - What is SwiftData

    image 41 2.png

    @Model

    @Model // needed for SwiftData
    class DataItem: Identifiable {
        var id: String
        var creationDate: Date
        
        init() {
            id = UUID().uuidString //every instance will get UUID
            creationDate = Date() // and date
        }
    }

    https://github.com/dmkeks1/swiftdata-demo/blob/main/swiftdata-demo/DataItem.swift

    modelContainer in entry point

    @main
    struct swiftdata_demoApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .modelContainer(for: DataItem.self) // if you have more datatypes you can put an array here
            }
        }
    }

    https://github.com/dmkeks1/swiftdata-demo/blob/main/swiftdata-demo/swiftdata_demoApp.swift

    @Environment Instance where we need it

     @Environment(\.modelContext) private var context

    https://github.com/dmkeks1/swiftdata-demo/blob/main/swiftdata-demo/ContentView.swift

  • Lesson 03 - SwiftData Operations - CRUD

    see add, update, create in source code

    you can turn off “autosave” with isAutosaveEnabled: **false**

    https://github.com/dmkeks1/swiftdata-demo/blob/main/swiftdata-demo/ContentView.swift

  • Lesson 04 - SwiftData Queries December 3, 2024

    image 42 2.png

    https://developer.apple.com/documentation/foundation/predicate

    Filter with @Query predicate

    image 43 2.png

    @Query(filter: \#Predicate<Person> { person in
    	person.age > 50 || person.age < 20
     
    }, sort: \Person.age, order: .reverse)  //order is adjustable

    Example for changing filters while using a picker:

    we are passing the selection to the initializer:

    import SwiftUI
    import SwiftData
     
    struct PeopleListView: View {
        
        @Environment(\.modelContext) private var context
        
        @Query(filter: \#Predicate<Person> { person in
            person.age < 20
        }, sort: \Person.name, order: .reverse)
        private var people: [Person]
        
        var body: some View {
            List(people) { p in
                
                HStack {
                    Text(p.name)
                    Spacer()
                    Text("Age: \(String(p.age))")
                }
                    .swipeActions {
                        Button("Delete") {
                            context.delete(p)
                        }
                    }
            }
        }
        
        init(filter: Filter) {
            // Based on the filter, set the predicate
            if filter == Filter.kids {
                _people = Query(filter: \#Predicate<Person> { person in
                    person.age < 20
                }, sort: \Person.age)
            }
            else if filter == Filter.adults {
                _people = Query(filter: \#Predicate<Person> { person in
                    person.age >= 20
                }, sort: \Person.age)
            }
            
        }
    }