13

Update: I have tried setting the policy on the timeline to .atEnd, since there is only one entry. This has not made a difference. I have tried both in the RSS Parser file, and in the AppDelegate to run the WidgetCenter reloadAllTimeLines method, and it too does nothing. This is driving me nuts, and I'm about to have to use a developer support ticket to get this figured out.

I am slowly getting my app ready for some iOS 14 features, but keep coming across bug after bug, or (MORE LIKELY) not doing things correctly on my end. I'm trying every thing in my power to get the widget to update but it will not. The widget itself is simple; it shows the title and some text of the most recent entry from an RSS feed. I built an XMLParser, and this code gets run for the timeline:

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

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

    func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
        WidgetCenter.shared.reloadAllTimelines()

        var entries: [SimpleEntry] = []
        
        
        feedParser.parseFeed(url: "https://fritchcoc.wordpress.com/feed") {(rssItems) in
            self.rssItems = rssItems
            let currentDate = Date()
            let string1 = "Latest News: \n\n"
            let string2 = rssItems[0].title
             
                var appendString1 = string1+string2
                let entry = SimpleEntry(date: currentDate, title:appendString1, description: rssItems[0].description, link: rssItems[0].link, pubDate: rssItems[0].pubDate)
                entries.append(entry)
            let refreshDate = Calendar.current.date(byAdding: .minute, value: 2, to: Date())!
           let timeline = Timeline(entries: entries, policy: .after(refreshDate))
            completion(timeline)

        }
       
    }
}

When you first install the widget, it correctly shows the latest news item. However, I added some more entries to the RSS feed to test it, and it never updates to show these changes, only whatever is new at the time of install. I install on a 2nd device, and on the 2nd device, it DOES show the new ones that I added for testing, so I know that it is working properly on the parser end, and on the RSS itself.

pawello2222
  • 46,897
  • 22
  • 145
  • 209
