Refactor & Debug code for PHP Web Services

I would like an experieced coder to just go over my HTTP “go to” code for implementing MySQL data services via PHP web services (LAMP).
Firebase just lack INNER JOIN

I have created an “View” that works, and will send and receive JSON to a server, and then decodes it for use in a dummy test list view, however I want someone with experience to just check my guard statements, check my returns, and debug it so that the code is as safe as it can be.

We can assume nil responses via JSON, and we can assume that my table_ fields may be null.

The completed “document” will be my go to for MySQL web services implementation

//
//  ContentView.swift
//  MySQL-WebServices-Example
//
//  Created by Alan Trundle on 09/02/2022.
//

import SwiftUI

// An Array of results:
struct arrayOfResults: Codable, Hashable {
    var results: [Result]
}

// Array of Items.
struct Result: Codable, Hashable{
    var table_id: Int
    var table_firstname: String
    var table_surname: String
}

// Example JSON with "results" key
let json = """
{
"results":[
    {
        "table_id": 1,
        "table_firstname": "Thomas",
        "table_surname": "Alwdry"
    },
    {
        "table_id": 2,
        "table_firstname": "James",
        "table_surname": "Alwdry"
    },
    {
        "table_id": 3,
        "table_firstname": "Gordon",
        "table_surname": "Alwdry"
    }
]
}
""".data(using: .utf8)!

// End of set up



struct ContentView: View {
    
    @State var results = [Result]()
    
    var body: some View {
        
        NavigationView() {
            
            List {
                Section(header: Text("Name")) {
                    
                    ForEach(results, id: \.self) { result in
                        VStack(alignment: .leading) {
                            HStack() {
                                
                                Image(systemName: "person.fill")
                                    .foregroundColor(.blue)
                                
                                Text(result.table_firstname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)
                                
                                Text(result.table_surname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)
                            }
                        }
                    }
                }
            }
            
            .navigationTitle("HTTP Demo")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                Button(action: {
                    Task {
                        results = await uploadData()!
                    }
                    
                }) {
                    Text("Click to test")
                    Image(systemName: "play.fill")
                }
            }
        }
    }
}

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

func encodeJSON(json: Data) -> Data? {
    if let encoded = try? JSONEncoder().encode(json) {
        return encoded
    }
    else {
        print ("Failed to encode data")
        return nil
    }
}

func decodeJSON(json: Data) -> [Result]? {
    
    let decoder = JSONDecoder()
    
    if let output = try? decoder.decode(arrayOfResults.self, from: json) {
        
        return output.results
    }
    else {
        // Should I return nil ?
        return nil
    }
}


func uploadData() async -> [Result]?
{
    // This URL sends back what it recieves.
    // Great for testing JSON
    let url = URL(string: "https://reqres.in/api/cupcakes")!
    var request = URLRequest(url: url)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"
    
    
    do {
        let(data, _) = try await URLSession.shared.upload(for: request, from:json)
        
        var results = [Result]()
        
        results = decodeJSON(json: data)!
        return results
        
    } catch {
        print("Upload Failed")
        // Return ?
    }
    // Should i return nil ?
    return nil
}

Thanks Alan (Novice)

@alantrundle

Hi Alan,

Welcome to the code crew community.

I’ve just had a look at your code and placed it in a test project.

I note that the 3 functions updloadData, decodeJSON and encodeJSON are global functions in this exercise. Ideally those should be in some kind of NetworkManager class or struct or perhaps a ViewModel (an ObservableObject). You may already have something in place but chose to include them in this example as global functions ??

The definition of your json data is a global variable too so I moved that inside the ContentView struct and passed that as a parameter to the uploadData function. With the 3 functions inside of their own Manager file they rely on data being passed to them rather than, in the case of the uploadData function, which was accessing the json constant declared as a global.

I am by no means an expert in coding but on the face of it, your code looks pretty good to me. It works and that’s all that matters. Everyone will have a different way of setting up an App and there is no right or wrong way in my experience.

The convention of Swift is to name properties, classes and structs using camel case rather than snake case. So if your json data is in snake case (as in this example) the convention is to add CodingKeys to translates them into camel case.

I have updated the code and placed the 3 functions inside a struct named NetworkManager.

ContentView

import SwiftUI

// An Array of results:
struct arrayOfResults: Codable, Hashable {
    var results: [Result]
}

// Array of Items.
struct Result: Codable, Hashable {
    var tableId: Int
    var tableFirstname: String
    var tableSurname: String

    enum CodingKeys: String, CodingKey {
        case tableId = "table_id"
        case tableFirstname = "table_firstname"
        case tableSurname = "table_surname"
    }
}

struct ContentView: View {

    @State var results = [Result]()

    // Example JSON with "results" key
    let json = """
    {
    "results":[
        {
            "table_id": 1,
            "table_firstname": "Thomas",
            "table_surname": "Alwdry"
        },
        {
            "table_id": 2,
            "table_firstname": "James",
            "table_surname": "Alwdry"
        },
        {
            "table_id": 3,
            "table_firstname": "Gordon",
            "table_surname": "Alwdry"
        }
    ]
    }
    """.data(using: .utf8)!


    var body: some View {

        NavigationView() {

            List {
                Section(header: Text("Name")) {

                    ForEach(results, id: \.self) { result in
                        VStack(alignment: .leading) {
                            HStack() {

                                Image(systemName: "person.fill")
                                    .foregroundColor(.blue)

                                Text(result.tableFirstname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)

                                Text(result.tableSurname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)
                            }
                        }
                    }
                }
            }

            .navigationTitle("HTTP Demo")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                Button(action: {
                    results.removeAll()
                    Task {
                        results = await NetworkManager.uploadData(json: json)!
                    }

                }) {
                    HStack {
                        Text("Click to test")
                        Image(systemName: "play.fill")
                    }
                }
            }
        }
    }
}

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

