美文网首页
基于Universal Type Identifiers的App

基于Universal Type Identifiers的App

作者: 刀客传奇 | 来源:发表于2020-05-28 17:43 被阅读0次

版本记录

版本号 时间
V1.0 2020.05.28 星期四

前言

这一篇主要看下使用Universal Type Identifiers进行App中的数据的导入和导出。感兴趣的可以看下面几篇文章。
1. 基于Universal Type Identifiers的App数据导入和导出(一)

源码

1. Swift

首先看下工程组织结构

下面就是源码了

1. Binding.swift
import SwiftUI

public extension Binding where Value: CaseIterable & Equatable {
  var caseIndex: Binding<Value.AllCases.Index> {
    Binding<Value.AllCases.Index>(
      // swiftlint:disable:next force_unwrapping
      get: { Value.allCases.firstIndex(of: self.wrappedValue)! },
      set: { self.wrappedValue = Value.allCases[$0] }
    )
  }
}
2. FileManager.swift
import Foundation

public extension FileManager {
  static var documentsDirectoryURL: URL {
    return `default`.urls(for: .documentDirectory, in: .userDomainMask)[0]
  }
}
3. ForEach.swift
import SwiftUI

public extension ForEach where Content: View {
  init<Base: RandomAccessCollection>(
    _ base: Base,
    @ViewBuilder content: @escaping (Base.Index) -> Content
  )
  where
    Data == IndexedCollection<Base>,
    Base.Element: Identifiable,
    ID == Base.Element.ID {
    self.init(IndexedCollection(base), id: \.element.id) { index, _ in
      content(index)
    }
  }
}
4. IndexedCollection.swift
public struct IndexedCollection<Base: RandomAccessCollection>: RandomAccessCollection {
  let base: Base

  public init(_ base: Base) {
    self.base = base
  }
}

// MARK: RandomAccessCollection
public extension IndexedCollection {
  typealias Index = Base.Index
  typealias Element = (index: Index, element: Base.Element)

  var startIndex: Index { base.startIndex }

  var endIndex: Index { base.endIndex }

  func index(after i: Index) -> Index {
    base.index(after: i)
  }

  func index(before i: Index) -> Index {
    base.index(before: i)
  }

  func index(_ i: Index, offsetBy distance: Int) -> Index {
    base.index(i, offsetBy: distance)
  }

  subscript(position: Index) -> Element {
    (index: position, element: base[position])
  }
}
5. AppDelegate.swift
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
  ) -> Bool {
    TaskStore.shared.importPrioritizedTasks(from: url)
    return true
  }

  func applicationDidFinishLaunching(_ application: UIApplication) {
  }
}
6. SceneDelegate.swift
import UIKit
import SwiftUI

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  var window: UIWindow?

  func scene(
    _ scene: UIScene,
    willConnectTo _: UISceneSession,
    options _: UIScene.ConnectionOptions
  ) {
    guard let windowScene = scene as? UIWindowScene
    else { return }

    let window = UIWindow(windowScene: windowScene)
    window.rootViewController = UIHostingController(
      rootView: ContentView(taskStore: TaskStore.shared)
    )
    self.window = window
    window.makeKeyAndVisible()
  }

  // 1
  func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    // 2
    guard let urlContext = URLContexts.first else {
      return
    }

    // 3
    TaskStore.shared.importPrioritizedTasks(from: urlContext.url)
  }
}
7. Task.swift
import Foundation

struct Task: Identifiable, Codable, Equatable {
  let id = UUID()

  var name: String
  var completed = false

  enum CodingKeys: String, CodingKey {
    case id = "identifier"
    case name
    case completed = "isComplete"
  }
}
8. Task.Priority.swift
extension Task {
  enum Priority: String, CaseIterable, Codable {
    // swiftlint:disable:next identifier_name
    case no, low, medium, high
  }
}
9. TaskStore.swift
import Combine
import Foundation

class TaskStore: ObservableObject {
  static let shared = TaskStore()
  static let fileExtension = "rwtl"

  private var cancellables: Set<AnyCancellable> = []

  let tasksDocURL = URL(
    fileURLWithPath: "PrioritizedTasks",
    relativeTo: FileManager.documentsDirectoryURL).appendingPathExtension(fileExtension)

  @Published var prioritizedTasks: [PrioritizedTasks] = [
    PrioritizedTasks(priority: .high, tasks: []),
    PrioritizedTasks(priority: .medium, tasks: []),
    PrioritizedTasks(priority: .low, tasks: []),
    PrioritizedTasks(priority: .no, tasks: [])
  ]

  private init() {
    print(tasksDocURL)
    loadPrioritizedTasks()

    $prioritizedTasks
      .removeDuplicates()
      .sink(receiveValue: savePrioritizedTasks)
      .store(in: &cancellables)
  }

  func getIndex(for priority: Task.Priority) -> Int {
    prioritizedTasks.firstIndex { $0.priority == priority }!
  }

  func importPrioritizedTasks(from url: URL) {
    loadPrioritizedTasks(from: url)
  }

