Timeline refresh issue for widget on ios 18.2

I use AppIntent to trigger a widget refresh, Appint is used on Button or Toggle,as follows

var isAudibleArming = false struct SoundAlarmIntent: AppIntent { static var title: LocalizedStringResource = "SoundAlarmIntent" func perform() async throws -> some IntentResult { isAudibleArming = true return .result() } }

func timeline( for configuration: DynamicIntentWidgetPersonIntent, in context: Context ) async -> Timeline<Entry> { var entries: [Entry] = [] let currentDate = Date() let entry = Entry(person: person(for: configuration)) entries.append(entry)

        if isAudibleArming {
            let entry2 = Entry(person: Person(name: "Friend4", dateOfBirth: currentDate.adding(.second, value: 6)))
            entries.append(entry2)
        }
        
        return .init(entries: entries, policy: .never)
    }

The timeline function fires, with entry corresponding to view1 and entry2 corresponding to view2. I expect to show view1 immediately and view2 6 seconds later. You get the correct response on iOS17. But the 6 second delay function on the discovery code in iOS18.2 takes effect immediately, view1 flashes, view2 appears immediately instead of waiting 6 seconds to appear.

Answered by darkpaw in 819360022

This is not how widgets work. The timeline is a series of snapshots of data, i.e. what should appear at the first date, then the second date, then the third etc. Your widget doesn't really run any code.

You do not need a .init() method in your Entry struct, and I suspect your issue with it immediately flashing to the next timeline entry is because your Entry struct sets the date to .now for every entry.

That struct should look something like this:

struct EventEntry: TimelineEntry {
	public let date: Date
	let configuration: DynamicIntentWidgetPersonIntent
	let person: Person
	let type: FriendType
}

Then in the timeline you create a series of entries that reflect what should happen at various times.

And note that you're limited to about 72 updates per day, and widgets aren't really deigned to update every few seconds, so adding 3 or 6 seconds to a date isn't going to guarantee that your widget gets updated when you think it will.

Once you have a timeline set up you can see it in the preview in Xcode with something like this:

#Preview(as: .systemSmall) {
	WidgetExtension()
} timeline: {
	let person = //some Person object
	let currentDate: Date = Date.now
	for minuteOffset in stride(from: 0, to: (24 * 60), by: 20) { // every 20 mins for one day = 72 entries
		let entryDate = Calendar.current.date(byAdding: .minute, value: minuteOffset, to: currentDate, wrappingComponents: false)!
		EventEntry(date: entryDate, configuration: configuration, person: person, type: .whatever))
	}
}

Scroll through the timeline at the bottom of the preview window to see how your widget changes over time.

I have no idea how you're using the .init method and this whole FriendType thing, but I don't think it's working how you think it's working.

var isAudibleArming = false
 struct SoundAlarmIntent: AppIntent {
   static var title: LocalizedStringResource = "SoundAlarmIntent" 

  func perform() async throws -> some IntentResult {
   isAudibleArming = true
   return .result()
  }
}

func timeline( for configuration: DynamicIntentWidgetPersonIntent, in context: Context ) async -> Timeline<Entry> { 
    var entries: [Entry] = []
    let currentDate = Date()
    let entry = Entry(person: person(for: configuration)) entries.append(entry)

    if isAudibleArming {
            let entry2 = Entry(person: Person(name: "Friend4", dateOfBirth: currentDate.adding(.second, value: 6)))
            entries.append(entry2)
        }
        
        return .init(entries: entries, policy: .never)
}

Maybe it's clearer this way。Can you help me? Thank you

How is that a 6-second delay? You've set currentDate to Date.now, then added six seconds to it and set that as the Person's date of birth, and added that entry to the timeline.

What are the dates of the entries in the timeline, and what does the Entry struct look like?

Thank you for your reply, there is a problem with the previous code date. I've put up part of my project code, and my project has a lot of different views, so I use FriendType to differentiate, and I use a global FriendGlobalType to keep track of which FriendType I'm currently in. For simplicity's sake, I've only used two types so far. Question: Why does the view2 corresponding to F2 flash by on iOS18.2 and show view1 corresponding to F1 without a delay of 3 seconds? Everything works on iOS17.

struct Entry: TimelineEntry {
        var date: Date = .now
        var person: Person?
        var type: FriendType = .F1
        
        init(date: Date, person: Person? = nil, type: FriendType) {
            self.date = date
            self.person = person
            self.type = type
            FriendGlobalType.type = type
        }
    }

extension DynamicIntentWidget {
    struct EntryView: View {
        let entry: Entry
        
