Updated UI for pagination between GameCollections

This commit is contained in:
Riley Testut 2015-11-24 11:53:36 -08:00
parent 4a2b5cd1dd
commit 925fb9454d
14 changed files with 323 additions and 75 deletions

View File

@ -11,15 +11,13 @@ import CoreData
class GameCollectionViewDataSource: NSObject
{
var gameTypeIdentifiers: [String] = [] {
var supportedGameCollectionIdentifiers: [String]? {
didSet
{
self.updateFetchedResultsController()
}
}
var sectionTitles: [String] = []
var cellConfigurationHandler: ((GameCollectionViewCell, Game) -> Void)?
private(set) var fetchedResultsController: NSFetchedResultsController = NSFetchedResultsController()
@ -47,10 +45,13 @@ class GameCollectionViewDataSource: NSObject
var predicates: [NSPredicate] = []
for typeIdentifier in self.gameTypeIdentifiers
if let identifiers = self.supportedGameCollectionIdentifiers
{
let predicate = NSPredicate(format: "%K == %@", GameAttributes.typeIdentifier.rawValue, typeIdentifier)
predicates.append(predicate)
for identifier in identifiers
{
let predicate = NSPredicate(format: "SUBQUERY(%K, $x, $x.%K == %@).@count > 0", GameAttributes.gameCollections.rawValue, GameCollectionAttributes.identifier.rawValue, identifier)
predicates.append(predicate)
}
}
if predicates.count > 0
@ -58,7 +59,7 @@ class GameCollectionViewDataSource: NSObject
fetchRequest.predicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
}
fetchRequest.sortDescriptors = [NSSortDescriptor(key: GameAttributes.name.rawValue, ascending: true)]
fetchRequest.sortDescriptors = [NSSortDescriptor(key: GameAttributes.typeIdentifier.rawValue, ascending: true), NSSortDescriptor(key: GameAttributes.name.rawValue, ascending: true)]
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.sharedManager.managedObjectContext, sectionNameKeyPath: GameAttributes.typeIdentifier.rawValue, cacheName: nil)
self.fetchedResultsController.delegate = previousDelegate
@ -117,6 +118,4 @@ extension GameCollectionViewDataSource: UICollectionViewDelegate
let size = self.prototypeCell.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
return size
}
}

View File

@ -88,8 +88,9 @@ class DatabaseManager
self.validationManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
self.validationManagedObjectContext.parentContext = self.managedObjectContext
self.validationManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("managedObjectContextDidSave:"), name: NSManagedObjectContextDidSaveNotification, object: nil)
}
func startWithCompletion(completionBlock: ((performingMigration: Bool) -> Void)?)
@ -158,9 +159,9 @@ class DatabaseManager
game.identifier = identifier
game.filename = filename
if let pathExtension = URL.pathExtension,
gameCollection = GameCollection.gameSystemCollectionForPathExtension(pathExtension, inManagedObjectContext: managedObjectContext)
if let pathExtension = URL.pathExtension
{
let gameCollection = GameCollection.gameSystemCollectionForPathExtension(pathExtension, inManagedObjectContext: managedObjectContext)
game.typeIdentifier = gameCollection.identifier
game.gameCollections.insert(gameCollection)
}
@ -171,7 +172,19 @@ class DatabaseManager
do
{
try NSFileManager.defaultManager().moveItemAtURL(URL, toURL: DatabaseManager.gamesDirectoryURL.URLByAppendingPathComponent(game.identifier + ".smc"))
let destinationURL = DatabaseManager.gamesDirectoryURL.URLByAppendingPathComponent(game.identifier + "." + game.preferredFileExtension)
if let path = destinationURL.path
{
if NSFileManager.defaultManager().fileExistsAtPath(path)
{
try NSFileManager.defaultManager().removeItemAtURL(URL)
}
else
{
try NSFileManager.defaultManager().moveItemAtURL(URL, toURL: destinationURL)
}
}
identifiers.append(game.identifier)
}
@ -275,7 +288,36 @@ private extension DatabaseManager
{
guard let managedObjectContext = notification.object as? NSManagedObjectContext where managedObjectContext.parentContext == self.validationManagedObjectContext else { return }
self.save()
self.validationManagedObjectContext.performBlockAndWait {
// Remove deleted games from disk
if let deletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject>
{
let games = deletedObjects.filter({ $0 is Game }).map({ self.validationManagedObjectContext.objectWithID($0.objectID) as! Game })
for game in games
{
do
{
try NSFileManager.defaultManager().removeItemAtURL(game.fileURL)
}
catch let error as NSError
{
print(error)
}
}
}
// Remove empty collections
let collections = GameCollection.instancesWithPredicate(NSPredicate(format: "%K.@count == 0", GameCollectionAttributes.games.rawValue), inManagedObjectContext: self.validationManagedObjectContext, type: GameCollection.self)
for collection in collections
{
self.validationManagedObjectContext.deleteObject(collection)
}
self.save()
}
}
}

View File

@ -12,6 +12,8 @@ import CoreData
import DeltaCore
import SNESDeltaCore
public let kUTTypeGBAGame: CFStringRef = "com.rileytestut.delta.game.gba"
@objc(Game)
class Game: NSManagedObject, GameType
{
@ -19,12 +21,22 @@ class Game: NSManagedObject, GameType
let fileURL = DatabaseManager.gamesDirectoryURL.URLByAppendingPathComponent(self.filename)
return fileURL
}
var preferredFileExtension: String {
switch self.typeIdentifier
{
case kUTTypeSNESGame as String as String: return "smc"
case kUTTypeGBAGame as String as String: return "gba"
case kUTTypeDeltaGame as String as String: fallthrough
default: return "delta"
}
}
}
extension Game
{
class func supportedTypeIdentifiers() -> Set<String>
{
return [kUTTypeSNESGame as String]
return [kUTTypeSNESGame as String, kUTTypeGBAGame as String]
}
}

View File

@ -14,18 +14,16 @@ import CoreData
enum GameCollectionAttributes: String
{
case name
case identifier
case shortName
case index
case games
}
extension GameCollection {
@NSManaged var name: String
@NSManaged var identifier: String
@NSManaged var shortName: String?
@NSManaged var index: Int16
@NSManaged var games: Set<Game>
}

View File

