Merge branch 'ipad' into develop

# Conflicts:
#	Delta.xcodeproj/project.pbxproj
This commit is contained in:
Riley Testut 2022-05-31 17:51:31 -07:00
commit 7c934cebe1
14 changed files with 286 additions and 49 deletions

@ -1 +1 @@
Subproject commit e2b3f0e46b4c64670e13fd0466ebdac719f84555
Subproject commit 2a6779e1271bc5d2e09aea2aa41fa6a0b75b62aa

View File

@ -168,6 +168,7 @@
D524F4A1273DE9A100D500B2 /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = D524F4A0273DE9A100D500B2 /* AltKit */; };
D524F4A3273DE9C000D500B2 /* ProcessInfo+JIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */; };
D524F4A5273DEBB400D500B2 /* ServerManager+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */; };
D5011C48281B6E8B00A0760B /* CharacterSet+Filename.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -363,6 +364,7 @@
C786AF1D2DDB6223BE2063CC /* Pods-Delta.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Delta.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Delta/Pods-Delta.debug.xcconfig"; sourceTree = "<group>"; };
D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+JIT.swift"; sourceTree = "<group>"; };
D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerManager+Delta.swift"; sourceTree = "<group>"; };
D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterSet+Filename.swift"; sourceTree = "<group>"; };
DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Delta.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@ -411,6 +413,7 @@
BFE56E1823EB7BE00014FECD /* UIImage+SymbolFallback.swift */,
D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */,
D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */,
D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -1126,6 +1129,7 @@
BF525EEA1FF6CD12004AA849 /* DeepLink.swift in Sources */,
BF8A334621A4926F00A42FD4 /* GameSyncStatusViewController.swift in Sources */,
BF59427E1E09BC830051894B /* Game.swift in Sources */,
D5011C48281B6E8B00A0760B /* CharacterSet+Filename.swift in Sources */,
BFE593CC21F3F8C2003412A6 /* _GameSave.swift in Sources */,
BF63A1A321A4AAAE00EE8F61 /* RecordSyncStatusViewController.swift in Sources */,
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
@ -1453,6 +1457,7 @@
SWIFT_OBJC_BRIDGING_HEADER = "Delta/Supporting Files/Delta-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
@ -1482,6 +1487,7 @@
SWIFT_OBJC_BRIDGING_HEADER = "Delta/Supporting Files/Delta-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};

View File

@ -46,6 +46,9 @@
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="importButton" destination="FeA-O5-xd2" id="A44-3S-Okz"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="JYx-xE-nis" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>

View File

@ -1048,6 +1048,7 @@
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="0QR-U9-gtx" customClass="RSTNavigationController" sceneMemberID="viewController">
<toolbarItems/>
<value key="contentSizeForViewInPopover" type="size" width="375" height="667"/>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" id="Y5H-O6-CQ5">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>

View File

@ -15,6 +15,8 @@ extension UIActivity.ActivityType
class CopyDeepLinkActivity: UIActivity
{
private var deepLink: URL?
override class var activityCategory: UIActivity.Category {
return .action
}
@ -28,7 +30,7 @@ class CopyDeepLinkActivity: UIActivity
}
override var activityImage: UIImage? {
return UIImage(named: "Link")
return UIImage(symbolNameIfAvailable: "link") ?? UIImage(named: "Link")
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool
@ -47,7 +49,19 @@ class CopyDeepLinkActivity: UIActivity
{
guard let game = activityItems.first(where: { $0 is Game }) as? Game else { return }
let deepLink = URL(action: .launchGame(identifier: game.identifier))
UIPasteboard.general.url = deepLink
self.deepLink = URL(action: .launchGame(identifier: game.identifier))
}
override func perform()
{
if let deepLink = self.deepLink
{
UIPasteboard.general.url = deepLink
self.activityDidFinish(true)
}
else
{
self.activityDidFinish(false)
}
}
}

View File