        var body: some View {
            VStack {
                switch entry.type {
                case .F1:
                    Button(intent: FriendIntent()) {
                        Text("Click")
                    }
                case .F2:
                    Text("F2")
                }
                
            } .containerBackground(.clear, for: .widget)
        }
    }
}

enum FriendType: String, AppEnum {
    case F1
    case F2

    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "FriendType")
    
    static let caseDisplayRepresentations: [Self: DisplayRepresentation] = [
        .F1: DisplayRepresentation(title: "F1"),
        .F2: DisplayRepresentation(title: "F2"),
    ]
}

struct FriendGlobalType {
    static var type: FriendType = .F1
}

struct FriendIntent: AppIntent {
    static var title: LocalizedStringResource = "FriendIntent"
    func perform() async throws -> some IntentResult {
        FriendGlobalType.type = .F2
        return .result()
    }
}


func timeline(
            for configuration: DynamicIntentWidgetPersonIntent,
            in context: Context
        ) async -> Timeline<Entry> {
            var entries: [Entry] = []
            let currentDate = Date.now
            let entry = Entry(date: currentDate,person: person(for: configuration),type: FriendGlobalType.type)
            entries.append(entry)
            
            if FriendGlobalType.type == .F2 {
                let entry2 = Entry(date: currentDate.adding(.second, value: 3), type: .F1)
                entries.append(entry2)
            }
            
            return .init(entries: entries, policy: .never)
        }
Accepted Answer

This is not how widgets work. The timeline is a series of snapshots of data, i.e. what should appear at the first date, then the second date, then the third etc. Your widget doesn't really run any code.

You do not need a .init() method in your Entry struct, and I suspect your issue with it immediately flashing to the next timeline entry is because your Entry struct sets the date to .now for every entry.

That struct should look something like this:

struct EventEntry: TimelineEntry {
	public let date: Date
	let configuration: DynamicIntentWidgetPersonIntent
	let person: Person
	let type: FriendType
}

Then in the timeline you create a series of entries that reflect what should happen at various times.

And note that you're limited to about 72 updates per day, and widgets aren't really deigned to update every few seconds, so adding 3 or 6 seconds to a date isn't going to guarantee that your widget gets updated when you think it will.

Once you have a timeline set up you can see it in the preview in Xcode with something like this:

#Preview(as: .systemSmall) {
	WidgetExtension()
} timeline: {
	let person = //some Person object
	let currentDate: Date = Date.now
	for minuteOffset in stride(from: 0, to: (24 * 60), by: 20) { // every 20 mins for one day = 72 entries
		let entryDate = Calendar.current.date(byAdding: .minute, value: minuteOffset, to: currentDate, wrappingComponents: false)!
		EventEntry(date: entryDate, configuration: configuration, person: person, type: .whatever))
	}
}

Scroll through the timeline at the bottom of the preview window to see how your widget changes over time.

I have no idea how you're using the .init method and this whole FriendType thing, but I don't think it's working how you think it's working.

Thank you very much for your reply. I think the default is F1. The func timeline function does not enter the criteria for if FriendGlobalType.type ==.f2, so there is only one entry.

Every time I click the Button, the state changes to F2. At this point, the func timeline will generate two entries. One is F2. The other is the status change to F1 after 3 seconds.

The init method and FriendGlobalType are for logging state. Because my request here is to trigger the network request after clicking Button on F1, await the network request, refresh the UI to F2. And then back to F1 in three seconds. I simplified the code and did not attach the network request code.

This logic actually works on iOS17. But I don't know why ios 18.2 doesn't work. Is there a better design?

By the way, code like the following. It runs directly, and it does work on iOS18.2. But not if it's a timeline refresh triggered by Button or Toggle.

    func timeline(
            for configuration: DynamicIntentWidgetPersonIntent,
            in context: Context
        ) async -> Timeline<Entry> {
            var entries: [Entry] = []
            let currentDate = Date.now
            let entry = Entry(date: currentDate,person: person(for: configuration),type: .F2)
            entries.append(entry)
            
            let entry2 = Entry(date: currentDate.adding(.second, value: 3), type: .F1)
            entries.append(entry2)
            
            return .init(entries: entries, policy: .never)
        }

I've figured out why, because of this static global variable, FriendGlobalType. I'm going to use UserDefaults to record the states, and then I'm going to get the states. It's not quite clear why you can't use static global variables this way. But at least it worked out. Thank you very much!

Timeline refresh issue for widget on ios 18.2
 
 
Q