I just recently started to use Swift and am facing a "weird" bug with the viewDidLoad method.
My very simple app currently only has 2 viewcontrollers:
MainViewController, which is the Apps entry point and serves as an overview for the data which has already been created. It also provides the option to add new data, which would trigger a segue to
DataViewController, which provides the UI to create new data, after which it goes back to the MainViewController
Now my issue is that the viewDidLoad method of MainViewController is always called whenever the MainViewController appears (At the start of the app and every time the DataViewController disappears). Particularly, the msg "MainViewController newly created" is always printed.
Even worse, it seems that my app is "secretly" resetting. To show this I have defined the class variable "createView" in my MainViewController which is true by default, and is set to false during the viewDidLoad (the only place where this variable is called/set). However the msg "MVC newly created" is still always printed in the output after the MainViewController shows up. How can that be? Why / how is createView reset to true?
Hope this snippet is enough to find the issue. Otherwise, let me know if something is missing.
Thanks for your help!
override func viewDidLoad()
{
super.viewDidLoad()
if (createView)
{
determineArraySize()
createDataArray()
print("MainViewController newly created")
createView = false
}
else {print("Nothing happened")}
}
As #moritz mentioned in the comments, check out the way you present the DataViewController in your storyboard.
If the view is presented modally, you should call:
dismiss(animated: true, completion: nil)
If the view is presented using a show seque, you should call:
_ = navigationController?.popViewControllerAnimated(true)
Related
I've a table view with navigation controller embedded in. I've added a UIBarButtonItem (add) button. When I click this button it opens a new view where user enters the data and submits it (which makes a web service call) and returns back to the previous view. This navigation happens as shown below,
func addTapped(_ sender:UIBarButtonItem) {
print("Called Add")
let vc = (storyboard?.instantiateViewController( withIdentifier: "newNote")) as! newNoteVC
self.navigationController?.pushViewController(vc, animated: true)
}
And in new view I do following,
#IBAction func saveButton(_ sender: UIButton) {
if (self.noteDescription.text?.isEmpty)! {
print("Enter missing note description")
return
} else {
let desc = self.noteDescription.text
self.uploadNote(noteText: desc!, noteDate: self.dateInMilliseconds)
self.navigationController?.popViewController(animated: true)
}
}
This way a record gets saved and a view gets popped from the navigation controller stack but only thing I don't how to do is refresh the table view data in the parent view (where I might need to make a new http service call to read all the records).
I hope I'm able to explain the issue? Any help is appreciated.
As mentioned in the comments, making a service call just to update the tableview might be a overkill. However, if this is the business scenario which needs to be implemented, you can do the same in
func viewWillAppear
in the parent view controller. You can make the service call in this method and reload the table view with the data.
You would also need to check the overall navigation of the application as making service calls in viewWillAppear method is not a good approach as these gets called everytime the view is shown. For Ex: If coming back from a navigated view then also the method is called.
Good evening,
I am trying to pass data from one View Controller to another, but it crashes my program in the final View Controller. Can anyone help me?
This is the ViewController:
func updateAfterPlayAgain(){
labelSay.text = ">Result<"
scoreLabel.text = "Score: 0"
score = 0
seconds = 10
mainButton.isEnabled = true
userResult.isUserInteractionEnabled = true
}
This is the PopUpViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var fate: ViewController = segue.destination as! ViewController
fate.updateAfterPlayAgain()
}
It is from this one that I am trying to access the data from ViewController when it(PopUpViewController) closes. I would like to update the information in the ViewController when I close the PopUpViewController.
Thank you.
When prepareForSegue is called the destination view controllers' views haven't been loaded, so all of it's outlets will be nil.
Outlets are set up as implicitly unwrapped optionals, and that causes a crash if you try to reference an outlet that's nil. That's why you're crashing.
You should not call updateAfterPlayAgain() from prepareForSegue, since it tries to reference your outlets before they are connected. Instead call updateAfterPlayAgain() from viewWillAppear (or better yet from viewDidLoad, since that only gets called once when a view controller's views are first loaded.)
#Banjo, your question is not completely clear. Please add the crash it self so we will have more information ti help you.
From what I see, when you create a screen, the #IBOutlet labels, are still nil until the screen performs the "viewDidLoad" method, therefore, instead of updating the screen in the prepare(for segue), I'd set a flag using a boolean, i.e.: "requiresUpdate: Bool", and set it here, then in the viewDidAppear, check the flag and call updateAferPlayAgain.
If you are talking about dismissing a controller and updating the main controller instead, you should read about how to use "unwind segues" and handling their events, this way the parent controller gets the event of the displayed controller closing.
This article should help you with that:
https://spin.atomicobject.com/2014/10/25/ios-unwind-segues/
Good luck.
I have a UIViewController that I have had in a storyboard for a while with no problems. As my application grew, and I was using that view controller in more and more places, I realized that I should probably make it more portable, rather than have so many segues to it from hither and yon across the board. I've done splits like this before, so I did what I figured was logical here. I selected that view controller, cut it, and pasted into an empty .xib file. After changing each call to performSegueWithIdentifier to an init(nibName:bundle:) and presentViewController, I get a crash, with an object found unexpectedly nil in viewDidLoad()...
I set the value of this object after each init(...) call, just before presenting the view controller. The nil object is called from viewDidLoad(). This is a problem. I just set this, and now it's gone?!
I overrode the init(...) method, and found that self in init(nibName:bundle:) doesn't have the same memory address as self in viewDidLoad(). Also strange.
I overrode the other init() methods, and found that, after I call to present my view, my object is being instantiated again via init(coder:)! The self in here happens to be the exact self where my property is found nil!
The only reason I see for init(coder:) to be called at all is that I am loading my view from a .xib, but I thought this was handled in init(nibNamed:bundle:)? According to the docs, I do indeed get a call to init(coder:) if I'm loading from a storyboard, and doesn't touch the former... It also says that the nib isn't loaded until the controller's view is queried. If I understand it correctly, my view shouldn't get queried until I present the view. As the crash happens only when I present it, the issue likely stems from that.
I'm stuck here. I still need to get this contextual information to the view controller before it's presented. I've even tried making a proxy class to do the instantiating and property setting before presentation, but I still can't shake this second instance! I get one from init(nibName:bundle:), and another from init(coder:). Neither gets presented, and the latter gives me a nil object error. Any help at all in understanding why this is, and how I might work around this bug (feature?) would be much appreciated. Thank you!
Update:
On a whim, I decided to paste the view controller back into the storyboard, separate from the main hierarchy, and try instantiating it by its identifier. It worked! Not entirely sure how, but by George it worked! Now my question is this: Why?? What is so terribly evil and taboo about .xibs that Xcode and iOS won't tell me? I'm not a little flummoxed by this behavior. I'll keep trying with the .xib, if only to keep Xcode from yelling at me about entrance points...
I don't know what dark magic Xcode is doing, but here's two helper methods I wrote to easily instantiate any Storyboard VC - you just need the Storyboard name and VC identifier (optionally, otherwise will initial VC). By splitting up my VCs into many different Storyboards, I avoid dealing with xibs while still keeping things simple. One loads it into a nav controller of your choice, the other just returns it by itself:
struct StoryboardHelper {
///instantiates a VC with (optional) identifier viewController from storyboardName, pushes it to hierarcy of navigationController, and runs setup block on it, animated specifies whether the push is animated
internal static func showStoryboard(storyboardName: String, viewController: String?, navigationController: UINavigationController, animated: Bool = true, setup: (UIViewController) -> () ){
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let destinationVC = viewController != nil ? storyboard.instantiateViewControllerWithIdentifier(viewController!) : storyboard.instantiateInitialViewController()!
setup(destinationVC)
navigationController.pushViewController(destinationVC, animated: animated)
}
///instantiates and returns a VC with (optional) identifier viewController from storyboardName
internal static func instantiateViewControllerFromStoryboard(storyboardName: String, viewController: String?) -> UIViewController{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
return viewController != nil ? storyboard.instantiateViewControllerWithIdentifier(viewController!) : storyboard.instantiateInitialViewController()!
}
}
I bounce in a quite weird issue. When I push a specific view controller for another one, the former get dismissed soon after being showed. When I push it fom the main View Controller, it stays put without any problems. I put breakpoints and the viewDidDisappear is in fact called just after the viewDidAppear.
By smell it look like the second view controller becomes nil in one way, but how is it possible if that is wired to the storyboard?
Has anyone got any idea about what could be the reason for the weird behavior?
The main view controller and the first view controller are both in Swift, the pushed controller is still in Objective-c.
This is how I open the second view controller:
func didSelectRow(indexPath: NSIndexPath, from owner: DestinationsViewController){
if let currentElement=DestinationsContentProvider.sharedContentProvider().stations[indexPath.row]{
print("a \(indexPath.row) elemento \(currentElement)")
let targetModel = currentElement.model
//NSLog(#"targetMetro:%# targetPaletta=%#", owner.targetMetro, owner.targetPaletta);
if ((targetModel != nil) && (targetModel!.myTraffic != nil)){
targetModel!.segueExecute()
}
}
segueExecute is called on the model that is not dismissed. I put a breakpoint on the dealloc and it is never reached.
The only peculiar issue is that in the model I perform the segue on the main controller instead of the actual controller by this piece of code:
mapController.performSegueWithIdentifier("ShowWaiting", sender:self)
Still the same behavior happens even if I manually push the controller by executing:
let mainStoryboard:UIStoryboard!
if (UIDevice.currentDevice().userInterfaceIdiom == .Pad){
mainStoryboard=UIStoryboard(name:"StoryboardiPad", bundle: nil)
} else {
mainStoryboard=UIStoryboard(name:"MainStoryboard_iPhone", bundle: nil)
}
let controller = mainStoryboard.instantiateViewControllerWithIdentifier("Situation") as! StationSituation
controller.model=targetModel;
InArrivoHDViewController.sharedDetailController().navigationController?.pushViewController(controller, animated: true)
without using the segue construct.
Just check whether second view controller used for pushing is a property or not. If secondVC instance is created within the method in which pushing is done, secondVC will become nil after execution of the method.
I fixed the issue by directly calling performSegue on the view controller rather than delegating it to the root controller. For some reason this delegation works if there is the same kind of view controller on the Navigation queue in which you are pushing the controller: I have this construct in another class and I just checked it actually work. Otherwise the effect is the weird one I experienced.
I think, but I may not be sure, that in Objective-c the situation was different.
The issue I'm having is this.
I have a navigation controller with 3 viewController. In the 1st controller, I have the user select an image. This image is passed to 2nd and 3rd controller via prepareForSegue.
At the 3rd controller, I have a button that takes the user back to the 1st view controller. I explored 2 ways in doing this:
1) use performSegue, but I don't like this because it just push the 1st controller to my navigation stack. So I have this weird "Back" button at the 1st Viewcontroller now, which is not what I want. I want the app to take user directly to 1st viewcontroller without the back button.
2) I tried Poptorootviewcontroller. This solves the issue of the "back" button. But, when I pop back to the 1st viewcontroller, the user's selected image is still on screen. I want to clear this image when the user goes from the 3rd viewcontroller back to the 1st viewcontroller.
So with approach 2), how do I make sure all memory is refreshed and the image becomes nil in the 1st viewcontroller? Since I'm not using performSegue, 3rd viewcontroller does not have access to the 1st Viewcontroller.
For refresh, you'd have to clear it in viewWillAppear but I find this rather dangerous. Best you can do there is to create a new copy of the view controller everytime and Swift will take care of the rest. I don't know if you are using the storyboard but I would recommend using the class UIStoryboard and the function instiantiateViewControllerWithIdentifier("something") as! YourCustomVC
As long as you stay in the navigation stack, you'll not lose any of the current configurations of previous View Controllers.
As for passing data back to the first controller. You can either just throw it in the global scope which is the easiest way but might be difficult to know when it was updated or if the data is fresh. But you can always just:
var something: String = ""
class someView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
something = "foo"
}
}
Something will be availabe everywhere then.
You could make a protocol and pass the delegate along the 3 view controllers. So when you are starting it you could do:
func someAction() {
let v = SomeViewController()
v.delegate = self
self.navigationController?.pushViewController(v, animated: true)
}
And then with each following view:
func someOtherAction() {
let v = SomeOtherViewController()
v.delegate = self.delegate
self.navigationController?.pushViewController(v, animated: true)
}
Although personally I find it hard to keep track of this.
Lastly you could use the NSNotificationCenter to pass an object along with all the data and catch it in a function on your first controller.
To do this you first register your VC for the action in viewDidLoad() or something:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "someAction:", name: "someNotification", object: nil)
Then when you are done in the 3rd view make some object or a collection of string and send it back as follows:
NSNotificationCenter.defaultCenter().postNotificationName("someNotification", object: CustomObject())
And then lastly you'll catch it in the function "someAction"
func someAction(note: NSNotification) {
if let object = note.object as? CustomObject {
//Do something with it
}
}
Hope this helps!
Use an unwind segue which provides the functionality to unwind from the 3rd to the 1st (root) view controller.
The unwind segue is tied to an action in the root view controller. Within this action, you simply nil the image:
#IBAction func unwindToRootViewController(sender: UIStoryboardSegue)
{
let sourceViewController = sender.sourceViewController
// Pull any data from the view controller which initiated the unwind segue.
// Nil the selected image
myImageView.image = nil
}
As you can see in the action, segues also let you pass data back from the source view controller. This is a much simpler approach than needing to resort to using delegates, notifications, or global variables.
It also helps keep things encapsulated, as the third view controller should never need to know specifics about a parent view controller, or try to nil any image that belongs to another view controller.
In general, you pass details to a controller, which then acts on it itself, instead of trying to manipulate another controller's internals.