Adds Delete action when previewing/long pressing games

This commit is contained in:
Riley Testut 2016-08-29 13:19:47 -07:00
parent 75903552f9
commit 89905618e4
7 changed files with 147 additions and 62 deletions

View File

@ -16,28 +16,26 @@ extension Action
case cancel
case destructive
case selected
}
}
extension Action.Style
{
var alertActionStyle: UIAlertActionStyle
{
switch self
var alertActionStyle: UIAlertActionStyle
{
case .default, .selected: return .default
case .cancel: return .cancel
case .destructive: return .destructive
switch self
{
case .default, .selected: return .default
case .cancel: return .cancel
case .destructive: return .destructive
}
}
}
var previewActionStyle: UIPreviewActionStyle
{
switch self
var previewActionStyle: UIPreviewActionStyle?
{
case .default, .cancel: return .default
case .destructive: return .destructive
case .selected: return .selected
switch self
{
case .default: return .default
case .destructive: return .destructive
case .selected: return .selected
case .cancel: return nil
}
}
}
}
@ -47,38 +45,52 @@ struct Action
let title: String
let style: Style
let action: ((Action) -> Void)?
var alertAction: UIAlertAction
{
let alertAction = UIAlertAction(title: self.title, style: self.style.alertActionStyle) { (action) in
self.action?(self)
}
return alertAction
}
var previewAction: UIPreviewAction
{
let previewAction = UIPreviewAction(title: self.title, style: self.style.previewActionStyle) { (action, viewController) in
self.action?(self)
}
return previewAction
}
}
// There is no public designated initializer for UIAlertAction or UIPreviewAction, so we cannot add our own convenience init
// If only there were factory initializers... https://github.com/apple/swift-evolution/pull/247
/*
extension UIAlertAction
{
convenience init(action: Action)
convenience init(_ action: Action)
{
self.init(title: action.title, style: action.style.alertActionStyle) { (alertAction) in
action.action?(action)
}
}
}
extension UIPreviewAction
{
convenience init(action: Action)
convenience init?(_ action: Action)
{
guard let previewActionStyle = action.style.previewActionStyle else { return nil }
self.init(title: action.title, style: previewActionStyle) { (previewAction, viewController) in
action.action?(action)
}
}
}
extension UIAlertController
{
convenience init(actions: [Action])
{
self.init(title: nil, message: nil, preferredStyle: .actionSheet)
for action in actions.alertActions
{
self.addAction(action)
}
}
}
extension RangeReplaceableCollection where Iterator.Element == Action
{
var alertActions: [UIAlertAction] {
let actions = self.map { UIAlertAction($0) }
return actions
}
var previewActions: [UIPreviewAction] {
let actions = self.flatMap { UIPreviewAction($0) }
return actions
}
}
*/

View File

@ -41,6 +41,7 @@ class Game: NSManagedObject, GameProtocol
@NSManaged var gameCollections: Set<GameCollection>
@NSManaged var previewSaveState: SaveState?
@NSManaged var saveStates: Set<SaveState>
var fileURL: URL {
var fileURL: URL!
@ -87,6 +88,13 @@ extension Game
managedObjectContext.delete(collection)
}
// Manually cascade deletion since SaveState.fileURL references Game, and so we need to ensure we delete SaveState's before Game
// Otherwise, we crash when accessing SaveState.game since it is nil
for saveState in self.saveStates
{
managedObjectContext.delete(saveState)
}
if managedObjectContext.hasChanges
{
managedObjectContext.saveWithErrorLogging()

View File

@ -37,9 +37,15 @@ class GameViewController: DeltaCore.GameViewController
override var game: GameProtocol? {
willSet {
self.emulatorCore?.removeObserver(self, forKeyPath: #keyPath(EmulatorCore.state), context: &kvoContext)
let game = self.game as? Game
NotificationCenter.default.removeObserver(self, name: .NSManagedObjectContextDidSave, object: game?.managedObjectContext)
}
didSet {
self.emulatorCore?.addObserver(self, forKeyPath: #keyPath(EmulatorCore.state), options: [.old], context: &kvoContext)
let game = self.game as? Game
NotificationCenter.default.addObserver(self, selector: #selector(GameViewController.managedObjectContextDidChange(with:)), name: .NSManagedObjectContextObjectsDidChange, object: game?.managedObjectContext)
}
}
@ -713,4 +719,16 @@ private extension GameViewController
{
self.updateAutoSaveState()
}
@objc func managedObjectContextDidChange(with notification: Notification)
{
guard let deletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject> else { return }
guard let game = self.game as? Game else { return }
if deletedObjects.contains(game)
{
self.emulatorCore?.gameViews.forEach { $0.inputImage = nil }
self.game = nil
}
}
}

View File

@ -53,6 +53,9 @@ extension GameCollectionViewController
layout.itemWidth = 90
self.registerForPreviewing(with: self, sourceView: self.collectionView!)
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(GameCollectionViewController.handleLongPressGesture(_:)))
self.collectionView?.addGestureRecognizer(longPressGestureRecognizer)
}
override func viewWillAppear(_ animated: Bool)
@ -187,6 +190,52 @@ private extension GameCollectionViewController
}
}
//MARK: - Game Actions -
private extension GameCollectionViewController
{
func actions(for game: Game) -> [Action]
{
let cancelAction = Action(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, action: nil)
let deleteAction = Action(title: NSLocalizedString("Delete", comment: ""), style: .destructive, action: { [unowned self] action in
self.delete(game)
})
return [cancelAction, deleteAction]
}
func delete(_ game: Game)
{
let confirmationAlertController = UIAlertController(title: NSLocalizedString("Are you sure you want to delete this game? All associated data, such as saves, save states, and cheat codes, will also be deleted.", comment: ""), message: nil, preferredStyle: .actionSheet)
confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Delete Game", comment: ""), style: .destructive, handler: { action in
DatabaseManager.shared.performBackgroundTask { (context) in
let temporaryGame = context.object(with: game.objectID) as! Game
context.delete(temporaryGame)
context.saveWithErrorLogging()
}
}))
confirmationAlertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel, handler: nil))
self.present(confirmationAlertController, animated: true, completion: nil)
}
@objc func handleLongPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer)
{
guard gestureRecognizer.state == .began else { return }
guard let indexPath = self.collectionView?.indexPathForItem(at: gestureRecognizer.location(in: self.collectionView)) else { return }
let game = self.dataSource.fetchedResultsController.object(at: indexPath)
let actions = self.actions(for: game)
let alertController = UIAlertController(actions: actions)
self.present(alertController, animated: true, completion: nil)
}
}
//MARK: - UIViewControllerPreviewingDelegate -
/// UIViewControllerPreviewingDelegate
extension GameCollectionViewController: UIViewControllerPreviewingDelegate
@ -212,6 +261,9 @@ extension GameCollectionViewController: UIViewControllerPreviewingDelegate
gameViewController.previewImage = UIImage(contentsOfFile: previewSaveState.imageFileURL.path)
}
let actions = self.actions(for: game).previewActions
gameViewController.overridePreviewActionItems = actions
return gameViewController
}