Network Manager:

import Foundation

struct NetworkManager {

    static func encodeJSON(json: Data) -> Data? {
        if let encoded = try? JSONEncoder().encode(json) {
            return encoded
        }
        else {
            print ("Failed to encode data")
            return nil
        }
    }

    static func decodeJSON(json: Data) -> [Result]? {

        let decoder = JSONDecoder()

        if let output = try? decoder.decode(arrayOfResults.self, from: json) {

            return output.results
        }
        else {
            // Should I return nil ?
            return nil
        }
    }


    static func uploadData(json: Data) async -> [Result]? {
        // This URL sends back what it recieves.
        // Great for testing JSON
        let url = URL(string: "https://reqres.in/api/cupcakes")!
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"


        do {
            let(data, _) = try await URLSession.shared.upload(for: request, from:json)

            var results = [Result]()

            results = decodeJSON(json: data)!
            return results

        } catch {
            print("Upload Failed")
            // Return ?
        }
        // Should i return nil ?
        return nil
    }
}

Let me know if there is anything you don’t understand.

Hi Chris,

Thank you very much for looking through my code.
Yes I am very much a beginner, and as you pointed out for the sake of simplicity, to get it working, I did lay it out without organisation.

I have seen Chris Chang’s lesson on Classes (Playground)

Now, I am trying to create my JSONDecoder and JSONEncoder functions as functions.
For example I wish to pass in the name of my struct [Result].self in this case as an input parameter, but no matter what I try, I can’t move past a rake of errors.

// Taken from the www
func decodeDataToModel<T:Decodable>(data : Data?,ele:T.Type) -> T? {
    guard let data = data else { return nil }
    do {
        let object = try JSONDecoder().decode(T.self, from: data)
        return object
    } catch  {
        print("Unable to decode", error)
    }
    return nil
}

Trying to call it using, but it fails miserably.

// Pass in [Result] as input parameter
if let results = decodeDataToModel(data: data, ele: Result.self) {
            return results.self
        }

I have updated my Result struct as follows

// An Array of results:
// Encodable and Decodable added to remove errors, but it's still not working
struct arrayOfResults: Encodable, Decodable, Hashable {
    var results: [Result]
}

// Array of Items.
struct Result: Encodable, Decodable, Hashable{
    var table_id: Int
    var table_firstname: String
    var table_surname: String
}

I am still very much in play ground mode at the moment.
My wish is to come up with some "go to " functions to allow me to interface my play ground with a LAMP server to add “database ability”.
I am very good with PHP, and not bad at MySQL, and can even do nesting at a push to group results for JSON encoding.

I will have a PHP script pretty much for each View, and obviously I will need to encode JSON data from my forms, and UI input parameters like text boxes etc to push JSON to my PHP scripts as well.

The uploadData() function must accept my Results.self, since as part of that function it must decode my JSON result after posting data, and return it to an Array.

I have set up a “VMware Fusion” Debian server, running PHP and MySQL on my Apple Mac with a whopping 256MB of allocated RAM.
runlevel2 and maintenance via SSH

I will get there with my camel casing, and will learn to break out my functions into Data Models, and things, but first my priority is to get stuck in play a little, then watch Chris Chang’s videos again in hope that I understand better the next time I watch it.
I am learning, and I am spending 4-5 hours every day practising.

Thank you for your time

Alan

Hi,

This is the project of many errors as it stands this evening.

Please note it’s not been laid out in anything other than this at the moment.

I am still not quite understanding ObservableObject, but I think that has something to do with live updates on the View doesn’t it?
Data changes, and so does the view.

I would fully like that to be implemented if possible?

Once the code is fixed, I think I can generally fathom how to use it (by example).

This will become one view to replicate of many !

In a nutshell this “sheet of code” will be my GOD “MySQL data model”, and it means I can do away without learning Firestore.

I do see some advantages, but still it’s way beyond my comprehension, even after watching half a dozen of experienced people on Youtube trying to explain it to me.

I can go on to say that a Web service, with a bunch of PHP scripts with api key authentication, and then an iPhone as a client running compiled Swift is pretty much the way I want to go as a coder at the moment.

//
//  ContentView.swift
//  MySQL-WebServices-Example
//
//  Created by Alan Trundle on 09/02/2022.
//

import SwiftUI

// An Array of results:
struct arrayOfResults: Encodable, Decodable, Hashable {
    var results: [Result]
}

// Array of Items.
struct Result: Encodable, Decodable, Hashable{
    var table_id: Int
    var table_firstname: String
    var table_surname: String
}

// Example JSON with "results" key
let json = """
{
"results":[
    {
        "table_id": 1,
        "table_firstname": "Thomas",
        "table_surname": "Alwdry"
    },
    {
        "table_id": 2,
        "table_firstname": "James",
        "table_surname": "Alwdry"
    },
    {
        "table_id": 3,
        "table_firstname": "Gordon",
        "table_surname": "Alwdry"
    }
]
}
""".data(using: .utf8)!

// End of set up



struct ContentView: View {
    
    @State var results = [Result]()
    
