代码之家  ›  专栏  ›  技术社区  ›  Santi Albus

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

  •  1
  • Santi Albus  · 技术社区  · 7 年前

    我试图用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)
            }
        }
    }        
    

    类别代码:

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

    我不知道到底是什么问题,但当我尝试阅读时 arrayCategorias() 只是为了看看是否有任何数据,它只显示了0。在我发现错误的地方,它告诉我:“解析失败”。

    如果有人能帮我,我只是一个安卓程序员,我不知道为什么它不工作。

    我需要帮助把数据放到PickerView上。

    1 回复  |  直到 7 年前
        1
  •  0
  •   Community miroxlav    4 年前

    有一大堆问题:

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

    2. 您在内部实现了一些委托方法 didStartElement . 这些必须是解析器委托的顶级方法。

    3. 次要无关问题,但您的班级名称 Categories 不太正确,因为它代表一个“类别”,而不是很多。所以我把这个重命名为 Category . (我个人也会做一个 struct ,但这取决于你。)

    4. 你的 id name 值不是 category 要素它们是自己的元素,因此您必须单独解析它们(使用 foundCharacters 并构建 类别 didEndElement 属于 类别 .

    5. 这个 身份证件 其中的个类别似乎是一个整数,因此我将 身份证件 属于 类别 Int

    6. 你在打电话 XMLParser 这是使用来自主线程的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
        }
        
    }