mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-19 11:43:24 +01:00
Accurately displays dynamically-sized screenshots in AppViewController
This commit is contained in:
@@ -368,6 +368,7 @@
|
|||||||
D54058B92A1D6269008CCC58 /* AppPermissionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54058B82A1D6269008CCC58 /* AppPermissionProtocol.swift */; };
|
D54058B92A1D6269008CCC58 /* AppPermissionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54058B82A1D6269008CCC58 /* AppPermissionProtocol.swift */; };
|
||||||
D54058BB2A1D8FE3008CCC58 /* UIColor+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54058BA2A1D8FE3008CCC58 /* UIColor+AltStore.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 */; };
|
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 */; };
|
D54DED1428CBC44B008B27A0 /* ErrorLogTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */; };
|
||||||
D552B1D82A042A740066216F /* AppPermissionsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D552B1D72A042A740066216F /* AppPermissionsCard.swift */; };
|
D552B1D82A042A740066216F /* AppPermissionsCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D552B1D72A042A740066216F /* AppPermissionsCard.swift */; };
|
||||||
D55467B82A8D5E2600F4CE90 /* AppShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D55467B12A8D5E2600F4CE90 /* AppShortcuts.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 */; };
|
D5F5AF2E28FDD2EC00C938F5 /* TestErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF2D28FDD2EC00C938F5 /* TestErrors.swift */; };
|
||||||
D5F5AF7D28ECEA990067C736 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */; };
|
D5F5AF7D28ECEA990067C736 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */; };
|
||||||
D5F9821D2AB900060045751F /* AppScreenshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F9821C2AB900060045751F /* AppScreenshot.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 */; };
|
D5F99A1828D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */; };
|
||||||
D5F99A1A28D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */; };
|
D5F99A1A28D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */; };
|
||||||
D5FB28EC2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB28EB2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift */; };
|
D5FB28EC2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5FB28EB2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift */; };
|
||||||
@@ -1034,6 +1036,7 @@
|
|||||||
D54058B82A1D6269008CCC58 /* AppPermissionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermissionProtocol.swift; sourceTree = "<group>"; };
|
D54058B82A1D6269008CCC58 /* AppPermissionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermissionProtocol.swift; sourceTree = "<group>"; };
|
||||||
D54058BA2A1D8FE3008CCC58 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = "<group>"; };
|
D54058BA2A1D8FE3008CCC58 /* UIColor+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+AltStore.swift"; sourceTree = "<group>"; };
|
||||||
D540E93728EE1BDE000F1B0F /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
D540E93728EE1BDE000F1B0F /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
||||||
|
D5418F162AD740890014ABD6 /* AppScreenshotCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreenshotCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogTableViewCell.swift; sourceTree = "<group>"; };
|
D54DED1328CBC44B008B27A0 /* ErrorLogTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
D552B1D72A042A740066216F /* AppPermissionsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermissionsCard.swift; sourceTree = "<group>"; };
|
D552B1D72A042A740066216F /* AppPermissionsCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermissionsCard.swift; sourceTree = "<group>"; };
|
||||||
D55467B12A8D5E2600F4CE90 /* AppShortcuts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppShortcuts.swift; sourceTree = "<group>"; };
|
D55467B12A8D5E2600F4CE90 /* AppShortcuts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppShortcuts.swift; sourceTree = "<group>"; };
|
||||||
@@ -1098,6 +1101,7 @@
|
|||||||
D5F5AF2D28FDD2EC00C938F5 /* TestErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestErrors.swift; sourceTree = "<group>"; };
|
D5F5AF2D28FDD2EC00C938F5 /* TestErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestErrors.swift; sourceTree = "<group>"; };
|
||||||
D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
D5F5AF7C28ECEA990067C736 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
||||||
D5F9821C2AB900060045751F /* AppScreenshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreenshot.swift; sourceTree = "<group>"; };
|
D5F9821C2AB900060045751F /* AppScreenshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreenshot.swift; sourceTree = "<group>"; };
|
||||||
|
D5F982202AB910180045751F /* AppScreenshotsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScreenshotsViewController.swift; sourceTree = "<group>"; };
|
||||||
D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore10ToAltStore11.xcmappingmodel; sourceTree = "<group>"; };
|
D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore10ToAltStore11.xcmappingmodel; sourceTree = "<group>"; };
|
||||||
D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp10ToStoreApp11Policy.swift; sourceTree = "<group>"; };
|
D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp10ToStoreApp11Policy.swift; sourceTree = "<group>"; };
|
||||||
D5FB28EB2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFontDescriptor+Bold.swift"; sourceTree = "<group>"; };
|
D5FB28EB2ADDF68D00A1C337 /* UIFontDescriptor+Bold.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFontDescriptor+Bold.swift"; sourceTree = "<group>"; };
|
||||||
@@ -1406,6 +1410,7 @@
|
|||||||
BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */,
|
BF3D64AF22E8D4B800E9056B /* AppContentViewControllerCells.swift */,
|
||||||
D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */,
|
D52EF2BD2A0594550096C377 /* AppDetailCollectionViewController.swift */,
|
||||||
D552B1D72A042A740066216F /* AppPermissionsCard.swift */,
|
D552B1D72A042A740066216F /* AppPermissionsCard.swift */,
|
||||||
|
D5418F152AD740750014ABD6 /* Screenshots */,
|
||||||
);
|
);
|
||||||
path = "App Detail";
|
path = "App Detail";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2210,6 +2215,15 @@
|
|||||||
path = Previews;
|
path = Previews;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D5418F152AD740750014ABD6 /* Screenshots */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D5F982202AB910180045751F /* AppScreenshotsViewController.swift */,
|
||||||
|
D5418F162AD740890014ABD6 /* AppScreenshotCollectionViewCell.swift */,
|
||||||
|
);
|
||||||
|
path = Screenshots;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D55467B02A8D5E2600F4CE90 /* App Intents */ = {
|
D55467B02A8D5E2600F4CE90 /* App Intents */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -3186,6 +3200,7 @@
|
|||||||
BF3BEFC124086A1E00DE7D55 /* RefreshAppOperation.swift in Sources */,
|
BF3BEFC124086A1E00DE7D55 /* RefreshAppOperation.swift in Sources */,
|
||||||
BFE60740231AFD2A002B0E8E /* InsetGroupTableViewCell.swift in Sources */,
|
BFE60740231AFD2A002B0E8E /* InsetGroupTableViewCell.swift in Sources */,
|
||||||
BF0DCA662433BDF500E3A595 /* AnalyticsManager.swift in Sources */,
|
BF0DCA662433BDF500E3A595 /* AnalyticsManager.swift in Sources */,
|
||||||
|
D5418F172AD740890014ABD6 /* AppScreenshotCollectionViewCell.swift in Sources */,
|
||||||
BFCCB51A245E3401001853EA /* VerifyAppOperation.swift in Sources */,
|
BFCCB51A245E3401001853EA /* VerifyAppOperation.swift in Sources */,
|
||||||
BFF0B6982322CAB8007A79E1 /* InstructionsViewController.swift in Sources */,
|
BFF0B6982322CAB8007A79E1 /* InstructionsViewController.swift in Sources */,
|
||||||
BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */,
|
BF9ABA4522DCFF43008935CF /* BrowseViewController.swift in Sources */,
|
||||||
@@ -3277,6 +3292,7 @@
|
|||||||
BFB3645A2325985F00CD0EB1 /* FindServerOperation.swift in Sources */,
|
BFB3645A2325985F00CD0EB1 /* FindServerOperation.swift in Sources */,
|
||||||
D52EF2BE2A0594550096C377 /* AppDetailCollectionViewController.swift in Sources */,
|
D52EF2BE2A0594550096C377 /* AppDetailCollectionViewController.swift in Sources */,
|
||||||
BF3BEFBF2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift in Sources */,
|
BF3BEFBF2408673400DE7D55 /* FetchProvisioningProfilesOperation.swift in Sources */,
|
||||||
|
D5F982212AB910180045751F /* AppScreenshotsViewController.swift in Sources */,
|
||||||
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */,
|
BFF0B69023219C6D007A79E1 /* PatreonComponents.swift in Sources */,
|
||||||
BFBE0004250ACFFB0080826E /* ViewApp.intentdefinition in Sources */,
|
BFBE0004250ACFFB0080826E /* ViewApp.intentdefinition in Sources */,
|
||||||
BF56D2AF23DF9E310006506D /* AppIDsViewController.swift in Sources */,
|
BF56D2AF23DF9E310006506D /* AppIDsViewController.swift in Sources */,
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ final class AppContentViewController: UITableViewController
|
|||||||
var app: StoreApp!
|
var app: StoreApp!
|
||||||
|
|
||||||
private lazy var screenshotsDataSource = self.makeScreenshotsDataSource()
|
private lazy var screenshotsDataSource = self.makeScreenshotsDataSource()
|
||||||
|
private lazy var dateFormatter: DateFormatter = {
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateStyle = .medium
|
||||||
|
dateFormatter.timeStyle = .none
|
||||||
|
return dateFormatter
|
||||||
|
}()
|
||||||
|
|
||||||
private lazy var byteCountFormatter: ByteCountFormatter = {
|
private lazy var byteCountFormatter: ByteCountFormatter = {
|
||||||
let formatter = ByteCountFormatter()
|
let formatter = ByteCountFormatter()
|
||||||
@@ -43,33 +49,18 @@ final class AppContentViewController: UITableViewController
|
|||||||
@IBOutlet private var versionDateLabel: UILabel!
|
@IBOutlet private var versionDateLabel: UILabel!
|
||||||
@IBOutlet private var sizeLabel: 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(set) var appDetailCollectionViewController: AppDetailCollectionViewController!
|
||||||
@IBOutlet private var appDetailCollectionViewHeightConstraint: NSLayoutConstraint!
|
@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()
|
override func viewDidLoad()
|
||||||
{
|
{
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
self.tableView.contentInset.bottom = 20
|
self.tableView.contentInset.bottom = 20
|
||||||
|
|
||||||
self.screenshotsCollectionView.dataSource = self.screenshotsDataSource
|
|
||||||
self.screenshotsCollectionView.prefetchDataSource = self.screenshotsDataSource
|
|
||||||
|
|
||||||
self.subtitleLabel.text = self.app.subtitle
|
self.subtitleLabel.text = self.app.subtitle
|
||||||
self.descriptionTextView.text = self.app.localizedDescription
|
self.descriptionTextView.text = self.app.localizedDescription
|
||||||
|
|
||||||
@@ -99,17 +90,24 @@ final class AppContentViewController: UITableViewController
|
|||||||
{
|
{
|
||||||
super.viewDidLayoutSubviews()
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
guard var size = self.preferredScreenshotSize else { return }
|
var needsTableViewUpdate = false
|
||||||
size.height = min(size.height, self.screenshotsCollectionView.bounds.height) // Silence temporary "item too tall" warning.
|
|
||||||
|
|
||||||
let layout = self.screenshotsCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
|
let screenshotsHeight = self.appScreenshotsViewController.collectionView.contentSize.height
|
||||||
layout.itemSize = size
|
if self.appScreenshotsHeightConstraint.constant != screenshotsHeight && screenshotsHeight > 0
|
||||||
|
{
|
||||||
|
self.appScreenshotsHeightConstraint.constant = screenshotsHeight
|
||||||
|
needsTableViewUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
let permissionsHeight = self.appDetailCollectionViewController.collectionView.contentSize.height
|
let permissionsHeight = self.appDetailCollectionViewController.collectionView.contentSize.height
|
||||||
if self.appDetailCollectionViewHeightConstraint.constant != permissionsHeight && permissionsHeight > 0
|
if self.appDetailCollectionViewHeightConstraint.constant != permissionsHeight && permissionsHeight > 0
|
||||||
{
|
{
|
||||||
self.appDetailCollectionViewHeightConstraint.constant = permissionsHeight
|
self.appDetailCollectionViewHeightConstraint.constant = permissionsHeight
|
||||||
|
needsTableViewUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if needsTableViewUpdate
|
||||||
|
{
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation {
|
||||||
// Update row height without animation.
|
// Update row height without animation.
|
||||||
self.tableView.beginUpdates()
|
self.tableView.beginUpdates()
|
||||||
@@ -121,40 +119,12 @@ final class AppContentViewController: UITableViewController
|
|||||||
|
|
||||||
private extension AppContentViewController
|
private extension AppContentViewController
|
||||||
{
|
{
|
||||||
func makeScreenshotsDataSource() -> RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>
|
@IBSegueAction
|
||||||
|
func makeAppScreenshotsViewController(_ coder: NSCoder, sender: Any?) -> UIViewController?
|
||||||
{
|
{
|
||||||
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<NSURL, UIImage>(items: self.app.screenshotURLs as [NSURL])
|
let appScreenshotsViewController = AppScreenshotsViewController(app: self.app, coder: coder)
|
||||||
dataSource.cellConfigurationHandler = { (cell, screenshot, indexPath) in
|
self.appScreenshotsViewController = appScreenshotsViewController
|
||||||
let cell = cell as! ScreenshotCollectionViewCell
|
return appScreenshotsViewController
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func makePermissionsDataSource() -> RSTArrayCollectionViewDataSource<AppPermission>
|
func makePermissionsDataSource() -> RSTArrayCollectionViewDataSource<AppPermission>
|
||||||
@@ -216,8 +186,8 @@ extension AppContentViewController
|
|||||||
switch Row.allCases[indexPath.row]
|
switch Row.allCases[indexPath.row]
|
||||||
{
|
{
|
||||||
case .screenshots:
|
case .screenshots:
|
||||||
guard let size = self.preferredScreenshotSize else { return 0.0 }
|
guard !self.app.screenshots.isEmpty else { return 0.0 }
|
||||||
return size.height
|
return UITableView.automaticDimension
|
||||||
|
|
||||||
case .permissions:
|
case .permissions:
|
||||||
guard !self.app.permissions.isEmpty else { return 0.0 }
|
guard !self.app.permissions.isEmpty else { return 0.0 }
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<AppScreenshot, UIImage>
|
||||||
|
{
|
||||||
|
let dataSource = RSTArrayCollectionViewPrefetchingDataSource<AppScreenshot, UIImage>(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
|
||||||
|
}
|
||||||
@@ -256,35 +256,24 @@
|
|||||||
<rect key="frame" x="0.0" y="107" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="107" width="375" height="44"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nI6-wC-H2d" id="Z4y-vb-Z4Q">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nI6-wC-H2d" id="Z4y-vb-Z4Q">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="300"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="ppk-lL-at8">
|
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5yj-Nb-f5H">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="300"/>
|
||||||
<color key="backgroundColor" name="Background"/>
|
<constraints>
|
||||||
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="15" id="ace-Ns-Jd2">
|
<constraint firstAttribute="height" priority="999" constant="300" id="dpf-ba-NNr"/>
|
||||||
<size key="itemSize" width="189" height="406"/>
|
</constraints>
|
||||||
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
<connections>
|
||||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
<segue destination="nX2-hQ-qjX" kind="embed" destinationCreationSelector="makeAppScreenshotsViewController:sender:" id="VxG-Pu-Kf1"/>
|
||||||
<inset key="sectionInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
|
</connections>
|
||||||
</collectionViewFlowLayout>
|
</containerView>
|
||||||
<cells>
|
|
||||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="2U6-d3-e4r" customClass="ScreenshotCollectionViewCell">
|
|
||||||
<rect key="frame" x="15" y="-181" width="189" height="406"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="189" height="406"/>
|
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
</view>
|
|
||||||
</collectionViewCell>
|
|
||||||
</cells>
|
|
||||||
</collectionView>
|
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="trailing" secondItem="ppk-lL-at8" secondAttribute="trailing" id="3QR-Y2-v26"/>
|
<constraint firstAttribute="trailing" secondItem="5yj-Nb-f5H" secondAttribute="trailing" id="2DI-44-pC1"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="ppk-lL-at8" secondAttribute="bottom" id="EgJ-Uw-5ta"/>
|
<constraint firstItem="5yj-Nb-f5H" firstAttribute="top" secondItem="Z4y-vb-Z4Q" secondAttribute="top" id="URh-5T-73x"/>
|
||||||
<constraint firstItem="ppk-lL-at8" firstAttribute="leading" secondItem="Z4y-vb-Z4Q" secondAttribute="leading" id="wHf-S9-gMV"/>
|
<constraint firstAttribute="bottom" secondItem="5yj-Nb-f5H" secondAttribute="bottom" id="Yb6-aZ-qNF"/>
|
||||||
<constraint firstItem="ppk-lL-at8" firstAttribute="top" secondItem="Z4y-vb-Z4Q" secondAttribute="top" id="xY5-w8-roA"/>
|
<constraint firstItem="5yj-Nb-f5H" firstAttribute="leading" secondItem="Z4y-vb-Z4Q" secondAttribute="leading" id="rpG-Ip-qZU"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</tableViewCellContentView>
|
</tableViewCellContentView>
|
||||||
<color key="backgroundColor" name="Background"/>
|
<color key="backgroundColor" name="Background"/>
|
||||||
@@ -384,7 +373,7 @@
|
|||||||
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||||
</tableViewCell>
|
</tableViewCell>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="300" id="nM7-vJ-W8b">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="300" id="nM7-vJ-W8b">
|
||||||
<rect key="frame" x="0.0" y="386.5" width="375" height="300"/>
|
<rect key="frame" x="0.0" y="642.5" width="375" height="300"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nM7-vJ-W8b" id="cQ2-Jd-pRK">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nM7-vJ-W8b" id="cQ2-Jd-pRK">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="300"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="300"/>
|
||||||
@@ -489,8 +478,8 @@ World</string>
|
|||||||
<size key="freeformSize" width="375" height="667"/>
|
<size key="freeformSize" width="375" height="667"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="appDetailCollectionViewHeightConstraint" destination="HFx-PP-dAt" id="ti3-q6-ku1"/>
|
<outlet property="appDetailCollectionViewHeightConstraint" destination="HFx-PP-dAt" id="ti3-q6-ku1"/>
|
||||||
|
<outlet property="appScreenshotsHeightConstraint" destination="dpf-ba-NNr" id="shO-Kq-Y90"/>
|
||||||
<outlet property="descriptionTextView" destination="Pyt-8D-BZA" id="cgV-Hg-LrH"/>
|
<outlet property="descriptionTextView" destination="Pyt-8D-BZA" id="cgV-Hg-LrH"/>
|
||||||
<outlet property="screenshotsCollectionView" destination="ppk-lL-at8" id="YoQ-Z6-WTP"/>
|
|
||||||
<outlet property="sizeLabel" destination="DgM-bD-bBY" id="Oky-ax-u20"/>
|
<outlet property="sizeLabel" destination="DgM-bD-bBY" id="Oky-ax-u20"/>
|
||||||
<outlet property="subtitleLabel" destination="BsL-O2-UjD" id="cfe-cf-4a9"/>
|
<outlet property="subtitleLabel" destination="BsL-O2-UjD" id="cfe-cf-4a9"/>
|
||||||
<outlet property="versionDateLabel" destination="wGD-mS-8fO" id="icB-lC-g9x"/>
|
<outlet property="versionDateLabel" destination="wGD-mS-8fO" id="icB-lC-g9x"/>
|
||||||
@@ -502,6 +491,31 @@ World</string>
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="3302" y="-18"/>
|
<point key="canvasLocation" x="3302" y="-18"/>
|
||||||
</scene>
|
</scene>
|
||||||
|
<!--App Screenshots View Controller-->
|
||||||
|
<scene sceneID="E6k-TI-c4N">
|
||||||
|
<objects>
|
||||||
|
<collectionViewController storyboardIdentifier="appScreenshotsViewController" id="nX2-hQ-qjX" customClass="AppScreenshotsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" contentInsetAdjustmentBehavior="never" dataMode="prototypes" id="zXl-if-KtH">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="300"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" automaticEstimatedItemSize="YES" minimumLineSpacing="10" minimumInteritemSpacing="10" id="MGS-YY-5g9">
|
||||||
|
<size key="itemSize" width="150" height="300"/>
|
||||||
|
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||||
|
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||||
|
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||||
|
</collectionViewFlowLayout>
|
||||||
|
<cells/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="dataSource" destination="nX2-hQ-qjX" id="QRj-01-ddR"/>
|
||||||
|
<outlet property="delegate" destination="nX2-hQ-qjX" id="Ha5-Xa-Q6e"/>
|
||||||
|
</connections>
|
||||||
|
</collectionView>
|
||||||
|
</collectionViewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="np0-Hj-vy7" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="4096.8000000000002" y="-437.18140929535235"/>
|
||||||
|
</scene>
|
||||||
<!--App Detail Collection View Controller-->
|
<!--App Detail Collection View Controller-->
|
||||||
<scene sceneID="Pcn-h5-5fk">
|
<scene sceneID="Pcn-h5-5fk">
|
||||||
<objects>
|
<objects>
|
||||||
@@ -520,7 +534,7 @@ World</string>
|
|||||||
</collectionViewController>
|
</collectionViewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="fxm-bB-W29" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="fxm-bB-W29" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="4070" y="-18"/>
|
<point key="canvasLocation" x="4097" y="-19"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Settings-->
|
<!--Settings-->
|
||||||
<scene sceneID="KlD-j0-ROn">
|
<scene sceneID="KlD-j0-ROn">
|
||||||
|
|||||||
@@ -8,6 +8,11 @@
|
|||||||
|
|
||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
|
public extension AppScreenshot
|
||||||
|
{
|
||||||
|
static let defaultAspectRatio = CGSize(width: 9, height: 19.5)
|
||||||
|
}
|
||||||
|
|
||||||
@objc(AppScreenshot)
|
@objc(AppScreenshot)
|
||||||
public class AppScreenshot: NSManagedObject, Fetchable, Decodable
|
public class AppScreenshot: NSManagedObject, Fetchable, Decodable
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user