How to create a json 'POST' request in SwiftUI? - json

I'm making a contact list app for a project to learn programming, and am currently creating the method to add a contact to a database through a Rest API I created.
The code below is for the Add Contact view, and I followed some tutorials on how to take data from a form, convert it to JSON and send it to a database via a POST request.
This is the code I've gotten, and what should be happening is that I should see a print of responseJSON, but that isn't happening, in fact nothing is happening and when I run the checkDetails function by pressing the button on the view I get the prints of body and jsonData, but nothing else. Not even an error.
I am a bit stumped here as I haven't changed much, if anything, from the tutorials I followed, so I am guessing it is something to do with my own code.
Any help would be appreciated!
import SwiftUI
import Combine
struct AddContact: View {
#State var id = 999
#State var first_name: String = ""
#State var last_name: String = ""
#State var phone_number: String = ""
#State var address: String = ""
#State var birthday = Date()
#State var birthdayString: String = ""
#State var create_date = Date()
#State var create_dateString: String = ""
#State var updated_date = Date()
#State var updated_dateString: String = ""
#State var manager = DataPost()
var body: some View {
if manager.formCompleted {
Text("Done").font(.headline)
}
VStack {
NavigationView {
Form {
Section() {
TextField("First Name", text: $first_name)
TextField("Last Name", text: $last_name)
}
Section() {
TextField("Phone Number", text: $phone_number)
TextField("Address", text: $address)
}
Section() {
DatePicker("Birthday", selection: $birthday, displayedComponents: .date)
}
Section() {
Button(action: {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
birthdayString = dateFormatter.string(from: birthday)
create_dateString = dateFormatter.string(from: create_date)
updated_dateString = dateFormatter.string(from: updated_date)
print("Clicked")
self.manager.checkDetails(id: self.id, first_name: self.first_name, last_name: self.last_name, phone_number: self.phone_number, address: self.address, birthday: self.birthdayString, create_date: self.create_dateString, updated_date: self.updated_dateString)
}, label: {
Text("Add Contact")
.fontWeight(.bold)
.multilineTextAlignment(.center)
})
}.disabled(first_name.isEmpty || last_name.isEmpty || phone_number.isEmpty || address.isEmpty)
}
}.navigationTitle("New Contact")
.navigationBarTitleDisplayMode(.inline)
}
}
}
class DataPost: ObservableObject {
var didChange = PassthroughSubject<DataPost, Never>()
var formCompleted = false {
didSet {
didChange.send(self)
}
}
func checkDetails(id: Int, first_name: String, last_name: String, phone_number: String, address: String, birthday: String, create_date: String, updated_date: String) {
let body: [String: Any] = ["data": ["id": id, "first_name": first_name, "last_name": last_name, "birthday": birthday, "phone_number": phone_number, "create_date": create_date, "updated_date": updated_date, "address": address]]
let jsonData = try? JSONSerialization.data(withJSONObject: body)
let url = URL(string: "https://flaskcontact-list-app.herokuapp.com/contacts")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("\(String(describing: jsonData?.count))", forHTTPHeaderField: "Content-Length")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
print(body)
print(jsonData)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON)
}
}
task.resume()
}
}

