• DownloadingImages 下载缓存图片,显示图片文字列表


    1. 用到的技术点:

      1) Codable : 可编/解码 JSON 数据

      2) background threads : 后台线程

      3) weak self : 弱引用

      4) Combine : 取消器/组合操作

      5) Publishers and Subscribers : 发布者与订阅者

      6) FileManager : 文件管理器

      7) NSCache : 缓存

    2. 网址:

      2.1 测试接口网址:

    jsonplaceholdericon-default.png?t=N7T8https://jsonplaceholder.typicode.com/

      2.2 JSON 转 Model 网址:

    quicktypeicon-default.png?t=N7T8https://app.quicktype.io/

    3. 项目结构图

    4. Model 层

      4.1 创建 PhotoModel.swift 文件

    1. import Foundation
    2. struct PhotoModel: Identifiable, Codable{
    3. let albumId: Int
    4. let id: Int
    5. let title: String
    6. let url: String
    7. let thumbnailUrl: String
    8. }
    9. /*
    10. {
    11. "albumId": 1,
    12. "id": 1,
    13. "title": "accusamus beatae ad facilis cum similique qui sunt",
    14. "url": "https://via.placeholder.com/600/92c952",
    15. "thumbnailUrl": "https://via.placeholder.com/150/92c952"
    16. }
    17. */

    5. 工具类

      5.1 创建请求数据服务类,PhotoModelDataService.swift

    1. import Foundation
    2. import Combine
    3. /// 请求数据服务
    4. class PhotoModelDataService{
    5. // 单例模式 Singleton
    6. static let instance = PhotoModelDataService()
    7. // 返回 JSON 数据,解码成模型
    8. @Published var photoModel:[PhotoModel] = []
    9. // 随时取消请求
    10. var cancellables = Set<AnyCancellable>()
    11. // 只能内部实例化,保证一个 App 只有一次实例化
    12. private init() {
    13. downloadData()
    14. }
    15. // 测试接口网址: https://jsonplaceholder.typicode.com/
    16. // 下载数据
    17. func downloadData(){
    18. // 获取 URL
    19. guard let url = URL(string: "https://jsonplaceholder.typicode.com/photos") else { return }
    20. // 进行请求
    21. URLSession.shared.dataTaskPublisher(for: url)
    22. .subscribe(on: DispatchQueue.global(qos: .background))
    23. .receive(on: DispatchQueue.main)
    24. .tryMap(handleOutput)
    25. .decode(type: [PhotoModel].self, decoder: JSONDecoder())
    26. .sink { completion in
    27. switch(completion){
    28. case .finished:
    29. break
    30. case .failure(let error):
    31. print("Error downloading data. \(error)")
    32. break
    33. }
    34. } receiveValue: { [weak self] returnedPhotoModel in
    35. guard let self = self else { return }
    36. self.photoModel = returnedPhotoModel
    37. }
    38. // 随时取消
    39. .store(in: &cancellables)
    40. }
    41. // 输出数据
    42. private func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data{
    43. guard
    44. let response = output.response as? HTTPURLResponse,
    45. response.statusCode >= 200 && response.statusCode < 300 else {
    46. throw URLError(.badServerResponse)
    47. }
    48. return output.data
    49. }
    50. }

      5.2 创建图片缓存管理器类,PhotoModelCacheManager.swift

    1. import Foundation
    2. import SwiftUI
    3. /// 图片缓存管理器
    4. class PhotoModelCacheManager{
    5. // 单例模式
    6. static let instance = PhotoModelCacheManager()
    7. // 只能内部实例化,保证一个 App 只有一次实例化
    8. private init() {}
    9. // 图片数量缓存,计算型属性
    10. var photoCache: NSCache<NSString, UIImage> = {
    11. let cache = NSCache<NSString, UIImage>()
    12. cache.countLimit = 200
    13. cache.totalCostLimit = 1024 * 1024 * 200 // 200mb
    14. return cache
    15. }()
    16. // 添加
    17. func add(key: String, value: UIImage){
    18. photoCache.setObject(value, forKey: key as NSString)
    19. }
    20. // 获取
    21. func get(key: String) -> UIImage? {
    22. return photoCache.object(forKey: key as NSString)
    23. }
    24. }

      5.3 创建储存图片文件管理类,PhotoModelFileManager.swift

    1. import Foundation
    2. import SwiftUI
    3. // 存储图片文件管理器
    4. class PhotoModelFileManager{
    5. // 单例模式
    6. static let instance = PhotoModelFileManager()
    7. let folderName = "downloaded_photos"
    8. private init(){
    9. createFolderIfNeeded()
    10. }
    11. // 创建存放图片的目录
    12. private func createFolderIfNeeded(){
    13. guard let url = getFolderPath() else { return }
    14. if !FileManager.default.fileExists(atPath: url.path){
    15. do {
    16. try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
    17. print("Created folder success.")
    18. } catch let error {
    19. print("Error creating folder. \(error)")
    20. }
    21. }
    22. }
    23. // 创建文件夹路径
    24. private func getFolderPath()-> URL?{
    25. return FileManager
    26. .default
    27. .urls(for: .cachesDirectory, in: .userDomainMask)
    28. .first?
    29. .appendingPathComponent(folderName)
    30. }
    31. // .../downloaded_photos
    32. // .../downloaded_photos/image_name.png
    33. /// 获取图片路径
    34. /// - Parameter key: 名字
    35. /// - Returns: 图片路径
    36. private func getImagePath(key: String) -> URL?{
    37. guard let folder = getFolderPath() else { return nil}
    38. return folder.appendingPathComponent(key + ".png")
    39. }
    40. // 添加图片
    41. func add(key: String, value: UIImage){
    42. // 获取数据和路径
    43. guard let data = value.pngData(),
    44. let url = getImagePath(key: key) else { return }
    45. // 文件写人数据
    46. do {
    47. try data.write(to: url)
    48. print("Saving to file success.")
    49. } catch let error {
    50. print("Error saving to file manager. \(error)")
    51. }
    52. }
    53. // 获取图片
    54. func get(key: String) -> UIImage?{
    55. guard
    56. let path = getImagePath(key: key)?.path,
    57. FileManager.default.fileExists(atPath: path) else {
    58. //print("Error getting path.")
    59. return nil
    60. }
    61. return UIImage(contentsOfFile: path)
    62. }
    63. }

    6. ViewModel 层

      6.1 创建下载图片 ViewModel 类,DownloadingImageViewModel.swift

    1. import Foundation
    2. import Combine
    3. class DownloadingImageViewModel: ObservableObject{
    4. // 数组模型
    5. @Published var dataArray:[PhotoModel] = []
    6. // 请求数据服务
    7. let dataService = PhotoModelDataService.instance
    8. // 取消操作
    9. var cancellables = Set<AnyCancellable>()
    10. init() {
    11. addSubscribers()
    12. }
    13. // 订阅数据
    14. func addSubscribers(){
    15. dataService.$photoModel
    16. .sink {[weak self] returnedPhotoModel in
    17. guard let self = self else { return }
    18. self.dataArray = returnedPhotoModel
    19. }
    20. .store(in: &cancellables)
    21. }
    22. }

      6.2 创建图片加载 ViewModel 类,ImageLoadingViewModel.swift

    1. import Foundation
    2. import SwiftUI
    3. import Combine
    4. class ImageLoadingViewModel: ObservableObject{
    5. @Published var image: UIImage?
    6. @Published var isLoading: Bool = false
    7. // 取消
    8. var cancellables = Set<AnyCancellable>()
    9. // 缓存管理器
    10. let manager = PhotoModelFileManager.instance
    11. let urlString: String
    12. let imageKey: String
    13. init(url: String, key: String) {
    14. urlString = url
    15. imageKey = key
    16. getImage()
    17. }
    18. // 获取图片
    19. func getImage() {
    20. if let saveImage = manager.get(key: imageKey){
    21. image = saveImage
    22. print("Getting saved image.")
    23. }else{
    24. downLoadImage()
    25. print("Downloading image now!")
    26. }
    27. }
    28. // 下载图片
    29. func downLoadImage(){
    30. isLoading = true
    31. guard let url = URL(string: urlString) else {
    32. isLoading = false
    33. return
    34. }
    35. // 请求
    36. URLSession.shared.dataTaskPublisher(for: url)
    37. .map { UIImage(data: $0.data) }
    38. .receive(on: DispatchQueue.main)
    39. .sink { [weak self] _ in
    40. self?.isLoading = false
    41. } receiveValue: { [weak self] returnedImage in
    42. guard
    43. let self = self,
    44. let image = returnedImage else { return }
    45. self.image = image
    46. // 下载的图像保存在缓存中
    47. self.manager.add(key: imageKey, value: image)
    48. }
    49. .store(in: &cancellables)
    50. }
    51. }

    7. 创建 View 层

      7.1 创建下载,缓存,显示图片视图,DownloadingImageView.swift

    1. import SwiftUI
    2. /// 下载,缓存,显示图片
    3. struct DownloadingImageView: View {
    4. @StateObject var loaderViewModel: ImageLoadingViewModel
    5. init(url: String, key: String) {
    6. // _ : 加载器 wrappedValue: 包装器
    7. _loaderViewModel = StateObject(wrappedValue: ImageLoadingViewModel(url: url, key: key))
    8. }
    9. var body: some View {
    10. ZStack {
    11. if loaderViewModel.isLoading{
    12. ProgressView()
    13. }else if let image = loaderViewModel.image{
    14. Image(uiImage: image)
    15. .resizable()
    16. .clipShape(Circle())
    17. }
    18. }
    19. }
    20. }
    21. struct DownloadingImageView_Previews: PreviewProvider {
    22. static var previews: some View {
    23. DownloadingImageView(url: "https://via.placeholder.com/600/92c952", key: "1")
    24. .frame(width: 75, height: 75)
    25. .previewLayout(.sizeThatFits)
    26. }
    27. }

      7.2 创建下载显示图片文字行视图,DownloadingImagesRow.swift

    1. import SwiftUI
    2. struct DownloadingImagesRow: View {
    3. let model : PhotoModel
    4. var body: some View {
    5. HStack {
    6. DownloadingImageView(url: model.url, key: "\(model.id)")
    7. .frame(width: 75, height: 75)
    8. VStack (alignment: .leading){
    9. Text(model.title)
    10. .font(.headline)
    11. Text(model.url)
    12. .foregroundColor(.gray)
    13. .italic()
    14. }
    15. .frame( maxWidth: .infinity, alignment: .leading)
    16. }
    17. }
    18. }
    19. struct DownloadingImagesRow_Previews: PreviewProvider {
    20. static var previews: some View {
    21. DownloadingImagesRow(model: PhotoModel(albumId: 1, id: 1, title: "title", url: "https://via.placeholder.com/600/92c952", thumbnailUrl: "thumbnaolUrl here"))
    22. .padding()
    23. .previewLayout(.sizeThatFits)
    24. }
    25. }

      7.3 创建下载显示图片文字列表视图,DownloadingImagesBootcamp.swift

    1. import SwiftUI
    2. // Codable : 可编/解码 JSON 数据
    3. // background threads : 后台线程
    4. // weak self : 弱引用
    5. // Combine : 取消器/组合操作
    6. // Publishers and Subscribers : 发布者与订阅者
    7. // FileManager : 文件管理器
    8. // NSCache : 缓存
    9. struct DownloadingImagesBootcamp: View {
    10. @StateObject var viewModel = DownloadingImageViewModel()
    11. var body: some View {
    12. NavigationView {
    13. List {
    14. ForEach(viewModel.dataArray) { model in
    15. DownloadingImagesRow(model: model)
    16. }
    17. }
    18. .navigationTitle("Downloading Images")
    19. }
    20. }
    21. }
    22. struct DownloadingImagesBootcamp_Previews: PreviewProvider {
    23. static var previews: some View {
    24. DownloadingImagesBootcamp()
    25. }
    26. }

    8. 效果图:

  • 相关阅读:
    数据库随堂笔记(6)ᝰ数据库设计
    Flink DataStream 侧输出流 Side Output
    使用JMeter测试.Net5.0,.Net6.0框架下无数据处理的并发情况
    驱动开发:内核LoadLibrary实现DLL注入
    python 中import的用法:
    (续)SSM整合之springmvc笔记(@RequestMapping注解)(P124-130)还没完
    Redis 为什么这么快,你知道 I/O 多路复用吗?
    IOS硬件模拟定位原理
    QT当中的connect+eventFilter函数全解
    【正点原子STM32连载】 第六十二章 UCOSII实验2-信号量和邮箱 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
  • 原文地址:https://blog.csdn.net/u011193452/article/details/133685038