Refresh value in text label from JSON, with Xcode - ios

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.

Related

How to get an especific object from a JSON of an API in Xcode with Alamofire?

I am doing a project of an E-commerce app in swift and the data must be obtained from an API that i created with mockapi.io
The link of the API is: https://62858a2ff0e8f0bb7c057f14.mockapi.io/categorias but if you dont want to enter the link here is how the JSON looks like:
[
{
"categorie1": "Fruits",
"categorie2": "Animals",
"categorie3": "Juices",
"categorie4": "Vegetables",
"categorie5": "Alcohol",
"categorie6": "Desserts",
"id": "1"
}
]
I have a file named "ResponseWS" that contains the sructs to obtain the data from the API, it looks like this:
import Foundation
struct ResponseWS:Decodable{
let Datos:[Categories]
}
struct Categories:Decodable{
let id: String
let categorie: String
}
I also have a file named "ConnectWS" where I process the data with the Alamofire:
import Foundation
import Alamofire
class ConnectWS{
static let cmd = ConnectWS()
private let urlBase = "https://62858a2ff0e8f0bb7c057f14.mockapi.io/categorias"
private let code = 200...299
func getUsers(user: String, pass: String,success: #escaping(_ user: String) -> (), failure: #escaping(_ error: Error?)-> ()) {
AF.request(urlBase,method: .get).validate(statusCode: code).responseDecodable(of: ResponseWS.self){
response in
if let respuesta = response.data {
print(String(decoding: respuesta, as: UTF8.self))
success(String(decoding: respuesta, as: UTF8.self))
}else{
print(response.error!)
failure(response.error)
}
}
}
}
And finally I have my ViewController where I want to show the data of the API:
import UIKit
class ViewControllerCategorias: UIViewController {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var message: UILabel!
#IBOutlet weak var buttonOutlet: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonAction(_ sender: Any) {
ConnectWS.cmd.getUsers(user: "erik", pass: "1234", success: {user in
self.name.text = user
}, failure: {error in
self.message.text = error.debugDescription
})
}
}
As you can see in the "ViewController" I have a button that when I click it, it returns ALL the data from the API, and what I want to do is get especifics "categories" from the API
When I click the button, the label shows:
[{"categorie1":"Fruits","categorie2":"Animals","categorie3":"Juices","categorie4":"Vegetables","categorie5":"Alcohol","categorie6":"Desserts","id":"1"}]
¿How can I get an especific object of the API?
You have to decode the response data into that object. You can use this generic method for decoding. Just create a model class/struct that you want to use for converting after decoding JSON.
func decode<T: Codable>(_ data: Data) -> T {
// Create a decoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
// Create a property for the decoded data
guard let results = try? decoder.decode(T.self, from: data) else {
fatalError("Failed to decode!")
}
// Return the ready-to-use data
return results
}

No options in dynamically provided intent for widget

