apple / sample-food-truck
- пятница, 10 июня 2022 г. в 00:31:08
SwiftUI sample code from WWDC22
Create a single codebase and app target for Mac, iPad, and iPhone.
Using the Food Truck app, someone who operates a food truck can keep track of orders, discover the most-popular menu items, and check the weather at their destination. The sample implements the new NavigationSplitView
to manage the app's views, Layout
to show the main interface and pending orders, Charts
to show trends, and WeatherService
to get weather data.
You can access the source code for this sample on GitHub.
The Food Truck sample project contains two types of app targets:
Simple app target you can build using personal team signing. This app runs in Simulator, and only requires a standard Apple ID to install on a device. It includes in-app purchase, and a widget extension that enable users to add a widget to their iOS Home Screen or the macOS Notification Center.
Full-featured Food Truck All app target. The full app runs in Simulator, and on devices with an Apple Developer membership. It also allows you to create and sign in with passkeys.
To configure the Food Truck app without an Apple Developer account, follow these steps:
To configure the Food Truck All app to run on your devices, follow these steps:
webcredentials
service. For more information about the webcredentials
service, see Associated Domains Entitlement.apple-app-site-association
(AASA) file is present on your domain, in the .well-known
directory, and it contains an entry for this app’s App ID for the webcredentials
service. For more information about the apple-app-site-association
file, see Supporting Associated Domains.AccountManager.swift
file, replace all occurrences of example.com
with the name of your domain.Food Truck is a multiplatform app, and there are no separate targets to run on macOS or iOS. Instead, there is only one app target that builds for macOS, iPadOS, and iOS.
The sample's navigation interface consists of a NavigationSplitView
with a Sidebar
view, and a NavigationStack
:
NavigationSplitView {
Sidebar(selection: $selection)
} detail: {
NavigationStack(path: $path) {
DetailColumn(selection: $selection, model: model)
}
}
At app launch, the sample presents the TruckView
as the default view. The Panel
enum encodes the views the user can select in the sidebar, and hence appear in the detail view. The value corresponding to TruckView
is .truck
, and the app sets this to be the default selection.
@State private var selection: Panel? = Panel.truck
In the Truck view, the New Orders panel shows the five most-recent orders, and each order shows a DonutStackView
, which is a diagonal stack of donut thumbnails. The Layout
protocol allows the app to define a DiagonalDonutStackLayout
that arranges the donut thumbnails into the diagonal layout. The layout's placeSubviews(in:proposal:subviews:cache:)
implementation calculates the donuts' positions.
for index in subviews.indices {
switch (index, subviews.count) {
case (_, 1):
subviews[index].place(
at: center,
anchor: .center,
proposal: ProposedViewSize(size)
)
case (_, 2):
let direction = index == 0 ? -1.0 : 1.0
let offsetX = minBound * direction * 0.15
let offsetY = minBound * direction * 0.20
subviews[index].place(
at: CGPoint(x: center.x + offsetX, y: center.y + offsetY),
anchor: .center,
proposal: ProposedViewSize(CGSize(width: size.width * 0.7, height: size.height * 0.7))
)
case (1, 3):
subviews[index].place(
at: center,
anchor: .center,
proposal: ProposedViewSize(CGSize(width: size.width * 0.65, height: size.height * 0.65))
)
case (_, 3):
let direction = index == 0 ? -1.0 : 1.0
let offsetX = minBound * direction * 0.15
let offsetY = minBound * direction * 0.23
subviews[index].place(
at: CGPoint(x: center.x + offsetX, y: center.y + offsetY),
anchor: .center,
proposal: ProposedViewSize(CGSize(width: size.width * 0.7, height: size.height * 0.65))
)
The sample contains several charts. The most popular items are shown on the TopFiveDonutsView
. This chart is implemented in TopDonutSalesChart
, which uses a BarMark
to construct a bar chart.
Chart {
ForEach(sortedSales) { sale in
BarMark(
x: .value("Donut", sale.donut.name),
y: .value("Sales", sale.sales)
)
.cornerRadius(6, style: .continuous)
.foregroundStyle(.linearGradient(colors: [Color("BarBottomColor"), .accentColor], startPoint: .bottom, endPoint: .top))
.annotation(position: .top, alignment: .top) {
Text(sale.sales.formatted())
.padding(.vertical, 4)
.padding(.horizontal, 8)
.background(.quaternary.opacity(0.5), in: Capsule())
.background(in: Capsule())
.font(.caption)
}
}
}
The x axis of the chart shows labels with the names and thumbnails of the items that correspond to each data point.
.chartXAxis {
AxisMarks { value in
AxisValueLabel {
let donut = donutFromAxisValue(for: value)
VStack {
DonutView(donut: donut)
.frame(height: 35)
Text(donut.name)
.lineLimit(2, reservesSpace: true)
.multilineTextAlignment(.center)
}
.frame(idealWidth: 80)
.padding(.horizontal, 4)
}
}
}
The app shows a forecasted temperature graph in the Forecast panel in the Truck view. The app obtains this data from the WeatherKit
framework.
.task(id: city.id) {
for parkingSpot in city.parkingSpots {
do {
let weather = try await WeatherService.shared.weather(for: parkingSpot.location)
condition = weather.currentWeather.condition
willRainSoon = weather.minuteForecast?.contains(where: { $0.precipitationChance >= 0.3 })
cloudCover = weather.currentWeather.cloudCover
temperature = weather.currentWeather.temperature
symbolName = weather.currentWeather.symbolName
let attribution = try await WeatherService.shared.attribution
attributionLink = attribution.legalPageURL
attributionLogo = colorScheme == .light ? attribution.combinedMarkLightURL : attribution.combinedMarkDarkURL
if willRainSoon == false {
spot = parkingSpot
break
}
} catch {
print("Could not gather weather information...", error.localizedDescription)
condition = .clear
willRainSoon = false
cloudCover = 0.15
}
}
}
The data from the WeatherService
instance in WeatherKit requires additional configuration for the Food Truck All target. If you don't configure WeatherKit, the sample will detect an error and use static data in the project instead.
.Widgets
the same as the bundle ID for the Food Truck All target.