list view is not updating in swift UI - swiftui

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()

Related

#EnvironmentVariable not being passed to child views

I have 2 views - which I want to navigate between, and have a viewModel object shared between them as an EnvironmentObject. I keep getting the "A View.environmentObject(_:) for TidesViewModel may be missing as an ancestor of this view." error - but none of the solutions I have found seem to work. Please find below my code. The following is the first view:
import SwiftUI
struct ContentView: View {
#ObservedObject var tidesViewModel: TidesViewModel = TidesViewModel()
var body: some View {
NavigationView
{
List
{
ForEach (tidesViewModel.stations.indices) {
stationid in
HStack
{
NavigationLink(destination: TideDataView(stationId: tidesViewModel.stations[stationid].properties.Id))
{
Text(tidesViewModel.stations[stationid].properties.Name)
}
}
}
}
}.environmentObject(tidesViewModel)
}
}
and below is the child view - which throws the error.
import SwiftUI
struct TideDataView: View {
#EnvironmentObject var tidesViewModel : TidesViewModel
var stationId: String
init(stationId: String) {
self.stationId = stationId
getTidesForStation(stationId: stationId)
}
var body: some View {
List
{
ForEach (tidesViewModel.tides.indices)
{
tideIndex in
Text(tidesViewModel.tides[tideIndex].EventType)
}
}
}
func getTidesForStation(stationId: String)
{
tidesViewModel.getTidalData(forStation: stationId)
}
}
For completeness - below is the Observable object being passed:
import Foundation
import SwiftUI
class TidesViewModel: ObservableObject
{
private var tideModel: TideModel = TideModel()
var currentStation: Feature?
init()
{
readStations()
}
var stations: [Feature]
{
tideModel.features
}
var tides: [TidalEvent]
{
tideModel.tides
}
func readStations()
{
let stationsData = readLocalFile(forName: "stations")
parseStations(jsonData: stationsData!)
}
private func readLocalFile(forName name: String) -> Data? {
do {
if let bundlePath = Bundle.main.path(forResource: name,
ofType: "json"),
let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
return jsonData
}
} catch {
print(error)
}
return nil
}
private func parseStations(jsonData: Data) {
do {
let decodedData: FeatureCollection = try JSONDecoder().decode(FeatureCollection.self,
from: jsonData)
//print(decodedData)
tideModel.features = decodedData.features
} catch let jsonError as NSError{
print(jsonError.userInfo)
}
}
func getTidalData(forStation stationId: String)
{
let token = "f43c068141bb417fb88909be5f68781b"
guard let url = URL(string: "https://admiraltyapi.azure-api.net/uktidalapi/api/V1/Stations/" + stationId + "/TidalEvents") else {
fatalError("Invalid URL")
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(token, forHTTPHeaderField: "Ocp-Apim-Subscription-Key")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else{ return }
do{
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64
let decodedData = try decoder.decode([TidalEvent].self, from: data)
DispatchQueue.main.async {
self.tideModel.tides = decodedData
}
}catch let error{
print(error)
}
}.resume()
}
}
You need to attach the modifier .environmentObject() directly to the view that will receive it. So, in your case, attach it to TideDataView, not to the NavigationView around it.
Your code would look like this:
NavigationLink {
TideDataView(stationId: tidesViewModel.stations[stationid].properties.Id)
.environmentObject(tidesViewModel)
} label: {
Text(tidesViewModel.stations[stationid].properties.Name)
}
// Delete the modifier .environmentObject() attached to the NavigationView

Swiftui Dynamic Cloudkit predicate results not filling array

