Skip to content

Commit

Permalink
MMMLoadableImageView: separate 'syncing' and 'failed' placeholders
Browse files Browse the repository at this point in the history
  • Loading branch information
aleh committed Sep 2, 2024
1 parent 6f84dcf commit 7d8e10d
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 14 deletions.
2 changes: 1 addition & 1 deletion MMMCommonUI.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
45 changes: 32 additions & 13 deletions Sources/MMMCommonUI/MMMLoadableImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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()

Expand Down Expand Up @@ -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)
}
}
}

Expand Down

0 comments on commit 7d8e10d

Please sign in to comment.