Adds support for Locked Save States

This commit is contained in:
Riley Testut 2016-03-15 04:56:52 -05:00
parent 0b12732e6e
commit aa4f5fb532
7 changed files with 188 additions and 10 deletions

View File

@ -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
{

View File

@ -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>

View File

@ -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

View File

@ -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 */,

View File

@ -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"/>

View File

@ -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]))
}
}

View File

@ -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> -