【问题标题】:Parsing RSS XML in WidgetView Extension of iOS 14在 iOS 14 的 WidgetView 扩展中解析 RSS XML
【发布时间】:2021-01-08 23:25:54
【问题描述】:

我在我的应用程序中使用 Swift 设置了 XMLParser,并且希望能够在小部件扩展中解析 RSS 提要,并在小部件中返回该数据。但是,我在让两个 Swift 文件相互通信时遇到了一些问题。在解析器中,我有:

struct RSSItem {
    var title: String
    var description: String
    var link: String
    var pubDate: String
}

// download xml from the internet

class FeedParser: NSObject, XMLParserDelegate
{
    private var rssItems: [RSSItem] = []
    private var currentElement = ""
    private var currentTitle: String = ""
    private var currentDescription: String = ""
    private var currentPubDate: String = ""
    private var currentLink: String = ""
    
    private var parserCompletionHandler: (([RSSItem]) -> Void)?
    
    func parseFeed(url: String, completionHandler: (([RSSItem]) -> Void)?)
    {
        self.parserCompletionHandler = completionHandler
        
        let request = URLRequest(url: URL(string: url)!)
        let urlSession = URLSession.shared
        let task = urlSession.dataTask(with: request) { (data, response, error) in
            guard let data = data else {
                if let error = error {
                    print(error.localizedDescription)
                }
                
                return
            }
            
            /// parse our xml data
            let parser = XMLParser(data: data)
            parser.delegate = self
            parser.parse()
        }
        
        task.resume()
    }
    // MARK: - XML Parser Delegate

    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        if currentElement == "item" {
            currentTitle = ""
            currentDescription = ""
            currentPubDate = ""
            currentLink = ""
        }
    }
    func parser(_ parser: XMLParser, foundCharacters string: String) {
        switch currentElement {
        case "title": currentTitle += string
        case "description": currentDescription += string
        case "pubDate" : currentPubDate += string
        case "link" : currentLink += string
        default: break
        }
    }
    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        if elementName == "item" {
            let rssItem = RSSItem(title: currentTitle, description: currentDescription, link: currentLink, pubDate: currentPubDate)
            self.rssItems.append(rssItem)
        }
    }
    func parserDidEndDocument(_ parser: XMLParser) {
        parserCompletionHandler?(rssItems)
    }
    
    func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
        print(parseError.localizedDescription)
    }
}

在小部件中,我有:

struct Provider: TimelineProvider {
    @State private var rssItems:[RSSItem]?
    let feedParser = FeedParser()
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), title:"News", description: "Stuff happened", link: "Http://link", pubDate: "The day it posted")
    }

    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), title:"News", description: "Stuff happened", link: "Http://link", pubDate: "The day it posted")
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []
        feedParser.parseFeed(url: "") {(rssItems) in
            self.rssItems = rssItems
            let currentDate = Date()
            for hourOffset in 0 ..< 5 {
                let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
                let entry = SimpleEntry(date: entryDate, title:rssItems.title, description: rssItems.description, link: rssItems.link, pubDate: rssItems.pubDate)
                entries.append(entry)
            
        }
        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
       
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let title: String
    let description: String
    let link: String
    let pubDate: String
}

但是,在 TimelineProvider 部分,它告诉我 rssItems 没有名为 title、description、pubDate 或 link 的成员

【问题讨论】:

  • 这可能对你有帮助:How to refresh Widget data?
  • @pawello2222 感谢您的链接,但我可能太深入了,因为每一点都在我的脑海中。第一次使用 Swift 而不是 Obj-C,这一切都让我失望。
  • 这个答案可以很容易地应用于您的案例 - 您只需使用 FeedParser 而不是 NetworkManager。但如果这是您第一次使用 Swift,我不建议您创建小部件 - 从 standard 应用程序开始,然后先习惯 Swift SwiftUI。
  • @pawello2222 相信我,我完全明白,但唉,这就是我所在的地方。我已经构建了解析器,但是在尝试将其数据传递到 Widget 文件时遇到了很多问题。我更新了小部件中的 SimpleEntry 以包含 XML 所需的值,但在尝试让解析器运行时仍然遇到问题。在 TimelineProvider 的结构中,我是否应该运行提要解析器,因为当我尝试这样做时它会引发很多错误。
  • @pawello2222 了解,我更新了 OP 以包含 Widget 文件中的相关代码,显示问题所在。我认为(?)那是我调用代码来解析 xml 的地方,但你会看到它没有看到 rssItems 中标题链接发布日期或描述的任何成员

标签: swift xml-parsing widget ios14 widgetkit


【解决方案1】:

您不能在 SwiftUI 视图之外使用 @State 变量 - 这意味着您不能在 TimelineProvider 中使用它们。

您可以使用WidgetCenter.shared.reloadAllTimelines()

  1. 更新 FeedParser 以存储已解析的 RSSItems 并在完成时通知 WidgetCenter
class FeedParser: NSObject, XMLParserDelegate {
    var rssItems: [RSSItem] = [] // make public, here you will store parsed RSSItems

    func parseFeed(url: String) { // remove `completionHandler` form the function signature
        ...
    }

    func parserDidEndDocument(_ parser: XMLParser) {
        // instead of calling `completionHandler` force reload the timeline
        WidgetCenter.shared.reloadAllTimelines()
    }
}
  1. 在创建时间线时从 FeedParser 中获取第一个 RSSItem 并从中创建一个条目:
struct Provider: TimelineProvider {
    // no `@State` variables here

    let feedParser = FeedParser()

    ...

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        guard !feedParser.rssItems.isEmpty else { return }
        let entry = SimpleEntry(date: Date(), rssItem: feedParser.rssItems[0])
        
        let timeline = Timeline(entries: [entry], policy: .never)
        completion(timeline)
    }
}
  1. 要使所有这些工作,您需要一种方法来了解何时必须再次调用 parseFeed 函数(以刷新项目)。

您可以尝试观察您的 Feed 的通知,并在收到通知后致电:

feedParser.parseFeed(url: "some_feed")

(您需要为此使用相同的 FeedParser 实例,因此可能需要将 FeedParser 移出 Provider)

【讨论】:

  • 我认为这实际上可以解决我在添加新内容时小部件不刷新的问题。但是,我无法添加 WidgetCenter.shared.reloadAllTimelines(),因为它在范围内找不到 WidgetCenter
  • @user717452 你导入WidgetKit了吗?
  • 是的,但是它给了我一个错误,即 WidgetKit 仅在 iOS 14.0 或更高版本中可用。我肯定将基本 SDK 设置为 14。
  • @user717452 那你就可以if #available(iOS 14.0, *)
  • 好的,所以我已经完成了一天的工作,并让小部件显示我想要的方式,除了它不会用新的刷新。现在只添加 WidgetCenter 部分而不是 completionHandler 为我提供了占位符视图,小部件上没有任何内容。将其取出并仅使用 completionHandler 使其显示第一次工作时最新的项目,而不是较新的项目。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-09
  • 2015-03-01
  • 2021-02-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多