I'm trying to implement dynamically provided options for my iOS 15 widget. So far I successfully implemented static intent parameters, and now I wanted to extend it with dynamically provided ones.
The dynamic parameter in my .intentdefinition file is called HotspotList and has type String.
I have defined a struct where I have also saved a list of availableHotspots:
struct Hotspot: Hashable, Identifiable, Codable {
...
static var availableHotspots = UserDefaults.standard.object(forKey: "hotspots") as? [String] ?? []
}
I have checked that this array is successfully saved with print(Hotspot.availableHotspots) somewhere in my main View.
Now I want to use this array in my IntentHandler.swift file:
import Intents
class IntentHandler: INExtension, WidgetConfigurationIntentHandling {
override func handler(for intent: INIntent) -> Any {
return self
}
func provideHotspotListOptionsCollection(for intent: WidgetConfigurationIntent) async throws -> INObjectCollection<NSString> {
let hotspots: [NSString] = Hotspot.availableHotspots.map { element in
let nsstring = element as NSString
return nsstring
}
let collection = INObjectCollection(items: hotspots)
return collection
}
func defaultHotspotList(for intent: WidgetConfigurationIntent) -> String? {
return "thisIsJustATest"
}
}
I see that the intent is implemented correctly, because defaultHotspotList() returns the default parameter. But somehow provideHotspotListOptionsCollection() doesn't return the list of Strings. What am I doing wrong?
Note: I also tried the non-async option of the function:
func provideHotspotListOptionsCollection(for intent: WidgetConfigurationIntent, with completion: #escaping (INObjectCollection<NSString>?, Error?) -> Void) {
let hotspots: [NSString] = Hotspot.availableHotspots.map { element in
let nsstring = element as NSString
return nsstring
}
let collection = INObjectCollection(items: hotspots)
print(collection)
completion(collection, nil)
}
So with the hint of #loremipsum I was able to fix it by myself. For anyone interested, here's my solution:
I added the following lines in my MainView in the App target:
struct MainView: View {
#AppStorage("hotspots", store: UserDefaults(suiteName: "group.com.<<bundleID>>"))
var hotspotData: Data = Data()
...
save(hotspots: miners)
...
func save(hotspots: [String]) {
do {
hotspotData = try NSKeyedArchiver.archivedData(withRootObject: hotspots, requiringSecureCoding: false)
WidgetCenter.shared.reloadAllTimelines()
} catch let error {
print("error hotspots key data not saved \(error.localizedDescription)")
}
}
and then to retrieve the data in the IntentHandler.swift:
import Intents
import SwiftUI
class IntentHandler: INExtension, WidgetConfigurationIntentHandling {
#AppStorage("hotspots", store: UserDefaults(suiteName: "group.com.<<bundleID>>"))
var hotspotData: Data = Data()
func provideHotspotListOptionsCollection(for intent: WidgetConfigurationIntent) async throws -> INObjectCollection<NSString> {
var hotspots: [String] {
var hotspots: [String]?
do {
hotspots = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(hotspotData) as? [String]
} catch let error {
print("color error \(error.localizedDescription)")
}
return hotspots ?? []
}
let NSHotspots: [NSString] = hotspots.map { element in
let nsstring = element as NSString
return nsstring
}
let collection = INObjectCollection(items: NSHotspots)
return collection
}

Swift Does not see data

I am trying to parse the data and display on the screen but i am getting " Value of type 'EmployeeData' has no member 'employee_name' "
What i am missing ?
I created my struct, parsed data and tried to divide into two parts. first part will be related with listing, second part is all data.
struct EmployeeData: Codable {
var data: Employee
var status: String
}
struct Employee: Codable {
var employee_name: String
var employee_salary: String
var employee_age: String
}
class WebServices {
func getData(completion: #escaping (EmployeeData?) -> ()){
guard let url = URL(string:"http://dummy.restapiexample.com/api/v1/employees")
else { fatalError("There is error!") }
URLSession.shared.dataTask(with: url) { (data, response,error) in
guard let data = data, error == nil else {
DispatchQueue.main.async{
completion(nil)
}
return
}
let empleyees = try? JSONDecoder().decode(EmployeeData.self, from: data)
DispatchQueue.main.async {
completion(empleyees)
}
}.resume()
}
}
class MVDesingnListView: ObservableObject {
}
struct MVDesignCellView {
let employeeDatas: EmployeeData
init(employeeDatas: EmployeeData) {
self.employeeDatas = employeeDatas
}
var employee_name: String {
self.employeeDatas.employee_name
}
}
The compiler is all right. Your struct EmployeeData has no member employee_name.
You need to go to the employee first, to get her name:
var employee_name: String {
self.employeeDatas.data.employee_name
}
should do the job.

Swift issues getting images from URL in forEach list

I'm currently trying to transition from a web developer only to a mobile and web developer and have been having great difficulties learning swift, I think I learn something and write it only to have numerous errors. Right now I have the following code to get an image from my server inside of a foreach loop.
struct MainNotLoggedInView: View {
#ObservedObject var fetcher = Fetcher()
func getPic(urlLink: String) -> Image? {
let baseURL = "https://mywebsite.com"
let url = URL(string: baseURL + urlLink)
let data = try? Data(contentsOf: url!)
let image = UIImage(data: data!)
let image2 = Image(uiImage: (image ?? nil)!)
return image2
}
var body: some View{
VStack{
TabView {
ScrollView {
ZStack {
if fetcher.hasFinished == true {
VStack {
Text("Featured").font(.largeTitle).padding()
ForEach(fetcher.ac?.ac.featuredAc ?? []) { result in
VStack {
Text(result.name)
Image(self.getPic(urlLink: result.acPic))
}
}
}
} else {
Text("Featured").font(.largeTitle).padding()
}
}
}.tabItem {
Text("Featured").font(.subheadline)
}
OtherView().tabItem {
Text("Other").font(.subheadline)
}
}
}
}
}
I assume in order to figure out what is going on, I should also include the JSON fetcher script and the structures, which are below
import SwiftUI
import Combine
import Foundation
public struct AcModel: Codable, Identifiable {
public let id: Int
public let name: String
public let acPic: String
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case acPic = "picture_link"
}
}
public struct AcModel2: Codable {
public let location: String
private var popular: [String:AcModel]
public var popularAc: [AcModel] {
get {
return Array(self.popular.values)
}
}
private var featured: [String:AcModel]
public var featuredAc: [AcModel] {
get {
return Array(self.featured.values)
}
}
}
public struct AcModel: Codable {
public let ac: AcModel2
}
public class Fetcher: ObservableObject {
public let objectWillChange = PassthroughSubject<Fetcher,Never>()
#Published var hasFinished: Bool = false {
didSet {
objectWillChange.send(self)
}
}
var ac: AcModel?
init(){
guard let url = URL(string: "https://mywebsite.com/api/loadactivitiesguest") else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode(AcModel.self, from: d)
DispatchQueue.main.async {
self.ac = decodedLists
self.hasFinished = true
print("dispatching")
}
} else {
print("No Data")
}
} catch {
print("Error")
}
}.resume()
}
}
I'm sure because I'm a web developer by trade I'm thinking about it the wrong way, but as you can tell from the code, I'm trying to get the picture inside the foreach using the swift method of retrieving pictures from a server, and trying to display it. The code as I have it written has the error "Type of expression is ambiguous without more context". This error is on the line inside the actual view where I try to call the function; "Image(self.getPic(urlLink: result.acPic))"
I bolded the actual words highlight by the error.
Of course, the acPic variable is an actual variable, which is not optional, of the structure. I've seen a lot of other StackOverflow posts about this error, but as with several of the swift errors, it seems the same error message can be caused by several difference types of code with different purposes, and in combination with my lack of swift experience, I have difficulty understanding the relation between what they did wrong and what I did wrong.
From reading around however, I read that I should use something called Kingfisher to cache the images as there is hundreds. How do I install those git repositories into my project? Just a side question, no need to really answer there's probably hundreds of videos.
Any help would be appreciated.
Update
Image(self.getPic(urlLink: result.acPic))
to
self.getPic(urlLink: result.acPic)
Image hasn't a init method which gets an optional Image object and returns an Image object

