From b80dbd4e26bc8d78b3fe3d4c6272d2695277fcc2 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Fri, 30 Sep 2016 19:09:08 -0700 Subject: [PATCH] Adds support for dark theme when presenting SaveStatesViewController via 3D Touch/Long Press --- Delta.xcodeproj/project.pbxproj | 4 + Delta/Base.lproj/Main.storyboard | 39 +++++++- .../GameCollectionViewController.swift | 90 ++++++++++--------- .../Game Selection/GamesViewController.swift | 4 + .../Segues/SaveStatesStoryboardSegue.swift | 82 +++++++++++++++++ .../SaveStatesViewController.swift | 17 +--- 6 files changed, 174 insertions(+), 62 deletions(-) create mode 100644 Delta/Game Selection/Segues/SaveStatesStoryboardSegue.swift diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index 3eb413f..a71bfd7 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -58,6 +58,7 @@ BFDD04EF1D5E27DB002D450E /* NSFetchedResultsController+Conveniences.m in Sources */ = {isa = PBXBuildFile; fileRef = BF02BCFF1D361BD1000892F2 /* NSFetchedResultsController+Conveniences.m */; }; BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */; }; BFDE393C1BC0CEDF003F72E8 /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDE39391BC0CEDF003F72E8 /* Game.swift */; }; + BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */; }; BFE704F51CEA426E0058BAC8 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22506DA00971C4300AF90A35 /* Pods.framework */; }; 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, ); }; }; @@ -146,6 +147,7 @@ BFDB28441BC9DA7B001D0C83 /* GamePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GamePickerController.swift; sourceTree = ""; }; BFDD04F01D5E2C27002D450E /* GameCollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameCollectionViewController.swift; sourceTree = ""; }; BFDE39391BC0CEDF003F72E8 /* Game.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Game.swift; sourceTree = ""; }; + BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SaveStatesStoryboardSegue.swift; path = Segues/SaveStatesStoryboardSegue.swift; sourceTree = ""; }; BFEC732C1AAECC4A00650035 /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; @@ -436,6 +438,7 @@ BFFC461D1D59823500AF2CC6 /* InitialGamesStoryboardSegue.swift */, BFFC461C1D59823500AF2CC6 /* GamesStoryboardSegue.swift */, BFFC461B1D59823500AF2CC6 /* GamesPresentationController.swift */, + BFE4269D1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift */, ); name = Segues; sourceTree = ""; @@ -595,6 +598,7 @@ BF353FF91C5D870B00C1184C /* PauseItem.swift in Sources */, BFBAA86A1D5A483900A29C1B /* DatabaseManager.swift in Sources */, BFDD04F11D5E2C27002D450E /* GameCollectionViewController.swift in Sources */, + BFE4269E1D9C68E600DC913F /* SaveStatesStoryboardSegue.swift in Sources */, BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */, BF7AE81E1C2E984300B1B5BC /* GridCollectionViewCell.swift in Sources */, BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */, diff --git a/Delta/Base.lproj/Main.storyboard b/Delta/Base.lproj/Main.storyboard index bcf2a7e..de831e4 100644 --- a/Delta/Base.lproj/Main.storyboard +++ b/Delta/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - - + + - + @@ -80,6 +80,7 @@ + @@ -129,7 +130,7 @@ - + @@ -157,6 +158,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Delta/Game Selection/GameCollectionViewController.swift b/Delta/Game Selection/GameCollectionViewController.swift index 5be616b..b4edfa0 100644 --- a/Delta/Game Selection/GameCollectionViewController.swift +++ b/Delta/Game Selection/GameCollectionViewController.swift @@ -97,44 +97,60 @@ extension GameCollectionViewController { override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - guard let identifier = segue.identifier, identifier == "unwindFromGames" else { return } + guard let identifier = segue.identifier else { return } - let destinationViewController = segue.destination as! GameViewController - let cell = sender as! UICollectionViewCell - - let indexPath = self.collectionView?.indexPath(for: cell) - let game = self.dataSource.fetchedResultsController.object(at: indexPath!) - - destinationViewController.game = game - - if let saveState = self.activeSaveState + switch identifier { - // Must be synchronous or else there will be a flash of black - destinationViewController.emulatorCore?.start() - destinationViewController.emulatorCore?.pause() + case "showSaveStates": + let game = sender as! Game - do + let saveStatesViewController = (segue.destination as! UINavigationController).topViewController as! SaveStatesViewController + saveStatesViewController.delegate = self + saveStatesViewController.game = game + saveStatesViewController.mode = .loading + saveStatesViewController.theme = self.theme + + case "unwindFromGames": + let destinationViewController = segue.destination as! GameViewController + let cell = sender as! UICollectionViewCell + + let indexPath = self.collectionView?.indexPath(for: cell) + let game = self.dataSource.fetchedResultsController.object(at: indexPath!) + + destinationViewController.game = game + + if let saveState = self.activeSaveState { - try destinationViewController.emulatorCore?.load(saveState) - } - catch EmulatorCore.SaveStateError.doesNotExist - { - print("Save State does not exist.") - } - catch - { - print(error) + // Must be synchronous or else there will be a flash of black + destinationViewController.emulatorCore?.start() + destinationViewController.emulatorCore?.pause() + + do + { + try destinationViewController.emulatorCore?.load(saveState) + } + catch EmulatorCore.SaveStateError.doesNotExist + { + print("Save State does not exist.") + } + catch + { + print(error) + } + + destinationViewController.emulatorCore?.resume() } - destinationViewController.emulatorCore?.resume() - } - - self.activeSaveState = nil - - if _performing3DTouchTransition - { - _destination3DTouchTransitionViewController = destinationViewController + self.activeSaveState = nil + + if _performing3DTouchTransition + { + _destination3DTouchTransitionViewController = destinationViewController + } + + default: break } + } } @@ -228,17 +244,7 @@ private extension GameCollectionViewController func viewSaveStates(for game: Game) { - let storyboard = UIStoryboard(name: "PauseMenu", bundle: nil) - - let saveStatesViewController = storyboard.instantiateViewController(withIdentifier: "saveStatesViewController") as! SaveStatesViewController - saveStatesViewController.delegate = self - saveStatesViewController.game = game - saveStatesViewController.mode = .loading - saveStatesViewController.theme = .light - saveStatesViewController.showsDoneButton = true - - let navigationController = UINavigationController(rootViewController: saveStatesViewController) - self.present(navigationController, animated: true, completion: nil) + self.performSegue(withIdentifier: "showSaveStates", sender: game) } @objc func handleLongPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) diff --git a/Delta/Game Selection/GamesViewController.swift b/Delta/Game Selection/GamesViewController.swift index ba84659..9a72d1b 100644 --- a/Delta/Game Selection/GamesViewController.swift +++ b/Delta/Game Selection/GamesViewController.swift @@ -126,6 +126,10 @@ extension GamesViewController @IBAction private func unwindFromSettingsViewController(_ segue: UIStoryboardSegue) { } + + @IBAction private func unwindFromSaveStatesViewController(_ segue: UIStoryboardSegue) + { + } } // MARK: - UI - diff --git a/Delta/Game Selection/Segues/SaveStatesStoryboardSegue.swift b/Delta/Game Selection/Segues/SaveStatesStoryboardSegue.swift new file mode 100644 index 0000000..21faa88 --- /dev/null +++ b/Delta/Game Selection/Segues/SaveStatesStoryboardSegue.swift @@ -0,0 +1,82 @@ +// +// SaveStatesStoryboardSegue.swift +// Delta +// +// Created by Riley Testut on 9/28/16. +// Copyright © 2016 Riley Testut. All rights reserved. +// + +import UIKit + +class SaveStatesStoryboardSegue: UIStoryboardSegue +{ + private var destinationNavigationController: UINavigationController? + + override func perform() + { + super.perform() + + self.destinationNavigationController = self.destination as? UINavigationController + + guard let saveStatesViewController = self.destinationNavigationController?.topViewController as? SaveStatesViewController else { return } + + // Ensures saveStatesViewController doesn't later remove our Done button + saveStatesViewController.loadViewIfNeeded() + + let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(SaveStatesStoryboardSegue.handleDoneButton)) + saveStatesViewController.navigationItem.rightBarButtonItem = doneButton + + guard saveStatesViewController.theme == .dark else { return } + + let sourceView = self.source.navigationController!.view! + + let maskView = UIView(frame: CGRect(x: 0, y: 0, width: sourceView.bounds.width, height: sourceView.bounds.height)) + maskView.backgroundColor = UIColor.white + sourceView.mask = maskView + + self.destination.transitionCoordinator?.animate(alongsideTransition: { (context) in + maskView.frame.size.height = 0 + }, completion: { (context) in + sourceView.mask = nil + }) + } + + @objc private func handleDoneButton() + { + self.destinationNavigationController?.performSegue(withIdentifier: "unwindFromSaveStates", sender: nil) + } +} + +class SaveStatesStoryboardUnwindSegue: UIStoryboardSegue +{ + override func perform() + { + super.perform() + + guard let saveStatesViewController = (self.source as? UINavigationController)?.topViewController as? SaveStatesViewController, saveStatesViewController.theme == .dark else { return } + + let destinationView = self.destination.navigationController!.view! + + let maskView = UIView(frame: CGRect(x: 0, y: 0, width: destinationView.bounds.width, height: 0)) + maskView.backgroundColor = UIColor.white + destinationView.mask = maskView + + // Need to add a dummy view to view hierarchy + animate it to ensure UIViewPropertyAnimator actually runs the animations 🙄 + let dummyView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 0)) + dummyView.backgroundColor = UIColor.clear + self.source.view?.window?.insertSubview(dummyView, at: 0) + + // Apparently UIViewControllerTransitionCoordinator doesn't run its additional animations with same timing curve as the transition animation, so we use our own spring animation + let animator = UIViewPropertyAnimator(duration: 0, timingParameters: UISpringTimingParameters()) + animator.addAnimations { + maskView.frame.size.height = destinationView.bounds.height + dummyView.transform = CGAffineTransform(rotationAngle: CGFloat.pi) + } + animator.addCompletion { (position) in + destinationView.mask = nil + dummyView.removeFromSuperview() + } + + animator.startAnimation() + } +} diff --git a/Delta/Pause Menu/Save States/SaveStatesViewController.swift b/Delta/Pause Menu/Save States/SaveStatesViewController.swift index e34ea4c..cbbf116 100644 --- a/Delta/Pause Menu/Save States/SaveStatesViewController.swift +++ b/Delta/Pause Menu/Save States/SaveStatesViewController.swift @@ -57,9 +57,7 @@ class SaveStatesViewController: UICollectionViewController } } } - - var showsDoneButton = false - + fileprivate var vibrancyView: UIVisualEffectView! fileprivate var backgroundView: RSTBackgroundView! @@ -126,12 +124,6 @@ extension SaveStatesViewController self.navigationItem.rightBarButtonItem = nil } - if self.showsDoneButton - { - let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(SaveStatesViewController.handleDoneButton)) - self.navigationItem.rightBarButtonItem = doneButton - } - // Manually update prototype cell properties self.prototypeCellWidthConstraint = self.prototypeCell.contentView.widthAnchor.constraint(equalToConstant: collectionViewLayout.itemWidth) self.prototypeCellWidthConstraint.isActive = true @@ -224,13 +216,6 @@ private extension SaveStatesViewController } } - //MARK: - Navigation - - - @objc func handleDoneButton() - { - self.presentingViewController?.dismiss(animated: true, completion: nil) - } - //MARK: - Configure Views - func configureCollectionViewCell(_ cell: GridCollectionViewCell, forIndexPath indexPath: IndexPath, ignoreExpensiveOperations ignoreOperations: Bool = false)