  private func loadPrioritizedTasks(from url: URL? = nil) {
    let urlToRead = url ?? tasksDocURL
    guard FileManager.default.fileExists(atPath: urlToRead.path) else {
      return
    }

    let decoder = PropertyListDecoder()

    do {
      let tasksData = try Data(contentsOf: urlToRead)
      prioritizedTasks = try decoder.decode([PrioritizedTasks].self, from: tasksData)
    } catch let error {
      print(error)
    }
  }

  private func savePrioritizedTasks(_ tasks: [PrioritizedTasks]) {
    let encoder = PropertyListEncoder()
    encoder.outputFormat = .xml

    do {
      let tasksData = try encoder.encode(tasks)
      try tasksData.write(to: tasksDocURL, options: .atomicWrite)
    } catch let error {
      print(error)
    }
  }
}

private extension TaskStore.PrioritizedTasks {
  init(priority: Task.Priority, names: [String]) {
    self.init(
      priority: priority,
      tasks: names.map { Task(name: $0) }
    )
  }
}
10. TaskStore.PrioritizedTasks.swift
extension TaskStore {
  struct PrioritizedTasks: Codable, Equatable {
    let priority: Task.Priority
    var tasks: [Task]
  }
}

extension TaskStore.PrioritizedTasks: Identifiable {
  var id: Task.Priority { priority }
}
11. ContentView.swift
import SwiftUI
import MessageUI

// swiftlint:disable multiple_closures_with_trailing_closure
struct ContentView: View {
  @ObservedObject var taskStore: TaskStore
  @State var result: Result<MFMailComposeResult, Error>?
  @State var modalIsPresented = false
  @State var mailViewIsPresented = false
  @State var shareSheetIsPresented = false

  var body: some View {
    NavigationView {
      List {
        ForEach(taskStore.prioritizedTasks) { index in
          SectionView(prioritizedTasks: self.$taskStore.prioritizedTasks, index: index)
        }
      }
      .listStyle(GroupedListStyle())
      .navigationBarTitle("Tasks")
      .navigationBarItems(
        leading: EditButton(),
        trailing:
        HStack {
          // Add Item Button
          Button(action: { self.modalIsPresented = true }) {
            Image(systemName: "plus")
          }
          .frame(width: 44, height: 44, alignment: .center)
          .sheet(isPresented: $modalIsPresented) {
            NewTaskView(taskStore: self.taskStore)
          }
          // Export Via Email
          Button(action: { self.mailViewIsPresented = true }) {
            Image(systemName: "envelope")
          }
          .frame(width: 44, height: 44, alignment: .center)
          .disabled(!MFMailComposeViewController.canSendMail())
          .sheet(isPresented: $mailViewIsPresented) {
            MailView(
              messageBody: "This is a test email string",
              attachmentInfo: (
                fileURL: TaskStore.shared.tasksDocURL,
                mimeType: "application/xml"),
              result: self.$result)
          }

          // Share Sheet
          Button(action: { self.shareSheetIsPresented = true }) {
            Image(systemName: "square.and.arrow.up")
          }
          .frame(width: 44, height: 44, alignment: .center)
          .sheet(isPresented: $shareSheetIsPresented) {
            ShareSheet(
              activityItems: [TaskStore.shared.tasksDocURL],
              excludedActivityTypes: [.copyToPasteboard])
          }
        }
      )
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView( taskStore: TaskStore.shared )
  }
}
12. NewTaskView.swift
import SwiftUI

struct NewTaskView: View {
  var taskStore: TaskStore

  @Environment(\.presentationMode) var presentationMode

  @State var text = ""
  @State var priority: Task.Priority = .no

  var body: some View {
    Form {
      TextField("Task Name", text: $text)

      VStack {
        Text("Priority")

        Picker("Priority", selection: $priority.caseIndex) {
          ForEach(Task.Priority.allCases.indices) { priorityIndex in
            Text(
              Task.Priority.allCases[priorityIndex].rawValue
                .capitalized
            )
              .tag(priorityIndex)
          }
        }
        .pickerStyle( SegmentedPickerStyle() )
      }

      Button("Add") {
        let priorityIndex = self.taskStore.getIndex(for: self.priority)
        self.taskStore.prioritizedTasks[priorityIndex].tasks.append(
          Task(name: self.text)
        )

        self.presentationMode.wrappedValue.dismiss()
      }
      .disabled(text.isEmpty)
    }
  }
}

struct NewTaskView_Previews: PreviewProvider {
  static var previews: some View {
    NewTaskView( taskStore: TaskStore.shared )
  }
}
13. RowView.swift
import SwiftUI

struct RowView: View {
  @Binding var task: Task

  let checkmark = Image(systemName: "checkmark")

