How do I update my List using a ForEach on an EnvironmentObject? - swiftui

I am trying to update a List using an availableModels array inside an EnvironmentObject. For some reason it doesn't refresh my view but I have no idea why. Very confused about this so help would be much appreciated, I'm new to SwiftUI and transferring data between views. I'm pretty sure that the array is updating because when I print it out the values are correct, but the list doesn't update in the ForEach loop or in the Text fields.
This is my view with the List:
import SwiftUI
var selectedModel: String = "rdps"
struct ModelSelectView: View {
#EnvironmentObject var viewModel: WeatherData
var body: some View {
NavigationView {
List{
Group {
WeatherModelHeader()
}
if !viewModel.weatherArray.isEmpty {
ForEach(viewModel.weatherArray[0].availableModels, id: \.apiName) { model in
WeatherModelCell(weatherModel: model)
}
} else {
Text("No Weather 4 u")
}
Button(action: {
fetchModelInventory(for: viewModel)
}, label: {
Text("Fetch Inventory")
})
Text(String(viewModel.weatherArray[0].availableModels[0].apiName))
Text(String(viewModel.weatherArray[0].availableModels[1].apiName))
Text(String(viewModel.weatherArray[0].availableModels[2].apiName))
Text(String(viewModel.weatherArray[0].availableModels[3].apiName))
}
.navigationTitle("Models")
.onAppear(perform: {
fetchModelInventory(for: viewModel)
print("viewModel.weatherArray.availableModels \(viewModel.weatherArray[0].availableModels)")
})
}
}
}
//Layout for the header of the list
struct WeatherModelHeader: View {
var body: some View {
HStack {
Text("Model \nName")
.bold()
.frame(width: 60, alignment: .leading)
Text("Range")
.bold()
.frame(width: 80, alignment: .leading)
.padding(.leading, 30)
Text("Resolution")
.bold()
.frame(width: 85, alignment: .leading)
.padding(.leading, 30)
}
}
}
//create the layout for the weather model list
struct WeatherModelCell: View {
let weatherModel: WeatherModel
#EnvironmentObject var viewModel: WeatherData
var body: some View {
HStack {
//need to make navlink go to correct graph model. This will be passed in the GraphViews(). Clicking this nav link will trigger the API Call for the coordinates and model.
NavigationLink(
destination: InteractiveChart()
.onAppear{
selectedModel = weatherModel.apiName
fetchData(for: viewModel)
},
label: {
Text(weatherModel.name)
.frame(width: 60, alignment: .leading)
Text("\(String(weatherModel.range)) days")
.frame(width: 80, alignment: .leading)
.padding(.leading, 30)
Text("\(String(weatherModel.resolution)) km")
.frame(width: 80, alignment: .leading)
.padding(.leading, 30)
//this triggers the api call when the model is tapped. it passes the selected model name to the variable selectedModel for the call.
})
}
}
}
And these are my models where I set up my Observable Object:
class WeatherForecastClass: ObservableObject {
//var id = UUID()
var chartMODEL: String = "Model"
var chartModelHeight: Int = 0
var chartLAT: Double = selectedCords.latitude
var chartLON: Double = selectedCords.longitude
var chartDATETIME: Date = formatDate(date: "2023-02-10 18:00")
var chartTMP: Int = 1
var chartRH: Int = 0
var chartDP: Float = 0.0
var chartTTLSnow: Float = 0.0
var chartTTLRain: Float = 0.0
var chartWindSpeed: Int = 0
var chartWindDirDegs: Int = 0
var chartWindDirString: String = ""
var availableModels: [WeatherModel] = weatherModels
}
class WeatherData: ObservableObject {
/// weather data. This is the master class.
#Published var weatherArray: [WeatherForecastClass] = [WeatherForecastClass()]
//#Published var weatherProperties: WeatherForecastClass = WeatherForecastClass()
}
And this is my function for updating the availableModels array:
import Foundation
import SwiftUI
var modelInventory: [String] = []
func fetchModelInventory(for weatherData: WeatherData) {
// #EnvironmentObject var viewModel: WeatherData
let lat = Float(selectedCords.latitude)
let lon = Float(selectedCords.longitude)
let key = "my apiKey"
//this is the url with the API call. it has the data for the call request and my API key.
let urlString = "https://spotwx.io/api.php?key=\(key)&lat=\(lat)&lon=\(lon)&model=inventory"
guard let url = URL(string: urlString) else {
return
}
print(url)
//Gets the data from the api call. tasks are async
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else {
print("Error")
return
}
//clear the model inventory
modelInventory = []
weatherData.weatherArray[0].availableModels = []
//decode the modelinventory csv and fill the array
if let csvString = String(data: data, encoding: .utf8) {
let lines = csvString.split(separator: "\n")
for line in lines {
let columns = line.split(separator: ",")
for column in columns {
let value = String(column)
modelInventory.append(value)
}
}
}
print("Model Inventory -----")
print(modelInventory)
if !modelInventory.isEmpty {
DispatchQueue.main.async {
for model in weatherModels {
if modelInventory.contains(model.apiName) {
weatherData.weatherArray[0].availableModels.append(model)
}
}
print(weatherData.weatherArray[0].availableModels)
}
} else {
return
}
}
task.resume()
}
I feel like I've tried everything but I no matter what the list wont update.

