I'm trying to initialize a variable in a view controller before that view controller takes over the view, and it seems no matter how I try to initialize it, it doesn't work and the software runs as if the value is the default value. Part of the problem seems to be the view that contains the variable in question is nil when I try to set the variable's value. I don't know why this is. Any help would be appreciated. Thanks! Here is my code:
override func setStartPosition()
{
if sessionNameIndex != nil
{
if let curSession = ShareData.sharedInstance.sessionDataObjectContainer[sessionNames[sessionNameIndex]]
{
initialValue = YesNo(rawValue: (curSession.currentValue))
if mySessionDisplay == nil
{
mySessionDisplay = SessionDisplayView(frame: self.view.frame)
if mySessionDisplay == nil
{
var shouldneverbehere = 0 //Always goes here!
}
else
{
mySessionDisplay.onView(index: 2, sessionName: sessionNames[sessionNameIndex])
mySessionDisplay.curScene.setStartPosition(newValue: val!)
}
}
}
}
}
This function gets called in the following code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if let destination = segue.destination as? SessionDisplayViewControllerTwo
{
if let myNames = sender as? [String]
{
destination.sessionNames = myNames
destination.sessionNameIndex = 1
destination.setStartPosition()
}
}
}
Let me know if you need more information. Once again, thanks for your consideration of this matter.
A view controller's views have not yet been loaded in prepareForSegue. As #muescha suggests in his comment, you should set properties in prepareForSegue, and then wait for viewDidLoad or viewWillAppear before you try to install them in your views. I'm not sure why your attempt to manually create your SessionDisplayView is failing, but you should not be trying to create views in prepareForSegue in any case, so the solution is "Don't do that."
Related
I have a view controller that shows detail for my "SkillGroup" entity. I want to use this for both viewing and editing/creating a "SkillGroup". Thus I have an optional variable skillGroup which is either unset - and nil when you are first creating the SkillGroup and before you save it, OR it is set and you will be simply viewing the SkillGroup. Here is my code
class GroupViewController:UIViewController {
var skillGroup: SkillGroup?
override func viewDidLoad() {
super.viewDidLoad()
if let skillGroup = skillGroup {
self.title = skillGroup.name
}
}
}
and in the previous view controller:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "createGroupSegue" {
let destination = segue.destination as? GroupViewController
destination?.createOrEdit = true
}
if segue.identifier == "showGroupSegue" {
if let selectedGroupPath = self.groupsTableView.indexPathForSelectedRow {
let destination = segue.destination as? GroupViewController
destination?.createOrEdit = false
let group = groups[selectedGroupPath.row]
destination?.skillGroup = group
}
}
}
If I set a breakpoint right after the call to super, and I inspect skillGroup, it says its <uninitialized>. I don't think this because its "nil" like a normal optional variable that hasn't been set. Additionally I did set the skillGroup variable in the prepare for segue code.
I really can't find much information on what means. Can someone help me out here?
I think you are inspecting the wrong variable in the debugger (the local variable instead of the instance variable).
I made a quick sample:
class ViewController: UIViewController {
public class TestClass {
var foo: String
var bar: Int
init() {
foo = "foo"
bar = 4711
}
}
public var test: TestClass?
override func viewDidLoad() {
super.viewDidLoad()
test = TestClass()
if let test = test {
print(test.foo)
}
}
}
When I set a breakpoint on the line if let test = test { I will see the following in the debugger:
Here you can see that it will print <uninitialized> when I print the description of the local test variable because the let statement on that line was not executed yet.
Please note that there is a local variable (only living inside the if scope) and an instance variable. In the debugger you will see the instance variables when you expand the self node.
For some reason this prepare code for a segue crashes because newGridViewController is nil. Any ideas?
override func prepare(for segue: UIStoryboardSegue,sender: Any?) {
if segue.identifier == "Grid" {
if let newGridViewController = segue.destination as? GridViewController
if savePhotoWithSlicer.isOn {
newGridViewController?.savePhotoWithSlicer = true
if (newGridViewController?.savePhotoWithSlicer)! { print("TRUE") }
}
}
}
I guess the problem is let newGridController = segue.destination as? GridViewController
My thought is your GridViewController is nil. Since you force unwrap newGridViewController in this line if (newGridViewController?.savePhotoWithSlicer)! { print("TRUE") } the code crashes.
Show us some more code to give you detailed explanations.
You don't say what the exception is, but I imagine that it is "unexpectedly found nil..." caused by the force unwrap on the second last line,
I suspect that the root cause is the conditional downcast to GridViewController failed, so newGridViewController is actually nil. Since you use a conditional unwrap everywhere except the second last line, you don't get a crash until that point.
A better structure is to use an if let... conditional downcast:
if let newGridViewController = segue.destination as? GridViewController {
newGridViewController.savePhotoWithSlicer = savePhotoWithSlicer.isOn
if newGridViewController.savePhotoWithSlicer {
print("TRUE")
}
}
This will prevent the crash, but it probably still won't print "TRUE" as I strongly suspect that segue.destination is not a GridViewController - You need to check your storyboard and make sure that you have the right custom class for your scene.
UPDATE
Since you have now clarified that the segue leads to a navigation controller that embeds the GridViewController you can use this to get the desired view controller:
override func prepare(for segue: UIStoryboardSegue,sender: Any?) {
if segue.identifier == "Grid" {
if let navController = segue.destination as? UINavigationController {
if let newGridViewController = navController.viewControllers.first as? GridViewController {
newGridViewController.savePhotoWithSlicer = savePhotoWithSlicer.isOn
if newGridViewController.savePhotoWithSlicer {
print("TRUE")
}
}
}
}
}
Objective c and Swift will happily let you "assign" a value to nil, and that's probably what you are doing. Just b/c you assign "true" to nil, doesn't mean you actually did anything ("true" just gets ignored). So when you force unwrap the nil, you still crash.
I have a MapViewController with a prepareForSegue(_:sender:)method, which I intend to use to send data to LandmarkTableViewController, and is called when a button is pressed.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationvc = segue.destinationViewController
if let landmarkvc = destinationvc as? LandmarkTableViewController {
if let identifier = segue.identifier {
let library = Landmark(name: "Run Run Shaw Library", properties: ["Chinese Kanji", "Gray", "Green Plants"])
let bank = Landmark(name: "Hang Seng Bank", properties: ["Chinese Kanji", "Green"])
switch identifier {
case "showLibrary" : landmarkvc.passedLandmark = library // pass data to LandmarkTableViewController
case "showBank" : landmarkvc.passedLandmark = bank // pass data to LandmarkTableViewController
default : break
}
}
}
}
The LandmarkTableViewController is properly set up to display the String array properties, with one String on each row. So what I intend to do is pass the appropriate data for the table to properties according to which button was pressed, and let LandmarkTableViewController display the corresponding properties.
class LandmarkTableViewController: UITableViewController {
var properties = [String]()
var passedLandmark = Landmark(name: "temp", properties: ["temp"]) // initially set to default value
override func viewDidLoad() {
super.viewDidLoad()
loadSampleProperties()
}
func loadSampleProperties() {
self.properties = passedLandmark!.properties
}
// other methods....
}
class Landmark {
var name: String
var properties: [String]
init?(name: String, properties: [String]) {
self.name = name
self.properties = properties
// Initialization should fail if there is no name or if there is no property.
if name.isEmpty || properties.isEmpty {
return nil
}
}
However, when I run the code, only temp is displayed in the table view. I've been stuck on this for a long time now, so any help is much appreciated!
Edit: loadData() inside of viewDidLoad() is changed to the correct loadSampleProperties(). I made an error while posting the code to the question.
I think this should solve your problem if not double check your identifiers
and you can make sure to data passing with adding print(passedLandmark) to viewDidLoad() or breakpoint to make sure you getting the data
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationvc = segue.destinationViewController
if let landmarkvc = destinationvc as? LandmarkTableViewController {
if segue.identifier == "showLibrary" {
let library = Landmark(name: "Run Run Shaw Library", properties: ["Chinese Kanji", "Gray", "Green Plants"])
landmarkvc.passedLandmark = library
}
if segue.identifier == "showBank" {
let bank = Landmark(name: "Hang Seng Bank", properties: ["Chinese Kanji", "Green"])
landmarkvc.passedLandmark = bank
}
}
}
Hope this will helps
Code is missing from your quote, so I can't be sure, but I assume your loadData() method is the one that reloads the table view data with Landmark you've passed in prepareForSegue. If that is the case:
viewDidLoad() is called before prepareForSegue, so that all the views and elements of the destinationViewController are loaded and ready to use. Thus, in your case, the table view is loaded with your "temp" data and nothing makes it reload when you set the proper one.
You have two options:
You could call loadData()/reloadData() in viewWillAppear for example, which is called after prepareForSegue(). Bare in mind that viewWillAppear will possibly be called again in some other navigation.
Otherwise, you could instantiate and present/push the new controller in your parent view controller, instead of using the segue.
To pass data between views, I decided to use a "temporary" object that would act as the data model of my views.
var tempMedecine = TempMedecine()
var xValue = 0
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let dmController = segue.destinationViewController as? JRBDosageMainTableViewController {
dmController.tempMedecine = self.tempMedecine
}
}
#IBAction func unwindToViewController(segue: UIStoryboardSegue) {
if let dosageController = segue.sourceViewController as? JRBDosageMainTableViewController {
self.tempMedecine = dosageController.tempMedecine!
self.xValue = 10
let dosageCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: 1, inSection: 0))
dosageCell?.detailTextLabel?.text = String(self.tempMedecine.dosageQuantity!) + " " + self.tempMedecine.dosageQuantityType!
}
}
override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool {
if identifier == "saveMedecine" {
print(xValue)
guard tempMedecine.name != nil else {
Common.genericAlertController(self, title: "Error", message: "You need to define a name", preferedStyle: .Alert)
return false
}
guard self.tempMedecine.dosageQuantityType != nil else {
Common.genericAlertController(self, title: "Error", message: "You need to set a quantity", preferedStyle: .Alert)
return false
}
}
else {
return true
}
return false
}
This is some of my code from the "index" viewController where I need to tackle validation.
As you can see all of my viewControllers have a property named tempMedecine. I pass it around and update the data if needed.
The problem is that self.tempMedecine.dosageQuantityType returns nil in the shouldPerformSegueWithIdentifier method but isn't returning nil in the unwindToViewController method.
I figured there could be two instances of my TempMedecine object, but that's not the case. I also thought there might be a problem with the way I pass the tempMedecine variable between my viewControllers but the property tempMedecine.name is effectively transfered, the only difference is that this property is set in the same viewController where I want to implement validation :
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
self.tempMedecine.name = textField.text
return true
}
It's really looking like I'm working with two different scope. As soon as I leave the unwindToViewController method, I would get back to another scope where the tempMedecine variable isn't updated.
But the weird part is when I use a simple variable like xValue. If I update its value in the unwindToViewController method I get the correct value in shouldPerformSegueWithIdentifier
What am I missing? Thanks for your help.
Okay, I fixed it. What happened was that I implemented some code to reset the dataSource which is tempMedecine if the user decided to click on the Back Button in the navigation bar :
if self.isMovingToParentViewController() {
tempMedecine?.dosageQuantity = nil
tempMedecine?.dosageQuantityType = nil
}
The thing is, I never thought the issue could come from this as I can use the tempMedecine data to set the value in my tableView after unwinding to the index viewController but I totally missed the part when object are passed my reference.
I assure you that i have checked all the answers prior posting this question on Unwrapping an object, but this thing simply does not seem to work for me. I am simply trying to pass on a text value from my cell tapped to the Label on the next screen.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if(segue.identifier == "detailViewSegue")
{
var DVC:DetailedViewController = segue.destinationViewController as DetailedViewController
let path:NSIndexPath = self.tableView.indexPathForSelectedRow()
var segueRecipeName:String = recipeMgr.RecipeName[path.row] as String
DVC.detailedRecipeLabel.text = segueRecipeName
}
}
The problem occurs at this line -
DVC.detailedRecipeLabel.text = segueRecipeName //Can't UnWrap Optional - None
I know I'm supposed to check the nil value of segueRecipeName before assigning. I do a print check on the console and it clearly is not null. The error occurs only when I'm assigning the value to the 2nd view controller class object. I'm sure this will help others learning Swift :)
DVC.detailedRecipeLabel is nil
Probably it's a IBOutlet that hasn't been loaded yet.
You should add a custom property to your DetailedViewController that will accept your text, save it and then you can assign it to the label in viewDidLoad.
Example:
class DetailedViewController ... {
...
var recipeName: NSString? = nil
...
override func viewDidLoad() -> () {
...
self.detailedRecipeLabel.text = self.recipeName
}
}
DVC.recipeName = segueRecipeName