Обновление метки времени каждую минуту в WidgetKit

Можно ли создать текстовую метку в виджете, которая будет показывать текущее время и будет обновляться в реальном времени?

Пытаюсь создать виджет часов, но виджет обновляется только 1 раз каждые 5 минут.

  • Создание временной шкалы не помогло
  • Поддержание виджета в актуальном состоянии не работает с текущим временем, только с таймерами и т. Д.

person Maks    schedule 24.09.2020    source источник
comment
Можете ли вы показать код, который вы используете для временной шкалы? временная шкала с политикой обновления 5 минут должна работать в вашем случае   -  person Itay Brenner    schedule 24.09.2020
comment
@ItayBrenner временная шкала с политикой обновления 5 минут будет обновлять текстовую метку времени каждые 5 минут или будет обновлять метку чаще?   -  person Maks    schedule 24.09.2020
comment
Он будет обновляться каждые 5 минут, я неправильно прочитал ваш вопрос. Вы не можете обновлять его, как это делают Часы, но вы можете обновлять его каждую секунду / минуту с помощью политики Timeline .after(1 minute). Вы просто получаете фактическую дату каждого выполнения вместо того, чтобы использовать для этого таймер.   -  person Itay Brenner    schedule 24.09.2020
comment
@ItayBrenner Я пробовал это и создал временную шкалу на 1 минуту, но эта временная шкала не выполняется чаще, чем раз в 5 минут. Apple установила ограничение на эти казни   -  person Maks    schedule 24.09.2020
comment
Существует автоматическое обновление для текста (дата) (см. Раздел «Отображение динамических дат»), но я не думаю, что вы не можете обновить что-либо еще.   -  person Itay Brenner    schedule 25.09.2020


Ответы (3)


Возможное решение - использовать стиль даты time:

/// A style displaying only the time component for a date.
///
///     Text(event.startDate, style: .time)
///
/// Example output:
///     11:23PM
public static let time: Text.DateStyle

  1. Вам нужен простой Entry со свойством Date:
struct SimpleEntry: TimelineEntry {
    let date: Date
}
  1. Создавайте Entry каждую минуту до следующей полуночи:
struct SimpleProvider: TimelineProvider {
    ...

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
        var entries = [SimpleEntry]()
        let currentDate = Date()
        let midnight = Calendar.current.startOfDay(for: currentDate)
        let nextMidnight = Calendar.current.date(byAdding: .day, value: 1, to: midnight)!

        for offset in 0 ..< 60 * 24 {
            let entryDate = Calendar.current.date(byAdding: .minute, value: offset, to: midnight)!
            entries.append(SimpleEntry(date: entryDate))
        }

        let timeline = Timeline(entries: entries, policy: .after(nextMidnight))
        completion(timeline)
    }
}
  1. Отображение даты в стиле time:
struct SimpleWidgetEntryView: View {
    var entry: SimpleProvider.Entry

    var body: some View {
        Text(entry.date, style: .time)
    }
}

Если вы хотите настроить формат даты, вы можете использовать свой собственный DateFormatter:

struct SimpleWidgetEntryView: View {
    var entry: SimpleProvider.Entry
    
    static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "HH:mm"
        return formatter
    }()

    var body: some View {
        Text("\(entry.date, formatter: Self.dateFormatter)")
    }
}

Вот репозиторий GitHub с различными примерами виджетов, включая виджет часов.

person pawello2222    schedule 24.09.2020
comment
Работает как шарм. Вы спасли мою жизнь. Спасибо! - person Maks; 25.09.2020
comment
есть ли способ скрыть Am / Pm. Кто-нибудь достиг этого? - person Amit Verma; 06.10.2020
comment
@AmitVerma Я думаю, вы можете использовать entry.date внутри своей собственной функции, которая возвращает строку по мере необходимости. - person Happygallo; 13.10.2020
comment
@Happygallo Вы также можете использовать DateFormatter - см. Обновленный ответ. - person pawello2222; 13.10.2020
comment
@Alan Вот возможное решение: stackoverflow.com/a/64054466/8697793 - person pawello2222; 09.11.2020

Большое спасибо за ответ @ pawello222, но проблема с этим решением заключается в том, что на временной шкале сохраняется слишком много вещей (24 * 60).

Как только вид виджета будет содержать много элементов (например, 5 или более Text (...) в моем случае), виджет полностью прекратит рендеринг на реальном устройстве ios, и xcode сообщит об этом:

2020-12-03 11:22:46.094442+0800 PixelClockWidgetExtension[660:53387] 
[default] -[EXSwiftUI_Subsystem beginUsing:withBundle:] unexpectedly called multiple times.

Я думаю, что одним из возможных решений является разделение ежедневного времени на несколько небольших частей и сохранение их на временной шкале несколько раз.

Моя среда:

  • iOS 14.2
  • Xcode 12.2
  • iPhone 12 pro
person D.Sai    schedule 03.12.2020
comment
@ pawello2222 Отчет об ошибке - person D.Sai; 03.12.2020
comment
Смотрите и это. У моего первого виджета было изображение и 1 текст (). Создайте 10 записей на временной шкале. Макс на 1 минуту позади. Перешел к виджету с изображениями и многим другим текстом (). Создает множество записей на временной шкале, но просто игнорируется, по-видимому, обновляется только при получении каждого пакета (все еще выполняется отладка). Может быть, мне придется визуализировать представление как изображение, чтобы обойтись? Удачи на твоей стороне? - person t9mike; 12.12.2020
comment
@ t9mike Определенный факт: вы не можете обновить виджет слишком быстро, иначе система откажется обновлять и перезапустит механизм в то время, которое она сочтет подходящим. Согласно моему тесту, лучше не создавать пакет временной шкалы быстрее 15 минут. - person D.Sai; 16.12.2020

Мое решение обновлять виджет каждую минуту в 0 секунд. Используйте это расширение, чтобы получить текущее время с 0 секундами минуты.

extension Date {

var zeroSeconds: Date? {
    let calendar = Calendar.current
    let dateComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: self)
    return calendar.date(from: dateComponents)
}}

и используйте эту функцию getTimeline

    func getTimeline(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 date = Date().zeroSeconds!
    
    for hourOffset in 0 ..< 60 {
        
        let entryDate = Calendar.current.date(byAdding: .minute , value: hourOffset, to: date)!
        print(entryDate)
        let entry = SimpleEntry(date: entryDate)
        entries.append(entry)
    }

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

simpleEntry такой же, как пример по умолчанию из Xcode

struct SimpleEntry: TimelineEntry {
let date: Date}

и то же тело

var body: some View {
    
    Text(entry.date, style: .time) }

Итак, теперь у вас есть виджет, который обновляется каждую минуту без задержки.

person Vlad Bruno    schedule 26.04.2021
comment
Это позволяет не более одного часа, после чего время на виджете остановится. - person Ben; 09.05.2021
comment
@Ben только что проверил это снова. Работает безупречно более часа - person Vlad Bruno; 11.05.2021