I hope someone can help me - I have a dynamic cloudkit array which works fine up until it gets to the List part of my view. In the quertresultBlock when I print returnedItems the records are all there and filtered inline with my predicate. However, the staff array is empty. Its driving me mad. Could someone help please?
struct MarketingModel: Hashable {
let firstName: String
let lastName: String
let country: String
let department: String
let phoneNumber: String
let job: String
let headshot: URL?
let record: CKRecord
}
struct predicateFilter: View {
#State var text: String = ""
#State var staff: [MarketingModel] = []
#State private var countryselected: Country = .Management
var body: some View {
List {
ForEach(staff, id: \.self) { thepeople in
VStack (alignment: .leading) {
Text(thepeople.firstName)
extension predicateFilter {
init(filter: String) {
UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(red: 116/255, green: 152/255, blue: 192/255, alpha: 1.0)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
//func fetchItems() {
//let predicate = NSPredicate(value: true)
let predicate = NSPredicate(format: "department == %#", filter)
let query = CKQuery(recordType: "Staff", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "lastName", ascending: true)]
let queryOperation = CKQueryOperation(query: query)
var returnedItems: [MarketingModel] = []
queryOperation.recordMatchedBlock = { (returnedRecordID, returnedResult) in
switch returnedResult {
case .success(let record):
guard let firstName = record["firstName"] as? String else { return }
guard let lastName = record["lastName"] as? String else { return }
guard let department = record["department"] as? String else { return }
guard let country = record["country"] as? String else { return }
guard let mobile = record["phone"] as? String else { return }
guard let job = record["job"] as? String else { return }
let imageAsset = record["headshot"] as? CKAsset
let imageURL = imageAsset?.fileURL
returnedItems.append(MarketingModel(firstName: firstName, lastName: lastName, country: country, department: department, phoneNumber: mobile, job: job, headshot: imageURL, record: record))
case .failure(let error):
print("Error recordMatchedBlock: \(error)")
}
}
queryOperation.queryResultBlock = { [self] returnedResult in
print("RETURNED RESULT \(returnedResult)")
DispatchQueue.main.async {
staff = returnedItems
print(staff)
}
}
addOperation(opertaion: queryOperation)
}
func addOperation(opertaion: CKDatabaseOperation) {
CKContainer.default().publicCloudDatabase.add(opertaion)
print("working")
}
}

Dynamically Changing the Base Currency

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()
}
}

waiting until URLSession.shared.dataTask ends

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
}

How get list value in Userdefaults

I have data with json in one func 1 get data have 1 list value but I go out func call value with userdefault I get 1 value.
func getdevices() {
var token = UserDefaults.standard.string(forKey: "token")
print("token", token)
var username = UserDefaults.standard.string(forKey: "username")
print("username", username)
let url: URL = URL(string: "/public/devices")!
let request: NSMutableURLRequest = NSMutableURLRequest(url: url)
request.httpMethod = "Get"
request.setValue(token , forHTTPHeaderField: "token")
request.setValue(username, forHTTPHeaderField: "username")
var responseError: NSError?
var response: URLResponse?
var urlData: Data?
do {
urlData = try NSURLConnection.sendSynchronousRequest(request as URLRequest, returning:&response)
} catch let error as NSError {
responseError = error
urlData = nil
}
if ( urlData != nil ) {
let res = response as! HTTPURLResponse!;
print("Response code: %ld tra ve" , res?.statusCode);
if ((res?.statusCode)! >= 200 && (res?.statusCode)! < 300)
{
//let responseData:NSString = NSString(data:urlData!, encoding:String.Encoding.utf8.rawValue)!
if let json = try? JSONSerialization.jsonObject(with: urlData!, options: []) as? [String : Any],
//let dataArray = json?["status"] as? [String : Any],
let items = json?["units"] as? [[String : Any]] {
for item in items {
var lat = item["latitude"] as? String
UserDefaults.standard.set(lat, forKey: "latitude")
var test = UserDefaults.standard.double(forKey: "latitude")
print("test", test)
var long = item["longitude"] as? String
UserDefaults.standard.set(long, forKey: "longitude")
print("long", long)
var devid = item["devid"] as? String
UserDefaults.standard.set(devid, forKey: "devid")
var devname = item["devname"] as? String
UserDefaults.standard.set(devname, forKey: "devname")
var speed = item["speed"] as? String
UserDefaults.standard.set(speed, forKey: "speed")
var statustt = item["status"] as? String
UserDefaults.standard.set(statustt, forKey: "statusxe")
var drivername = item["drivername"] as? String
UserDefaults.standard.set(drivername, forKey: "drivername")
var address = item["address"] as? String
UserDefaults.standard.set(address, forKey: "address")
print("address", address)
}
}
}
}
}
But when the app exists the func it doesn't get the list data, it has one value.
override func viewDidAppear(_ animated: Bool) {
GMSServices.provideAPIKey("AIzaSyB2IJzNc2s5km1qvdwAePDEat1OdPsOFVA")
guard let address = UserDefaults.standard.string(forKey: "address") else {return}
getdevices()
var lat = UserDefaults.standard.double(forKey: "latitude")
var long = UserDefaults.standard.double(forKey: "longitude")
var devname = UserDefaults.standard.string(forKey: "devname")
var speed = UserDefaults.standard.string(forKey: "speed")
print("lat", lat)
print("long", long)
print("address***", address)
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude: long, zoom: 10)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: camera)
view = mapView
let marKer = GMSMarker()
marKer.position = CLLocationCoordinate2D(latitude: lat, longitude: long)
marKer.icon = UIImage(named: "vehicle_105")
marKer.title = "Biển số \(devname)"
//marKer.title = "Speed \(speed)"
marKer.map = mapView
}
You can only store a single object/value under a single key in UserDefaults. The problem is, you are overwriting the value under the same key in each iteration of your loop. What you should do is create an array before the loop, add the values to that array inside the loop and store the array in UserDefaults after the loop finished its iterations.
if let items = json?["units"] as? [[String : Any]] {
var latitudes = [String]()
var longitudes = [String]()
var devids = [String]()
var devnames = [String]()
var speeds = [String]()
var statustts = [String]()
var drivernames = [String]()
var addresses = [String]()
for item in items {
let lat = item["latitude"] as? String
latitudes.append(lat)
let long = item["longitude"] as? String
longitudes.append(long)
let devid = item["devid"] as? String
devids.append(devid)
var devname = item["devname"] as? String
devnames.append(lat)
var speed = item["speed"] as? String
speeds.append(speed)
var statustt = item["status"] as? String
statuses.append(statustt)
var drivername = item["drivername"] as? String
drivernames.append(drivername)
var address = item["address"] as? String
addresses.append(address)
}
UserDefaults.standard.set(latitudes, forKey: "latitude")
UserDefaults.standard.set(longitudes, forKey: "longitude")
UserDefaults.standard.set(devids, forKey: "devid")
UserDefaults.standard.set(devnames, forKey: "devname")
UserDefaults.standard.set(speeds, forKey: "speed")
UserDefaults.standard.set(statustts, forKey: "statusxe")
UserDefaults.standard.set(drivername, forKey: "drivername")
UserDefaults.standard.set(addresses, forKey: "address")
}