fix: image single image misaligned issue in timeline
This commit is contained in:
parent
45748735c4
commit
fd764dcf33
|
@ -251,7 +251,7 @@ extension StatusSection {
|
|||
default: return 0.7
|
||||
}
|
||||
}()
|
||||
return CGSize(width: maxWidth, height: maxWidth * scale)
|
||||
return CGSize(width: maxWidth, height: floor(maxWidth * scale))
|
||||
}()
|
||||
let mosaics: [MosaicImageViewContainer.ConfigurableMosaic] = {
|
||||
if mosaicImageViewModel.metas.count == 1 {
|
||||
|
@ -268,13 +268,16 @@ extension StatusSection {
|
|||
let blurhashOverlayImageView = mosaic.blurhashOverlayImageView
|
||||
let meta = mosaicImageViewModel.metas[i]
|
||||
|
||||
// set blurhash image
|
||||
meta.blurhashImagePublisher()
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { image in
|
||||
blurhashOverlayImageView.image = image
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
|
||||
let isSingleMosaicLayout = mosaics.count == 1
|
||||
|
||||
// set image
|
||||
let imageSize = CGSize(
|
||||
width: mosaic.imageViewSize.width * imageView.traitCollection.displayScale,
|
||||
height: mosaic.imageViewSize.height * imageView.traitCollection.displayScale
|
||||
|
@ -282,12 +285,18 @@ extension StatusSection {
|
|||
let request = ImageRequest(
|
||||
url: meta.url,
|
||||
processors: [
|
||||
ImageProcessors.Resize(size: imageSize, contentMode: .aspectFill)
|
||||
ImageProcessors.Resize(
|
||||
size: imageSize,
|
||||
unit: .pixels,
|
||||
contentMode: isSingleMosaicLayout ? .aspectFill : .aspectFit,
|
||||
crop: isSingleMosaicLayout
|
||||
)
|
||||
]
|
||||
)
|
||||
let options = ImageLoadingOptions(
|
||||
transition: .fadeIn(duration: 0.2)
|
||||
)
|
||||
|
||||
Nuke.loadImage(
|
||||
with: request,
|
||||
options: options,
|
||||
|
@ -296,28 +305,17 @@ extension StatusSection {
|
|||
switch result {
|
||||
case .failure:
|
||||
break
|
||||
case .success:
|
||||
case .success(let response)
|
||||
statusItemAttribute.isImageLoaded.value = true
|
||||
}
|
||||
}
|
||||
//imageView.af.setImage(
|
||||
// withURL: meta.url,
|
||||
// placeholderImage: UIImage.placeholder(color: .systemFill),
|
||||
// imageTransition: .crossDissolve(0.2)
|
||||
//) { response in
|
||||
// switch response.result {
|
||||
// case .success:
|
||||
// statusItemAttribute.isImageLoaded.value = true
|
||||
// case .failure:
|
||||
// break
|
||||
// }
|
||||
//}
|
||||
|
||||
imageView.accessibilityLabel = meta.altText
|
||||
Publishers.CombineLatest(
|
||||
statusItemAttribute.isImageLoaded,
|
||||
statusItemAttribute.isRevealing
|
||||
)
|
||||
.receive(on: RunLoop.main)
|
||||
.receive(on: DispatchQueue.main) // needs call immediately
|
||||
.sink { [weak cell] isImageLoaded, isMediaRevealing in
|
||||
guard let cell = cell else { return }
|
||||
guard isImageLoaded else {
|
||||
|
|
|
@ -104,7 +104,7 @@ extension MosaicImageViewContainer {
|
|||
imageViews = []
|
||||
blurhashOverlayImageViews = []
|
||||
|
||||
container.spacing = 1
|
||||
container.spacing = UIView.separatorLineHeight(of: self) * 2 // 2px
|
||||
}
|
||||
|
||||
struct ConfigurableMosaic {
|
||||
|
@ -120,12 +120,16 @@ extension MosaicImageViewContainer {
|
|||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.addArrangedSubview(contentView)
|
||||
|
||||
let rect = AVMakeRect(
|
||||
aspectRatio: aspectRatio,
|
||||
insideRect: CGRect(origin: .zero, size: maxSize)
|
||||
)
|
||||
let imageViewSize: CGSize = {
|
||||
let rect = AVMakeRect(
|
||||
aspectRatio: aspectRatio,
|
||||
insideRect: CGRect(origin: .zero, size: maxSize)
|
||||
).integral
|
||||
return rect.size
|
||||
}()
|
||||
let imageViewFrame = CGRect(origin: .zero, size: imageViewSize)
|
||||
|
||||
let imageView = UIImageView()
|
||||
let imageView = UIImageView(frame: imageViewFrame)
|
||||
imageViews.append(imageView)
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = ContentWarningOverlayView.cornerRadius
|
||||
|
@ -138,9 +142,9 @@ extension MosaicImageViewContainer {
|
|||
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
|
||||
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
|
||||
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
||||
imageView.widthAnchor.constraint(equalToConstant: floor(rect.width)).priority(.required - 1),
|
||||
imageView.widthAnchor.constraint(equalToConstant: imageViewFrame.width).priority(.required - 1),
|
||||
])
|
||||
containerHeightLayoutConstraint.constant = floor(rect.height)
|
||||
containerHeightLayoutConstraint.constant = imageViewFrame.height
|
||||
containerHeightLayoutConstraint.isActive = true
|
||||
|
||||
let blurhashOverlayImageView = UIImageView()
|
||||
|
@ -170,7 +174,7 @@ extension MosaicImageViewContainer {
|
|||
return ConfigurableMosaic(
|
||||
imageView: imageView,
|
||||
blurhashOverlayImageView: blurhashOverlayImageView,
|
||||
imageViewSize: maxSize
|
||||
imageViewSize: imageViewSize
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -181,6 +185,7 @@ extension MosaicImageViewContainer {
|
|||
}
|
||||
|
||||
let maxHeight = maxSize.height
|
||||
let spacing: CGFloat = 1
|
||||
|
||||
containerHeightLayoutConstraint.constant = maxHeight
|
||||
containerHeightLayoutConstraint.isActive = true
|
||||
|
@ -190,7 +195,7 @@ extension MosaicImageViewContainer {
|
|||
[contentLeftStackView, contentRightStackView].forEach { stackView in
|
||||
stackView.axis = .vertical
|
||||
stackView.distribution = .fillEqually
|
||||
stackView.spacing = 1
|
||||
stackView.spacing = spacing
|
||||
}
|
||||
container.addArrangedSubview(contentLeftStackView)
|
||||
container.addArrangedSubview(contentRightStackView)
|
||||
|
@ -310,22 +315,23 @@ extension MosaicImageViewContainer {
|
|||
let imageViewSize: CGSize = {
|
||||
switch (i, count) {
|
||||
case (_, 4):
|
||||
return CGSize(width: maxSize.width * 0.5, height: maxSize.height * 0.5)
|
||||
return CGSize(width: maxSize.width * 0.5 - spacing, height: maxSize.height * 0.5 - spacing)
|
||||
case (i, 3):
|
||||
let width = maxSize.width * 0.5
|
||||
let width = maxSize.width * 0.5 - spacing
|
||||
if i == 0 {
|
||||
return CGSize(width: width, height: maxSize.height)
|
||||
} else {
|
||||
return CGSize(width: width, height: maxSize.height * 0.5)
|
||||
return CGSize(width: width, height: maxSize.height * 0.5 - spacing)
|
||||
}
|
||||
case (_, 2):
|
||||
let width = maxSize.width * 0.5
|
||||
let width = maxSize.width * 0.5 - spacing
|
||||
return CGSize(width: width, height: maxSize.height)
|
||||
default:
|
||||
assertionFailure()
|
||||
return maxSize
|
||||
}
|
||||
}()
|
||||
imageView.frame.size = imageViewSize
|
||||
let mosaic = ConfigurableMosaic(
|
||||
imageView: imageView,
|
||||
blurhashOverlayImageView: blurhashOverlayImageView,
|
||||
|
|
|
@ -15,22 +15,25 @@ final class BlurhashImageCacheService {
|
|||
let workingQueue = DispatchQueue(label: "org.joinmastodon.app.BlurhashImageCacheService.working-queue", qos: .userInitiated, attributes: .concurrent)
|
||||
|
||||
func image(blurhash: String, size: CGSize, url: URL) -> AnyPublisher<UIImage?, Never> {
|
||||
let key = Key(blurhash: blurhash, size: size, url: url)
|
||||
|
||||
if let image = self.cache.object(forKey: key) {
|
||||
return Just(image).eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return Future { promise in
|
||||
self.workingQueue.async {
|
||||
let key = Key(blurhash: blurhash, size: size, url: url)
|
||||
guard let image = self.cache.object(forKey: key) else {
|
||||
if let image = BlurhashImageCacheService.blurhashImage(blurhash: blurhash, size: size, url: url) {
|
||||
self.cache.setObject(image, forKey: key)
|
||||
promise(.success(image))
|
||||
} else {
|
||||
promise(.success(nil))
|
||||
}
|
||||
guard let image = BlurhashImageCacheService.blurhashImage(blurhash: blurhash, size: size, url: url) else {
|
||||
promise(.success(nil))
|
||||
return
|
||||
}
|
||||
self.cache.setObject(image, forKey: key)
|
||||
promise(.success(image))
|
||||
}
|
||||
}
|
||||
.receive(on: RunLoop.main)
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
}
|
||||
|
||||
static func blurhashImage(blurhash: String, size: CGSize, url: URL) -> UIImage? {
|
||||
|
@ -55,13 +58,7 @@ final class BlurhashImageCacheService {
|
|||
}
|
||||
|
||||
extension BlurhashImageCacheService {
|
||||
class Key: Hashable {
|
||||
static func == (lhs: BlurhashImageCacheService.Key, rhs: BlurhashImageCacheService.Key) -> Bool {
|
||||
return lhs.blurhash == rhs.blurhash
|
||||
&& lhs.size == rhs.size
|
||||
&& lhs.url == rhs.url
|
||||
}
|
||||
|
||||
class Key: NSObject {
|
||||
let blurhash: String
|
||||
let size: CGSize
|
||||
let url: URL
|
||||
|
@ -72,11 +69,19 @@ extension BlurhashImageCacheService {
|
|||
self.url = url
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(blurhash)
|
||||
hasher.combine(size.width)
|
||||
hasher.combine(size.height)
|
||||
hasher.combine(url)
|
||||
override func isEqual(_ object: Any?) -> Bool {
|
||||
guard let object = object as? Key else { return false }
|
||||
return object.blurhash == blurhash
|
||||
&& object.size == size
|
||||
&& object.url == url
|
||||
}
|
||||
|
||||
override var hash: Int {
|
||||
return blurhash.hashValue ^
|
||||
size.width.hashValue ^
|
||||
size.height.hashValue ^
|
||||
url.hashValue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ final class PlaceholderImageCacheService {
|
|||
}
|
||||
|
||||
extension PlaceholderImageCacheService {
|
||||
class Key: Hashable {
|
||||
class Key: NSObject {
|
||||
let color: UIColor
|
||||
let size: CGSize
|
||||
let cornerRadius: CGFloat
|
||||
|
@ -44,17 +44,18 @@ extension PlaceholderImageCacheService {
|
|||
self.cornerRadius = cornerRadius
|
||||
}
|
||||
|
||||
static func == (lhs: PlaceholderImageCacheService.Key, rhs: PlaceholderImageCacheService.Key) -> Bool {
|
||||
return lhs.color == rhs.color
|
||||
&& lhs.size == rhs.size
|
||||
&& lhs.cornerRadius == rhs.cornerRadius
|
||||
override func isEqual(_ object: Any?) -> Bool {
|
||||
guard let object = object as? Key else { return false }
|
||||
return object.color == color
|
||||
&& object.size == size
|
||||
&& object.cornerRadius == cornerRadius
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(color)
|
||||
hasher.combine(size.width)
|
||||
hasher.combine(size.height)
|
||||
hasher.combine(cornerRadius)
|
||||
override var hash: Int {
|
||||
return color.hashValue ^
|
||||
size.width.hashValue ^
|
||||
size.height.hashValue ^
|
||||
cornerRadius.hashValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue