美文网首页
App Clips详细解析(三) —— 一个简单示例(二)

App Clips详细解析(三) —— 一个简单示例(二)

作者: 刀客传奇 | 来源:发表于2020-11-19 20:43 被阅读0次

    版本记录

    版本号 时间
    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的一个简单示例,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

          本文标题:App Clips详细解析(三) —— 一个简单示例(二)

          本文链接:https://www.haomeiwen.com/subject/uqnfiktx.html