From 50515382d0fb795bf6984570b5c5ec11ac9c7d21 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Wed, 11 Oct 2023 18:13:01 -0500 Subject: [PATCH] Accurately displays dynamically-sized screenshots in AppViewController --- AltStore.xcodeproj/project.pbxproj | 16 ++ .../App Detail/AppContentViewController.swift | 80 +++------- .../AppScreenshotCollectionViewCell.swift | 127 +++++++++++++++ .../AppScreenshotsViewController.swift | 145 ++++++++++++++++++ AltStore/Base.lproj/Main.storyboard | 106 +++++++------ AltStoreCore/Model/AppScreenshot.swift | 5 + 6 files changed, 374 insertions(+), 105 deletions(-) create mode 100644 AltStore/App Detail/Screenshots/AppScreenshotCollectionViewCell.swift create mode 100644 AltStore/App Detail/Screenshots/AppScreenshotsViewController.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index a0c8431b..88c28ca6 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -367,6 +367,7 @@ D54058B92A1D6269008CCC58 /* AppPermissionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54058B82A1D6269008CCC58 /* AppPermissionProtocol.swift */; }; D54058BB2A1D8FE3008CCC58 /* UIColor+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54058BA2A1D8FE3008CCC58 /* UIColor+AltStore.swift */; }; D540E93828EE1BDE000F1B0F /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D540E93728EE1BDE000F1B0F /* ErrorDetailsViewController.swift */; }; + D5418F172AD740890014ABD6 /* AppScreenshotCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5418F162AD740890014ABD6 /* AppScreenshotCollectionViewCell.swift */; }; D54DED1428CBC44B008B27A0 /* ErrorLogTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */; }; D552B1D82A042A740066216F /* AppPermissionsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D552B1D72A042A740066216F /* AppPermissionsCard.swift */; }; D55467B82A8D5E2600F4CE90 /* AppShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55467B12A8D5E2600F4CE90 /* AppShortcuts.swift */; }; @@ -436,6 +437,7 @@ D5F5AF2E28FDD2EC00C938F5 /* TestErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF2D28FDD2EC00C938F5 /* TestErrors.swift */; }; D5F5AF7D28ECEA990067C736 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */; }; D5F9821D2AB900060045751F /* AppScreenshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F9821C2AB900060045751F /* AppScreenshot.swift */; }; + D5F982212AB910180045751F /* AppScreenshotsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F982202AB910180045751F /* AppScreenshotsViewController.swift */; }; D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */; }; D5F99A1A28D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */; }; D5FB7A0E2AA25A4E00EF863D /* Previews.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D5FB7A0D2AA25A4E00EF863D /* Previews.xcassets */; }; @@ -973,6 +975,7 @@ D54058B82A1D6269008CCC58 /* AppPermissionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermissionProtocol.swift; sourceTree = ""; }; D54058BA2A1D8FE3008CCC58 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = ""; }; D540E93728EE1BDE000F1B0F /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = ""; }; + D5418F162AD740890014ABD6 /* AppScreenshotCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreenshotCollectionViewCell.swift; sourceTree = ""; }; D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogTableViewCell.swift; sourceTree = ""; }; D552B1D72A042A740066216F /* AppPermissionsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermissionsCard.swift; sourceTree = ""; }; D55467B12A8D5E2600F4CE90 /* AppShortcuts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppShortcuts.swift; sourceTree = ""; }; @@ -1037,6 +1040,7 @@ D5F5AF2D28FDD2EC00C938F5 /* TestErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestErrors.swift; sourceTree = ""; }; D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = ""; }; D5F9821C2AB900060045751F /* AppScreenshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreenshot.swift; sourceTree = ""; }; + D5F982202AB910180045751F /* AppScreenshotsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreenshotsViewController.swift; sourceTree = ""; }; D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore10ToAltStore11.xcmappingmodel; sourceTree = ""; }; D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp10ToStoreApp11Policy.swift; sourceTree = ""; }; D5FB7A0D2AA25A4E00EF863D /* Previews.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Previews.xcassets; sourceTree = ""; }; @@ -1250,6 +1254,7 @@ BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */, D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */, D552B1D72A042A740066216F /* AppPermissionsCard.swift */, + D5418F152AD740750014ABD6 /* Screenshots */, ); path = "App Detail"; sourceTree = ""; @@ -2122,6 +2127,15 @@ path = Previews; sourceTree = ""; }; + D5418F152AD740750014ABD6 /* Screenshots */ = { + isa = PBXGroup; + children = ( + D5F982202AB910180045751F /* AppScreenshotsViewController.swift */, + D5418F162AD740890014ABD6 /* AppScreenshotCollectionViewCell.swift */, + ); + path = Screenshots; + sourceTree = ""; + }; D55467B02A8D5E2600F4CE90 /* App Intents */ = { isa = PBXGroup; children = ( @@ -3125,6 +3139,7 @@ BF3BEFC124086A1E00DE7D55 /* RefreshAppOperation.swift in Sources */, BFE60740231AFD2A002B0E8E /* InsetGroupTableViewCell.swift in Sources */, BF0DCA662433BDF500E3A595 /* AnalyticsManager.swift in Sources */, + D5418F172AD740890014ABD6 /* AppScreenshotCollectionViewCell.swift in Sources */, BFCCB51A245E3401001853EA /* VerifyAppOperation.swift in Sources */, BFF0B6982322CAB8007A79E1 /* InstructionsViewController.swift in Sources */, BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */, @@ -3202,6 +3217,7 @@ BFB3645A2325985F00CD0EB1 /* FindServerOperation.swift in Sources */, D52EF2BE2A0594550096C377 /* AppDetailCollectionViewController.swift in Sources */, BF3BEFBF2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift in Sources */, + D5F982212AB910180045751F /* AppScreenshotsViewController.swift in Sources */, BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */, BFBE0004250ACFFB0080826E /* ViewApp.intentdefinition in Sources */, BF770E5622BC3C03002A40FE /* Server.swift in Sources */, diff --git a/AltStore/App Detail/AppContentViewController.swift b/AltStore/App Detail/AppContentViewController.swift index 01affae1..058bcb85 100644 --- a/AltStore/App Detail/AppContentViewController.swift +++ b/AltStore/App Detail/AppContentViewController.swift @@ -29,8 +29,6 @@ class AppContentViewController: UITableViewController { var app: StoreApp! - private lazy var screenshotsDataSource = self.makeScreenshotsDataSource() - private lazy var dateFormatter: DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium @@ -50,32 +48,17 @@ class AppContentViewController: UITableViewController @IBOutlet private var versionDateLabel: UILabel! @IBOutlet private var sizeLabel: UILabel! - @IBOutlet private var screenshotsCollectionView: UICollectionView! + @IBOutlet private(set) var appScreenshotsViewController: AppScreenshotsViewController! + @IBOutlet private var appScreenshotsHeightConstraint: NSLayoutConstraint! @IBOutlet private(set) var appDetailCollectionViewController: AppDetailCollectionViewController! @IBOutlet private var appDetailCollectionViewHeightConstraint: NSLayoutConstraint! - var preferredScreenshotSize: CGSize? { - let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout - - let aspectRatio: CGFloat = 16.0 / 9.0 // Hardcoded for now. - - let width = self.screenshotsCollectionView.bounds.width - (layout.minimumInteritemSpacing * 2) - - let itemWidth = width / 1.5 - let itemHeight = itemWidth * aspectRatio - - return CGSize(width: itemWidth, height: itemHeight) - } - override func viewDidLoad() { super.viewDidLoad() self.tableView.contentInset.bottom = 20 - - self.screenshotsCollectionView.dataSource = self.screenshotsDataSource - self.screenshotsCollectionView.prefetchDataSource = self.screenshotsDataSource self.subtitleLabel.text = self.app.subtitle self.descriptionTextView.text = self.app.localizedDescription @@ -106,17 +89,24 @@ class AppContentViewController: UITableViewController { super.viewDidLayoutSubviews() - guard var size = self.preferredScreenshotSize else { return } - size.height = min(size.height, self.screenshotsCollectionView.bounds.height) // Silence temporary "item too tall" warning. + var needsTableViewUpdate = false - let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout - layout.itemSize = size + let screenshotsHeight = self.appScreenshotsViewController.collectionView.contentSize.height + if self.appScreenshotsHeightConstraint.constant != screenshotsHeight && screenshotsHeight > 0 + { + self.appScreenshotsHeightConstraint.constant = screenshotsHeight + needsTableViewUpdate = true + } let permissionsHeight = self.appDetailCollectionViewController.collectionView.contentSize.height if self.appDetailCollectionViewHeightConstraint.constant != permissionsHeight && permissionsHeight > 0 { self.appDetailCollectionViewHeightConstraint.constant = permissionsHeight - + needsTableViewUpdate = true + } + + if needsTableViewUpdate + { UIView.performWithoutAnimation { // Update row height without animation. self.tableView.beginUpdates() @@ -128,40 +118,12 @@ class AppContentViewController: UITableViewController private extension AppContentViewController { - func makeScreenshotsDataSource() -> RSTArrayCollectionViewPrefetchingDataSource + @IBSegueAction + func makeAppScreenshotsViewController(_ coder: NSCoder, sender: Any?) -> UIViewController? { - let dataSource = RSTArrayCollectionViewPrefetchingDataSource(items: self.app.screenshotURLs as [NSURL]) - dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in - let cell = cell as! ScreenshotCollectionViewCell - cell.imageView.image = nil - cell.imageView.isIndicatingActivity = true - } - dataSource.prefetchHandler = { (imageURL, indexPath, completionHandler) in - return RSTAsyncBlockOperation() { (operation) in - let request = ImageRequest(url: imageURL as URL, processors: [.screenshot]) - ImagePipeline.shared.loadImage(with: request, progress: nil) { result in - guard !operation.isCancelled else { return operation.finish() } - - switch result - { - case .success(let response): completionHandler(response.image, nil) - case .failure(let error): completionHandler(nil, error) - } - } - } - } - dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in - let cell = cell as! ScreenshotCollectionViewCell - cell.imageView.isIndicatingActivity = false - cell.imageView.image = image - - if let error = error - { - print("Error loading image:", error) - } - } - - return dataSource + let appScreenshotsViewController = AppScreenshotsViewController(app: self.app, coder: coder) + self.appScreenshotsViewController = appScreenshotsViewController + return appScreenshotsViewController } @IBSegueAction @@ -205,8 +167,8 @@ extension AppContentViewController switch Row.allCases[indexPath.row] { case .screenshots: - guard let size = self.preferredScreenshotSize else { return 0.0 } - return size.height + guard !self.app.screenshots.isEmpty else { return 0.0 } + return UITableView.automaticDimension case .permissions: guard !self.app.permissions.isEmpty else { return 0.0 } diff --git a/AltStore/App Detail/Screenshots/AppScreenshotCollectionViewCell.swift b/AltStore/App Detail/Screenshots/AppScreenshotCollectionViewCell.swift new file mode 100644 index 00000000..426595b7 --- /dev/null +++ b/AltStore/App Detail/Screenshots/AppScreenshotCollectionViewCell.swift @@ -0,0 +1,127 @@ +// +// AppScreenshotCollectionViewCell.swift +// AltStore +// +// Created by Riley Testut on 10/11/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import UIKit + +import AltStoreCore + +class AppScreenshotCollectionViewCell: UICollectionViewCell +{ + let imageView: UIImageView + + var aspectRatio: CGSize = AppScreenshot.defaultAspectRatio { + didSet { + self.updateAspectRatio() + } + } + + private var isRounded: Bool = false { + didSet { + self.setNeedsLayout() + self.layoutIfNeeded() + } + } + + private var aspectRatioConstraint: NSLayoutConstraint? + + override init(frame: CGRect) + { + self.imageView = UIImageView(frame: .zero) + self.imageView.clipsToBounds = true + self.imageView.layer.cornerCurve = .continuous + self.imageView.layer.borderColor = UIColor.tertiaryLabel.cgColor + + super.init(frame: frame) + + self.imageView.translatesAutoresizingMaskIntoConstraints = false + self.contentView.addSubview(self.imageView) + + let widthConstraint = self.imageView.widthAnchor.constraint(equalTo: self.contentView.widthAnchor) + widthConstraint.priority = UILayoutPriority(999) + + let heightConstraint = self.imageView.heightAnchor.constraint(equalTo: self.contentView.heightAnchor) + heightConstraint.priority = UILayoutPriority(999) + + NSLayoutConstraint.activate([ + widthConstraint, + heightConstraint, + self.imageView.widthAnchor.constraint(lessThanOrEqualTo: self.contentView.widthAnchor), + self.imageView.heightAnchor.constraint(lessThanOrEqualTo: self.contentView.heightAnchor), + self.imageView.centerXAnchor.constraint(equalTo: self.contentView.centerXAnchor), + self.imageView.centerYAnchor.constraint(equalTo: self.contentView.centerYAnchor) + ]) + + self.updateAspectRatio() + self.updateTraits() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) + { + super.traitCollectionDidChange(previousTraitCollection) + + self.updateTraits() + } + + override func layoutSubviews() + { + super.layoutSubviews() + + guard self.imageView.bounds.width != 0 else { + self.setNeedsLayout() + return + } + + if self.isRounded + { + let cornerRadius = self.imageView.bounds.width / 9.0 // Based on iPhone 15 + self.imageView.layer.cornerRadius = cornerRadius + } + else + { + let cornerRadius = self.imageView.bounds.width / 25.0 // Based on iPhone 8 + self.imageView.layer.cornerRadius = cornerRadius + } + } +} + +private extension AppScreenshotCollectionViewCell +{ + func updateAspectRatio() + { + self.aspectRatioConstraint?.isActive = false + + self.aspectRatioConstraint = self.imageView.widthAnchor.constraint(equalTo: self.imageView.heightAnchor, multiplier: self.aspectRatio.width / self.aspectRatio.height) + self.aspectRatioConstraint?.isActive = true + + let aspectRatio: Double + if self.aspectRatio.width > self.aspectRatio.height + { + aspectRatio = self.aspectRatio.height / self.aspectRatio.width + } + else + { + aspectRatio = self.aspectRatio.width / self.aspectRatio.height + } + + let tolerance = 0.001 as Double + let modernAspectRatio = AppScreenshot.defaultAspectRatio.width / AppScreenshot.defaultAspectRatio.height + + let isRounded = (aspectRatio >= modernAspectRatio - tolerance) && (aspectRatio <= modernAspectRatio + tolerance) + self.isRounded = isRounded + } + + func updateTraits() + { + let displayScale = (self.traitCollection.displayScale == 0.0) ? 1.0 : self.traitCollection.displayScale + self.imageView.layer.borderWidth = 1.0 / displayScale + } +} diff --git a/AltStore/App Detail/Screenshots/AppScreenshotsViewController.swift b/AltStore/App Detail/Screenshots/AppScreenshotsViewController.swift new file mode 100644 index 00000000..99c2c950 --- /dev/null +++ b/AltStore/App Detail/Screenshots/AppScreenshotsViewController.swift @@ -0,0 +1,145 @@ +// +// AppScreenshotsViewController.swift +// AltStore +// +// Created by Riley Testut on 9/18/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import UIKit + +import AltStoreCore +import Roxas + +import Nuke + +class AppScreenshotsViewController: UICollectionViewController +{ + let app: StoreApp + + private lazy var dataSource = self.makeDataSource() + + init?(app: StoreApp, coder: NSCoder) + { + self.app = app + + super.init(coder: coder) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() + { + super.viewDidLoad() + + self.collectionView.showsHorizontalScrollIndicator = false + + // Allow parent background color to show through. + self.collectionView.backgroundColor = nil + + // Match the parent table view margins. + self.collectionView.directionalLayoutMargins.top = 0 + self.collectionView.directionalLayoutMargins.bottom = 0 + self.collectionView.directionalLayoutMargins.leading = 20 + self.collectionView.directionalLayoutMargins.trailing = 20 + + let collectionViewLayout = self.makeLayout() + self.collectionView.collectionViewLayout = collectionViewLayout + + self.collectionView.register(AppScreenshotCollectionViewCell.self, forCellWithReuseIdentifier: RSTCellContentGenericCellIdentifier) + + self.collectionView.dataSource = self.dataSource + self.collectionView.prefetchDataSource = self.dataSource + } +} + +private extension AppScreenshotsViewController +{ + func makeLayout() -> UICollectionViewCompositionalLayout + { + let layoutConfig = UICollectionViewCompositionalLayoutConfiguration() + layoutConfig.contentInsetsReference = .layoutMargins + + let preferredHeight = 400.0 + let estimatedWidth = preferredHeight * (AppScreenshot.defaultAspectRatio.width / AppScreenshot.defaultAspectRatio.height) + + let layout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in + + let itemSize = NSCollectionLayoutSize(widthDimension: .estimated(estimatedWidth), heightDimension: .fractionalHeight(1.0)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let groupSize = NSCollectionLayoutSize(widthDimension: .estimated(estimatedWidth), heightDimension: .absolute(preferredHeight)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) + + let layoutSection = NSCollectionLayoutSection(group: group) + layoutSection.interGroupSpacing = 10 + layoutSection.orthogonalScrollingBehavior = .groupPaging + + return layoutSection + }, configuration: layoutConfig) + + return layout + } + + func makeDataSource() -> RSTArrayCollectionViewPrefetchingDataSource + { + let dataSource = RSTArrayCollectionViewPrefetchingDataSource(items: self.app.screenshots) + dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in + let cell = cell as! AppScreenshotCollectionViewCell + cell.imageView.image = nil + cell.imageView.isIndicatingActivity = true + + var aspectRatio = screenshot.size ?? AppScreenshot.defaultAspectRatio + if aspectRatio.width > aspectRatio.height + { + aspectRatio = CGSize(width: aspectRatio.height, height: aspectRatio.width) + } + + cell.aspectRatio = aspectRatio + } + dataSource.prefetchHandler = { (screenshot, indexPath, completionHandler) in + let imageURL = screenshot.imageURL + return RSTAsyncBlockOperation() { (operation) in + let request = ImageRequest(url: imageURL, processors: [.screenshot]) + ImagePipeline.shared.loadImage(with: request, progress: nil) { result in + guard !operation.isCancelled else { return operation.finish() } + + switch result + { + case .success(let response): completionHandler(response.image, nil) + case .failure(let error): completionHandler(nil, error) + } + } + } + } + dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in + let cell = cell as! AppScreenshotCollectionViewCell + cell.imageView.isIndicatingActivity = false + cell.imageView.image = image + + if let error = error + { + print("Error loading image:", error) + } + } + + return dataSource + } +} + +@available(iOS 17, *) +#Preview(traits: .portrait) { + DatabaseManager.shared.startForPreview() + + let fetchRequest = StoreApp.fetchRequest() + let storeApp = try! DatabaseManager.shared.viewContext.fetch(fetchRequest).first! + + let storyboard = UIStoryboard(name: "Main", bundle: .main) + let appViewConttroller = storyboard.instantiateViewController(withIdentifier: "appViewController") as! AppViewController + appViewConttroller.app = storeApp + + let navigationController = UINavigationController(rootViewController: appViewConttroller) + return navigationController +} diff --git a/AltStore/Base.lproj/Main.storyboard b/AltStore/Base.lproj/Main.storyboard index c79fe870..73f82020 100644 --- a/AltStore/Base.lproj/Main.storyboard +++ b/AltStore/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -238,7 +238,7 @@ - + @@ -262,48 +262,37 @@ - + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - + + + + - + - - + + @@ -324,29 +313,29 @@ - + - + - + - + - + - + - + @@ -393,7 +382,7 @@ - + @@ -447,8 +436,8 @@ + - @@ -460,6 +449,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -478,7 +492,7 @@ - + diff --git a/AltStoreCore/Model/AppScreenshot.swift b/AltStoreCore/Model/AppScreenshot.swift index 5f5ad149..f2a2be0b 100644 --- a/AltStoreCore/Model/AppScreenshot.swift +++ b/AltStoreCore/Model/AppScreenshot.swift @@ -8,6 +8,11 @@ import CoreData +public extension AppScreenshot +{ + static let defaultAspectRatio = CGSize(width: 9, height: 19.5) +} + @objc(AppScreenshot) public class AppScreenshot: NSManagedObject, Fetchable, Decodable {