user717452
  • 33
  • 14
  • 73
  • 149
  • For future readers this might be helpful: [How to refresh Widget data?](https://stackoverflow.com/q/63976424/8697793) and [Setting the TimelineProvider refresh interval for Widget](https://stackoverflow.com/q/64010346/8697793) – pawello2222 Jan 14 '21 at 20:03

5 Answers5

7

iOS is handling timeline execution, and it will not show it right away. I have checked your code and everything seems ok. I am building a similar app, and everything is working. I have almost the same code. I have API hit test, and it seems that it refreshes widget every 10-20 minutes. At least in my case.

I would put it as .atEnd timeline policy.

Each configured widget receives a limited number of refreshes every day. Several factors affect how many refreshes a widget receives, such as whether the containing app is running in the foreground or background, how frequently the widget is shown onscreen, and what types of activities the containing app engages in.

WidgetKit does not impose this limit when debugging your widget in Xcode. To verify that your widget behaves correctly, test your app and widget’s behavior outside of Xcode’s debugger.Source

Did you try to debug it? What your result?

Can you try testing some API that has hit test so you can check how many times API got hit?

EDIT: If you want to update your widget as soon as the news article is added then you will need to implement notifications that will force refresh of the widget.

EDIT2: I have found that sometimes my widget wouldn't refresh after initial refresh and removing and adding widget to the screen fixed that problem. I think that this is problem occur only when developing/debugging.

EDIT3: Remove WidgetCenter.shared.reloadAllTimelines()from the getTimeline(). Build project and run it. Check console log, you should print something on every request. It should trigger in the next few minutes.

StaticV0id
  • 411
  • 4
  • 13
  • I’ve done this and as you had noticed, it works fine but then stops refreshing. I put in a call to reloadAllWidgets when receiving a push notification, and when didLaunch from app delegate. None of this has worked – user717452 Oct 01 '20 at 17:33
  • I've tried the timeline to be adding 1 entry, then doing an .atEnd. I tried doing one entry and having it do it as an .after and set it to 5 minutes. None of these worked. I tried doing it .never, and called reloadAllWidgets when the app received a notification. Nothing there either. I created 4 different entries in the timeline for 5 entries at every 5 minutes, and then set it to .atEnd, and still nothing. – user717452 Oct 01 '20 at 18:45
  • When you debug it, so set it as target and then building it to the device, do you see any logs? Just wait few minutes and check them. I made app like this today and it seems that it is refreshing every time when I unlock the device or when it passed 10-20min mark. I am only doing network request. It's really simple app. Put some print lines in the request and in the getTimeline functions so you know that it is refreshing. As I said, I had problems with it while developing but removing and adding back to the home screen fixed it. Since then it is working great. – StaticV0id Oct 01 '20 at 19:00
  • I have been running it as a TestFlight beta for the last 16 hours, and not once has the widget refreshed, and this was coming from a clean install. – user717452 Oct 01 '20 at 19:06
  • I did go ahead and put in some print commands, and am waiting to see if anything shows in the console. – user717452 Oct 01 '20 at 19:39
  • I put print in the getTimeline and in the parser of the RSS feed, and it's not showing at all in console. – user717452 Oct 01 '20 at 20:00
  • 1
    Hmm, if you don't see any output from the `getTimeline` something is wrong. Don't you see even the initial call of the `getTimeline` method? Did you change your target to build the only widget? Have you tried to remove the widget from the home screen and build the project again? If so, I think that you should consider making another empty widget extension, try to print something in the example `getTimeline` implementation, and slowly start adding everything from your original implementation. In that case, you will observe what is breaking a widget. – StaticV0id Oct 01 '20 at 23:29
2
 WidgetCenter.shared.reloadTimelines(ofKind: "Widgets")

won't work if you don't check background fetch in background mode capabilities

it took me a week to find it!

amin torabi
  • 281
  • 5
  • 8
1

https://developer.apple.com/forums/thread/653265?answerId=619739022#619739022

Updating every minute is far too aggressive. Widgets have a limited number of updates and if you were to try to update every minute, you'll quickly run out of updates. While debugging with Xcode these limits are not imposed, but if you're running your app outside of Xcode you'd see the behavior you're describing where your widget would stop updating.
What happens if you update every 15 minutes? If that doesn't work, please file a Feedback Assistant report with the details of what you're doing and the results you see.

0

Try using the .onChange function in one of your View-Files. If there is any change to your SimpleEntry array, you can call WidgetCenter.shared.reloadAllTimelines().

It could look like:

.onChange(of: simpleEntryArray.count) { change in 
      // some code to save your changes
      WidgetCenter.shared.reloadAllTimelines()
}
Patrick
  • 273
  • 2
  • 6
-1

Discovered the issue with the iOS 14 widget not refreshing in my app was caused by the default cache policy of the URLRequest. By default, the cache was being used rather than requesting new data from the server. The JSON was loading from the cached data. After adding this cachePolicy parameter to the URLRequest "cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData", the widget started to update correctly and displayed the correct up-to-date data from the server.

var urlRequest = URLRequest(url: url, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData)
mobileappz
  • 59
  • 7
  • Where should I put that? Like in the main swift file for it or within the getTimeline? – user717452 May 05 '21 at 17:08
  • Put that in the main swift file with the code where the network request is being made. Not within getTimeline. It may also depend upon the server side API’s cache policy as to whether this change makes any difference. – mobileappz May 05 '21 at 17:14
  • Within reference to my OP, I don't see where this can go. I'm using FeedParser and it won't accept a URLRequest cache policy setting – user717452 May 05 '21 at 17:17
  • Assuming you're using currently using FeedParser from here: https://cocoapods.org/pods/FeedParser, you could try using https://github.com/nmdias/FeedKit instead which seems to have superseded FeedParser. And then change the code in the FeedKit library to load the data in an uncached reading mode. Change line 78 of Feedkit/Sources/Feedkit/Parser/FeedParser.swift from data = try Data(contentsOf: sanitizedSchemeUrl) to data = try Data(contentsOf: sanitizedSchemeUrl, options: [.uncached]) I'm not sure if this will make any difference if it is already uncached by default. – mobileappz May 06 '21 at 11:50
  • Don't know how to set this in FeedParser however. – mobileappz May 06 '21 at 11:51