Here's code for ArticleInfo since it was updated to integrate the extension into the actual ObservableObject:
import Foundation
struct ArticleInfo: Identifiable, Codable {
var id: String = ""
var title: String = ""
var date: Date?
var author: String = ""
var img: URL?
var content: String = ""
}
class ArticlesInfo: ObservableObject {
@Published var articles: [ArticleInfo] = []
var dataUrl = URL(string: "https://storage.googleapis.com/thehairsociety/2021/03/2f23e2d4-ths.xml")
func loadArticles() {
do {
let data = try Data(contentsOf: dataUrl!)
let parser = ArticlesParser(data: data)
if parser.parse() {
print(parser.articles) // Value of type 'ArticlesParser' has no property 'articles'
let articles: try //...?
self.articles = articles // Unsure what I put here
} else {
if let error = parser.parserError {
print(error)
} else {
print("Failed with unknown reason")
}
}
} catch {
print(error)
}
}
}
class ArticlesParser: XMLParser {
var dateTimeZone = TimeZone(abbreviation: "GMT-6")
lazy var dateFormater: DateFormatter = {
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "MMM dd, yyyy"
df.timeZone = dateTimeZone
return df
}()
private var textBuffer: String = ""
private var nextArticle: ArticleInfo? = nil
override init(data: Data) {
super.init(data: data)
self.delegate = self
}
}
extension ArticlesParser: XMLParserDelegate {
// Called when opening tag (`elementName`) is found
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
switch elementName {
case "posts":
nextArticle = ArticleInfo()
case "title":
textBuffer = ""
case "date":
textBuffer = ""
case "author":
textBuffer = ""
case "img":
textBuffer = ""
case "content":
textBuffer = ""
default:
print("Ignoring \(elementName)")
break
}
}
// Called when closing tag (`/elementName`) is found
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
switch elementName {
case "posts":
if let article = nextArticle {
articles.append(article) // Cannot find 'articles' in scope
}
case "title":
nextArticle?.title = textBuffer
case "date":
print("date: \(textBuffer)")
nextArticle?.date = dateFormater.date(from: textBuffer)
case "author":
nextArticle?.author = textBuffer
case "img":
print("img: \(textBuffer)")
nextArticle?.img = URL(string: textBuffer)
case "content":
nextArticle?.content = textBuffer
default:
print("Ignoring \(elementName)")
break
}
}
// Called when a character sequence is found
// This may be called multiple times in a single element
func parser(_ parser: XMLParser, foundCharacters string: String) {
textBuffer += string
}
// Called when a CDATA block is found
func parser(_ parser: XMLParser, foundCDATA CDATABlock: Data) {
guard let string = String(data: CDATABlock, encoding: .utf8) else {
print("CDATA contains non-textual data, ignored")
return
}
textBuffer += string
}
// For debugging
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print(parseError)
print("on:", parser.lineNumber, "at:", parser.columnNumber)
}
}
Post
Replies
Boosts
Views
Activity
Could you please provide the code in which you’re previewing from? Even if the compiler sees no issue, there could still be an error that makes the app malfunction.
Also, did you add any code that made it stop working? What was the difference from the working code versus the non-working code?
Could you provide some code so I can better understand how you’re using Button?
extension ArticlesInfo {
var dataUrl: URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("THS.xml")
}
func saveArticles() {
do {
//Convert Array of `CardInfo` to `Data`
let data = try JSONEncoder().encode(dataUrl)
//Write `Data` to a file specified by URL
try data.write(to: dataUrl, options: .atomic)
} catch {
print(error)
}
}
func loadArticles() {
do {
if FileManager.default.fileExists(atPath: dataUrl.path) {
return
}
//Read `Data` from a file specified by URL
let data = try Data(contentsOf: dataUrl)
//Parse Available Articles
let articles = try JSONDecoder().decode([ArticleInfo].self, from: data)
self.articles = articles
} catch {
print(error)
}
}
}
The code that was here was moved into ArticlesInfo itself and eventually changed into what it is now.
You continue saying words which I do not understand
I added an extension for ArticlesInfo and decided against it. Apologies for not clarifying.
What do you mean by integrate?
Sorry, meant "merge." There was no point for the extension if the main ObservableObject would just have the variable.
Why did you remove articles? I completely do not understand why.
I don't understand how I removed articles. Sorry if I'm missing something but I don't remember ever removing it. It's in the ObservableObject where you told me to put it in the first place.
You can do it in the same way:
class ArticlesInfo: ObservableObject {
@Published var articles: ...
//...
func loadArticles() {
// Use `ArticlesParser` here
//...
}
}
Sorry for the late reply.
A bit confused of how I'm supposed to use ArticlesParser in the ObservableObject. Here's the code so if I missed something, it's here:
class ArticlesInfo: ObservableObject {
@Published var articles: [Article] = []
func loadArticles() {
class ArticlesParser: XMLParser {
var dateTimeZone = TimeZone(abbreviation: "GMT-6")
lazy var dateFormater: DateFormatter = {
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "MMM dd, yyyy"
df.timeZone = dateTimeZone
return df
}()
private var textBuffer: String = ""
private var nextArticle: Article? = nil
override init(data: Data) {
super.init(data: data)
self.delegate = self // Cannot assign value of type 'ArticlesParser' to type 'XMLParserDelegate?'
}
}
extension ArticlesParser: XMLParserDelegate { // Declaration is only valid at file scope
// Called when opening tag (`elementName`) is found
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
switch elementName {
case "posts":
nextArticle = Article()
case "title":
textBuffer = ""
case "date":
textBuffer = ""
case "author":
textBuffer = ""
case "img":
textBuffer = ""
case "content":
textBuffer = ""
default:
print("Ignoring \(elementName)")
break
}
}
// Called when closing tag (`/elementName`) is found
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
switch elementName {
case "posts":
if let article = nextArticle {
articles.append(article) // Cannot find 'articles' in scope
}
case "title":
nextArticle?.title = textBuffer
case "date":
print("date: \(textBuffer)")
nextArticle?.date = dateFormater.date(from: textBuffer)
case "author":
nextArticle?.author = textBuffer
case "img":
print("img: \(textBuffer)")
nextArticle?.img = URL(string: textBuffer)
case "content":
nextArticle?.content = textBuffer
default:
print("Ignoring \(elementName)")
break
}
}
// Called when a character sequence is found
// This may be called multiple times in a single element
func parser(_ parser: XMLParser, foundCharacters string: String) {
textBuffer += string
}
// Called when a CDATA block is found
func parser(_ parser: XMLParser, foundCDATA CDATABlock: Data) {
guard let string = String(data: CDATABlock, encoding: .utf8) else {
print("CDATA contains non-textual data, ignored")
return
}
textBuffer += string
}
// For debugging
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
print(parseError)
print("on:", parser.lineNumber, "at:", parser.columnNumber)
}
}
}
}
You have no need to (and should not, in this case) make ArticlesParser a local class nested in a function.
Good to know. I just need to know where to put this. I will make this a separate thread because this is pretty much the parser I needed.
Edit:
New thread: https://developer.apple.com/forums/thread/675586
Sorry, I do not understand why you removed it. It is I who is confused.
Apologies, I must have switched it around.
ArticlesParser now holds articles as a published variable. But ArticlesInfo - where the loadArticles() function is held - now cannot call articles because that property does not exist in ArticlesInfo.
class ArticlesInfo: ObservableObject {
var dataUrl = URL(string: https://storage.googleapis.com/thehairsociety/2021/03/2f23e2d4-ths.xml)
func loadArticles() {
do {
if FileManager.default.fileExists(atPath: dataUrl!.path) {
return
}
//Read `Data` from a file specified by URL
let data = try Data(contentsOf: dataUrl!)
//Parse Available Articles
let articles = try // ArticlesParser? JSONDecoder?
self.articles = articles // -- Value of type 'ArticlesInfo' has no property 'articles'
} catch {
print(error)
}
}
}
So do I need to call articles from ArticlesParser or should I just redefine articles? Hopefully this clears some things up a bit.
On the topic of using the Lunch Card method, would using JSON be easier to use than XML? I'm curious because after doing a few tests it seems it's a bit simpler though I haven't tested actually article content (HTML) yet.
You need to pass the right type.
Thanks for pointing that out.
Your new ArticlesParser lacks the property articles and it does not compile.
So do I redefine it? I'm still a little confused.
In the latest code for AddView , colorScheme is used.
You're right, my mistake.
It'd probably be after cards.append(newCard) using the add() function in info.
Assign empty to newCard It is better for you to make it by yourself. Good idea. Thanks.
Edit: False alarm!! Didn't work after all!
Alright, I kinda figured this out myself:
I just created a new Xcode project and copied over my code. It's pretty simple.
I think it may have been a server problem on Apple's side, because I was finally able to delete the version, but I'll leave this open for anyone else having problems.