美文网首页
Background Modes详细解析(二) —— 几种Mod

Background Modes详细解析(二) —— 几种Mod

作者: 刀客传奇 | 来源:发表于2022-11-06 18:27 被阅读0次

    版本记录

    版本号 时间
    V1.0 2022.11.06 星期日

    前言

    Background Modes我们在程序中总会用到,包括语音、定位更新、后台任务以及远程通知等,这个模块我们就一起来学习下。感兴趣的可以看下面几篇文章。
    1. Background Modes详细解析(一) —— 几种Mode使用示例(一)

    源码

    1. Swift

    首先看下工程组织架构

    下面就是源码了

    1. AppMain.swift
    
    import SwiftUI
    
    @main
    struct AppMain: App {
      @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
      var body: some Scene {
        WindowGroup {
          ContentView()
        }
      }
    }
    
    2. AppDelegate.swift
    
    import UIKit
    import BackgroundTasks
    
    class AppDelegate: UIResponder, UIApplicationDelegate {
      static var dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        formatter.timeStyle = .long
        return formatter
      }()
    
      var window: UIWindow?
    
      func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
      ) -> Bool {
        BGTaskScheduler.shared.register(
          forTaskWithIdentifier: AppConstants.backgroundTaskIdentifier,
          using: nil) { task in
            self.refresh()
            task.setTaskCompleted(success: true)
            self.scheduleAppRefresh()
        }
    
        scheduleAppRefresh()
        return true
      }
    
      func scheduleAppRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: AppConstants.backgroundTaskIdentifier)
        request.earliestBeginDate = Date(timeIntervalSinceNow: 1 * 60)
        do {
          try BGTaskScheduler.shared.submit(request)
          print("background refresh scheduled")
        } catch {
          print("Couldn't schedule app refresh \(error.localizedDescription)")
        }
      }
    
      func refresh() {
        // to simulate a refresh, just update the last refresh date to current date/time
        let formattedDate = Self.dateFormatter.string(from: Date())
        UserDefaults.standard.set(formattedDate, forKey: UserDefaultsKeys.lastRefreshDateKey)
        print("refresh occurred")
      }
    }
    
    3. AppConstants.swift
    
    import Foundation
    
    enum AppConstants {
      static let backgroundTaskIdentifier = "com.mycompany.myapp.task.refresh"
    }
    
    enum UserDefaultsKeys {
      static let lastRefreshDateKey = "lastRefreshDate"
    }
    
    4. AudioModel.swift
    
    import SwiftUI
    import AVFoundation
    import Combine
    
    extension AudioView {
      class Model: ObservableObject {
        @Published var time: TimeInterval = 0
        @Published var item: AVPlayerItem?
        @Published var isPlaying = false
    
        var itemTitle: String {
          if let asset = item?.asset as? AVURLAsset {
            return asset.url.lastPathComponent
          } else {
            return "-"
          }
        }
    
        private var itemObserver: AnyCancellable?
        private var timeObserver: Any?
        private let player: AVQueuePlayer
    
        init() {
          do {
            try AVAudioSession
              .sharedInstance()
              .setCategory(.playback, mode: .default)
          } catch {
            print("Failed to set audio session category. Error: \(error)")
          }
    
          self.player = AVQueuePlayer(items: Self.songs)
          player.actionAtItemEnd = .advance
    
          itemObserver = player.publisher(for: \.currentItem).sink { [weak self] newItem in
            self?.item = newItem
          }
    
          timeObserver = player.addPeriodicTimeObserver(
            forInterval: CMTime(seconds: 0.5, preferredTimescale: 600),
            queue: nil) { [weak self] time in
              self?.time = time.seconds
          }
        }
    
        func playPauseAudio() {
          isPlaying.toggle()
          if isPlaying {
            player.play()
          } else {
            player.pause()
          }
        }
    
        static var songs: [AVPlayerItem] = {
          // find the mp3 song files in the bundle and return player item for each
          let songNames = ["FeelinGood", "IronBacon", "WhatYouWant"]
          return songNames.map {
            guard let url = Bundle.main.url(forResource: $0, withExtension: "mp3") else {
              return nil
            }
            return AVPlayerItem(url: url)
          }
          .compactMap { $0 }
        }()
      }
    }
    
    5. LocationModel.swift
    
    import Combine
    import CoreLocation
    import MapKit
    
    extension LocationView {
      class Model: NSObject, CLLocationManagerDelegate, ObservableObject {
        @Published var isLocationTrackingEnabled = false
        @Published var location: CLLocation?
        @Published var region = MKCoordinateRegion(
          center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275),
          span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
        @Published var pins: [PinLocation] = []
    
        let mgr: CLLocationManager
    
        override init() {
          mgr = CLLocationManager()
          mgr.desiredAccuracy = kCLLocationAccuracyBest
          mgr.requestAlwaysAuthorization()
          mgr.allowsBackgroundLocationUpdates = true
    
          super.init()
          mgr.delegate = self
        }
    
        func enable() {
          mgr.startUpdatingLocation()
        }
    
        func disable() {
          mgr.stopUpdatingLocation()
        }
    
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
          if let currentLocation = locations.first {
            print(currentLocation)
            location = currentLocation
            appendPin(location: currentLocation)
            updateRegion(location: currentLocation)
          }
        }
    
        func appendPin(location: CLLocation) {
          pins.append(PinLocation(coordinate: location.coordinate))
        }
    
        func updateRegion(location: CLLocation) {
          region = MKCoordinateRegion(
            center: location.coordinate,
            span: MKCoordinateSpan(latitudeDelta: 0.0015, longitudeDelta: 0.0015))
        }
    
        func startStopLocationTracking() {
          isLocationTrackingEnabled.toggle()
          if isLocationTrackingEnabled {
            enable()
          } else {
            disable()
          }
        }
      }
    
      struct PinLocation: Identifiable {
        let id = UUID()
        var coordinate: CLLocationCoordinate2D
      }
    }
    
    6. CompleteTaskModel.swift
    
    import Combine
    import SwiftUI
    
    extension CompleteTaskView {
      class Model: ObservableObject {
        @Published var isTaskExecuting = false
        @Published var resultsMessage = initialMessage
    
        static let initialMessage = "Fibonacci Computations"
        static let maxValue = NSDecimalNumber(mantissa: 1, exponent: 40, isNegative: false)
    
        var previous = NSDecimalNumber.one
        var current = NSDecimalNumber.one
        var position: UInt = 1
        var backgroundTask: UIBackgroundTaskIdentifier = .invalid
        var updateTimer: Timer?
    
        func beginPauseTask() {
          isTaskExecuting.toggle()
          if isTaskExecuting {
            resetCalculation()
            updateTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
              self?.calculateNextNumber()
            }
          } else {
            updateTimer?.invalidate()
            updateTimer = nil
            endBackgroundTaskIfActive()
            resultsMessage = Self.initialMessage
          }
        }
    
        func registerBackgroundTask() {
          backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
            print("iOS has signaled time has expired")
            self?.endBackgroundTaskIfActive()
          }
        }
    
        func endBackgroundTaskIfActive() {
          let isBackgroundTaskActive = backgroundTask != .invalid
          if isBackgroundTaskActive {
            print("Background task ended.")
            UIApplication.shared.endBackgroundTask(backgroundTask)
            backgroundTask = .invalid
          }
        }
    
        func resetCalculation() {
          previous = .one
          current = .one
          position = 1
        }
    
        func calculateNextNumber() {
          let result = current.adding(previous)
    
          if result.compare(Self.maxValue) == .orderedAscending {
            previous = current
            current = result
            position += 1
          } else {
            // This is just too much.... Start over.
            resetCalculation()
          }
    
          resultsMessage = "Position \(self.position) = \(self.current)"
    
          switch UIApplication.shared.applicationState {
          case .background:
            let timeRemaining = UIApplication.shared.backgroundTimeRemaining
            if timeRemaining < Double.greatestFiniteMagnitude {
              let secondsRemaining = String(format: "%.1f seconds remaining", timeRemaining)
              print("App is backgrounded - \(resultsMessage) - \(secondsRemaining)")
            }
          default:
            break
          }
        }
    
        func onChangeOfScenePhase(_ newPhase: ScenePhase) {
          switch newPhase {
          case .background:
            let isTimerRunning = updateTimer != nil
            let isTaskUnregistered = backgroundTask == .invalid
    
            if isTimerRunning && isTaskUnregistered {
              registerBackgroundTask()
            }
          case .active:
            endBackgroundTaskIfActive()
          default:
            break
          }
        }
      }
    }
    
    7. ContentView.swift
    
    import SwiftUI
    
    struct ContentView: View {
      var body: some View {
        TabView {
          AudioView()
            .tabItem {
              VStack {
                Image(systemName: "music.note")
                Text("Audio")
              }
            }
            .tag(0)
    
          LocationView()
            .tabItem {
              VStack {
                Image(systemName: "mappin")
                Text("Location")
              }
            }
            .tag(1)
    
          CompleteTaskView()
            .tabItem {
              VStack {
                Image(systemName: "platter.filled.bottom.and.arrow.down.iphone")
                Text("Completion")
              }
            }
            .tag(2)
    
          RefreshView()
            .tabItem {
              VStack {
                Image(systemName: "arrow.clockwise.circle")
                Text("Refresh")
              }
            }
            .tag(3)
        }
      }
    }
    
    struct ContentView_Previews: PreviewProvider {
      static var previews: some View {
        ContentView()
      }
    }
    
    8. AudioView.swift
    
    import SwiftUI
    import AVFoundation
    
    struct AudioView: View {
      @StateObject var model = Model()
    
      var body: some View {
        VStack(alignment: .center, spacing: 20) {
          Text("Audio Player")
            .font(.largeTitle)
            .fontWeight(.bold)
            .padding(EdgeInsets(top: 50, leading: 50, bottom: 0, trailing: 50))
    
          Spacer()
    
          Button(
            action: model.playPauseAudio) {
              VStack {
                Text(model.isPlaying ? "Pause" : "Play")
                  .padding()
                Image(systemName: model.isPlaying ? "pause" : "play")
              }
          }
          .font(.title)
          .padding()
    
          Text("Now Playing: \(model.itemTitle)")
            .font(.title2)
    
          Text(Self.formatter.string(from: model.time) ?? "")
            .font(.title2)
    
          Spacer()
          Spacer()
        }
      }
    
      static let formatter: DateComponentsFormatter = {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .positional
        formatter.allowedUnits = [.minute, .second]
        formatter.zeroFormattingBehavior = .pad
        return formatter
      }()
    }
    
    struct AudioView_Previews: PreviewProvider {
      static var previews: some View {
        AudioView()
      }
    }
    
    9. LocationView.swift
    
    import SwiftUI
    import CoreLocation
    import MapKit
    
    struct LocationView: View {
      @StateObject var model = Model()
    
      var body: some View {
        VStack(alignment: .center, spacing: 20) {
          Text("Location Tracker")
            .font(.largeTitle)
            .fontWeight(.bold)
            .padding(EdgeInsets(top: 50, leading: 50, bottom: 0, trailing: 50))
    
          Button(
            action: { model.startStopLocationTracking() },
            label: {
              VStack {
                Image(systemName: model.isLocationTrackingEnabled ? "stop" : "location")
                Text(model.isLocationTrackingEnabled ? "Stop" : "Start")
              }
            })
          .font(.title)
          .padding(EdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10))
    
          Map(coordinateRegion: $model.region, annotationItems: model.pins) { pin in
            MapPin(coordinate: pin.coordinate, tint: .red)
          }
          .padding(EdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20))
        }
      }
    }
    
    struct LocationView_Previews: PreviewProvider {
      static var previews: some View {
        LocationView()
      }
    }
    
    10. CompleteTaskView.swift
    
    import SwiftUI
    
    struct CompleteTaskView: View {
      @Environment(\.scenePhase) var scenePhase
      @StateObject var model = Model()
    
      var body: some View {
        VStack(alignment: .center, spacing: 20) {
          Text("Task Completion")
            .font(.largeTitle)
            .fontWeight(.bold)
            .padding(EdgeInsets(top: 50, leading: 50, bottom: 0, trailing: 50))
    
          Spacer()
    
          Button(
            action: { model.beginPauseTask() },
            label: {
              VStack {
                Text(model.isTaskExecuting ? "Stop Task" : "Begin Task")
                  .padding()
                Image(systemName: model.isTaskExecuting ? "stop" : "play")
              }
            })
          .font(.title)
          .padding()
    
          Text(model.resultsMessage)
            .font(.title2)
    
          Spacer()
          Spacer()
        }
        .onChange(of: scenePhase) { newPhase in
          model.onChangeOfScenePhase(newPhase)
        }
      }
    }
    
    struct FinishTaskView_Previews: PreviewProvider {
      static var previews: some View {
        CompleteTaskView()
      }
    }
    
    11. RefreshView.swift
    
    import SwiftUI
    import BackgroundTasks
    
    struct RefreshView: View {
      @AppStorage(UserDefaultsKeys.lastRefreshDateKey) var lastRefresh = "Never"
      @Environment(\.scenePhase) var scenePhase
    
      var body: some View {
        VStack(alignment: .center, spacing: 20) {
          Text("Background Refresh")
            .font(.largeTitle)
            .fontWeight(.bold)
            .padding(EdgeInsets(top: 50, leading: 50, bottom: 0, trailing: 50))
    
          Spacer()
    
          Text("Refresh last performed:")
            .multilineTextAlignment(.center)
            .font(.title)
            .onChange(of: scenePhase) { scenePhase in
              if scenePhase == .background {
                print("moved to background")
              }
            }
            .padding()
    
          Text(lastRefresh)
            .font(.title2)
    
          Spacer()
          Spacer()
        }
      }
    }
    
    struct FetchView_Previews: PreviewProvider {
      static var previews: some View {
        RefreshView()
      }
    }
    

    后记

    本篇主要讲述了Background Modes几种Mode使用示例,感兴趣的给个赞或者关注~~~

    相关文章

      网友评论

          本文标题:Background Modes详细解析(二) —— 几种Mod

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