URLSession for XMLParser in SwiftUI returning empty or nil

I am attempting to parse XML using an URLSession, XCode 12, SwiftUI but it keeps returning [] or nil. If I print immediately after the parse(see code), all the data is there, but for some reason, it seems to be clearing it all out.

If I try it with a .xml file, the code works fine, so it must be something in my URLSession in a XMLParserDelegate class:
Code Block
class ParseController: NSObject, XMLParserDelegate{
var items: [Item] = []
var itemStore: [Item]?
func loadData() {
let url = URL(string: "website")!
let request=URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if data == nil {
print("dataTaskWithRequest error: \(String(describing: error?.localizedDescription))")
return
}
let parser = XMLParser(data: data!)
parser.delegate=self
parser.parse()
self.itemStore=self.items
print(self.itemStore)
}
task.resume()
/*
if let path = Bundle.main.url(forResource: "Items", withExtension: "xml") {
if let parser = XMLParser(contentsOf: path) {
parser.delegate = self
parser.parse()
self.itemStore=self.items
}
}
*/
}

And then I call that with a button in my View:
Code Block
struct MyParserView: View {
@State var itemsResult: [Item]?
var body: some View {
if ((itemsResult?.isEmpty) == nil) {
VStack {
Text("Stuff here")
Button(action: {
let parserControl = ParseController()
parserControl.loadData()
itemsResult = parserControl.itemStore
}){
Text("Push")
}
}
}else {
List{
ForEach(itemResult!, id:\.id){item in
Text(item.name)
}
}
}
}
}

Answered by OOPer in 637885022
Your code does not work because it does not handle asynchronous calls properly.

When you call loadData(), the completion handler passed to dataTask (line 9. { (data, response, error) in to line 21 }) is executed after loadData() is finished.

One way of utilizing the result of async calls in SwiftUI is using @Published var in an ObservableObject.

Update your ParseController as follows:
Code Block
class ParseController: NSObject, XMLParserDelegate, ObservableObject { //<-
var items: [Item] = []
@Published var itemStore: [Item]? //<-
func loadData() {
let url = URL(string: "website")!
let request=URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("dataTaskWithRequest error: \(error)")
return
}
guard let data = data else {
print("dataTaskWithRequest data is nil")
return
}
let parser = XMLParser(data: data)
parser.delegate = self
parser.parse()
self.itemStore = self.items
print(self.itemStore)
}
task.resume()
}
//... I assume you are implementing `XMLParserDelegate` methods properly...
}


And use it in a SwiftUI view like this:
Code Block
struct MyParserView: View {
@StateObject var parserControl = ParseController() //<-
var body: some View {
if let itemsResult = parserControl.itemStore, !itemsResult.isEmpty {
List{
ForEach(itemsResult, id:\.id) {item in //<-
Text(item.name)
}
}
} else {
VStack {
Text("Stuff here")
Button(action: {
//Do nothing here //<-
parserControl.loadData()
//Do nothing here //<-
}){
Text("Push")
}
}
}
}
}


This is not tested, so you may need to modify some parts, but please try.
Accepted Answer
Your code does not work because it does not handle asynchronous calls properly.

When you call loadData(), the completion handler passed to dataTask (line 9. { (data, response, error) in to line 21 }) is executed after loadData() is finished.

One way of utilizing the result of async calls in SwiftUI is using @Published var in an ObservableObject.

Update your ParseController as follows:
Code Block
class ParseController: NSObject, XMLParserDelegate, ObservableObject { //<-
var items: [Item] = []
@Published var itemStore: [Item]? //<-
func loadData() {
let url = URL(string: "website")!
let request=URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("dataTaskWithRequest error: \(error)")
return
}
guard let data = data else {
print("dataTaskWithRequest data is nil")
return
}
let parser = XMLParser(data: data)
parser.delegate = self
parser.parse()
self.itemStore = self.items
print(self.itemStore)
}
task.resume()
}
//... I assume you are implementing `XMLParserDelegate` methods properly...
}


And use it in a SwiftUI view like this:
Code Block
struct MyParserView: View {
@StateObject var parserControl = ParseController() //<-
var body: some View {
if let itemsResult = parserControl.itemStore, !itemsResult.isEmpty {
List{
ForEach(itemsResult, id:\.id) {item in //<-
Text(item.name)
}
}
} else {
VStack {
Text("Stuff here")
Button(action: {
//Do nothing here //<-
parserControl.loadData()
//Do nothing here //<-
}){
Text("Push")
}
}
}
}
}


This is not tested, so you may need to modify some parts, but please try.
Thanks!
Lost track of the forest through the trees...this was not a XML problem at all!

I used @ObservedObject instead of @State and also had to add a DispatchQueue when updating itemStore.
URLSession for XMLParser in SwiftUI returning empty or nil
 
 
Q