    var body: some View {
        
        NavigationView() {
            
            List {
                Section(header: Text("Name")) {
                    
                    ForEach(results, id: \.self) { result in
                        VStack(alignment: .leading) {
                            HStack() {
                                
                                Image(systemName: "person.fill")
                                    .foregroundColor(.blue)
                                
                                Text(result.table_firstname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)
                                
                                Text(result.table_surname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)
                            }
                        }
                    }
                }
            }
            
            .navigationTitle("HTTP Demo")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                Button(action: {
                    Task {
                        results = await uploadData(ele: Result.self)
                    }
                    
                }) {
                    Text("Click to test")
                    Image(systemName: "play.fill")
                }
            }
        }
    }
}

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

func encodeJSON(json: Data) -> Data? {
    if let encoded = try? JSONEncoder().encode(json) {
        return encoded
    }
    else {
        print ("Failed to encode data")
        return nil
    }
}

func decodeJSON(json: Data) -> [Result]? {
    
    let decoder = JSONDecoder()
    
    if let output = try? decoder.decode(arrayOfResults.self, from: json) {
        
        return output.results
    }
    else {
        // Should I return nil ?
        return nil
    }
}


func decodeDataToModel<T:Decodable>(data : Data?,ele:T.Type) -> T? {
    guard let data = data else { return nil }
    do {
        let object = try JSONDecoder().decode(T.self, from: data)
        return object
    } catch  {
        print("Unable to decode", error)
    }
    return nil
}


func uploadData<T:Decodable>(ele:T.Type) async -> T?
{
    // This URL sends back what it recieves.
    // Great for testing JSON
    let url = URL(string: "https://reqres.in/api/cupcakes")!
    var request = URLRequest(url: url)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"
    
    
    do {
        let(data, _) = try await URLSession.shared.upload(for: request, from:json)
        
        //var results = [Result]()
        
        //results = decodeJSON(json: data)!
        //return results
        
        if let results = decodeDataToModel(data: data, ele: Result.self) {
            return results.self
        }
        
    } catch {
        print("Upload Failed")
        // Return ?
    }
    // Should i return nil ?
    //return results
}
enum NetworkError: Error {
    case badUrl
    case invalidRequest
}

class Webservice {
    
    func uploadData2(url: URL?) async throws -> [Result]{
        
        guard let url = url else {
            throw NetworkError.badUrl
        }
        
        let (data, response) = try await URLSession.shared.data(from: url)
        
        guard (response as? HTTPURLResponse)?.statusCode == 200 else {
            throw NetworkError.invalidRequest
        }
        
        let result =  try? JSONDecoder().decode([Result].self, from: data)
        return result ?? []
    }
    
}


//
//  ContentView.swift
//  MySQL-WebServices-Example
//
//  Created by Alan Trundle on 09/02/2022.
//

import SwiftUI

// An Array of results:
struct arrayOfResults: Encodable, Decodable, Hashable {
    var results: [Result]
}

// Array of Items.
struct Result: Encodable, Decodable, Hashable{
    var table_id: Int
    var table_firstname: String
    var table_surname: String
}

// Example JSON with "results" key
let json = """
{
"results":[
    {
        "table_id": 1,
        "table_firstname": "Thomas",
        "table_surname": "Alwdry"
    },
    {
        "table_id": 2,
        "table_firstname": "James",
        "table_surname": "Alwdry"
    },
    {
        "table_id": 3,
        "table_firstname": "Gordon",
        "table_surname": "Alwdry"
    }
]
}
""".data(using: .utf8)!

// End of set up



struct ContentView: View {
    
    @State var results = [Result]()
    
    var body: some View {
        
        NavigationView() {
            
            List {
                Section(header: Text("Name")) {
                    
                    ForEach(results, id: \.self) { result in
                        VStack(alignment: .leading) {
                            HStack() {
                                
                                Image(systemName: "person.fill")
                                    .foregroundColor(.blue)
                                
                                Text(result.table_firstname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)
                                
                                Text(result.table_surname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)
                            }
                        }
                    }
                }
            }
            
            .navigationTitle("HTTP Demo")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                Button(action: {
                    Task {
                        results = await uploadData(ele: Result.self)
                    }
                    
                }) {
                    Text("Click to test")
                    Image(systemName: "play.fill")
                }
            }
        }
    }
}

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

func encodeJSON(json: Data) -> Data? {
    if let encoded = try? JSONEncoder().encode(json) {
        return encoded
    }
    else {
        print ("Failed to encode data")
        return nil
    }
}

func decodeJSON(json: Data) -> [Result]? {
    
    let decoder = JSONDecoder()
    
    if let output = try? decoder.decode(arrayOfResults.self, from: json) {
        
        return output.results
    }
    else {
        // Should I return nil ?
        return nil
    }
}


func decodeDataToModel<T:Decodable>(data : Data?,ele:T.Type) -> T? {
    guard let data = data else { return nil }
    do {
        let object = try JSONDecoder().decode(T.self, from: data)
        return object
    } catch  {
        print("Unable to decode", error)
    }
    return nil
}


func uploadData<T:Decodable>(ele:T.Type) async -> T?
{
    // This URL sends back what it recieves.
    // Great for testing JSON
    let url = URL(string: "https://reqres.in/api/cupcakes")!
    var request = URLRequest(url: url)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"
    
    
    do {
        let(data, _) = try await URLSession.shared.upload(for: request, from:json)
        
        //var results = [Result]()
        
        //results = decodeJSON(json: data)!
        //return results
        
        if let results = decodeDataToModel(data: data, ele: Result.self) {
            return results.self
        }
        
    } catch {
        print("Upload Failed")
        // Return ?
    }
    // Should i return nil ?
    //return results
}
enum NetworkError: Error {
    case badUrl
    case invalidRequest
}

class Webservice {
    
