diff --git a/README.md b/README.md index 43a4a53e3..37cc6240c 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Kingfisher is a powerful, pure-Swift library for downloading and caching images - [x] Low Data Mode support. - [x] SwiftUI support. - [x] Swift 6 & Swift Concurrency (strict mode) prepared. +- [x] Load & cache for Live Photo. ### Kingfisher 101 diff --git a/Sources/Cache/DiskStorage.swift b/Sources/Cache/DiskStorage.swift index 513c8a1e6..05d028304 100644 --- a/Sources/Cache/DiskStorage.swift +++ b/Sources/Cache/DiskStorage.swift @@ -284,9 +284,11 @@ public enum DiskStorage { } /// Determines whether there is valid cached data under a given key. - /// - /// - Parameter key: The cache key of the value. - /// - Returns: `true` if there is valid data under the key; otherwise, `false`. + /// + /// - Parameters: + /// - key: The cache key of the value. + /// - forcedExtension: The file extension, if exists. + /// - Returns: `true` if there is valid data under the key and file extension; otherwise, `false`. /// /// > This method does not actually load the data from disk, so it is faster than directly loading the cached /// value by checking the nullability of the ``DiskStorage/Backend/value(forKey:extendingExpiration:)`` method. @@ -299,6 +301,7 @@ public enum DiskStorage { /// - Parameters: /// - key: The cache key of the value. /// - referenceDate: A reference date to check whether the cache is still valid. + /// - forcedExtension: The file extension, if exists. /// /// - Returns: `true` if there is valid data under the key; otherwise, `false`. /// @@ -321,7 +324,9 @@ public enum DiskStorage { } /// Removes a value from a specified key. - /// - Parameter key: The cache key of the value. + /// - Parameters: + /// - key: The cache key of the value. + /// - forcedExtension: The file extension, if exists. /// - Throws: An error during the removal of the value. public func remove(forKey key: String, forcedExtension: String? = nil) throws { let fileURL = cacheFileURL(forKey: key, forcedExtension: forcedExtension) @@ -344,15 +349,18 @@ public enum DiskStorage { try prepareDirectory() } } - + /// The URL of the cached file with a given computed `key`. - /// - /// - Parameter key: The final computed key used when caching the image. Please note that usually this is not + /// - Parameters: + /// - key: The final computed key used when caching the image. Please note that usually this is not /// the ``Source/cacheKey`` of an image ``Source``. It is the computed key with the processor identifier /// considered. + /// - forcedExtension: The file extension, if exists. + /// - Returns: The expected file URL on the disk based on the `key` and the `forcedExtension`. + /// + /// This method does not guarantee that an image is already cached at the returned URL. It just provides the URL + /// where the image should be if it exists in the disk storage, with the given key and file extension. /// - /// This method does not guarantee that an image is already cached at the returned URL. It just provides the URL - /// where the image should be if it exists in the disk storage, with the given key. public func cacheFileURL(forKey key: String, forcedExtension: String? = nil) -> URL { let fileName = cacheFileName(forKey: key, forcedExtension: forcedExtension) return directoryURL.appendingPathComponent(fileName, isDirectory: false) diff --git a/Sources/Documentation.docc/Documentation.md b/Sources/Documentation.docc/Documentation.md index 80d097bd9..d90f8d57e 100644 --- a/Sources/Documentation.docc/Documentation.md +++ b/Sources/Documentation.docc/Documentation.md @@ -1,4 +1,4 @@ -# Kingfisher +# ``Kingfisher`` @Metadata { @PageImage( @@ -40,8 +40,7 @@ downloading again. ### Loading Images in Simple Way - ``KingfisherCompatible`` -- ``KingfisherWrapper/setImage(with:placeholder:options:completionHandler:)-2srsv`` -- ``KingfisherWrapper/setImage(with:placeholder:options:completionHandler:)-3ft7a`` +- ``KingfisherWrapper/setImage(with:placeholder:options:completionHandler:)-8qfkr`` - ``KingfisherManager`` - ``Source`` @@ -75,6 +74,11 @@ downloading again. - ``AnimatedImageView`` - ``GIFAnimatedImage`` +### Live Photo + +- +- ``KingfisherWrapper/setImage(with:options:completionHandler:)-1to8a`` + ### SwiftUI - ``KFImage`` diff --git a/Sources/Documentation.docc/Topics/Topic_LivePhoto.md b/Sources/Documentation.docc/Topics/Topic_LivePhoto.md new file mode 100644 index 000000000..1502de85a --- /dev/null +++ b/Sources/Documentation.docc/Topics/Topic_LivePhoto.md @@ -0,0 +1,104 @@ +# Loading Live Photos + +Load and cache Live Photos from network sources using Kingfisher. + +## Overview + +Kingfisher provides a seamless way to load Live Photos, which consist of a still image and a video, from network sources. This guide will walk you through the process of utilizing Kingfisher's Live Photo support. + +## Live Photo Data Preparation + +Before loading a Live Photo with Kingfisher, you need to prepare and host the data. Kingfisher can download and cache the live photo data from the network (usually your server or a CDN). This section demonstrates how to get the necessary data from a `PHAsset`. + +If you've already set up the data and prepared the necessary URLs for the live photo components, you can skip to the next section to learn how to load it. + +Assuming you have a valid `PHAsset` from the Photos framework, here's a sample of how to extract its data: + +```swift +let asset: PHAsset = // ... your PHAsset +if !asset.mediaSubtypes.contains(.photoLive) { + print("Not a live photo") + return +} + +let resources = PHAssetResource.assetResources(for: asset) +var allData = [Data]() + +let group = DispatchGroup() +group.notify(queue: .main) { + allData.forEach { data in + // Upload data to your server + serverRequest.upload(data) + } +} + +resources.forEach { resource in + group.enter() + var data = Data() + PHAssetResourceManager.default().requestData(for: resource, options: nil) { chunk in + data.append(chunk) + } completionHandler: { error in + defer { group.leave() } + if let error = error { + print("Error: \(error)") + return + } + allData.append(data) + } +} +``` + +Important notes: +- This is a basic example showing how to retrieve data from a live photo asset. +- Use [`PHAssetResource.type`]((https://developer.apple.com/documentation/photokit/phassetresource/1623987-type)) to get more information about each live photo resource. Typically, resources with `.photo` and `.pairedVideo` types are necessary for a minimal Live Photo. +- Do not modify the metadata or actual data of the resources, as this may cause problems when loading in Kingfisher later. +- When serving the files, it's recommended to include the file extensions (`.heic` for the still image, and `.mov` for the video) in the URL. While not mandatory, this helps Kingfisher identify the file type more accurately. +- You can use [`PHAssetResource.originalFilename`](https://developer.apple.com/documentation/photokit/phassetresource/1623985-originalfilename) to get and preserve the original file extension. + + +## Loading Live Photos + +### Step 1: Import Required Frameworks and Set Up PHLivePhotoView + +```swift +import Kingfisher +import PhotosUI + +let livePhotoView = PHLivePhotoView(frame: CGRect(x: 0, y: 0, width: 300, height: 300)) +view.addSubview(livePhotoView) +``` + +### Step 2: Prepare URLs + +```swift +let imageURL = URL(string: "https://example.com/image.heic")! +let videoURL = URL(string: "https://example.com/video.mov")! +let urls = [imageURL, videoURL] +``` + +### Step 3: Load the Live Photo + +```swift +livePhotoView.kf.setImage(with: urls) { result in + switch result { + case .success(let retrieveResult): + print("Live photo loaded: \(retrieveResult.livePhoto)") + print("Cache type: \(retrieveResult.loadingInfo.cacheType)") + case .failure(let error): + print("Error: \(error)") + } +} +``` + +The loaded live photo will be stored in the disk cache of Kingfisher to boost future loading requests. + +## Notes + +- Verify that the provided URLs are valid and accessible. +- Loading may take time, especially for resources fetched over the network. +- Certain `KingfisherOptionsInfo` options, such as custom processors, are not supported for Live Photos. +- To load a Live Photo, its data must be cached on disk at least during the loading process. If you prefer not to retain the Live Photo data on disk, you can set a short disk cache expiration using options like `.diskCacheExpiration(.seconds(10))`, or manually clear the disk cache regularly after using. + +## Conclusion + +By following these steps, you can efficiently load and cache Live Photos in your iOS applications using Kingfisher, enhancing the user experience with smooth integration of this dynamic content type. \ No newline at end of file diff --git a/Sources/Extensions/PHLivePhotoView+Kingfisher.swift b/Sources/Extensions/PHLivePhotoView+Kingfisher.swift index 10fe5999a..c88cd5d49 100644 --- a/Sources/Extensions/PHLivePhotoView+Kingfisher.swift +++ b/Sources/Extensions/PHLivePhotoView+Kingfisher.swift @@ -145,7 +145,7 @@ extension KingfisherWrapper where Base: PHLivePhotoView { /// /// - Parameters: /// - source: The ``LivePhotoSource`` object defining the live photo resource. - /// - options: An options set to define image setting behaviors. See `KingfisherOptionsInfo` for more. + /// - options: An options set to define image setting behaviors. See ``KingfisherOptionsInfo`` for more. /// - completionHandler: Called when the image setting process finishes. /// - Returns: A task represents the image downloading. /// The return value will be `nil` if the image is set with a empty source.