I'm trying to make app with slide menu to switch between view controllers .
Opening of the view controller is done by this code :
let destViewController : UIViewController = self.storyboard!.instantiateViewController(withIdentifier: strIdentifier)
let topViewController : UIViewController = self.navigationController!.topViewController!
if (topViewController.restorationIdentifier! == destViewController.restorationIdentifier!) {
print("Same VC")
} else {
self.navigationController!.pushViewController(destViewController, animated: true)
}
one of the view controllers contains SKview with skscene presented. However, when I open this view controller after switching to another , the sprite kit scene is not shown (the sound in it continues to play), so that it creates another skscene.
Is there any way to keep all variables/spritekit scenes during switching between view controllers ?
Related
so my question today is how can I add a view similar to Snapchat where you use gestures to swipe to other views: left, top, bottom, right. I'm trying to use this framework but I'm not sure how to really implement it as they haven't provided a sample project. The layout of my app is I have a signup/login view controller, from there I want it to go to another view (blue) and that's the view that I want to have the different gestures mentioned above. `import UIKit
import SwipeNavigationController
class BlueViewController: UIViewController {
let orangeVC = OrangeView()
let pinkVC = PinkView()
let greenVC = GreenView()
let purpleVC = PurpleView()
override func viewDidLoad() {
let swipeNavigationController = SwipeNavigationController(centerViewController: self)
swipeNavigationController.topViewController = self.pinkVC
swipeNavigationController.bottomViewController = self.purpleVC
swipeNavigationController.leftViewController = self.greenVC
swipeNavigationController.rightViewController = self.orangeVC
view.backgroundColor = UIColor.facebookBlueColor
}
}'
I'm also not using storyboards for this project.
The SwipeNavigationController is a UIViewController and can be pushed onto a stack or presented the same as any other UIViewController. Wherever you are creating and presenting BlueViewController, you should instead create the SwipeNavigationController as the top level object that contains BlueViewController and all of the direction view controllers. BlueViewController and all of the other directions should not know anything about the SwipeViewController. BlueViewController and all of the other directions should not know anything about each other. The SwipeNavigationController is the top level view controller, all of the view controllers associated with a direction are child view controllers of it. I'm assuming that you have a navigation controller somewhere in your flow that pushes the SwipeNavigationController. In that case, you would have something like this in whatever method you want to trigger the push. I've called it nextTapped, but I'm sure it'll be something different in your code:
func nextTapped() {
let swipeNavigationController = SwipeNavigationController(centerViewController: BlueViewController())
swipeNavigationController.topViewController = PinkViewController()
swipeNavigationController.bottomViewController = PurpleViewController()
swipeNavigationController.leftViewController = GreenViewController()
swipeNavigationController.rightViewController = OrangeViewController()
navigationController?.pushViewController(swipeNavigationController, animated: true)
}
And then remove everything from viewDidLoad in BlueViewController except for the line that sets the background color. This creates the SwipeNavigationController with all of the directional view controllers, keeping the BlueViewController as the center and then pushes it onto your view controller stack. If you don't have a UINavigationController in the view controller that is displayed before the SwipeNavigationController, you can present it modally by replacing the last line with this:
present(swipeNavigationController, animated: true, completion: nil)
We are looking to change the way a user logs out of our app. In order to do that, we want to dismiss all the VCs below the current VC and put another VC on top as the root VC. Right now we are doing this which I believe does not dismiss any VC below from memory.
let viewController = storyboard?.instantiateViewController(withIdentifier: "SignIn")
if let unwrappedViewController = viewController {
self.present(unwrappedViewController, animated: true, completion: {})
}
The problem is that the VC that we want to put on top is not embedded in a Navigation Controller or tab bar controller. How would we dismiss the VCs and set the new VC as the main VC as if the user was opening the app for the first time without having previously logged in? We also do want the transition to be animated with whatever animation is normal for that event (modal animation is fine). I have read a bunch of different ways on doing it but I want to know which way is best practice and should be implemented specifically dismissing all VCs and putting a new VC that isn't in a Nav controller on top.
If you can access the UIWindow of the app, you can set its rootViewController property to your sign-in view controller, effectively removing all current view controllers and adding the sign-in view controller instead. Here's an example:
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
// Should remove all subsequent view controllers from memory.
appDelegate.window?.rootViewController.dismiss(animated: true, completion: nil)
// Set the root view controller to a new instance of the sign in view controller.
appDelegate.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SignIn")
I'm running into an issue where after changing the rootViewController on my UINavigationController and changing it back to my original UINavigationController, a UISplitViewController begins to show both it's master and detail view in a phone device on compact/portrait orientation (so not only on plus size phones, but also others).
Basic overview of architecture:
A TabBarController houses several tabs. One of these tabs is a UISplitViewController. I currently override the following to ensure that the MasterViewController is shown on compact orientations:
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
// this prevents phone from going straight to detail on showing the split view controller
return true
}
This works fine and displays the master on portrait as expected. At any point pressing a button on another tab can create a new UINavigationController instance and display it, in which I'm doing the below to change the rootViewController to the newly created UINavigationController to display:
let appDelegate = UIApplication.shared.delegate
appDelegate?.window??.rootViewController = newNavVC
On dismiss, I'm just swapping the UINavigationController back to the original one through the same code above. However, once I do this one time (create nav/display/dismiss), and I switch my tab back to the one with the UISplitViewController, it changes itself to show a side-by-side master detail view. I didn't know this was possible in portrait mode for compact sizing. I tried changing to any of the 4 preferred display modes in the UISplitViewController, but that didn't fix it.
Below is what it looks like (iPhone 6 simulator), am I missing delegates or misunderstanding collapsing?
Before:
After:
You can replace the the logic that assigned the rootViewController with the code snippet found at this link:
Leaking views when changing rootViewController inside transitionWithView
Basically you just create an extension for the UIWindow class that will set the root view controller correctly.
extension UIWindow {
/// Fix for https://stackoverflow.com/a/27153956/849645
func set(rootViewController newRootViewController: UIViewController, withTransition transition: CATransition? = nil) {
let previousViewController = rootViewController
if let transition = transition {
// Add the transition
layer.add(transition, forKey: kCATransition)
}
rootViewController = newRootViewController
// Update status bar appearance using the new view controllers appearance - animate if needed
if UIView.areAnimationsEnabled {
UIView.animate(withDuration: CATransaction.animationDuration()) {
newRootViewController.setNeedsStatusBarAppearanceUpdate()
}
} else {
newRootViewController.setNeedsStatusBarAppearanceUpdate()
}
/// The presenting view controllers view doesn't get removed from the window as its currently transistioning and presenting a view controller
if let transitionViewClass = NSClassFromString("UITransitionView") {
for subview in subviews where subview.isKind(of: transitionViewClass) {
subview.removeFromSuperview()
}
}
if let previousViewController = previousViewController {
// Allow the view controller to be deallocated
previousViewController.dismiss(animated: false) {
// Remove the root view in case its still showing
previousViewController.view.removeFromSuperview()
}
}
}
I am creating an application which shows a PageVC: UIPageViewController as intro and guide to app.
After navigating through the intro, a Next Button leads to "SettingsVC" which is used to store default settings for the app.
Settings have to be chosen by the user initially although can be changed later.
PageVC ---> SettingsVC
A Save and a Cancel button on the SettingsVC leads to the MainVC of the app.
A button on MainVC leads to SettingsVC.
SettingsVC <---> MainVC
The app would work as follows:
if isFirstLaunch{
instantiate PageVC
}else{
instantiate MainVC
}
in PageVC
nextButtonPressedinPageVC{
instantiate SettingsVC
}
in SettingsVC
if saveButtonPressed && cameFromPageVC{
instantiate MainVC
}
if cancelButtonPressed && cameFromPageVC {
do Nothing
}
if saveButtonPressed && cameFromMainVC{
dismiss currentVC
}
if cancelButtonPressed && cameFromMainVC {
dismiss currentVC
}
in MainVC
if settingsButtonPressedinMainVC {
instantiate SettingsVC
}
I have made sure that if it is application's first launch, PageVC will be instantiated else MainVC will be instantiated.
How can I move between the viewControllers without a possible memory leak i.e. where to performSegue and where to dismiss current VC?
Please include code for reference.
There are many ways to do this, here is one that I find very straightforward because you can do most of the work in your Storyboard:
Think of your MainVC as the rootViewController and the other two as accessory views that will only temporarily be shown. The MainVC should always be your entry point, so set it as the initial VC in your Storyboard.
The other two should be displayed modally so that you can easily return to the MainVC by dismissing them, no matter how you opened them in the first place.
To do this, draw a segue from your MainVC button to the PageVC and name it "showPageVC". From the Next button in your PageVC, draw another segue to the SettingsVC. Now you need some code to handle the dismiss actions: put this snippet in your MainVC:
#IBAction func unwindToMain(segue:UIStoryboardSegue) {
}
This function is just a marker, so it doesn't need a body. It just enables you to create a unwind segue back to MainVC: For each of the buttons in SettingsVC, hold Ctrl and draw from the button to the right exit icon in the header of the SettingsVC storyboard scene and choose unwindToMain in the tiny black popup.
Now you only have to implement the logic to decide if you want to show the PageVC or not in viewDidAppear() of the MainVC. So the whole code would look something like this:
class MainVC: UIViewController {
var didDisplayPageVC = false
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if (didDisplayPageVC == false) {
performSegue(withIdentifier: "showPageVC", sender: self)
didDisplayPageVC = true
}
}
#IBAction func unwindToMain(segue:UIStoryboardSegue) {
}
}
The rest is in the storyboard. If this little proof-of-concept is working, you can go and configure the segues (you might want to remove the animation) etc.
There seems to be so many contradicting ideas on this topic.
I simply wish to have my menu in a UIViewController and my game in the SKScene
In my SKScene I used:
self.removeFromParent()
self.view?.presentScene(nil)
The nodes are removed but the scene is still in place as I still have the grey background and fps counter. Can I return to the View aspect of the UIViewController and hide the scene?
My method one implementation:
RootViewController:
class RootViewController: UIViewController {
var menu = MenuViewController()
var game = GameViewController()
override func viewDidLoad() {
super.viewDidLoad()
print("root")
MenuPresent()
}
func GamePresent() {
self.addChildViewController(game)
self.view.addSubview((game.view)!)
game.didMoveToParentViewController(self)
}
func MenuPresent() {
self.addChildViewController(menu)
self.view.addSubview((menu.view)!)
menu.didMoveToParentViewController(self)
}
func menuDismiss() {
menu.willMoveToParentViewController(nil)
menu.removeFromParentViewController()
menu.view.removeFromSuperview()
}
}
MenuViewController:
class MenuViewController: UIViewController {
//var root = RootViewController()
override func viewDidLoad() {
super.viewDidLoad()
print("menu")
}
}
The print("menu") appears in my console, But the actual view and all assets of the MenuViewController do no appear.
On the other hand my GameViewController and it's SKScene work fine.
Views and scenes are two different things. Scenes are held inside of a view. You would have to simply present a scene within your menu view or transition to another view and present an SKScene from there. The code to present the scene might look like this:
let scene = GameScene(fileNamed: "GameScene")
let skView = self.view as! SKView
scene?.scaleMode = .AspectFit
skView.presentScene(scene)
First you need to know that SKScene and UIViewController are totally two different things. The hierarchy is typically as following:
UIViewController --> UIView(SKView) --> SKScene
So you SKScene is presented in a SKView which can be a UIView, then the UIView is presented in a UIViewController.
Once you know the hierarchy, everything is easy. There are many ways to use different UIViewController for menu and GameScene stuff.
Method One
For example, you can have a RootViewController, a GameViewController and a MenuViewController. The RootViewController is the initial ViewController when the app launches.
In the RootViewController, you can create a function to present the GameViewController:
func setupGameViewController() {
self.gameViewController = GameViewController()
self.addChildViewController(gameViewController!)
self.view.addSubview((gameViewController!.view)!)
gameViewController?.didMoveToParentViewController(self)
}
You need to present you SKScene in GameViewController, I guess you should be familiar with this step.
Then when you need to display the menu, you can add the MenuViewController to RootViewController with a function like:
func setupMenuViewController() {
self.menuViewController = MenuViewController()
self.addChildViewController(menuViewController!)
self.view.addSubview((menuViewController!.view)!)
menuViewController?.didMoveToParentViewController(self)
}
You also need to present you menuView in this ViewController, which I suppose you already know.
Also create a function to dismiss the MenuViewController:
func removeMenuViewController(){
self.menuViewController?.willMoveToParentViewController(nil)
self.menuViewController?.removeFromParentViewController()
self.menuViewController?.view.removeFromSuperview()
}
And everything is done.
Method Two
You can also have only one UIViewController, but create you menu as a UIView, then you can use self.view.addSuview(menuView) to present your menu. Of course you root view, which is the self.view as SKView is still there, but it doesn't matter because it's hidden behind the menuView.
A Note for your updated question
You cannot remove the scene because the scene is actually self.view as SKView, self.view is the root view of a UIViewController, it cannot be removed. If you really want to remove you scene (for most cases, it's not necessary), you can create a new SKView that present your SKScene, then add this SKView to your UIViewController by self.view.addSubview(skView), when you want to completely remove the scene, just use skView.removeFromSuperview().