    func uploadData2(url: URL?) async throws -> [Result]{
        
        guard let url = url else {
            throw NetworkError.badUrl
        }
        
        let (data, response) = try await URLSession.shared.data(from: url)
        
        guard (response as? HTTPURLResponse)?.statusCode == 200 else {
            throw NetworkError.invalidRequest
        }
        
        let result =  try? JSONDecoder().decode([Result].self, from: data)
        return result ?? []
    }
    
}


Since your json is fundamentally a dictionary you have to specify the correct type when you are using Generics.

Your function signature does not need to have anything passed to it that describes the Type. That type is inferred, ie In the Task where you had:

Task {
    results = await uploadData(ele: Result.self)
}

This is how you should do that.

Task {
    if let result: ArrayOfResults = await uploadData() {
        results = result.results
    }
}

the inference is stated by the Type after the property if let result: ArrayOfResults = ....... which means you do not need to pass the type as a parameter. That’s how generics works and I should point out that I am by no means an expert in generics.

In your uploadData function you have the line of code:

if let results = decodeDataToModel(data: data, ele: Result.self) {
    return results.self
}

That only needs to be:

if let result: T = decodeDataToModel(data: data) {
    return result
}

which means that the function signature of decodeDataToModel only needs the one parameter which is data.

So the overall code should look like this:

import SwiftUI

// An Array of results:
struct ArrayOfResults: Encodable, Decodable, Hashable {
    var results: [Result]
}

// Array of Items.
struct Result: Encodable, Decodable, Hashable{
    var table_id: Int
    var table_firstname: String
    var table_surname: String
}

// Example JSON with "results" key
let json = """
{
"results":[
    {
        "table_id": 1,
        "table_firstname": "Thomas",
        "table_surname": "Alwdry"
    },
    {
        "table_id": 2,
        "table_firstname": "James",
        "table_surname": "Alwdry"
    },
    {
        "table_id": 3,
        "table_firstname": "Gordon",
        "table_surname": "Alwdry"
    }
]
}
""".data(using: .utf8)!

// End of set up



struct ContentView: View {

    @State var results = [Result]()

    var body: some View {

        NavigationView() {

            List {
                Section(header: Text("Name")) {

                    ForEach(results, id: \.self) { result in
                        VStack(alignment: .leading) {
                            HStack() {

                                Image(systemName: "person.fill")
                                    .foregroundColor(.blue)

                                Text(result.table_firstname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)

                                Text(result.table_surname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)
                            }
                        }
                    }
                }
            }

            .navigationTitle("HTTP Demo")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                Button(action: {
                    Task {
                        if let result: ArrayOfResults = await uploadData() {
                            results = result.results
                        }
                    }

                }) {
                    Text("Click to test")
                    Image(systemName: "play.fill")
                }
            }
        }
    }
}

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

func encodeJSON(json: Data) -> Data? {
    if let encoded = try? JSONEncoder().encode(json) {
        return encoded
    }
    else {
        print ("Failed to encode data")
        return nil
    }
}

func decodeJSON(json: Data) -> [Result]? {

    let decoder = JSONDecoder()

    if let output = try? decoder.decode(ArrayOfResults.self, from: json) {

        return output.results
    }
    else {
        // Should I return nil ?
        return nil
    }
}


func decodeDataToModel<T:Decodable>(data : Data?) -> T? {
    guard let data = data else { return nil }
    do {
        let object = try JSONDecoder().decode(T.self, from: data)
        return object
    } catch  {
        print("Unable to decode", error)
    }
    return nil
}


func uploadData<T:Decodable>() async -> T? {
    // This URL sends back what it recieves.
    // Great for testing JSON
    let url = URL(string: "https://reqres.in/api/cupcakes")!
    var request = URLRequest(url: url)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpMethod = "POST"


    do {
        let(data, _) = try await URLSession.shared.upload(for: request, from:json)

        if let result: T = decodeDataToModel(data: data) {
            return result
        }

    } catch {
        print("Upload Failed")
        // Return ?
    }
    // Should i return nil ?
    return nil
}

enum NetworkError: Error {
    case badUrl
    case invalidRequest
}

class Webservice {

    func uploadData2(url: URL?) async throws -> [Result]{

        guard let url = url else {
            throw NetworkError.badUrl
        }

        let (data, response) = try await URLSession.shared.data(from: url)

        guard (response as? HTTPURLResponse)?.statusCode == 200 else {
            throw NetworkError.invalidRequest
        }

        let result =  try? JSONDecoder().decode([Result].self, from: data)
        return result ?? []
    }

}
1 Like

Hi Chris,

Thank you for your help on this.
Using your prompts, and advice, I think it’s very nearly there.

Just two questions before I leave you in peace.

  1. How would I implement an ObservableObject?

  2. Surely I would want to iterate through the arrayOfResults dictionary, and not the resultsArr as I am at the moment ?

  • I cannot seem to get my ForEach to iterate over “arrayOfResults.results”
import SwiftUI

// Global declarations for testing


// How do I implement an Observerable Object ? and is it needed?
// Receive JSON
struct arrayOfResults: Codable, Hashable {
    var results: [resultsArr]
}

// Array of json keys to map "results" to
struct resultsArr: Codable, Hashable{
    var table_id: Int
    var table_firstname: String
    var table_surname: String
}




// Send JSON
struct arrayOfResults2: Codable, Hashable {
    var results: [resultsArr2]
}

// Array of json keys to map "results" to
struct resultsArr2: Codable, Hashable{
    var table_id: Int
    var table_firstname: String
    var table_surname: String
}



// data to test JSONEncoder
let dataToEncode = arrayOfResults2(results: [
    resultsArr2(table_id: 1, table_firstname: "George", table_surname: "Cooper"),
    resultsArr2(table_id: 2, table_firstname: "Paul", table_surname: "Smith"),
    resultsArr2(table_id: 3, table_firstname: "Alex", table_surname: "Peters")
])

