【问题标题】:Need help to make iOS 14 Widget show content from from REST api需要帮助以使 iOS 14 小部件显示来自 REST api 的内容
【发布时间】:2020-11-13 02:04:31
【问题描述】:

最初的问题是在 Apple 论坛上,但没有人可以提供帮助。 https://developer.apple.com/forums/thread/654967?answerId=622833022#622833022

我决定问一下。

问题

我正在开发一个显示 REST api 内容的小部件扩展。 它显示更新的以下股票信息。

Widgets Code-along, part 3: Advancing timelines 对我没有帮助。

调查给我带来了一个虽然“这是 iOS 测试版的错误”:

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)
  }

上面的代码在 1 秒内打印 Provider.timeline: 14->19 次。


这是我处理网络请求但没有成功的代码:

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 
  }

我得到了日志:

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:)

以上代码不会更新或向小部件 UI 显示 Stock,它有时会出现一些奇怪的崩溃。

任何人都可以帮我完成它吗?

【问题讨论】:

标签: swift widget swiftui ios14 widgetkit


【解决方案1】:

我不是快速网络方面的专家,但在我看来,您有一个 dataTask 实例,并在发出新请求之前取消它。那么,当您的时间线重新加载时,是否会取消现有请求,从而使您没有任何条目导致它再次重新加载?

如果没有看到您的 Provider 实施以及您的重新加载策略,很难说出真正发生的事情。

【讨论】:

  • 同意,原因是时间线已重新加载。但我不明白为什么我的时间线在短短 1 秒内就被重新加载了这么多次。
  • IIRC 在 beta 3 中修复了一个时间线重新加载错误。您使用的是什么重新加载策略?
  • 我在上面的代码片段中使用reloadPolicy: .atEnd
【解决方案2】:

在等待它稳定约 3 个月后,我返回到小部件开发。

我可以确认,自从我昨天升级到Xcode Version 12.2 beta 2 (12B5025f) 后,我之前的代码基本上可以正常工作。 但是我需要删除小部件并创建一个新的小部件以避免过时的代码。

--- 更新---

由于某些因素,iOS 限制小部件刷新,因此每 5 分钟从 API 刷新一次内容可能并不总是像我预期的那样工作。

请查看Refreshing Widgets Efficiently的官方文档

如文档中所述,使用以下方法来优化小部件刷新:

  • 让包含的应用程序在小部件需要数据之前为小部件准备数据。使用共享组容器来存储数据。
  • 在您的应用程序中使用后台处理时间来使共享数据保持最新。有关更多信息,请参阅使用后台应用刷新更新您的应用。
  • 如前一节所述,为所显示的信息选择最合适的刷新策略。
  • 仅当小部件当前显示的信息发生变化时才调用 reloadTimelines(ofKind:)。
  • 当您的应用处于前台、有活动的媒体会话或使用标准位置服务时,刷新不计入微件的每日限制

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-04
    • 2013-12-21
    • 1970-01-01
    • 1970-01-01
    • 2020-06-22
    • 1970-01-01
    相关资源
    最近更新 更多