Destination's View Controller variable becoming nil after being set - ios

Basically there is something wrong either with my code, Swift or the compiler/Xcode itself. I have 2 ViewControllers and the second one (destination) has 2 UIImage variables. First ViewController sets those 2 images in prepareForSegue like this:
if let destVC = segue.destinationViewController as? SecondViewController {
destVC.firstImage = firstImage
destVC.secondImage = secondImage
}
Every time I checked what's going on in that part of code both firstImage and secondImage are set and are never nil (they are both created before that part of code, and I even logged to check it and they were never nil), however in viewDidLoad method in SecondViewController the app crashes cause firstImage is set, but secondImage is nil (and I force unwrapped it). I've tried compiling it numerous times - every time it's nil and it crashes. After that I've added print("hello world") line to the segue code, erased it, saved the file, compiled again and app never crashed again.
So my question here is - what exactly happened - judging by code, the secondImage should never be nil (since the secondImage is never nil, nor was it nil in debugger or when I make a breakpoint), so why did the secondImage in destVC became nil?
Also I need to mention that I'm using inbuilt Git, and I'm not sure if it's maybe somehow affecting or corrupting the files since I've already had issues where Xcode sometimes takes forever to reindex the code and autocompletion stops working for around 20-30 seconds until it does that (I have an iMac Retina with an i5 so this really shouldn't be an issue).
edit: let me add the code from SecondViewController to make things clear:
...
class SecondViewController: UIViewController {
...
var firstImage: UIImage?
var secondImage: UIImage?
...
override func viewDidLoad() {
super.viewDidLoad()
self.loadMainImage(self.firstImage!)
self.loadSecondaryImage(self.secondImage!) // secondImage was nil all the time here so it crashed at this point cause of force unwrap
...
}
...
}
So as you can see I'm setting everything properly, and the methods take UIImage(not optional UIImage?) and do something with those images, however secondImage was always nil. So my questions are - why is firstImage set normally, and second isn't? And why it started working normally when I added one line of code, then deleted it, and compiled everything again.
I'm aware that I can check if the secondImage is nil and that I can do optional unwrapping but the point here is - why did it get set to nil with no reason at all.

I was able to fix this by moving my code from viewDidLoad to viewWillAppear My class variables (set in the presenting view controller) were nil in viewDidLoad but were instantiated in the viewWillAppear The strange thing is that they worked in the viewDidLoad for a while. This seems to indicate a race condition, though I'm not sure that makes any sense.

You can pass image to another image property of the second VC and then set the "passed" image in the viewDidLoad of the SecondVC.
BUT you cannot pass or set the image directly to that of the imageView.image of the SecondVC, as secondVC.imageView is not yet allocated in memory.
What you need to do is create an UIImage Property in the .h of the secondVC and pass the image to that Image Property.
Set the image in the viewDidLoad

Related

Struct Array in secondVC not being appended from the firstVC

I have created a struct and made an array of that type. The struct consists of two variable:
struct notesarray
{
var prioritycolor : UIColor
var note : String
}
In my secondVC which houses a collectionViewController, I have made an array of type notesarray. I am sending values for prioritycolor and note from firstVC.
I will be setting up CoreData later on, for now I just want this to work in simplest of manners. I am appending data from firstVC to this array like so:
#objc func handleCheckButton()
{
print("Added")
let secondVC = AddedNotesCollectionViewController()
secondVC.allnotes.append(notesarray(prioritycolor: taskTextView.backgroundColor!, note: taskTextView.text))
print(secondVC.allnotes.count)
taskTextView.text = nil
}
allnotes is the name of the array found in secondVC.
For testing purposes I am printing secondVC.allnotes.count but I am just getting '1' in console no matter how many time I add elements to the array.
I have also tested this by placing print(allnotes.count) under viewDidAppear func in secondVC so that whenever I go to secondVC it gives me count of the elements in the array but it also shows '0' every time.
I don't know what I am doing wrong here. Please help me!
Thats because you end up getting a new instance of AddedNotesCollectionViewController every time you press the button.
let secondVC = AddedNotesCollectionViewController()
And new instance is initiated with an empty array and you add one element to it by calling
secondVC.allnotes.append(notesarray(prioritycolor: taskTextView.backgroundColor!, note: taskTextView.text))
Hence count is always one. iOS is correct there my friend :)
What you need:
If second VC is already loaded either by pushing a it on to navigation stack of FirstVC or if its presented then get the reference to the presented/pushed VC rather than creating a new one every time. There are many answers in SO which explains how to access the pushed/modally presented VC :)
If you are about to present/push the SecondVC, as you mentioned in the comments you can always make use of prepareForSegue to pass the data.
If in case your AddedNotesCollectionViewController is never presented then rather consider creating singleton instance of notesArray which you will share between multiple VCs.
Hope it helps

