Problem is that you return submenu immediately, before any item has been added:
line 6 dispatches to a thread
then code continues immediately at line 38, where subMenu is not yet filled
1. func laodRss(outline: Outline, subMenu: NSMenu) -> NSMenu {
2. var articleList = [NSMenuItem] ()
3.
4. let url = URL(string: outline.xmlUrl)!
5.
6. AF.request(url).responseRSS() { (response) -> Void in
7. if let feed: RSSFeed = response.value {
8. for item in feed.items {
9. let article = NSMenuItem()
10. let timeString = self.formatDate(item: item)
11. if timeString != "" {
12. var title = self.shortenText(item: item.title!)
13. title = title + " " + timeString
14.
15. let someObj: NSString = item.link! as NSString
16. article.representedObject = someObj
17. article.action = #selector(self.openBrowser(urlSender:))
18. article.title = title
19.
20. //// Get the url from the article and add /favicon.ico to get the image
21. //// Will add the image to each article to indicate the source
22. let url = URL(string: outline.icon)
23.
24. self.getData(from: url!) { data, response, error in
25. guard let data = data, error == nil else { return }
26.
27.
28. DispatchQueue.main.async() { [weak self] in
29. article.image = NSImage(data: data)
30. article.image?.size = CGSize(width: 15, height: 15)
31. }
32. }
33. subMenu.addItem(article)
34. }
35. }
36. }
37. }
38. return subMenu
39. }
You have a few ways to correct this:
for a very quick test, add a sleep(20) just before line 38. But that's just for test, not a solution for real app.
use semaphore, to make sure all operations requests are completed
use completion handler
or, with iOS 15, use await/async
For completion handler, should be like this (sorry, I could not test in app, hope there's no error here:
var theMenu: NSMenu?
typealias FinishedDownload = (NSMenu) -> Void
func loadRss(outline: Outline, subMenu: NSMenu, completed : @escaping FinishedDownload) { // Corrected name
var articleList = [NSMenuItem]()
let url = URL(string: outline.xmlUrl)!
AF.request(url).responseRSS() { (response) -> Void in
if let feed: RSSFeed = response.value {
for item in feed.items {
let article = NSMenuItem()
let timeString = self.formatDate(item: item)
if timeString != "" {
var title = self.shortenText(item: item.title!)
title = title + " " + timeString
let someObj: NSString = item.link! as NSString
article.representedObject = someObj
article.action = #selector(self.openBrowser(urlSender:))
article.title = title
//// Get the url from the article and add /favicon.ico to get the image
//// Will add the image to each article to indicate the source
let url = URL(string: outline.icon)
self.getData(from: url!) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async() { [weak self] in
article.image = NSImage(data: data)
article.image?.size = CGSize(width: 15, height: 15)
}
}
subMenu.addItem(article)
}
}
completed(subMenu)
}
}
// No more return return subMenu
}
// Then you call as:
func callIt() {
theMenu = NSMenu() // Or the initial value of your submenu
loadRss(outline: someOutline, subMenu: theMenu) {
subMenu in theMenu = subMenu
}
}
.
You will find investing discussion here:
https://stackoverflow.com/questions/36829749/how-to-wait-for-a-function-to-end-on-ios-swift-before-starting-the-second-one
Note: you have probably misspelled loadRss as laodRss. Not an immediate issue, but may cause you trouble some time.