How to implement simple MVC design pattern in Swift?

I am new to MVC design pattern. I created "DataModel" it will make an API call, create data, and return data to the ViewController using Delegation and "DataModelItem" that will hold all data. How to call a DataModel init function in "requestData" function. Here is my code:
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[DataModelItem])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
func requestData() {
}
private func setDataWithResponse(response:[AnyObject]){
var data = [DataModelItem]()
for item in response{
if let tableViewModel = DataModelItem(data: item as? [String : String]){
data.append(tableViewModel)
}
}
delegate?.didRecieveDataUpdata(data: data)
}
}
And for DataModelItem:
class DataModelItem{
var name:String?
var id:String?
init?(data:[String:String]?) {
if let data = data, let serviceName = data["name"] , let serviceId = data["id"] {
self.name = serviceName
self.id = serviceId
}
else{
return nil
}
}
}
Controller:
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
How to implement simple MVC design pattern in Swift?
As a generic answer, in iOS development you're already doing this implicitly! Dealing with storyboard(s) implies the view layer and controlling the logic of how they work and how they are connected to the model is done by creating view controller, that's the default flow.
For your case, let's clarify a point which is: according to the standard MVC, by default the responsible layer for calling an api should be -logically- the view controller. However for the purpose of modularity, reusability and avoiding to create massive view controllers we can follow the approach that you are imitate, that doesn't mean that its the model responsibility, we can consider it a secondary helper layer (MVC-N for instance), which means (based on your code) is DataModel is not a model, its a "networking" layer and DataModelItem is the actual model.
How to call a DataModel init function in "requestData" function
It seems to me that it doesn't make scene. What do you need instead is an instance from DataModel therefore you could call the desired method.
In the view controller:
let object = DataModel()
object.delegate = self // if you want to handle it in the view controller itself
object.requestData()
I am just sharing my answer here and I am using a codable. It will be useful for anyone:
Model:
import Foundation
struct DataModelItem: Codable{
struct Result : Codable {
let icon : String?
let name : String?
let rating : Float?
let userRatingsTotal : Int?
let vicinity : String?
enum CodingKeys: String, CodingKey {
case icon = "icon"
case name = "name"
case rating = "rating"
case userRatingsTotal = "user_ratings_total"
case vicinity = "vicinity"
}
}
let results : [Result]?
}
NetWork Layer :
import UIKit
protocol DataModelDelegate:class {
func didRecieveDataUpdata(data:[String])
func didFailUpdateWithError(error:Error)
}
class DataModel: NSObject {
weak var delegate : DataModelDelegate?
var theatreNameArray = [String]()
var theatreVicinityArray = [String]()
var theatreiconArray = [String]()
func requestData() {
Service.sharedInstance.getClassList { (response, error) in
if error != nil {
self.delegate?.didFailUpdateWithError(error: error!)
} else if let response = response{
self.setDataWithResponse(response: response as [DataModelItem])
}
}
}
private func setDataWithResponse(response:[DataModelItem]){
for i in response[0].results!{
self.theatreNameArray.append(i.name!)
self.theatreVicinityArray.append(i.vicinity!)
self.theatreiconArray.append(i.icon!)
}
delegate?.didRecieveDataUpdata(data: theatreNameArray)
print("TheatreName------------------------->\(self.theatreNameArray)")
print("TheatreVicinity------------------------->\(self.theatreVicinityArray)")
print("Theatreicon------------------------->\(self.theatreiconArray)")
}
}
Controller :
class ViewController: UIViewController {
private let dataSource = DataModel()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
dataSource.requestData()
}
}
extension ViewController : DataModelDelegate{
func didRecieveDataUpdata(data: [DataModelItem]) {
print(data)
}
func didFailUpdateWithError(error: Error) {
print("error: \(error.localizedDescription)")
}
}
APIManager :
class Service : NSObject{
static let sharedInstance = Service()
func getClassList(completion: (([DataModelItem]?, NSError?) -> Void)?) {
guard let gitUrl = URL(string: "") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(DataModelItem.self, from: data)
completion!([gitData],nil)
} catch let err {
print("Err", err)
completion!(nil,err as NSError)
}
}.resume()
}
}
I would recommend using a singleton instance for DataModel, since this would be a class you would be invoking from many points in your application.
You may refer its documentation at :
Managing Shared resources using singleton
With this you wont need to initialise this class instance every time you need to access data.

Resources