Created By Ningyuan — 2020/09/27
First of all, to make fun of Apple, the press conference and official documents only demonstrated the normal process based on Swift project, while we OC project or mixed, have to step on the pit research.
IOS14 brings with it the new WidgetKit framework, which is the Extension enhancement that came with iOS10. The lowest supported version is iOS14, and the development language is SwiftUI.
-
Widgets come in only three sizes: systemSmall, systemMedium, and systemLarge
Screen size (portrait) Small widget Medium widget Large widget 414×896 pt 169×169 pt 360×169 pt 360×379 pt 375×812 pt 155×155 pt 329×155 pt 329×345 pt 414×736 pt 159×159 pt 348×159 pt 348×357 pt 375×667 pt 148×148 pt 321×148 pt 321×324 pt 320×568 pt 141×141 pt 292×141 pt 292×311 pt -
An App can create multiple widgets with different functions or presentation styles, and each Widget can optionally be provided in one of the three sizes mentioned above
-
Like previous versions of Extension, widgets are standalone applications with their own life cycle (called Timeline) and are theoretically “independent” of the main project
-
Widgets can only refresh data through a preset Timeline, not in real time
The following sections, in conjunction with the Demo, talk about some of the essentials of Widget development
1. Create a non-configurable Widget
Skip the main project creation step here and start creating the Widget project directly
Create a new Target in theApplication Extension
Found in theWidget Extension
After clicking next, fill the ProductName of the Widget with the Include Configuration Intent. Whether the Widget should use a StaticConfiguration or a user configurable IntentConfiguration, leave the box blank and start with the normal unconfigurable mode for the Widget
Normally, when you’re creating something for the first time, you’ll get this popover, so you can hit Activate
Xcode then creates a time-display Widget by default, running as shown in the figure below
You can also manually select widgets of other sizes and add them to your desktop
2. Basic code
Here’s a look at the base code that Xcode creates, slightly different from the Beta version
Provider
Provide refresh data, control data refresh related methods, there are three default methods
- Placeholder method, which provides placeholder data to the widget and asynchronously returns a TimelineEtry
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date()}Copy the code
It looks something like this
- The getSnapshot method provides the data required when the Widget first starts rendering or appears for the first time, which is generally considered initialization data
func getSnapshot(in context: Context.completion: @escaping (SimpleEntry) - > ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
Copy the code
- The getTimeline method, which calls back to receive an array of entries, can be thought of as a data source and refresh interval
func getTimeline(in context: Context.completion: @escaping (Timeline<Entry>) - > ()) {
var entries: [SimpleEntry] = []
// Add five entries with a refresh time of 1 hour for each entry
let currentDate = Date(a)for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
// Here is the key point, the Entry and the refresh mechanism you want to set. AtEnd indicates that you can refresh the Timeline after the entries are displayed
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
Copy the code
SimpleEntry
In the simplest TimelineEntry, only a date variable is declared to record the entry time. The official interpretation is that WidgetKit renders the widget at the time
struct SimpleEntry: TimelineEntry {
let date: Date
}
Copy the code
EntryView
The View part of the Widget that binds data to the View
struct WeatherWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
Text(entry.date, style: .time)
}
}
Copy the code
Widget
The widgets we created are not configurable, so a StaticConfiguration is automatically generated to provide configuration for statically typed widgets
@main
struct WeatherWidget: Widget {
let kind: String = "WeatherWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
WeatherWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")}}Copy the code
Preview
The preview on canvas, which often goes wrong, feels negligible ~
struct WeatherWidget_Previews: PreviewProvider {
static var previews: some View {
WeatherWidgetEntryView(entry: SimpleEntry(date: Date()))
.previewContext(WidgetPreviewContext(family: .systemSmall))
}
}
Copy the code
3. Fit different sizes
- Change EntryView to the following way, using @Environment provided by SwiftUI, you can display different layouts according to different sizes
struct MyWidgetEntryView : View {
@Environment(\.widgetFamily)
var family: WidgetFamily
var entry: Provider.Entry
@ViewBuilder
var body: some View {
switch family {
case .systemSmall:Text(entry.date, style: .time)
case .systemMedium: Text(entry.date, style: .time)
case .systemLarge: Text(entry.date, style: .time)
default:Text(entry.date, style: .time)
}
}
}
Copy the code
- If you want to fit only a certain size, you can add it after Configuration
.supportedFamilies
, if not, it means that three sizes are used by default
4. Add configurable modes to widgets
Next, how to add a configurable mode to a Widget is described in two ways: Add a new project or add it on a static configuration basis
(1) Select Include Configuration Intent when creating a new Widget
There will be an additional XX.intentDefinition file in the new project
IntentConfiguration replaces StaticConfiguration in the code
@main
struct WeatherWidget: Widget {
let kind: String = "WeatherWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
WeatherWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")}}Copy the code
The getSnapshot and getTimeline methods in the Provider have a configuration parameter
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(a)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)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
Copy the code
Then add different Parameters in the Parameters, you can also add enumeration, custom type, etc., specific still need to explore by yourself
Then fetch the title for display
After the configuration is complete, run the Widget. Hold down the Widget to display a configuration Edit Widget. Tap to enter the Widget editing interface
The configuration page is displayed
complete
(2) Change the static configuration to dynamic configuration
This is a bit trickier than creating a widget and checking the Intent directly. First, we create an Intent Definition file within the widget project
The file name can be created at will. Remember Targets requires Extension
This is what happens when you create the Intents inside
Press + to select New Intent, as shown in the following figure
Give the Intent a name. Here I’ll use Configuration, which I’ll use later. Under the Custom Intent, modify the Category to View, and select Widgets, and set it to Work without any preset preset information or Shortcuts. Then add a content to Parameter
To change the Intent, add import Intents to the Widget’s Swift file, and replace it with a “ConfigurationIntent” Configuration. When Xcode generates the Configuration file, The Intent is appended at the end
To modify the Provider
- The protocol to follow is IntentTimelineProvider
- GetSnapshot and getTimeline also need to be modified accordingly. It is recommended to annotate the original method temporarily, type a few words, be prompted by Xcode and press Enter, confirm the modification before deleting the original code
I guess 99% of you will get an error at this step, so please go toFAQ Q&A
Modify SimpleEntry
Add a data variable of type ConfigurationIntent
Modify the Widget
The StaticConfiguration was changed to IntentConfiguration
If you need Preview, modify it as prompted by Xcode
FAQ Q&A:
Q1: An error is reported when you select Include Configuration Intent when creating a Widget
The class name of the Intent should also be prefixed, for example, ABConfigurationIntent
Q2: An error occurs when a new Intent is manually created
Common errors are: Type ‘Provider’ does not confirm to protocol ‘IntentTimelineProvider’ or Cannot find ‘ConfigurationIntent’ in Scope, ConfigurationIntent represents the custom configuration name of the IntentDefinition file
We’ll start with the ConfigurationIntent file, which is not present in our main project but generated each time we compile the Widget by checking the generated file path
Since the compile time error, means that the ConfigurationIntent file is not associated with our project, with no import this project is a bit like, so just can have always find a ‘XXXIntent’ in the Scope of errors
In the BuildSettings of the Extension Target, search for the intent. There is an Intent Definition Complier-code Generation property bar. The Intent Class Generation Language configuration is Automatic
If not, please confirm if the DefinitionIntent file has been added to the project
You can change it to Swift and then compile it. You can see that the cache file just generated OC file… No wonder compiling failed. Then we have the problem.
Automatic does not automatically generate the Intent configuration file based on the language used by the project.
OC project, create is OC file
Mix the project, create an OC file
The pure Swift project creates OC files
Check this option and change it to Swift to solve this problem
Q3: Q2 error, modify, use the simulator to run after clickEdit Widget
Have no reaction
This seems to be a bug, you need to delete the application in the emulator first, and then run again
References:
- Widget to introduce the official document: developer.apple.com/documentati…
- Widget official Demo:developer.apple.com/documentati…
- Widget additional configurable mode: developer.apple.com/documentati…