Can't Call Segue from Async Function like XMLParser
Perhaps perform segue from main thread will do the job:
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?)
{
DispatchQueue.main.async {
performSegue(withIdentifier: "userDidRequestReset", sender: nil)
}
}
XML Parser blocking main thread and dispatch_async crashing app
Print your array. Check it twice. I think you are getting an empty array. Print every object in the console which you get from server and which you parse. So you will get idea.
Update:
Just reload the table data on the main thread and don't parse data on it. like:
dispatch_async(dispatch_get_main_queue(), ^{
[messageList reloadData];
});
Put the other code outside the main thread.
Trying to parse XML from URL to PickerView with swift
There are a whole bunch of issues:
You have set your
delegate
after you parsed, whereas you must do this before you callparse()
.You have some of your delegate methods implemented inside
didStartElement
. These need to be top level methods of your parser delegate.Minor unrelated issue, but your class name of
Categories
isn't quite right, because it represents a single "category", not many. So I'd rename this to beCategory
. (I'd personally also make it astruct
, but that's up to you.)Your
id
andname
values are not attributes of thecategory
element. They are their own elements, so you have to parse them separately (usingfoundCharacters
and build theCategory
object ondidEndElement
ofcategory
.The
id
of the categories appears to be an integer, so I'd make theid
ofCategory
anInt
.You're calling
parse()
of aXMLParser
that is using a URL from the main thread. That's inadvisable because you're blocking the main thread while it does the request. Personally, I'd useURLSession
to request the data asynchronously, process it on a background queue, and only dispatch the final update of the model object and UI update to the main queue.Personally, I'd adopt asynchronous patterns myself with
@escaping
completion handlers, to help isolate UI updates that I trigger after a parse from the parsing itselfA stylistic matter, but I wouldn't put all of this
XMLParserDelegate
code in the view controller. At the very least, put it in an extension so it's nicely organized. Even better, define a stand-alone parser delegate object in the spirit of the single responsibility principle and ensuring that you're not accidentally updating view controller model objects while the UI might be referring to it. It more safely ensures thread-safety and keeps your code more nicely encapsulated.
Pulling this all together, you could do something like:
struct Category {
let id: Int
let name: String
}
class ViewController: UIViewController {
@IBOutlet weak var pickerView: UIPickerView!
var categories = [Category]()
override func viewDidLoad() {
super.viewDidLoad()
startRequestAndParse() { categories, error in
guard let categories = categories, error == nil else {
print(error?.localizedDescription ?? "Unknown error")
return
}
// if you got here, everything is OK, so update model and UI on main thread
DispatchQueue.main.async {
self.categories = categories
print(self.categories)
// trigger whatever UI update you want here, too;
self.pickerView.reloadAllComponents()
}
}
}
/// Initiate request from server and parse results
///
/// - Parameters:
/// - completion: This is called when the request/parsing is done. This may be called
/// on background thread. If parsing failed, the array of categories
/// will be `nil` and we should have `error`.
/// - categories: First parameter of the `completion` closure is the array of `Category` objects, or `nil` on error.
/// - error: Second parameter of the `completion` closure is the resulting `Error`, if any.
private func startRequestAndParse(completion: @escaping (_ categories: [Category]?, _ error: Error?) -> Void) {
let url = URL(string: "http://thecatapi.com/api/categories/list")!
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else {
completion(nil, error)
return
}
// ok, now parse
let parser = XMLParser(data: data)
let delegate = ParserDelegate()
parser.delegate = delegate
parser.parse()
completion(delegate.categories, parser.parserError)
}
task.resume()
}
}
// this assumes you set the picker's `delegate` to be the view controller (either in IB or programmatically in `viewDidLoad`
extension ViewController: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return categories[row].name
}
}
// this assumes you set the picker's `dataSource` to be the view controller (either in IB or programmatically in `viewDidLoad`
extension ViewController: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return categories.count
}
}
/// Parser delegate for categories
class ParserDelegate: NSObject, XMLParserDelegate {
private var id: Int?
private var name: String?
private var value: String?
var categories: [Category]?
// initialize `categories`
func parserDidStartDocument(_ parser: XMLParser) {
categories = []
}
// if `id` or `name`, initialize `value` so we'll capture the appropriate value
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
if elementName == "id" || elementName == "name" {
value = ""
}
}
// if `value` is not `nil`, go ahead and concatenate the additional characters
func parser(_ parser: XMLParser, foundCharacters string: String) {
value? += string
}
// if `id` or `name`, update the appropriate property
// if `category`, build a `Category` object and add it to our array
// regardless, reset `value` to `nil`
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
switch elementName {
case "id":
if let value = value {
id = Int(value)
}
case "name":
name = value
case "category":
if let id = self.id, let name = self.name {
categories!.append(Category(id: id, name: name))
}
id = nil
name = nil
default:
()
}
value = nil
}
// if any error, reset `categories` so caller knows there was an issue
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
categories = nil
}
}
Related Topics
Swift: Handling an Unexpected Nil Value, When Variable Is Not Optional
Creating Semaphore with Initial Value of 0 Make Issues with Execution
How to Post Parameter with (+ Plus Sign) in Alamofire
Implement a Custom Staggeregrid in UIview Like Etsy App in Swift
How to Calculate a Distance Between Two Anchorentities
Cloudkit - What to Do When a User Adds, Modifies or Deletes an Object While Offline
Is There an Object Class in Swift
Add Custom .Colornames to UIcolor Somehow
iOS 10. Coredata Insert New Object Sig Abrt
Binary Search: Error in Passing Array
Calculate Age with Textfield Swift 4
How to Use a Protocol with a Typealias as a Func Parameter
Add Value to Variable Inside Closure in Swift
Transparency Issues with Repeated Stamping of Textures on an Mtkview