Adds support for Locked Save States
This commit is contained in:
parent
0b12732e6e
commit
aa4f5fb532
@ -49,6 +49,8 @@ class GridCollectionViewLayout: UICollectionViewFlowLayout
|
||||
|
||||
for (index, attributes) in layoutAttributes.enumerate()
|
||||
{
|
||||
guard attributes.representedElementCategory == .Cell else { continue }
|
||||
|
||||
// Ensure equal spacing between items (that also match the section insets)
|
||||
if index > 0
|
||||
{
|
||||
|
||||
@ -42,6 +42,7 @@
|
||||
<attribute name="identifier" attributeType="String" syncable="YES"/>
|
||||
<attribute name="modifiedDate" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="type" attributeType="Integer 16" defaultValueString="1" syncable="YES"/>
|
||||
<relationship name="game" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Game" inverseName="saveStates" inverseEntity="Game" syncable="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
@ -52,6 +53,6 @@
|
||||
<elements>
|
||||
<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"/>
|
||||
<element name="SaveState" positionX="-198" positionY="113" width="128" height="150"/>
|
||||
</elements>
|
||||
</model>
|
||||
@ -20,9 +20,17 @@ extension SaveState
|
||||
case name
|
||||
case creationDate
|
||||
case modifiedDate
|
||||
case type
|
||||
|
||||
case game
|
||||
}
|
||||
|
||||
@objc enum Type: Int16
|
||||
{
|
||||
case Auto
|
||||
case General
|
||||
case Locked
|
||||
}
|
||||
}
|
||||
|
||||
@objc(SaveState)
|
||||
@ -30,6 +38,7 @@ class SaveState: NSManagedObject, SaveStateType
|
||||
{
|
||||
@NSManaged var name: String?
|
||||
@NSManaged var modifiedDate: NSDate
|
||||
@NSManaged var type: Type
|
||||
|
||||
@NSManaged private(set) var filename: String
|
||||
@NSManaged private(set) var identifier: String
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
BF2A53FC1BB74FC10052BD0C /* SNESDeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BF2A53FD1BB74FC60052BD0C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; };
|
||||
BF2A53FE1BB74FC60052BD0C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */; };
|
||||
BF353FF21C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; };
|
||||
BF353FF31C5D7FB000C1184C /* PauseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF353FF11C5D7FB000C1184C /* PauseViewController.swift */; };
|
||||
BF353FF61C5D837600C1184C /* PauseMenu.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF353FF41C5D837600C1184C /* PauseMenu.storyboard */; };
|
||||
@ -136,6 +137,7 @@
|
||||
BF27CC901BCB156200A20D89 /* EmulationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmulationViewController.swift; sourceTree = "<group>"; };
|
||||
BF27CC941BCB7B7A00A20D89 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS9.0.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; };
|
||||
BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesCollectionViewController.swift; sourceTree = "<group>"; };
|
||||
BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesCollectionHeaderView.swift; path = "Pause Menu/Save States/SaveStatesCollectionHeaderView.swift"; sourceTree = "<group>"; };
|
||||
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseViewController.swift; path = "Pause Menu/PauseViewController.swift"; sourceTree = "<group>"; };
|
||||
BF353FF51C5D837600C1184C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PauseMenu.storyboard; sourceTree = "<group>"; };
|
||||
BF353FF81C5D870B00C1184C /* PauseItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseItem.swift; path = "Pause Menu/PauseItem.swift"; sourceTree = "<group>"; };
|
||||
@ -242,6 +244,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */,
|
||||
BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */,
|
||||
);
|
||||
name = "Save States";
|
||||
sourceTree = "<group>";
|
||||
@ -714,6 +717,7 @@
|
||||
BFDE393C1BC0CEDF003F72E8 /* Game.swift in Sources */,
|
||||
BFC2731A1BE6152200D22B05 /* GameCollection.swift in Sources */,
|
||||
BFF1E5641BE04CAF000E9EF6 /* BoxArtImageView.swift in Sources */,
|
||||
BF2B98E61C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift in Sources */,
|
||||
BF762EAB1BC1B076002C8866 /* NSManagedObject+Conveniences.swift in Sources */,
|
||||
BF353FFF1C5DA3C500C1184C /* PausePresentationController.swift in Sources */,
|
||||
BF7AE80A1C2E8C7600B1B5BC /* UIColor+Delta.swift in Sources */,
|
||||
|
||||
@ -150,13 +150,13 @@
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<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="headerReferenceSize" width="50" height="50"/>
|
||||
<size key="footerReferenceSize" width="0.0" height="0.0"/>
|
||||
<inset key="sectionInset" minX="20" minY="20" maxX="20" maxY="20"/>
|
||||
<inset key="sectionInset" minX="20" minY="10" maxX="20" maxY="20"/>
|
||||
</collectionViewFlowLayout>
|
||||
<cells>
|
||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="c3N-1A-ryV" customClass="GridCollectionViewCell" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="84" width="50" height="50"/>
|
||||
<rect key="frame" x="20" y="124" width="50" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
@ -165,6 +165,10 @@
|
||||
</view>
|
||||
</collectionViewCell>
|
||||
</cells>
|
||||
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Header" id="YeY-W9-CC6" customClass="SaveStatesCollectionHeaderView" customModule="Delta" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</collectionReusableView>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="OOk-k7-INg" id="8l7-6Q-tQp"/>
|
||||
<outlet property="delegate" destination="OOk-k7-INg" id="aLg-5i-MAd"/>
|
||||
|
||||
@ -0,0 +1,45 @@
|
||||
//
|
||||
// SaveStatesCollectionHeaderView.swift
|
||||
// Delta
|
||||
//
|
||||
// Created by Riley Testut on 3/15/16.
|
||||
// Copyright © 2016 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SaveStatesCollectionHeaderView: UICollectionReusableView
|
||||
{
|
||||
let textLabel = UILabel()
|
||||
|
||||
override init(frame: CGRect)
|
||||
{
|
||||
super.init(frame: frame)
|
||||
|
||||
self.initialize()
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder)
|
||||
{
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.initialize()
|
||||
}
|
||||
|
||||
private func initialize()
|
||||
{
|
||||
self.textLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.textLabel.textColor = UIColor.whiteColor()
|
||||
|
||||
var fontDescriptor = UIFontDescriptor.preferredFontDescriptorWithTextStyle(UIFontTextStyleTitle3)
|
||||
fontDescriptor = fontDescriptor.fontDescriptorWithSymbolicTraits([.TraitBold])
|
||||
|
||||
self.textLabel.font = UIFont(descriptor: fontDescriptor, size: 0.0)
|
||||
self.textLabel.textAlignment = .Center
|
||||
self.addSubview(self.textLabel)
|
||||
|
||||
// Auto Layout
|
||||
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[textLabel]-20-|", options: [], metrics: nil, views: ["textLabel": self.textLabel]))
|
||||
NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-10-[textLabel]|", options: [], metrics: nil, views: ["textLabel": self.textLabel]))
|
||||
}
|
||||
}
|
||||
@ -26,6 +26,13 @@ extension SaveStatesViewController
|
||||
case Saving
|
||||
case Loading
|
||||
}
|
||||
|
||||
enum Section: Int
|
||||
{
|
||||
case Auto
|
||||
case General
|
||||
case Locked
|
||||
}
|
||||
}
|
||||
|
||||
class SaveStatesViewController: UICollectionViewController
|
||||
@ -42,6 +49,7 @@ class SaveStatesViewController: UICollectionViewController
|
||||
|
||||
private var prototypeCell = GridCollectionViewCell()
|
||||
private var prototypeCellWidthConstraint: NSLayoutConstraint!
|
||||
private var prototypeHeader = SaveStatesCollectionHeaderView()
|
||||
|
||||
private var fetchedResultsController: NSFetchedResultsController!
|
||||
|
||||
@ -140,9 +148,9 @@ private extension SaveStatesViewController
|
||||
let fetchRequest = SaveState.fetchRequest()
|
||||
fetchRequest.returnsObjectsAsFaults = false
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@", SaveState.Attributes.game.rawValue, game)
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: SaveState.Attributes.creationDate.rawValue, ascending: true)]
|
||||
fetchRequest.sortDescriptors = [NSSortDescriptor(key: SaveState.Attributes.type.rawValue, ascending: true), NSSortDescriptor(key: SaveState.Attributes.creationDate.rawValue, ascending: true)]
|
||||
|
||||
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.sharedManager.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
|
||||
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.sharedManager.managedObjectContext, sectionNameKeyPath: SaveState.Attributes.type.rawValue, cacheName: nil)
|
||||
self.fetchedResultsController.delegate = self
|
||||
}
|
||||
|
||||
@ -158,7 +166,7 @@ private extension SaveStatesViewController
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Configure Cell -
|
||||
//MARK: - Configure Views -
|
||||
|
||||
func configureCollectionViewCell(cell: GridCollectionViewCell, forIndexPath indexPath: NSIndexPath, ignoreExpensiveOperations ignoreOperations: Bool = false)
|
||||
{
|
||||
@ -175,6 +183,7 @@ private extension SaveStatesViewController
|
||||
|
||||
if let image = image
|
||||
{
|
||||
cell.imageView.backgroundColor = nil
|
||||
cell.imageView.image = image
|
||||
}
|
||||
}
|
||||
@ -197,6 +206,22 @@ private extension SaveStatesViewController
|
||||
cell.textLabel.text = name
|
||||
}
|
||||
|
||||
func configureCollectionViewHeaderView(headerView: SaveStatesCollectionHeaderView, forSection section: Int)
|
||||
{
|
||||
let section = self.correctedSectionForSectionIndex(section)
|
||||
|
||||
let title: String
|
||||
|
||||
switch section
|
||||
{
|
||||
case .Auto: title = NSLocalizedString("Auto Save", comment: "")
|
||||
case .General: title = NSLocalizedString("General", comment: "")
|
||||
case .Locked: title = NSLocalizedString("Locked", comment: "")
|
||||
}
|
||||
|
||||
headerView.textLabel.text = title
|
||||
}
|
||||
|
||||
//MARK: - Gestures -
|
||||
|
||||
@objc func handleLongPressGesture(gestureRecognizer: UILongPressGestureRecognizer)
|
||||
@ -205,12 +230,29 @@ private extension SaveStatesViewController
|
||||
|
||||
guard let indexPath = self.collectionView?.indexPathForItemAtPoint(gestureRecognizer.locationInView(self.collectionView)) else { return }
|
||||
|
||||
let saveState = self.fetchedResultsController.objectAtIndexPath(indexPath) as! SaveState
|
||||
|
||||
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .Cancel, handler: nil))
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Delete Save State", comment: ""), style: .Destructive, handler: { action in
|
||||
let saveState = self.fetchedResultsController.objectAtIndexPath(indexPath) as! SaveState
|
||||
self.deleteSaveState(saveState)
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .Cancel, handler: nil))
|
||||
|
||||
let section = self.correctedSectionForSectionIndex(indexPath.section)
|
||||
switch section
|
||||
{
|
||||
case .Auto: break
|
||||
case .General:
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Lock Save State", comment: ""), style: .Default, handler: { action in
|
||||
self.lockSaveState(saveState)
|
||||
}))
|
||||
|
||||
case .Locked:
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Unlock Save State", comment: ""), style: .Default, handler: { action in
|
||||
self.unlockSaveState(saveState)
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
self.presentViewController(alertController, animated: true, completion: nil)
|
||||
}
|
||||
@ -261,11 +303,48 @@ private extension SaveStatesViewController
|
||||
|
||||
self.presentViewController(confirmationAlertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func lockSaveState(saveState: SaveState)
|
||||
{
|
||||
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
|
||||
backgroundContext.performBlockAndWait() {
|
||||
let temporarySaveState = backgroundContext.objectWithID(saveState.objectID) as! SaveState
|
||||
temporarySaveState.type = .Locked
|
||||
backgroundContext.saveWithErrorLogging()
|
||||
}
|
||||
}
|
||||
|
||||
func unlockSaveState(saveState: SaveState)
|
||||
{
|
||||
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
|
||||
backgroundContext.performBlockAndWait() {
|
||||
let temporarySaveState = backgroundContext.objectWithID(saveState.objectID) as! SaveState
|
||||
temporarySaveState.type = .General
|
||||
backgroundContext.saveWithErrorLogging()
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Convenience Methods -
|
||||
|
||||
func correctedSectionForSectionIndex(section: Int) -> Section
|
||||
{
|
||||
let sectionInfo = self.fetchedResultsController.sections![section]
|
||||
let sectionIndex = Int(sectionInfo.name)!
|
||||
|
||||
let section = Section(rawValue: sectionIndex)!
|
||||
return section
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - <UICollectionViewDataSource> -
|
||||
extension SaveStatesViewController
|
||||
{
|
||||
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
|
||||
{
|
||||
let numberOfSections = self.fetchedResultsController.sections!.count
|
||||
return numberOfSections
|
||||
}
|
||||
|
||||
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
{
|
||||
let section = self.fetchedResultsController.sections![section]
|
||||
@ -278,6 +357,13 @@ extension SaveStatesViewController
|
||||
self.configureCollectionViewCell(cell, forIndexPath: indexPath)
|
||||
return cell
|
||||
}
|
||||
|
||||
override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView
|
||||
{
|
||||
let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "Header", forIndexPath: indexPath) as! SaveStatesCollectionHeaderView
|
||||
self.configureCollectionViewHeaderView(headerView, forSection: indexPath.section)
|
||||
return headerView
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - <UICollectionViewDelegate> -
|
||||
@ -289,7 +375,26 @@ extension SaveStatesViewController
|
||||
|
||||
switch self.mode
|
||||
{
|
||||
case .Saving: self.updateSaveState(saveState)
|
||||
case .Saving:
|
||||
|
||||
let section = self.correctedSectionForSectionIndex(indexPath.section)
|
||||
switch section
|
||||
{
|
||||
case .Auto: break
|
||||
case .General:
|
||||
let backgroundContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
|
||||
backgroundContext.performBlockAndWait() {
|
||||
let temporarySaveState = backgroundContext.objectWithID(saveState.objectID) as! SaveState
|
||||
self.updateSaveState(temporarySaveState)
|
||||
}
|
||||
|
||||
case .Locked:
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Cannot Modify Locked Save State", comment: ""), message: NSLocalizedString("This save state must first be unlocked before it can be modified.", comment: ""), preferredStyle: .Alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .Cancel, handler: nil))
|
||||
self.presentViewController(alertController, animated: true, completion: nil)
|
||||
|
||||
}
|
||||
|
||||
case .Loading: self.loadSaveState(saveState)
|
||||
}
|
||||
}
|
||||
@ -312,6 +417,14 @@ extension SaveStatesViewController: UICollectionViewDelegateFlowLayout
|
||||
let size = self.prototypeCell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
|
||||
return size
|
||||
}
|
||||
|
||||
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
|
||||
{
|
||||
self.configureCollectionViewHeaderView(self.prototypeHeader, forSection: section)
|
||||
|
||||
let size = self.prototypeHeader.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - <NSFetchedResultsControllerDelegate> -
|
||||
|
||||
Loading…
Reference in New Issue
Block a user