// End of set up



struct ContentView: View {
    // Surely I want to interate through the arrayOfResults dictionary, and not my resultsArr ???
    // Can't seem to get it working with resultsArr
    @State var results = [resultsArr]()


    var body: some View {
        
        // New instance of Webserver class
        let a = Webservice()

        NavigationView() {

            List {
                Section(header: Text("Name")) {
                 // Surely I want to interate through the arrayOfResults dictionary, and not my resultsArr ???
                    
                    ForEach(results, id: \.self) { result in
                        VStack(alignment: .leading) {
                            HStack() {

                                Image(systemName: "person.fill")
                                    .foregroundColor(.blue)

                                Text(result.table_firstname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)

                                Text(result.table_surname)
                                    .font(.system (size: 15))
                                    .foregroundColor(.red)
                            }
                        }
                    }
                }
            }

            .navigationTitle("HTTP Demo")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                Button(action: {
                    Task {
                        // I can now send data to my uploadData Webserver class, and store back the results
                        if let result: arrayOfResults = await a.uploadData(json: dataToEncode, url: "https://reqres.in/api/cupcakes")! {
                            results = result.results
                        }
                    }

                }) {
                    Text("Click to test")
                    Image(systemName: "play.fill")
                }
            }
        }
    }
}

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




class Webservice {

    func uploadData<T:Codable, A:Codable>(json: A, url: String) async -> T? {
        // This URL sends back what it recieves.
        // Great for testing JSON
        let url = URL(string: url.self)!
        
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        
        // Try to encode JSON data to send to my Webserver
        let encoder = JSONEncoder()
        let resultJSONData = try! encoder.encode(json)
        print(String(data: resultJSONData, encoding: .utf8)!)


        do {
            let(data, _) = try await URLSession.shared.upload(for: request, from: resultJSONData)

            if let result: T = decodeDataToModel(data: data) {
                return result
            }

        } catch {
            print("Upload Failed")
            return nil
        }
        // Should i return nil ?
        return nil
    }

    enum NetworkError: Error {
        case badUrl
        case invalidRequest
    }
    
    func decodeDataToModel<T:Codable>(data : Data?) -> T? {
        guard let data = data else { return nil }
        do {
            let object = try JSONDecoder().decode(T.self, from: data)
            return object
        } catch  {
            print("Unable to decode", error)
        }
        return nil
    }

}

also can I have an explanation of what is going on here please as I think I have misunderstood.

Is arrayOfResults an array of resultsArr objects?

I am iterating through resultsArr when printing out my list on my main view.
Surely I would want to iterate through results in the master array of arrayOfResults?

I cannot work out how to do this.

// Receive JSON
struct arrayOfResults: Codable, Hashable {
    var results: [resultsArr]
}

// Array of json keys to map "results" to
struct resultsArr: Codable, Hashable{
    var table_id: Int
    var table_firstname: String
    var table_surname: String
}

Thanks

Alan

Hi Chris,

Well I got home after a busy night delivering Chinese takeaway, and in the last 3 hours have managed to find myself a VPS with 512MB memory, running Debian 11, and a free domain for £2.00 a month.
Bargain! - Equates to probably 2.80USD a month ~

So I have registered myswift.co.uk, and have install Apache2, PHP and MySQL, and have set up a self-signed SSL certificate (not much good), and have installed media wiki, and phpmyadmin, and done myself a basic boiler plate.

Tomorrow I will be tightening down my wrappers, and will be introducing iptables.

At least I can do away with my A4 notepad, and ball point pen :slight_smile:

ionos

Cheap, and well its cheap.

My needs are small right now, and very much still in “single user mode”, so this “$” VPS works a treat for me.

If you can brave trusting my SSL certificate, then please look me up

https://www.myswift.co.uk/wiki

I am yet to find free wildcard SSL certificates for educational use.
I guess root certs cost mega money.

It’s a step forward because I can test my Apps on 4G, and it will free up space on my Mac.
The wiki, called Notepad will be just that. A notepad filled with Go to code.

Regards

Alan

Hi,

Well I have spent the afternoon studying videos on Published vars in classes, and then calling ObservedObject & @StateObject to call the class in a View to ensure that my variable data, ie data that is subject to change gets published to my View, and then using the StateObject to init my function “View1” and then using ObservedObject in other subviews to ensure that data changes within my class variables get published to my Views.

Its been a challenge, however I sort of know where I am going, and will now look to study Arrays.

//
//  ContentView.swift
//  ObservableObject-Example
//
//  Created by Alan Trundle on 13/02/2022.
//

import SwiftUI

// Custom data type
struct FruitModel: Identifiable {
    let id: String = UUID().uuidString
    let name: String
    let count: Int
    let image: String
}

class FruitViewModel: ObservableObject {
    
    // These are objects which contain data that changes.
    // use @Published to push refreshed data to the view.
    // use @Published in a class, and then refrence the class using
    // @StateObject or @ObservedObject
    // use in view ->  @ObservedObject var fruitViewModel: FruitViewModel = FruitViewModel()
    @Published var fruitArray: [FruitModel] = []
    @Published var isLoading: Bool = false
    
    init() {
        getFruits()
    }
    
