forked from zelo72/mastodon-ios
feat: using Nuke framework for timeline image loading
This commit is contained in:
parent
a413adc613
commit
f5e3ec7c76
|
@ -416,6 +416,7 @@
|
|||
DBAE3FA92617106E004B8251 /* MastodonMetricFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FA82617106E004B8251 /* MastodonMetricFormatter.swift */; };
|
||||
DBAE3FAF26172FC0004B8251 /* RemoteProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAE3FAE26172FC0004B8251 /* RemoteProfileViewModel.swift */; };
|
||||
DBAEDE5C267A058D00D25FF5 /* BlurhashImageCacheService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAEDE5B267A058D00D25FF5 /* BlurhashImageCacheService.swift */; };
|
||||
DBAEDE5F267A0B1500D25FF5 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = DBAEDE5E267A0B1500D25FF5 /* Nuke */; };
|
||||
DBAFB7352645463500371D5F /* Emojis.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBAFB7342645463500371D5F /* Emojis.swift */; };
|
||||
DBB525082611EAC0002F1F29 /* Tabman in Frameworks */ = {isa = PBXBuildFile; productRef = DBB525072611EAC0002F1F29 /* Tabman */; };
|
||||
DBB5250E2611EBAF002F1F29 /* ProfileSegmentedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBB5250D2611EBAF002F1F29 /* ProfileSegmentedViewController.swift */; };
|
||||
|
@ -781,7 +782,6 @@
|
|||
DB1FD45925F27898004CFCFC /* CategoryPickerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerItem.swift; sourceTree = "<group>"; };
|
||||
DB1FD45F25F278AF004CFCFC /* CategoryPickerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPickerSection.swift; sourceTree = "<group>"; };
|
||||
DB221B15260C395900AEFE46 /* CustomEmojiPickerInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiPickerInputViewModel.swift; sourceTree = "<group>"; };
|
||||
DB297B192679F5EF00704C90 /* ActiveLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ActiveLabel.swift; path = ../ActiveLabel.swift; sourceTree = "<group>"; };
|
||||
DB297B1A2679FAE200704C90 /* PlaceholderImageCacheService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderImageCacheService.swift; sourceTree = "<group>"; };
|
||||
DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = "<group>"; };
|
||||
|
@ -1050,6 +1050,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DBAEDE5F267A0B1500D25FF5 /* Nuke in Frameworks */,
|
||||
DB6F5E32264E7410009108F4 /* TwitterTextEditor in Frameworks */,
|
||||
DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */,
|
||||
DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */,
|
||||
|
@ -1689,7 +1690,6 @@
|
|||
children = (
|
||||
DBF53F5F25C14E88008AAC7B /* Mastodon.xctestplan */,
|
||||
DBF53F6025C14E9D008AAC7B /* MastodonSDK.xctestplan */,
|
||||
DB297B192679F5EF00704C90 /* ActiveLabel.swift */,
|
||||
DB3D0FED25BAA42200EAA174 /* MastodonSDK */,
|
||||
DB427DD425BAA00100D1B89D /* Mastodon */,
|
||||
DB427DEB25BAA00100D1B89D /* MastodonTests */,
|
||||
|
@ -2505,6 +2505,7 @@
|
|||
DB9A487D2603456B008B817C /* UITextView+Placeholder */,
|
||||
DBB525072611EAC0002F1F29 /* Tabman */,
|
||||
DB6F5E31264E7410009108F4 /* TwitterTextEditor */,
|
||||
DBAEDE5E267A0B1500D25FF5 /* Nuke */,
|
||||
);
|
||||
productName = Mastodon;
|
||||
productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */;
|
||||
|
@ -2694,6 +2695,7 @@
|
|||
DBF8AE842632992700C9C23C /* XCRemoteSwiftPackageReference "Base85" */,
|
||||
DB6804722637CC1200430867 /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
||||
DB6F5E30264E7410009108F4 /* XCRemoteSwiftPackageReference "TwitterTextEditor" */,
|
||||
DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */,
|
||||
);
|
||||
productRefGroup = DB427DD325BAA00100D1B89D /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -4037,7 +4039,7 @@
|
|||
repositoryURL = "https://github.com/TwidereProject/ActiveLabel.swift";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 5.0.2;
|
||||
version = 5.0.3;
|
||||
};
|
||||
};
|
||||
2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */ = {
|
||||
|
@ -4112,6 +4114,14 @@
|
|||
minimumVersion = 1.4.1;
|
||||
};
|
||||
};
|
||||
DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kean/Nuke.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 10.3.0;
|
||||
};
|
||||
};
|
||||
DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/uias/Tabman";
|
||||
|
@ -4195,6 +4205,11 @@
|
|||
package = DB9A487C2603456B008B817C /* XCRemoteSwiftPackageReference "UITextView-Placeholder" */;
|
||||
productName = "UITextView+Placeholder";
|
||||
};
|
||||
DBAEDE5E267A0B1500D25FF5 /* Nuke */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DBAEDE5D267A0B1500D25FF5 /* XCRemoteSwiftPackageReference "Nuke" */;
|
||||
productName = Nuke;
|
||||
};
|
||||
DBB525072611EAC0002F1F29 /* Tabman */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = DBB525062611EAC0002F1F29 /* XCRemoteSwiftPackageReference "Tabman" */;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
"repositoryURL": "https://github.com/TwidereProject/ActiveLabel.swift",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "2132a7bf8da2bea74bb49b0b815950ea4d8f6a7e",
|
||||
"version": "5.0.2"
|
||||
"revision": "d503eb3bfabc54a70139618ab2ba09ebb8c09672",
|
||||
"version": "5.0.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -73,6 +73,15 @@
|
|||
"version": "6.2.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Nuke",
|
||||
"repositoryURL": "https://github.com/kean/Nuke.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "69ae6d5b8c4b898450432f94bd35f863d3830cfc",
|
||||
"version": "10.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "Pageboy",
|
||||
"repositoryURL": "https://github.com/uias/Pageboy",
|
||||
|
|
|
@ -11,6 +11,7 @@ import CoreDataStack
|
|||
import os.log
|
||||
import UIKit
|
||||
import AVKit
|
||||
import Nuke
|
||||
|
||||
protocol StatusCell: DisposeBagCollectable {
|
||||
var statusView: StatusView { get }
|
||||
|
@ -258,12 +259,13 @@ extension StatusSection {
|
|||
let mosaic = cell.statusView.statusMosaicImageViewContainer.setupImageView(aspectRatio: meta.size, maxSize: imageViewMaxSize)
|
||||
return [mosaic]
|
||||
} else {
|
||||
let mosaics = cell.statusView.statusMosaicImageViewContainer.setupImageViews(count: mosaicImageViewModel.metas.count, maxHeight: imageViewMaxSize.height)
|
||||
let mosaics = cell.statusView.statusMosaicImageViewContainer.setupImageViews(count: mosaicImageViewModel.metas.count, maxSize: imageViewMaxSize)
|
||||
return mosaics
|
||||
}
|
||||
}()
|
||||
for (i, mosaic) in mosaics.enumerated() {
|
||||
let (imageView, blurhashOverlayImageView) = mosaic
|
||||
let imageView = mosaic.imageView
|
||||
let blurhashOverlayImageView = mosaic.blurhashOverlayImageView
|
||||
let meta = mosaicImageViewModel.metas[i]
|
||||
|
||||
meta.blurhashImagePublisher()
|
||||
|
@ -272,18 +274,44 @@ extension StatusSection {
|
|||
blurhashOverlayImageView.image = image
|
||||
}
|
||||
.store(in: &cell.disposeBag)
|
||||
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
|
||||
|
||||
let imageSize = CGSize(
|
||||
width: mosaic.imageViewSize.width * imageView.traitCollection.displayScale,
|
||||
height: mosaic.imageViewSize.height * imageView.traitCollection.displayScale
|
||||
)
|
||||
let request = ImageRequest(
|
||||
url: meta.url,
|
||||
processors: [
|
||||
ImageProcessors.Resize(size: imageSize, contentMode: .aspectFill)
|
||||
]
|
||||
)
|
||||
let options = ImageLoadingOptions(
|
||||
transition: .fadeIn(duration: 0.2)
|
||||
)
|
||||
Nuke.loadImage(
|
||||
with: request,
|
||||
options: options,
|
||||
into: imageView
|
||||
) { result in
|
||||
switch result {
|
||||
case .failure:
|
||||
break
|
||||
case .success:
|
||||
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,
|
||||
|
|
|
@ -107,7 +107,11 @@ extension MosaicImageViewContainer {
|
|||
container.spacing = 1
|
||||
}
|
||||
|
||||
typealias ConfigurableMosaic = (imageView: UIImageView, blurhashOverlayImageView: UIImageView)
|
||||
struct ConfigurableMosaic {
|
||||
let imageView: UIImageView
|
||||
let blurhashOverlayImageView: UIImageView
|
||||
let imageViewSize: CGSize
|
||||
}
|
||||
|
||||
func setupImageView(aspectRatio: CGSize, maxSize: CGSize) -> ConfigurableMosaic {
|
||||
reset()
|
||||
|
@ -163,15 +167,21 @@ extension MosaicImageViewContainer {
|
|||
contentWarningOverlayView.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
|
||||
])
|
||||
|
||||
return (imageView, blurhashOverlayImageView)
|
||||
return ConfigurableMosaic(
|
||||
imageView: imageView,
|
||||
blurhashOverlayImageView: blurhashOverlayImageView,
|
||||
imageViewSize: maxSize
|
||||
)
|
||||
}
|
||||
|
||||
func setupImageViews(count: Int, maxHeight: CGFloat) -> [ConfigurableMosaic] {
|
||||
func setupImageViews(count: Int, maxSize: CGSize) -> [ConfigurableMosaic] {
|
||||
reset()
|
||||
guard count > 1 else {
|
||||
return []
|
||||
}
|
||||
|
||||
let maxHeight = maxSize.height
|
||||
|
||||
containerHeightLayoutConstraint.constant = maxHeight
|
||||
containerHeightLayoutConstraint.isActive = true
|
||||
|
||||
|
@ -295,7 +305,35 @@ extension MosaicImageViewContainer {
|
|||
contentWarningOverlayView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
||||
])
|
||||
|
||||
return zip(imageViews, blurhashOverlayImageViews).map { ($0, $1) }
|
||||
var mosaics: [ConfigurableMosaic] = []
|
||||
for (i, (imageView, blurhashOverlayImageView)) in zip(imageViews, blurhashOverlayImageViews).enumerated() {
|
||||
let imageViewSize: CGSize = {
|
||||
switch (i, count) {
|
||||
case (_, 4):
|
||||
return CGSize(width: maxSize.width * 0.5, height: maxSize.height * 0.5)
|
||||
case (i, 3):
|
||||
let width = maxSize.width * 0.5
|
||||
if i == 0 {
|
||||
return CGSize(width: width, height: maxSize.height)
|
||||
} else {
|
||||
return CGSize(width: width, height: maxSize.height * 0.5)
|
||||
}
|
||||
case (_, 2):
|
||||
let width = maxSize.width * 0.5
|
||||
return CGSize(width: width, height: maxSize.height)
|
||||
default:
|
||||
assertionFailure()
|
||||
return maxSize
|
||||
}
|
||||
}()
|
||||
let mosaic = ConfigurableMosaic(
|
||||
imageView: imageView,
|
||||
blurhashOverlayImageView: blurhashOverlayImageView,
|
||||
imageViewSize: imageViewSize
|
||||
)
|
||||
mosaics.append(mosaic)
|
||||
}
|
||||
return mosaics
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -366,11 +404,11 @@ struct MosaicImageView_Previews: PreviewProvider {
|
|||
UIViewPreview(width: 375) {
|
||||
let view = MosaicImageViewContainer()
|
||||
let image = images[3]
|
||||
let (imageView, _) = view.setupImageView(
|
||||
let mosaic = view.setupImageView(
|
||||
aspectRatio: image.size,
|
||||
maxSize: CGSize(width: 375, height: 400)
|
||||
)
|
||||
imageView.image = image
|
||||
mosaic.imageView.image = image
|
||||
return view
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 400))
|
||||
|
@ -378,14 +416,14 @@ struct MosaicImageView_Previews: PreviewProvider {
|
|||
UIViewPreview(width: 375) {
|
||||
let view = MosaicImageViewContainer()
|
||||
let image = images[1]
|
||||
let (imageView, _) = view.setupImageView(
|
||||
let mosaic = view.setupImageView(
|
||||
aspectRatio: image.size,
|
||||
maxSize: CGSize(width: 375, height: 400)
|
||||
)
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.cornerRadius = 8
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
imageView.image = image
|
||||
mosaic.imageView.layer.masksToBounds = true
|
||||
mosaic.imageView.layer.cornerRadius = 8
|
||||
mosaic.imageView.contentMode = .scaleAspectFill
|
||||
mosaic.imageView.image = image
|
||||
return view
|
||||
}
|
||||
.previewLayout(.fixed(width: 375, height: 400))
|
||||
|
@ -393,10 +431,9 @@ struct MosaicImageView_Previews: PreviewProvider {
|
|||
UIViewPreview(width: 375) {
|
||||
let view = MosaicImageViewContainer()
|
||||
let images = self.images.prefix(2)
|
||||
let mosaics = view.setupImageViews(count: images.count, maxHeight: 162)
|
||||
for (i, mosiac) in mosaics.enumerated() {
|
||||
let (imageView, _) = mosiac
|
||||
imageView.image = images[i]
|
||||
let mosaics = view.setupImageViews(count: images.count, maxSize: CGSize(width: 375, height: 162))
|
||||
for (i, mosaic) in mosaics.enumerated() {
|
||||
mosaic.imageView.image = images[i]
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
@ -405,10 +442,9 @@ struct MosaicImageView_Previews: PreviewProvider {
|
|||
UIViewPreview(width: 375) {
|
||||
let view = MosaicImageViewContainer()
|
||||
let images = self.images.prefix(3)
|
||||
let mosaics = view.setupImageViews(count: images.count, maxHeight: 162)
|
||||
for (i, mosiac) in mosaics.enumerated() {
|
||||
let (imageView, _) = mosiac
|
||||
imageView.image = images[i]
|
||||
let mosaics = view.setupImageViews(count: images.count, maxSize: CGSize(width: 375, height: 162))
|
||||
for (i, mosaic) in mosaics.enumerated() {
|
||||
mosaic.imageView.image = images[i]
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
@ -417,10 +453,9 @@ struct MosaicImageView_Previews: PreviewProvider {
|
|||
UIViewPreview(width: 375) {
|
||||
let view = MosaicImageViewContainer()
|
||||
let images = self.images.prefix(4)
|
||||
let mosaics = view.setupImageViews(count: images.count, maxHeight: 162)
|
||||
for (i, mosiac) in mosaics.enumerated() {
|
||||
let (imageView, _) = mosiac
|
||||
imageView.image = images[i]
|
||||
let mosaics = view.setupImageViews(count: images.count, maxSize: CGSize(width: 375, height: 162))
|
||||
for (i, mosaic) in mosaics.enumerated() {
|
||||
mosaic.imageView.image = images[i]
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
|
|
@ -619,10 +619,9 @@ struct StatusView_Previews: PreviewProvider {
|
|||
)
|
||||
statusView.headerContainerView.isHidden = false
|
||||
let images = MosaicImageView_Previews.images
|
||||
let mosaics = statusView.statusMosaicImageViewContainer.setupImageViews(count: 4, maxHeight: 162)
|
||||
let mosaics = statusView.statusMosaicImageViewContainer.setupImageViews(count: 4, maxSize: CGSize(width: 375, height: 162))
|
||||
for (i, mosaic) in mosaics.enumerated() {
|
||||
let (imageView, _) = mosaic
|
||||
imageView.image = images[i]
|
||||
mosaic.imageView.image = images[i]
|
||||
}
|
||||
statusView.statusMosaicImageViewContainer.isHidden = false
|
||||
statusView.statusMosaicImageViewContainer.contentWarningOverlayView.isHidden = true
|
||||
|
@ -644,10 +643,9 @@ struct StatusView_Previews: PreviewProvider {
|
|||
statusView.updateContentWarningDisplay(isHidden: false, animated: false)
|
||||
statusView.drawContentWarningImageView()
|
||||
let images = MosaicImageView_Previews.images
|
||||
let mosaics = statusView.statusMosaicImageViewContainer.setupImageViews(count: 4, maxHeight: 162)
|
||||
let mosaics = statusView.statusMosaicImageViewContainer.setupImageViews(count: 4, maxSize: CGSize(width: 375, height: 162))
|
||||
for (i, mosaic) in mosaics.enumerated() {
|
||||
let (imageView, _) = mosaic
|
||||
imageView.image = images[i]
|
||||
mosaic.imageView.image = images[i]
|
||||
}
|
||||
statusView.statusMosaicImageViewContainer.isHidden = false
|
||||
return statusView
|
||||
|
|
Loading…
Reference in New Issue