From 0efa3c46949ed2dc1f77dbc1809509a83b633d00 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 30 Jul 2019 12:41:50 -0700 Subject: [PATCH] [AltStore] Refactors BrowseViewController screenshot size logic --- AltStore.xcodeproj/project.pbxproj | 4 + AltStore/App Detail/AppViewController.swift | 12 ++ AltStore/Base.lproj/Main.storyboard | 129 +----------------- .../Browse/BrowseCollectionViewCell.swift | 27 ++-- AltStore/Browse/BrowseCollectionViewCell.xib | 119 ++++++++++++++++ AltStore/Browse/BrowseViewController.swift | 88 ++++++++++-- .../Browse/ScreenshotCollectionViewCell.swift | 28 ++-- 7 files changed, 248 insertions(+), 159 deletions(-) create mode 100644 AltStore/Browse/BrowseCollectionViewCell.xib diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 01d935d8..6b4651ed 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -169,6 +169,7 @@ BFD52C2222A1A9EC000B7ED1 /* cnary.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD52C1F22A1A9EC000B7ED1 /* cnary.c */; }; BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD6B03222DFF20800B86064 /* MyAppsComponents.swift */; }; BFDB5B1622EE90D300F74113 /* Date+RelativeDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */; }; + BFDB5B2622EFBBEA00F74113 /* BrowseCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFDB5B2522EFBBEA00F74113 /* BrowseCollectionViewCell.xib */; }; BFDB69FD22A9A7B7007EA6D6 /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB69FC22A9A7B7007EA6D6 /* AccountViewController.swift */; }; BFDB6A0522A9AFB2007EA6D6 /* Fetchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0422A9AFB2007EA6D6 /* Fetchable.swift */; }; BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */; }; @@ -418,6 +419,7 @@ BFD52C1F22A1A9EC000B7ED1 /* cnary.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = cnary.c; path = Dependencies/libplist/libcnary/cnary.c; sourceTree = SOURCE_ROOT; }; BFD6B03222DFF20800B86064 /* MyAppsComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsComponents.swift; sourceTree = ""; }; BFDB5B1522EE90D300F74113 /* Date+RelativeDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+RelativeDate.swift"; sourceTree = ""; }; + BFDB5B2522EFBBEA00F74113 /* BrowseCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BrowseCollectionViewCell.xib; sourceTree = ""; }; BFDB69FC22A9A7B7007EA6D6 /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = ""; }; BFDB6A0422A9AFB2007EA6D6 /* Fetchable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fetchable.swift; sourceTree = ""; }; BFDB6A0722AAED73007EA6D6 /* ResignAppOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResignAppOperation.swift; sourceTree = ""; }; @@ -703,6 +705,7 @@ children = ( BF9ABA4422DCFF43008935CF /* BrowseViewController.swift */, BF9ABA4622DD0638008935CF /* BrowseCollectionViewCell.swift */, + BFDB5B2522EFBBEA00F74113 /* BrowseCollectionViewCell.xib */, BF9ABA4822DD0742008935CF /* ScreenshotCollectionViewCell.swift */, ); path = Browse; @@ -1124,6 +1127,7 @@ BF770E6922BD57DD002A40FE /* Silence.m4a in Resources */, BFD247772284B9A700981D42 /* Assets.xcassets in Resources */, BFD247752284B9A500981D42 /* Main.storyboard in Resources */, + BFDB5B2622EFBBEA00F74113 /* BrowseCollectionViewCell.xib in Resources */, BFE6325A22A83BEB00F30809 /* Authentication.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/AltStore/App Detail/AppViewController.swift b/AltStore/App Detail/AppViewController.swift index 2e0210b1..30e620b1 100644 --- a/AltStore/App Detail/AppViewController.swift +++ b/AltStore/App Detail/AppViewController.swift @@ -319,6 +319,18 @@ class AppViewController: UIViewController } } +extension AppViewController +{ + class func makeAppViewController(app: App) -> AppViewController + { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + + let appViewController = storyboard.instantiateViewController(withIdentifier: "appViewController") as! AppViewController + appViewController.app = app + return appViewController + } +} + private extension AppViewController { func update() diff --git a/AltStore/Base.lproj/Main.storyboard b/AltStore/Base.lproj/Main.storyboard index 9022cf07..a8f1be41 100644 --- a/AltStore/Base.lproj/Main.storyboard +++ b/AltStore/Base.lproj/Main.storyboard @@ -39,133 +39,12 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -180,7 +59,7 @@ - + @@ -1026,7 +905,7 @@ World - + diff --git a/AltStore/Browse/BrowseCollectionViewCell.swift b/AltStore/Browse/BrowseCollectionViewCell.swift index c1993a9a..ba22bcb6 100644 --- a/AltStore/Browse/BrowseCollectionViewCell.swift +++ b/AltStore/Browse/BrowseCollectionViewCell.swift @@ -18,22 +18,24 @@ import Roxas } } private lazy var dataSource = self.makeDataSource() - - private lazy var imageSizes = [NSString: CGSize]() - + @IBOutlet var nameLabel: UILabel! @IBOutlet var developerLabel: UILabel! @IBOutlet var appIconImageView: UIImageView! @IBOutlet var actionButton: PillButton! @IBOutlet var subtitleLabel: UILabel! + @IBOutlet var screenshotsCollectionView: UICollectionView! + @IBOutlet private var screenshotsContentView: UIView! - @IBOutlet private var screenshotsCollectionView: UICollectionView! override func awakeFromNib() { super.awakeFromNib() + // Must be registered programmatically, not in BrowseCollectionViewCell.xib, or else it'll throw an exception 🤷‍♂️. + self.screenshotsCollectionView.register(ScreenshotCollectionViewCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier) + self.screenshotsCollectionView.delegate = self self.screenshotsCollectionView.dataSource = self.dataSource self.screenshotsCollectionView.prefetchDataSource = self.dataSource @@ -59,6 +61,7 @@ private extension BrowseCollectionViewCell let dataSource = RSTArrayCollectionViewPrefetchingDataSource(items: []) dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in let cell = cell as! ScreenshotCollectionViewCell + cell.imageView.image = nil cell.imageView.isIndicatingActivity = true } dataSource.prefetchHandler = { (imageName, indexPath, completion) in @@ -87,15 +90,13 @@ extension BrowseCollectionViewCell: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let imageURL = self.dataSource.item(at: indexPath) - let dimensions = self.imageSizes[imageURL] ?? UIScreen.main.nativeBounds.size - - let aspectRatio = dimensions.width / dimensions.height - - let height = self.screenshotsCollectionView.bounds.height - let width = (self.screenshotsCollectionView.bounds.height * aspectRatio).rounded(.down) - - let size = CGSize(width: width, height: height) + // Assuming 9.0 / 16.0 ratio for now. + let aspectRatio: CGFloat = 9.0 / 16.0 + + let itemHeight = collectionView.bounds.height + let itemWidth = itemHeight * aspectRatio + + let size = CGSize(width: itemWidth.rounded(.down), height: itemHeight.rounded(.down)) return size } } diff --git a/AltStore/Browse/BrowseCollectionViewCell.xib b/AltStore/Browse/BrowseCollectionViewCell.xib new file mode 100644 index 00000000..f6ff5f38 --- /dev/null +++ b/AltStore/Browse/BrowseCollectionViewCell.xib @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AltStore/Browse/BrowseViewController.swift b/AltStore/Browse/BrowseViewController.swift index 7a53944d..29c871ea 100644 --- a/AltStore/Browse/BrowseViewController.swift +++ b/AltStore/Browse/BrowseViewController.swift @@ -14,12 +14,22 @@ class BrowseViewController: UICollectionViewController { private lazy var dataSource = self.makeDataSource() + private let prototypeCell = BrowseCollectionViewCell.instantiate(with: BrowseCollectionViewCell.nib!)! + + private var cachedItemSizes = [String: CGSize]() + override func viewDidLoad() { super.viewDidLoad() + self.prototypeCell.contentView.translatesAutoresizingMaskIntoConstraints = false + + self.collectionView.register(BrowseCollectionViewCell.nib, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier) + self.collectionView.dataSource = self.dataSource self.collectionView.prefetchDataSource = self.dataSource + + self.registerForPreviewing(with: self, sourceView: self.collectionView) } override func viewWillAppear(_ animated: Bool) @@ -29,14 +39,6 @@ class BrowseViewController: UICollectionViewController self.fetchApps() } - override func viewDidLayoutSubviews() - { - super.viewDidLayoutSubviews() - - let collectionViewLayout = self.collectionViewLayout as! UICollectionViewFlowLayout - collectionViewLayout.itemSize.width = self.view.bounds.width - } - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { guard segue.identifier == "showApp" else { return } @@ -166,3 +168,73 @@ private extension BrowseViewController UIApplication.shared.open(installedApp.openAppURL) } } + +extension BrowseViewController: UICollectionViewDelegateFlowLayout +{ + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize + { + let item = self.dataSource.item(at: indexPath) + + if let previousSize = self.cachedItemSizes[item.bundleIdentifier] + { + return previousSize + } + + let maxVisibleScreenshots = 2 as CGFloat + let aspectRatio: CGFloat = 16.0 / 9.0 + + let layout = collectionViewLayout as! UICollectionViewFlowLayout + let padding = (layout.minimumInteritemSpacing * (maxVisibleScreenshots - 1)) + + self.dataSource.cellConfigurationHandler(self.prototypeCell, item, indexPath) + + let widthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionView.bounds.width) + widthConstraint.isActive = true + defer { widthConstraint.isActive = false } + + self.prototypeCell.layoutIfNeeded() + + let collectionViewWidth = self.prototypeCell.screenshotsCollectionView.bounds.width + let screenshotWidth = ((collectionViewWidth - padding) / maxVisibleScreenshots).rounded(.down) + let screenshotHeight = screenshotWidth * aspectRatio + + let heightConstraint = self.prototypeCell.screenshotsCollectionView.heightAnchor.constraint(equalToConstant: screenshotHeight) + heightConstraint.isActive = true + defer { heightConstraint.isActive = false } + + let itemSize = self.prototypeCell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + self.cachedItemSizes[item.bundleIdentifier] = itemSize + return itemSize + } + + override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) + { + let app = self.dataSource.item(at: indexPath) + + let appViewController = AppViewController.makeAppViewController(app: app) + self.navigationController?.pushViewController(appViewController, animated: true) + } +} + +extension BrowseViewController: UIViewControllerPreviewingDelegate +{ + func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? + { + guard + let indexPath = self.collectionView.indexPathForItem(at: location), + let cell = self.collectionView.cellForItem(at: indexPath) + else { return nil } + + previewingContext.sourceRect = cell.frame + + let app = self.dataSource.item(at: indexPath) + + let appViewController = AppViewController.makeAppViewController(app: app) + return appViewController + } + + func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) + { + self.navigationController?.pushViewController(viewControllerToCommit, animated: true) + } +} diff --git a/AltStore/Browse/ScreenshotCollectionViewCell.swift b/AltStore/Browse/ScreenshotCollectionViewCell.swift index a0bb049c..107f4dc2 100644 --- a/AltStore/Browse/ScreenshotCollectionViewCell.swift +++ b/AltStore/Browse/ScreenshotCollectionViewCell.swift @@ -13,15 +13,25 @@ import Roxas @objc(ScreenshotCollectionViewCell) class ScreenshotCollectionViewCell: UICollectionViewCell { - let imageView: UIImageView + let imageView = UIImageView(image: nil) + + override init(frame: CGRect) + { + super.init(frame: frame) + + self.initialize() + } required init?(coder aDecoder: NSCoder) { - self.imageView = UIImageView(image: nil) - self.imageView.layer.masksToBounds = true - super.init(coder: aDecoder) + self.initialize() + } + + private func initialize() + { + self.imageView.layer.masksToBounds = true self.addSubview(self.imageView, pinningEdgesWith: .zero) } @@ -29,14 +39,6 @@ class ScreenshotCollectionViewCell: UICollectionViewCell { super.layoutSubviews() - if let image = self.imageView.image, (image.size.height / image.size.width) > ((16.0 / 9.0) + 0.1) - { - // Image aspect ratio is taller than 16:9, so assume it's an X-style screenshot and set corner radius. - self.imageView.layer.cornerRadius = max(self.imageView.bounds.width / 9.8, 8) - } - else - { - self.imageView.layer.cornerRadius = 0 - } + self.imageView.layer.cornerRadius = 4 } }