Referencing item's initialized in viewDidLoad safely

I ran into a bizarre bug earlier this week and wanted to follow up to see how to prevent the root cause of the issue.
Take the following code.
//*****************************
//MAINVIEWCONTROLLER CLASS CODE
//*****************************
//Some event happens that triggers me to want to load up TestViewController.
func showViewController(){
var testController = TestViewController()
testController.someMethod("Test1")
self.navigationController?.pushViewController(testController, animated: true)
}
//*****************************
//TESTVIEWCONTROLLER CLASS CODE
//*****************************
testView:TestView!
override func viewDidLoad(){
super.viewDidLoad()
testView = TestView()
...
}
func someMethod(someData:String){
testView.name = someData //AppCrashes here because testView might be nil.
...
}
So someMethod is getting fired before TestViewController has had the chance to go through and create the testView. I'm then getting a cannot unwrap an optional value because testView is nil and I'm accessing a property on it.
Whats strange is the application I'm running probably does this exact thing in 6 different places, and 5/6 are working perfectly fine, but 1/6 is now giving me this error. I'm guessing its because of the viewDidLoad not being guaranteed to fire immediately or complete before someMethod is executed, but why then is this not happening on all 6 of the use cases.
So my main questions are:
Why does this crash happen?
What is the best practice to avoid it.
Thanks! Thoughtful answers will get up-votes as always! Let me know if any more info would be helpful.
Basically never run code in the destination controller called from the source controller which involves UI elements. Create a property, set it in the source controller and assign the value to the UI element in viewDidLoad() of the destination controller, for example:
//*****************************
//MAINVIEWCONTROLLER CLASS CODE
//*****************************
//Some event happens that triggers me to want to load up TestViewController.
func showViewController(){
var testController = TestViewController()
testController.someData = "Test1"
self.navigationController?.pushViewController(testController, animated: true)
}
//*****************************
//TESTVIEWCONTROLLER CLASS CODE
//*****************************
testView:TestView!
var someData = ""
override func viewDidLoad(){
super.viewDidLoad()
testView = TestView()
testView.name = someData
...
}
viewDidLoad is called when the ViewController completes loading in preparation to be shown, e.g. when a segue takes place or when involved in a present.
As written your code shouldn't even compile since testView is optional, but you have two options. Use optionals (in which case the view may not get the information if not called after viewDidLoad, but it won't crash) or store the passed information and update your view in viewDidLoad or viewWillAppear.
Something you might want to be aware of is viewIfLoaded
You can force the viewDidLoad method with with:
var testViewController = TestViewController()
_ = testViewController.view
testViewController.someMethod("Test")
Initializing the ViewController doesn't automatically call viewDidLoad

ViewDidLoad is always called

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)

UIViewController initWithNibNamed:bundle: initialized two objects?

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()!
}
}

Swift-Keep getting "fatal error: unexpectedly found nil while unwrapping an Optional value"