View File

@ -89,7 +89,7 @@ extension GamesViewController
if self.fetchedResultsController.performFetchIfNeeded()
{
self.updateSections()
self.updateSections(animated: false)
}
DispatchQueue.global().async {
@ -189,10 +189,13 @@ private extension GamesViewController
viewController.theme = self.theme
viewController.activeEmulatorCore = self.activeEmulatorCore
// Need to set content inset here AND willTransitionTo callback to ensure its correct for all edge cases
viewController.collectionView?.contentInset.top = self.topLayoutGuide.length
return viewController
}
func updateSections()
func updateSections(animated: Bool)
{
let sections = self.fetchedResultsController.sections?.first?.numberOfObjects ?? 0
self.pageControl.numberOfPages = sections
@ -214,7 +217,7 @@ private extension GamesViewController
}
self.navigationController?.setToolbarHidden(sections < 2, animated: self.view.window != nil)
self.navigationController?.setToolbarHidden(sections < 2, animated: animated)
if sections > 0
{
@ -223,8 +226,8 @@ private extension GamesViewController
{
if let viewController = self.viewControllerForIndex(0)
{
self.pageViewController.view.isHidden = false
self.backgroundView.isHidden = true
self.pageViewController.view.setHidden(false, animated: animated)
self.backgroundView.setHidden(true, animated: animated)
self.pageViewController.setViewControllers([viewController], direction: .forward, animated: false, completion: nil)
@ -240,11 +243,8 @@ private extension GamesViewController
{
self.title = NSLocalizedString("Games", comment: "")
if !self.pageViewController.view.isHidden
{
self.pageViewController.view.isHidden = true
self.backgroundView.isHidden = false
}
self.pageViewController.view.setHidden(true, animated: animated)
self.backgroundView.setHidden(false, animated: animated)
}
}
}
@ -313,6 +313,6 @@ extension GamesViewController: NSFetchedResultsControllerDelegate
{
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
{
self.updateSections()
self.updateSections(animated: true)
}
}

View File

@ -251,15 +251,9 @@ private extension SaveStatesViewController
let saveState = self.fetchedResultsController.object(at: indexPath) as! SaveState
guard let actions = self.actionsForSaveState(saveState)?.map({ $0.alertAction }) else { return }
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
for action in actions
{
alertController.addAction(action)
}
guard let actions = self.actionsForSaveState(saveState) else { return }
let alertController = UIAlertController(actions: actions)
self.present(alertController, animated: true, completion: nil)
}
@ -559,10 +553,11 @@ extension SaveStatesViewController: UIViewControllerPreviewingDelegate
self.emulatorCoreSaveState != nil
else { return nil }
previewingContext.sourceRect = layoutAttributes.frame
let saveState = self.fetchedResultsController.object(at: indexPath) as! SaveState
let actions = self.actionsForSaveState(saveState)?.lazy.filter{ $0.style != .cancel }.map{ $0.previewAction } ?? []
let actions = self.actionsForSaveState(saveState)?.previewActions ?? []
let previewImage = self.imageCache.object(forKey: saveState.imageFileURL) ?? UIImage(contentsOfFile: saveState.imageFileURL.path)
let previewGameViewController = PreviewGameViewController()

2
External/Roxas vendored

@ -1 +1 @@
Subproject commit f0661f78b095212ed2f564a4bbf1c1f6c9c50384
Subproject commit e334730200712202feb470f1525c7eea30542146