Ok, this is the test code I use. Although I am on macos 12.beta, xcode 13.beta, target ios 15 and macCatalyst.
Tested on iPhone ios15 and macos 12. It "should be the same" on xcode 12.5 and macos 11.4.
import Foundation
import SwiftUI
import Combine
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct AddContact: View {
#State var id = 999
#State var first_name: String = ""
#State var last_name: String = ""
#State var phone_number: String = ""
#State var address: String = ""
#State var birthday = Date()
#State var birthdayString: String = ""
#State var create_date = Date()
#State var create_dateString: String = ""
#State var updated_date = Date()
#State var updated_dateString: String = ""
#State var manager = DataPost()
var body: some View {
if manager.formCompleted {
Text("Done").font(.headline)
}
VStack {
NavigationView {
Form {
Section() {
TextField("First Name", text: $first_name)
TextField("Last Name", text: $last_name)
}
Section() {
TextField("Phone Number", text: $phone_number)
TextField("Address", text: $address)
}
Section() {
DatePicker("Birthday", selection: $birthday, displayedComponents: .date)
}
Section() {
Button(action: {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
birthdayString = dateFormatter.string(from: birthday)
create_dateString = dateFormatter.string(from: create_date)
updated_dateString = dateFormatter.string(from: updated_date)
print("Clicked")
self.manager.checkDetails(id: self.id, first_name: self.first_name, last_name: self.last_name, phone_number: self.phone_number, address: self.address, birthday: self.birthdayString, create_date: self.create_dateString, updated_date: self.updated_dateString)
}, label: {
Text("Add Contact")
.fontWeight(.bold)
.multilineTextAlignment(.center)
})
}.disabled(first_name.isEmpty || last_name.isEmpty || phone_number.isEmpty || address.isEmpty)
}
}.navigationTitle("New Contact")
.navigationBarTitleDisplayMode(.inline)
}
}
}
class DataPost: ObservableObject {
var didChange = PassthroughSubject<DataPost, Never>()
var formCompleted = false {
didSet {
didChange.send(self)
}
}
func checkDetails(id: Int, first_name: String, last_name: String, phone_number: String, address: String, birthday: String, create_date: String, updated_date: String) {
let body: [String: Any] = ["data": ["id": id, "first_name": first_name, "last_name": last_name, "birthday": birthday, "phone_number": phone_number, "create_date": create_date, "updated_date": updated_date, "address": address]]
let jsonData = try? JSONSerialization.data(withJSONObject: body)
// "https://flaskcontact-list-app.herokuapp.com/contacts"
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("\(String(describing: jsonData?.count))", forHTTPHeaderField: "Content-Length")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
print("-----> data: \(data)")
print("-----> error: \(error)")
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
print("-----1> responseJSON: \(responseJSON)")
if let responseJSON = responseJSON as? [String: Any] {
print("-----2> responseJSON: \(responseJSON)")
}
}
task.resume()
}
}
struct ContentView: View {
var body: some View {
AddContact()
}
}

Related

turning the json result into dynamic

