diff --git a/MMMCommonUI.podspec b/MMMCommonUI.podspec index 1c1f845..d318106 100644 --- a/MMMCommonUI.podspec +++ b/MMMCommonUI.podspec @@ -6,7 +6,7 @@ Pod::Spec.new do |s| s.name = "MMMCommonUI" - s.version = "3.12.2" + s.version = "3.13.0" s.summary = "Small UI-related pieces reused in many components from MMMTemple" s.description = "#{s.summary}." s.homepage = "https://github.com/mediamonks/#{s.name}" diff --git a/Sources/MMMCommonUI/MMMLoadableImageView.swift b/Sources/MMMCommonUI/MMMLoadableImageView.swift index f5ad7e4..724c031 100644 --- a/Sources/MMMCommonUI/MMMLoadableImageView.swift +++ b/Sources/MMMCommonUI/MMMLoadableImageView.swift @@ -34,9 +34,6 @@ public final class MMMLoadableImageView: NonStoryboardableView { /// Note that in case of the `.fill` mode the bounds of this view might reside outside of the receiver's bounds. public var alignmentView: UIView { imageView } - // No reason to open this for changes at any time. - private let placeholderImage: UIImage? - private var imageObserver: MMMLoadableObserver? /// A loadable image this image view should display and track. Just set and forget. @@ -58,13 +55,30 @@ public final class MMMLoadableImageView: NonStoryboardableView { } } - // MARK: Init - - // TODO: visually distinguish between 'loading' and 'failed to load' states, e.g. by using two placeholders or possibly using "shimmer"-kind animation. - // TODO: this is where effects like shadows and corners can be added as well. - public init(placeholderImage: UIImage? = nil, mode: Mode = .fit) { + /// A version of the initializer allowing one placeholder for both cases for compatibility + /// with the existing code. + /// + /// - Parameters: + /// - placeholderImage: An image to use while `image` has no contents or is not set. + public convenience init(placeholderImage: UIImage? = nil, mode: Mode = .fit) { + self.init(syncingPlaceholder: placeholderImage, failedPlaceholder: placeholderImage, mode: mode) + } - self.placeholderImage = placeholderImage + private let syncingPlaceholder: UIImage? + private let failedPlaceholder: UIImage? + + /// - Parameters: + /// - syncingPlaceholder: A placeholder to use when `image` has no contents (or is not set) + /// and is either `.idle` or `.syncing`. + /// - failedPlaceholder: An placeholder to use when `image` has failed loading. + public init( + syncingPlaceholder: UIImage? = nil, + failedPlaceholder: UIImage? = nil, + mode: Mode = .fit + ) { + + self.syncingPlaceholder = syncingPlaceholder + self.failedPlaceholder = failedPlaceholder super.init() @@ -123,16 +137,21 @@ public final class MMMLoadableImageView: NonStoryboardableView { private func update(animated: Bool) { guard let loadableImage = image else { - imageView.image = placeholderImage + imageView.image = syncingPlaceholder return } if loadableImage.isContentsAvailable { updateImage(with: loadableImage.image, animated: animated) } else { - assert(loadableImage.loadableState == .syncing || loadableImage.loadableState == .didFailToSync) - // TODO: currently we don't distinguish between loading and failed here, but would be better to so. - updateImage(with: placeholderImage) + switch loadableImage.loadableState { + case .idle, .syncing: + updateImage(with: syncingPlaceholder) + case .didFailToSync, .didSyncSuccessfully: + updateImage(with: failedPlaceholder) + // We expect `isContentsAvailable` to be true in this case. + assert(loadableImage.loadableState != .didSyncSuccessfully) + } } }