Skip to content

Commit

Permalink
Improve downloadImage() API with ImageDownloadRequest structure
Browse files Browse the repository at this point in the history
  • Loading branch information
nuno-vieira committed Oct 25, 2022
1 parent 70103c0 commit 1802b0b
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ open class ChatMessageLinkPreviewView: _Control, ThemeProvider {

authorLabel.textColor = tintColor

components.imageLoader.loadImage(into: imagePreview, from: payload?.previewURL)
components.imageLoader.loadImage(
into: imagePreview,
from: payload?.previewURL,
with: ImageLoaderOptions()
)
imagePreview.isHidden = isImageHidden

authorLabel.text = payload?.author
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,11 @@ open class ChatChannelAvatarView: _View, ThemeProvider, SwiftUIRepresentable {
}

let avatarSize = components.avatarThumbnailSize
let urlsAndOptions = avatarUrls.map {
(url: $0, options: ImageDownloadOptions(resize: .init(avatarSize)))
let requests = avatarUrls.map {
ImageDownloadRequest(url: $0, options: ImageDownloadOptions(resize: .init(avatarSize)))
}

components.imageLoader.downloadMultipleImages(from: urlsAndOptions) { results in
components.imageLoader.downloadMultipleImages(with: requests) { results in
let imagesMapper = ImageResultsMapper(results: results)
let images = imagesMapper.mapErrors(with: placeholderImages)
completion(images, channelId)
Expand Down
16 changes: 16 additions & 0 deletions Sources/StreamChatUI/Utils/ImageLoading/ImageDownloadRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// Copyright © 2022 Stream.io Inc. All rights reserved.
//

import Foundation

/// The url and options information of an image download request.
public struct ImageDownloadRequest {
public let url: URL
public let options: ImageDownloadOptions

public init(url: URL, options: ImageDownloadOptions) {
self.url = url
self.options = options
}
}
30 changes: 10 additions & 20 deletions Sources/StreamChatUI/Utils/ImageLoading/ImageLoading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,22 @@ public protocol ImageLoading: AnyObject {

/// Download an image from the given `URL`.
/// - Parameters:
/// - url: The `URL` of the image.
/// - options: The loading options on how to fetch the image.
/// - request: The url and options information of an image download request.
/// - completion: The completion when the loading is finished.
/// - Returns: A cancellable task.
@discardableResult
func downloadImage(
from url: URL,
with options: ImageDownloadOptions,
with request: ImageDownloadRequest,
completion: @escaping ((_ result: Result<UIImage, Error>) -> Void)
) -> Cancellable?

/// Load a batch of images and get notified when all of them complete loading.
/// - Parameters:
/// - urlsAndOptions: A tuple of urls and the options on how to fetch the image.
/// - requests: The urls and options information of each image download request.
/// - completion: The completion when the loading is finished.
/// It returns an array of image and errors in case the image failed to load.
func downloadMultipleImages(
from urlsAndOptions: [(url: URL, options: ImageDownloadOptions)],
with requests: [ImageDownloadRequest],
completion: @escaping (([Result<UIImage, Error>]) -> Void)
)

Expand Down Expand Up @@ -93,6 +91,7 @@ public extension ImageLoading {
return loadImage(
into: imageView,
from: attachmentPayload?.imageURL,
with: ImageLoaderOptions(),
completion: completion
)
}
Expand All @@ -116,20 +115,12 @@ public extension ImageLoading {
// MARK: - Default Parameters

public extension ImageLoading {
@discardableResult
func downloadImage(
from url: URL,
with options: ImageDownloadOptions = .init(),
completion: @escaping ((_ result: Result<UIImage, Error>) -> Void)
) -> Cancellable? {
downloadImage(from: url, with: options, completion: completion)
}

// Default empty completion block.
@discardableResult
func loadImage(
into imageView: UIImageView,
from url: URL?,
with options: ImageLoaderOptions = .init(),
with options: ImageLoaderOptions,
completion: ((_ result: Result<UIImage, Error>) -> Void)? = nil
) -> Cancellable? {
loadImage(into: imageView, from: url, with: options, completion: completion)
Expand All @@ -150,8 +141,7 @@ public extension ImageLoading {
}

return downloadImage(
from: url,
with: ImageDownloadOptions(resize: nil),
with: ImageDownloadRequest(url: url, options: ImageDownloadOptions()),
completion: completion
)
}
Expand Down Expand Up @@ -188,10 +178,10 @@ public extension ImageLoading {
completion: @escaping (([UIImage]) -> Void)
) {
let urlsAndOptions = urls.map { url in
(url: url, options: ImageDownloadOptions(resize: .init(thumbnailSize)))
ImageDownloadRequest(url: url, options: .init(resize: .init(thumbnailSize)))
}

downloadMultipleImages(from: urlsAndOptions) { results in
downloadMultipleImages(with: urlsAndOptions) { results in
let imagesMapper = ImageResultsMapper(results: results)
let images = imagesMapper.mapErrors(with: placeholders)
completion(images)
Expand Down
15 changes: 10 additions & 5 deletions Sources/StreamChatUI/Utils/ImageLoading/NukeImageLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,11 @@ open class NukeImageLoader: ImageLoading {

@discardableResult
open func downloadImage(
from url: URL,
with options: ImageDownloadOptions = ImageDownloadOptions(),
with request: ImageDownloadRequest,
completion: @escaping ((Result<UIImage, Error>) -> Void)
) -> Cancellable? {
let url = request.url
let options = request.options
let urlRequest = imageCDN.urlRequest(forImageUrl: url, resize: options.resize)
let cachingKey = imageCDN.cachingKey(forImageUrl: url)

Expand Down Expand Up @@ -99,16 +100,20 @@ open class NukeImageLoader: ImageLoading {
}

open func downloadMultipleImages(
from urlsAndOptions: [(url: URL, options: ImageDownloadOptions)],
with requests: [ImageDownloadRequest],
completion: @escaping (([Result<UIImage, Error>]) -> Void)
) {
let group = DispatchGroup()
var results: [Result<UIImage, Error>] = []

for (url, downloadOptions) in urlsAndOptions {
for request in requests {
let url = request.url
let downloadOptions = request.options

group.enter()

downloadImage(from: url, with: downloadOptions) { result in
let request = ImageDownloadRequest(url: url, options: downloadOptions)
downloadImage(with: request) { result in
results.append(result)

group.leave()
Expand Down
18 changes: 16 additions & 2 deletions StreamChat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,8 @@
AD87D0A1263C7823008B466C /* AttachmentButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD87D0A0263C7823008B466C /* AttachmentButton.swift */; };
AD87D0AB263C7A7E008B466C /* ShrinkInputButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD87D0AA263C7A7E008B466C /* ShrinkInputButton.swift */; };
AD87D0BD263C7C09008B466C /* CircularCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD87D0BC263C7C09008B466C /* CircularCloseButton.swift */; };
AD8B72752908016400921C31 /* ImageDownloadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8B72742908016400921C31 /* ImageDownloadRequest.swift */; };
AD8B72762908016400921C31 /* ImageDownloadRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8B72742908016400921C31 /* ImageDownloadRequest.swift */; };
AD8D1809268F7290004E3A5C /* TypingSuggester.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8D1808268F7290004E3A5C /* TypingSuggester.swift */; };
AD8D180B268F8ED4004E3A5C /* SlackComposerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD8D180A268F8ED4004E3A5C /* SlackComposerVC.swift */; };
AD90D18525D56196001D03BB /* CurrentUserUpdater_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD90D18425D56196001D03BB /* CurrentUserUpdater_Tests.swift */; };
Expand Down Expand Up @@ -3281,6 +3283,7 @@
AD87D0A0263C7823008B466C /* AttachmentButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentButton.swift; sourceTree = "<group>"; };
AD87D0AA263C7A7E008B466C /* ShrinkInputButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShrinkInputButton.swift; sourceTree = "<group>"; };
AD87D0BC263C7C09008B466C /* CircularCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularCloseButton.swift; sourceTree = "<group>"; };
AD8B72742908016400921C31 /* ImageDownloadRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDownloadRequest.swift; sourceTree = "<group>"; };
AD8D1808268F7290004E3A5C /* TypingSuggester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypingSuggester.swift; sourceTree = "<group>"; };
AD8D180A268F8ED4004E3A5C /* SlackComposerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlackComposerVC.swift; sourceTree = "<group>"; };
AD90D18425D56196001D03BB /* CurrentUserUpdater_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrentUserUpdater_Tests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6751,13 +6754,13 @@
ACCA772826C40C7A007AE2ED /* ImageLoading */ = {
isa = PBXGroup;
children = (
AD8B7277290801B800921C31 /* ImageCDN */,
ACCA772926C40C96007AE2ED /* ImageLoading.swift */,
ACCA772B26C40D43007AE2ED /* NukeImageLoader.swift */,
AD552E0028F46CE700199A6F /* ImageLoaderOptions.swift */,
AD95FD1028FA038900DBDF41 /* ImageDownloadOptions.swift */,
AD8B72742908016400921C31 /* ImageDownloadRequest.swift */,
AD95FD0C28F991ED00DBDF41 /* ImageResize.swift */,
BDC80CB4265CF4B800F62CE2 /* ImageCDN.swift */,
ADD2A99928FF4F4B00A83305 /* StreamCDN.swift */,
ACD502A826BC0C670029FB7D /* ImageMerger.swift */,
ADD2A98F28FF0CD300A83305 /* ImageSizeCalculator.swift */,
AD7BBFCA2901AF3F004E8B76 /* ImageResultsMapper.swift */,
Expand Down Expand Up @@ -6959,6 +6962,15 @@
path = ShrinkInputButton;
sourceTree = "<group>";
};
AD8B7277290801B800921C31 /* ImageCDN */ = {
isa = PBXGroup;
children = (
ADD2A99928FF4F4B00A83305 /* StreamCDN.swift */,
BDC80CB4265CF4B800F62CE2 /* ImageCDN.swift */,
);
path = ImageCDN;
sourceTree = "<group>";
};
AD8E6BBD2642DB520013E01E /* CommandLabelView */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -8687,6 +8699,7 @@
88BD82B02549D18F00369074 /* ChatChannelListItemView.swift in Sources */,
A3C5022B284F9CF70048753E /* Token.swift in Sources */,
A39A8AE7263825F4003453D9 /* ChatMessageLayoutOptionsResolver.swift in Sources */,
AD8B72752908016400921C31 /* ImageDownloadRequest.swift in Sources */,
8825334C258CE82500B77352 /* AlertsRouter.swift in Sources */,
AD95FD0D28F991ED00DBDF41 /* ImageResize.swift in Sources */,
AD4474FD263B19F90030E583 /* ImageAttachmentComposerPreview.swift in Sources */,
Expand Down Expand Up @@ -10246,6 +10259,7 @@
C121EBA32746A1E800023E4C /* QuotedChatMessageView.swift in Sources */,
C121EBA42746A1E800023E4C /* QuotedChatMessageView+SwiftUI.swift in Sources */,
C121EBA52746A1E800023E4C /* OnlineIndicatorView.swift in Sources */,
AD8B72762908016400921C31 /* ImageDownloadRequest.swift in Sources */,
AD78F9FE28EC735700BC0FCE /* Token.swift in Sources */,
C121EBA62746A1E800023E4C /* ChatPresenceAvatarView.swift in Sources */,
C121EBA72746A1E800023E4C /* ChatAvatarView.swift in Sources */,
Expand Down
19 changes: 11 additions & 8 deletions Tests/StreamChatUITests/Mocks/ImageLoader_Mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ import UIKit

/// A mock implementation of the image loader which loads images synchronusly
final class ImageLoader_Mock: ImageLoading {
func downloadImage(from url: URL, with options: ImageDownloadOptions, completion: @escaping ((Result<UIImage, Error>) -> Void)) -> Cancellable? {
let image = UIImage(data: try! Data(contentsOf: url))!
completion(.success(image))
return nil
}

func loadImage(into imageView: UIImageView, from url: URL?, with options: ImageLoaderOptions, completion: ((Result<UIImage, Error>) -> Void)?) -> Cancellable? {
if let url = url {
let image = UIImage(data: try! Data(contentsOf: url))!
Expand All @@ -24,12 +18,21 @@ final class ImageLoader_Mock: ImageLoading {

return nil
}

func downloadImage(
with request: ImageDownloadRequest,
completion: @escaping ((Result<UIImage, Error>) -> Void)
) -> Cancellable? {
let image = UIImage(data: try! Data(contentsOf: request.url))!
completion(.success(image))
return nil
}

func downloadMultipleImages(
from urlsAndOptions: [(url: URL, options: ImageDownloadOptions)],
with requests: [ImageDownloadRequest],
completion: @escaping (([Result<UIImage, Error>]) -> Void)
) {
let results = urlsAndOptions.map(\.0).map {
let results = requests.map(\.url).map {
Result<UIImage, Error>.success(UIImage(data: try! Data(contentsOf: $0))!)
}
completion(results)
Expand Down

0 comments on commit 1802b0b

Please sign in to comment.