Skip to content

Commit

Permalink
Don't flicker avatar-image in MainTabBar (IOS-238) (#1299)
Browse files Browse the repository at this point in the history
Every time, the account gets updated, the `avatarURL` is updated as
well. But not only new image was set. Before setting the image, the
button-image was reset to the placeholder first. After that, the
downloaded image was applied.

Now we don't reset the image to the placeholder, but set the placeholder
once.
Also: Cleanup.
  • Loading branch information
kimar authored May 22, 2024
2 parents 5317e8c + 47bedff commit 068da20
Show file tree
Hide file tree
Showing 14 changed files with 41 additions and 104 deletions.
4 changes: 1 addition & 3 deletions Mastodon/Scene/Account/AccountListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,7 @@ extension AccountListViewModel {
guard let account = authentication.account() else { return }

// avatar
cell.avatarButton.avatarImageView.configure(
configuration: .init(url: account.avatarImageURL())
)
cell.avatarButton.avatarImageView.configure(with: account.avatarImageURL())

// name
do {
Expand Down
2 changes: 1 addition & 1 deletion Mastodon/Scene/Account/Cell/AccountListTableViewCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class AccountListTableViewCell: UITableViewCell {
private var _disposeBag = Set<AnyCancellable>()
var disposeBag = Set<AnyCancellable>()

let avatarButton = CircleAvatarButton(frame: .zero)
let avatarButton = CircleAvatarButton()
let nameLabel = MetaLabel(style: .accountListName)
let usernameLabel = MetaLabel(style: .accountListUsername)
let badgeButton = BadgeButton()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,7 @@ extension ProfileCardView.ViewModel {
private func bindUser(view: ProfileCardView) {
$authorAvatarImageURL
.sink { url in
view.avatarButton.avatarImageView.configure(
configuration: .init(
url: url,
placeholder: .placeholder(color: .systemGray3)
)
)
view.avatarButton.avatarImageView.configure(with: url)
view.avatarButton.avatarImageView.configure(
cornerConfiguration: .init(corner: .fixed(radius: 12))
)
Expand Down
2 changes: 1 addition & 1 deletion Mastodon/Scene/Discovery/ForYou/ProfileCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public final class ProfileCardView: UIView, AXCustomContentProvider {

// avatar
public let avatarButtonBackgroundView = UIView()
public let avatarButton = AvatarButton()
public let avatarButton = AvatarButton(avatarPlaceholder: .placeholder(color: .systemGray3))

// author name
public let authorNameLabel = MetaLabel(style: .profileCardName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ extension NotificationView {
let author = notification.account

// author avatar
let configuration = AvatarImageView.Configuration(url: author.avatarImageURL())
avatarButton.avatarImageView.configure(configuration: configuration)
avatarButton.avatarImageView.configure(with: author.avatarImageURL())
avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12)))

// author name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,8 @@ extension ProfileHeaderView.ViewModel {
viewDidAppear
)
.sink { avatarImageURL, avatarImageEditing, isEditing, _ in
view.avatarButton.avatarImageView.configure(configuration: .init(
url: (!isEditing || avatarImageEditing == nil) ? avatarImageURL : nil,
placeholder: isEditing ? (avatarImageEditing ?? AvatarImageView.placeholder) : AvatarImageView.placeholder
))
view.avatarButton.avatarImageView.image = avatarImageEditing
view.avatarButton.avatarImageView.configure(with: (!isEditing || avatarImageEditing == nil) ? avatarImageURL : nil)
}
.store(in: &disposeBag)
// blur for blocking & blockingBy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ extension AutoCompleteSection {
}
cell.subtitleLabel.text = "@" + account.acct
cell.avatarImageView.isHidden = false
cell.avatarImageView.configure(configuration: .init(url: URL(string: account.avatar)))
cell.avatarImageView.configure(with: URL(string: account.avatar))
}

private static func configureEmoji(cell: AutoCompleteTableViewCell, emoji: Mastodon.Entity.Emoji, isFirst: Bool) {
Expand All @@ -88,7 +88,7 @@ extension AutoCompleteSection {
// cell.subtitleLabel.text = isFirst ? L10n.Scene.Compose.AutoComplete.spaceToAdd : " "
cell.subtitleLabel.text = " "
cell.avatarImageView.isHidden = false
cell.avatarImageView.configure(configuration: .init(url: URL(string: emoji.url)))
cell.avatarImageView.configure(with: URL(string: emoji.url))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,14 @@ extension FLAnimatedImageView {

public func setImage(
url: URL?,
placeholder: UIImage? = nil,
scaleToSize: CGSize? = nil
) {
// cancel task
cancelTask()

// set placeholder
image = placeholder

// set image
guard let url = url else { return }
guard let url else { return }

activeAvatarRequestURL = url
let avatarRequest = AF.request(url).publishData()
avatarRequestCancellable = avatarRequest
Expand Down
18 changes: 6 additions & 12 deletions MastodonSDK/Sources/MastodonUI/View/Button/AvatarButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,9 @@ open class AvatarButton: UIControl {
public var size = CGSize(width: 46, height: 46)
public let avatarImageView = AvatarImageView()

public override init(frame: CGRect) {
super.init(frame: frame)
_init()
}

public required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}

open func _init() {
public init(avatarPlaceholder: UIImage? = UIImage.placeholder(color: .systemFill)) {
super.init(frame: .zero)
avatarImageView.image = avatarPlaceholder
avatarImageView.frame = bounds
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(avatarImageView)
Expand All @@ -37,7 +29,9 @@ open class AvatarButton: UIControl {
accessibilityLabel = L10n.Common.Controls.Status.showUserProfile
accessibilityTraits.insert(.image)
}


public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented")}

public override func layoutSubviews() {
super.layoutSubviews()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ public final class CircleAvatarButton: AvatarButton {

public var borderColor: UIColor = UIColor.systemFill
public var borderWidth: CGFloat = 2.0

public init() {
super.init(avatarPlaceholder: .placeholder(color: .systemFill))
}

public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented")}

public override func updateAppearance() {
super.updateAppearance()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,13 @@ extension FamiliarFollowersDashboardView.ViewModel {
let borderWidth = min(1.5, UIFontMetrics.default.scaledValue(for: 1))

for (i, avatarURL) in avatarURLs.enumerated() {
let avatarButton = AvatarButton()
let avatarButton = AvatarButton(avatarPlaceholder: .placeholder(color: .systemGray3))
let origin = CGPoint(x: offset * CGFloat(i), y: 0)
let size = CGSize(width: dimension, height: dimension)
avatarButton.size = size
avatarButton.frame = CGRect(origin: origin, size: size)
view.avatarContainerView.addSubview(avatarButton)
avatarButton.avatarImageView.configure(
configuration: .init(
url: avatarURL,
placeholder: .placeholder(color: .systemGray3)
)
)
avatarButton.avatarImageView.configure(with: avatarURL)
avatarButton.avatarImageView.configure(
cornerConfiguration: .init(
corner: .fixed(radius: 7),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,19 +241,9 @@ extension StatusView.ViewModel {
private func bindAuthor(statusView: StatusView) {
let authorView = statusView.authorView
// avatar
Publishers.CombineLatest(
$authorAvatarImage.removeDuplicates(),
$authorAvatarImageURL.removeDuplicates()
)
.sink { image, url in
let configuration: AvatarImageView.Configuration = {
if let image = image {
return AvatarImageView.Configuration(image: image)
} else {
return AvatarImageView.Configuration(url: url)
}
}()
authorView.avatarButton.avatarImageView.configure(configuration: configuration)
$authorAvatarImageURL.removeDuplicates()
.sink { url in
authorView.avatarButton.avatarImageView.configure(with: url)
authorView.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 12)))
}
.store(in: &disposeBag)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ extension UserView {
public var disposeBag = Set<AnyCancellable>()
public var observations = Set<NSKeyValueObservation>()

@Published public var authorAvatarImage: UIImage?
@Published public var authorAvatarImageURL: URL?
@Published public var authorName: MetaContent?
@Published public var authorUsername: String?
Expand All @@ -36,22 +35,13 @@ extension UserView.ViewModel {

func bind(userView: UserView) {
// avatar
Publishers.CombineLatest(
$authorAvatarImage,
$authorAvatarImageURL
)
.sink { image, url in
let configuration: AvatarImageView.Configuration = {
if let image = image {
return AvatarImageView.Configuration(image: image)
} else {
return AvatarImageView.Configuration(url: url)
}
}()
userView.avatarButton.avatarImageView.configure(configuration: configuration)
userView.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 7)))
}
.store(in: &disposeBag)
$authorAvatarImageURL
.sink { url in
userView.avatarButton.avatarImageView.configure(with: url)
userView.avatarButton.avatarImageView.configure(cornerConfiguration: .init(corner: .fixed(radius: 7)))
}
.store(in: &disposeBag)

// name
$authorName
.sink { metaContent in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import AlamofireImage

public class AvatarImageView: FLAnimatedImageView {
public var imageViewSize: CGSize?
public var configuration = Configuration(url: nil)
public var url: URL? = nil
public var cornerConfiguration = CornerConfiguration()
}

Expand Down Expand Up @@ -55,41 +55,17 @@ extension AvatarImageView {

public static let placeholder = UIImage.placeholder(color: .systemFill)

public struct Configuration {
public let url: URL?
public let placeholder: UIImage?

public init(
url: URL?,
placeholder: UIImage = AvatarImageView.placeholder
) {
self.url = url
self.placeholder = placeholder
}

public init(
image: UIImage
) {
self.url = nil
self.placeholder = image
}
}

public func configure(configuration: Configuration) {
public func configure(with url: URL?) {
prepareForReuse()

self.configuration = configuration

guard let url = configuration.url else {
image = configuration.placeholder
return
}
self.url = url

guard let url else { return }

switch url.pathExtension.lowercased() {
case "gif":
setImage(
url: configuration.url,
placeholder: configuration.placeholder,
url: url,
scaleToSize: imageViewSize
)
default:
Expand All @@ -105,7 +81,6 @@ extension AvatarImageView {

af.setImage(
withURL: url,
placeholderImage: configuration.placeholder,
filter: filter
)
}
Expand Down

0 comments on commit 068da20

Please sign in to comment.