QuickLook框架詳細(xì)解析(五) —— QuickLook預(yù)覽和縮略圖擴(kuò)展的實(shí)現(xiàn)(二)

版本記錄

版本號(hào) 時(shí)間
V1.0 2021.03.22 星期一

前言

QuickLook框架提供了文檔的預(yù)覽功能。接下來幾篇我們就一起看一下這個(gè)框架瘪校。感興趣的可以看下面幾篇。
1. QuickLook框架詳細(xì)解析(一) —— 基本概覽(一)
2. QuickLook框架詳細(xì)解析(二) —— QuickLook 預(yù)覽簡(jiǎn)單示例(一)
3. QuickLook框架詳細(xì)解析(三) —— QuickLook 預(yù)覽簡(jiǎn)單示例(二)
4. QuickLook框架詳細(xì)解析(四) —— QuickLook預(yù)覽和縮略圖擴(kuò)展的實(shí)現(xiàn)(一)

源碼

1. Swift

首先看下工程組織文件

下面就是源碼了

1. AppMain.swift
import SwiftUI

@main
struct AppMain: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}
2. ContentView.swift
import SwiftUI

struct ContentView: View {
  let documents = Document.documents
  let title = "Thumbs"

  var body: some View {
    NavigationView {
      ScrollView(.vertical) {
        LazyVGrid(
          columns: [GridItem(.adaptive(minimum: 150, maximum: 150), spacing: 44)],
          spacing: 10
        ) {
          ForEach(documents) { document in
            NavigationLink(destination: DocumentPreviewView(document: document)) {
              DocumentThumbnailView(document: document)
            }
          }
        }
      }
      .navigationBarTitle(title)
    }
  }
}

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
3. Document.swift
import Foundation
import QuickLook

struct Document {
  let url: URL

  var name: String {
    url.lastPathComponent
  }
}

extension Document: Identifiable {
  var id: URL {
    url
  }
}

// MARK: - Static helper methods
extension Document {
  static let documents = [
    Bundle.main.url(forResource: "zombiethumb", withExtension: "jpg"),
    Bundle.main.url(forResource: "humanthumb", withExtension: "pdf"),
    Bundle.main.url(forResource: "thumbsup", withExtension: "txt"),
    Bundle.main.url(forResource: "thumbsdown", withExtension: "md"),
    Bundle.main.url(forResource: "thumbsdown", withExtension: "html"),
    Bundle.main.url(forResource: "greenthumb", withExtension: "thumb")
  ]
  .compactMap { $0 }
  .map { Document(url: $0) }
}

// MARK: - QLThumbnailGenerator
extension Document {
  func generateThumbnail(
    size: CGSize,
    scale: CGFloat,
    completion: @escaping (UIImage) -> Void
  ) {
    let request = QLThumbnailGenerator.Request(
      fileAt: url,
      size: size,
      scale: scale,
      representationTypes: .all
    )

    let generator = QLThumbnailGenerator.shared
    generator.generateRepresentations(for: request) { thumbnail, _, error in
      if let thumbnail = thumbnail {
        print("\(name) thumbnail generated")
        completion(thumbnail.uiImage)
      } else if let error = error {
        print("\(name) - \(error)")
      }
    }
  }
}
4. DocumentThumbnailView.swift
import SwiftUI

struct DocumentThumbnailView: View {
  let document: Document
  var thumbnailSize = CGSize(width: 150, height: 150)
  @State var thumbnail = Image(systemName: "doc")
  @Environment(\.displayScale) var displayScale: CGFloat

  var body: some View {
    GroupBox(label: Text(verbatim: document.name).font(.system(size: 12))) {
      thumbnail
        .font(.system(size: 120))
        .foregroundColor(Color(.label))
        .frame(width: thumbnailSize.width, height: thumbnailSize.height, alignment: .center)
        .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
        .padding()
    }
    .groupBoxStyle(PlainGroupBoxStyle())
    .onAppear {
      document.generateThumbnail(
        size: thumbnailSize,
        scale: displayScale
      ) { uiImage in
        DispatchQueue.main.async {
          self.thumbnail = Image(uiImage: uiImage)
        }
      }
    }
  }
}

struct PlainGroupBoxStyle: GroupBoxStyle {
  func makeBody(configuration: Configuration) -> some View {
    VStack(alignment: .center) {
      configuration.label
        .padding()
      configuration.content
    }
    .background(Color(.systemGroupedBackground))
    .clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
  }
}
5. DocumentPreviewView.swift
import SwiftUI
import QuickLook

struct DocumentPreviewView: View {
  let document: Document

  var body: some View {
    QLPreviewView(previewItem: document.url as QLPreviewItem)
      .navigationBarTitle(document.name, displayMode: .inline)
  }
}
6. ThumbFile.swift
import Foundation
import UIKit.UIImage

struct ThumbFile: Codable {
  let title: String
  let imageBase64: String

  init?(from fileURL: URL) {
    guard
      let data = FileManager.default.contents(atPath: fileURL.path),
      let thumb = try? JSONDecoder().decode(Self.self, from: data)
    else {
      return nil
    }

    self = thumb
  }

  var uiImage: UIImage? {
    return Data(base64Encoded: imageBase64)
      .flatMap { UIImage(data: $0) }
  }
}
7. ThumbFileViewController.swift
import UIKit