Related

How to set up Textfield and Button in Custom SwiftUI View

I am attempting the configure the text field and button in my openweathermap app to be in its own view other than the main content view. In TextFieldView, the action of the button is set up to call an API response. Then, the weather data from the response is populated on a sheet-based DetailView, which is triggered by the button in TextFieldView. I configured the ForEach method in the sheet to return the last city added to the WeatherModel array (which would technically be the most recent city entered into the text field), then populate the sheet-based DetailView with weather data for that city. Previously, When I had the HStack containing the text field, button, and sheet control set up in the ContentView, the Sheet would properly display weather for the city that had just entered into the text field. After moving those items to a separate TextFieldView, the ForEach method appears to have stopped working. Instead, the weather info returned after entering a city name into the text field is displayed on the wrong count. For instance, if I were to enter "London" in the text field, the DetailView in the sheet is completely blank. If I then enter "Rome" as the next entry, the DetailView in the sheet shows weather info for the previous "London" entry. Entering "Paris" in the textfield displays weather info for "Rome", and so on...
To summarize, the ForEach method in the sheet stopped working properly after I moved the whole textfield and button feature to a separate view. Any idea why the issue I described is happening?
Here is my code:
ContentView
struct ContentView: View {
// Whenever something in the viewmodel changes, the content view will know to update the UI related elements
#StateObject var viewModel = WeatherViewModel()
var body: some View {
NavigationView {
VStack(alignment: .leading) {
List {
ForEach(viewModel.cityNameList.reversed()) { city in
NavigationLink(destination: DetailView(detail: city), label: {
Text(city.name).font(.system(size: 18))
Spacer()
Text("\(city.main.temp, specifier: "%.0f")°")
.font(.system(size: 18))
})
}
.onDelete { indexSet in
let reversed = Array(viewModel.cityNameList.reversed())
let items = Set(indexSet.map { reversed[$0].id })
viewModel.cityNameList.removeAll { items.contains($0.id) }
}
}
.refreshable {
viewModel.updatedAll()
}
TextFieldView(viewModel: viewModel)
}.navigationBarTitle("Weather", displayMode: .inline)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
TextFieldView
struct TextFieldView: View {
#State private var cityName = ""
#State private var showingDetail = false
#FocusState var isInputActive: Bool
var viewModel: WeatherViewModel
var body: some View {
HStack {
TextField("Enter City Name", text: $cityName)
.focused($isInputActive)
Spacer()
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button("Done") {
isInputActive = false
}
}
}
if isInputActive == false {
Button(action: {
viewModel.fetchWeather(for: cityName)
cityName = ""
self.showingDetail.toggle()
}) {
Image(systemName: "plus")
.font(.largeTitle)
.frame(width: 75, height: 75)
.foregroundColor(Color.white)
.background(Color(.systemBlue))
.clipShape(Circle())
}
.sheet(isPresented: $showingDetail) {
ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
if (city == viewModel.cityNameList.count-1) {
DetailView(detail: viewModel.cityNameList[city])
}
}
}
}
}
.frame(minWidth: 100, idealWidth: 150, maxWidth: 500, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
.padding(.leading, 16)
.padding(.trailing, 16)
}
}
struct TextFieldView_Previews: PreviewProvider {
static var previews: some View {
TextFieldView(viewModel: WeatherViewModel())
}
}
DetailView
struct DetailView: View {
#State private var cityName = ""
#State var selection: Int? = nil
var detail: WeatherModel
var body: some View {
VStack(spacing: 20) {
Text(detail.name)
.font(.system(size: 32))
Text("\(detail.main.temp, specifier: "%.0f")°")
.font(.system(size: 44))
Text(detail.firstWeatherInfo())
.font(.system(size: 24))
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(detail: WeatherModel.init())
}
}
ViewModel
class WeatherViewModel: ObservableObject {
#Published var cityNameList = [WeatherModel]()
func fetchWeather(for cityName: String) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName.escaped())&units=imperial&appid=<YourAPIKey>") else { return }
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else { return }
do {
let model = try JSONDecoder().decode(WeatherModel.self, from: data)
DispatchQueue.main.async {
self.addToList(model)
}
}
catch {
print(error)
}
}
task.resume()
}
func updatedAll() {
// keep a copy of all the cities names
let listOfNames = cityNameList.map{$0.name}
// fetch the up-to-date weather info
for city in listOfNames {
fetchWeather(for: city)
}
}
func addToList( _ city: WeatherModel) {
// if already have this city, just update
if let ndx = cityNameList.firstIndex(where: {$0.name == city.name}) {
cityNameList[ndx].main = city.main
cityNameList[ndx].weather = city.weather
} else {
// add a new city
cityNameList.append(city)
}
}
}
Model
struct WeatherModel: Identifiable, Codable {
let id = UUID()
var name: String = ""
var main: CurrentWeather = CurrentWeather()
var weather: [WeatherInfo] = []
func firstWeatherInfo() -> String {
return weather.count > 0 ? weather[0].description : ""
}
}
struct CurrentWeather: Codable {
var temp: Double = 0.0
var humidity = 0
}
struct WeatherInfo: Codable {
var description: String = ""
}
You need to use an ObservedObject in your TextFieldView to use your
original (single source of truth) #StateObject var viewModel that you create in ContentView and observe any change to it.
So use this:
struct TextFieldView: View {
#ObservedObject var viewModel: WeatherViewModel
...
}

