How to close a View Controller after Performing segue in Xcode? - ios

My storyboard looks something like this:-
Main View Controller -> Game View Controller -> Game Result View Controller
I have performed a modal segue from Main V.C to the Game V.C. Now when I perform a modal segue from Game V.C. to Game Result V.C., the Game V.C. is not closing and since the Game V.C. detects if touches began, it crashes when it detects touch outside the Game V.C. after performing a modal segue to the Game Result V.C. I am not using a navigation View Controller. Could anyone please help me on how shall I close the Game V.C after performing a modal segue to the Game Result V.C.? Would appreciate your help! Thanks:)
**Game V.C.**
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// First touch to start the game
if gameState == .ready {
startGame()
}
if let touchLocation = event?.allTouches?.first?.location(in: self.view) {
// Move the player to the new position
movePlayer(to: touchLocation)
// Move all enemies to the new position to trace the player
moveEnemies(to: touchLocation)
}
}
func gameOver() {
stopGame()
performSegue(withIdentifier: "2to3segue", sender: self)
}

Some tips:
Method 1
You can use Tap controller, let storyboard works as following
Main View Controller -> (tab-1) Game View Controller
\
-> (tab-2) Game Result View Controller
Hide tab bar, and change tab by program.
I suggest to use method 1.
Method 2
Dismiss Game View Controller, WITHOUT animation. And then present Game Result View Controller WITH animation.
But, I remember method 2 has some problems. I used it before, and finally I use method 1 now.
// Example:
// Two or more animations will produce problem.
v2.dismiss(animated: false, completion: nil);
v.present(v3, animated: true, completion: nil);

First dismiss the "v2" view controller and in completion block present "v3" view controller
v2.dismiss(animated: false, completion: {
v.present(v3, animated: true, completion: nil)
})
or
if v2 is your presented view controller then :
self.presentedViewController.dismiss(animated: false, completion: {
v.present(v3, animated: true, completion: nil)
})

Related

Dismiss default card presentation (.automatic) with animation Swift 5

On apps using iOS 13, there is an option for ViewControllers to be shown as cards displayed on screen, or .automatic, that can be dismissed by a swipe. Adding a cancellation button, though, may dismiss the page, but, it does not include the animation of dismissing the card. Is there a way to add this programmatically?
This is my code, which is currently crashing the app:
#IBAction func cancel(_ sender: Any) {
let detailVC = home()
present(detailVC, animated: true)
}
If I just add a segue to a button, the animation is just the page popping over, not dismissing as if you were swiping. Thank you!
I think you want this:
#IBAction func cancel(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
What you're doing is preventing a new vc on an already presented modal.
What you actually want is to dismiss the currently presented modal.
See: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621505-dismiss

Dismiss multiple ViewControllers together when using current context as presentation style

I'm creating an application where I place a view controller ontop of another with
Presentation: current context
But when I'm trying to dismiss the screen by dragging the top towards the bottom the view does not disappear.
I've also tried to add a button to make it dismiss but that doesn't work either.
#IBAction func dis(_ sender: Any) {
navigationController?.popViewController(animated: true)
self.dismiss(animated: true)
}
So, how can I dismiss both views when dragging the top one down when it uses "current context" as presentation style?
I'm using "current context" because the previous screen should never be displayed again. And instead of dragging down both, I would like to drag down just one to make both of them disappear. But it does not seem to work as expected.
Although "current context" is not for this purpose as #matt mentioned,
You should dismiss the controller who presents this one to dismiss both together in this case:
self.presentingViewController!.dismiss(animated: true) // `!` is to makeing sure it's not crashing.
Demo:
Use this simple code to demo:
class ViewController: UIViewController {
#IBAction func present() {
let destination = storyboard!.instantiateInitialViewController()!
if presentingViewController != nil {
// You are not in the A
if presentingViewController?.presentingViewController != nil {
// You are in the C
presentingViewController?.presentingViewController?.dismiss(animated: true)
return
} else {
// You are in the B
destination.modalPresentationStyle = .currentContext
}
}
present(destination, animated: true, completion: nil)
}
}
Usage:
Create a single view application
Drag a button into the view controller
Replace the ViewController class with the provided code
Connect the #IBAction
Run it to see the result.
If you use fullscreen presentation (so that there is no drag-to-dismiss) it's quite simple to do this with essentially no code at all.
Note that the yellow view controller appears as an intermediary when presenting, but not when dismissing.

How to dismiss 2 modal view controllers without weird animation [duplicate]