    func getFruits() {
        let fruit1 = FruitModel(name: "Orange", count: 1, image: "http://myswift.co.uk/fruitapp/orange_fruit.png")
        let fruit2 = FruitModel(name: "Banana", count: 2, image: "http://myswift.co.uk/fruitapp/banana_fruit.png")
        let fruit3 = FruitModel(name: "Watermelon", count: 88, image: "http://myswift.co.uk/fruitapp/watermelon_fruit.png")
        
        // Add delay to mimic database
        isLoading = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
            
            self.fruitArray.append(fruit1)
            self.fruitArray.append(fruit2)
            self.fruitArray.append(fruit3)
            self.isLoading = false
        }
    }
    
    func deleteFruit(index: IndexSet) {
        fruitArray.remove(atOffsets: index)
    }
}


struct ContentView: View {
    
    // Needed for real time view updates when using classes.
    // @StateObject -> USE THIS ON CREATION / INIT
    // @ObservedObject - > USE FOR SUBVIEWS
    @StateObject var fruitViewModel: FruitViewModel = FruitViewModel()
    
    
    var body: some View {
        NavigationView {
            List {
                
                if fruitViewModel.isLoading {
                    ProgressView()
                } else {
                    
                    ForEach(fruitViewModel.fruitArray) { fruit in
                        
                        NavigationLink(destination: SecondScreen(fruitViewModel: fruitViewModel, index: fruit.id)) {
                            
                            HStack {
                                Text("\(fruit.count)")
                                    .foregroundColor(.red)
                                Text(fruit.name)
                                    .font(.headline)
                                    .bold()
                            }
                        }
                    }
                    .onDelete(perform: fruitViewModel.deleteFruit)
                    
                }
            }
            .listStyle(GroupedListStyle())
            .navigationTitle("Fruit List")
        }
    }
}

struct SecondScreen: View {
    
    @StateObject var fruitViewModel: FruitViewModel = FruitViewModel()
    var index: String
    
    var body: some View {
        if let myFruit = fruitViewModel.fruitArray.firstIndex(where: {$0.id == index}) {
            
            
            VStack(alignment: .leading) {
                HStack() {
                    AsyncImage(url: URL(string:fruitViewModel.fruitArray[myFruit].image)) { image in
                        image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(width:40, height:40)
                            
                            
                    } placeholder: {
                        ProgressView()
                    }

                    Text(fruitViewModel.fruitArray[myFruit].name)
                        .font(.headline)
                        .fontWeight(.semibold)
                    
                    Text(String(fruitViewModel.fruitArray[myFruit].count))
                        .foregroundColor(.red)
                        .font(.headline)
                        .fontWeight(.semibold)
                    
                    Spacer()
                
                }
                .padding(.horizontal)
                
                .navigationTitle("My Selected Fruit")
            }
            
            Spacer()   
        }
        
        
        
    }
}


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

You will need to set Allow Arbitrary Loads (YES) in App Transport Security under Custom IOS Target Properties for the images to load since I haven’t got a trusted SSL certificate.

1-2-3reg are selling Wildcards for £60 a year.
Not bad

Thanks

Alan

Hi Chris Parker,

Okay, so I spent the weekend researching classes, observable objects, and well I now have a slightly better understanding.
Progress is moderate, but I am getting there.

Lots of YouTube.

I’m trying to create a class of “functions” to build JSON from one dictionary, and then in this case “copy” it to another, and then spew it out to the view in a ForEach, but seeing a few errors.

For the sake of example I am running the whole lot in my class init().

I was wondering if you could review, and update it pretty please?

I will say I am a bit confused at this level types and dictionaries, and how to pass them and receive data back, but pretty much put this code together without any googling, so my ability to syntax has improved enormously.

Newbie to rookie!

t//
//  ContentView.swift
//  Array-Example
//
//  Created by Alan Trundle on 14/02/2022.
//

import SwiftUI

// Test Array  - Alan in playground playing tag.
struct arrDataToTx: Codable {
    var id: Int
    var firstName: String
    var surName: String
}

struct arrDataToRX: Codable, Hashable {
    var id: Int
    var firstName: String
    var surName: String
}

class dataModel: ObservableObject {
    
    
    @Published var myTestArrayTX: [arrDataToTx] = []
    @Published var myTestArrayRX: [arrDataToRX] = []
    
    init() {
        // Dummy data
        let a = arrDataToTx(id: 1, firstName: "Alan", surName: "Trundle")
        let b = arrDataToTx(id: 2, firstName: "Malcolm", surName: "Trundle")
        let c = arrDataToTx(id: 3, firstName: "Gilly", surName: "Trundle")
        
        // Easy as 1,2,3
        myTestArrayTX.append(a)
        myTestArrayTX.append(b)
        myTestArrayTX.append(c)
        
        // Saves buttons & .onAppear code for sake of example
        // runs when you init the class in the view.
        // Encode my Array of data
        let resultTX: arrDataToTx = encodeJSON(object: myTestArrayTX)
        
        // print it for testing.
        print(String(data: resultTX, encoding: .utf8)!)
        
        // Try to decode the JSON Array so that it can be printed to the View
        // Try to pass the resultTX JSON data, and save it in a new array/dictionary instance called myTextArrayRX
        let result: arrDataToRX = decodeJSON(data: resultTX) {
            myTestArrayRX = result
        }
        
        
        
        
        func encodeJSON<T: Codable>(object: T) -> T? {
            // Try to encode JSON data to send to my Webserver
            // Will do, do, big doggy do do, guard etc
            let encoder = JSONEncoder()
            let resultJSONData = try! encoder.encode(object)
            //print(String(data: resultJSONData, encoding: .utf8)!)
            return resultJSONData
        }
        
        func decodeJSON<T:Codable>(data : Data) -> T? {
            guard let data: T else { return nil }
            do {
                let object = try JSONDecoder().decode(T.self, from: data)
                return object
            } catch  {
                print("Unable to decode", error)
            }
            return nil
        }
        
    }


    
    struct ContentView: View {
        
