问题描述
我在我的应用程序的Swift中设置了XMLParser,并希望能够在小部件扩展中解析RSS Feed,并在小部件中返回该数据。但是,在使两个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,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(),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的成员
解决方法
您不能在SwiftUI视图之外使用@State
变量-这意味着您不能在TimelineProvider
中使用它们。
您可以使用WidgetCenter.shared.reloadAllTimelines()
来做到这一点:
- 更新FeedParser以存储已解析的RSSItem,并在完成后通知
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()
}
}
- 在创建时间线时从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)
}
}
- 要使所有这些工作正常进行,您需要一种方法来知道何时必须再次调用
parseFeed
函数(以刷新项目)。
您可以尝试观察提要的通知,并在收到通知后致电:
feedParser.parseFeed(url: "some_feed")
(为此,您需要使用相同的FeedParser
实例,因此可能有必要将FeedParser移出提供者)