Edmond is an iOS developer and currently works at Snowball. GitHub, blog, Twitter, Twitter

WWDC20 Session 10033 – wwdc. IO /share/wwdc2…

Contents of this article

This article is part of the WWDC20 Widgets series. Widgets are developed using SwiftUI. If you’re not familiar with SwiftUI, check out Introduction to SwiftUI. If you want to learn more about Widgets first, check out the links at the end of this article.

SwiftUI in widgets

We can provide timeline based content for Widgets that will be displayed on the home screen of iOS 14 or macOS Big Sur when appropriate. Thanks to SwiftUI’s screen adaptation, Widgets work well on different systems. So even though our application was shipped with an older version of the system (SwiftUI was not available), Widgets will be a great opportunity for us to learn and use SwiftUI.

Widgets look like this:

Without further ado, let’s go straight to Demo how to develop a Widget using SwiftUI

Caffiine Drink Demo

We’re going to create an app that records and tracks the caffeinated beverages we drink and estimates the amount of caffeine in our bodies. The applications are as follows:

Widgets can be an important addition to the app to see our current caffeine content at a glance.

This is the Widget we want to show:

First, you can see that it has the same visual and color scheme as our application.

At the top, we show our current caffeine levels, and at the bottom, we record the time of our last cup of coffee. Note ⚠️ that the background shape of the caffeine content is in concentric circles with the widget shape, which is implemented using our new API.

Finally, you also want the duration at the bottom of the widget to be updated in real time to always be correct.

Hello Widget!

Let’s start with a new project, either iOS, macOS or Multiplatform:

Then we need to create a new Widge target. Here we choose the widget based on the iOS project:

Once generated, let’s add the Hello Widget! And add WidgetPreviewContext to the PreviewProvider:

struct CafeineDrinkWidget_Previews: PreviewProvider {

    static var previews: some View {
        Group {
            CafeineDrinkWidgetEntryView(entry: .preivewEntry, data: .previewData)
        }
        .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}
Copy the code

The widget styles we currently offer fall into three categories:

public enum WidgetFamily : Int.RawRepresentable.CustomDebugStringConvertible.CustomStringConvertible {
    /// A small widget.
    case systemSmall
    /// A medium-sized widget.
    case systemMedium
    /// A large widget.
    case systemLarge
}
Copy the code

Let’s take a look at the simplest version of widget 😄.

Data Configuration

Next, let’s configure CaffeineWidgetData:

struct CaffeineWidgetData {

    public let drinkName: String
    public let drinkData: Date
    public let caffeineAmount: Measurement<UnitMass>}extension CaffeineWidgetData {
    public static let previewData = CaffeineWidgetData(
        drinkName: "Cappuccino",
        drinkData: Date().advanced(by: -60 * 29 + 5),
        caffeineAmount: Measurement<UnitMass>(value: 56.23, unit: .milligrams)
    )
}
Copy the code

We then add simple View LazyVStack and Text to hold our new data. The effect is as follows:

Now that we have the basic structure, let’s enrich it. First add the background color, here we use ZStack to wrap:

var body: some View {
  	ZStack {
      Color("cappuccino")

      LazyVStack(alignment: .leading) { .}}}Copy the code

Let’s show drinkName and drinkDate as well:

Text("\(data.drinkName)☕ ️")
   .font(.body)
   .bold()
   .foregroundColor(Color("milk"))

/// NOTE: *New* Date provider API
Text("\(data.drinkDate, style: .relative) ago")
   .font(.caption)
   .foregroundColor(Color("milk"))
Copy the code

Note that we are using SwiftUI’s new DateStyle API

DateStyle

public struct DateStyle {

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

    /// A style displaying a date.
    /// Example output:
    /// June 3, 2019
    public static let date: Text.DateStyle

    /// A style displaying a date as relative to now.
    /// Example output:
    /// 2 hours, 23 minutes
    /// 1 year, 1 month
    public static let relative: Text.DateStyle

    /// A style displaying a date as offset from now.
    /// Example output:
    /// +2 hours
    /// -3 months
    public static let offset: Text.DateStyle

