The original question is at Apple forum but no one can help. https://developer.apple.com/forums/thread/654967?answerId=622833022#622833022
I decided to ask on SO.
I'm developing a widget extension that show content from REST api. It show the updated following stock info.
Widgets Code-along, part 3: Advancing timelines doesn't help me.
Investigation brings me a though "it is a bug of iOS beta":
public func timeline(
for configuration: ConfigurationIntent,
with context: Context,
completion: @escaping (Timeline<Entry>) -> ()
) {
print("Provider.timeline: ")
var entries: [SimpleEntry] = []
let currentDate = Date()
entries.append(SimpleEntry(
date: Calendar.current.date(byAdding: .second, value: 15, to: currentDate)!,
configuration: configuration
))
let timeline = Timeline(entries: entries, policy: reloadPolicy)
completion(timeline)
}
Above code print Provider.timeline:
14->19 times in just 1 second.
Here's my code to work with network request without success:
public func timeline(
for configuration: ConfigurationIntent,
with context: Context,
completion: @escaping (Timeline<Entry>) -> ()
) {
print("Provider.timeline: ")
fetchStocks { (stocks: [Stock], error: Bool) in
print("fetchStocks: stocks: ", stocks)
completion(getTimeLineFromStocks(stocks: stocks, for: configuration, with: context, reloadPolicy: .atEnd))
}
}
func getTimeLineFromStocks(
stocks: [Stock],
for configuration: ConfigurationIntent,
with context: Context,
reloadPolicy: TimelineReloadPolicy
) -> Timeline<Entry> {
var entries: [SimpleEntry] = []
let currentDate = Date()
entries.append(SimpleEntry(
date: Calendar.current.date(byAdding: .second, value: 15, to: currentDate)!,
configuration: configuration,
stocks: stocks
))
let timeline = Timeline(entries: entries, policy: reloadPolicy)
return timeline
}
func fetchStocks(completion: @escaping ([Stock], Bool) -> Void) {
// Fetch stocks info from API
myStockService.getSearchResults(searchTerm: "FIT", perPage: 5) { results, errorMessage in
if !errorMessage.isEmpty {
print("--> Search error: " + errorMessage)
completion([], true)
} else if results == nil {
print("--> Search result with ERROR: nil results")
completion([], true)
} else {
print("--> searchResults: ", results)
completion(results!, false)
// reloadTimeline()
}
}
}
// ------- MyStockService.swift -------
// If I set breakpoint I can see the list of stocks
func getSearchResults(searchTerm: String, perPage: Int, completion: @escaping QueryResult) {
// 1
dataTask?.cancel()
// 2
if var urlComponents = URLComponents(string: "https://****************/my-stocks") {
urlComponents.query = "foo=bar"
// 3
guard let url = urlComponents.url else {
return
}
// 4
dataTask = defaultSession.dataTask(with: url) { [weak self] data, response, error in
defer {
self?.dataTask = nil
}
// 5
if let error = error {
self?.errorMessage += "DataTask error: " + error.localizedDescription + "\n"
} else if
let data = data,
let response = response as? HTTPURLResponse,
response.statusCode == 200 {
// update the: self?.resultItems, self?.errorMessage
self?.updateSearchResults(data, perPage: perPage)
// 6
DispatchQueue.main.async {
completion(self?.resultItems, self?.errorMessage ?? "")
}
}
}
// 7
dataTask?.resume()
}
}
func updateSearchResults(....) {
... This fn convert data to [Stock] and assign it to resultItems
}
I got the log:
Provider.timeline:
Provider.timeline:
Provider.timeline:
Provider.timeline:
Provider.timeline:
Provider.timeline:
Provider.timeline:
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
fetchStocks: stocks: []
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
fetchStocks: stocks: []
2020-07-23 18:06:38.131476+0700 my-widgetExtension[5315:1272323] libMobileGestalt MobileGestaltCache.c:166: Cache loaded with 4563 pre-cached in CacheData and 53 items in CacheExtra.
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
fetchStocks: stocks: []
Provider.timeline:
Provider.timeline:
Provider.timeline:
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
fetchStocks: stocks: []
--> Search error: DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
DataTask error: cancelled
fetchStocks: stocks: []
2020-07-23 18:06:39.751035+0700 my-widgetExtension[5315:1272388] [connection] nw_resolver_start_query_timer_block_invoke [C1] Query fired: did not receive all answers in time for api-t19.24hmoney.vn:443
2020-07-23 18:06:51.891582+0700 my-widgetExtension[5315:1272323] [widget] No intent in timeline(for:with:completion:)
Above code doesn't update or show Stock to the widget UI, it sometimes has some strange crashed.
Anyone can help me to make it work?
Touch and hold the Lock Screen until the Customize button appears, then tap Customize. Tap the box above or below the time to see the widgets that you can add to your Lock Screen. Tap or drag the widgets that you want to add. Tap Done.
I return to widget development after ~3 months of waiting for it to be stable.
I can confirm that my above previous code basically works since I upgrade to Xcode Version 12.2 beta 2 (12B5025f)
yesterday.
But I need to delete the widget and create a new one to avoid outdated code.
--- Updated ---
iOS limit widget refreshment by some factor so content refreshing from API every 5 mins might not always work as I expected.
Please see the official docs about Refreshing Widgets Efficiently
As mentioned in the docs, use the following approaches to optimize your widget refreshes:
- Have the containing app prepare data for the widget in advance of when the widget needs it. Use a shared group container to store the data.
- Use background processing time in your app to keep shared data up to date. For more information, see Updating Your App with Background App Refresh.
- Choose the most appropriate refresh policy for the information being shown, as described in the preceding section.
- Call reloadTimelines(ofKind:) only when information the widget is currently displaying changes.
- When your app is in the foreground, has an active media session, or is using the standard location service, refreshes don’t count against the widget’s daily limit
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With