@ -0,0 +1,22 @@
//
// CharacterSet+Filename.swift
// Delta
//
// Created by Riley Testut on 4/28/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import Foundation
extension CharacterSet
{
// Different than .urlPathAllowed
// Copied from https://stackoverflow.com/a/39443252
static var urlFilenameAllowed: CharacterSet {
var illegalCharacters = CharacterSet(charactersIn: ":/")
illegalCharacters.formUnion(.newlines)
illegalCharacters.formUnion(.illegalCharacters)
illegalCharacters.formUnion(.controlCharacters)
return illegalCharacters.inverted
}
}

View File

@ -66,6 +66,8 @@ class GameCollectionViewController: UICollectionViewController
private weak var _previewTransitionViewController: PreviewGameViewController?
private weak var _previewTransitionDestinationViewController: UIViewController?
private weak var _popoverSourceView: UIView?
private var _renameAction: UIAlertAction?
private var _changingArtworkGame: Game?
private var _importingSaveFileGame: Game?
@ -93,10 +95,6 @@ extension GameCollectionViewController
self.collectionView?.prefetchDataSource = self.dataSource
self.collectionView?.delegate = self
let layout = self.collectionViewLayout as! GridCollectionViewLayout
layout.itemWidth = 90
layout.minimumInteritemSpacing = 12
if #available(iOS 13, *) {}
else
{
@ -105,6 +103,8 @@ extension GameCollectionViewController
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(GameCollectionViewController.handleLongPressGesture(_:)))
self.collectionView?.addGestureRecognizer(longPressGestureRecognizer)
}
self.update()
}
override func viewWillDisappear(_ animated: Bool)
@ -131,6 +131,13 @@ extension GameCollectionViewController
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
{
super.traitCollectionDidChange(previousTraitCollection)
self.update()
}
}
//MARK: - Segues -
@ -224,6 +231,26 @@ extension GameCollectionViewController
//MARK: - Private Methods -
private extension GameCollectionViewController
{
func update()
{
let layout = self.collectionViewLayout as! GridCollectionViewLayout
switch self.traitCollection.horizontalSizeClass
{
case .regular:
layout.itemWidth = 150
layout.minimumInteritemSpacing = 25 // 30 == only 3 games per line for iPad mini 6 in portrait
case .unspecified, .compact:
layout.itemWidth = 90
layout.minimumInteritemSpacing = 12
@unknown default: break
}
self.collectionView.reloadData()
}
//MARK: - Data Source
func prepareDataSource()
{
@ -284,7 +311,19 @@ private extension GameCollectionViewController
cell.imageView.image = #imageLiteral(resourceName: "BoxArt")
cell.maximumImageSize = CGSize(width: 90, height: 90)
if self.traitCollection.horizontalSizeClass == .regular
{
let fontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .subheadline).withSymbolicTraits(.traitBold)!
cell.textLabel.font = UIFont(descriptor: fontDescriptor, size: 0)
}
else
{
cell.textLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
}
let layout = self.collectionViewLayout as! GridCollectionViewLayout
cell.maximumImageSize = CGSize(width: layout.itemWidth, height: layout.itemWidth)
cell.textLabel.text = game.name
cell.textLabel.textColor = UIColor.gray
cell.tintColor = cell.textLabel.textColor
@ -484,7 +523,9 @@ private extension GameCollectionViewController
func delete(_ game: Game)
{
let confirmationAlertController = UIAlertController(title: NSLocalizedString("Are you sure you want to delete this game? All associated data, such as saves, save states, and cheat codes, will also be deleted.", comment: ""), message: nil, preferredStyle: .actionSheet)
let confirmationAlertController = UIAlertController(title: NSLocalizedString("Are you sure you want to delete this game?", comment: ""),
message: NSLocalizedString("All associated data, such as saves, save states, and cheat codes, will also be deleted.", comment: ""),
preferredStyle: .alert)
confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Delete Game", comment: ""), style: .destructive, handler: { action in
DatabaseManager.shared.performBackgroundTask { (context) in
@ -554,6 +595,7 @@ private extension GameCollectionViewController
let importController = ImportController(documentTypes: [kUTTypeImage as String])
importController.delegate = self
importController.importOptions = [clipboardImportOption, photoLibraryImportOption, gamesDatabaseImportOption]
importController.sourceView = self._popoverSourceView
self.present(importController, animated: true, completion: nil)
}
@ -663,26 +705,36 @@ private extension GameCollectionViewController
func share(_ game: Game)
{
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
let symbolicURL = temporaryDirectory.appendingPathComponent(game.name + "." + game.fileURL.pathExtension)
let temporaryDirectory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
let sanitizedName = game.name.components(separatedBy: .urlFilenameAllowed.inverted).joined()
let temporaryURL = temporaryDirectory.appendingPathComponent(sanitizedName + "." + game.fileURL.pathExtension, isDirectory: false)
do
{
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
// Create a symbolic link so we can control the file name used when sharing.
// Make a temporary copy so we can control the filename used when sharing.
// Otherwise, if we just passed in game.fileURL to UIActivityViewController, the file name would be the game's SHA1 hash.
try FileManager.default.createSymbolicLink(at: symbolicURL, withDestinationURL: game.fileURL)
try FileManager.default.copyItem(at: game.fileURL, to: temporaryURL, shouldReplace: true)
}
catch
{
print(error)
let alertController = UIAlertController(title: NSLocalizedString("Could Not Share Game", comment: ""), error: error)
self.present(alertController, animated: true, completion: nil)
return
}
let copyDeepLinkActivity = CopyDeepLinkActivity()
let activityViewController = UIActivityViewController(activityItems: [symbolicURL, game], applicationActivities: [copyDeepLinkActivity])
let activityViewController = UIActivityViewController(activityItems: [temporaryURL, game], applicationActivities: [copyDeepLinkActivity])
activityViewController.popoverPresentationController?.sourceView = self._popoverSourceView?.superview
activityViewController.popoverPresentationController?.sourceRect = self._popoverSourceView?.frame ?? .zero
activityViewController.completionWithItemsHandler = { (activityType, finished, returnedItems, error) in
// Make sure the user either shared the game or cancelled before deleting temporaryDirectory.
guard finished || activityType == nil else { return }
do
{
try FileManager.default.removeItem(at: temporaryDirectory)
@ -692,6 +744,7 @@ private extension GameCollectionViewController
print(error)
}
}
self.present(activityViewController, animated: true, completion: nil)
}
@ -747,8 +800,7 @@ private extension GameCollectionViewController
{
do
{
let illegalCharacterSet = CharacterSet(charactersIn: "\"\\/?<>:*|")
let sanitizedFilename = game.name.components(separatedBy: illegalCharacterSet).joined() + "." + game.gameSaveURL.pathExtension
let sanitizedFilename = game.name.components(separatedBy: .urlFilenameAllowed.inverted).joined()
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(sanitizedFilename)
try FileManager.default.copyItem(at: game.gameSaveURL, to: temporaryURL, shouldReplace: true)
@ -807,6 +859,9 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
previewingContext.sourceRect = layoutAttributes.frame
let cell = collectionView.cellForItem(at: indexPath)
self._popoverSourceView = cell
let game = self.dataSource.item(at: indexPath)
let gameViewController = self.makePreviewGameViewController(for: game)
@ -974,6 +1029,9 @@ extension GameCollectionViewController
let game = self.dataSource.item(at: indexPath)
let actions = self.actions(for: game)
let cell = self.collectionView.cellForItem(at: indexPath)
self._popoverSourceView = cell
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: { [weak self] in
guard let self = self else { return nil }

View File

@ -47,6 +47,7 @@ class GamesViewController: UIViewController
private let fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>
private var searchController: RSTSearchController?
private lazy var importController: ImportController = self.makeImportController()
private var syncingToastView: RSTToastView? {
didSet {
@ -58,6 +59,8 @@ class GamesViewController: UIViewController
}
private var syncingProgressObservation: NSKeyValueObservation?
@IBOutlet private var importButton: UIBarButtonItem!
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
fatalError("initWithNibName: not implemented")
}
@ -114,10 +117,16 @@ extension GamesViewController
let navigationBarAppearance = navigationController.navigationBar.standardAppearance.copy()
navigationBarAppearance.backgroundEffect = UIBlurEffect(style: .dark)
navigationController.navigationBar.standardAppearance = navigationBarAppearance
navigationController.navigationBar.scrollEdgeAppearance = navigationBarAppearance
let toolbarAppearance = navigationController.toolbar.standardAppearance.copy()
toolbarAppearance.backgroundEffect = UIBlurEffect(style: .dark)
navigationController.toolbar.standardAppearance = toolbarAppearance
navigationController.toolbar.standardAppearance = toolbarAppearance
if #available(iOS 15, *)
{
navigationController.toolbar.scrollEdgeAppearance = toolbarAppearance
}
}
else
{
@ -126,6 +135,22 @@ extension GamesViewController
}
}
if #available(iOS 14, *)
{
self.importController.presentingViewController = self
let importActions = self.importController.makeActions().menuActions
let importMenu = UIMenu(title: NSLocalizedString("Import From…", comment: ""), image: UIImage(systemName: "square.and.arrow.down"), children: importActions)
self.importButton.menu = importMenu
self.importButton.action = nil
self.importButton.target = nil
}
else
{
self.importController.barButtonItem = self.importButton
}
self.prepareSearchController()
self.updateTheme()
@ -352,8 +377,8 @@ private extension GamesViewController
/// Importing
extension GamesViewController: ImportControllerDelegate
{
@IBAction private func importFiles()
{
private func makeImportController() -> ImportController
{
var documentTypes = Set(System.registeredSystems.map { $0.gameType.rawValue })
documentTypes.insert(kUTTypeZipArchive as String)
documentTypes.insert("com.rileytestut.delta.skin")
@ -373,7 +398,13 @@ extension GamesViewController: ImportControllerDelegate
let importController = ImportController(documentTypes: documentTypes)
importController.delegate = self
importController.importOptions = [itunesImportOption]
self.present(importController, animated: true, completion: nil)
return importController
}
@IBAction private func importFiles()
{
self.present(self.importController, animated: true, completion: nil)
}
func importController(_ importController: ImportController, didImportItemsAt urls: Set<URL>, errors: [Error])

View File

@ -13,7 +13,7 @@ import DeltaCore
struct iTunesImportOption: ImportOption
{
let title = NSLocalizedString("iTunes", comment: "")
let image: UIImage? = nil
let image: UIImage? = UIImage(symbolNameIfAvailable: "music.note")
private let presentingViewController: UIViewController

View File

@ -37,7 +37,10 @@ class ImportController: NSObject
var delegate: ImportControllerDelegate?
var importOptions: [ImportOption]?
private weak var presentingViewController: UIViewController?
weak var presentingViewController: UIViewController?
weak var barButtonItem: UIBarButtonItem?
weak var sourceView: UIView?
// Store presentedViewController separately, since when we dismiss we don't know if it has already been dismissed.
// Calling dismiss on presentingViewController in that case would dismiss presentingViewController, which is bad.
@ -61,26 +64,54 @@ class ImportController: NSObject
super.init()
}
func makeActions() -> [Action]
{
assert(self.presentingViewController != nil, "presentingViewController must be set before calling makeActions()")
var actions = (self.importOptions ?? []).map { (option) -> Action in
let action = Action(title: option.title, style: .default, image: option.image) { _ in
option.import { importedURLs in
self.finish(with: importedURLs, errors: [])
}
}
return action
}
let filesAction = Action(title: NSLocalizedString("Files", comment: ""), style: .default, image: UIImage(symbolNameIfAvailable: "doc")) { action in
self.presentDocumentBrowser()
}
actions.append(filesAction)
return actions
}
fileprivate func presentImportController(from presentingViewController: UIViewController, animated: Bool, completionHandler: (() -> Void)?)
{
self.presentingViewController = presentingViewController
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction.cancel)
let actions = self.makeActions()
if let importOptions = self.importOptions
if actions.count > 1
{
for importOption in importOptions
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction.cancel)
let alertActions = actions.map { UIAlertAction($0) }
for action in alertActions
{
alertController.add(importOption) { [unowned self] (urls) in
self.finish(with: urls, errors: [])
}
alertController.addAction(action)
}
let filesAction = UIAlertAction(title: NSLocalizedString("Files", comment: ""), style: .default) { (action) in
self.presentDocumentBrowser()
if let sourceView = self.sourceView
{
alertController.popoverPresentationController?.sourceView = sourceView.superview
alertController.popoverPresentationController?.sourceRect = sourceView.frame
}
else
{
alertController.popoverPresentationController?.barButtonItem = self.barButtonItem
}
alertController.addAction(filesAction)
self.presentedViewController = alertController
self.presentingViewController?.present(alertController, animated: true, completion: nil)
@ -198,7 +229,7 @@ private var ImportControllerKey: UInt8 = 0
extension UIViewController
{
fileprivate(set) var importController: ImportController?
fileprivate var importController: ImportController?
{
set
{

View File

@ -93,14 +93,6 @@ extension SaveStatesViewController
self.collectionView?.dataSource = self.dataSource
self.collectionView?.prefetchDataSource = self.dataSource
let collectionViewLayout = self.collectionViewLayout as! GridCollectionViewLayout
let averageHorizontalInset = (collectionViewLayout.sectionInset.left + collectionViewLayout.sectionInset.right) / 2
let portraitScreenWidth = UIScreen.main.coordinateSpace.convert(UIScreen.main.bounds, to: UIScreen.main.fixedCoordinateSpace).width
// Use dimensions that allow two cells to fill the screen horizontally with padding in portrait mode
// We'll keep the same size for landscape orientation, which will allow more to fit
collectionViewLayout.itemWidth = floor((portraitScreenWidth - (averageHorizontalInset * 3)) / 2)
switch self.mode
{
case .saving:
@ -113,8 +105,7 @@ extension SaveStatesViewController
self.navigationItem.rightBarButtonItems?.removeFirst()
}
// Manually update prototype cell properties
self.prototypeCellWidthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionViewLayout.itemWidth)
self.prototypeCellWidthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: 0)
self.prototypeCellWidthConstraint.isActive = true
self.prepareEmulatorCoreSaveState()
@ -238,6 +229,26 @@ private extension SaveStatesViewController
}
self.sortButton.transform = CGAffineTransform.identity.rotated(by: Settings.sortSaveStatesByOldestFirst ? 0 : .pi)
let collectionViewLayout = self.collectionViewLayout as! GridCollectionViewLayout
if self.traitCollection.horizontalSizeClass == .regular
{
collectionViewLayout.itemWidth = 180
collectionViewLayout.minimumInteritemSpacing = 30
}
else
{
let averageHorizontalInset = (collectionViewLayout.sectionInset.left + collectionViewLayout.sectionInset.right) / 2
let portraitScreenWidth = UIScreen.main.coordinateSpace.convert(UIScreen.main.bounds, to: UIScreen.main.fixedCoordinateSpace).width
// Use dimensions that allow two cells to fill the screen horizontally with padding in portrait mode
// We'll keep the same size for landscape orientation, which will allow more to fit
collectionViewLayout.itemWidth = floor((portraitScreenWidth - (averageHorizontalInset * 3)) / 2)
}
// Manually update prototype cell properties
self.prototypeCellWidthConstraint.constant = collectionViewLayout.itemWidth
}
//MARK: - Configure Views -

View File

@ -40,6 +40,8 @@ class ControllerInputsViewController: UIViewController
private var activeCalloutView: InputCalloutView?
private var _didLayoutSubviews = false
@IBOutlet private var actionsMenuViewControllerHeightConstraint: NSLayoutConstraint!
@IBOutlet private var cancelTapGestureRecognizer: UITapGestureRecognizer!
@ -65,7 +67,15 @@ class ControllerInputsViewController: UIViewController
self.gameViewController.controllerView.addReceiver(self)
self.navigationController?.navigationBar.barStyle = .black
if let navigationController = self.navigationController, #available(iOS 13, *)
{
navigationController.overrideUserInterfaceStyle = .dark
navigationController.navigationBar.scrollEdgeAppearance = navigationController.navigationBar.standardAppearance // Fixes invisible navigation bar on iPad.
}
else
{
self.navigationController?.navigationBar.barStyle = .black
}
NSLayoutConstraint.activate([self.gameViewController.gameView.centerYAnchor.constraint(equalTo: self.actionsMenuViewController.view.centerYAnchor)])
@ -81,6 +91,23 @@ class ControllerInputsViewController: UIViewController
{
self.actionsMenuViewControllerHeightConstraint.constant = self.actionsMenuViewController.preferredContentSize.height
}
if let window = self.view.window, !_didLayoutSubviews
{
var traits = DeltaCore.ControllerSkin.Traits.defaults(for: window)
traits.orientation = .portrait
if traits.device == .ipad
{
// Use standard iPhone skins instead of iPad skins.
traits.device = .iphone
traits.displayType = .standard
}
self.gameViewController.controllerView.overrideControllerSkinTraits = traits
_didLayoutSubviews = true
}
}
override func viewDidAppear(_ animated: Bool)
@ -403,6 +430,7 @@ private extension ControllerInputsViewController
}
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.popoverPresentationController?.barButtonItem = sender
alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Reset Controls to Defaults", comment: ""), style: .destructive, handler: { (action) in
reset()
@ -562,3 +590,15 @@ extension ControllerInputsViewController: SMCalloutViewDelegate
self.toggle(calloutView)
}
}
extension ControllerInputsViewController: UIAdaptivePresentationControllerDelegate
{
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
{
switch (traitCollection.horizontalSizeClass, traitCollection.verticalSizeClass)
{
case (.regular, .regular): return .formSheet // Regular width and height, so display as form sheet
default: return .fullScreen // Compact width and/or height, so display full screen
}
}
}

View File

@ -108,9 +108,18 @@ extension ControllersSettingsViewController
switch identifier
{
case "controllerInputsSegue":
let controllerInputsViewController = (segue.destination as! UINavigationController).topViewController as! ControllerInputsViewController
let navigationController = segue.destination as! UINavigationController
let controllerInputsViewController = navigationController.topViewController as! ControllerInputsViewController
controllerInputsViewController.gameController = self.gameController
if self.view.traitCollection.userInterfaceIdiom == .pad
{
// For now, only iPads can display ControllerInputsViewController as a form sheet.
navigationController.modalPresentationStyle = .formSheet
navigationController.presentationController?.delegate = controllerInputsViewController
}
default: break
}
@ -296,6 +305,8 @@ extension ControllersSettingsViewController
{
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let previousGameController = self.gameController
switch Section(rawValue: indexPath.section)!
{
case .localDevice: self.gameController = self.localDeviceController
@ -310,7 +321,7 @@ extension ControllersSettingsViewController
let previousIndexPath: IndexPath?
if let gameController = self.gameController
if let gameController = previousGameController
{
if gameController == self.localDeviceController
{

View File

@ -215,6 +215,8 @@
<array>
<string>armv7</string>
</array>
<key>UIRequiresFullScreen</key>
<true/>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UISupportedInterfaceOrientations</key>
@ -223,6 +225,13 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>UTExportedTypeDeclarations</key>