i have a very complicated issue for a beginner. firstly I have this result from json
{
"success": true,
"timeframe": true,
"start_date": "2018-01-01",
"end_date": "2018-01-05",
"source": "TRY",
"quotes": {
"2018-01-01": {
"TRYEUR": 0.21947
},
"2018-01-02": {
"TRYEUR": 0.220076
},
"2018-01-03": {
"TRYEUR": 0.220132
},
"2018-01-04": {
"TRYEUR": 0.220902
},
"2018-01-05": {
"TRYEUR": 0.222535
}
}
}
and when I use https://app.quicktype.io to create the object for me it gives this and that is right.
import Foundation
// MARK: - APIResult
struct APIResult {
let success, timeframe: Bool
let startDate, endDate, source: String
let quotes: [String: Quote]
}
// MARK: - Quote
struct Quote {
let tryeur: Double
}
but I don't want my currencies hardcoded like this so if I choose from: USD to : EUR in my app I want to get the result under Quote as USDEUR. And I also know that if I change anything in this struct it won't work. So how will make those currency selections dynamic to make it work in different currencies. This is a currency converter app and I want to get these rates and reflect it on a chart in my app. Thank you.
Edit: I think I need to get used to using stack overflow properly. Sorry for any inconvenience . At last I could get the dates and rates written in the console. my question is now :
how can i get these results in the console passed into my charts x(dates) and y axis(rates) ?
["2022-12-22": 19.803011, "2022-12-18": 19.734066, "2022-12-23": 19.907873, "2022-12-21": 19.79505, "2022-12-24": 19.912121, "2022-12-17": 19.756527, "2022-12-16": 19.752446, "2022-12-25": 19.912121, "2022-12-19": 19.794356, "2022-12-20": 19.824031]
this is the func i get these
func updateChart () {
let date = Date()
let endDate = formatter.string(from: date)
let startDate = Calendar.current.date(byAdding: .day, value: -9, to: date)
let startDatee = formatter.string(from: startDate ?? Date())
print(endDate)
print(startDatee)
let result: () = currencyManager.fetchRatesForTimeframe(from: from, to: to, startDate: startDatee, endDate: endDate)
print(result)
}
and this is my previously created and hardcoded charts
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
lineChart.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 240)
lineChart.center = view.center
view.addSubview(lineChart)
var entries = [ChartDataEntry]()
for x in 0..<10 {
entries.append(ChartDataEntry(x: Double(x), y: Double(x)))
}
let set = LineChartDataSet(entries: entries)
set.colors = ChartColorTemplates.material()
let data = LineChartData(dataSet: set)
lineChart.data = data
}
Decodable is pretty versatile and highly customizable.
Write a custom initializer and map the quote dictionary to an array of Quote instances which contains the date and the quote. The key TRYEUR is irrelevant and will be ignored.
let jsonString = """
{
"success": true,
"timeframe": true,
"start_date": "2018-01-01",
"end_date": "2018-01-05",
"source": "TRY",
"quotes": {
"2018-01-01": {
"TRYEUR": 0.21947
},
"2018-01-02": {
"TRYEUR": 0.220076
},
"2018-01-03": {
"TRYEUR": 0.220132
},
"2018-01-04": {
"TRYEUR": 0.220902
},
"2018-01-05": {
"TRYEUR": 0.222535
}
}
}
"""
struct APIResult: Decodable {
private enum CodingKeys: String, CodingKey {
case success, timeframe, startDate = "start_date", endDate = "end_date", source, quotes
}
let success, timeframe: Bool
let startDate, endDate, source: String
let quotes: [Quote]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
success = try container.decode(Bool.self, forKey: .success)
timeframe = try container.decode(Bool.self, forKey: .timeframe)
startDate = try container.decode(String.self, forKey: .startDate)
endDate = try container.decode(String.self, forKey: .endDate)
source = try container.decode(String.self, forKey: .source)
let quoteData = try container.decode([String: [String:Double]].self, forKey: .quotes)
quotes = quoteData.compactMap({ (key, value) in
guard let quote = value.values.first else { return nil }
return Quote(date: key, quote: quote)
}).sorted{$0.date < $1.date}
}
}
struct Quote {
let date: String
let quote: Double
}
do {
let result = try JSONDecoder().decode(APIResult.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}
I changed my api provider now but this new one is not that much different than the previous one . this is the response I get inside browser
{
"success": true,
"timeseries": true,
"start_date": "2022-04-01",
"end_date": "2022-04-05",
"base": "USD",
"rates": {
"2022-04-01": {
"TRY": 14.686504
},
"2022-04-02": {
"TRY": 14.686504
},
"2022-04-03": {
"TRY": 14.686145
},
"2022-04-04": {
"TRY": 14.696501
},
"2022-04-05": {
"TRY": 14.72297
}
}
}
this is my object
struct APIResult: Codable {
let timeseries: Bool
let success: Bool
let startDate: String
let endDate: String
let base: String
var rates: [String:[String:Double]]
}
and this is my code inside VC to get the current date and 10 days before and I can see it printed in the console.
lazy var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.timeZone = .current
formatter.locale = .current
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
func updateChart () {
let date = Date()
let endDate = formatter.string(from: date)
let startDate = Calendar.current.date(byAdding: .day, value: -10, to: date)
let startDatee = formatter.string(from: startDate ?? Date())
print(endDate)
print(startDatee)
currencyManager.fetchRatesForTimeframe(from: from, to: to, startDate: startDatee, endDate: endDate)
lastly these are the codes inside my other file called CurrencyManager
func fetchRatesForTimeframe(from: String, to: String, startDate: String, endDate:String) {
let urlString = "\(timeFrameUrl)base=\(from)&symbols=\(to)&start_date=\(startDate)&end_date=\(endDate)&apikey=\(api.convertApi)"
performRequestforTimeframe(with: urlString)
}
func performRequestforTimeframe(with urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
} else {
if let safeData = data {
if let timeFrameRates = parseJSONForTimeframe(currencyData: safeData) {
print(timeFrameRates)
self.delegate?.didGetTimeframeRates(timeFrameRates)
}
}
}
}
task.resume()
}
}
func cut(_ value: [String: [String: Double]]) -> [String: [String: Double]] {
let dic = value
.sorted(by: { $0.0 < $1.0 })[0..<10] // <-- last 10 results
.reduce(into: [String: [String: Double]]()) {
$0[$1.key] = $1.value
}
return dic
}
func parseJSONForTimeframe(currencyData: Data) -> APIResult? {
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let decoder = JSONDecoder()
var jsondata = try decoder.decode(APIResult.self, from: currencyData)
jsondata.rates = cut(jsondata.rates)
return jsondata
} catch {
return nil
}
}
}
why I can't get the result for print(timeFrameRates) inside func performRequestforTimeframe ? what s missing?

