How can I declare the Json variable in this case on swiftUI - swiftui

It cannot show the result, anyone knows?
struct Course: Hashable, Codable {
let id:String
let display:String
}
class ViewModel: ObservableObject{
#Published var courses: [Course]= []
func fetch(){
guard let url = URL(string:"https://www.i-design.hk/api/menu/userMenuRequest.php?type=userMenu&action=l&userId=200380")else{
return
}
let task = URLSession.shared.dataTask(with: url) { [weak self]data,_, error in
guard let data = data, error == nil else {
return
}
I have tried many times to change the code, but I cannot show the JSON string correctly. Anyone can help me?

when the response came from API it came in form of JSON and you need to decode that JSON so that can easily read by swift, so you should decode the response like that and when you get the response you should assign the data to the courses variable so that it will published to the view
struct Course: Hashable, Codable {
let id:String
let display:String
}
class ViewModel: ObservableObject{
#Published var courses: [Course]= []
#Published var error: String = ""
func fetch(){
guard let url = URL(string:"https://www.i-
design.hk/api/menu/userMenuRequest.php?
type=userMenu&action=l&userId=200380")else{
return
}
let task = URLSession.shared.dataTask(with: url) { [weak
self]data,_, error in
guard let data = data, error == nil else {
return
}
do{
let ResponseResult = try JSONDecoder().decode([Course], from: data)
courses = ResponseResult
}catch(let error){
error = error.localizedDescription
}
}

Related

Encode and decode swiftui MKPlacemark

I am new to SwiftUI and I am trying to encode and decode a MKPlacemark struct to json.
I have the struct defined as below. I am able to display the details in the app but I am not able to decode it.
import Foundation
import MapKit
import UIKit
struct Landmark {
let placemark: MKPlacemark
var id: UUID {
return UUID()
}
var name: String {
self.placemark.name ?? ""
}
var title: String {
self.placemark.title ?? ""
}
var coordinate: CLLocationCoordinate2D {
self.placemark.coordinate
}
}
I can search for placemarks like this:
import Foundation
import Combine
import MapKit
class SearchPlaces: NSObject, ObservableObject {
#Published var searchQuery = ""
#Published var landmarks: [Landmark] = [Landmark]()
#Published var items: [MapItem] = [MapItem]()
public func getNearByLandmarks() {
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = searchQuery
let search = MKLocalSearch(request: request)
search.start { (response, error) in
if let response = response {
let mapItems = response.mapItems
self.landmarks = mapItems.map {
Landmark(placemark: $0.placemark)
}
Task {
await self.getData()
}
print("Lamdmarks \(self.landmarks)")
}
}
}
private func getData() async {
guard let landmark = try? JSONEncoder().encode(self.landmarks) else { return }
do {
let decodedLandmark = try JSONDecoder().decode(Landmark.self, from: landmark)
print("decodedLandmark \(decodedLandmark.id)")
} catch {
print("Error \(error.localizedDescription)")
}
}
}
But I get this error: Error
The data couldn’t be read because it isn’t in the correct format.
The placemark looks like this in xcode
Lamdmarks \[Landmark(placemark: La Hacienda Market, 249 Hillside Blvd, South San Francisco, CA 94080, United States # \<+37.66312925,-122.40844847\> +/- 0.00m, region CLCircularRegion (identifier:'\<+37.66307481,-122.40861130\> radius 141.17', center:\<+37.66307481,-122.40861130\>, radius:141.17m))
How do I decode a MKPlacemark to json when I don't know all of its keys.
I tried this
extension NSSecureCoding { func archived() throws -> Data { try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false) } }
extension Data { func unarchived<T: NSSecureCoding>() throws -> T? { try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(self) as? T } }
extension Landmark: Codable {
func encode(to encoder: Encoder) throws {
var unkeyedContainer = encoder.unkeyedContainer()
try unkeyedContainer.encode(placemark.archived())
try unkeyedContainer.encode(id)
try unkeyedContainer.encode(name)
try unkeyedContainer.encode(title)
try unkeyedContainer.encode(coordinate)
}
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
placemark = try container.decode(Data.self).unarchived()!
coordinate = try container.decode(CLLocationCoordinate2D.self, "coordinate")
id = try container.decode(UUID.self)
name = placemark.name ?? "no name"
title = placemark.title ?? "no title"
}
}
First of all never print(error.localizedDescription) in a Codable context. The generic error message is meaningless.
Always
print(error)
to get the real meaningful DecodingError.
Second of all don't try to adopt Codable by serializing each single property in classes which conform to NSSecureCoding. Take advantage of the built-in serialization and also of the PropertyWrapper pattern.
This PropertyWrapper converts/serializes MKPlacemark to Data and vice versa
#propertyWrapper
struct CodablePlacemark {
var wrappedValue: MKPlacemark
}
extension CodablePlacemark: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let data = try container.decode(Data.self)
guard let placemark = try NSKeyedUnarchiver.unarchivedObject(ofClass: MKPlacemark.self, from: data) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Invalid placemark"
)
}
wrappedValue = placemark
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let data = try NSKeyedArchiver.archivedData(withRootObject: wrappedValue, requiringSecureCoding: true)
try container.encode(data)
}
}
In the Landmark struct adopt Codable and declare the placemark
struct Landmark: Codable {
#CodablePlacemark var placemark: MKPlacemark
}
But the property wrapper makes only sense if you encode the placemark.

URLSession HTTP Error not updating back in view unless the action is initiated again

I have a view with a button that calls an API, the API either returns an HTTP code 200 or 400 based on a particular scenario.
The button works just fine and everything works smoothly if code 200 is returned, however if code 400 is returned, the view is not updated that the user have to click on the button once again to get the updated message.
I added the http code property as a published variable in the VM's class and the http is an observable, but it doesn't get updated in the view on the first API call, I'm not sure what I'm missing.
I made a lot of changes to the shared code just to help in demonstrating the actual problem.
Update: Also I think another part of the problem, is that the url function returns the value before the url session returns the data, I don't know why this is happening, that when I execute it a second time it uses the values from the previous execution.
HTTPError Class
class HTTPError : Codable, ObservableObject {
var statusCode: Int?
var message: [String]?
var error: String?
init(statusCode: Int? = nil, message: [String]? = [], error: String? = nil){
self.statusCode = statusCode
self.message = message ?? []
self.error = error
}
convenience required init(from decoder: Decoder) throws {
self.init()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.statusCode = try container.decodeIfPresent(Int.self, forKey: .statusCode)
do {
self.message = try container.decodeIfPresent([String].self, forKey: .message)
} catch {
guard let value = try container.decodeIfPresent(String.self, forKey:
.message) else {return}
self.message = []
self.message?.append(value)
}
self.error = try container.decodeIfPresent(String.self, forKey: .error)
}
VM Class
class VM: ObservableObject {
#Published var isLoading = true
#Published var httpError = HTTPError()
func checkDriverIn(_ record: DriverQRParam) async -> (Bool) {
...
var request = URLRequest(url: url)
...
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
...
let task = URLSession.shared.dataTask(with: request) { (data, response,
error) in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
self.httpError = try! JSONDecoder().decode(HTTPError.self, from: data)
//gets updated just fine in this class//
}
task.resume()
}catch {
print("Couldn't encode data \(String(describing: error))")
}
if httpError.statusCode != nil && httpError.statusCode == 400 {
return (false)
} else {
return (true)
}
}
View.Swift
struct xyz: View {
#State private var VM = VM()
Button("click") {
Task {
await VM.checkDriverIn(driverParam)
}
}
}

Memory Leak with swiftUI List and Firebase database observing childAdded

I'm writing a chat application using firebase , I notice memory leak in ChatView while observing database changes which is when a message is sent or received.
when I comment out the database observation the memory leak dose not happen anymore so I'm guessing this is a firebase problem .
I'm sharing the code so please if you know what is acutely causing the memory leak help me out.
ChatViewModel:
class ChatViewModel : ObservableObject {
/// - sub ViewModels :
#Published private(set) var messages : [MessageModel] = []
private(set) var conversationID : String? = nil
/// set shared conversationID
/// - Parameter convesationID: shared conversationID if exist
func setConverationID(convesationID : String?){
guard let convesationID = convesationID else {
print("CONVERSATION ID DOUS NOT EXIT")
return
}
self.conversationID = convesationID
startObservingConversation()
}
/// start observing the conversation with viewModel conversationID
private func startObservingConversation(){
guard let conversationID = self.conversationID else {
return
}
DatabaseManager.shared.observeMessagesForConversation(conversationId: conversationID) { [weak self] message in
self?.messages += message
}
}}
ChatView :
struct ChatView: View {
#StateObject var viewModel = ChatViewModel()
var body: some View {
VStack(alignment : .leading , spacing: 0){
ScrollViewReader { scrollViewReader in
List{
ForEach(viewModel.messages) { item in
MessageView(messsage: item.text)
.id(item.id)
}
}
}
}
}}
observerMessages :
func observeMessagesForConversation(conversationId id :String,compelition : #escaping ([MessageModel]) -> Void ) {
database.child(id).child("messages").observe(.childAdded) { snapshot in
guard let value = snapshot.value as? [String:Any] else {
compelition([])
return
}
var messages : [MessageModel] = []
let decoder = JSONDecoder()
guard
let jsonData = try? JSONSerialization.data(withJSONObject:value),
let message = try? decoder.decode(MessageModel.self, from: jsonData) else {
compelition([])
return
}
messages.append(message)
compelition(messages)
}
}

Implementing Login Navigation in SwiftUI

Here is my requirement: I want to display a login screen, perform authentication via a network request, and if successful, display the content page.
I have implemented the network request part as per the suggestion by jpdnx here.
I have a UserAuth class and the corresponding code to display the Login View if not logged in, and the Content View if logged in, as per M Reza here.
However, I am not able to combine the two - when the network request finishes in LoginView, I am not able to get the flow back to the LoginControllerView to navigate to ContentView. Any help will be appreciate here.
Here is my code:
Network.swift
class ViewModel<T: Codable> : ObservableObject {
//#Published var calendar : IDCCalendar?
#Published var modelData : T?
func getData(url: URL, encoded: String, completion: (#escaping ()->()) ) {
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
print(encoded)
let encoder = JSONEncoder()
if let data = try? encoder.encode(encoded) {
request.httpBody = data
}
print("Request: \(request)")
URLSession.shared.dataTask(with: request) { data1, response, error in
if let error = error {
print("Request error: ", error)
return
}
guard let data1 = data1 else { return }
DispatchQueue.main.async {
let decoder = JSONDecoder()
print(String(decoding: data1, as: UTF8.self))
if let value = try? decoder.decode(T.self, from: data1)
{
self.modelData = value
completion()
print("Success!")
}
else {
print("Does not decode correctly")
}
}
}.resume()
}
LoginControllerView.swift
var body: some View {
Group {
if !userAuth.isLoggedin {
LoginView(userAuth: userAuth)
} else {
ContentView()
}
}
LoginView.swift
struct LoginView: View {
#EnvironmentObject var userAuth: UserAuth
#StateObject var viewModel = ViewModel<Login>()
var body: some View {
// Login Screen UI here
Button("Login") {
// Commenting out network request code out for now
/*
let url = URL(<URL goes here>)!
let encoded = <JSON String goes here>
let completion = {
if viewModel.modelData?.Status == 0 {
userAuth.isLoggedin = true
let login = viewModel.modelData!
let encoder = JSONEncoder()
if let data = try? encoder.encode(login) {
UserDefaults.standard.set(data, forKey: "LoginData")
}
}
viewModel.getData(url: url, encoded: encoded, completion: completion)
*/
// For test purposes, setting it to true, I want this to percolate back to
// LoginControllerView
userAuth.isLoggedin = true
}
UserAuth.swift
import Combine
class UserAuth: ObservableObject {
let didChange = PassthroughSubject<UserAuth,Never>()
// required to conform to protocol 'ObservableObject'
let willChange = PassthroughSubject<UserAuth,Never>()
func login() {
// login request... on success:
self.isLoggedin = true
}
var isLoggedin = false {
didSet {
didChange.send(self)
}
}
}

Passing Decoded JSON Data to SwiftUI ContentView

API call and JSON decoding are working fine, as I can print to console any item from the JSON data set without a problem.
Here's API call and test print:
import Foundation
import SwiftUI
import Combine
class APICall : ObservableObject {
#Published var summary: Summary?
init () {
pullSummary()
}
func pullSummary() {
let urlCall = URL(string: "https://api.covid19api.com/summary")
guard urlCall != nil else {
print("Error reaching API")
return
}
let session = URLSession.shared
let dataTask = session.dataTask(with: urlCall!) { (data, response, error) in
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
let summary = try decoder.decode(Summary.self, from: data!)
print(summary.byCountry[40].cntry as Any)
DispatchQueue.main.async {
self.summary = summary
}
}
catch {
print("Server busy, try again in 5 min.")
}
}
}
dataTask.resume()
}
}
And here is the structure of the "Summary" data model used for the decoding and data object structure:
import Foundation
struct Summary: Decodable {
let global: Global
let byCountry: [ByCountry]
let date: String
enum CodingKeys: String, CodingKey {
case global = "Global"
case byCountry = "Countries"
case date = "Date"
}
struct Global: Decodable {
let globalNC: Int
let globalTC: Int
let globalND: Int
let globalTD: Int
let globalNR: Int
let globalTR: Int
enum CodingKeys: String, CodingKey {
case globalNC = "NewConfirmed"
case globalTC = "TotalConfirmed"
case globalND = "NewDeaths"
case globalTD = "TotalDeaths"
case globalNR = "NewRecovered"
case globalTR = "TotalRecovered"
}
}
struct ByCountry: Decodable {
let cntry: String?
let ccode: String
let slug: String
let cntryNC: Int
let cntryTC: Int
let cntryND: Int
let cntryTD: Int
let cntryNR: Int
let cntryTR: Int
let date: String
enum CodingKeys: String, CodingKey {
case cntry = "Country"
case ccode = "CountryCode"
case slug = "Slug"
case cntryNC = "NewConfirmed"
case cntryTC = "TotalConfirmed"
case cntryND = "NewDeaths"
case cntryTD = "TotalDeaths"
case cntryNR = "NewRecovered"
case cntryTR = "TotalRecovered"
case date = "Date"
}
}
}
As shown, the results of the API call and JSON decode are published as required using ObserveableObject and #Published.
Over in the ContentView, I have followed the ObservedObject rules and only want to display on the UI a data point from the JSON data to confirm it's working:
import SwiftUI
import Foundation
import Combine
struct ContentView: View {
#ObservedObject var summary = APICall()
var body: some View {
Text($summary.date)
.onAppear {
self.summary.pullSummary()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
}
BUT... I get these 2 errors at the Text display line, 1) Initializer 'init(_:)' requires that 'Binding<Subject>' conform to 'StringProtocol' and 2) Value of type 'ObservedObject<APICall>.Wrapper' has no dynamic member 'date' using the key path from root type 'APICall'.
I am guessing the 2nd error is the root cause of the problem, indicating the data is not being passed into the ContentView correctly.
I appreciate any suggestions.
Thanks.
It is messed view model with internal property
struct ContentView: View {
#ObservedObject var viewModel = APICall()
var body: some View {
Text(viewModel.summary?.date ?? "Loading...") // << no $ sign !!!
.onAppear {
self.viewModel.pullSummary()
}
}
}