Adds basic support for saving + loading save states
Stores SaveState model objects with Core Data, and displays the available save states in SaveStatesViewController. Currently, tapping + button creates a new save state, while tapping a collection view cell loads the save state, regardless of whether Save State or Load State was selected from the pause menu.
This commit is contained in:
parent
544f1a1801
commit
bf70df6611
@ -21,50 +21,6 @@ class DatabaseManager
|
||||
|
||||
let managedObjectContext: NSManagedObjectContext
|
||||
|
||||
class var databaseDirectoryURL: NSURL
|
||||
{
|
||||
let documentsDirectoryURL: NSURL
|
||||
|
||||
if UIDevice.currentDevice().userInterfaceIdiom == .TV
|
||||
{
|
||||
documentsDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first!
|
||||
}
|
||||
else
|
||||
{
|
||||
documentsDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first!
|
||||
}
|
||||
|
||||
let databaseDirectoryURL = documentsDirectoryURL.URLByAppendingPathComponent("Database")
|
||||
|
||||
|
||||
do
|
||||
{
|
||||
try NSFileManager.defaultManager().createDirectoryAtURL(databaseDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
return databaseDirectoryURL
|
||||
}
|
||||
|
||||
class var gamesDirectoryURL: NSURL
|
||||
{
|
||||
let gamesDirectoryURL = DatabaseManager.databaseDirectoryURL.URLByAppendingPathComponent("Games")
|
||||
|
||||
do
|
||||
{
|
||||
try NSFileManager.defaultManager().createDirectoryAtURL(gamesDirectoryURL, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
|
||||
return gamesDirectoryURL
|
||||
}
|
||||
|
||||
private let privateManagedObjectContext: NSManagedObjectContext
|
||||
private let validationManagedObjectContext: NSManagedObjectContext
|
||||
|
||||
@ -228,6 +184,52 @@ class DatabaseManager
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseManager
|
||||
{
|
||||
class var databaseDirectoryURL: NSURL
|
||||
{
|
||||
let documentsDirectoryURL: NSURL
|
||||
|
||||
if UIDevice.currentDevice().userInterfaceIdiom == .TV
|
||||
{
|
||||
documentsDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first!
|
||||
}
|
||||
else
|
||||
{
|
||||
documentsDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first!
|
||||
}
|
||||
|
||||
let databaseDirectoryURL = documentsDirectoryURL.URLByAppendingPathComponent("Database")
|
||||
self.createDirectoryAtURLIfNeeded(databaseDirectoryURL)
|
||||
|
||||
return databaseDirectoryURL
|
||||
}
|
||||
|
||||
class var gamesDirectoryURL: NSURL
|
||||
{
|
||||
let gamesDirectoryURL = DatabaseManager.databaseDirectoryURL.URLByAppendingPathComponent("Games")
|
||||
self.createDirectoryAtURLIfNeeded(gamesDirectoryURL)
|
||||
|
||||
return gamesDirectoryURL
|
||||
}
|
||||
|
||||
class var saveStatesDirectoryURL: NSURL
|
||||
{
|
||||
let saveStatesDirectoryURL = DatabaseManager.databaseDirectoryURL.URLByAppendingPathComponent("Save States")
|
||||
self.createDirectoryAtURLIfNeeded(saveStatesDirectoryURL)
|
||||
|
||||
return saveStatesDirectoryURL
|
||||
}
|
||||
|
||||
class func saveStatesDirectoryURLForGame(game: Game) -> NSURL
|
||||
{
|
||||
let gameDirectoryURL = DatabaseManager.saveStatesDirectoryURL.URLByAppendingPathComponent(game.identifier)
|
||||
self.createDirectoryAtURLIfNeeded(gameDirectoryURL)
|
||||
|
||||
return gameDirectoryURL
|
||||
}
|
||||
}
|
||||
|
||||
private extension DatabaseManager
|
||||
{
|
||||
// MARK: - Saving -
|
||||
@ -324,4 +326,18 @@ private extension DatabaseManager
|
||||
self.save()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - File Management -
|
||||
|
||||
class func createDirectoryAtURLIfNeeded(URL: NSURL)
|
||||
{
|
||||
do
|
||||
{
|
||||
try NSFileManager.defaultManager().createDirectoryAtURL(URL, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
catch
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,8 +20,8 @@ enum GameCollectionAttributes: String
|
||||
case games
|
||||
}
|
||||
|
||||
extension GameCollection {
|
||||
|
||||
extension GameCollection
|
||||
{
|
||||
@NSManaged var identifier: String
|
||||
@NSManaged var index: Int16
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="1.0" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Xcode 7.0">
|
||||
<model userDefinedModelVersionIdentifier="1.0" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9525" systemVersion="15D21" minimumToolsVersion="Xcode 7.0">
|
||||
<entity name="Game" representedClassName="Game" syncable="YES">
|
||||
<attribute name="artworkURL" optional="YES" attributeType="Transformable" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueClassName" value="NSURL"/>
|
||||
</userInfo>
|
||||
</attribute>
|
||||
<attribute name="filename" optional="YES" attributeType="String" syncable="YES">
|
||||
<attribute name="filename" attributeType="String" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueClassName" value="NSURL"/>
|
||||
</userInfo>
|
||||
@ -15,6 +15,7 @@
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="typeIdentifier" attributeType="String" syncable="YES"/>
|
||||
<relationship name="gameCollections" toMany="YES" deletionRule="Nullify" destinationEntity="GameCollection" inverseName="games" inverseEntity="GameCollection" syncable="YES"/>
|
||||
<relationship name="saveStates" toMany="YES" deletionRule="Cascade" destinationEntity="SaveState" inverseName="game" inverseEntity="SaveState" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
@ -31,8 +32,26 @@
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="SaveState" representedClassName="SaveState" syncable="YES">
|
||||
<attribute name="creationDate" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="filename" attributeType="String" syncable="YES">
|
||||
<userInfo>
|
||||
<entry key="attributeValueClassName" value="NSURL"/>
|
||||
</userInfo>
|
||||
</attribute>
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="modifiedDate" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="saveStates" inverseEntity="Game" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Game" positionX="-200" positionY="9" width="128" height="135"/>
|
||||
<element name="Game" positionX="-200" positionY="-72" width="128" height="148"/>
|
||||
<element name="GameCollection" positionX="-198" positionY="-207" width="128" height="90"/>
|
||||
<element name="SaveState" positionX="-198" positionY="113" width="128" height="135"/>
|
||||
</elements>
|
||||
</model>
|
||||
37
Common/Database/Model/SaveState+CoreDataProperties.swift
Normal file
37
Common/Database/Model/SaveState+CoreDataProperties.swift
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// SaveState+CoreDataProperties.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 1/31/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
// Choose "Create NSManagedObject Subclass…" from the Core Data editor menu
|
||||
// to delete and recreate this implementation file for your updated model.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
enum SaveStateAttributes: String
|
||||
{
|
||||
case filename
|
||||
case identifier
|
||||
case name
|
||||
case creationDate
|
||||
case modifiedDate
|
||||
|
||||
case game
|
||||
}
|
||||
|
||||
extension SaveState
|
||||
{
|
||||
@NSManaged var filename: String
|
||||
@NSManaged var identifier: String
|
||||
@NSManaged var name: String?
|
||||
@NSManaged var creationDate: NSDate
|
||||
@NSManaged var modifiedDate: NSDate
|
||||
|
||||
// Must be optional relationship to satisfy weird Core Data requirement
|
||||
// https://forums.developer.apple.com/thread/20535
|
||||
@NSManaged var game: Game!
|
||||
}
|
||||
21
Common/Database/Model/SaveState.swift
Normal file
21
Common/Database/Model/SaveState.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// SaveState.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 1/31/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
import DeltaCore
|
||||
|
||||
@objc(SaveState)
|
||||
class SaveState: NSManagedObject, SaveStateType
|
||||
{
|
||||
var fileURL: NSURL {
|
||||
let fileURL = DatabaseManager.saveStatesDirectoryURLForGame(self.game).URLByAppendingPathComponent(self.filename)
|
||||
return fileURL
|
||||
}
|
||||
}
|
||||
26
Common/Extensions/NSManagedObjectContext+Conveniences.swift
Normal file
26
Common/Extensions/NSManagedObjectContext+Conveniences.swift
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// NSManagedObjectContext+Conveniences.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 2/8/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
extension NSManagedObjectContext
|
||||
{
|
||||
// MARK: - Saving -
|
||||
|
||||
func saveWithErrorLogging()
|
||||
{
|
||||
do
|
||||
{
|
||||
try self.save()
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
print("Error saving NSManagedObjectContext: ", error, error.userInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +1 @@
|
||||
Subproject commit d5b6ac78401dc1c9bab50bbf636f42b1c0e9fe7c
|
||||
Subproject commit 23ce4c9b10760ad88d5c4c6703e88f6868a400df
|
||||
@ -1 +1 @@
|
||||
Subproject commit f4e27814258993234883fbca040ea66dc1d9609e
|
||||
Subproject commit 6d9dbe986cdacd0cf2602035608188bcdf2c178a
|
||||
@ -10,6 +10,12 @@
|
||||
AF0535CD7331785FA15E0864 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22506DA00971C4300AF90A35 /* Pods.framework */; };
|
||||
BF090CF41B490D8300DCAB45 /* UIDevice+Vibration.m in Sources */ = {isa = PBXBuildFile; fileRef = BF090CF31B490D8300DCAB45 /* UIDevice+Vibration.m */; };
|
||||
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF107EC31BF413F000E0C32C /* GamesViewController.swift */; };
|
||||
BF172AEB1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF172AEA1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift */; };
|
||||
BF172AEC1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF172AEA1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift */; };
|
||||
BF1FB1841C5EE643007E2494 /* SaveState+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1FB1821C5EE643007E2494 /* SaveState+CoreDataProperties.swift */; };
|
||||
BF1FB1851C5EE643007E2494 /* SaveState+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1FB1821C5EE643007E2494 /* SaveState+CoreDataProperties.swift */; };
|
||||
BF1FB1861C5EE643007E2494 /* SaveState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1FB1831C5EE643007E2494 /* SaveState.swift */; };
|
||||
BF1FB1871C5EE643007E2494 /* SaveState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1FB1831C5EE643007E2494 /* SaveState.swift */; };
|
||||
BF27CC8B1BC9FE4D00A20D89 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */; };
|
||||
BF27CC8C1BC9FE5300A20D89 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22506DA00971C4300AF90A35 /* Pods.framework */; };
|
||||
BF27CC8D1BC9FE5300A20D89 /* Pods.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 22506DA00971C4300AF90A35 /* Pods.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@ -127,6 +133,9 @@
|
||||
BF090CF21B490D8300DCAB45 /* UIDevice+Vibration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIDevice+Vibration.h"; sourceTree = "<group>"; };
|
||||
BF090CF31B490D8300DCAB45 /* UIDevice+Vibration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIDevice+Vibration.m"; sourceTree = "<group>"; };
|
||||
BF107EC31BF413F000E0C32C /* GamesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesViewController.swift; sourceTree = "<group>"; };
|
||||
BF172AEA1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Conveniences.swift"; sourceTree = "<group>"; };
|
||||
BF1FB1821C5EE643007E2494 /* SaveState+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SaveState+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
BF1FB1831C5EE643007E2494 /* SaveState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveState.swift; sourceTree = "<group>"; };
|
||||
BF27CC861BC9E3C600A20D89 /* Delta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Delta.entitlements; sourceTree = "<group>"; };
|
||||
BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pods.framework; path = "Pods/../build/Debug-appletvos/Pods.framework"; sourceTree = "<group>"; };
|
||||
BF27CC901BCB156200A20D89 /* EmulationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmulationViewController.swift; sourceTree = "<group>"; };
|
||||
@ -273,6 +282,8 @@
|
||||
BFDE39381BC0CEDF003F72E8 /* Game+CoreDataProperties.swift */,
|
||||
BFC273171BE6152200D22B05 /* GameCollection.swift */,
|
||||
BFC273161BE6152200D22B05 /* GameCollection+CoreDataProperties.swift */,
|
||||
BF1FB1831C5EE643007E2494 /* SaveState.swift */,
|
||||
BF1FB1821C5EE643007E2494 /* SaveState+CoreDataProperties.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@ -302,6 +313,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF762EAA1BC1B076002C8866 /* NSManagedObject+Conveniences.swift */,
|
||||
BF172AEA1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@ -673,7 +685,9 @@
|
||||
BFC273191BE6152200D22B05 /* GameCollection+CoreDataProperties.swift in Sources */,
|
||||
BF6BB2411BB73FE800CCF94A /* GameSelectionViewController.swift in Sources */,
|
||||
BF27CC8F1BCA010200A20D89 /* GamePickerController.swift in Sources */,
|
||||
BF172AEC1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift in Sources */,
|
||||
BF7AE81F1C2E984300B1B5BC /* GridCollectionViewCell.swift in Sources */,
|
||||
BF1FB1851C5EE643007E2494 /* SaveState+CoreDataProperties.swift in Sources */,
|
||||
BF7AE8251C2E984300B1B5BC /* GridCollectionViewLayout.swift in Sources */,
|
||||
BFDE393D1BC0CEDF003F72E8 /* Game.swift in Sources */,
|
||||
BFC2731B1BE6152200D22B05 /* GameCollection.swift in Sources */,
|
||||
@ -681,6 +695,7 @@
|
||||
BFDE393B1BC0CEDF003F72E8 /* Game+CoreDataProperties.swift in Sources */,
|
||||
BF27CC911BCB156200A20D89 /* EmulationViewController.swift in Sources */,
|
||||
BFB141191BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */,
|
||||
BF1FB1871C5EE643007E2494 /* SaveState.swift in Sources */,
|
||||
BFF1E5651BE04CAF000E9EF6 /* BoxArtImageView.swift in Sources */,
|
||||
BF353FFA1C5D870B00C1184C /* PauseItem.swift in Sources */,
|
||||
BF3540061C5DA70400C1184C /* SaveStatesViewController.swift in Sources */,
|
||||
@ -699,7 +714,9 @@
|
||||
BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */,
|
||||
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
|
||||
BF7AE8241C2E984300B1B5BC /* GridCollectionViewLayout.swift in Sources */,
|
||||
BF1FB1861C5EE643007E2494 /* SaveState.swift in Sources */,
|
||||
BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */,
|
||||
BF1FB1841C5EE643007E2494 /* SaveState+CoreDataProperties.swift in Sources */,
|
||||
BFFB709F1AF99B1700DE56FE /* EmulationViewController.swift in Sources */,
|
||||
BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */,
|
||||
BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */,
|
||||
@ -719,6 +736,7 @@
|
||||
BF797A2D1C2D339F00F1A000 /* UILabel+FontSize.swift in Sources */,
|
||||
BF3540021C5DA3D500C1184C /* PauseStoryboardSegue.swift in Sources */,
|
||||
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */,
|
||||
BF172AEB1C68986300C26774 /* NSManagedObjectContext+Conveniences.swift in Sources */,
|
||||
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */,
|
||||
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */,
|
||||
BFDB28451BC9DA7B001D0C83 /* GamePickerController.swift in Sources */,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Dt0-nV-isV">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Dt0-nV-isV">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Pause View Controller-->
|
||||
@ -144,11 +144,11 @@
|
||||
<scene sceneID="9Fi-Ti-W8T">
|
||||
<objects>
|
||||
<collectionViewController storyboardIdentifier="saveStatesViewController" id="OOk-k7-INg" customClass="SaveStatesViewController" customModule="Delta" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="XgF-OF-CVf">
|
||||
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" id="XgF-OF-CVf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="tvW-q1-PD8" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="20" minimumInteritemSpacing="20" id="tvW-q1-PD8" customClass="GridCollectionViewLayout" customModule="Delta" customModuleProvider="target">
|
||||
<size key="itemSize" width="50" height="50"/>
|
||||
<size key="headerReferenceSize" width="0.0" height="0.0"/>
|
||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||
@ -170,7 +170,13 @@
|
||||
<outlet property="delegate" destination="OOk-k7-INg" id="aLg-5i-MAd"/>
|
||||
</connections>
|
||||
</collectionView>
|
||||
<navigationItem key="navigationItem" title="Save State" id="BoG-k2-aR2"/>
|
||||
<navigationItem key="navigationItem" title="Save State" id="BoG-k2-aR2">
|
||||
<barButtonItem key="rightBarButtonItem" systemItem="add" id="lKg-Ks-hWN">
|
||||
<connections>
|
||||
<action selector="addSaveState" destination="OOk-k7-INg" id="xY2-94-EOr"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</navigationItem>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" barStyle="black" prompted="NO"/>
|
||||
</collectionViewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="cL5-DH-K3b" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
||||
@ -39,7 +39,8 @@ class EmulationViewController: UIViewController
|
||||
return NSStringFromClass(presentationController.dynamicType).containsString("PreviewPresentation")
|
||||
}
|
||||
|
||||
private var _isPauseViewControllerPresented = false
|
||||
private var pauseViewController: PauseViewController?
|
||||
|
||||
|
||||
|
||||
//MARK: - Initializers -
|
||||
@ -120,7 +121,7 @@ class EmulationViewController: UIViewController
|
||||
|
||||
coordinator.animateAlongsideTransition({ _ in
|
||||
|
||||
if self._isPauseViewControllerPresented
|
||||
if self.pauseViewController != nil
|
||||
{
|
||||
// We need to manually "refresh" the game screen, otherwise the system tries to cache the rendered image, but skews it incorrectly when rotating b/c of UIVisualEffectView
|
||||
self.gameView.inputImage = self.gameView.outputImage
|
||||
@ -148,11 +149,11 @@ class EmulationViewController: UIViewController
|
||||
}
|
||||
|
||||
let saveStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Save State", comment: ""), action: { _ in
|
||||
pauseViewController.presentSaveStateViewController()
|
||||
pauseViewController.presentSaveStateViewController(delegate: self)
|
||||
})
|
||||
|
||||
let loadStateItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Load State", comment: ""), action: { _ in
|
||||
pauseViewController.presentSaveStateViewController()
|
||||
pauseViewController.presentSaveStateViewController(delegate: self)
|
||||
})
|
||||
|
||||
let cheatCodesItem = PauseItem(image: UIImage(named: "SmallPause")!, text: NSLocalizedString("Cheat Codes", comment: ""), action: dismissAction)
|
||||
@ -165,13 +166,13 @@ class EmulationViewController: UIViewController
|
||||
|
||||
pauseViewController.items = [saveStateItem, loadStateItem, cheatCodesItem, fastForwardItem, sustainButtonItem]
|
||||
|
||||
self._isPauseViewControllerPresented = true
|
||||
self.pauseViewController = pauseViewController
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func unwindFromPauseViewController(segue: UIStoryboardSegue)
|
||||
{
|
||||
self._isPauseViewControllerPresented = false
|
||||
self.pauseViewController = nil
|
||||
|
||||
self.emulatorCore.resumeEmulation()
|
||||
}
|
||||
@ -217,6 +218,46 @@ private extension EmulationViewController
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Save States
|
||||
/// Save States
|
||||
extension EmulationViewController: SaveStatesViewControllerDelegate
|
||||
{
|
||||
func saveStatesViewControllerActiveGame(saveStatesViewController: SaveStatesViewController) -> Game
|
||||
{
|
||||
return self.game
|
||||
}
|
||||
|
||||
func saveStatesViewController(saveStatesViewController: SaveStatesViewController, updateSaveState saveState: SaveState)
|
||||
{
|
||||
guard let filepath = saveState.fileURL.path else { return }
|
||||
|
||||
self.emulatorCore.saveSaveState { temporarySaveState in
|
||||
do
|
||||
{
|
||||
if NSFileManager.defaultManager().fileExistsAtPath(filepath)
|
||||
{
|
||||
try NSFileManager.defaultManager().replaceItemAtURL(saveState.fileURL, withItemAtURL: temporarySaveState.fileURL, backupItemName: nil, options: [], resultingItemURL: nil)
|
||||
}
|
||||
else
|
||||
{
|
||||
try NSFileManager.defaultManager().moveItemAtURL(temporarySaveState.fileURL, toURL: saveState.fileURL)
|
||||
}
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveStatesViewController(saveStatesViewController: SaveStatesViewController, loadSaveState saveState: SaveState)
|
||||
{
|
||||
self.emulatorCore.loadSaveState(saveState)
|
||||
|
||||
self.pauseViewController?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - <GameControllerReceiver> -
|
||||
/// <GameControllerReceiver>
|
||||
extension EmulationViewController: GameControllerReceiverType
|
||||
|
||||
@ -16,6 +16,8 @@ class PauseViewController: UIViewController, PauseInfoProvidable
|
||||
/// <PauseInfoProvidable>
|
||||
var pauseText: String? = nil
|
||||
|
||||
private weak var saveStatesViewControllerDelegate: SaveStatesViewControllerDelegate?
|
||||
|
||||
/// UIViewController
|
||||
override var preferredContentSize: CGSize {
|
||||
set { }
|
||||
@ -69,8 +71,9 @@ extension PauseViewController
|
||||
|
||||
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
|
||||
{
|
||||
if segue.identifier == "embedNavigationController"
|
||||
switch segue.identifier ?? ""
|
||||
{
|
||||
case "embedNavigationController":
|
||||
self.pauseNavigationController = segue.destinationViewController as! UINavigationController
|
||||
self.pauseNavigationController.delegate = self
|
||||
self.pauseNavigationController.navigationBar.tintColor = UIColor.deltaLightPurpleColor()
|
||||
@ -81,6 +84,12 @@ extension PauseViewController
|
||||
|
||||
// Keep navigation bar outside the UIVisualEffectView's
|
||||
self.view.addSubview(self.pauseNavigationController.navigationBar)
|
||||
|
||||
case "saveState":
|
||||
let saveStatesViewController = segue.destinationViewController as! SaveStatesViewController
|
||||
saveStatesViewController.delegate = self.saveStatesViewControllerDelegate
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -92,8 +101,10 @@ extension PauseViewController
|
||||
self.performSegueWithIdentifier("unwindFromPauseMenu", sender: self)
|
||||
}
|
||||
|
||||
func presentSaveStateViewController()
|
||||
func presentSaveStateViewController(delegate delegate: SaveStatesViewControllerDelegate)
|
||||
{
|
||||
self.saveStatesViewControllerDelegate = delegate
|
||||
|
||||
self.performSegueWithIdentifier("saveState", sender: self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,17 +7,47 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreData
|
||||
|
||||
import DeltaCore
|
||||
import Roxas
|
||||
|
||||
private let SaveStatesViewControllerContentInset: CGFloat = 20
|
||||
protocol SaveStatesViewControllerDelegate: class
|
||||
{
|
||||
func saveStatesViewControllerActiveGame(saveStatesViewController: SaveStatesViewController) -> Game
|
||||
func saveStatesViewController(saveStatesViewController: SaveStatesViewController, updateSaveState saveState: SaveState)
|
||||
func saveStatesViewController(saveStatesViewController: SaveStatesViewController, loadSaveState saveState: SaveState)
|
||||
}
|
||||
|
||||
class SaveStatesViewController: UICollectionViewController
|
||||
{
|
||||
weak var delegate: SaveStatesViewControllerDelegate?
|
||||
|
||||
private var backgroundView: RSTBackgroundView!
|
||||
|
||||
private var prototypeCell = GridCollectionViewCell()
|
||||
private var prototypeCellWidthConstraint: NSLayoutConstraint!
|
||||
|
||||
private let fetchedResultsController: NSFetchedResultsController
|
||||
|
||||
private let dateFormatter: NSDateFormatter
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
let fetchRequest = SaveState.fetchRequest()
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: SaveStateAttributes.creationDate.rawValue, ascending: true)]
|
||||
|
||||
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.sharedManager.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
|
||||
|
||||
self.dateFormatter = NSDateFormatter()
|
||||
self.dateFormatter.timeStyle = .ShortStyle
|
||||
self.dateFormatter.dateStyle = .ShortStyle
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.fetchedResultsController.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
extension SaveStatesViewController
|
||||
@ -35,20 +65,38 @@ extension SaveStatesViewController
|
||||
self.backgroundView.detailTextLabel.textColor = UIColor.whiteColor()
|
||||
self.view.insertSubview(self.backgroundView, atIndex: 0)
|
||||
|
||||
// We update the layout in code because we need to use our SaveStatesViewControllerContentInset constant
|
||||
// The reason for this is we cannot query the layout for its sectionInset in viewDidLayoutSubviews, so might as well be explicit in code with a constant
|
||||
// Otherwise, we could configure this all in Interface Builder, but we'd still need to hardcode 20 in for viewDidLayoutSubviews
|
||||
let collectionViewLayout = self.collectionViewLayout as! GridCollectionViewLayout
|
||||
collectionViewLayout.sectionInset = UIEdgeInsets(top: SaveStatesViewControllerContentInset, left: SaveStatesViewControllerContentInset, bottom: SaveStatesViewControllerContentInset, right: SaveStatesViewControllerContentInset)
|
||||
collectionViewLayout.minimumInteritemSpacing = SaveStatesViewControllerContentInset
|
||||
collectionViewLayout.minimumLineSpacing = SaveStatesViewControllerContentInset
|
||||
|
||||
let averageHorizontalInset = (collectionViewLayout.sectionInset.left + collectionViewLayout.sectionInset.right) / 2
|
||||
let portraitScreenWidth = UIScreen.mainScreen().coordinateSpace.convertRect(UIScreen.mainScreen().bounds, toCoordinateSpace: UIScreen.mainScreen().fixedCoordinateSpace).width
|
||||
collectionViewLayout.itemWidth = (portraitScreenWidth - ((SaveStatesViewControllerContentInset) * 3)) / 2
|
||||
|
||||
// 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 = (portraitScreenWidth - (averageHorizontalInset * 3)) / 2
|
||||
|
||||
// Manually update prototype cell properties
|
||||
self.prototypeCellWidthConstraint = self.prototypeCell.contentView.widthAnchor.constraintEqualToConstant(collectionViewLayout.itemWidth)
|
||||
self.prototypeCellWidthConstraint.active = true
|
||||
|
||||
self.updateBackgroundView()
|
||||
}
|
||||
|
||||
override func viewWillAppear(animated: Bool)
|
||||
{
|
||||
if self.fetchedResultsController.fetchedObjects == nil
|
||||
{
|
||||
do
|
||||
{
|
||||
try self.fetchedResultsController.performFetch()
|
||||
}
|
||||
catch let error as NSError
|
||||
{
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
self.updateBackgroundView()
|
||||
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning()
|
||||
@ -57,10 +105,27 @@ extension SaveStatesViewController
|
||||
}
|
||||
}
|
||||
|
||||
private extension SaveStatesViewController
|
||||
{
|
||||
func updateBackgroundView()
|
||||
{
|
||||
if let fetchedObjects = self.fetchedResultsController.fetchedObjects where fetchedObjects.count > 0
|
||||
{
|
||||
self.backgroundView.hidden = true
|
||||
}
|
||||
else
|
||||
{
|
||||
self.backgroundView.hidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension SaveStatesViewController
|
||||
{
|
||||
func configureCollectionViewCell(cell: GridCollectionViewCell, forIndexPath indexPath: NSIndexPath)
|
||||
{
|
||||
let saveState = self.fetchedResultsController.objectAtIndexPath(indexPath) as! SaveState
|
||||
|
||||
cell.imageView.backgroundColor = UIColor.whiteColor()
|
||||
cell.imageView.image = UIImage(named: "DeltaPlaceholder")
|
||||
|
||||
@ -68,7 +133,42 @@ private extension SaveStatesViewController
|
||||
|
||||
cell.textLabel.textColor = UIColor.whiteColor()
|
||||
cell.textLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
|
||||
cell.textLabel.text = "Save State"
|
||||
|
||||
let name = saveState.name ?? self.dateFormatter.stringFromDate(saveState.modifiedDate)
|
||||
cell.textLabel.text = name
|
||||
}
|
||||
}
|
||||
|
||||
private extension SaveStatesViewController
|
||||
{
|
||||
@IBAction func addSaveState()
|
||||
{
|
||||
guard let delegate = self.delegate else { return }
|
||||
|
||||
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
|
||||
backgroundContext.performBlock {
|
||||
|
||||
let identifier = NSUUID().UUIDString
|
||||
let date = NSDate()
|
||||
|
||||
var game = delegate.saveStatesViewControllerActiveGame(self)
|
||||
game = backgroundContext.objectWithID(game.objectID) as! Game
|
||||
|
||||
let saveState = SaveState.insertIntoManagedObjectContext(backgroundContext)
|
||||
saveState.identifier = identifier
|
||||
saveState.filename = identifier
|
||||
saveState.creationDate = date
|
||||
saveState.modifiedDate = date
|
||||
saveState.game = game
|
||||
|
||||
self.updateSaveState(saveState)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSaveState(saveState: SaveState)
|
||||
{
|
||||
self.delegate?.saveStatesViewController(self, updateSaveState: saveState)
|
||||
saveState.managedObjectContext?.saveWithErrorLogging()
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +176,8 @@ extension SaveStatesViewController
|
||||
{
|
||||
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
{
|
||||
return 12
|
||||
let section = self.fetchedResultsController.sections![section]
|
||||
return section.numberOfObjects
|
||||
}
|
||||
|
||||
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
|
||||
@ -87,6 +188,15 @@ extension SaveStatesViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension SaveStatesViewController
|
||||
{
|
||||
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
|
||||
{
|
||||
let saveState = self.fetchedResultsController.objectAtIndexPath(indexPath) as! SaveState
|
||||
self.delegate?.saveStatesViewController(self, loadSaveState: saveState)
|
||||
}
|
||||
}
|
||||
|
||||
extension SaveStatesViewController: UICollectionViewDelegateFlowLayout
|
||||
{
|
||||
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
|
||||
@ -96,4 +206,13 @@ extension SaveStatesViewController: UICollectionViewDelegateFlowLayout
|
||||
let size = self.prototypeCell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
extension SaveStatesViewController: NSFetchedResultsControllerDelegate
|
||||
{
|
||||
func controllerDidChangeContent(controller: NSFetchedResultsController)
|
||||
{
|
||||
self.collectionView?.reloadData()
|
||||
self.updateBackgroundView()
|
||||
}
|
||||
}
|
||||
2
External/Roxas
vendored
2
External/Roxas
vendored
@ -1 +1 @@
|
||||
Subproject commit 41ed3e9fcc247258a83e103f8816dbc07652c75a
|
||||
Subproject commit 6dc906703bb5870d268d341cefd1fdcc40001eb9
|
||||
Loading…
Reference in New Issue
Block a user