Why is my API call not completely reached?

I am currently building a public transportation app in SwiftUI Xcode 12 beta, where you input origin, destination, time of arrival, and date. The idea is to then convert the origin and destination input (private func FetchOriginCoordinates and private func FetchDestCoordinates) into coordinates using an API. This API will then give that data to another API that plans the route.
The problem is that the route planner API doesn't give any output. I have tried printing self.Trips.append(...) but the list is completely empty. I believe this is because the code doesn't reach that part of the code. I have put some print statements in the code in order to understand where the error is occurring. When I call the API my output in the console is this:
Test 2021-04-16 22:33:19.271246+0200 OnTrack2.0[6592:428359] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed Optional(http://api.sl.se/api2/TravelplannerV3_1/trip.json?key=40892db48b394d3a86b2439f9f3800fd&originExtId=300101115&destExtId=300101426&Date=2021-04-18&Time=09:20&searchForArrival=1) [] 10767 bytes Level 1 Level 1.5
This means that the code only reaches the print("Level 1.5") statement. But never the following print statements such as print("Level 2") or print(self.trips). Why is that and how do I solve it?
import Foundation
struct NominationStructure: Decodable {
var lat: String
var lon: String
enum CodingKeys: String, CodingKey {
case lat = "lat"
case lon = "lon"
}
}
struct NearbyStopsStructure: Decodable {
var stopLocationOrCoordLocation: [stopLocationOrCoordLocationStructure]
}
struct stopLocationOrCoordLocationStructure: Decodable {
var StopLocation: StopLocationStructure
}
struct StopLocationStructure: Decodable {
var mainMastExtId: String
}
struct JSONStructure: Decodable {
var Trip: [TripStructure]
}
struct TripStructure: Decodable {
var LegList: LegListStructure
}
struct LegListStructure: Decodable {
var Leg: [LegStructure]
}
struct LegStructure: Decodable {
var Origin: StationStructure
var Destination: StationStructure
var Product: ProductStructure
var name: String
var type: String
var dist: String
}
struct StationStructure: Decodable {
var time: String
var name: String
var date: String
}
struct ProductStructure: Decodable {
var catIn: String
}
struct LocationInfo {
var iD = String()
var input = String()
var lat = String()
var lon = String()
var name = String()
var time = String()
var date = String()
var vehicleType = String()
var transportType = String()
var dist = String()
var legName = String()
}
//One way is to call your functions when you know a step is completed
enum TripFetchStatus: String {
case start
case fetchedOriginCoordinates
case fetchedOriginId
case fetchedDestinationCoordinates
case fetchedDestinationId
case fetchingTrip
case done
case stopped
}
class PlanTripViewModel: ObservableObject {
//Apple frowns upon frezzing a screen so having a way to update the user on what is going on
//When a step is complete have it do something else
#Published var fetchStatus: TripFetchStatus = .stopped{
didSet{
switch fetchStatus {
case .start:
print("Test")
FetchOriginCoordinates { (cors) in
self.origin.lat = cors[0].lat
self.origin.lon = cors[0].lon
self.fetchStatus = .fetchedOriginCoordinates
}
case .fetchedOriginCoordinates:
self.FetchOriginID { (stops) in
self.origin.iD = stops.stopLocationOrCoordLocation[0].StopLocation.mainMastExtId
self.fetchStatus = .fetchedOriginId
}
case .fetchedOriginId:
FetchDestCoordinates { (cors) in
self.dest.lat = cors[0].lat
self.dest.lon = cors[0].lon
self.fetchStatus = .fetchedDestinationCoordinates
}
case .fetchedDestinationCoordinates:
self.FetchDestID { (stops) in
self.dest.iD = stops.stopLocationOrCoordLocation[0].StopLocation.mainMastExtId
self.fetchStatus = .fetchedDestinationId
}
case .fetchedDestinationId:
//Once you have everthing in place then go to the next API
FetchTrip()
case .fetchingTrip:
print("almost done")
case .done:
print("any other code you need to do")
case .stopped:
print("just a filler")
}
}
}
#Published var trip: LocationInfo = LocationInfo()
#Published var dest: LocationInfo = LocationInfo()
#Published var origin: LocationInfo = LocationInfo()
#Published var arrivalTime = String()
#Published var travelDate = String()
#Published var tripIndex = Int()
#Published var Trips: [Dictionary<String, String>] = []
public func FetchTrip() {
Trips.removeAll()
let tripKey = "40892db48b394d3a86b2439f9f3800fd"
let tripUrl = URL(string: "http://api.sl.se/api2/TravelplannerV3_1/trip.json?key=\(tripKey)&originExtId=\(self.origin.iD)&destExtId=\(self.dest.iD)&Date=\(self.travelDate)&Time=\(self.arrivalTime)&searchForArrival=1")
print(tripUrl)
URLSession.shared.dataTask(with: tripUrl!) {data, response, error in
if let data = data {
print(data)
print("Level 1")
do {
print("Level 1.5")
if let decodedJson = try? JSONDecoder().decode(JSONStructure.self, from: data) {
self.tripIndex = decodedJson.Trip.count - 1
print("Level 2")
for i in 0..<decodedJson.Trip[self.tripIndex].LegList.Leg.count {
self.trip.transportType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].type
if self.trip.transportType == "WALK" {
self.origin.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.name
self.origin.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.time.prefix(5))
self.origin.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.date
self.dest.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.name
self.dest.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.time.prefix(5))
self.dest.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.date
self.trip.vehicleType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Product.catIn
self.trip.dist = decodedJson.Trip[self.tripIndex].LegList.Leg[i].dist
print(self.origin.name)
print(self.dest.name)
self.Trips.append(["Origin": self.origin.name, "Destination": self.dest.name, "OriginTime": self.origin.time, "DestTime": self.dest.time, "OriginDate": self.origin.date, "DestDate": self.dest.date, "TransportType": self.trip.transportType, "VehicleType": self.trip.vehicleType, "Distance": self.trip.dist])
}
else {
self.origin.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.name
self.origin.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.time.prefix(5))
self.origin.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.date
self.dest.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.name
self.dest.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.time.prefix(5))
self.dest.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.date
self.trip.vehicleType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Product.catIn
self.trip.legName = decodedJson.Trip[self.tripIndex].LegList.Leg[i].name
print(self.origin.name)
print(self.dest.name)
self.Trips.append(["Origin": self.origin.name, "Destination": self.dest.name, "OriginTime": self.origin.time, "DestTime": self.dest.time, "OriginDate": self.origin.date, "DestDate": self.dest.date, "TransportType": self.trip.transportType, "VehicleType": self.trip.vehicleType, "LegName": self.trip.legName])
}
}
}
} catch {
print(error)
}
}
}.resume()
print(self.Trips)
}
//Simple version just to replicate put your code within
private func FetchOriginCoordinates(completion: #escaping ([NominationStructure]) -> ()) {
let scheme = "https"
let host = "nominatim.openstreetmap.org"
let path = "/search"
let queryItemCountry = URLQueryItem(name: "country", value: "Sweden")
let queryItemCity = URLQueryItem(name: "city", value: "Stockholm")
let queryItemStreet = URLQueryItem(name: "street", value: self.origin.input)
let queryItemFormat = URLQueryItem(name: "format", value: "json")
var urlComponents = URLComponents()
urlComponents.scheme = scheme
urlComponents.host = host
urlComponents.path = path
urlComponents.queryItems = [queryItemCountry, queryItemCity, queryItemStreet, queryItemFormat]
URLSession.shared.dataTask(with: urlComponents.url!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode([NominationStructure].self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
private func FetchDestCoordinates(completion: #escaping ([NominationStructure]) -> ()) {
let scheme = "https"
let host = "nominatim.openstreetmap.org"
let path = "/search"
let queryItemCountry = URLQueryItem(name: "country", value: "Sweden")
let queryItemCity = URLQueryItem(name: "city", value: "Stockholm")
let queryItemStreet = URLQueryItem(name: "street", value: self.dest.input)
let queryItemFormat = URLQueryItem(name: "format", value: "json")
var urlComponents = URLComponents()
urlComponents.scheme = scheme
urlComponents.host = host
urlComponents.path = path
urlComponents.queryItems = [queryItemCountry, queryItemCity, queryItemStreet, queryItemFormat]
URLSession.shared.dataTask(with: urlComponents.url!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode([NominationStructure].self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
private func FetchOriginID(completion: #escaping (NearbyStopsStructure) -> ()) {
let nearbyStopsKey = "8444f9a2f75f4c27937a7165abd532a0"
let stopIDUrl = URL(string: "http://api.sl.se/api2/nearbystopsv2.json?key=\(nearbyStopsKey)&originCoordLat=\(self.origin.lat)&originCoordLong=\(self.origin.lon)&maxNo=1")
URLSession.shared.dataTask(with: stopIDUrl!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode(NearbyStopsStructure.self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
private func FetchDestID(completion: #escaping (NearbyStopsStructure) -> ()) {
let nearbyStopsKey = "8444f9a2f75f4c27937a7165abd532a0"
let stopIDUrl = URL(string: "http://api.sl.se/api2/nearbystopsv2.json?key=\(nearbyStopsKey)&originCoordLat=\(self.dest.lat)&originCoordLong=\(self.dest.lon)&maxNo=1")
URLSession.shared.dataTask(with: stopIDUrl!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode(NearbyStopsStructure.self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
}

Api Service Function Can Not Fetch Data At First Trial SwiftUI

This API function of mine called fetchAuthorArticles can not fetch JSON data at the first tap. If I close the sheet and tap it once more it does fetch JSON and fills SelectedAuthorView screen accordingly. What might be the reason?
class AuthorService: ObservableObject {
#Published var authorArticles = AuthorList(author: Author(author_id: 0, articles: 0, newspaper_image: "", author_name: "", author_image: "", newspaper: ""), articles: [])
func fetchAuthorArticles(authorID: Int){
isLoadingPage = true
if let selectedAuthorUrl = URL(string:"http://author/article/list/\(authorID)/") {
let session = URLSession(configuration: .default)
var request = URLRequest(url: selectedAuthorUrl)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
print(data)
print(response)
print(error)
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let results = try decoder.decode(AuthorArticleList.self, from: safeData)
DispatchQueue.main.async {
self.authorArticles = results
self.isLoadingPage = false
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
}
AuthorArticleList Model
struct AuthorArticleList:Decodable {
var author: Author
var articles: [Articles]
}
struct Author: Decodable, Identifiable {
var id: Int {
return author_id
}
var author_id: Int
var articles: Int
var newspaper_image: String
var author_name: String
var author_image: String
var newspaper: String
}
struct Articles: Decodable, Identifiable {
var id: Int {
return article_id
}
var article_id: Int
var title: String
var url: String
var abstract: String
var article_date: String
}
SelectedAuthorView
struct SelectedAuthorView: View {
#Binding var selectedAuthor: AuthorPost
#ObservedObject var service = AuthorService()
var cellNumber: Int = 0
let columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 0), count: 2)
var body: some View {
ScrollView(.vertical, showsIndicators: true) {
LazyVGrid(columns: columns, spacing: 0) {
ForEach(Array(service.authorArticles.articles.enumerated()), id: \.1.id) {i, post in
AuthorArticleListItem(articlePost: post, cellNumber: i)
}
.background(Color(.white))
if service.isLoadingPage {
ProgressView()
.padding()
.offset(x: UIScreen.main.bounds.width / 4)
}
}
.onAppear {
self.service.fetchAuthorArticles(authorID: selectedAuthor.author_id)
}

POST request errors in swift

I am new to swift (I am more used to python) and I am trying to send a POST request to a server and use the JSON response. I have been able to make the POST request and retrieve data and print it using this code that I got from a tutorial but the variable server.authenticated wasn't changing and after I made some changes now I'm getting two errors: Instance member 'authenticated' cannot be used on type 'HttpAuth' and 'String' is not convertible to 'Any'
Could someone please help?
import SwiftUI
import Combine
struct ServerMessage : Decodable {
let status, message: String
}
class HttpAuth: ObservableObject {
var didChange = PassthroughSubject<HttpAuth, Never>()
#Published var authenticated = false {
didSet{
didChange.send(self)
}
}
func checkDetails(username: String, password: String) {
guard let url = URL(string: "https://example.com") else { return }
let body: [String: String] = ["username": username, "password": password ]
let finalBody = try! JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = finalBody
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
let finalData = try! JSONDecoder().decode(ServerMessage.self, from: data)
print(finalData)
if finalData.status == "ok" {
DispatchQueue.global().async {
HttpAuth.authenticated = true //ERROR: Instance member 'authenticated' cannot be used on type 'HttpAuth'
print("correct credentials")
}
}
}.resume()
}
}
struct loginView: View {
#State private var username: String = ""
#State private var password: String = ""
#State var server = HttpAuth()
var body: some View {
VStack{
TextField("Username", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 200, height: nil)
.multilineTextAlignment(.center)
.disableAutocorrection(true)
.accessibility(identifier: "Username")
.autocapitalization(.none)
SecureField("Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(width: 200, height: nil)
.multilineTextAlignment(.center)
.disableAutocorrection(true)
.accessibility(identifier: "Password")
Button(action: {
self.server.checkDetails(username: self.username, password: self.password)
//print(self.username + ", " + self.password)
}) {
HStack{
Spacer()
Text("Login").font(.headline).foregroundColor(.white)
Spacer()
}.padding(.vertical, 10)
.background(Color.red)
.padding(.horizontal, 40)
}
if self.server.authenticated {
Text("Correct Credentials")
} //ERROR: 'String' is not convertible to 'Any'
}
}
}
struct loginView_Previews: PreviewProvider {
static var previews: some View {
loginView()
}
}
You need to pass HttpAuth to your loginView like
struct loginView_Previews: PreviewProvider {
static var previews: some View {
loginView().environmentObject(HttpAuth())
}
}
then in your LoginView declare EnvironmentObject of HttpAuth
struct loginView: View {
#State private var username: String = ""
#State private var password: String = ""
#EnvironmentObject var server : HttpAuth
...

Get value from JSON API response in SwiftUI Xcode

I need to post a https request to login view (SwiftUI), my code follow I have in getres.swift:
so I neet to get value from response and put it in the text
import Foundation
import Combine
struct result: Decodable {
let res, ordercount, rate: String
}
class getres: ObservableObject {
let objectWillChange = PassthroughSubject<getres, Never>()
#Published var authenticated = ""
#Published var todos = [result]() {
didSet {
objectWillChange.send(self)
}
}
func auth(username: String, password: String) {
guard let url = URL(string: "http://company.com/auth.php") else { return }
let body: [String: String] = ["username": username, "password": password]
let finalBody = try! JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = finalBody
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data=data else{return}
let fineldata=try! JSONDecoder().decode(result.self, from: data)
DispatchQueue.main.async {
self.todos = [fineldata]
self.authenticated=fineldata.res
}
print(fineldata)
}.resume()
}
}
and in the login page I try to show other view
in this code I will get values from function as json response I will get ordercount and rate I put it in the other view
import SwiftUI
struct ContentView: View {
#State private var username: String = ""
#State private var password: String = ""
#ObservedObject var manager = getres()
var body: some View {
VStack(alignment: .leading) {
if manager.authenticated == "2"{
userdetails()
}else{
Text("Username")
TextField("placeholder", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.green)
.autocapitalization(.none)
Text("Password")
SecureField("placeholder", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.green)
Button(action: {
self.manager.auth(username: self.username, password: self.password)
}) {
HStack{
Spacer()
Text("Login")
Spacer()
}
.accentColor(Color.white)
.padding(.vertical, 10)
.background(Color.red)
.cornerRadius(5)
.padding(.horizontal, 40)
}
}.padding()}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
and in userdetails this so I need to get values from response
struct userdetails: View {
#State var controller = getres()
var body : some View{
ScrollView(Axis.Set.vertical, showsIndicators: true) {
VStack(spacing: 20){
Image("wlogo").renderingMode(.original); HStack(spacing: 15){
Spacer()
VStack(alignment: .center, spacing: 10) {
Text(???).foregroundColor(Color.white).bold()
.font(.largeTitle)
Text("")
.foregroundColor(Color.white)
.font(.headline) }
Spacer()
}}}}
how can I get ordercount from response and put in
text(???)
in the view userdetails
for example controller.todos.ordercount
I get this error
Value of type '[result]' has no member 'ordercount'
when I try
Text(controller.todos.ordercount)
json response
{"res":"2","ordercount":"20","rate":"5"}
UPDATED ANSWER
1.) it would be helpful if you copy your code in just one part - this is easier for all of us to copy
2.) you should try out your code yourself before you copy or change something in your code manually. it is annoying to find mistakes like:
if manager.authenticated == "2"{
userdetails()
} else{
Text("Username")
TextField("placeholder", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.green)
.autocapitalization(.none)
Text("Password")
SecureField("placeholder", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.green)
Button(action: {
self.manager.auth(username: self.username, password: self.password)
}) {
HStack{
Spacer()
Text("Login")
Spacer()
}
.accentColor(Color.white)
.padding(.vertical, 10)
.background(Color.red)
.cornerRadius(5)
.padding(.horizontal, 40)
}
}.padding()}
}
where you are adding .padding() to an if-statement....
3.) you should name class names in that way people can understand what the class if for and start with a capitalized letter like Apple does with all class names
4.) you should also begin View names capitalized (like Apple does)
5.) since i have to access to your page and you did not provide example data and/or password i have no idea what data are coming there...
here is my solution code so far:
i added some faking data, because i cannot access your data ....so you can see something in details.
struct ToDo: Decodable, Identifiable {
var id = UUID().uuidString
let res, ordercount, rate: String
}
class ToDoGetter: ObservableObject {
let objectWillChange = PassthroughSubject<ToDoGetter, Never>()
#Published var authenticated = ""
#Published var todos = [ToDo]() {
didSet {
objectWillChange.send(self)
}
}
let someFakingTodos = [
ToDo(res: "a", ordercount: "5", rate: "75%"),
ToDo(res: "b", ordercount: "52", rate: "5%"),
ToDo(res: "c", ordercount: "566", rate: "7%"),
ToDo(res: "d", ordercount: "53", rate: "33%"),
ToDo(res: "e", ordercount: "15", rate: "44%"),
ToDo(res: "f", ordercount: "345", rate: "10%")
]
func auth(username: String, password: String) {
guard let url = URL(string: "http://company.com/auth.php") else { return }
let body: [String: String] = ["username": username, "password": password]
let finalBody = try! JSONSerialization.data(withJSONObject: body)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = finalBody
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data=data else{return}
let fineldata=try! JSONDecoder().decode(ToDo.self, from: data)
DispatchQueue.main.async {
self.todos = [fineldata]
self.authenticated=fineldata.res
}
print(fineldata)
}.resume()
}
}
struct ContentView: View {
#EnvironmentObject var todoGetter : ToDoGetter
#State private var username: String = ""
#State private var password: String = ""
#State var navigateToDetail : Bool = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
if todoGetter.authenticated == "2"{
Userdetails().environmentObject(todoGetter)
} else{
Text("Username")
TextField("placeholder", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.green)
.autocapitalization(.none)
Text("Password")
SecureField("placeholder", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.border(Color.green)
NavigationLink(destination: Userdetails(), isActive: self.$navigateToDetail) {
EmptyView() }
.hidden()
.padding()
Button(action: {
self.todoGetter.auth(username: self.username, password: self.password)
self.navigateToDetail.toggle()
}) {
HStack{
Spacer()
Text("Login")
Spacer()
}
.accentColor(Color.white)
.padding(.vertical, 10)
.background(Color.red)
.cornerRadius(5)
.padding(.horizontal, 40)
}
}
}
}
}
}
struct Userdetails: View {
#EnvironmentObject var todoGetter : ToDoGetter
var body : some View{
VStack(spacing: 20) {
Image("wlogo").renderingMode(.original); HStack(spacing: 15){
Spacer()
List(todoGetter.someFakingTodos) { todo in
VStack(alignment: .center, spacing: 10) {
HStack {
Text(todo.res).foregroundColor(Color.white).bold()
.font(.largeTitle)
Text(todo.ordercount)
.foregroundColor(Color.white)
.font(.headline)
Text(todo.rate)
.foregroundColor(Color.white)
.font(.headline)
}
}.background(Color.black)
Spacer()
}
}
}
}
}
OLD ANSWER
you call getres 2 times. you have to call it just once and then give the value to the detailview.
the model should only be created once per app.