        @StateObject var dataViewModel: dataModel = dataModel()
        
        var body: some View {
            // Spit out my myTestArrayRX
            // should run because the code which handles the encode and decode functions is run inside my class init()
            ForEach(dataViewModel.myTestArrayRX, id: \.self) { item in
                Text(item.firstName)
            }
            .padding()
        }
    }
    
    
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

Many Thanks

Alan

@alantrundle
Hi Alan,

Was your post on Facebook earlier referring to this post above?

You said on FaceBook: “I’ve just updated my post, and just require one more fix, almost there with it.”

Hi Chris,

Yes the code above is the code that needs fixing.

I’ve created an example to better understand how to encode json from a dictionary and then to create another from decoded json.

I’m seeing a few errors which I can’t seem to fix.

Thanks

Alan

Yeah I’ve just got that working. Bit of stuffing around.

You can’t do that. The encoding process generates an object of type Data so your encoding function needs to return an Optional Data.
ie code should be:

    func encodeJSON<T: Codable>(object: T) -> Data? {
        // Try to encode JSON data to send to my Webserver
        // Will do, do, big doggy do do, guard etc
        let encoder = JSONEncoder()
        let resultJSONData = try! encoder.encode(object)
        //print(String(data: resultJSONData, encoding: .utf8)!)
        return resultJSONData
    }

which means the calling code now needs to be:

let returnedData = encodeJSON(object: myTestArrayTX)

I changed the name to be returnedData just to be clear.

Your decodeJSON function works well after completely removing the guard statement but your calling code needed some work.

What you are returning is an array of Type arrDataToRX or [arrDataToRX]. Because the returnedData in the encoding process is Optional, that needs to force unwrapped when passed in as the data parameter to the decoding process.

Since you have specified that the decoding process return could be nil which means that the return type is Optional, you need to unwrap the result.

So the call site code should be:

if let result: [arrDataToRX] = decodeJSON(data: returnedData!) {
    myTestArrayRX = result
}

The sample code encodes and decodes the data in a split second so in order to make it look like there was a “slow Network” I used a 5 second delay before invoking the decoding process:

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    if let result: [arrDataToRX] = self.decodeJSON(data: returnedData!) {
        self.myTestArrayRX = result
    }
}

Then in the ContentView I changed it a little to incorporate a ProgressView if the returned array was empty pending the “slow Network” finally returning the decoded data.

struct ContentView: View {
    
    @StateObject var dataViewModel: dataModel = dataModel()
    
    var body: some View {
        // Spit out my myTestArrayRX
        // should run because the code which handles the encode and decode functions is run inside my class init()
        NavigationView {
            VStack(alignment: .leading) {
                if dataViewModel.myTestArrayRX.isEmpty {
                    ProgressView()
                } else {
                    Text("Decoded Data")
                        .font(.title)
                        .padding(.horizontal)
                    List {
                        ForEach(dataViewModel.myTestArrayRX, id: \.self) { item in
                            Text(item.firstName)
                        }
                    }
                    .listStyle(PlainListStyle())
                }
            }
            .navigationTitle("JSON send/receive")
        }
        
    }
}

Anyway, over to you.
Time for me to get some sleep.

Hi Chris,

Thank you so much for all your help!
wish I could buy you a beer :slight_smile:

I have managed with some googling to get my functions to work in my classes, and as you can see I have managed so far to fathom StateObjects and ObservedObject, and have factored in classes.

I have to admit Chris Ching has helped loads with the explanations, and the practice is starting to pay off.
I am beginning to code Swift without having to google everything.

good news.

I have my Go to class, which will send JSON, and receive JSON, so under normal circumstances I know enough to be able to create my arrays, and call them with State in a global sense, or to publish them, and Observe them if placed in a class.

I’m not sure how I will reference my arrays yet.
I will have some, to store form data, and others to expect results from MySQL via JSON and my web services (arrays wise).

The class you helped with me is the platform for my Web Services.
You will note, I have included your name in the code, and I have done it for respectful reasons.

Fantastic - Thank you ever so much.

//
//  ContentView.swift
//  Array-Example
//
//  Created by Alan Trundle on 14/02/2022.
//  Modified by Chris Parker on 15/02/2022 - Code Fixes
//

import SwiftUI

// set up my Arrays
// One Array for data to send to my web service
struct arrDataToSend: Codable {
    var id: Int
    var firstName: String
    var surName: String
    var avatar: String
}

// Second Array for storing data receieved from my web service.
struct arrDataRecv: Codable, Hashable {
    var id: Int
    var firstName: String
    var surName: String
    var avatar: String
}


class webServices: ObservableObject  {
    
    // Call this in Views to set up a new instance of this class.
    // @ObservableObject var dataViewModel: webServices = webServices() -> SUBVIEWS
    // @StateObject var dataViewModel: webServices = webServices() -> MAIN VIEW
    
    // This isn't currently updating any views, so make it a variable that can be written to.
    // It is being referenced in my view, so I can't make it private.
    var myTestArrayTX: [arrDataToSend] = []
    
    // Publish a copy of the arrDataRecv struct so that it can update my View
    @Published var myTestArrayRX: [arrDataRecv] = []
    