This question already has answers here:
Dismissing multiple modal view controllers at once?
(7 answers)
Closed 4 years ago.
I recreated the question and describe its essence more precisely.
I have two view controllers presented modally on Swift 4, without storyboard (can't use unwind) and without navigation controller
A presents B which presents C.
The reason why we don't use navigation controller, it's because we what simple animation from bottom to top and instead of breaking the standard animation from right to left, we decided to use present.
I would like to dismiss 2 view controllers and go from C to A.
Please, don't mark this question as duplicate before you read my question carefully. I found a tone of similar post, but neither solved my problem. Some of them Objective-C or some of the suggest to use:
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
Or:
self.presentingViewController?.dismiss(animated: false, completion: nil)
self.presentingViewController?.dismiss(animated: true, completion: nil)
It's works, but it create weird animation. It's just delete C and animate dismiss for B:
Expected result:
Idea: you need to dismiss third controller with animation and after dismissing you need to dismiss second without animation. While third controller is being dismissed, second shouldn't be visible.
First, set Presentation style of second view controller to Over Current Context when you're presenting it (since we will need to hide its view when we will dismiss third controller)
let vc2 = VC2()
vc2.modalPresentationStyle = .overCurrentContext
present(vc2, animated: true)
continue with creating callbacks properties for willDismiss and didDismiss inside third controller. This callback will be called before and after you dismiss third controller
class VC3: UIViewController {
var willDismiss: (() -> Void)?
var didDismiss: (() -> Void)?
#IBAction func dismissButtonPressed(_ sender: UIButton) {
willDismiss?()
dismiss(animated: true) {
self.didDismiss?()
}
}
}
then in second view controller in the place where you present third view controller, set third controller's callback properties: declare what happens when third controller will dismiss: you need to hide view of second and then after dismissing third you need to dismiss second without animation (view can stay hidden since view controller will be deinitialized)
class VC2: UIViewController {
#objc func buttonPressed(_ sender: UIButton) {
var vc3 = VC3()
vc3.willDismiss = {
self.view.isHidden = true
}
vc3.didDismiss = {
self.dismiss(animated: false)
}
present(vc3, animated: true)
}
}
Anyway, second option is using UINavigationController and then just call its method popToViewController(_:animated:).
Create a snapshot from the currently visible view and add it as a subview to the first presented view controller. To find that you can simply "loop through" the presenting view controllers and dismiss from the initial one:
#IBAction func dismissViewControllers(_ sender: UIButton) {
var initialPresentingViewController = self.presentingViewController
while let previousPresentingViewController = initialPresentingViewController?.presentingViewController {
initialPresentingViewController = previousPresentingViewController
}
if let snapshot = view.snapshotView(afterScreenUpdates: true) {
initialPresentingViewController?.presentedViewController?.view.addSubview(snapshot)
}
initialPresentingViewController?.dismiss(animated: true)
}
This is the result with slow animations enabled for the dismissal:
https://www.dropbox.com/s/tjkthftuo9kqhsg/result.mov?dl=0
If you are not using a navigation controller and you want to dismiss all ViewControllers to show the root ViewController (assuming A is the root):
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
Use this in button action method.
The current VC will dismiss when you dismiss the parent VC.
This will dismiss both VCs in single animation.
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)

SpriteKit pause Scene (Swift4.2)

I built a Game with a pause button which shows a new modal ViewController with a Pause menu over the current and pauses the scene with the following function (which is located in the scene and called via protocol and delegate):
func pause(isPaused: Bool) {
let pauseAction = SKAction.run {
self.view?.isPaused = isPaused
}
self.run(pauseAction)
}
Pausing the scene and showing the modal view controller works, but when I return to the scene by a unwind segue the scene doesn't unpause.
The pause button is in the same view as the scene.
For communication with the scene from the Pause menu, I use an unwind segue where I call the pause function via Protocol and delegate.
I present the pause menu with that:
#IBAction func PauseMenuButton(_ sender: Any) {
let vc = storyboard!.instantiateViewController(withIdentifier: "PauseMenuVC")
vc.providesPresentationContextTransitionStyle = true
vc.definesPresentationContext = true
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
SceneIsPausedDelegate.pause(isPaused: true)
}
and go back to the scene with that :
#IBAction func unwindToGameVC(segue: UIStoryboardSegue) {
SceneIsPausedDelegate.pause(isPaused: false)
}
I think that the function is never called because it is in the scene since it is paused the is no execution.
SKView pause and SKScene pause behave differently
When an SKView is paused, it will not call the update functions on the scene
When an SKScene is paused, SKView will still call the update functions on the scene, and the scene will not call the update functions on the children nodes.
In your scenario, you are calling pause on the SKView. This means any action you run on your scene will not fire because update never happens.
I recommend either switching to SKScene pause, or not using an action to pause and unpause.
I changed:
func pause(isPaused: Bool) {
let pauseAction = SKAction.run {
self.view?.isPaused = isPaused
}
self.run(pauseAction)
}
to:
func pause(isPaused: Bool) {
self.view?.isPaused = isPaused
}
it wokes without an action.

UIPageViewController stacking ViewControllers when segueing to home page

I have two storyboards, a main then a second storyboard which houses a UIPageViewController. Inside the second storyboard, an exit button allows a user to return to the home screen which is on the main storyboard.
When I attempt to segue back to the home screen, the page controller views are stacking. I have tried to use:
self.navigationController?.popViewController
Along with some other methods to deallocate the UIPageViewController sets. Nothing seems to work? How do I fix this?
You can see in the image below where the controllers are marked by (3).
Memory Graph Image
Below are a few of my attempts. I am using a notification to invoke a method on the root controller after the user confirms via a yes/no custom modal.
func attempt1() {
self.viewControllerList.forEach {
index in
index.removeFromParentViewController()
index.dismiss(animated: false, completion: nil)
index.navigationController?.popViewController(animated: false)
}
self.performSegue(withIdentifier: "unwindHome", sender: self)
}
func attempt2() {
self.childViewControllers.forEach {
c in
print("CHILD...>", c)
c.removeFromParentViewController()
c.dismiss(animated: false, completion: nil)
}
}
func attempt3() {
(view.window?.rootViewController as? UIPageViewController)?.dismiss(animated: true, completion: nil)
self.dismiss(animated: false, completion: {
self.viewControllerList.forEach {
i in
i.navigationController?.popViewController(animated: true)
}
})
self.performSegue(withIdentifier: "unwindHome", sender: self)
}
Here were my issues:
On the first storyboard, my nav controller was not set to initial.
After setting it to initial, i then linked from the main SB to the second SB
Delegates var's were not set to weak
RxSwift was not being disposed of properly.
I was at first doing pub/sub via Variables as a Global. When moving the observer into the root of my UIPageViewController, upon page breakdown the observers were disposed of causing the onComplete callback to hit.

Resources