I've only been working with Swift and Xcode 6 for a few weeks, but I'm already working on an idea I have for an app. The part of the app I'm trying to work on would go like this:
1) User has a grid of buttons, which each do something different when tapped
2) User can tap a gear button on the bottom right of the view and then will have a new view presented modally, a makeshift settings pane (labels and switches). If the switch by the label of the name of the button is off, than that button is hidden.
3) User hits a Save button (which currently has NOTHING to do with NSUserDefaults or anything to do with actually saving features once the app is closed, it's CURRENTLY really just a back button) and any switches which are turned off make their respective button outlets hidden.
However, the actual project runs like this:
1) Works perfectly
2) When I tap the gear, the settings pane appears, as planned. I can also slide switches all around, but when I tap the save button, the app crashes and I get "fatal error: unexpectedly found nil while unwrapping an Optional value (lldb)" in the console.
Here's my code:
This is part of the first view controller with the buttons on it:
#IBAction func soundButton(sender: AnyObject) {
var soundID: SystemSoundID = 0
let soundFile: String = NSBundle.mainBundle().pathForResource("Sound", ofType: "wav")!
let soundURL: NSURL = NSURL(fileURLWithPath: soundFile)!
AudioServicesCreateSystemSoundID(soundURL, &soundID)
AudioServicesPlaySystemSound(soundID)
}
Above is an action which plays a sound when a button is pushed.
Now here's the outlet for the button and one for the border for it:
#IBOutlet var soundOutlet: UIButton!
#IBOutlet var soundRingOutlet: UIImageView!
In the storyboard, I have a tab bar controller and two views (ViewController and ViewControllerTwo), to access the settings view (SettingsViewController) I made a button with an image of a gear on it in the lower right hand corner, which is connected to the settings pane by a modally presented segue. HERE'S PART OF THE CODE FOR THE SettingsViewController:
//The switch
#IBOutlet var soundSwitch: UISwitch!
HERE'S WHERE THE ISSUE IS:
#IBAction func saveButton(sender: AnyObject) { if soundSwitch.on{
(presentedViewController as! ViewController).soundOutlet.hidden = true} else {(presentedViewController as! ViewController).soundOutlet.hidden = false
dismissViewControllerAnimated(true, completion: nil)
}
The issue is from the second instance of "presentedViewController" to "false"
Very frustrating problem, I've been working on this for hours, swapped code around, researched this, but nothing seems to work. There is obviously something I'm missing, after all, this is just exchanging data between views.
UPDATE: I've changed the two "presentedViewController"s in my problem with "presentingViewController"s instead. I got this error message in the console: "Could not cast value of type 'Buttons.TabBarViewController' (0x10d590d90) to 'Buttons.ViewController' (0x10d590e10).
(lldb)"
"Buttons" would be the project name.
presentingViewController is your tabBarViewController, while presentedViewController is what you are expecting to be your ViewController that is being presented, but infact this variable is nil because at the point in time you clicked this button, the modal viewcontroller hasnt set the presentedViewController variable for some reason, so the variable is actually nil, and when you trying to go as! ViewController on a nil variable, you are trying to cast nil to something it cant be so you get the crash, you'll have to find some other way to get hold of your viewcontroller that will be presented
Red, most of this looks OK. Just a couple of tips.
You don't have to duplicate your variable declarations.
let soundURL: NSURL = NSURL(fileURLWithPath: soundFile)!
Swift will infer your type in this case by your assignment.
As others have mentioned, use descriptive class names for your controllers. It will save you a lot of time when you're trying to figure out the purpose of the class. We've all been there 8>).
You will need to instantiate an instance of your controller that holds the soundOutlet property. Something like:
let dvc = YourControllerClass()
dvc.soundOutlet.hidden = true
dvc.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
dvc.modalPresentationStyle = UIModalPresentationStyle.Popover
dvc.presentViewController(YourViewController, animated: true, completion: nil)
If you have not figured it out, post more of your implementation.
Good Luck.

Resources