    /// A style displaying a date as timer counting from now.
    /// Example output:
    / / / 2:32
    / / / 36:59:01
    public static let timer: Text.DateStyle
}
Copy the code

Take another look at the effect:

Restructuring

Next, we’ll tweak the code structure a bit to separate the two internal Lazystacks into separate Views. Here we can use Xcode’s Extra Subview to do the work for us. We just need to move 🖱️ to LazyStack, hold down Command and click 🖱️ to pop up the following window:

After adjustment, our structure is much cleaner:

var body: some View {
    ZStack {
        Color("cappuccino")

        LazyVStack(alignment: .leading) {
            CaffineAmountView(data: data)
            Spacer(a)DrinkView(data: data)
        }
        .padding(.all)
    }
}
Copy the code

Remember to add caffeine wide data to our new CaffineAmountView and DrinkView.

CaffineAmountView

Finally, we’ll modify CaffineAmountView with our other new API.

var body: some View {
    LazyHStack {
        VStack(alignment: .leading) {
            Text("Caffeine")
                .font(.body)
                .foregroundColor(Color("espresso"))
                .bold()

            Text(Formatter.measurement.string(from: data.caffeineAmount))
                .font(.title)
                .bold()
                .foregroundColor(Color("espresso"))
                .minimumScaleFactor(0.8)}Spacer(minLength: 0) / / # 1
    }
    .padding(.all, 8.0)
    .background(ContainerRelativeShape().fill(Color("latte"))) / / # 2
}
Copy the code

Notice that Spacer’s minLength is set to 0 to fill the entire AmountView. Then, in the process of compiling the Demo, I found that the new LazyVStack had a bug in the layout process and could not normally fill the content, so I changed to VStack here.

ContainerRelativeShape

A shape that is replaced by an inset version of the current container shape. If no container shape was defined, is replaced by a rectangle.

Since devices of different sizes may use different radii for their widgets, this can cause things to get a little messy. ContainerRelativeShape is a new shape type that takes the path closest to the superview container shape and uses an appropriate chamfering radius based on the shape position.

By adding ContainerRelativeShape, our AmountView takes the same concentric angular radius as the system defined the container shape for the widget. If you change the padding of the main VStack, the AmountView chamfering changes so that the border around it maintains a constant thickness around the corner curve.

For example, if we change the padding of the container to 10, the AmountView chamfer will look like this:

PreviewProvider

Let’s take a look at Widge in action in different scenarios.

DarkMode

The first is the effect of the.envrioment modifier in DarkMode, which is completely guaranteed by our ColorSet configuration.

Then we add the colorScheme environment configuration to the View.

CafeineDrinkWidgetEntryView(entry: .preivewEntry, data: .previewData)
    .previewContext(WidgetPreviewContext(family: .systemSmall))
    .environment(\.colorScheme, .dark)
Copy the code

SizeCategory

The font size of the widget can be controlled with the sizeCategory of the.envrioment modifier.

CafeineDrinkWidgetEntryView(entry: .preivewEntry, data: .previewData)
    .previewContext(WidgetPreviewContext(family: .systemSmall))
    .environment(\.sizeCategory, .extraExtraLarge)
Copy the code

Let’s take a look at the effects of both cases first:

Placeholder

This year’s SwiftUI also has a great feature that allows us to set skeleton maps with the isPlaceholder modifier.

.Isplaceholder also supports control of individual views, etc. However, the current version of Xcode 12 Beta1 is not available at 😅. Just take a look.

WidgetFamily

In the final preview, let’s take a look at how WidgetFamily controls Widge’s presentation.

struct CafeineDrinkWidgetEntryView : View {
    .    
    @Environment(\.widgetFamily) var widgetFamily

    var body: some View {
        ZStack {
            .
            LazyHStack {
                LazyVStack(alignment: .leading) {
                    CaffineAmountView(data: data)
                    Spacer(a)DrinkView(data: data)
                }
                .padding(.all, 10)

                if widgetFamily = = .systemMedium, let phoneName = data.phoneName {
                    Image(phoneName).resizable()
                }
            }
        }
    }
}
Copy the code

Here we add a picture of the last coffee we drank with widgetFamily as.systemMedium.

The effect is as follows:

New APIs

In the final section, let’s review the new API we used

DateStyle

ContainerRelativeShape

At the end of the article’s welfare

Because the Session in this article does not provide the corresponding SampleCode. I wrote a complete Demo in the process of writing the article. If you need it, you can help yourself: Github. ⚠️ Note that running the widget through Xcode 12 Beta on some macOS may not be possible on an emulator, but in the Sidebar preview of Xcode, the full effect is recommended on a real device.

conclusion

  • Apple through the independent Taget willWidgetIsolation from the main project supports Widget development well because there is no historical baggage.
  • WidgeThe Widget is clearly positioned to compensate for the host application’s inability to present the data the user is interested in in a timely manner.
  • With the increasing SwiftUI functionality and performance enhancements, widgets can also better integrate with the existing Apple ecosystem.
  • Widgets also take Apple’s integration of the iOS and macOS ecosystems a step further.

Knowledge points and problems sorting

  1. WidgetIs it supported on all Apple systems?
  2. What new apis are used in the examples in this article, and try to describe their functionality?
  3. How many environment descriptors do the examples in this article show?

Recommended reading

✨ Apple Widget: The next top Traffic entry point?

✨ Details the major changes and core strengths of WWDC 20 SwiftUI

WWDC20 10041 – What’s new in SwiftUI

WWDC20 10048 – Create complex features in SwiftUI

WWDC20 10039 – How to write a standalone App with SwiftUI?

Limited welfare

This article is from WWDC20 Inside Reference. I recommend this column here.

The “WWDC Insider reference” series was initiated by the veteran Driver Weekly, Knowledge Collection and SwiftGG technology organizations. We’ve been doing it for a couple of years, and it’s been good. It is mainly for the annual WWDC content, do a selection, and call on a group of front-line Internet iOS developers, combined with their actual development experience, Apple documents and video content to do a second creation.

There are 213 sessions this year. WWDC20 Insider selected 135 of these sessions, and the column has now produced 97 articles. It is on sale now, only 29.9 yuan, very discount.

See article also not enough friends, to subscribe to the WWDC20 inside ~ https://xiaozhuanlan.com/wwdc20 to continue reading

Pay attention to our

We launched LSJCoiding, a weekly newsletter for veteran drivers, which is called a welcome update to each issue.