class ThumbFileViewController: UIViewController {
  private lazy var titleLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.textAlignment = .center
    label.font = UIFont.preferredFont(forTextStyle: .title1)
    label.text = title
    return label
  }()

  private lazy var imageView: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    return imageView
  }()

  override func viewDidLoad() {
    super.viewDidLoad()

    view.backgroundColor = .systemBackground
    view.addSubview(titleLabel)
    view.addSubview(imageView)

    NSLayoutConstraint.activate([
      titleLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 30),
      titleLabel.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
      titleLabel.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
      imageView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 50.0),
      imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
    ])
  }

  var thumbFile: ThumbFile? {
    didSet {
      titleLabel.text = thumbFile?.title
      imageView.image = thumbFile?.uiImage
    }
  }

  static func generateThumbnail(for thumbFile: ThumbFile, size: CGSize) -> UIImage {
    dispatchPrecondition(condition: .onQueue(.main))

    let viewController = ThumbFileViewController()
    viewController.thumbFile = thumbFile
    viewController.view.frame = CGRect(origin: .zero, size: CGSize(width: 450, height: 450))
    viewController.view.updateConstraintsIfNeeded()
    viewController.view.layoutIfNeeded()

    let renderer = UIGraphicsImageRenderer(bounds: viewController.view.bounds)
    return renderer.image { context in
      viewController.view.layer.render(in: context.cgContext)
    }
  }
}
8. QLPreviewView.swift
import SwiftUI
import QuickLook

struct QLPreviewView: UIViewControllerRepresentable {
  var previewItem: QLPreviewItem

  func makeCoordinator() -> Coordinator {
    Coordinator(parent: self)
  }

  func updateUIViewController(
    _ viewController: QLPreviewController,
    context: UIViewControllerRepresentableContext<QLPreviewView>
  ) {
    viewController.reloadData()
  }

  func makeUIViewController(context: Context) -> QLPreviewController {
    // For more on using QLPreviewController, check out this tutorial:
    // https://www.raywenderlich.com/10447506-quicklook-previews-for-ios-getting-started
    let controller = QLPreviewController()
    controller.dataSource = context.coordinator
    controller.reloadData()
    return controller
  }

  class Coordinator: NSObject, QLPreviewControllerDataSource {
    var parent: QLPreviewView

    init(parent: QLPreviewView) {
      self.parent = parent
      super.init()
    }

    // MARK: - QLPreviewControllerDataSource
    func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
      return 1
    }

    func previewController(
      _ controller: QLPreviewController,
      previewItemAt index: Int
    ) -> QLPreviewItem {
      return parent.previewItem
    }
  }
}
9. PreviewViewController.swift
import UIKit
import QuickLook

class PreviewViewController: ThumbFileViewController, QLPreviewingController {
  enum ThumbFilePreviewError: Error {
    case unableToOpenFile(atURL: URL)
  }

  func preparePreviewOfFile(
    at url: URL,
    completionHandler handler: @escaping (Error?) -> Void
  ) {
    guard let thumbFile = ThumbFile(from: url) else {
      handler(ThumbFilePreviewError.unableToOpenFile(atURL: url))
      return
    }

    self.thumbFile = thumbFile

    handler(nil)
  }
}
10. ThumbnailProvider.swift
import UIKit
import QuickLookThumbnailing

class ThumbnailProvider: QLThumbnailProvider {
  enum ThumbFileThumbnailError: Error {
    case unableToOpenFile(atURL: URL)
  }

  override func provideThumbnail(
    for request: QLFileThumbnailRequest,
    _ handler: @escaping (QLThumbnailReply?, Error?) -> Void
  ) {
    guard let thumbFile = ThumbFile(from: request.fileURL) else {
      handler(nil, ThumbFileThumbnailError.unableToOpenFile(atURL: request.fileURL))
      return
    }

    DispatchQueue.main.async {
      let image = ThumbFileViewController.generateThumbnail(
        for: thumbFile,
        size: request.maximumSize
      )

      let reply = QLThumbnailReply(contextSize: request.maximumSize) {
        image.draw(in: CGRect(origin: .zero, size: request.maximumSize))
        return true
      }

      handler(reply, nil)
    }
  }
}

后記

本篇主要講述了QuickLook預(yù)覽和縮略圖擴(kuò)展的實(shí)現(xiàn)名段,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阱扬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子伸辟,更是在濱河造成了極大的恐慌麻惶,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件信夫,死亡現(xiàn)場(chǎng)離奇詭異窃蹋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)静稻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門警没,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人振湾,你說我怎么就攤上這事杀迹。” “怎么了押搪?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵树酪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我大州,道長(zhǎng)续语,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任摧茴,我火速辦了婚禮绵载,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘苛白。我一直安慰自己娃豹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布购裙。 她就那樣靜靜地躺著懂版,像睡著了一般。 火紅的嫁衣襯著肌膚如雪躏率。 梳的紋絲不亂的頭發(fā)上躯畴,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音薇芝,去河邊找鬼蓬抄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛夯到,可吹牛的內(nèi)容都是我干的嚷缭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼耍贾,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼阅爽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起荐开,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤付翁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后晃听,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體百侧,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年能扒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了佣渴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赫粥,死狀恐怖观话,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情越平,我是刑警寧澤频蛔,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站秦叛,受9級(jí)特大地震影響晦溪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挣跋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一三圆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦舟肉、人聲如沸修噪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)黄琼。三九已至,卻和暖如春整慎,著一層夾襖步出監(jiān)牢的瞬間脏款,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工裤园, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撤师,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓拧揽,卻偏偏與公主長(zhǎng)得像剃盾,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子强法,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容