版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.11.19 星期四 |
前言
App Clips
是2020年WWDC新推出的功能,它的功能非常强大,因为它使没有您的应用程序的用户仍可以使用其功能。 从订购咖啡到停车,App Clips
有很多很好的用途。 下面我们就一起学习和看一下。
1. App Clips详细解析(一) —— 基本概览(一)
2. App Clips详细解析(二) —— 一个简单示例(一)
源码
1. Swift
首先看下工程组织结构
下面就是源码啦
1. Package.swift
import PackageDescription
let package = Package(
name: "LemonadeStandLocations",
products: [
.library(
name: "LemonadeStandLocations",
targets: ["LemonadeStandLocations"])
],
dependencies: [
],
targets: [
.target(
name: "LemonadeStandLocations",
dependencies: [])
]
)
2. LemonadeStand.swift
import MapKit
import SwiftUI
import LemonadeStandLocations
struct LemonadeStand {
let id = UUID()
let title: String
let coordinate: CLLocationCoordinate2D
var isFavorite: Bool
let menu: [Lemonade]
}
extension LemonadeStand: Identifiable { }
let standData = [
LemonadeStand(
title: "LA Galaxy",
coordinate: .dignityHealthSportsPark,
isFavorite: true,
menu: fullMenu),
LemonadeStand(
title: "Chicago Fire",
coordinate: .soldierField,
isFavorite: false,
menu: fullMenu),
LemonadeStand(
title: "Seattle Sounders",
coordinate: .centuryLinkField,
isFavorite: false,
menu: fullMenu),
LemonadeStand(
title: "New York City FC",
coordinate: .yankeeStadium,
isFavorite: false,
menu: expressMenu),
LemonadeStand(
title: "Los Angeles FC",
coordinate: .bancOfCalifornia,
isFavorite: false,
menu: expressMenu)
]
3. Lemonade.swift
import Foundation
struct Lemonade {
let id = UUID()
let title: String
let imageName: String
let calories: Int
}
extension Lemonade: Identifiable { }
let fullMenu = [
Lemonade(
title: "Lemon",
imageName: "lemon",
calories: 120),
Lemonade(
title: "Lime",
imageName: "lime",
calories: 120),
Lemonade(
title: "Watermelon",
imageName: "watermelon",
calories: 110),
Lemonade(
title: "Frozen Lemon",
imageName: "lemon",
calories: 140),
Lemonade(
title: "Frozen Lime",
imageName: "lime",
calories: 140),
Lemonade(
title: "Frozen Watermelon",
imageName: "watermelon",
calories: 110)
]
let expressMenu = [
Lemonade(
title: "Lemon",
imageName: "lemon",
calories: 120),
Lemonade(
title: "Lime",
imageName: "lime",
calories: 120),
Lemonade(
title: "Watermelon",
imageName: "watermelon",
calories: 110)
]
4. ContentView.swift
import SwiftUI
struct ContentView: View {
@State private var hideFavorites = false
@State var stands = standData
var body: some View {
StandTabView()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
5. StandTabView.swift
import SwiftUI
struct StandTabView: View {
@State var stands = standData
var body: some View {
TabView {
NavigationView {
StandList(
stands: $stands,
tabTitle: "Stands",
hideFav: false)
MenuList(stand: stands[0])
}
.tabItem {
Label("Stands", systemImage: "house")
}
NavigationView {
StandList(
stands: $stands,
tabTitle: "Favorite Stands",
hideFav: true)
MenuList(stand: stands[0])
}
.tabItem {
Label("Favorites", systemImage: "heart.fill")
}
}
}
}
struct StandTabView_Previews: PreviewProvider {
static var previews: some View {
StandTabView()
}
}
6. MenuList.swift
import SwiftUI
struct MenuList: View {
let stand: LemonadeStand
var body: some View {
List(stand.menu, id: \.id) { item in
NavigationLink(
destination: DetailView(lemonade: item)) {
Text(item.title)
}
}.navigationTitle(stand.title)
}
}
struct MenuList_Previews: PreviewProvider {
static var previews: some View {
MenuList(stand: standData[0])
}
}
7. DetailView.swift
import SwiftUI
import PassKit
struct DetailView: View {
@State private var orderPlaced = false
@State private var showWarningAlert = false
#if APPCLIP
@EnvironmentObject private var model: SwiftyLemonadeClipModel
#endif
let lemonade: Lemonade
private func placeOrder() {
#if APPCLIP
guard model.paymentAllowed else {
showWarningAlert = true
return
}
#endif
orderPlaced = true
}
var body: some View {
VStack {
Image(lemonade.imageName)
.resizable()
.frame(maxWidth: 300, maxHeight: 600)
.aspectRatio(contentMode: .fit)
Text(lemonade.title)
.font(.headline)
Divider()
Text("\(lemonade.calories) Calories")
.font(.subheadline)
.padding(15)
// swiftlint:disable:next multiple_closures_with_trailing_closure
Button(action: { placeOrder() }) {
Text("Place Order")
.foregroundColor(.white)
}
.frame(minWidth: 100, maxWidth: 400)
.frame(height: 45)
.background(Color.black)
}
.padding()
.navigationBarTitle(Text(lemonade.title), displayMode: .inline)
.sheet(isPresented: $orderPlaced, onDismiss: nil) {
OrderPlacedView(lemonade: lemonade)
}
.alert(isPresented: $showWarningAlert) {
Alert(
title: Text("Payment Disabled"),
message: Text("The QR was scanned at an invalid location."),
dismissButton: .default(Text("OK"))
)
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(lemonade: standData[0].menu[0])
}
}
8. OrderPlacedView.swift
import SwiftUI
struct OrderPlacedView: View {
let lemonade: Lemonade
var body: some View {
VStack {
Image(lemonade.imageName)
.resizable()
.frame(maxWidth: 300, maxHeight: 600)
.aspectRatio(contentMode: .fit)
Text("Thank you!\nYour Lemonade is on its way!")
.lineLimit(2)
.multilineTextAlignment(.center)
.padding(15)
}
}
}
struct OrderPlacedView_Previews: PreviewProvider {
static var previews: some View {
OrderPlacedView(lemonade: standData[0].menu[0])
}
}
9. StandList.swift
import SwiftUI
struct StandList: View {
@Binding var stands: [LemonadeStand]
let tabTitle: String
let hideFav: Bool
var showFav: [LemonadeStand] {
hideFav ? stands.filter { $0.isFavorite == true } : stands
}
private func setFavorite(_ favorite: Bool, for stand: LemonadeStand) {
if let index = stands.firstIndex(
where: { $0.id == stand.id }) {
stands[index].isFavorite = favorite
}
}
var body: some View {
List(showFav) { stand in
NavigationLink(
destination: MenuList(stand: stand)) {
Label(
"\(stand.title)",
systemImage: stand.isFavorite ? "heart.fill": "heart"
)
.contextMenu {
Button("Like") {
setFavorite(true, for: stand)
}
Button("Unlike") {
setFavorite(false, for: stand)
}
}
}
}
.navigationBarTitle(tabTitle)
}
}
10. AppDelegate.swift
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate { }
11. SceneDelegate.swift
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: ContentView())
self.window = window
window.makeKeyAndVisible()
}
}
}
12. SwiftyLemonadeClipApp.swift
import SwiftUI
import MapKit
import AppClip
import CoreLocation
@main
struct SwiftyLemonadeClipApp: App {
@StateObject private var model = SwiftyLemonadeClipModel()
private var locationManager = CLLocationManager()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(model)
.onContinueUserActivity(
NSUserActivityTypeBrowsingWeb,
perform: handleUserActivity)
.onAppear {
requestNotificationAuthorization()
}
}
}
func handleUserActivity(_ userActivity: NSUserActivity) {
guard
let incomingURL = userActivity.webpageURL,
let components = URLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
let queryItems = components.queryItems
else {
return
}
guard
let latValue = queryItems.first(where: { $0.name == "lat" })?.value,
let lonValue = queryItems.first(where: { $0.name == "lon" })?.value,
let lat = Double(latValue),
let lon = Double(lonValue)
else {
return
}
let location = CLLocationCoordinate2D(
latitude: CLLocationDegrees(lat),
longitude: CLLocationDegrees(lon))
if let stand = standData.first(where: { $0.coordinate == location }) {
model.selectedStand = stand
} else {
model.locationFound = false
}
guard let payload = userActivity.appClipActivationPayload else {
return
}
let region = CLCircularRegion(
center: location,
radius: 500,
identifier: "stand_location"
)
payload.confirmAcquired(in: region) { inRegion, error in
guard error == nil else {
print(String(describing: error?.localizedDescription))
return
}
DispatchQueue.main.async {
model.paymentAllowed = inRegion
}
}
}
func requestNotificationAuthorization() {
let notifCenter = UNUserNotificationCenter.current()
notifCenter.getNotificationSettings { setting in
if setting.authorizationStatus == .ephemeral {
return
}
notifCenter.requestAuthorization(options: .alert) { result, error in
print("Authorization Request result: \(result) - \(String(describing: error))")
}
}
}
}
13. ContentView.swift
import SwiftUI
struct ContentView: View {
@EnvironmentObject private var model: SwiftyLemonadeClipModel
var body: some View {
if let selectedStand = model.selectedStand {
NavigationView {
MenuList(stand: selectedStand)
}
}
if model.locationFound == false {
Text("Error finding stand.")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
14. SwiftyLemonadeClipModel.swift
import Foundation
class SwiftyLemonadeClipModel: ObservableObject {
@Published var selectedStand: LemonadeStand?
@Published var paymentAllowed = true
@Published var locationFound = true
}
后记
本篇主要讲述了
App Clips
的一个简单示例,感兴趣的给个赞或者关注~~~
网友评论