尝试使用 swift 将 XML 从 URL 解析为 PickerView

Trying to parse XML from URL to PickerView with swift

我正在尝试使用 XMLParser 解析 XML 并将其放在 PickerView 上。这是我的代码:

主要代码:

import UIKit

class ViewController: UIViewController, XMLParserDelegate {
    @IBOutlet weak var containerView: UIView!
    @IBOutlet weak var pickerView: UIPickerView!
    @IBOutlet weak var Image: UIImageView!
    @IBOutlet weak var Button: UIButton!

    var arrayCategorias = [Categories]()

    var parser = XMLParser()

    override func viewDidLoad() {
        super.viewDidLoad()

        let urlString = URL(string: "http://thecatapi.com/api/categories/list")
        self.parser = XMLParser(contentsOf: urlString!)!
        let success:Bool = self.parser.parse()
        parser.delegate = self
        if success {
            print("success")
        } else {
            print("parse failure!")
        }

        print(arrayCategorias.count)
    }

    @IBAction func botonPulsado(_ sender: Any) {
    }

    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
        if(elementName=="category")
        {
            let categoria = Categories()
            for string in attributeDict {
                let strvalue = string.value as NSString
                switch string.key {
                case "id":
                    categoria.id = strvalue.integerValue
                    break
                case "name":
                    categoria.name = strvalue as String
                    break
                default:
                    break
                }
            }
            arrayCategorias.append(categoria)
        }

        func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        }

        func parser(_ parser: XMLParser, foundCharacters string: String) {
        }

        func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
            print("failure error: ", parseError)
        }
    }
}        

Class 类别代码:

import Foundation

class Categories{
     var id: Int = 0
     var name: String = ""
 }        

我不知道到底是什么问题,但是当我尝试读取 arrayCategorias() 只是为了查看是否有任何数据时,它只显示 0。我发现错误的地方是显示给我:"parse failure".

如果有人能帮助我,我只是一个android程序员,我不知道为什么它不起作用。

我需要帮助将数据放在 PickerView 上。

有一大堆问题:

  1. 您在解析后设置了 delegate,而您必须在调用 parse() 之前执行此操作。

  2. 您在 didStartElement 中实现了一些委托方法。这些需要是您的解析器委托的顶级方法。

  3. 不相关的小问题,但是您的 class 名称 Categories 不太正确,因为它代表一个“类别”,而不是很多。所以我将其重命名为 Category。 (我个人也会将其设为 struct,但这取决于您。)

  4. 您的 idname 值不是 category 元素的属性。它们是它们自己的元素,因此您必须单独解析它们(使用 foundCharacters 并在 categorydidEndElement 上构建 Category 对象。

  5. 类别的 id 似乎是一个整数,所以我将 Categoryid 设为 Int

  6. 您正在从主线程调用 XMLParserparse(),而后者正在使用 URL。这是不可取的,因为您在主线程执行请求时阻塞了主线程。就个人而言,我会使用 URLSession 异步请求数据,在后台队列中处理它,并且只将模型对象的最终更新和 UI 更新分派到主队列。

    就个人而言,我自己会采用异步模式与 @escaping 完成处理程序,以帮助隔离 UI 我在解析后触发的更新与解析本身

  7. 风格问题,但我不会将所有这些 XMLParserDelegate 代码都放在视图控制器中。至少,把它放在一个扩展中,这样它就可以很好地组织起来。更好的是,本着 single responsibility principle 的精神定义一个独立的解析器委托对象,并确保您不会在 UI 可能引用它时意外更新视图控制器模型对象。它更安全地确保线程安全并使您的代码更好地封装。

将所有这些放在一起,您可以执行以下操作:

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