UIView is nil in prepareForSegue method - ios

sorry for confusing title, I didn't know how to make it more meaningfull.
I was implementing information window using this example: github
I have added few labels into popView and linked them to secondaryVieController.
I have amended prepareForSegue method in PrimaryViewController to:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showInfo" {
let info = segue.destinationViewController as! SecondaryViewController
info.adrLabel.text = branch.address
info.nameLabel.text = branch.name
info.telLabel.text = branch.tel
dim(.In, alpha: dimLevel, speed: dimSpeed)
}
}
But when I run it i get error "fatal error: unexpectedly found nil while unwrapping an Optional value"
it is because info.popupView is nil.
But can you explain me how should I set it? And why it is working fine if there are no any additional labels.
Thanks.

The first method where you will be access this
info.adrLabel.text = branch.address
info.nameLabel.text = branch.name
info.telLabel.text = branch.tel
values will be viewDidLoad() of SecondaryViewController.
You need to create variables and assign values in those variables and in the viewDidLoad() you can use those variables to assign the value to your labels.

You are getting nil because you are trying to set values before the view has loaded from your prepareForSegue method. Instead, setup some variables in your SecondaryViewController to hold the values and set up your labels in viewDidLoad:
class SecondaryViewController {
var address: String?
var name: String?
var tel: String?
override func viewDidLoad() {
adrLabel.text = address
nameLabel.text = name
telLabel.text = tel
}
}
Now all you need to do in your prepareForSegue is:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showInfo" {
let info = segue.destinationViewController as! SecondaryViewController
info.address = branch.address
info.name = branch.name
info.tel = branch.tel
dim(.In, alpha: dimLevel, speed: dimSpeed)
}
}

As a general rule you should not try to manipulate another view controller's views directly. That violates the principle of encapsulation. You should consider them private.
Instead, add properties to your destination view controller and set those properties. Then in your destiination view controller's viewWillAppear method install the values from those properties into the appropriate views.
That way if you later change your view controller's views you only have to change the code in one place (the view controller)
(Plus, in situations like your prepareForSegue function, it doesn't work.)

Related

Why do view objects of the destination ViewController default to nil in prepareForSegue?

I was wondering if someone could explain why the outlets that are view objects of the destination ViewController in the prepareForSegue() function are set to nil. I can only guess that it means that at the time PrepareForView is called, these objects are not created yet. Wouldn't it make sense though that by the time you have your destination ViewController object, you'd have the view object outlets associated with it initialized as well? I also know it's probably not good practice to directly modify the values of another ViewController's view, but I just want to understand the inner workings of Swift 3 better. Thanks!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ItemController"
{
let indexPath = tableView.indexPathForSelectedRow?.row
if let itemStruct = itemList[indexPath!] as ItemStruct?
{
let correspondingItemController : ItemController = segue.destination as! ItemController
if let textView = correspondingItemController.textView
{
print("This is not nil!") //this will NOT get hit. Why is this still nil??
}
correspondingItemController.itemStruct = itemStruct
}
}
}
As you said at the time of prepareForSegue outlet objects are not yet created. ViewController loads/creates its view when the view property is accessed. When prepareForSegue is being called your destination view controller is intantiated but its view is yet to be loaded. You can force controller to load its view from prepareForSegue by accessing view property.
let correspondingItemController : ItemController = segue.destination as! ItemController
let _ = correspondingItemController.view // Forces controller to load its view.
Now you can access your outlets, but its not recommended. A good approach would be create variable in destination controller, set its value from prepareForSegue
// prepareForSegue
correspondingItemController.name = "something"
// Destination controller
var name:String
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
textView.text = name
}

Set value in random ViewController (random segue)

I have 12 ViewControllers. My code gives a random segue to one of these ViewControllers (1-12)
let segues = ["View1", "View2", "View3", "View4", "View5", "View6", "View7", "View8", "View9", "View10", "View11", "View12"]
let index = Int(arc4random_uniform(UInt32(segues.count)))
let segueName = segues[index]
self.performSegueWithIdentifier(segueName, sender: self)
.
Now, I want to change a variable in the random ViewController that has been chosen (var firstSegue = false) but I can't figure out how?
.
Could someone change this into something that will work?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as UIViewController
destinationVC.firstSegue = true
}
Make a protocol with the firstSegue property and extend that to all of your view controllers. Then use as? TheProtocolYouMade instead of as UIViewController
Or use a common sub-class.
EDIT: sample code
Make a protocol with the firstSegue property
protocol P {
var firstSegue: Bool { get set }
}
extend that to all of your view controllers.
extension YOURViewController1: P {
}
It is assumed that YOURViewController1 has a var firstSegue: Bool in it. Now that property is how YOURViewController1 conforms to protocol P. Do this for all view controllers that have that property.
Now you can write this code
if let asP = segue.destinationViewController as? P {
// you can access asP.firstSegue here
}
As this controller has property that's not from standard UIViewController, than it's some custom controller, so you can cast it and than fill this value. As well you can use KVC - which is not really safe solution.

Why can't set a variable value of a viewController by using another viewController?