Published/Observed var not updating in view swiftui w/ called function

Struggling to get a simple example up and running in swiftui:
Load default list view (working)
click button that launches picker/filtering options (working)
select options, then click button to dismiss and call function with selected options (call is working)
display new list of objects returned from call (not working)
I'm stuck on #4 where the returned query isn't making it to the view. I suspect I'm creating a different instance when making the call in step #3 but it's not making sense to me where/how/why that matters.
I tried to simplify the code some, but it's still a bit, sorry for that.
Appreciate any help!
Main View with HStack and button to filter with:
import SwiftUI
import FirebaseFirestore
struct TestView: View {
#ObservedObject var query = Query()
#State var showMonPicker = false
#State var monFilter = "filter"
var body: some View {
VStack {
HStack(alignment: .center) {
Text("Monday")
Spacer()
Button(action: {
self.showMonPicker.toggle()
}, label: {
Text("\(monFilter)")
})
}
.padding()
ScrollView(.horizontal) {
LazyHStack(spacing: 35) {
ForEach(query.queriedList) { menuItems in
MenuItemView(menuItem: menuItems)
}
}
}
}
.sheet(isPresented: $showMonPicker, onDismiss: {
//optional function when picker dismissed
}, content: {
CuisineTypePicker(selectedCuisineType: $monFilter)
})
}
}
The Query() file that calls a base query with all results, and optional function to return specific results:
import Foundation
import FirebaseFirestore
class Query: ObservableObject {
#Published var queriedList: [MenuItem] = []
init() {
baseQuery()
}
func baseQuery() {
let queryRef = Firestore.firestore().collection("menuItems").limit(to: 50)
queryRef
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.queriedList = querySnapshot?.documents.compactMap { document in
try? document.data(as: MenuItem.self)
} ?? []
}
}
}
func filteredQuery(category: String?, glutenFree: Bool?) {
var filtered = Firestore.firestore().collection("menuItems").limit(to: 50)
// Sorting and Filtering Data
if let category = category, !category.isEmpty {
filtered = filtered.whereField("cuisineType", isEqualTo: category)
}
if let glutenFree = glutenFree, !glutenFree {
filtered = filtered.whereField("glutenFree", isEqualTo: true)
}
filtered
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
self.queriedList = querySnapshot?.documents.compactMap { document in
try? document.data(as: MenuItem.self);
} ?? []
print(self.queriedList.count)
}
}
}
}
Picker view where I'm calling the filtered query:
import SwiftUI
struct CuisineTypePicker: View {
#State private var cuisineTypes = ["filter", "American", "Chinese", "French"]
#Environment(\.presentationMode) var presentationMode
#Binding var selectedCuisineType: String
#State var gfSelected = false
let query = Query()
var body: some View {
VStack(alignment: .center) {
//Buttons and formatting code removed to simplify..
}
.padding(.top)
Picker("", selection: $selectedCuisineType) {
ForEach(cuisineTypes, id: \.self) {
Text($0)
}
}
Spacer()
Button(action: {
self.query.filteredQuery(category: selectedCuisineType, glutenFree: gfSelected)
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text( "apply filters")
})
}
.padding()
}
}
I suspect that the issue stems from the fact that you aren't sharing the same instance of Query between your TestView and your CuisineTypePicker. So, when you start a new Firebase query on the instance contained in CuisineTypePicker, the results are never reflected in the main view.
Here's an example of how to solve that (with the Firebase code replaced with some non-asynchronous sample code for now):
struct MenuItem : Identifiable {
var id = UUID()
var cuisineType : String
var title : String
var glutenFree : Bool
}
struct ContentView: View {
#ObservedObject var query = Query()
#State var showMonPicker = false
#State var monFilter = "filter"
var body: some View {
VStack {
HStack(alignment: .center) {
Text("Monday")
Spacer()
Button(action: {
self.showMonPicker.toggle()
}, label: {
Text("\(monFilter)")
})
}
.padding()
ScrollView(.horizontal) {
LazyHStack(spacing: 35) {
ForEach(query.queriedList) { menuItem in
Text("\(menuItem.title) - \(menuItem.cuisineType)")
}
}
}
}
.sheet(isPresented: $showMonPicker, onDismiss: {
//optional function when picker dismissed
}, content: {
CuisineTypePicker(query: query, selectedCuisineType: $monFilter)
})
}
}
class Query: ObservableObject {
#Published var queriedList: [MenuItem] = []
private let allItems: [MenuItem] = [.init(cuisineType: "American", title: "Hamburger", glutenFree: false),.init(cuisineType: "Chinese", title: "Fried Rice", glutenFree: true)]
init() {
baseQuery()
}
func baseQuery() {
self.queriedList = allItems
}
func filteredQuery(category: String?, glutenFree: Bool?) {
queriedList = allItems.filter({ item in
if let category = category {
return item.cuisineType == category
} else {
return true
}
}).filter({item in
if let glutenFree = glutenFree {
return item.glutenFree == glutenFree
} else {
return true
}
})
}
}
struct CuisineTypePicker: View {
#ObservedObject var query : Query
#Binding var selectedCuisineType: String
#State private var gfSelected = false
private let cuisineTypes = ["filter", "American", "Chinese", "French"]
#Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack(alignment: .center) {
//Buttons and formatting code removed to simplify..
}
.padding(.top)
Picker("", selection: $selectedCuisineType) {
ForEach(cuisineTypes, id: \.self) {
Text($0)
}
}
Spacer()
Button(action: {
self.query.filteredQuery(category: selectedCuisineType, glutenFree: gfSelected)
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text( "apply filters")
})
}
}

