How do you send data from one class to another in SwiftUI? - ios

I currently have two classes in my app.
One class stores important values in variables (ex. size, color, width) that correspond to user inputs.
Another class initiates a URLRequest to an API in order to fetch data.
My goal is to make a parameter in the URL change depending on the values stored in the variable of the first class.
For instance, the URL looks like this: "www.google.com/docs/(VAR)"
I made VAR into a variable in the class with an API request and I'm trying to mutate it with conditional statements using variables from the first class.
Unfortunately, this is giving me an error and I am unable to transfer data between two classes.
Any suggestions?
Here is the code for the first class:
import Foundation
class Product: ObservableObject, CustomStringConvertible {
#Published var color = ""
#Published var size = ""
#Published var finish = ""
#Published var cut = ""
#Published var length = ""
#Published var type = ""
var description: String {
"\(size) - \(finish) - \(cut) -\(length)"
}
}
Here is the code for the APIRequest
import Foundation
class APIRequest: ObservableObject {
#Published
var lengthOptions: [String] = []
init () {
fetchLengths { lengths in
self.lengthOptions = lengths[0].OptionList.map { $0.OptionName }
}
}
private func fetchLengths (completion: #escaping ([OptionsHead]) -> ()) {
let catalogID = 1877
// how can I change catalogID based on Product class variables
guard let url = URL(string: "blablaAPI.com/Products/\(catalogID)/Options?limit=1&offset=3")
else {
fatalError("URL is not correct")
}
var request = URLRequest(url: url)
request.addValue("12345", forHTTPHeaderField: "PrivateKey")
request.addValue("https://www.blaaaaa.com", forHTTPHeaderField: "SecureURL")
request.addValue("12345", forHTTPHeaderField: "Token")
request.httpMethod = "GET" // "POST", "PUT", "DELE"
URLSession.shared.dataTask(with: request) { data, _, _ in
let outputs = try!
JSONDecoder().decode([OptionsHead].self, from: data!)
DispatchQueue.main.async {
completion(outputs)
}
}.resume()
}
}

