Is it possible to dynamicly update / change they layout (e.g. number of views) in an iOS 14 Widget?

1.3k Views Asked by At

I am working on my very first iOS 14 widget. I know that widgets are mostly static and cannot show dynamic content like animations or. But is it possible to change the layout of / number of views in a widget?

Since I am new to SwiftUI I don't see any way to change the view content.

Context:

Assume an ToDo app where the widget should show the buttons/links for up to 5 entries. However, if there are currently only 3 entries in the app, the widget should of course show only 3 links.

The app already offers a Today Widget. Here I have solved the problem by simply adding controls (buttons) for fixed number of entries (e.g. 5) to the widget view. If less entries should be shown, the unused controls are simply hidden when the widget updates its view.

Creating a widget view with a fixed number of entries (links) in SwiftUI is no problem. But how do I hide/remove the unused views?

In SwiftUI the view is a some View variable I don't see a way to dynamically change its content:

struct MyEntryView: View {
    var body: some View {
        Hstrack {
            // Item 1

            // Item 2

            ...

            // Item n
        }
    }
}
1

There are 1 best solutions below

0
On BEST ANSWER

Widget views are static but they are redrawn whenever a new entry is passed to them.

It is not possible to create a Widget view which will update itself when the collection changes.

You can either:

  • create entries in advance
  • or force refresh the timeline when you detect changes to your collection

In both cases you need to create an Entry:

struct SimpleEntry: TimelineEntry {
    let date: Date
    let items: [String]
}

and display its items in a view:

struct SimpleWidgetEntryView: View {
    var entry: SimpleProvider.Entry

    var body: some View {
        VStack {
            ForEach(entry.items, id: \.self) {
                Text($0)
            }
        }
    }
}

The only difference is how you create Entries:

You can:

  • create Entries in advance (size of items may vary):
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
    var entries = [SimpleEntry]()

    let currentDate = Date()

    for offset in 0 ..< 5 {
        let entryDate = Calendar.current.date(byAdding: .minute, value: offset, to: currentDate)!
        entries.append(SimpleEntry(date: entryDate, items: Array(repeating: "Test", count: offset + 1)))
    }

    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
}
  • or create a single Entry with the current data and whenever the data changes, refresh the Widget by calling:
WidgetCenter.shared.reloadAllTimelines()

(There likely is a limit to how often you can reload the timeline).