    init() {
        // Dummy data
        // Pretend its from a shopping basket array, or made from form data, or something else.
        let a = arrDataToSend(id: 1, firstName: "Alan", surName: "Trundle", avatar: "http://myswift.co.uk/familyapp/bartSimpson_135x135.png")
        let b = arrDataToSend(id: 2, firstName: "Malcolm", surName: "Trundle", avatar: "http://myswift.co.uk/familyapp/homerSimpson_135x135.png")
        let c = arrDataToSend(id: 3, firstName: "Gilly", surName: "Trundle", avatar: "http://myswift.co.uk/familyapp/margeSimpson_135x135.png")
        
        // Easy as 1,2,3
        myTestArrayTX.append(a)
        myTestArrayTX.append(b)
        myTestArrayTX.append(c)
        
        // Use my Web Services Upload function to send JSON to my server, and receive JSON data back
        // Task() {
        //    if let result: [arrDataToRX]  = await uploadData(json: myTestArrayTX, url: "https://reqres.in/api/cupcakes")! {
        //        self.myTestArrayRX = [result]
        //    }
        // }
        
        // Encoding JSON
        // Call this to encodeJSON:
        // let returnedData = encodeJSON(object: myTestArrayTX)!
        
        // Decoding JSON
        // if let result: [arrDataToRX] = decodeJSON(data: returnedData!) {
        // myTestArrayRX = result
        // }
        
        // Typical 1 Second Delay routine
        // Example shows decoding JSON
        // DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        //    if let result: [arrDataToRX] = decodeJSON(data: returnedData) {
        //        self.myTestArrayRX = result
        //    }
        // }
        
        // Good print statement for printing JSON / Data
        // print(String(data: returnedData, encoding: .utf8)!)
    }
    
    // Functions
    func encodeJSON<T: Codable>(object: T) -> Data? {
        // Try to encode JSON data to send to my Webserver
        // Will do, do, big doggy do do, guard etc
        let encoder = JSONEncoder()
        let resultJSONData = try! encoder.encode(object)
        //print(String(data: resultJSONData, encoding: .utf8)!)
        return resultJSONData
    }
    
    func decodeJSON<T:Codable>(data : Data?) -> T? {
        guard let data = data else { return nil }
        do {
            let object = try JSONDecoder().decode(T.self, from: data)
            
            return object
        } catch  {
            print("Unable to decode", error)
        }
        return nil
    }
    
    func uploadData<T:Codable, A:Codable>(json: A, url: String) async -> T? {
        // This URL sends back what it recieves.
        // Great for testing JSON
        let url = URL(string: url.self)!
        
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        
        // Try to encode JSON data to send to my Webserver
        let resultJSONData = encodeJSON(object: json)!
        
        do {
            let(dataFromServer, _) = try await URLSession.shared.upload(for: request, from: resultJSONData)
            
            if let result: T = decodeJSON(data: dataFromServer) {
                return result
            }
        } catch {
            print("Upload Failed")
            return nil
        }
        // Should i return nil ?
        return nil
    }
    
    enum NetworkError: Error {
        case badUrl
        case invalidRequest
    }
}

struct ContentView: View {
    
    @StateObject var dataViewModel: webServices = webServices()
    
    var body: some View {
        
        NavigationView {
            VStack(alignment: .leading) {
                if dataViewModel.myTestArrayRX.isEmpty {
                    ProgressView()
                } else {
                    Text("Family Members")
                        .font(.title)
                        .padding(.horizontal)
                    List {
                        ForEach(dataViewModel.myTestArrayRX, id: \.self) { item in
                            
                            NavigationLink(destination: AvatarScreen(dataViewModel: dataViewModel, index: item.id)) {
                                
                                HStack() {
                                    Image(systemName: "person.fill")
                                        .foregroundColor(.blue)
                                    Text(item.firstName + " " + item.surName)
                                        .foregroundColor(.blue)
                                    Spacer()
                                    
                                    
                                }
                            }
                            
                            
                            
                            
                        }
                    }
                    .listStyle(PlainListStyle())
                    
                }
            }
            .navigationTitle("WS Example")
            .navigationBarTitleDisplayMode(.inline)
            .onAppear {
                Task {
                    // I can now send data to my uploadData Webserver class, and store back the results
                    // This URL sends back what it recieves.
                    if let result: [arrDataRecv] = await dataViewModel.uploadData(json: dataViewModel.myTestArrayTX, url: "https://reqres.in/api/cupcakes")! {
                        dataViewModel.myTestArrayRX = result
                    }
                }
            }
        }
    }
}

struct AvatarScreen: View {
    
    @Environment(\.presentationMode) var presentation
    
    @ObservedObject var dataViewModel: webServices = webServices()
    var index: Int
    
    var body: some View {
   
        if let myArrItem = dataViewModel.myTestArrayRX.firstIndex(where: {$0.id == index}) {
            ZStack {
                Color.pink
                    .ignoresSafeArea()
                
                VStack {
                    HStack {
                        Text(dataViewModel.myTestArrayRX[myArrItem].firstName + "'s Avatar")
                            .font(.title2)
                            .foregroundColor(.white)
                            .padding([.top, .leading, .bottom])
                        
                        Spacer()
                    }
                    
                    AsyncImage(url: URL(string:dataViewModel.myTestArrayRX[myArrItem].avatar)) { image in
                        image
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                            .frame(width:200, height:200)
                        
                        
                    } placeholder: {
                        ProgressView()
                    }
                    
                    
                    Spacer()
                    
                        .navigationTitle("My Avatar")
                        .navigationBarBackButtonHidden(true)
                                        .navigationBarItems(leading: Button(action : {
                                            self.presentation.wrappedValue.dismiss()
                                        }){
                                            Image(systemName: "arrow.left")
                                                .foregroundColor(Color.white)
                                        })
                }
            }
        }
    }
}




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



run it, It works perfect.

Best Regards

Alan

Cheers Alan.

Thanks for the shout out. :+1: