diff --git a/Common/Collection View/GameCollectionViewCell.swift b/Common/Collection View/GameCollectionViewCell.swift new file mode 100644 index 0000000..3e8707a --- /dev/null +++ b/Common/Collection View/GameCollectionViewCell.swift @@ -0,0 +1,137 @@ +// +// GameCollectionViewCell.swift +// Delta +// +// Created by Riley Testut on 10/21/15. +// Copyright © 2015 Riley Testut. All rights reserved. +// + +import UIKit + +class GameCollectionViewCell: UICollectionViewCell +{ + let imageView = BoxArtImageView() + let nameLabel = UILabel() + + private var maximumBoxArtSize: CGSize = CGSize(width: 100, height: 100) { + didSet + { + self.imageViewWidthConstraint.constant = self.maximumBoxArtSize.width + self.imageViewHeightConstraint.constant = self.maximumBoxArtSize.height + + self.nameLabelVerticalSpacingConstraint.constant = self.maximumBoxArtSize.height / 20.0 + self.nameLabelFocusedVerticalSpacingConstraint?.constant = self.maximumBoxArtSize.height / 20.0 + + } + } + + private var imageViewWidthConstraint: NSLayoutConstraint! + private var imageViewHeightConstraint: NSLayoutConstraint! + + private var nameLabelBottomAnchorConstraint: NSLayoutConstraint! + + private var nameLabelVerticalSpacingConstraint: NSLayoutConstraint! + private var nameLabelFocusedVerticalSpacingConstraint: NSLayoutConstraint? + + override init(frame: CGRect) + { + super.init(frame: frame) + + self.configureSubviews() + } + + required init?(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + + self.configureSubviews() + } + + private func configureSubviews() + { + // Fix super annoying Unsatisfiable Constraints message in debugger + self.contentView.translatesAutoresizingMaskIntoConstraints = false + + self.imageView.translatesAutoresizingMaskIntoConstraints = false + self.contentView.addSubview(self.imageView) + + self.nameLabel.translatesAutoresizingMaskIntoConstraints = false + self.nameLabel.font = UIFont.boldSystemFontOfSize(12) + self.nameLabel.textAlignment = .Center + self.nameLabel.numberOfLines = 0 + self.contentView.addSubview(self.nameLabel) + + + // Auto Layout + + self.imageView.leadingAnchor.constraintEqualToAnchor(self.contentView.leadingAnchor).active = true + self.imageView.trailingAnchor.constraintEqualToAnchor(self.contentView.trailingAnchor).active = true + self.imageView.topAnchor.constraintEqualToAnchor(self.contentView.topAnchor).active = true + + let verticalSpacingConstant = self.maximumBoxArtSize.height / 20.0 + + self.nameLabelVerticalSpacingConstraint = self.nameLabel.topAnchor.constraintEqualToAnchor(self.imageView.bottomAnchor, constant: verticalSpacingConstant) + + #if os(tvOS) + + self.nameLabelVerticalSpacingConstraint.active = false + + self.nameLabelFocusedVerticalSpacingConstraint = self.nameLabel.topAnchor.constraintEqualToAnchor(self.imageView.focusedFrameGuide.bottomAnchor, constant: verticalSpacingConstant) + self.nameLabelFocusedVerticalSpacingConstraint?.active = true + #else + self.nameLabelVerticalSpacingConstraint.active = true + #endif + + self.nameLabel.leadingAnchor.constraintEqualToAnchor(self.contentView.leadingAnchor).active = true + self.nameLabel.trailingAnchor.constraintEqualToAnchor(self.contentView.trailingAnchor).active = true + + self.nameLabelBottomAnchorConstraint = self.nameLabel.bottomAnchor.constraintEqualToAnchor(self.contentView.bottomAnchor) + self.nameLabelBottomAnchorConstraint.active = true + + self.imageViewWidthConstraint = self.imageView.widthAnchor.constraintEqualToConstant(self.maximumBoxArtSize.width) + self.imageViewWidthConstraint.active = true + + self.imageViewHeightConstraint = self.imageView.heightAnchor.constraintEqualToConstant(self.maximumBoxArtSize.height) + self.imageViewHeightConstraint.active = true + } + + override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) + { + guard let attributes = layoutAttributes as? GameCollectionViewLayoutAttributes else { return } + + self.maximumBoxArtSize = attributes.maximumBoxArtSize + } + + override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) + { + super.didUpdateFocusInContext(context, withAnimationCoordinator: coordinator) + + coordinator.addCoordinatedAnimations({ + + if context.nextFocusedView == self + { + self.nameLabelBottomAnchorConstraint?.active = false + self.nameLabelVerticalSpacingConstraint.active = false + + self.nameLabelFocusedVerticalSpacingConstraint?.active = true + + self.nameLabel.textColor = UIColor.whiteColor() + + } + else + { + self.nameLabelFocusedVerticalSpacingConstraint?.active = false + + self.nameLabelBottomAnchorConstraint?.active = true + self.nameLabelVerticalSpacingConstraint.active = true + + self.nameLabel.textColor = UIColor.blackColor() + } + + self.layoutIfNeeded() + + }, completion: nil) + } + + +} diff --git a/Common/Collection View/GameCollectionViewDataSource.swift b/Common/Collection View/GameCollectionViewDataSource.swift new file mode 100644 index 0000000..0e20705 --- /dev/null +++ b/Common/Collection View/GameCollectionViewDataSource.swift @@ -0,0 +1,122 @@ +// +// GameCollectionViewDataSource.swift +// Delta +// +// Created by Riley Testut on 10/30/15. +// Copyright © 2015 Riley Testut. All rights reserved. +// + +import UIKit +import CoreData + +class GameCollectionViewDataSource: NSObject +{ + var gameTypeIdentifiers: [String] = [] { + didSet + { + self.updateFetchedResultsController() + } + } + + var sectionTitles: [String] = [] + + var cellConfigurationHandler: ((GameCollectionViewCell, Game) -> Void)? + + private(set) var fetchedResultsController: NSFetchedResultsController = NSFetchedResultsController() + private let prototypeCell = GameCollectionViewCell(frame: CGRectZero) + + // MARK: - Update - + + func update() + { + do + { + try self.fetchedResultsController.performFetch() + } + catch let error as NSError + { + print(error) + } + } + + private func updateFetchedResultsController() + { + let previousDelegate = self.fetchedResultsController.delegate + + let fetchRequest = Game.fetchRequest() + + var predicates: [NSPredicate] = [] + + for typeIdentifier in self.gameTypeIdentifiers + { + let predicate = NSPredicate(format: "%K == %@", GameAttributes.typeIdentifier.rawValue, typeIdentifier) + predicates.append(predicate) + } + + if predicates.count > 0 + { + fetchRequest.predicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicates) + } + + fetchRequest.sortDescriptors = [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 + + self.update() + } + + // MARK: - Collection View - + + private func configureCell(cell: GameCollectionViewCell, forIndexPath indexPath: NSIndexPath) + { + let game = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Game + + if let handler = self.cellConfigurationHandler + { + handler(cell, game) + } + } +} + +extension GameCollectionViewDataSource: UICollectionViewDataSource +{ + func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int + { + return self.fetchedResultsController.sections?.count ?? 0 + } + + func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int + { + let count = self.fetchedResultsController.sections?[section].numberOfObjects ?? 0 + return count + } + + func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell + { + let cell = collectionView.dequeueReusableCellWithReuseIdentifier("GameCell", forIndexPath: indexPath) as! GameCollectionViewCell + + self.configureCell(cell, forIndexPath: indexPath) + + return cell + } +} + +extension GameCollectionViewDataSource: UICollectionViewDelegate +{ + func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize + { + if let layoutAttributes = collectionViewLayout.layoutAttributesForItemAtIndexPath(indexPath) + { + self.prototypeCell.applyLayoutAttributes(layoutAttributes) + } + + self.configureCell(self.prototypeCell, forIndexPath: indexPath) + self.prototypeCell.layoutIfNeeded() + + let size = self.prototypeCell.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) + return size + } + + +} diff --git a/Common/Collection View/GameCollectionViewLayout.swift b/Common/Collection View/GameCollectionViewLayout.swift new file mode 100644 index 0000000..41720a7 --- /dev/null +++ b/Common/Collection View/GameCollectionViewLayout.swift @@ -0,0 +1,113 @@ +// +// GameCollectionViewLayout.swift +// Delta +// +// Created by Riley Testut on 10/24/15. +// Copyright © 2015 Riley Testut. All rights reserved. +// + +import UIKit + +class GameCollectionViewLayout: UICollectionViewFlowLayout +{ + var maximumBoxArtSize = CGSize(width: 100, height: 100) { + didSet + { + self.invalidateLayout() + } + } + + override class func layoutAttributesClass() -> AnyClass + { + return GameCollectionViewLayoutAttributes.self + } + + override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? + { + // Need to implement this method as well in case the view controller calls it (which it does) + + let layoutAttributes = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as! GameCollectionViewLayoutAttributes + self.configureLayoutAttributes(layoutAttributes) + + return layoutAttributes + } + + override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? + { + guard let collectionView = self.collectionView else { return nil } + + let maximumItemsPerRow = floor((collectionView.bounds.width - self.minimumInteritemSpacing) / (self.maximumBoxArtSize.width + self.minimumInteritemSpacing)) + let interitemSpacing = (collectionView.bounds.width - maximumItemsPerRow * self.maximumBoxArtSize.width) / (maximumItemsPerRow + 1) + + self.sectionInset = UIEdgeInsets(top: interitemSpacing, left: interitemSpacing, bottom: interitemSpacing, right: interitemSpacing) + + let layoutAttributes = super.layoutAttributesForElementsInRect(rect)?.map({ $0.copy() }) as! [GameCollectionViewLayoutAttributes] + + var minimumY: CGFloat? = nil + var maximumY: CGFloat? = nil + var tempLayoutAttributes: [GameCollectionViewLayoutAttributes] = [] + + for (index, attributes) in layoutAttributes.enumerate() + { + // Ensure equal spacing between items (that also match the section insets) + if index > 0 + { + let previousLayoutAttributes = layoutAttributes[index - 1] + + if abs(attributes.frame.minX - self.sectionInset.left) > 1 + { + attributes.frame.origin.x = previousLayoutAttributes.frame.maxX + interitemSpacing + } + } + + self.configureLayoutAttributes(attributes) + + if let maxY = maximumY, minY = minimumY + { + // If attributes.frame.minY is greater than maximumY, then it is a new row + // In this case, we need to align all the previous tempLayoutAttributes to the same Y-value + if attributes.frame.minY > maxY + { + for tempAttributes in tempLayoutAttributes + { + tempAttributes.frame.origin.y = minY + } + + // Reset tempLayoutAttributes + tempLayoutAttributes.removeAll() + minimumY = nil + maximumY = nil + } + } + + if minimumY == nil || attributes.frame.minY < minimumY! + { + minimumY = attributes.frame.minY + } + + if maximumY == nil || attributes.frame.maxY > maximumY! + { + maximumY = attributes.frame.maxY + } + + tempLayoutAttributes.append(attributes) + } + + // Handle the remaining tempLayoutAttributes + if let minimumY = minimumY + { + for tempAttributes in tempLayoutAttributes + { + tempAttributes.frame.origin.y = minimumY + } + } + + return layoutAttributes + } + + // You'd think you could just do this in layoutAttributesForItemAtIndexPath, but alas layoutAttributesForElementsInRect does not call that method :( + private func configureLayoutAttributes(layoutAttributes: GameCollectionViewLayoutAttributes) + { + layoutAttributes.maximumBoxArtSize = self.maximumBoxArtSize + } +} diff --git a/Common/Collection View/GameCollectionViewLayoutAttributes.swift b/Common/Collection View/GameCollectionViewLayoutAttributes.swift new file mode 100644 index 0000000..c4b1d0f --- /dev/null +++ b/Common/Collection View/GameCollectionViewLayoutAttributes.swift @@ -0,0 +1,32 @@ +// +// GameCollectionViewLayoutAttributes.swift +// Delta +// +// Created by Riley Testut on 10/28/15. +// Copyright © 2015 Riley Testut. All rights reserved. +// + +import UIKit + +class GameCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes +{ + var maximumBoxArtSize = CGSize(width: 100, height: 100) + + override func copyWithZone(zone: NSZone) -> AnyObject + { + let copy = super.copyWithZone(zone) as! GameCollectionViewLayoutAttributes + copy.maximumBoxArtSize = self.maximumBoxArtSize + + return copy + } + + override func isEqual(object: AnyObject?) -> Bool + { + guard super.isEqual(object) else { return false } + guard let attributes = object as? GameCollectionViewLayoutAttributes else { return false } + + guard CGSizeEqualToSize(self.maximumBoxArtSize, attributes.maximumBoxArtSize) else { return false } + + return true + } +} diff --git a/Common/Components/BoxArtImageView.swift b/Common/Components/BoxArtImageView.swift new file mode 100644 index 0000000..47c8cdd --- /dev/null +++ b/Common/Components/BoxArtImageView.swift @@ -0,0 +1,55 @@ +// +// BoxArtImageView.swift +// Delta +// +// Created by Riley Testut on 10/27/15. +// Copyright © 2015 Riley Testut. All rights reserved. +// + +import UIKit + +class BoxArtImageView: UIImageView +{ + override var image: UIImage? { + didSet + { + if image == nil + { + image = UIImage(named: "BoxArt") + } + + } + } + + init() + { + super.init(image: nil) + + self.initialize() + } + + override init(frame: CGRect) + { + super.init(frame: frame) + + self.image = nil + + self.initialize() + } + + required init?(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + + self.initialize() + } + + private func initialize() + { + #if os(tvOS) + self.adjustsImageWhenAncestorFocused = true + #endif + + self.contentMode = .ScaleAspectFit + } +} diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index 3f5c00c..3187e31 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -14,17 +14,22 @@ BF27CC8D1BC9FE5300A20D89 /* Pods.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 22506DA00971C4300AF90A35 /* Pods.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; }; BF27CC8F1BCA010200A20D89 /* GamePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB28441BC9DA7B001D0C83 /* GamePickerController.swift */; }; + 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 */; }; BF2A53FE1BB74FC60052BD0C /* ZipZap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BF4566E81BC090B6007BFA1A /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BF4566E61BC090B6007BFA1A /* Model.xcdatamodeld */; }; BF4566E91BC090B6007BFA1A /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = BF4566E61BC090B6007BFA1A /* Model.xcdatamodeld */; }; - BF46894F1AAC46EF00A2586D /* DirectoryContentsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF46894E1AAC46EF00A2586D /* DirectoryContentsDataSource.swift */; }; + BF50DC421BD851740024C720 /* GameCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF50DC411BD851740024C720 /* GameCollectionViewCell.swift */; }; + BF50DC431BD851740024C720 /* GameCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF50DC411BD851740024C720 /* GameCollectionViewCell.swift */; }; BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */; }; BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF5E7F451B9A652600AE44F8 /* Settings.storyboard */; }; BF6BB23F1BB73FE800CCF94A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BB23E1BB73FE800CCF94A /* AppDelegate.swift */; }; - BF6BB2411BB73FE800CCF94A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BB2401BB73FE800CCF94A /* ViewController.swift */; }; + BF6BB2411BB73FE800CCF94A /* GameSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6BB2401BB73FE800CCF94A /* GameSelectionViewController.swift */; }; BF6BB2441BB73FE800CCF94A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2421BB73FE800CCF94A /* Main.storyboard */; }; BF6BB2461BB73FE800CCF94A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6BB2451BB73FE800CCF94A /* Assets.xcassets */; }; BF70798C1B6B464B0019077C /* ZipZap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF70798B1B6B464B0019077C /* ZipZap.framework */; }; @@ -39,8 +44,12 @@ BF8624AA1BB7464B00C12EEE /* DeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF9F4FCE1AAD7B87004C9500 /* DeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BF9F4FCF1AAD7B87004C9500 /* DeltaCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF9F4FCE1AAD7B87004C9500 /* DeltaCore.framework */; }; BF9F4FD01AAD7B87004C9500 /* DeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BF9F4FCE1AAD7B87004C9500 /* DeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + BFA5342A1BDC6B520088F1BE /* GameCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA534291BDC6B520088F1BE /* GameCollectionViewLayout.swift */; }; + BFA5342B1BDC6B520088F1BE /* GameCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA534291BDC6B520088F1BE /* GameCollectionViewLayout.swift */; }; BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FEC1B8AA4FA00495943 /* Settings.swift */; }; BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */; }; + BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */; }; + BFB141191BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */; }; BFC134E11AAD82460087AD7B /* SNESDeltaCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */; }; BFC134E21AAD82470087AD7B /* SNESDeltaCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; BFDB28451BC9DA7B001D0C83 /* GamePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDB28441BC9DA7B001D0C83 /* GamePickerController.swift */; }; @@ -50,12 +59,14 @@ BFDE393D1BC0CEDF003F72E8 /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDE39391BC0CEDF003F72E8 /* Game.swift */; }; BFEC732D1AAECC4A00650035 /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; }; BFEC732E1AAECC4A00650035 /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFEC732C1AAECC4A00650035 /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + BFF1E5641BE04CAF000E9EF6 /* BoxArtImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF1E5631BE04CAF000E9EF6 /* BoxArtImageView.swift */; }; + BFF1E5651BE04CAF000E9EF6 /* BoxArtImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF1E5631BE04CAF000E9EF6 /* BoxArtImageView.swift */; }; + BFF4EA011BE1B2420056AAA4 /* GameCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF4EA001BE1B2420056AAA4 /* GameCollectionViewLayoutAttributes.swift */; }; + BFF4EA021BE1B2420056AAA4 /* GameCollectionViewLayoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFF4EA001BE1B2420056AAA4 /* GameCollectionViewLayoutAttributes.swift */; }; BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; }; - BFFA71DF1AAC406100EE9DD1 /* GamesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DE1AAC406100EE9DD1 /* GamesViewController.swift */; }; BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFFA71E01AAC406100EE9DD1 /* Main.storyboard */; }; BFFA71E71AAC406100EE9DD1 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFFA71E51AAC406100EE9DD1 /* LaunchScreen.xib */; }; BFFB709F1AF99B1700DE56FE /* EmulationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFB709E1AF99B1700DE56FE /* EmulationViewController.swift */; }; - BFFB70A11AF99DFB00DE56FE /* EmulationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = BFFB70A01AF99DFB00DE56FE /* EmulationViewController.xib */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -99,13 +110,17 @@ BF090CF31B490D8300DCAB45 /* UIDevice+Vibration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIDevice+Vibration.m"; sourceTree = ""; }; BF27CC861BC9E3C600A20D89 /* Delta.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = Delta.entitlements; sourceTree = ""; }; BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pods.framework; path = "Pods/../build/Debug-appletvos/Pods.framework"; sourceTree = ""; }; + 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 = ""; }; + BF27CC981BCC8AE600A20D89 /* GamesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamesViewController.swift; sourceTree = ""; }; BF4566E71BC090B6007BFA1A /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; - BF46894E1AAC46EF00A2586D /* DirectoryContentsDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryContentsDataSource.swift; sourceTree = ""; }; + BF50DC411BD851740024C720 /* GameCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollectionViewCell.swift; path = "Collection View/GameCollectionViewCell.swift"; sourceTree = ""; }; BF5E7F431B9A650B00AE44F8 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; BF5E7F451B9A652600AE44F8 /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; BF6BB23C1BB73FE800CCF94A /* Delta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Delta.app; sourceTree = BUILT_PRODUCTS_DIR; }; BF6BB23E1BB73FE800CCF94A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - BF6BB2401BB73FE800CCF94A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + BF6BB2401BB73FE800CCF94A /* GameSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameSelectionViewController.swift; sourceTree = ""; }; BF6BB2431BB73FE800CCF94A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; BF6BB2451BB73FE800CCF94A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; BF6BB2471BB73FE800CCF94A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -113,21 +128,23 @@ BF762E9D1BC19D31002C8866 /* DatabaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseManager.swift; sourceTree = ""; }; BF762EAA1BC1B076002C8866 /* NSManagedObject+Conveniences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Conveniences.swift"; sourceTree = ""; }; BF9F4FCE1AAD7B87004C9500 /* DeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BFA534291BDC6B520088F1BE /* GameCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollectionViewLayout.swift; path = "Collection View/GameCollectionViewLayout.swift"; sourceTree = ""; }; BFAA1FEC1B8AA4FA00495943 /* Settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; BFAA1FF31B8AD7F900495943 /* ControllersSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControllersSettingsViewController.swift; sourceTree = ""; }; + BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollectionViewDataSource.swift; path = "Collection View/GameCollectionViewDataSource.swift"; sourceTree = ""; }; BFC134E01AAD82460087AD7B /* SNESDeltaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SNESDeltaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BFDB28441BC9DA7B001D0C83 /* GamePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamePickerController.swift; sourceTree = ""; }; BFDE39381BC0CEDF003F72E8 /* Game+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Game+CoreDataProperties.swift"; sourceTree = ""; }; BFDE39391BC0CEDF003F72E8 /* Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Game.swift; sourceTree = ""; }; BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BFF1E5631BE04CAF000E9EF6 /* BoxArtImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BoxArtImageView.swift; path = Components/BoxArtImageView.swift; sourceTree = ""; }; + BFF4EA001BE1B2420056AAA4 /* GameCollectionViewLayoutAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCollectionViewLayoutAttributes.swift; path = "Collection View/GameCollectionViewLayoutAttributes.swift"; sourceTree = ""; }; BFFA71D71AAC406100EE9DD1 /* Delta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Delta.app; sourceTree = BUILT_PRODUCTS_DIR; }; BFFA71DB1AAC406100EE9DD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - BFFA71DE1AAC406100EE9DD1 /* GamesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GamesViewController.swift; sourceTree = ""; }; BFFA71E11AAC406100EE9DD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; BFFA71E61AAC406100EE9DD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; BFFB709E1AF99B1700DE56FE /* EmulationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmulationViewController.swift; sourceTree = ""; }; - BFFB70A01AF99DFB00DE56FE /* EmulationViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EmulationViewController.xib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -137,6 +154,7 @@ files = ( BF2A53FB1BB74FC10052BD0C /* SNESDeltaCore.framework in Frameworks */, BF8624A91BB7464B00C12EEE /* DeltaCore.framework in Frameworks */, + BF27CC951BCB7B7A00A20D89 /* GameController.framework in Frameworks */, BF2A53FD1BB74FC60052BD0C /* ZipZap.framework in Frameworks */, BF8624881BB743FE00C12EEE /* Roxas.framework in Frameworks */, AF0535CD7331785FA15E0864 /* Pods.framework in Frameworks */, @@ -180,6 +198,8 @@ BF4566E31BC09026007BFA1A /* Common */ = { isa = PBXGroup; children = ( + BFF1E55F1BE04BF6000E9EF6 /* Components */, + BF9257571BD8244800B109DA /* Collection View */, BF4566E41BC0902E007BFA1A /* Database */, BFDB28431BC9D9D1001D0C83 /* Importing */, BF762EA91BC1B044002C8866 /* Extensions */, @@ -209,8 +229,8 @@ BF46894D1AAC469800A2586D /* Game Selection */ = { isa = PBXGroup; children = ( - BF46894E1AAC46EF00A2586D /* DirectoryContentsDataSource.swift */, - BFFA71DE1AAC406100EE9DD1 /* GamesViewController.swift */, + BF27CC981BCC8AE600A20D89 /* GamesViewController.swift */, + BF27CC961BCC890700A20D89 /* GamesCollectionViewController.swift */, ); path = "Game Selection"; sourceTree = ""; @@ -220,7 +240,8 @@ children = ( BF6BB23E1BB73FE800CCF94A /* AppDelegate.swift */, BF6BB2421BB73FE800CCF94A /* Main.storyboard */, - BF6BB2401BB73FE800CCF94A /* ViewController.swift */, + BF6BB2401BB73FE800CCF94A /* GameSelectionViewController.swift */, + BF27CC901BCB156200A20D89 /* EmulationViewController.swift */, BF8624621BB7400E00C12EEE /* Supporting Files */, ); path = DeltaTV; @@ -242,9 +263,21 @@ path = "Supporting Files"; sourceTree = ""; }; + BF9257571BD8244800B109DA /* Collection View */ = { + isa = PBXGroup; + children = ( + BF50DC411BD851740024C720 /* GameCollectionViewCell.swift */, + BFA534291BDC6B520088F1BE /* GameCollectionViewLayout.swift */, + BFF4EA001BE1B2420056AAA4 /* GameCollectionViewLayoutAttributes.swift */, + BFB141171BE46934004FBF46 /* GameCollectionViewDataSource.swift */, + ); + name = "Collection View"; + sourceTree = ""; + }; BF9F4FCD1AAD7B25004C9500 /* Frameworks */ = { isa = PBXGroup; children = ( + BF27CC941BCB7B7A00A20D89 /* GameController.framework */, BF27CC8A1BC9FE4D00A20D89 /* Pods.framework */, BF70798B1B6B464B0019077C /* ZipZap.framework */, BFEC732C1AAECC4A00650035 /* Roxas.framework */, @@ -282,6 +315,14 @@ path = Resources; sourceTree = ""; }; + BFF1E55F1BE04BF6000E9EF6 /* Components */ = { + isa = PBXGroup; + children = ( + BFF1E5631BE04CAF000E9EF6 /* BoxArtImageView.swift */, + ); + name = Components; + sourceTree = ""; + }; BFFA71CE1AAC406100EE9DD1 = { isa = PBXGroup; children = ( @@ -333,7 +374,6 @@ isa = PBXGroup; children = ( BFFB709E1AF99B1700DE56FE /* EmulationViewController.swift */, - BFFB70A01AF99DFB00DE56FE /* EmulationViewController.xib */, ); path = Emulation; sourceTree = ""; @@ -396,6 +436,11 @@ BF6BB23B1BB73FE800CCF94A = { CreatedOnToolsVersion = 7.1; DevelopmentTeam = 6XVY5G3U44; + SystemCapabilities = { + com.apple.GameControllers.appletvos = { + enabled = 1; + }; + }; }; BFFA71D61AAC406100EE9DD1 = { CreatedOnToolsVersion = 6.3; @@ -442,7 +487,6 @@ buildActionMask = 2147483647; files = ( BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */, - BFFB70A11AF99DFB00DE56FE /* EmulationViewController.xib in Resources */, BFFA71E71AAC406100EE9DD1 /* LaunchScreen.xib in Resources */, BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */, BF27CC8E1BC9FEA200A20D89 /* Assets.xcassets in Resources */, @@ -549,11 +593,17 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - BF6BB2411BB73FE800CCF94A /* ViewController.swift in Sources */, + BFF4EA021BE1B2420056AAA4 /* GameCollectionViewLayoutAttributes.swift in Sources */, + BF6BB2411BB73FE800CCF94A /* GameSelectionViewController.swift in Sources */, BF27CC8F1BCA010200A20D89 /* GamePickerController.swift in Sources */, BFDE393D1BC0CEDF003F72E8 /* Game.swift in Sources */, BF762E9F1BC19D31002C8866 /* DatabaseManager.swift in Sources */, BFDE393B1BC0CEDF003F72E8 /* Game+CoreDataProperties.swift in Sources */, + BF27CC911BCB156200A20D89 /* EmulationViewController.swift in Sources */, + BFB141191BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */, + BFF1E5651BE04CAF000E9EF6 /* BoxArtImageView.swift in Sources */, + BF50DC431BD851740024C720 /* GameCollectionViewCell.swift in Sources */, + BFA5342B1BDC6B520088F1BE /* GameCollectionViewLayout.swift in Sources */, BF6BB23F1BB73FE800CCF94A /* AppDelegate.swift in Sources */, BF4566E91BC090B6007BFA1A /* Model.xcdatamodeld in Sources */, BF762EAC1BC1B076002C8866 /* NSManagedObject+Conveniences.swift in Sources */, @@ -564,15 +614,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BF27CC991BCC8AE600A20D89 /* GamesViewController.swift in Sources */, + BFF4EA011BE1B2420056AAA4 /* GameCollectionViewLayoutAttributes.swift in Sources */, BFAA1FED1B8AA4FA00495943 /* Settings.swift in Sources */, - BFFA71DF1AAC406100EE9DD1 /* GamesViewController.swift in Sources */, + BF50DC421BD851740024C720 /* GameCollectionViewCell.swift in Sources */, + BFB141181BE46934004FBF46 /* GameCollectionViewDataSource.swift in Sources */, BFFB709F1AF99B1700DE56FE /* EmulationViewController.swift in Sources */, BFAA1FF41B8AD7F900495943 /* ControllersSettingsViewController.swift in Sources */, + BF27CC971BCC890700A20D89 /* GamesCollectionViewController.swift in Sources */, BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */, BF4566E81BC090B6007BFA1A /* Model.xcdatamodeld in Sources */, BFDE393C1BC0CEDF003F72E8 /* Game.swift in Sources */, - BF46894F1AAC46EF00A2586D /* DirectoryContentsDataSource.swift in Sources */, + BFF1E5641BE04CAF000E9EF6 /* BoxArtImageView.swift in Sources */, BF762EAB1BC1B076002C8866 /* NSManagedObject+Conveniences.swift in Sources */, + BFA5342A1BDC6B520088F1BE /* GameCollectionViewLayout.swift in Sources */, BF762E9E1BC19D31002C8866 /* DatabaseManager.swift in Sources */, BF090CF41B490D8300DCAB45 /* UIDevice+Vibration.m in Sources */, BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */, @@ -638,7 +693,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution"; INFOPLIST_FILE = "DeltaTV/Supporting Files/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PROVISIONING_PROFILE = ""; diff --git a/Delta/Base.lproj/Main.storyboard b/Delta/Base.lproj/Main.storyboard index 0cb735c..43b9065 100644 --- a/Delta/Base.lproj/Main.storyboard +++ b/Delta/Base.lproj/Main.storyboard @@ -1,7 +1,7 @@ - + - + @@ -66,6 +66,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -74,6 +234,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Delta/Emulation/EmulationViewController.swift b/Delta/Emulation/EmulationViewController.swift index e51f0b5..bd8e303 100644 --- a/Delta/Emulation/EmulationViewController.swift +++ b/Delta/Emulation/EmulationViewController.swift @@ -9,13 +9,19 @@ import UIKit import DeltaCore +import SNESDeltaCore class EmulationViewController: UIViewController { //MARK: - Properties - /** Properties **/ - let game: Game - let emulatorCore: EmulatorCore + var game: Game! { + didSet + { + self.emulatorCore = SNESEmulatorCore(game: game) + } + } + private(set) var emulatorCore: EmulatorCore! //MARK: - Private Properties @IBOutlet private var controllerView: ControllerView! @@ -25,21 +31,14 @@ class EmulationViewController: UIViewController //MARK: - Initializers - /** Initializers **/ - required init(game: Game) + required init?(coder aDecoder: NSCoder) { - self.game = game - self.emulatorCore = EmulatorCore(game: game) - - super.init(nibName: "EmulationViewController", bundle: nil) + super.init(coder: aDecoder) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("updateControllers"), name: ExternalControllerDidConnectNotification, object: nil) NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("updateControllers"), name: ExternalControllerDidDisconnectNotification, object: nil) } - required init(coder aDecoder: NSCoder) { - fatalError("initWithCoder: not implemented.") - } - //MARK: - Overrides /** Overrides **/ diff --git a/Delta/Emulation/EmulationViewController.xib b/Delta/Emulation/EmulationViewController.xib deleted file mode 100644 index b191768..0000000 --- a/Delta/Emulation/EmulationViewController.xib +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Delta/Game Selection/DirectoryContentsDataSource.swift b/Delta/Game Selection/DirectoryContentsDataSource.swift deleted file mode 100644 index ad46f72..0000000 --- a/Delta/Game Selection/DirectoryContentsDataSource.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// DirectoryContentsDataSource.swift -// Delta -// -// Created by Riley Testut on 3/8/15. -// Copyright (c) 2015 Riley Testut. All rights reserved. -// - -import UIKit -import Foundation - -public extension DirectoryContentsDataSource -{ - func URLAtIndexPath(indexPath: NSIndexPath) -> NSURL - { - let URL = self.directoryContents[indexPath.row] - return URL - } -} - -public class DirectoryContentsDataSource: NSObject -{ - public let directoryURL: NSURL - public var tableViewCellIdentifier: String = "Cell" - - public var contentsUpdateHandler: (Void -> Void)? - public var cellConfigurationBlock: ((UITableViewCell, NSIndexPath, NSURL) -> Void)? - - private let fileDescriptor: Int32 - private let directoryMonitorDispatchQueue: dispatch_queue_t - private let directoryMonitorDispatchSource: dispatch_source_t! - - private var directoryContents: [NSURL] - - required public init?(directoryURL: NSURL) - { - self.directoryURL = directoryURL - self.fileDescriptor = open(self.directoryURL.fileSystemRepresentation, O_EVTONLY) - - self.directoryMonitorDispatchQueue = dispatch_queue_create("com.rileytestut.DirectoryContentsDataSource", DISPATCH_QUEUE_SERIAL) - self.directoryMonitorDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, UInt(self.fileDescriptor), DISPATCH_VNODE_WRITE, self.directoryMonitorDispatchQueue) - - self.directoryContents = [NSURL]() - - super.init() - - if self.fileDescriptor < 0 - { - return nil - } - - if self.directoryMonitorDispatchSource == nil - { - close(self.fileDescriptor); - - return nil - } - - dispatch_source_set_event_handler(self.directoryMonitorDispatchSource, { - self.didUpdateDirectoryContents() - }); - - dispatch_source_set_cancel_handler(self.directoryMonitorDispatchSource, { - close(self.fileDescriptor); - }); - - dispatch_resume(self.directoryMonitorDispatchSource); - - self.didUpdateDirectoryContents() - } - - deinit - { - if self.fileDescriptor >= 0 - { - close(self.fileDescriptor); - } - - if self.directoryMonitorDispatchSource != nil - { - dispatch_source_cancel(self.directoryMonitorDispatchSource); - } - } -} - -private extension DirectoryContentsDataSource -{ - func didUpdateDirectoryContents() - { - do - { - self.directoryContents = try NSFileManager.defaultManager().contentsOfDirectoryAtURL(self.directoryURL, includingPropertiesForKeys: nil, options:[NSDirectoryEnumerationOptions.SkipsSubdirectoryDescendants, NSDirectoryEnumerationOptions.SkipsHiddenFiles]) - } - catch let error as NSError - { - print("\(error) \(error.userInfo)") - } - - self.contentsUpdateHandler?() - } -} - -extension DirectoryContentsDataSource: UITableViewDataSource -{ - public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int - { - return self.directoryContents.count - } - - public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell - { - let tableViewCell = tableView.dequeueReusableCellWithIdentifier(self.tableViewCellIdentifier, forIndexPath: indexPath) as UITableViewCell - - self.cellConfigurationBlock?(tableViewCell, indexPath, self.URLAtIndexPath(indexPath)) - - return tableViewCell - } -} \ No newline at end of file diff --git a/Delta/Game Selection/GamesCollectionViewController.swift b/Delta/Game Selection/GamesCollectionViewController.swift new file mode 100644 index 0000000..ca620a6 --- /dev/null +++ b/Delta/Game Selection/GamesCollectionViewController.swift @@ -0,0 +1,86 @@ +// +// GamesCollectionViewController.swift +// Delta +// +// Created by Riley Testut on 10/12/15. +// Copyright © 2015 Riley Testut. All rights reserved. +// + +import UIKit +import CoreData + +import DeltaCore + +class GamesCollectionViewController: UICollectionViewController +{ + var gameTypeIdentifier: String! { + didSet + { + self.dataSource.gameTypeIdentifiers = [self.gameTypeIdentifier] + } + } + + private let dataSource = GameCollectionViewDataSource() + + required init?(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + + self.dataSource.fetchedResultsController.delegate = self + self.dataSource.cellConfigurationHandler = self.configureCell + } + + override func viewDidLoad() + { + super.viewDidLoad() + + self.collectionView?.dataSource = self.dataSource + self.collectionView?.delegate = self.dataSource + + if let layout = self.collectionViewLayout as? GameCollectionViewLayout + { + layout.maximumBoxArtSize = CGSize(width: 100, height: 100) + } + } + + override func viewWillAppear(animated: Bool) + { + self.dataSource.update() + + super.viewWillAppear(animated) + } + + override func didReceiveMemoryWarning() + { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + // MARK: - Navigation - + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) + { + guard let viewController = segue.destinationViewController as? EmulationViewController else { return } + + let indexPath = self.collectionView?.indexPathsForSelectedItems()?.first + let game = self.dataSource.fetchedResultsController.objectAtIndexPath(indexPath!) as! Game + + viewController.game = game + } + + // MARK: - Collection View - + + private func configureCell(cell: GameCollectionViewCell, game: Game) + { + cell.nameLabel.text = game.name + } +} + +extension GamesCollectionViewController: NSFetchedResultsControllerDelegate +{ + func controllerDidChangeContent(controller: NSFetchedResultsController) + { + self.collectionView?.reloadData() + } +} diff --git a/Delta/Game Selection/GamesViewController.swift b/Delta/Game Selection/GamesViewController.swift index 7fbcd70..bf89e8c 100644 --- a/Delta/Game Selection/GamesViewController.swift +++ b/Delta/Game Selection/GamesViewController.swift @@ -2,88 +2,49 @@ // GamesViewController.swift // Delta // -// Created by Riley Testut on 3/8/15. -// Copyright (c) 2015 Riley Testut. All rights reserved. +// Created by Riley Testut on 10/12/15. +// Copyright © 2015 Riley Testut. All rights reserved. // import UIKit -import DeltaCore -class GamesViewController: UITableViewController +import SNESDeltaCore + +class GamesViewController: UIViewController { - let directoryContentsDataSource: DirectoryContentsDataSource? + var pageViewController: UIPageViewController! = nil - override init(style: UITableViewStyle) - { - if let documentsDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first - { - self.directoryContentsDataSource = DirectoryContentsDataSource(directoryURL: documentsDirectoryURL) - } - else - { - self.directoryContentsDataSource = nil - } - - super.init(style: style) - } + let supportedGameTypeIdentifiers = [kUTTypeSNESGame as String] - required init?(coder aDecoder: NSCoder) - { - if let documentsDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first - { - self.directoryContentsDataSource = DirectoryContentsDataSource(directoryURL: documentsDirectoryURL) - } - else - { - self.directoryContentsDataSource = nil - } - - super.init(coder: aDecoder) - } - override func viewDidLoad() { super.viewDidLoad() - self.tableView.dataSource = self.directoryContentsDataSource + self.automaticallyAdjustsScrollViewInsets = false - self.directoryContentsDataSource!.contentsUpdateHandler = { - dispatch_async(dispatch_get_main_queue(), { - self.tableView.reloadData() - }) - } + self.pageViewController = self.childViewControllers.first as? UIPageViewController + self.pageViewController.dataSource = self + self.pageViewController.delegate = self - self.directoryContentsDataSource?.cellConfigurationBlock = { (cell, indexPath, URL) in - cell.textLabel?.text = URL.lastPathComponent - } + let viewController = self.viewControllerForIndex(0) + self.pageViewController.setViewControllers([viewController], direction: .Forward, animated: false, completion: nil) + + // Do any additional setup after loading the view. } - override func viewDidAppear(animated: Bool) + override func viewDidLayoutSubviews() { - super.viewDidAppear(animated) + super.viewDidLayoutSubviews() - if self.directoryContentsDataSource == nil - { - let alertController = UIAlertController(title: NSLocalizedString("Invalid Games Directory", comment: ""), message: NSLocalizedString("Please ensure the current games directory exists, then restart Delta.", comment: ""), preferredStyle: .Alert) - alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: UIAlertActionStyle.Cancel, handler: nil)) - - self.presentViewController(alertController, animated: true, completion: nil) - } + let viewController = self.pageViewController.viewControllers?.first as! GamesCollectionViewController + viewController.collectionView?.contentInset.top = self.topLayoutGuide.length } - override func didReceiveMemoryWarning() - { + override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } - //MARK: - Settings - - - @IBAction func dismissSettingsViewController(segue: UIStoryboardSegue) - { - - } - // MARK: - Importing - @IBAction func importFiles() @@ -92,13 +53,23 @@ class GamesViewController: UITableViewController gamePickerController.delegate = self self.presentGamePickerController(gamePickerController, animated: true, completion: nil) } - - - //MARK: UITableViewDelegate - - override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) +} + +private extension GamesViewController +{ + func viewControllerForIndex(index: Int) -> GamesCollectionViewController { + var safeIndex = index % self.supportedGameTypeIdentifiers.count + if safeIndex < 0 + { + safeIndex = self.supportedGameTypeIdentifiers.count + safeIndex + } + + let viewController = self.storyboard?.instantiateViewControllerWithIdentifier("gamesCollectionViewController") as! GamesCollectionViewController + viewController.gameTypeIdentifier = self.supportedGameTypeIdentifiers[safeIndex] as String + viewController.collectionView?.contentInset.top = self.topLayoutGuide.length + return viewController } } @@ -106,7 +77,24 @@ extension GamesViewController: GamePickerControllerDelegate { func gamePickerController(gamePickerController: GamePickerController, didImportGames games: [Game]) { + DatabaseManager.sharedManager.save() print(games) } } +extension GamesViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource +{ + func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? + { + let index = self.supportedGameTypeIdentifiers.indexOf((viewController as! GamesCollectionViewController).gameTypeIdentifier) + let viewController = self.viewControllerForIndex(index! - 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) + return viewController + } +} diff --git a/DeltaTV/Base.lproj/Main.storyboard b/DeltaTV/Base.lproj/Main.storyboard index 6d2b8e4..f734464 100644 --- a/DeltaTV/Base.lproj/Main.storyboard +++ b/DeltaTV/Base.lproj/Main.storyboard @@ -1,25 +1,109 @@ - + - + - - + + - - - - - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DeltaTV/EmulationViewController.swift b/DeltaTV/EmulationViewController.swift new file mode 100644 index 0000000..f709124 --- /dev/null +++ b/DeltaTV/EmulationViewController.swift @@ -0,0 +1,59 @@ +// +// EmulationViewController.swift +// Delta +// +// Created by Riley Testut on 10/11/15. +// Copyright © 2015 Riley Testut. All rights reserved. +// + +import UIKit + +import DeltaCore +import SNESDeltaCore + +class EmulationViewController: UIViewController +{ + //MARK: - Properties - + /** Properties **/ + var game: Game! { + didSet + { + self.emulatorCore = SNESEmulatorCore(game: game) + } + } + private(set) var emulatorCore: EmulatorCore! + + //MARK: - Private Properties + @IBOutlet private var gameView: GameView! + + override func viewDidLoad() + { + super.viewDidLoad() + + self.emulatorCore.addGameView(self.gameView) + } + + override func viewDidAppear(animated: Bool) + { + super.viewDidAppear(animated) + + self.emulatorCore.startEmulation() + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + // Get the new view controller using segue.destinationViewController. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/DeltaTV/GameSelectionViewController.swift b/DeltaTV/GameSelectionViewController.swift new file mode 100644 index 0000000..9d0fb0b --- /dev/null +++ b/DeltaTV/GameSelectionViewController.swift @@ -0,0 +1,103 @@ +// +// ViewController.swift +// DeltaTV +// +// Created by Riley Testut on 9/26/15. +// Copyright © 2015 Riley Testut. All rights reserved. +// + +import UIKit +import CoreData + +import SNESDeltaCore + +class GameSelectionViewController: UICollectionViewController +{ + private let dataSource = GameCollectionViewDataSource() + + required init?(coder aDecoder: NSCoder) + { + super.init(coder: aDecoder) + + self.title = NSLocalizedString("Games", comment: "") + + self.dataSource.gameTypeIdentifiers = [kUTTypeSNESGame as String] + self.dataSource.fetchedResultsController.delegate = self + self.dataSource.cellConfigurationHandler = self.configureCell + } + + override func viewDidLoad() + { + super.viewDidLoad() + + self.collectionView?.dataSource = self.dataSource + self.collectionView?.delegate = self.dataSource + + if let layout = self.collectionViewLayout as? GameCollectionViewLayout + { + layout.maximumBoxArtSize = CGSize(width: 200, height: 200) + } + } + + override func viewWillAppear(animated: Bool) + { + self.dataSource.update() + + super.viewWillAppear(animated) + } + + override func didReceiveMemoryWarning() + { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + // MARK: - Importing - + + @IBAction func importFiles() + { + let gamePickerController = GamePickerController() + gamePickerController.delegate = self + self.presentGamePickerController(gamePickerController, animated: true, completion: nil) + } + + + // MARK: - Navigation - + + override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) + { + guard let viewController = segue.destinationViewController as? EmulationViewController else { return } + + let indexPath = self.collectionView?.indexPathsForSelectedItems()?.first + let game = self.dataSource.fetchedResultsController.objectAtIndexPath(indexPath!) as! Game + + viewController.game = game + } + + // MARK: - Collection View - + + private func configureCell(cell: GameCollectionViewCell, game: Game) + { + cell.nameLabel.font = UIFont.boldSystemFontOfSize(30) + cell.nameLabel.text = game.name + } +} + +// MARK: - - +extension GameSelectionViewController: GamePickerControllerDelegate +{ + func gamePickerController(gamePickerController: GamePickerController, didImportGames games: [Game]) + { + DatabaseManager.sharedManager.save() + print(games) + } +} + +extension GameSelectionViewController: NSFetchedResultsControllerDelegate +{ + func controllerDidChangeContent(controller: NSFetchedResultsController) + { + self.collectionView?.reloadData() + } +} + diff --git a/DeltaTV/ViewController.swift b/DeltaTV/ViewController.swift deleted file mode 100644 index 5bfb008..0000000 --- a/DeltaTV/ViewController.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// ViewController.swift -// DeltaTV -// -// Created by Riley Testut on 9/26/15. -// Copyright © 2015 Riley Testut. All rights reserved. -// - -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - - -} - diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj index 75deaf7..633224a 100644 --- a/Pods/Pods.xcodeproj/project.pbxproj +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -285,6 +285,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = DA312349A49333542E6F4B36B329960E /* Pods.release.xcconfig */; buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -300,7 +301,7 @@ OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_NAME = Pods; - SDKROOT = appletvos; + SDKROOT = iphoneos; SKIP_INSTALL = YES; TVOS_DEPLOYMENT_TARGET = 9.0; VERSIONING_SYSTEM = "apple-generic"; @@ -312,6 +313,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 087F6379BD69587FB559DDED4732D982 /* FileMD5Hash.xcconfig */; buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -324,7 +326,7 @@ MODULEMAP_FILE = "Target Support Files/FileMD5Hash/FileMD5Hash.modulemap"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = FileMD5Hash; - SDKROOT = appletvos; + SDKROOT = iphoneos; SKIP_INSTALL = YES; TVOS_DEPLOYMENT_TARGET = 9.0; VERSIONING_SYSTEM = "apple-generic"; @@ -336,6 +338,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 087F6379BD69587FB559DDED4732D982 /* FileMD5Hash.xcconfig */; buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -348,7 +351,7 @@ MODULEMAP_FILE = "Target Support Files/FileMD5Hash/FileMD5Hash.modulemap"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_NAME = FileMD5Hash; - SDKROOT = appletvos; + SDKROOT = iphoneos; SKIP_INSTALL = YES; TVOS_DEPLOYMENT_TARGET = 9.0; VERSIONING_SYSTEM = "apple-generic"; @@ -399,6 +402,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 977577C045EDA9D9D1F46E2598D19FC7 /* Pods.debug.xcconfig */; buildSettings = { + CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -414,7 +418,7 @@ OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; PRODUCT_NAME = Pods; - SDKROOT = appletvos; + SDKROOT = iphoneos; SKIP_INSTALL = YES; TVOS_DEPLOYMENT_TARGET = 9.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/Resources/Assets.xcassets/BoxArt.imageset/BoxArt-1.png b/Resources/Assets.xcassets/BoxArt.imageset/BoxArt-1.png new file mode 100644 index 0000000..5060eed Binary files /dev/null and b/Resources/Assets.xcassets/BoxArt.imageset/BoxArt-1.png differ diff --git a/Resources/Assets.xcassets/BoxArt.imageset/BoxArt.png b/Resources/Assets.xcassets/BoxArt.imageset/BoxArt.png new file mode 100644 index 0000000..2f8d3b7 Binary files /dev/null and b/Resources/Assets.xcassets/BoxArt.imageset/BoxArt.png differ diff --git a/Resources/Assets.xcassets/BoxArt.imageset/BoxArt@2x.png b/Resources/Assets.xcassets/BoxArt.imageset/BoxArt@2x.png new file mode 100644 index 0000000..5d5c1d4 Binary files /dev/null and b/Resources/Assets.xcassets/BoxArt.imageset/BoxArt@2x.png differ diff --git a/Resources/Assets.xcassets/BoxArt.imageset/BoxArt@3x.png b/Resources/Assets.xcassets/BoxArt.imageset/BoxArt@3x.png new file mode 100644 index 0000000..5060eed Binary files /dev/null and b/Resources/Assets.xcassets/BoxArt.imageset/BoxArt@3x.png differ diff --git a/Resources/Assets.xcassets/BoxArt.imageset/Contents.json b/Resources/Assets.xcassets/BoxArt.imageset/Contents.json new file mode 100644 index 0000000..ec0a894 --- /dev/null +++ b/Resources/Assets.xcassets/BoxArt.imageset/Contents.json @@ -0,0 +1,28 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "BoxArt.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "BoxArt@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "BoxArt@3x.png", + "scale" : "3x" + }, + { + "idiom" : "tv", + "filename" : "BoxArt-1.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file