I know this is Duplicate Question, but i didn't find the solution for my problem. I have two controllers. HomeViewcontroller's variable of "myValue"'s value can't be set by using ViewController class.I don't know the issue here but code is working without error.But when i print "myValue" it shows nil.Destination view controller is not the controller that i want to set variable value. Destination view controller is a tab bar controller, so i want to set the variable value of first tab bar and second tab bar view controllers. This is my code :-
import UIKit
class ViewController: UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if(segue.identifier == "gotoHome") {
println("1111")
var storyboard = UIStoryboard(name: "Main", bundle: nil)
var viewController = storyboard.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
viewController.myValue = 8888
}
}
}
this is the other view controller
import UIKit
class HomeViewController: UIViewController {
var myValue: Int!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
println(myValue)
}
}
You don't need to instantiate the viewcontroller, as it is already been done, you just need to fetch the viewcontroller for the current segue operation, so your code should look like below
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if(segue.identifier == "gotoHome") {
println("1111")
let controller = segue.destinationViewController as! UITabBarController
let homeController = controller.selectedViewController as !HomeViewController
homeController.myValue = 8888
}
}
Hope it helps.
Change to :
let viewController = segue.destinationViewController as! HomeViewController
You're printing the value of myValue in viewDidLoad which is called when you're instantiating it from your storyboard. You're setting myValue after creating your instance. Try to print myValue in viewWillAppear for example, then it should be set.

setting destinationviewcontroller's label's text in prepareForSegue throws error: unexpectedly found nil while unwrapping Optional value

I am sure the string is not nil and that the label exists, I am trying to find out why the text in the label is nil. The other members of the destinationViewController are getting set correctly , but as soon as I add the line to set the label the program crashes.
// Mark segue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get reference to the destination view controller
var detailVC = segue.destinationViewController as! DetailViewController
var detailImages: Array<UIImage> = []
detailImages.append(UIImage(named: "pup.jpg")!)
detailImages.append(UIImage(named: "dog.png")!)
// Set the property to the selected location so when the view for
// detail view controller loads, it can access that property to get the feeditem obj
detailVC.selectedLocation = _selectedLocation;
println(_str!)
detailVC.myLabel.text = "hello"
}
This is because the outlet for myLabel will not get set in prepareForSegue, so it will be nil. Try below approach instead,
create a string var in DetailViewController like,
var labelText: String?
in prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get reference to the destination view controller
var detailVC = segue.destinationViewController as! DetailViewController
var detailImages: Array<UIImage> = []
detailImages.append(UIImage(named: "pup.jpg")!)
detailImages.append(UIImage(named: "dog.png")!)
// Set the property to the selected location so when the view for
// detail view controller loads, it can access that property to get the feeditem obj
detailVC.selectedLocation = _selectedLocation;
println(_str!)
detailVC.labelText = "hello"
}
and in viewDidLoad of DetailViewController
override func viewDidLoad() {
super.viewDidLoad()
self.myLabel.text = labelText
// Do any additional setup after loading the view.
}
Yes, in the end I changed the last line in prepareForSegue to
detailVC.myString = _str as? String
I am not sure if I the the as? since in my detailVC I have added:
var myString:String?
Where would be the best place to convert the NSString to String, or it doesn't matter?
Thanks!

(Swift) PrepareForSegue: fatal error: unexpectedly found nil while unwrapping an Optional value

DetailViewController:
#IBOutlet var selectedBundesland: UILabel!
TableViewController:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "BackToCalculator") {
var vc:FirstViewController = segue.destinationViewController as FirstViewController
vc.selectedBundesland.text = "Test"
}
IBOutlet is connected!
Error: fatal error: unexpectedly found nil while unwrapping an Optional value
I read multiple pages about Optionals but i didn't know the answer to my problem.
Do you need more information about my project?
You cannot write directly to the UILabel in prepareForSegue because the view controller is not fully initialised yet. You need to create another string property to hold the value and put it into the label in the appropriate function - such as viewWillAppear.
DetailViewController:
var textValue: String = ""
#IBOutlet weak var selectedBundesland: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
selectedBundesland.text = textValue
}
TableViewController:
if (segue.identifier == "BackToCalculator") {
var vc:FirstViewController = segue.destinationViewController as FirstViewController
vc.textValue = "Test"
}
Recently had this problem. The problem was that I had dragged the segue from a specific object from my current view controller to the destination view controller - do not do this if you want to pass values.
Instead drag it from the yellow block at the top of the window to the destination view controller. Then name the segue appropriately.
Then use the if (segue.identifier == "BackToCalculator") to assign the value as you are currently. All should work out!
I just had the same problem, I solved it by defining a string that is not connected to an outlet in the new view controller and than referring to it in the prepareForSegue() method, in the new VC I made the label outlet to take the value of the non connected string in the viewDidLoad() method.
Cheers
While the correct solution is to store the text and attach it to the label later in viewDidLoad or something, for testing proposes, you can bypass the issue by forcing the destinationViewController to build itself from storyboard by calling its view property like:
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if (segue.identifier == "TestViewController") {
var vc:FirstViewController = segue.destination as! TestViewController
print(vc.view)
vc.testLabel.text = "Hello World!"
}
}
made for Swift 3.0 with love

Resources