you could try passing the Product into you APIRequest class,
to change the catalogID based on Product class variables, like this:
class APIRequest: ObservableObject {
#Published var lengthOptions: [String] = []
#ObservedObject var product: Product
init (product: Product) {
self.product = product
fetchLengths { lengths in
self.lengthOptions = lengths[0].OptionList.map { $0.OptionName }
}
}
private func fetchLengths (completion: #escaping ([OptionsHead]) -> ()) {
var catalogID = 1877
// how can I change catalogID based on Product class variables
if product.type == "some type" {
catalogID = 1234
} else {
catalogID = 4321
}
....
}

Related

How to add #Published property wrapper to class member in Swift [duplicate]

I am new to Swift and am having a spot of bother with a decodable class.
I am receiving an error on this class : Type 'VoucherCode' does not conform to protocol 'Decodable'
I have exactly the same syntax in another project without error which came from a tutorial.
Without the published line, the class works and is decoded from relevant json.
What am I missing please?
import Foundation
class VoucherCode: Decodable, Identifiable, ObservableObject {
#Published var logoData: Data?
var id:UUID?
var title: String?
var voucherCode: String?
var details: String?
var logo: String?
var url: String?
var termsAndConditions: String?
var highlight: String?
var whoFor:[String]?
func getLogoData() {
guard logo != nil else {
return
}
if let url = URL(string: logo!) {
let session = URLSession.shared
let dataTask = session.dataTask(with: url) { (data, response, error) in
if error == nil {
DispatchQueue.main.async {
self.logoData = data!
}
}
}
dataTask.resume()
}
}
}
A similar class (which works) from a CodeWithChris lesson. There is no error and it works.
add this to your class:
private enum CodingKeys: String, CodingKey {
case id, title, voucherCode, details, logo, url, termsAndConditions, highlight, whoFor
}
this will exclude logoData from the decodable and make VoucherCode Decodable.

Refresh value in text label from JSON, with Xcode

I am learning Swift to develop macOS applications and I ran into a problem. I am trying to get certain data from a JSON from the internet. I have managed to get such data and put it in simple text labels in the view but when I run Xcode and get the values, if the values from the JSON get updated, I can't see it reflected in my app. I know that I must perform a function to refresh the data but what I have always found is the function to refresh the data that is in a table, not a simple text label.
Regardless of this problem, if I wanted to add a table with 3 columns (each structure has 3 data, at least) with the values from the JSON. When I run the refresh of the table, I should include in the function the function that gets the data from the internet, right? I'm a bit lost with this too.
This is what I have:
ViewController.swift
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate {
let user_items = UserItems()
#IBOutlet var item_salida: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
let struc_item = user_items.Item_Struct()
let position = struc_item.firstIndex(where: { $0.name == "Leanne Graham" })!
print(struc_item[position].state!)
item_salida.stringValue = struc_item[position].state!
} }
Struct.swift
import Foundation
import SwiftyJSON
// MARK: - Dato
struct User: Codable {
var name: String?
var username: String?
var email: String?
}
typealias Datos = [User]
class UserItems {
func Item_Struct() -> Datos {
let urlString = "https://jsonplaceholder.typicode.com/users"
var items_available: [User] = []
if let url = NSURL(string: urlString){
if let data = try? NSData(contentsOf: url as URL, options: []){
let items = try! JSONDecoder().decode([User].self, from: data as Data)
for item in items {
items_available.append(item)
}
}
}
return items_available
}
}
Thanks, a lot!
First of all - as you are learning Swift - please stop using snake_case variable names and also the NS.. classes NSURL and NSData.
Never load data from a remote URL with synchronous Data(contentsOf. It blocks the thread.
You need URLSession and an asynchronous completion handler.
// MARK: - Dato
struct User: Codable {
let name: String
let username: String
let email: String
}
typealias Datos = [User]
class UserItems {
func loadData(completion: #escaping (Datos) -> Void) {
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error { print(error); return }
do {
let items = try JSONDecoder().decode([User].self, from: data!)
completion(items)
} catch {
print(error)
}
}.resume()
}
}
And use it in the controller
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet var itemSalida: NSTextField!
let userItems = UserItems()
override func viewDidLoad() {
super.viewDidLoad()
userItems.loadData { users in
if let position = users.firstIndex(where: { $0.name == "Leanne Graham" }) {
DispatchQueue.main.async {
print(users[position].username)
self.itemSalida.stringValue = users[position].username
}
}
}
}
}
And forget SwiftyJSON. It's not needed anymore in favor of Codable.

Cannot assign value, SwiftUI fetch API

