I have the below code. I got print "OK Call" before to print (finalData). I know the reason is URLSession goes in parallel, but my question is: How could avoid the parallel task and wait until URLSession ends ? THANKS
import SwiftUI
struct ContentView: View {
var body: some View {
Button ("Action", action: {
self.checkLogin(username:"test", password:"123456")
print ("OK Call")
} ) }
func checkLogin (username: String, password: String) {
var body: [String:String] = [:]
guard let url = URL(string: "http://test/apple/login.php") else { return }
body = ["user": username, "password": password]
let finalBody = try! JSONEncoder().encode (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
if let error = error { print ("Error: \(error)")
return
}
if let data = data {
let finalData = try! JSONDecoder().decode(ServerMessage.self, from: data)
print (finalData)
return
}
}.resume()
}
}
struct ServerMessage: Decodable {
let result: String
let nuser: String
}
Related
Elaborating the Problem in depth with code
I have a data model (API response) using which I am creating a list. The list items should also display their details in detail view. The problem is details of list items are coming from a different API other than the API used for creating the list. The id of one item is passed to API which in response provides the details of the item.
This is the data model(including items only problem specific):
struct TrackSample : Codable, Identifiable {
let id = UUID()
let success : Bool
let message : String
let trackResponse : [TrackResponse]
enum CodingKeys: String, CodingKey{
case success = "IsSuccess"
case message = "Message"
case trackResponse = "ResponseData"
}}
struct TrackResponse : Codable, Identifiable {
let id = UUID()
let patientID : String
let name : String
let testCount : Int
//..some more//
enum CodingKeys: String, CodingKey {
case patientID = "PatientId"
case name = "Name"
case status = "Status"
case testCount = "NoOfTests"
}}
ViewModel to fetch API response ( TrackResource() is a different class which implements the networking call):
class TrackViewModel : ObservableObject
{
#Published var trackReport = [TrackResponse]()
#Published var navigate:Bool = false
//other var
private let trackResource = TrackResource()
func getTrack()
{
//code to get data based on which button is clicked in Options
screen.
There are five options to choose:
if else statement follows//
centerID = "100"
let trackRequest = TrackRequest(CenterId:centerID,
SearchText:searchText, StartDate:startDate, EndDate:endDate)
trackResource.track(trackRequest: trackRequest)
{
response in
if(response?.success==true)
{
DispatchQueue.main.async {
self.navigate = true
self.trackReport = response?.trackResponse ?? []
}
}
else
{
DispatchQueue.main.async {
self.errorMessage = response?.message ?? "No Data"
// self.isPresentingAlert
}
}}}}
The view YdaySample which represents the list :
struct YdaySample: View {
#ObservedObject var tracking : TrackViewModel
var body: some View {
NavigationView{
List{
ForEach(tracking.trackReport)
{ truck in
NavigationLink(destination: TrackDetail(track:truck))
{
YdayRow(truck: truck)
}
}
if(tracking.trackReport.isEmpty){
Text("No Record Found !")
//formatting
}}}}}
struct YdaySample_Previews: PreviewProvider {
static var previews: some View {
YdaySample(tracking: TrackViewModel())
}}
The YdayRow() :
struct YdayRow: View {
var truck : TrackResponse
var body: some View {
VStack{
HStack{
Text(truck.name)
//formatting//
}
HStack{
Text("P.Id:")
//formatting//
Text(truck.patientID)
//formatting//
Spacer()
Text("Total Test:")
//formatting//
Text("\(truck.testCount)")
//formatting//
}}}}
struct YdayRow_Previews: PreviewProvider {
static var previews: some View {
YdayRow(truck: TrackResponse(patientID: "1", name: "test",
status: "null", crmNo: "null",
recordDate: "somedate", uniqueID: "null", testCount: 4))
}
}
TrackDetail() updated:
struct TrackDetail: View {
var track: TrackResponse
#State var patientDetail: DetailResponse
var body: some View {
VStack{
HStack{
Text(track.name)
//formatting
}.frame(maxWidth: .infinity, alignment: .center)
HStack{
Text("P.Id:")
//formatting
Text(track.patientId)
//formatting
}
List{ForEach(patientDetail.detailResponse)
{detail in
HStack
{ Text("Barcode: ")
Text(detail.barcode)
}
}
}.onAppear{
Task{
do{
try await getDetail()
}catch{
Alert(title: Text("Error"),message: Text("Not Found"),
dismissButton: .cancel())
}}}}}
func getDetail() async throws{
var urlComponents = URLComponents()
urlComponents.scheme = "http"
urlComponents.host = "xx.xxx.xx.xx"
urlComponents.path = "/api/reports/getalltests"
urlComponents.queryItems = [URLQueryItem(name: "centerId", value:
("\(668)")),URLQueryItem(name: "patientId", value: "\
(track.patientId)")]
let url = urlComponents.url
var request = URLRequest(url: url!)
print(request)
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("application/json", forHTTPHeaderField: "Content-
Type")
request.setValue("Basic xcvgjhalddjdj",forHTTPHeaderField:
"Authorization")
// Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data,
response, error) in
// Check if Error took place
if let error = error {
print("Error took place \(error)")
return
}
// Read HTTP Response Status code
if let response = response as? HTTPURLResponse {
print("Response HTTP Status code: \(response.statusCode)")
}
if let data = data
{
let dataString = String(data:data,encoding: .utf8)
print(dataString!)
do{
let json = try JSONDecoder().decode(DetailResponse.self,
from: data)
print(json)
DispatchQueue.main.async {
self.patientDetail = json
}
}catch{
print("error \(error)")
}
}
};task.resume()
}}
struct TrackDetail_Previews: PreviewProvider {
static var previews: some View {
TrackDetail(track: TrackResponse(patientId: "4193716", name: "Dummy
Report HCV RNA", status: "null", crmNo: "null", recordDate: "2022-
04-15", uniqueId: "null", testCount: 10), patientDetail:
DetailResponse(success: false, message: "mess", detailResponse:
[]))
}}
print(request) is printing right URL as desired also the patientID is correct in url (http://xxx..x.x/api/reports/getalltests/centerId=668&patientId=(tapped id))
But it is throwing error in decoding saying "Authrization has been denied for this request" error keynotfound(codingkeys(stringvalue: "ResponseData", intvalue:nil), Swift.decodingerror.context(codingpath: [],debugdescription: "No value associated with key CodingKeys(stringvalue: "ResponseData", intvalue:nil)("ResponseData", underlyingError:nil)
struct DetailResponse : Codable{
let success : Bool ?
let message : String
let detailResponse : [PatResponse]
enum CodingKeys: String, CodingKey{
case success = "IsSuccess"
case message = "Message"
case patResponse = "ResponseData"
}}
struct PatResponse : Codable, Identifiable {
var barcode: String
var:id String {barcode}
let testname : String?
let packageName : String?
let status: String?
let sampleRecievedTime: String?
let recordDate: String
enum CodingKeys: String, CodingKey {
case packageName = "PackageName"
case testname = "TestName"
case status = "Status"
case sampleRecievedTime = "SampleRecievedTime"
case recordDate = "RecordDate"
case barcode = "Barcode"
}}
///////**************///////////////////
The detail view is showing name and ID as they are coming from TrackResponse
but status and barcode are not as they are from different API.
When the user click/tap an item in list, the patientID and centerId is sent as query param in GET request which in response will provide the details associated with the given patientId (center ID is constant). How to implement this?
How about something like this?
struct TrackDetail: View{
var track: TrackResponse
#State var details: TrackDetails?
var body: some View{
HStack{
//Detail implementation
}.onAppear{
Task{
do{
try await doStuff()
}catch{
//Alert
}
}
}
}
func doStuff() async throws{
// pull Details from Api
}
}
I have a currency API that returns a JSON object containing a strange arrangement: the base currency is used as a label. Typical currency APIs have labels like "base", "date", "success", and "rates", but this API doesn't have any of those.
{
"usd": {
"aed": 4.420217,
"afn": 93.3213,
"all": 123.104693,
"amd": 628.026474,
"ang": 2.159569,
"aoa": 791.552347,
"ars": 111.887966,
"aud": 1.558363,
"awg": 2.164862,
"azn": 2.045728,
"bam": 1.9541,
"bbd": 2.429065,
"bch": 0.001278
}
}
The "usd" (US dollars) at the top is called the base or home currency. At the moment the storage structure and state parameter are hardcoded to "usd" which prevents using exchange rates with other base currencies. The exchange rate API works great for a base currency of US dollars.
I need help modifying things so that I can download the exchange rates with different base currencies. For example, can I use a string variable in the storage model and state parameter? Any enlightenment will be greatly appreciated.
struct RateResult: Codable {
let usd: [String: Double]
}
#State private var results = RateResult(usd: [:])
struct ContentView: View {
var body: some View {
}
func UpdateRates() {
let baseUrl = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/"
let baseCur = baseCurrency.baseCur.baseS // usd
let requestType = ".json"
guard let url = URL(string: baseUrl + baseCur + requestType) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(RateResult.self, from: data) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.results = decodedResponse
// save off currency exchange rates
}
// everything is good, so we can exit
return
}
}
// if we're still here it means there was a problem
print("Currency Fetch Failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
import SwiftUI
//You can't use the standard Codable for this. You have to make your own.
class BaseCurrency: Codable {
let id = UUID()
var baseCurrencies: [String : [String: Double]] = [:]
required public init(from decoder: Decoder) throws {
do{
print(#function)
let baseContainer = try decoder.singleValueContainer()
let base = try baseContainer.decode([String : [String: Double]].self)
for key in base.keys{
baseCurrencies[key] = base[key]
}
}catch{
print(error)
throw error
}
}
//#State should never be used outside a struct that is a View
}
struct CurrencyView: View {
#StateObject var vm: CurrencyViewModel = CurrencyViewModel()
var body: some View {
VStack{
List{
if vm.results != nil{
ForEach(vm.results!.baseCurrencies.sorted{$0.key < $1.key}, id: \.key) { key, baseCurrency in
DisclosureGroup(key){
ForEach(baseCurrency.sorted{$0.key < $1.key}, id: \.key) { key, rate in
HStack{
Text(key)
Text(rate.description)
}
}
}
}
}else{
Text("waiting...")
}
}
//To select another rate to go fetch
RatesPickerView().environmentObject(vm)
}.onAppear(){
vm.UpdateRates()
}
}
}
struct RatesPickerView: View {
#EnvironmentObject var vm: CurrencyViewModel
var body: some View {
if vm.results != nil{
//You can probaly populate this picker with the keys in
// baseCurrency.baseCur.baseS
Picker("rates", selection: $vm.selectedBaseCurrency){
ForEach((vm.results!.baseCurrencies.first?.value.sorted{$0.key < $1.key})!, id: \.key) { key, rate in
Text(key).tag(key)
}
}
}else{
Text("waiting...")
}
}
}
class CurrencyViewModel: ObservableObject{
#Published var results: BaseCurrency?
#Published var selectedBaseCurrency: String = "usd"{
didSet{
UpdateRates()
}
}
init() {
//If you can .onAppear you don't need it here
//UpdateRates()
}
func UpdateRates() {
print(#function)
let baseUrl = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/"
let baseCur = selectedBaseCurrency // usd
let requestType = ".json"
guard let url = URL(string: baseUrl + baseCur + requestType) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do{
let decodedResponse = try JSONDecoder().decode(BaseCurrency.self, from: data)
DispatchQueue.main.async {
if self.results == nil{
//Assign a new base currency
self.results = decodedResponse
}else{ //merge the existing with the new result
for base in decodedResponse.baseCurrencies.keys{
self.results?.baseCurrencies[base] = decodedResponse.baseCurrencies[base]
}
}
//update the UI
self.objectWillChange.send()
}
}catch{
//Error thrown by a try
print(error)//much more informative than error?.localizedDescription
}
}
if error != nil{
//data task error
print(error!)
}
}.resume()
}
}
struct CurrencyView_Previews: PreviewProvider {
static var previews: some View {
CurrencyView()
}
}
I'm currently developing an application using SwiftUI.
I want to close a sheet after an API connection finish using a closure method.
So I tried to do that with the code below, but in the case of these codes, they don't work well...
How could I solve this problem?
Here are the codes:
TestSheet.swift
import SwiftUI
struct TestSheet: View {
#EnvironmentObject var appState: AppState
#State var id:Int = 1
#State var memo:String = "new Memo"
#State var isFinish:Bool = false
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView{
VStack{
Button(action: {
appState.makeUpDate(
pk:id, memo:memo, finish: {returnData in
isFinish = returnData
}
)
if(isFinish){
self.presentationMode.wrappedValue.dismiss()
}
})
{
Text("UPDATE")
}
}
}
}
}
AppState.swift
...
func makeUpDate(pk the_pk:Int, memo the_memo:String, finish:#escaping(Bool)->Void) {
var isFinish:Bool = false
let endpoint: String = "https://sample.com/api/info/\(the_pk)/"
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.addValue("token xxxxxxxxxx", forHTTPHeaderField: "authorization")
urlRequest.httpMethod = "PUT"
urlRequest.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
let newInfo:[String:Any]=["memo":the_memo]
let jsonInfo: Data
do {
jsonInfo = try JSONSerialization.data(withJSONObject: newInfo, options: [])
urlRequest.httpBody = jsonInfo
} catch {
print("Error: cannot create JSON from newInfo")
return
}
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
guard error == nil else {
print("error calling Put")
print(error!)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard let response = response as? HTTPURLResponse else {
print("Error: did not response data")
return
}
print("The response code is \(response.statusCode)")
// parse the result as JSON, since that's what the API provides
do {
guard let receivedData = try JSONSerialization.jsonObject(with: responseData,
options: []) as? [String: Any] else {
print("Could not get JSON from responseData as dictionary")
return
}
print("The request is: " + receivedData.description)
} catch {
print("error parsing response from PUT")
return
}
DispatchQueue.main.async {
isFinish = true
finish(isFinish)
}
}
task.resume()
}
...
Xcode:Version 12.0.1
The isFinish is updated asynchronously, so we should react on it in different place
var body: some View {
NavigationView{
VStack{
Button(action: {
appState.makeUpDate(
pk:id, memo:memo, finish: {returnData in
isFinish = returnData
}
)
})
{
Text("UPDATE")
}
}
.onChange(of: isFinish) { result in
if result {
self.presentationMode.wrappedValue.dismiss() // << here !!
}
}
}
}
I'm currently developing an application using SwiftUI.
I want to show two kinds of data in two lists in a view.
(1)start_date
(2)the temperatures of each day from start_date(1) to today
in the case of my code, (1)start_date is shown well, but (2)the temperatures have a problem because each list should show different data but they show the same data in each list...
Although I can check when each method is called, they make deferent data in the console like below, the simulator shows the same data...
The result in the console:
temp_info
25.7
24.9
temp_info
25.6
25.7
24.9
24.1
23.5
25.7
26.4
23.7
23.0
24.4
26.1
How could I resolve this problem?
Here are the codes:
JsonModel.swift
import Foundation
struct DbVegetableInfos: Codable,Identifiable {
var id: Int
var start_date: String
}
struct WeatherAveInfos:Codable,Identifiable {
var id: Int
var ave_temp: Float
}
AppState.swift
import SwiftUI
import Foundation
import Combine
import UIKit
class AppState: ObservableObject {
#Published var arrayDbVegetableInfos:[DbVegetableInfos]?
#Published var weatherAveInfos:[WeatherAveInfos]?
func makeGetCallVegetableInfos() {
// Set up the URL request
let endpoint: String = "https://sample.com/api/info/"
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.addValue("token xxxxxxxxxx", forHTTPHeaderField: "authorization")
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
DispatchQueue.main.async {
do{ self.arrayDbVegetableInfos = try JSONDecoder().decode([DbVegetableInfos].self, from: responseData)
}catch{
print("Error: did not decode")
return
}
}
}
task.resume()
}
}
func makeGetCallWeatherAveTemp(start_date:String ) {
// Set up the URL request
let endpoint: String = "https://sample.com/api/weather_ave/?start_date=\(start_date)"
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.addValue("token xxxxxxxxxx", forHTTPHeaderField: "authorization")
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET")
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
DispatchQueue.main.async {
do{ self.weatherAveInfos = try JSONDecoder().decode([WeatherAveInfos].self, from: responseData)
print("temp_info")
for info in self.weatherAveInfos!{
print(info.ave_temp)
}
}catch{
print("Error: did not decode")
return
}
}
}
task.resume()
}
HomeView.swift
import SwiftUI
struct HomeView: View {
#EnvironmentObject var appState: AppState
var body: some View {
NavigationView{
VStack{
ForEach(appState.arrayDbVegetableInfos ?? []){ info in
VStack{
VegetableInfoRow(info:info)
}.background(Color(.secondarySystemFill))
.cornerRadius(10)
.padding(.top)
.padding(.leading)
.padding(.bottom)
}
}.onAppear(){
appState.makeGetCallVegetableInfos()
}
}
}
}
VegetableInfoRow.swift
import SwiftUI
struct VegetableInfoRow: View {
#EnvironmentObject var appState: AppState
var info:DbVegetableInfos
var body: some View {
ScrollView(.horizontal) {
HStack{
VStack{
VStack{
Text("start_date:").padding()
Text(stringToStringDate(stringDate: info.start_date, format: "yyyy-MM-dd"))
}
}
Divider()
.padding()
VStack{
VStack{
Text("progress_temperature:").padding()
ForEach(appState.weatherAveInfos ?? []){ info in
Text(String(info.ave_temp))
}
}
}
}
}.onAppear(){
appState.makeGetCallWeatherAveTemp(start_date: info.start_date)
}
}
}
func stringToStringDate(stringDate: String, format:String) -> String {
let formatter: DateFormatter = DateFormatter()
formatter.calendar = Calendar(identifier: .gregorian)
formatter.dateFormat = "yyyy/MM/dd"
let newDate = formatter.date(from: stringDate)!
formatter.dateFormat = format
return formatter.string(from: newDate)
}
Xcode:Version 12.0.1
I have started project in swiftUI. I am receiving data from web services adding into array list when I am changing data in backend but list view is not updating.
import Foundation
import SwiftUI
import Combine
var orders_List: [OrdersModel] = []
struct OrdersModel: Identifiable,Hashable{
var id: String = UUID().uuidString
let order_no: String
let seat_name: String
let seat_no: String
let time: String
init(id: String, order_no: String,seat_name:String,seat_no:String,time:String){
self.id = id
self.order_no = order_no
self.seat_name = seat_name
self.seat_no = seat_no
self.time = time
}
}
class Order_Manager: ObservableObject {
var objectWillChange = ObservableObjectPublisher()
var fetchedSongsResults = [orders_List] {
willSet {
objectWillChange.send()
}
}
init() {
fetch_orders()
}
func fetch_orders() {
let defaults = UserDefaults.standard
let parameters = ["tokken": "a56af0a01e137f61a44a93398195f5db","order_status":"Submitted"]
guard let url = URL(string: "https://www.appvelo.com/golfcourseios/api/OrderApis/fetch_status_based_orders") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { return }
request.httpBody = httpBody
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:Any]
let posts = json["data"] as? [[String: Any]] ?? []
orders_List.removeAll()
for result in posts {
//print(json)
let id = result["id"] as! String
let order_no = result["order_no"] as! String
let seat_no = result["order_no"] as! String
let seat_name = result["seat_type_image"] as! String
let time = result["time"] as! String
let order_Model = OrdersModel(id: id,order_no: order_no, seat_name: seat_name, seat_no: seat_no,time:time)
orders_List.append(order_Model)
// print(order_Model)
}
} catch {
print(error)
}
}
}.resume()
}
}
struct ContentView: View {
var body: some View {
VStack{
List {
ForEach(orders_List){ person in
Text("\(person.order_no)")
}
}
}
}
}
As mentioned in comments, you need to fix your Combine syntax:
class Order_Manager: ObservableObject {
#Published var fetchedSongsResults = [orders_List]
}
You need to remove var objectWillChange = ObservableObjectPublisher()