SwiftUI Drag And Drop with List

I try to implement drag and drop between two views as shown in the code bellow, when I embed the "ForEach" view inside Group its work well, but when I try to embed it in the "List" view it doesn't work and I don't know why, any one can help to solve this problem ??
import SwiftUI
import MobileCoreServices
struct DragAndDropExample: View {
var delegate = dropDelegate()
#State var data = [SomeData(id: 1), SomeData(id: 2), SomeData(id: 3), SomeData(id: 4), SomeData(id: 5)]
#State var selectedDate: [SomeData] = []
var body: some View {
VStack (spacing: 16) {
Group { // Try to maek this "List"
ForEach(data) { dat in
Image(systemName: "\(dat.data).square.fill")
.resizable()
.background(Color.blue)
.frame(width: 50, height: 50)
.cornerRadius(15)
.onDrag{
NSItemProvider(item: .some(URL(string: dat.data)! as NSSecureCoding), typeIdentifier: String(kUTTypeURL))
}
}
}
Group { // Try to maek this "List"
if selectedDate.isEmpty {
Text("Drop Here")
}else {
ForEach(selectedDate) { dat in
Text("\(dat.data)")
}
}
}
.background(Color.blue)
.onDrop(of: [String(kUTTypeURL)], delegate: delegate)
}
}
}
struct DragAndDropExample_Previews: PreviewProvider {
static var previews: some View {
DragAndDropExample()
}
}
class SomeData: Identifiable {
let id: Int
let data: String
init(id: Int) {
self.id = id
self.data = String(id)
}
}
class dropDelegate: DropDelegate {
func performDrop(info: DropInfo) -> Bool {
// Drag and drop code here...
return true
}
}