@ -9,31 +9,54 @@
import Foundation
import CoreData
import DeltaCore
import SNESDeltaCore
@objc(GameCollection)
class GameCollection: NSManagedObject
{
class func gameSystemCollectionForPathExtension(pathExtension: String?, inManagedObjectContext managedObjectContext: NSManagedObjectContext) -> GameCollection?
var name: String {
switch self.identifier
{
case kUTTypeSNESGame as String as String: return NSLocalizedString("Super Nintendo Entertainment System", comment: "")
case kUTTypeGBAGame as String as String: return NSLocalizedString("Game Boy Advance", comment: "")
case kUTTypeDeltaGame as String as String: return NSLocalizedString("Unsupported Games", comment: "")
default: return NSLocalizedString("Unknown", comment: "")
}
}
var shortName: String {
switch self.identifier
{
case kUTTypeSNESGame as String as String: return NSLocalizedString("SNES", comment: "")
case kUTTypeGBAGame as String as String: return NSLocalizedString("GBA", comment: "")
case kUTTypeDeltaGame as String as String: return NSLocalizedString("Unsupported", comment: "")
default: return NSLocalizedString("Unknown", comment: "")
}
}
class func gameSystemCollectionForPathExtension(pathExtension: String?, inManagedObjectContext managedObjectContext: NSManagedObjectContext) -> GameCollection
{
guard let pathExtension = pathExtension else { return nil }
let identifier: String
let name: String
let shortName: String
let index: Int16
switch pathExtension
switch pathExtension ?? ""
{
case "smc": fallthrough
case "sfc": fallthrough
case "fig":
identifier = kUTTypeSNESGame as String
name = "Super Nintendo Entertainment System"
shortName = "SNES"
index = 1990
case "gba":
identifier = kUTTypeGBAGame as String
index = 2001
default: return nil
default:
identifier = kUTTypeDeltaGame as String
index = Int16(INT16_MAX)
}
let predicate = NSPredicate(format: "%K == %@", GameCollectionAttributes.identifier.rawValue, identifier)
@ -43,10 +66,9 @@ class GameCollection: NSManagedObject
{
gameCollection = GameCollection.insertIntoManagedObjectContext(managedObjectContext)
gameCollection?.identifier = identifier
gameCollection?.name = name
gameCollection?.shortName = shortName
gameCollection?.index = index
}
return gameCollection
return gameCollection!
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15A284" minimumToolsVersion="Xcode 7.0">
<model userDefinedModelVersionIdentifier="1.0" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Xcode 7.0">
<entity name="Game" representedClassName="Game" syncable="YES">
<attribute name="artworkURL" optional="YES" attributeType="Transformable" syncable="YES">
<userInfo>
@ -23,8 +23,7 @@
</entity>
<entity name="GameCollection" representedClassName="GameCollection" syncable="YES">
<attribute name="identifier" attributeType="String" syncable="YES"/>
<attribute name="name" attributeType="String" syncable="YES"/>
<attribute name="shortName" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="index" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<relationship name="games" toMany="YES" deletionRule="Nullify" destinationEntity="Game" inverseName="gameCollections" inverseEntity="Game" syncable="YES"/>
<uniquenessConstraints>
<uniquenessConstraint>
@ -33,7 +32,7 @@
</uniquenessConstraints>
</entity>
<elements>
<element name="Game" positionX="-200" positionY="9" width="128" height="133"/>
<element name="GameCollection" positionX="-198" positionY="-207" width="128" height="103"/>
<element name="Game" positionX="-200" positionY="9" width="128" height="135"/>
<element name="GameCollection" positionX="-198" positionY="-207" width="128" height="90"/>
</elements>
</model>

View File

@ -9,6 +9,8 @@
import UIKit
import ObjectiveC
import DeltaCore
protocol GamePickerControllerDelegate
{
func gamePickerController(gamePickerController: GamePickerController, didImportGames games: [Game])
@ -59,7 +61,7 @@ class GamePickerController: NSObject
let managedObjectContext = DatabaseManager.sharedManager.backgroundManagedObjectContext()
managedObjectContext.performBlock() {
let gameURLs = contents.filter({ GameCollection.gameSystemCollectionForPathExtension($0.pathExtension, inManagedObjectContext: managedObjectContext) != nil })
let gameURLs = contents.filter({ GameCollection.gameSystemCollectionForPathExtension($0.pathExtension, inManagedObjectContext: managedObjectContext).identifier != kUTTypeDeltaGame as String })
self.importGamesAtURLs(gameURLs)
}

View File

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
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 */; };
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, ); }; };
@ -17,7 +18,6 @@
BF27CC911BCB156200A20D89 /* EmulationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF27CC901BCB156200A20D89 /* EmulationViewController.swift */; };
BF27CC951BCB7B7A00A20D89 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF27CC941BCB7B7A00A20D89 /* GameController.framework */; };
BF27CC971BCC890700A20D89 /* GamesCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */; };
BF27CC991BCC8AE600A20D89 /* GamesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF27CC981BCC8AE600A20D89 /* GamesViewController.swift */; };
BF2A53FB1BB74FC10052BD0C /* SNESDeltaCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */; };
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 */; };
@ -112,12 +112,12 @@
BF090CF11B490D8300DCAB45 /* Delta-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Delta-Bridging-Header.h"; sourceTree = "<group>"; };
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>"; };
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>"; };
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>"; };
BF27CC981BCC8AE600A20D89 /* GamesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesViewController.swift; sourceTree = "<group>"; };
BF4566E71BC090B6007BFA1A /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
BF50DC411BD851740024C720 /* GameCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollectionViewCell.swift; path = "Collection View/GameCollectionViewCell.swift"; sourceTree = "<group>"; };
BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
@ -204,7 +204,6 @@
BF4566E31BC09026007BFA1A /* Common */ = {
isa = PBXGroup;
children = (
BFC273151BE612CE00D22B05 /* Model */,
BFF1E55F1BE04BF6000E9EF6 /* Components */,
BF9257571BD8244800B109DA /* Collection View */,
BF4566E41BC0902E007BFA1A /* Database */,
@ -238,7 +237,7 @@
BF46894D1AAC469800A2586D /* Game Selection */ = {
isa = PBXGroup;
children = (
BF27CC981BCC8AE600A20D89 /* GamesViewController.swift */,
BF107EC31BF413F000E0C32C /* GamesViewController.swift */,
BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */,
);
path = "Game Selection";
@ -308,13 +307,6 @@
path = Settings;
sourceTree = "<group>";
};
BFC273151BE612CE00D22B05 /* Model */ = {
isa = PBXGroup;
children = (
);
name = Model;
sourceTree = "<group>";
};
BFDB28431BC9D9D1001D0C83 /* Importing */ = {
isa = PBXGroup;
children = (
@ -632,7 +624,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BF27CC991BCC8AE600A20D89 /* GamesViewController.swift in Sources */,
BFF4EA011BE1B2420056AAA4 /* GameCollectionViewLayoutAttributes.swift in Sources */,
BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */,
BF50DC421BD851740024C720 /* GameCollectionViewCell.swift in Sources */,
@ -650,6 +641,7 @@
BFA5342A1BDC6B520088F1BE /* GameCollectionViewLayout.swift in Sources */,
BF762E9E1BC19D31002C8866 /* DatabaseManager.swift in Sources */,
BF090CF41B490D8300DCAB45 /* UIDevice+Vibration.m in Sources */,
BF107EC41BF413F000E0C32C /* GamesViewController.swift in Sources */,
BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */,
BFDB28451BC9DA7B001D0C83 /* GamePickerController.swift in Sources */,
BFDE393A1BC0CEDF003F72E8 /* Game+CoreDataProperties.swift in Sources */,

View File

@ -1,4 +1,5 @@
{
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "A1BF268530E217C860B34A81A1DA1B446006BF9E",
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
},
@ -6,6 +7,7 @@
"377491A212AA92C1503AD101C88ADDBD6FD62CF0" : 0,
"C14902E1A817B9BD0549E16C5F6A56E2377FBF42" : 0,
"816D57F18EE93972A535858CB18F29A85BE91A39" : 0,
"A1BF268530E217C860B34A81A1DA1B446006BF9E" : 0,
"82DF43EF5CA016D15C41E0AF1AF686E9973475B7" : 0
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "8EE86D07-2AAF-47C3-91DF-0E46DBDAB128",
@ -13,6 +15,7 @@
"377491A212AA92C1503AD101C88ADDBD6FD62CF0" : "Delta\/External\/Roxas\/",
"C14902E1A817B9BD0549E16C5F6A56E2377FBF42" : "Delta\/Cores\/SNESDeltaCore\/",
"816D57F18EE93972A535858CB18F29A85BE91A39" : "Delta\/Cores\/DeltaCore\/",
"A1BF268530E217C860B34A81A1DA1B446006BF9E" : "Delta\/",
"82DF43EF5CA016D15C41E0AF1AF686E9973475B7" : "Delta\/Cores\/DeltaCore\/External\/ZipZap\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "Delta",
@ -34,6 +37,11 @@
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "82DF43EF5CA016D15C41E0AF1AF686E9973475B7"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:rileytestut\/Delta.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "A1BF268530E217C860B34A81A1DA1B446006BF9E"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:rileytestut\/SNESDeltaCore.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9059" systemVersion="15A284" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="wKV-3d-NIY">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9059" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="wKV-3d-NIY">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9049"/>
</dependencies>
@ -79,7 +79,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="tmn-gd-5UN">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="556"/>
<animations/>
<connections>
<segue destination="tpK-ou-yEA" kind="embed" id="cjU-nW-cHY"/>
@ -239,12 +239,18 @@
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="wKV-3d-NIY" sceneMemberID="viewController">
<toolbarItems/>
<nil key="simulatedBottomBarMetrics"/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="wj9-1e-eev">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
</navigationBar>
<nil name="viewControllers"/>
<toolbar key="toolbar" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="M4r-sO-G4H">
<rect key="frame" x="0.0" y="556" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
</toolbar>
<connections>
<segue destination="jeE-WD-wXO" kind="relationship" relationship="rootViewController" id="JbW-Xm-9mu"/>
</connections>

View File

@ -13,10 +13,11 @@ import DeltaCore
class GamesCollectionViewController: UICollectionViewController
{
var gameTypeIdentifier: String! {
var gameCollection: GameCollection! {
didSet
{
self.dataSource.gameTypeIdentifiers = [self.gameTypeIdentifier]
self.dataSource.supportedGameCollectionIdentifiers = [self.gameCollection.identifier]
self.title = self.gameCollection.shortName
}
}

View File

@ -7,37 +7,95 @@
//
import UIKit
import CoreData
import SNESDeltaCore
import Roxas
class GamesViewController: UIViewController
{
var pageViewController: UIPageViewController! = nil
private var pageViewController: UIPageViewController!
private var backgroundView: RSTBackgroundView!
private var pageControl: UIPageControl!
private let fetchedResultsController: NSFetchedResultsController
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
fatalError("initWithNibName: not implemented")
}
required init?(coder aDecoder: NSCoder)
{
let fetchRequest = GameCollection.fetchRequest()
fetchRequest.sortDescriptors = [NSSortDescriptor(key: GameCollectionAttributes.index.rawValue, ascending: true)]
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.sharedManager.managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
super.init(coder: aDecoder)
self.fetchedResultsController.delegate = self
}
let supportedGameTypeIdentifiers = [kUTTypeSNESGame as String]
override func viewDidLoad()
{
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
self.backgroundView = RSTBackgroundView(frame: self.view.bounds)
self.backgroundView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.backgroundView.textLabel.text = NSLocalizedString("No Games", comment: "")
self.backgroundView.detailTextLabel.text = NSLocalizedString("You can import games by pressing the + button in the top right.", comment: "")
self.view.insertSubview(self.backgroundView, atIndex: 0)
self.pageViewController = self.childViewControllers.first as? UIPageViewController
self.pageViewController.dataSource = self
self.pageViewController.delegate = self
self.pageViewController.view.hidden = true
let viewController = self.viewControllerForIndex(0)
self.pageViewController.setViewControllers([viewController], direction: .Forward, animated: false, completion: nil)
// Do any additional setup after loading the view.
self.pageControl = UIPageControl()
self.pageControl.translatesAutoresizingMaskIntoConstraints = false
self.pageControl.hidesForSinglePage = false
self.pageControl.numberOfPages = 3
self.pageControl.currentPageIndicatorTintColor = UIColor.purpleColor()
self.pageControl.pageIndicatorTintColor = UIColor.lightGrayColor()
self.navigationController?.toolbar.addSubview(self.pageControl)
self.pageControl.centerXAnchor.constraintEqualToAnchor(self.navigationController?.toolbar.centerXAnchor, constant: 0).active = true
self.pageControl.centerYAnchor.constraintEqualToAnchor(self.navigationController?.toolbar.centerYAnchor, constant: 0).active = true
}
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
if self.fetchedResultsController.fetchedObjects == nil
{
do
{
try self.fetchedResultsController.performFetch()
}
catch let error as NSError
{
print(error)
}
self.updateSections()
}
}
override func viewDidLayoutSubviews()
{
super.viewDidLayoutSubviews()
let viewController = self.pageViewController.viewControllers?.first as! GamesCollectionViewController
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
if let viewControllers = self.pageViewController.viewControllers as? [GamesCollectionViewController]
{
for viewController in viewControllers
{
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
}
}
}
override func didReceiveMemoryWarning() {
@ -57,20 +115,83 @@ class GamesViewController: UIViewController
private extension GamesViewController
{
func viewControllerForIndex(index: Int) -> GamesCollectionViewController
func viewControllerForIndex(index: Int) -> GamesCollectionViewController?
{
var safeIndex = index % self.supportedGameTypeIdentifiers.count
guard let pages = self.fetchedResultsController.sections?.first?.numberOfObjects where pages > 0 else { return nil }
// Return nil if only one section, and not asking for the 0th view controller
guard !(pages == 1 && index != 0) else { return nil }
var safeIndex = index % pages
if safeIndex < 0
{
safeIndex = self.supportedGameTypeIdentifiers.count + safeIndex
safeIndex = pages + safeIndex
}
let indexPath = NSIndexPath(forRow: safeIndex, inSection: 0)
let viewController = self.storyboard?.instantiateViewControllerWithIdentifier("gamesCollectionViewController") as! GamesCollectionViewController
viewController.gameTypeIdentifier = self.supportedGameTypeIdentifiers[safeIndex] as String
viewController.gameCollection = self.fetchedResultsController.objectAtIndexPath(indexPath) as! GameCollection
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
return viewController
}
func updateSections()
{
let sections = self.fetchedResultsController.sections?.first?.numberOfObjects ?? 0
self.pageControl.numberOfPages = sections
var resetPageViewController = false
if let viewController = pageViewController.viewControllers?.first as? GamesCollectionViewController, let gameCollection = viewController.gameCollection
{
if let index = self.fetchedResultsController.fetchedObjects?.indexOf({ $0 as! GameCollection == gameCollection })
{
self.pageControl.currentPage = index
}
else
{
resetPageViewController = true
self.pageControl.currentPage = 0
}
}
self.navigationController?.setToolbarHidden(sections < 2, animated: self.view.window != nil)
if sections > 0
{
// Reset page view controller if currently hidden or current child should view controller no longer exists
if self.pageViewController.view.hidden || resetPageViewController
{
if let viewController = self.viewControllerForIndex(0)
{
self.pageViewController.view.hidden = false
self.backgroundView.hidden = true
self.pageViewController.setViewControllers([viewController], direction: .Forward, animated: false, completion: nil)
self.title = viewController.title
}
}
else
{
self.pageViewController.setViewControllers(self.pageViewController.viewControllers, direction: .Forward, animated: false, completion: nil)
}
}
else
{
self.title = NSLocalizedString("Games", comment: "")
if !self.pageViewController.view.hidden
{
self.pageViewController.view.hidden = true
self.backgroundView.hidden = false
}
}
}
}
extension GamesViewController: GamePickerControllerDelegate
@ -85,15 +206,32 @@ extension GamesViewController: UIPageViewControllerDelegate, UIPageViewControlle
{
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let index = self.supportedGameTypeIdentifiers.indexOf((viewController as! GamesCollectionViewController).gameTypeIdentifier)
let viewController = self.viewControllerForIndex(index! - 1)
let viewController = self.viewControllerForIndex(self.pageControl.currentPage - 1)
return viewController
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
let index = self.supportedGameTypeIdentifiers.indexOf((viewController as! GamesCollectionViewController).gameTypeIdentifier)
let viewController = self.viewControllerForIndex(index! + 1)
let viewController = self.viewControllerForIndex(self.pageControl.currentPage + 1)
return viewController
}
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)
{
if let viewController = pageViewController.viewControllers?.first as? GamesCollectionViewController, let gameCollection = viewController.gameCollection
{
let index = self.fetchedResultsController.fetchedObjects?.indexOf({ $0 as! GameCollection == gameCollection }) ?? 0
self.pageControl.currentPage = index
}
self.title = pageViewController.viewControllers?.first?.title
}
}
extension GamesViewController: NSFetchedResultsControllerDelegate
{
func controllerDidChangeContent(controller: NSFetchedResultsController)
{
self.updateSections()
}
}

View File

@ -11,9 +11,12 @@ import CoreData
import SNESDeltaCore
import Roxas
class GameSelectionViewController: UICollectionViewController
{
private let dataSource = GameCollectionViewDataSource()
private var backgroundView: RSTBackgroundView! = nil
required init?(coder aDecoder: NSCoder)
{
@ -21,7 +24,7 @@ class GameSelectionViewController: UICollectionViewController
self.title = NSLocalizedString("Games", comment: "")
self.dataSource.gameTypeIdentifiers = [kUTTypeSNESGame as String]
self.dataSource.supportedGameCollectionIdentifiers = [kUTTypeSNESGame as String, kUTTypeGBAGame as String]
self.dataSource.fetchedResultsController.delegate = self
self.dataSource.cellConfigurationHandler = self.configureCell
}
@ -37,11 +40,18 @@ class GameSelectionViewController: UICollectionViewController
{
layout.maximumBoxArtSize = CGSize(width: 200, height: 200)
}
self.backgroundView = RSTBackgroundView(frame: self.view.bounds)
self.backgroundView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.backgroundView.textLabel.text = NSLocalizedString("No Games", comment: "")
self.backgroundView.detailTextLabel.text = NSLocalizedString("You can import games by pressing the + button in the top right.", comment: "")
self.view.addSubview(self.backgroundView)
}
override func viewWillAppear(animated: Bool)
{
self.dataSource.update()
self.updateCollections()
super.viewWillAppear(animated)
}
@ -83,6 +93,23 @@ class GameSelectionViewController: UICollectionViewController
}
}
private extension GameSelectionViewController
{
func updateCollections()
{
if self.dataSource.fetchedResultsController.sections?.count ?? 0 == 0
{
self.backgroundView.hidden = false
self.collectionView?.hidden = true
}
else
{
self.backgroundView.hidden = true
self.collectionView?.hidden = false
}
}
}
// MARK: - <GamePickerControllerDelegate> -
extension GameSelectionViewController: GamePickerControllerDelegate
{
@ -97,6 +124,8 @@ extension GameSelectionViewController: NSFetchedResultsControllerDelegate
func controllerDidChangeContent(controller: NSFetchedResultsController)
{
self.collectionView?.reloadData()
self.updateCollections()
}
}

2
External/Roxas vendored

@ -1 +1 @@
Subproject commit 9789612ecbdfd17a8c94b52f4872ec70d44cc4aa
Subproject commit 12973b60eeddd6e5b3b398d5ec5b2dadd461810d