Beginner swift-user here. I want to open a pop up connected to a seperate view controller (P2_Gift_Pop_Up) from the main view controller. To this end I include the following in a code snippet in my main view controller
let vc = P2_Gift_Pop_Up()
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
This starts running code in the popup window (a print statement works anyway), so so far, so good.
However, when I try to modify some elements in the view connected to the view controller I get a
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value. Here is the code in its entirety.
import UIKit
class P2_Gift_Pop_Up: UIViewController {
#IBOutlet weak var Slot1: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
Slot1.setImage(UIImage(named: "Card 2 Red"), for: .normal)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Although I have (via another answer on this site) an understanding of what the msg means, I don't understand why I get it in this context, and how to fix it. It might also bear emphasis that although code starts running after the P2_Gift_Pop_Up call, the corresponding view is not shown.
You need to present your controller like this(you need to set your storyboard ID and then add this in identifier):
let storyBoard = UIStoryboard(name: "main", bundle: Bundle.main)
let vc = storyBoard.instantiateViewController(withIdentifier: "your storyboard id") as! P2_Gift_Pop_Up
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
Your app is crashing because of your button(Slot1) not have a memory so you need to present like above.
#JogendarChoudhary told you what to do, but didn't really explain why.
When you create your P2_Gift_Pop_Up with
let vc = P2_Gift_Pop_Up()
You aren't initializing it properly. It doesn't get a chance to load it's views from it's XIB file/storyboard.
Assuming you have your view controller defined in your main app storyboard, you need to load the view controller from the storyboard.
You should add a unique identifier to your view controller in your storyboard, and then load it using that identifier. (Using the name of the class as the identifier is as good a choice as any.)
The UIViewController class has a property storyboard which will contain the storyboard from which it's loaded. Usually that's your app's main storyboard, and what you want. Thus:
if let vc = storyboard?.instantiateViewController(withIdentifier: "P2_Gift_Pop_Up id")
as? P2_Gift_Pop_Up {
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
} else {
print("error creating P2_Gift_Pop_Up")
}
Related
I have configured my UIView using Nib's .So, I just want to pass the name of nib in a method and it will open the particular ViewController.
For opening a particular UIView made with Xib, I have done it like this
func presentMyViewControllerNib(){
let controller = MyViewController(nibName: "MyViewController", bundle: nil)
let navigationController = UINavigationController(rootViewController: controller)
let currentController = getCurrentViewController()
currentController!.present(navigationController, animated: false, completion: nil)
getCurrentViewController here is just fetching the viewcontroller displayed.
So, i just want a method like this func presentGeneralisedNib(nibName:String) in which I pass the name of my Xib.
I think it's not possible but if someone can help.
I'll give an example of what I want so it's not so confusing:
Example:
Let's say that I have a map that adds every time that my user scrolls 3 annotations dynamically. Now I have a button under the map and when I press it I go to another viewController do what I want and get back to the viewController with the map, now I want to find all the annotations that my map had and not reload the view at all.
I used to use this function that I made to move between viewControllers:
func move(identifier: String , viewController : UIViewController) -> Void {
let mstoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: UIViewController = mstoryboard.instantiateViewControllerWithIdentifier(identifier)
viewController.presentViewController(vc, animated: true, completion: nil)
}
I also tried this:
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("view") as? MyViewcontroller
self.presentViewController(vc!, animated: true, completion: nil)
These two when I use them the viewcontroller that appears is calling viewDidload so its like it appeared for the first time.
Another example is the tabBarViewController if you notice when you navigate through tabs nothing reloads (only function that is called is viewDidAppear )
EDIT
test file
The problem is caused by the fact that the map controller gets deallocated when navigating back to the other controller, and another one is created when you want to move again to the map screen.
What you need is to hold on onto the same controller instance, and present that one. Keeping a strong reference in the presenting controller would suffice.
class PresentingController {
// making the property lazy will result in the getter code
// being executed only when asked the first time
lazy var mapController = { () -> UIViewController in
let mstoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
return mstoryboard.instantiateViewControllerWithIdentifier("mapControllerIdentifier")
}()
func moveToMap() {
// simply use the mapController property
// the property reference will make sure the controller won't
// get deallocated, so every time you navigate to that screen
// you'll get the same controller
presentViewController(mapController, animated: true, completion: nil)
}
}
According to the same project you posted, you instantiate a new UIViewController when going from view 2 back to view 1 and that is why your viewDidLoad gets called again and your entire map view is reloaded.
In your sample project, instead of
lazy var mapController2 = { () -> UIViewController in
let mstoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
return mstoryboard.instantiateViewController(withIdentifier: "first")
}
You should just dismiss your view 2 on the button press.
#IBAction func butto(_ sender: AnyObject) {
//Your initial code
//PresentingController().moveToMap(self, flag: 1)
self.dismiss(animated: true, completion: nil)
}
When you present a new UIViewController, the older UIViewController is not removed from memory, it is just hidden behind the new UIViewController. So whenever you wish to go back to a UIViewController with the previous state maintained, all you need to do is close the new UIViewController
However, if you are doing some tasks that you performed on your second UIViewController that you wish to be reflected in your initial UIViewController, you will have to setup closures to update your initial UIViewController.
I meet a strange problem: I made 2 view controllers for wich I can switch the view with code:
var currentViewController:UIViewController=UIApplication.shared.keyWindow!.rootViewController!
func showController()
{
let ViewControllernew1 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "viewController2")
currentViewController.present(ViewControllernew1, animated: true, completion: nil)
}
My app open correctly to the first view controller, then, when I click on the button created on a sprite kit scene, I can switch the view to my new view controller successfully (I get my second scene successfully showed) but then, I can not change anymore my view controller after this switch. If I click again on the button, I get this message:
Attempt to present on Test_Vuforia.GameViewController: 0x12f549610 whose view is not in the window hierarchy!
Do you know what is the problem ? I understand I'm in the root position so that I can not change anymore my view controller after having switched it, but how to change that ?
Thanks !
Edit:
My code is used inside a SKScene and not from a UIVewController and I get this error when I use the suffix self. : Value of type View (SKScene) has no member 'present'.
I'm creating an augmented reality game with Vuforia and I need to switch AR view with SKScene.
Issue
Current viewController is not the rootViewController from UIApplication. So you should find the current viewController which is visible and then present it from there.
Solution
Simply find the topViewController on your UIApplication Stack, and from there present your controller.
let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "viewController2")
UIApplication.topViewController()?.present(newViewController, animated: true, completion: nil)
This extension of UIApplication comes in handy for your case
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
References: Gist
Calling function in viewDidAppear helps in my case. Solution for Swift 3:
In your Main Controller:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
showTutorialModally()
}
func showTutorialModally() {
let tutorialViewController = TutorialViewController()
tutorialViewController.modalPresentationStyle = .overCurrentContext
present(tutorialViewController, animated: true, completion: nil)
}
In your Tutorial Controller:
view.backgroundColor = UIColor.clear
view.isOpaque = false
Use the extension below to retrieve the next available controller in the stack.
Swift 3
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
return next as? T ?? next?.next(type)
}
}
Swift 2.3
extension UIResponder {
func nextResponder<T: UIResponder>(_ type: T.Type) -> T? {
return nextResponder() as? T ?? nextResponder()?.nextResponder(type)
}
}
Inside your SKScene, view?.next(UIViewController.self) gives you the next available UIViewController in the hierarchy.
Add this extension to a group in your project called Categories, if this group does not exist already create it, then create a new file called UIResponder+NextOfType.swift and paste the extension.
Xcode error significance for roughly: this view is not in the Window of the view hierarchy.
What I don't think the above answer questions, but maybe you might have wondered why this would happen.
But I find that you are the reasons for this problem is likely to be in the ViewController life cycle at ViewDidLoading switch view Code execution inside.
Reason is probably that, when the ViewController implementation allco init during initialization, it will be executed asynchronously viewWillLoad - > viewDidLoad... -- -- -- -- > viewDidApper. Then may be in code execution to the viewDidLoad. The ViewController may not assign values to the Window. The rootViewController. So we directly use [self presentViewController:] will appear this error.
It is recommended that you move the code of the switch to ViewDidApper.
I hope it will help you.
Probably your rootViewController is not the current ViewController. Either you presented or pushed a new UIViewController on top of it.
The viewController's view is not in the
window's view hierarchy at the point that it has been loaded (when
the viewDidLoad message is sent), but it is in the window
hierarchy after it has been presented (when the viewDidAppear:
message is sent). if you calling showController method from
viewDidLoad just call it from viewDidAppear method
Do something like:
let vc: UIViewController = (self.storyboard?.instantiateViewControllerWithIdentifier("viewController2"))!
self.presentViewController(vc, animated: true, completion: nil)
// OR
self.navigationController?.pushViewController(vc, animated: true)
Use like this
let vc = self.view?.window?.rootViewController
func showController()
{
let ViewControllernew1 = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "viewController2")
vc.present(ViewControllernew1, animated: true, completion: nil)
}
Maybe the issue is with the currentViewController.
Suppose I have three view controllers in a Main.storyboard. Two of the three, vc_login and vc_studyDesc load the other view controller using a UIButton with 'present modally' option.
The other one vc_signup has a UIButton, which may go back to the previous controller. To implement this, I used the following methods:
vc_studyDesc has an identifier of studyDesc; I let it pass its identifier to vc_signup. In the same way, vc_login has login as an identifier.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if sender as! UIButton == qnaSignUp {
let signup = segue.destinationViewController as! vc_signup
signup.latestVC = "studyDesc"}}
This one is in the UIViewController class for vc_signup. By referencing a string latestVC, the method determines which VC to move on.
#IBAction func backBtnClick(sender: UIButton) {
print("latestVS: \(latestVC)")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier(latestVC)
vc.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
print("check check")
self.presentViewController(vc, animated: true, completion: nil)}
The problem I have is that the app gets terminated when vc_studyDesc is called by vc_signup. I found that this is because I missed a significant variable which must be loaded in vc_signup.
vc_studyDesc has some data to be referenced from Firebase when it is loaded. I did this by loading a variable postID from the prior vc to vc_studyDesc; which is vc_list.
So I just saved postID using NSUserdefaults.standardUserDefaults(). It is solved but I'm wondering if there's any way to pass data using the way I used in vc_signup.
As far as I see, I cannot find any way to pass the data into vc_studyDesc.swift; for the vc is chosen by its identifier..
Can I pass the variable I want in the way I want?? And adding tags would be appreciated!
So there are a couple problems with this design.
When you instantiate a viewController you are creating a new instance of that class, and presenting it adds it to the stack. Think of the stack like a deck of cards, you start with one card and then add or remove them, the top card being the visible vc. When you are going back to studyDesc you are instantiating and presenting it so you will have 3 VCs in your stack, of which two are studyDesc (the one you started with and the one you add when you try to go back)
To remove a VC from the stack you can use
dismissViewController(animated: true, completion: nil)
or if you have the VCs in a navigation controller you can use
popViewControllerController(animated: true, completion: nil)
in terms of passing information between viewControllers, if the info is in the VC you use to present your new controller you can use prepareForSegue like you already have. To pass information back you should use a delegate pattern. So to implement a delegate pattern in this case you would do the following:
Declare a protocol (not inside your classes, above there but below your import's)
protocol SignUpDelegate {
signInCompleted(infoToPass: AnyObject)
}
Then have your studyDesc class conform to this protocol and implement the function signInCompleted
StudyDescVC: UIViewController, SignUpDelegate {
func signInCompleted(infoToPass: AnyObject) {
// do what you want with the info here
}
}
Then in your signUpVc add a var delegate (which will be used to call the signInCompeleted function)
class SignInVC: UIViewController {
var delegate: SignUpDelegate!
func finishedSigningIn() {
delegate.signInCompleted(infoToPass: //yourinfo)
self.dismissViewControllerAnimated(true, completion: nil)
}
And then in your prepareForSegue set the delegate
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if sender as! UIButton == qnaSignUp {
let signup = segue.destinationViewController as! vc_signup
signup.delegate = self
}
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier(latestVC) as! YOUR_VIEW_CONTROLLER_NAME
vc.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
vc.name = "Andrew"
print("check check")
self.presentViewController(vc, animated: true, completion: nil)
//set a variale or property to your viewController
class YOUR_VIEW_CONTROLLER_NAME: UIViewController {
var name: String?
}
I have a problem, where I have two view controllers A and B. View controller B has a map, with a route draw on it. I can move back and forwards between the two view controllers at the moment, but the B view controller is reset every time it loads. I think this is because I am using segues and it is creating a new instance of a View controller every time.
I have tried using the following code to solve this issue, but it is still not working. The views load correctly, but view controller B is still being reset
#IBAction func mapButton(sender: AnyObject){
let storyboard = UIStoryboard(name: "MainStoryboard", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("SecondViewController") as! UIViewController
self.presentViewController(vc, animated: true, completion: nil)
}
What am I doing wrong and how can I fix it? I want view controller B to stay in the memory along with the map and the route, so when a user returns he doesn't have to enter all of the information again.
You should create a variable in your class of type UIViewController and change your code to the following:
#IBAction func mapButton(sender: AnyObject){
if yourVariable == nil {
let storyboard = UIStoryboard(name: "MainStoryboard", bundle: nil)
yourVariable = storyboard.instantiateViewControllerWithIdentifier("SecondViewController") as! UIViewController
}
self.presentViewController(yourVariable, animated: true, completion: nil)
}
That way you create the viewController one time, save it and if you want to open it again present the previously created one.