【问题标题】:Swift - Display value form API in widget and reload widgetSwift - 在小部件中显示值表单 API 并重新加载小部件
【发布时间】:2021-10-14 09:26:05
【问题描述】:

简要说明;我正在为我的特斯拉汽车做一个项目。 Tesla 已经有一个只能添加到“今日视图”选项卡的小部件,当我滑动到“今日视图”时,该小部件会自动刷新。这是它的样子:Image here

我想在我的小部件中完成同样的事情。我基本上有工作代码,请看下面:

注意:我的这个项目仅供实验和个人使用。

extension Date {
    func timeAgoDisplay() -> String {
        let formatter = RelativeDateTimeFormatter()
        formatter.unitsStyle = .full
        return formatter.localizedString(for: self, relativeTo: Date())
    }
}

import WidgetKit
import SwiftUI
import Intents
import TeslaSwift

struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), configuration: ConfigurationIntent())
    }

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), configuration: configuration)
        completion(entry)
    }

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        getVehicle() // Run this function to get vehicle info

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationIntent
}

var lastUpdated = String()
var batteryLevel = Int()
var interiorTemperature = Double()

func getVehicle() {
    
    let apii = TeslaSwift()
    
    if let jsonString = UserDefaults(suiteName: "group.widget")!.string(forKey: "GlobalToken"),
       let token: AuthToken = jsonString.decodeJSON(),
       let _ = UserDefaults(suiteName: "group.widget")!.string(forKey: "GlobalToken") {
        apii.reuse(token: token, email: nil)
    }
    
    apii.useMockServer = false
    apii.debuggingEnabled = true
    
    let id = UserDefaults(suiteName: "group.widget")!.string(forKey: "GlobalSelectedID")
    
    apii.getVehicle(id!).done {
        (vehicle: Vehicle) -> Void in
        
        apii.getAllData(vehicle).done { (extendedVehicle: VehicleExtended) in
            
            batteryLevel = (extendedVehicle.chargeState?.batteryLevel)!
            interiorTemperature = (extendedVehicle.climateState?.insideTemperature!.celsius)!
            
            let formatter = DateFormatter()
            formatter.dateFormat = "dd.MM.yyyy - HH:mm:ss"
            let now = Date()
            let dateString = formatter.string(from:now)
            lastUpdated = dateString
            
        }.catch { (error) in
            
            print("error1: \(error)")
        }
        
    }.catch { error in
        print("error2: \(error)")
    }
}

struct PWidgetEntryView : View {
    
    var entry: Provider.Entry
    
    var body: some View {

        VStack {
            Text("Battery: \(batteryLevel)%")
            Text("Temparature: \(String(format: "%.0f", interiorTemperature))")
            Text("Last Updated: \(lastUpdated)")
                .environment(\.sizeCategory, .extraSmall)
        }
    }
}

@main
struct PWidget: Widget {
    let kind: String = "PWidget"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            PWidgetEntryView(entry: entry)
        }
        .supportedFamilies([.systemMedium])
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
    }
}

struct PWidget_Previews: PreviewProvider {
    static var previews: some View {
        PWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
            .previewContext(WidgetPreviewContext(family: .systemMedium))
    }
}

所以现在,当使用此代码时。它完美地获取数据,但小部件不显示数据。

如果我在lastUpdated = dateString 之后添加WidgetCenter.shared.reloadAllTimelines(),小部件会更新,但也会保持大约每五秒更新一次。这会消耗大量电池。

我还尝试在 func getVehicle() { 之外添加 var didUpdateManually = false,然后像这样进行 if false 检查。这使得它更新了一次小部件,但永远不会再更新:

if (didUpdateManually == false) {
    WidgetCenter.shared.reloadAllTimelines()
    didUpdateManually = true
}

所以基本上我要完成两/三件事:

1. Display the value from API to my widget (batteryLevel, interiorTemperature and lastUpdated timestamp).

2. If either or both is possible:
2.A: When the widget is added to the Today View tab, I want to automatically update the widget by re-running the `func getVehicle()` and update the info when the user swipe to the Today View tab.
2.B: If the widget is on the home screen page, I want to widget to automatically update when the in the same way as 2A, or update once every hour or so.

【问题讨论】:

  • 这不是小部件的工作方式。您的小部件配置为每小时更新一次,持续 5 小时,但您不向其传递任何数据 — SimpleEntry 应包含小部件需要更新的所有数据。如果您还没有尝试过 Apple 的 WidgetKit 教程,那么这是了解所有这些组件如何协同工作的好方法:developer.apple.com/news/?id=yv6so7ie
  • @Adam 是的,看起来我有点误解了。谢谢你的解释。你说Your widget is configured to update once per hour for five hours这到底是什么意思?我的 Widget 会在 5 小时内每小时更新一次,然后就再也不会更新了?
  • 您的代码创建了一个包含 5 个条目的时间线和“.atEnd”刷新策略。因此,您的小部件将使用这 5 个条目更新 5 个小时,然后在第五个条目之后的“某个时间”,系统将再次调用您的 getTimeline() 方法。我说“有时”是因为系统控制小部件更新的频率;你只能告诉它你想要发生的事情。更多细节在这里:developer.apple.com/documentation/widgetkit/…
  • @Adam 好的,因为我的小部件显示的日期和时间在每次小部件更新时都应该不同。在这种情况下,我现在使用的方法是否正确?
  • 这是一个旧的“今日小部件”,它仍然可以工作,但在 iOS 14 中已被弃用。它们每次出现时都可以运行代码,但新的小部件不能。

标签: ios swift xcode widgetkit


【解决方案1】:

对于每 1 小时使用 atEnd Policy 的更新小部件,其中包含两个条目:-

  func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
             getVehicle() // Run this function to get vehicle info
             let entries = [
              SimpleEntry(date: Date(), configuration: configuration)
              SimpleEntry(date: Calendar.current.date(byAdding: .minute, value: 60, to: Date())!, configuration: configuration)
                ]
              let timeline = Timeline(entries: entries, policy: .atEnd)
              completion(timeline)
            }
    }
// If its not updating the widget just call api in getTimeline and on the success of api add timeline entries.

// 在我这边,我已经获取日历事件,它对我有用,希望它也对你有用。谢谢

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-03
    • 2020-11-19
    • 1970-01-01
    • 2020-02-21
    • 2013-05-08
    • 1970-01-01
    • 2020-09-09
    相关资源
    最近更新 更多