diff --git a/Common/Collection View/GridCollectionViewLayout.swift b/Common/Collection View/GridCollectionViewLayout.swift
index 048ee1e..e2351ef 100644
--- a/Common/Collection View/GridCollectionViewLayout.swift
+++ b/Common/Collection View/GridCollectionViewLayout.swift
@@ -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
{
diff --git a/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents b/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents
index 64dc8e4..430ddf7 100644
--- a/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents
+++ b/Common/Database/Model/Model.xcdatamodeld/Model.xcdatamodel/contents
@@ -42,6 +42,7 @@
+
@@ -52,6 +53,6 @@
-
+
\ No newline at end of file
diff --git a/Common/Database/Model/SaveState.swift b/Common/Database/Model/SaveState.swift
index 6f324bd..b1fdf53 100644
--- a/Common/Database/Model/SaveState.swift
+++ b/Common/Database/Model/SaveState.swift
@@ -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
diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj
index 70c4706..265f80c 100644
--- a/Delta.xcodeproj/project.pbxproj
+++ b/Delta.xcodeproj/project.pbxproj
@@ -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 = ""; };
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 = ""; };
+ BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesCollectionHeaderView.swift; path = "Pause Menu/Save States/SaveStatesCollectionHeaderView.swift"; sourceTree = ""; };
BF353FF11C5D7FB000C1184C /* PauseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseViewController.swift; path = "Pause Menu/PauseViewController.swift"; sourceTree = ""; };
BF353FF51C5D837600C1184C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PauseMenu.storyboard; sourceTree = ""; };
BF353FF81C5D870B00C1184C /* PauseItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseItem.swift; path = "Pause Menu/PauseItem.swift"; sourceTree = ""; };
@@ -242,6 +244,7 @@
isa = PBXGroup;
children = (
BF3540041C5DA70400C1184C /* SaveStatesViewController.swift */,
+ BF2B98E51C97E32F00F6D57D /* SaveStatesCollectionHeaderView.swift */,
);
name = "Save States";
sourceTree = "";
@@ -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 */,
diff --git a/Delta/Base.lproj/PauseMenu.storyboard b/Delta/Base.lproj/PauseMenu.storyboard
index ca21423..702513e 100644
--- a/Delta/Base.lproj/PauseMenu.storyboard
+++ b/Delta/Base.lproj/PauseMenu.storyboard
@@ -150,13 +150,13 @@
-
+
-
+
-
+
@@ -165,6 +165,10 @@
+
+
+
+
diff --git a/Delta/Pause Menu/Save States/SaveStatesCollectionHeaderView.swift b/Delta/Pause Menu/Save States/SaveStatesCollectionHeaderView.swift
new file mode 100644
index 0000000..31dda2b
--- /dev/null
+++ b/Delta/Pause Menu/Save States/SaveStatesCollectionHeaderView.swift
@@ -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]))
+ }
+}
diff --git a/Delta/Pause Menu/Save States/SaveStatesViewController.swift b/Delta/Pause Menu/Save States/SaveStatesViewController.swift
index 04636c0..04a023a 100644
--- a/Delta/Pause Menu/Save States/SaveStatesViewController.swift
+++ b/Delta/Pause Menu/Save States/SaveStatesViewController.swift
@@ -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: - -
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: - -
@@ -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: - -