Display filename in next View too

I have a code that makes a http Request, gets an array with filenames from that, displays them each with an image and the filename below. Everything works fine.
Now I made each image a button that opens a detail page.
That works but at the top it should say the matching filename from the page before.
But I am not able to hand over the filename (name) from ContentView4 to the next page (ts).
The language is SwiftUi
Could you please help me?
Thanks
Nikias
Here is my code:
import SwiftUI
struct ContentView4: View {
#State var showingDetail = false
#State var username: String = "."
#State var password: String = "."
#State private var name = String("Nikias2")
#State private var t = String()
#State private var x = -1
#State var dateien = ["word.png"]
var body: some View {
ScrollView(.vertical) {
ZStack{
VStack {
ForEach(0 ..< dateien.count, id: \.self) {
Button(action: {
print("button pressed")
x = x + 1
t = dateien[x]
self.showingDetail.toggle()
}) {
Image("datei")
}
.scaledToFit()
.padding(0)
Text(self.dateien[$0])
Text(t)
.foregroundColor(.white)
}
}
}
.sheet(isPresented:
$showingDetail) {
ts(name: t)
}
.onAppear { //# This `onAppear` is added to `ZStack{...}`
doHttpRequest()
}
}
}
func doHttpRequest() {
let myUrl = URL(string: "http://192.168.1.180/int.php")! //# Trailing semicolon is not needed
var request = URLRequest(url: myUrl)
request.httpMethod = "POST"// Compose a query string
let postString = "Name=\($username)&Passwort=\($password)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
//# Use if-let when you want to use the unwrapped value
if let error = error {
print("error=\(error)")
return
}
//# Use guard-let when nil has no meaning and want to exit on nil
guard let response = response else {
print("Unexpected nil response")
return
}
// You can print out response object
print("response = \(response)")
//Let's convert response sent from a server side script to a NSDictionary object:
do {
//# Use guard-let when nil has no meaning and want to exit on nil
guard let data = data else {
print("Unexpected nil data")
return
}
//#1 `mutableContainer` has no meaning in Swift
//#2 Use Swift Dictionary type instead of `NSDictionary`
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
if let parseJSON = json {
// Now we can access value of First Name by its key
//# Use if-let when you want to use the unwrapped value
if let firstNameValue = parseJSON["Name"] as? String {
print("firstNameValue: \(firstNameValue)")
let dateien = firstNameValue.components(separatedBy: ",")
print(dateien)
self.dateien = dateien
}
}
} catch {
print(error)
}
}
task.resume()
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
ContentView4()
}
}
struct ts: View {
#State var hin = false
#State var um = false
#State var datname: String = ""
var name: String
var body: some View {
NavigationView {
VStack {
Text(name)
.font(.system(size: 60))
.foregroundColor(.black)
.padding(50)
Button(action: {
self.hin.toggle()
}) {
Text("+")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.yellow)
.cornerRadius(35.0)
}
.padding()
if hin {
HStack {
Text("Datei auswählen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.yellow)
.cornerRadius(20.0)
.animation(Animation.default)
Text("Datei hochladen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.yellow)
.cornerRadius(20.0)
.animation(Animation.default)
}
}
Text("Datei herunterladen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.blue)
.cornerRadius(35.0)
Button(action: {
self.um.toggle()
}) {
Text("Datei umbenennen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.green)
.cornerRadius(35.0)
}
.padding()
if um {
HStack {
TextField(name, text: $datname)
.font(.headline)
.frame(width: 150, height: 70)
.cornerRadius(20.0)
.animation(Animation.default)
Text("Datei umbenennen")
.font(.headline)
.frame(width: 150, height: 70)
.background(Color.green)
.cornerRadius(20.0)
.animation(Animation.default)
}
}
Text("Datei löschen")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(width: 220, height: 60)
.background(Color.red)
.cornerRadius(35.0)
}
}
}
}
I believe your issue is a result of using #State variables to store all of the attributes. #State variables are not consistent and get refreshed in the background by SwiftUI depending on your views visibility.
The piece that you are missing is a view controller class stored in an #EnviornmentObject variable. This class gets Initiated in your main contentView and is used to keep track and alter of all your attributes.
Each ContentView should reference the single #EnviornmentObject and pull data from that class.
Another solution which may work would be to replace all your #State variables with #StateObject vars. #StateObject vars are basically #State vars but get initiated before the struct get loaded and the value is kept consistent regardless of the view state of the parent struct.
Here is a rough implementation of #EnvironmentObject within your project.
Basically use the #EnvironmentObject to pass values to child views
ContentView4.swift
struct ContentView4: View {
#EnvironmentObject cv4Controller: ContentView4Controller
var body: some View {
ScrollView(.vertical) {
ZStack{
VStack {
ForEach(0 ..< cv4Controller.dateien.count, id: \.self) {
Button(action: {
print("button pressed")
x = x + 1
t = cv4Controller.dateien[x]
self.showingDetail.toggle()
}) {
Image("datei")
}
.scaledToFit()
.padding(0)
Text(self.dateien[$0])
Text(cv4Controller.t)
.foregroundColor(.white)
}
}
}
.sheet(isPresented:
cv4Controller.$showingDetail) {
ts(name: cv4Controller.t)
}
.onAppear { //# This `onAppear` is added to `ZStack{...}`
cv4Controller.doHttpRequest()
}
}
}
ContentView4Controller.swift
class ContentView4Controller: ObservableObject {
#Published var showingDetail = false
#Published var username: String = "."
#Published var password: String = "."
#Published private var name = String("Nikias2")
#Published private var t = String()
#Published private var x = -1
#Published private var t = String()
#Published private var x = -1
#Published var dateien = ["word.png"]
func doHttpRequest() {
let myUrl = URL(string: "http://192.168.1.180/int.php")! //# Trailing semicolon is not needed
var request = URLRequest(url: myUrl)
request.httpMethod = "POST"// Compose a query string
let postString = "Name=\($username)&Passwort=\($password)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) {
(data, response, error) in
//# Use if-let when you want to use the unwrapped value
if let error = error {
print("error=\(error)")
return
}
//# Use guard-let when nil has no meaning and want to exit on nil
guard let response = response else {
print("Unexpected nil response")
return
}
// You can print out response object
print("response = \(response)")
//Let's convert response sent from a server side script to a NSDictionary object:
do {
//# Use guard-let when nil has no meaning and want to exit on nil
guard let data = data else {
print("Unexpected nil data")
return
}
//#1 `mutableContainer` has no meaning in Swift
//#2 Use Swift Dictionary type instead of `NSDictionary`
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
if let parseJSON = json {
// Now we can access value of First Name by its key
//# Use if-let when you want to use the unwrapped value
if let firstNameValue = parseJSON["Name"] as? String {
print("firstNameValue: \(firstNameValue)")
let dateien = firstNameValue.components(separatedBy: ",")
print(dateien)
self.dateien = dateien
}
}
} catch {
print(error)
}
}
task.resume()
}
}
Example of main ContentView.swift
struct ContentView: View {
var cv4Controller: ContentView4Controller = ContentView4Controller()
var body: some view {
// your main page output
GeometryReader { geo in
// just a guess for what you have in your main contentView
switch(page) {
case .main:
ContentView2()
default:
ContentView4()
break
}
}.environmentObject(cv4Controller) // this will make cv4Controller available to all child view structs
}
}
Add #Binding wrapper to the "name" variable in your ts view. And pass the t variable as a binding by adding a "$". This will keep your ts name variable updated to whatever is value it has in the parent view.
Also why do you use a NavigationView in your ts View?
struct ContentView4: View {
...
#State private var t = String()
...
var body: some View {
...
ZStack{
...
}
.sheet(isPresented: $showingDetail) {
ts(name: $t)
}
...
}
func doHttpRequest() {
...
}
}
struct ts: View {
...
#Binding var name: String
var body: some View {
...
}
}
My starting code works, but It's just displaying the Filenames in a row and if I tap a random image, the name won't fit, only if I'm going down in the row and tap them. The problem is, that I don't know how to set the variable to the id, not to pass them to the next view. Has anyone got and idea how I can pass the right filename into a variable in the for loop and read it in the next view?

Swiftui ObservableObject with array not update view

I have a problem with Array using ObservableObject in my view. I have an empty array. I call a function at page onAppear. When the data is returned, the view does not update with the new data in array:
class NewsState: ObservableObject {
private let base: String = "api"
let objectWillChange = ObservableObjectPublisher()
#Published var wagsList: Array<UserSlider> = [] {
willSet {
objectWillChange.send()
}
}
func getList() {
let url = NSURL(string: "\(base)/UserApi/getList")
var mutableURLRequest = URLRequest(url: url! as URL)
mutableURLRequest.httpMethod = "GET"
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
AF.request(mutableURLRequest).responseData { response in
guard let data = response.data else { return }
let resp = try! JSONDecoder().decode(Array<UserSlider>.self, from: data)
for i in resp {
let userSlider = UserSlider(id: i.id, uid: i.uid, image: i.image)
self.wagsList.append(userSlider)
}
}
}
}
In my view I have this:
HStack {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
if(self.newsState.wagsList.count != 0) {
ForEach(self.newsState.wagsList, id: \.self) { wags in
VStack {
HStack {
URLImage(URL(string: "\(wags.image)")!, expireAfter: Date(timeIntervalSinceNow: 10)) { proxy in
proxy.image
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 60, height: 60)
.clipShape(Circle())
.overlay(
RoundedRectangle(cornerRadius: 30)
.stroke(Color.white, lineWidth: 2)
)
.contentShape(Circle())
}.frame(width: 62, height: 62)
}
HStack {
Text("10K")
.foregroundColor(Color.white)
.font(Font.custom("Metropolis-Bold", size: 15))
}
HStack {
Text("followers")
.foregroundColor(Color.white)
.font(Font.custom("Metropolis-Normal", size: 15))
}
}
}
} else {
//loader
}
}.onAppear(perform: initPage)
}
}
What am I doing wrong? I see that the problem is caused by ScrollView.
Try this one
class NewsState: ObservableObject {
private let base: String = "api"
#Published var wagsList: Array<UserSlider> = []
func getList() {
let url = NSURL(string: "\(base)/UserApi/getList")
var mutableURLRequest = URLRequest(url: url! as URL)
mutableURLRequest.httpMethod = "GET"
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
AF.request(mutableURLRequest).responseData { response in
guard let data = response.data else { return }
let resp = try! JSONDecoder().decode(Array<UserSlider>.self, from: data)
let results = resp.map { UserSlider(id: $0.id, uid: $0.uid, image: $0.image) }
DispatchQueue.main.async {
self.wagsList = results
}
}
}
}
As it is not clear to me where the error might lay. It could be either in getList or in your View.
This is an easy example of how it works with a Published and ObserverdObject:
Note: your getList function is not in this solution as the error could be with your API, JSON ect.
import SwiftUI
struct ContentView: View {
#ObservedObject var state = NewsState()
var body: some View {
Group { //needed for the IF Statement below
if state.stringList.count > 0 {
ForEach(self.state.stringList, id: \.self){ s in
Text(String(s))
}
}
}.onTapGesture {
self.state.getNewList()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class NewsState: ObservableObject {
#Published var stringList: Array<String> = []
init() {
self.getList()
}
func getList() {
self.stringList.append("New")
}
func getNewList() {
self.stringList = []
self.stringList.append("New new")
}
}