I wanted to grab data from exchangeratesapi.io, but I have been struggling with modeling my data.
it says "Cannot assign value of type 'rates' to type rates.Type"
I have no idea what I did nor have any visualization , if there's any reference please do comment below.
Here's my class
class MoneyView:ObservableObject {
#Published var currency = rates.self//[rates]()
init() {
fetchData()
}
func fetchData() {
guard let url = URL(string: "http://api.exchangeratesapi.io/v1/latest?access_key=24a5ab7688a7044f60bfeb491eb37550") else {
return
}
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) {(data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do{
let result = try decoder.decode(rates.self, from: safeData)
DispatchQueue.main.async {
self.currency = result // here's the error
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
Here's the rates type :
struct rates: Decodable{
// USD CAD IDR GBP CHF SGD INR MYR JPY KRW
var USD:Int
var CAD:Int
var IDR:Int
var GBP:Int
var CHF:Int
var SGD:Int
var INR:Int
var MYR:Int
var JPY:Int
var KWR:Int
}
in case you guys wonder how the API looks like
{
"success":true,
"timestamp":1620597364,
"base":"EUR",
"date":"2021-05-09",
"rates":{
"AED":4.469059,
"AFN":93.55172,
"ALL":122.991702,
"AMD":629.683505,
"ANG":2.167635,
"AOA":795.883245,
}
}
Change
#Published var currency:Rate?
struct Root: Decodable{
var rates:Rate
}
struct Rate: Decodable{
var USD:Int
var CAD:Int
var IDR:Int
var GBP:Int
var CHF:Int
var SGD:Int
var INR:Int
var MYR:Int
var JPY:Int
var KWR:Int
}
let result = try decoder.decode(Root.self, from: safeData)
currency = result.rates
Change class start letter to capital Rates

Assign JSON Response to Model SwiftUI

I have successfully parsed JSON by creating a Model. But now I want a View Model which talks to View and the View updates whenever the Model Updates. But I m having hard time initialising the model.
My simplified model looks like this.
PeopleListM.swift
import SwiftUI
import Alamofire
struct PeopleListM: Decodable {
var all: [Person]
enum CodingKeys: String, CodingKey {
case all = "people"
}
struct Person: Decodable {
let name: String
let email: String
let favorite: Bool
let lastSent: String?
enum CodingKeys: String, CodingKey {
case name
case email = "email_id"
case favorite
case lastSent = "last_sent"
}
}
init() {
let headers: HTTPHeaders = ["disableauth" : "true", "Content-Type": "application/json"]
AF.request("MY URL", parameters: nil, headers: headers)
.validate()
.responseDecodable(of: PeopleListM.self) { (response) in
guard let result = response.value else { return }
self.all = result.all
}
}
This code gives me error Escaping closure captures mutating 'self' parameter
I tried changing line self.all = result.all with self = result, same error.
My further plan is create a class which conforms to ObservableObject
PeopleListVM.swift
import SwiftUI
class PeopleListVM: ObservableObject {
let people = PeopleListVM.createModel()
static func createModel() {
PeopleListM()
}
}
final class PeopleListVM: ObservableObject {
#Published var people = PeopleListVM.createModel()
static func createModel() {
PeopleListM()
}
func request() {
// networking here...
// update people in callback
// ... self.people = newPeople
}
}
struct Model: View {
#ObservableObject var vm = PeopleListVM()
// ...
}
You generally won't put networking code in struct, because networking always comes with state changes, when value type is meant to be immutable.

Error trying to return protocol associated type from method

I need to search for entities(photos, albums, etc) using unsplash.com search APIs. In order to reach reusability and not having to create a new function every time i need to search for a new entity i created a protocol:
protocol SearchApiResource {
associatedtype ModelType: Decodable
var methodPath: String { get }
var searchTerm: String { get set }
var pageNumber: Int { get set }
var parameters: [String: String] { get }
var url: URL { get }
}
And a structure conforming to that protocol:
struct SearchPhotoResource: SearchApiResource {
typealias ModelType = Photo
var methodPath = "/search/photos"
var searchTerm = String()
var pageNumber = Int()
let itemsPerPage = 30
let accessKey = "93e0a185df414cc1d0351dc2238627b7e5af3a64bb228244bc925346485f1f44"
var parameters: [String: String] {
var params = [String: String]()
params["query"] = searchTerm
params["page"] = String(pageNumber)
params["per_page"] = String(itemsPerPage)
params["client_id"] = accessKey
return params
}
var url: URL {
var components = URLComponents()
components.scheme = "https"
components.host = "api.unsplash.com"
components.path = methodPath
components.queryItems = parameters.map {URLQueryItem(name: $0, value: $1)}
return components.url!
}
}
Now I want to create a function that will accept an struct or class conforming to SearchApiResource protocol:
func searchForItem(resource: SearchApiResource, searchTerm: String, pageNumber: Int, completion: #escaping (SearchApiResource.ModelType) -> Void ) {
}
But I receive an error:
"Associated type 'ModelType' can only be used with a concrete type or generic parameter base"
How to fix an error and what am i doing wrong?
Just do what the error says - use SearchApiResource as a generic parameter base.
func searchForItem<T: SearchApiResource>(resource: T, searchTerm: String, pageNumber: Int, completion: #escaping (T.ModelType) -> Void ) {
}

Resources