  var body: some View {
    NavigationLink(
      destination: TaskEditingView(task: $task)
    ) {
      if task.completed {
        checkmark
      } else {
        checkmark.hidden()
      }

      Text(task.name)
        .strikethrough(task.completed)
    }
  }
}

struct RowView_Previews: PreviewProvider {
  static var previews: some View {
    RowView(
      task: .constant( Task(name: "To Do") )
    )
  }
}
14. SectionView.swift
import SwiftUI

struct SectionView: View {
  @Binding var prioritizedTasks: [TaskStore.PrioritizedTasks]
  let index: Int

  var body: some View {
    Section(
      header: Text(
        "\(prioritizedTasks[self.index].priority.rawValue.capitalized) Priority"
      )
    ) {
      ForEach(prioritizedTasks[index].tasks) { i in
        RowView(task: self.$prioritizedTasks[self.index].tasks[i])
      }
      .onMove { sourceIndices, destinationIndex in
        self.prioritizedTasks[self.index].tasks.move(
          fromOffsets: sourceIndices,
          toOffset: destinationIndex
        )
      }
      .onDelete { indexSet in
        self.prioritizedTasks[self.index].tasks.remove(atOffsets: indexSet)
      }
    }
  }
}
15. TaskEditingView.swift
import SwiftUI

struct TaskEditingView: View {
  @Binding var task: Task

  var body: some View {
    Form {
      TextField("Name", text: $task.name)
      Toggle("Completed", isOn: $task.completed)
    }.navigationBarTitle(task.name)
  }
}

struct TaskEditingView_Previews: PreviewProvider {
  static var previews: some View {
    TaskEditingView(
      task: .constant( Task(name: "To Do") )
    )
  }
}
16. MailView.swift
import SwiftUI
import MessageUI

typealias AttachmentInfo = (fileURL: URL, mimeType: String)

struct MailView: UIViewControllerRepresentable {
  @Environment(\.presentationMode) var presentation
  var messageBody: String
  var attachmentInfo: AttachmentInfo?
  @Binding var result: Result<MFMailComposeResult, Error>?

  class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
    @Binding var presentation: PresentationMode
    @Binding var result: Result<MFMailComposeResult, Error>?
    var messageBody: String
    var attachmentInfo: AttachmentInfo?

    init(
      presentation: Binding<PresentationMode>,
      messageBody: String,
      attachmentInfo: AttachmentInfo?,
      result: Binding<Result<MFMailComposeResult, Error>?>
    ) {
      _presentation = presentation
      self.messageBody = messageBody
      self.attachmentInfo = attachmentInfo
      _result = result
    }

    func mailComposeController(
      _ controller: MFMailComposeViewController,
      didFinishWith result: MFMailComposeResult,
      error: Error?
    ) {
      defer {
        $presentation.wrappedValue.dismiss()
      }
      if let error = error {
        self.result = .failure(error)
        return
      }
      self.result = .success(result)
    }
  }

  func makeCoordinator() -> Coordinator {
    return Coordinator(
      presentation: presentation,
      messageBody: messageBody,
      attachmentInfo: attachmentInfo,
      result: $result)
  }

  func makeUIViewController(
    context: UIViewControllerRepresentableContext<MailView>
  ) -> MFMailComposeViewController {
    let viewController = MFMailComposeViewController()
    viewController.mailComposeDelegate = context.coordinator
    viewController.setMessageBody(context.coordinator.messageBody, isHTML: false)

    if let fileURL = attachmentInfo?.fileURL,
      let mimeType = attachmentInfo?.mimeType,
      let fileData = try? Data(contentsOf: fileURL) {
      viewController.addAttachmentData(
        fileData,
        mimeType: mimeType,
        fileName: "ExportData.rwtl")
    }
    return viewController
  }

  func updateUIViewController(
    _ uiViewController: MFMailComposeViewController,
    context: UIViewControllerRepresentableContext<MailView>
  ) { }
}
17. ShareSheet.swift
import SwiftUI

struct ShareSheet: UIViewControllerRepresentable {
  typealias Callback = (
    _ activityType: UIActivity.ActivityType?,
    _ completed: Bool,
    _ returnedItems: [Any]?,
    _ error: Error?) -> Void

  var activityItems: [Any]
  var applicationActivities: [UIActivity]?
  var excludedActivityTypes: [UIActivity.ActivityType]?
  var callback: Callback?

  func makeUIViewController(context: Context) -> UIActivityViewController {
    let controller = UIActivityViewController(
      activityItems: activityItems,
      applicationActivities: applicationActivities)
    controller.excludedActivityTypes = excludedActivityTypes
    controller.completionWithItemsHandler = callback
    return controller
  }

  func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
    // nothing to do here
  }
}
struct ShareSheet_Previews: PreviewProvider {
  static var previews: some View {
    let theShareSheet = ShareSheet(
      activityItems: ["A preview string" as NSString],
      excludedActivityTypes: [UIActivity.ActivityType.airDrop])
    return theShareSheet
  }
}

后记

本篇主要讲述了使用Universal Type Identifiers进行App中的数据的导入和导出,感兴趣的给个赞或者关注~~~

相关文章

网友评论

      本文标题:基于Universal Type Identifiers的App

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