I have an issue that has been driving me crazy, i had my values working inside my main VC which is named ViewController for both Age and Gender, i tested this with print statement and it works, this is below
#IBAction func uploadBtnAction(_ sender: UIButton) {
NetworkServices.instance.getGender(image: imageView1.image!) { (gender) in
self.Gender = gender
// print(self.Gender!) // This prints a value
}
NetworkServices.instance.getAge(image: imageView1.image!) { (age) in
self.Age = age
// print(self.Age!) //This prints a value
}
performSegue(withIdentifier: "updates", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "updates" {
let destinationVC = segue.destination as! SecondViewController
destinationVC.userImage = image1
destinationVC.genderFromUser = Gender
destinationVC.ageFromUser = Age
}
}
but when i segue to another VC named SecondViewController which is below, i suddenly get an error "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value", i have created variables to hold my values which will come from the first VC but this first VC does not pass the values after seque, please help, i have checked stackoverflow and tried all solutions to no avail, i though this should be the easiest part, funny enough, my image successfully passes over from the first VC to second during seque. Please advice.
import UIKit
import CLTypingLabel
class SecondViewController: UIViewController {
var userImage: UIImage?
var genderFromUser: String?
var ageFromUser: Int?
override func viewDidLoad() {
super.viewDidLoad()
imageView2.image = userImage
genderlabel.text = "Gender:"
ageLabel.text = "Age:"
print(ageFromUser!) // fatal error here
print(genderFromUser!) // fatal error here
print(userImage) // This is always successful
// ageUser.text =
// genderUser.text = userGender
// Do any additional setup after loading the view.
}
#IBOutlet weak var ageUser: UILabel!
#IBOutlet weak var genderUser: UILabel!
#IBOutlet weak var imageView2: UIImageView!
#IBOutlet weak var genderlabel: CLTypingLabel!
#IBOutlet weak var ageLabel: CLTypingLabel!
}
My goal is to set labels to the values passed over from the first VC at the second VC.
The problem is the order of operations. Your NetworkServices.instance.get... methods are asynchronous. I will use numbers to show you the order in which your code actually runs:
#IBAction func uploadBtnAction(_ sender: UIButton) {
NetworkServices.instance.getGender(image: imageView1.image!) { (gender) in
self.Gender = gender // 2 or 3
}
NetworkServices.instance.getAge(image: imageView1.image!) { (age) in
self.Age = age // 3 or 2
}
performSegue(withIdentifier: "updates", sender: self) // 1
}
So the segue is performed, and prepare is called, and the instance variables genderFromUser and ageFromUser are set, before you have set up your self.Gender and your self.Age. Therefore they are set to nil, because that's what they are at the time.
Later, of course, your asynchronous code comes along and does set self.Gender and self.Age, but it's too late; the train has left the station. SecondViewController already exists, and it has been configured with nil values.
Of course, since it does exist, you could now come along and set those instance properties again and get the SecondViewController to update its interface in response. Or you might work out some other solution. In general, this is a tricky problem — the problem of transitioning to another view controller at a time when its data is not ready yet and has to be fetched asynchronously.
[Also, you need to ask yourself, in fashioning a solution, what should happen if the data never arrives. What if the network chooses to go down at the exact moment the user taps the button? Welcome to the wild and wooly world of real life!]
Related
This question already has answers here:
Unexpectedly found nil IBOutlet in prepareForSegue
(2 answers)
Closed 5 years ago.
I have a simple entity:
extension Team {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Team> {
return NSFetchRequest<Team>(entityName: "Team")
}
#NSManaged public var name: String?
#NSManaged public var phone: String?
#NSManaged public var department: String?
#NSManaged public var position: String?
}
Picture for easy understanding:
I load current entity from Core Data to App successfully. Furthermore, I have a UITableViewController with property members (it's storage of fetch result from Team entity).
var members: [Team] = []
After app launching (viewDidLoad(_:)) members.count is equal to 7 and that is right. Also, all these elements of members are using by UITableVIew in UITableViewController.
My task is open Detailed View Controller who will retrieve data from tapped cell. For this delegation I use classic prepare(for:sender:) method in UITableViewController (who is segue.source in current case):
// MARK: - Navigation
extension TeamListViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == segueIdentifier,
let memberDetailsViewController = segue.destination as? MemberDetailsViewController else {
fatalError("Wrong destination!")
}
guard let selectedIndex = tableView.indexPathForSelectedRow?.row else {
fatalError("Out of range")
}
guard let name = members[selectedIndex].name,
let phone = members[selectedIndex].phone,
let department = members[selectedIndex].department,
let position = members[selectedIndex].position else {
fatalError("No correct data")
}
memberDetailsViewController.memberNameLabel.text! = name
memberDetailsViewController.memberPhoneNumberLabel.text! = phone
memberDetailsViewController.memberDepartmentLabel.text! = department
memberDetailsViewController.memberPositionLabel.text! = position
}
}
So, App launches, I tap any cell whichever I want to open for more details and.. App crashes!
fatal error: unexpectedly found nil while unwrapping an Optional value
However, my variables (name, phone, department, position) are ok. They are with values!
Oh, yes. I try to retrieve a data for UILabels in MemberDetailsViewController below:
// MARK: - IBOutlets
#IBOutlet weak var memberNameLabel: UILabel!
#IBOutlet weak var memberPhoneNumberLabel: UILabel!
#IBOutlet weak var memberDepartmentLabel: UILabel!
#IBOutlet weak var memberPositionLabel: UILabel!
What is that can be a problem here?
Because of your are set your data in prepare(for segue: UIStoryboardSegue, sender: Any?) method in TeamListViewController where MemberDetailsViewController is not loaded properly thats way you got nil.
My suggestion is: Add Object like var members: Team? in MemberDetailsViewController and pass it from TeamListViewController so error will not appear.
Currently working with two view controllers and a swift file dealing with the details of a store such as the phone number. There is a main ViewController and a DetailsViewController.
I currently acquire data from google places api and am able to successfully store the values in a PlaceDetails Class. Testing out the data - I am able to print to the console. However, when I try to assign a value taken from the API to a UILabel the application crashes and shows that the value of the property is nil. I am not sure why this is the case. I feel in the two view controllers I am instantiating the PlaceDetails class correctly and accessing it's properties correctly but there is something I am not aware of that is going wrong.
class ViewController: UIViewController
{
let placeDetails = PlaceDetails()
let detailsVC = DetailsViewController()
func tapLabel( sender: UITapGestureRecognizer )
{
// print statement successfully prints out the stored value as - Optional("1 888-555-5555")
print(placeDetails.phoneNumber)
// assigning value to label causes a crash stating value is nil
detailsVC.phoneNumberLabel.text = placeDetails.phoneNumber!
self.performSegueWithIdentifier("showDetailsVC", sender: self)
}
}
class DetailsViewController: UIViewController
{
#IBOutlet weak var phoneNumberLabel : UILabel!
let placeDetails = PlaceDetails()
override func viewDidLoad()
{
super.viewDidLoad()
//This approach does not cause a crash but outputs nil to the console for both the print statement and the assignment statement
print(placeDetails.phoneNumber)
phoneNumberLabel.text = placeDetails.phoneNumber!
}
}
class PlaceDetails
{
override init()
{
super.init()
}
var phoneNumber : String? //viewcontroller actions give this class property its value
}
You need to assign placeDetails to your destination view controller in prepareForSegue. I know you aren't doing this as you have created placeDetails as a let constant rather than a variable so it can't ever change from the empty PlaceDetails you originally assign.
You should declare it as an optional variable and then unwrap it properly when you use it;
class ViewController: UIViewController
{
let placeDetails = PlaceDetails()
func tapLabel( sender: UITapGestureRecognizer )
{
self.performSegueWithIdentifier("showDetailsVC", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "showDetailsVC") {
let destVC = segue.destinationViewController as! DetailsViewController
destVC.placeDetails = self.placeDetails
}
}
}
class DetailsViewController: UIViewController
{
#IBOutlet weak var phoneNumberLabel : UILabel!
var placeDetails: PlaceDetails?
override func viewWillAppear(animated: Bool)
{
super.viewWillAppear(animated)
if let placeDetails = self.placeDetails {
if let phoneNumber = placeDetails.phoneNumber {
self.phoneNumberLabel.text = phoneNumber
}
}
}
}
You can't use the value in viewDidLoad as this method will execute before the property has been set; the view controller is loaded before prepareForSegue is called, so viewWillAppear is a better place.
Try to cast your phoneNumber in a string.
detailsVC.phoneNumberLabel.text = String(placeDetails.phoneNumber!)
Also, the nil value could come from the encoding method of the response of the API.
EDIT
I think you have to set the text of your UILabel in the viewDidLoad() method of your showDetailsVC. Declare a string variable in showDetailVC, and then pass your placeDetails.phoneNumber variable to the string you just declare. (Instead of directly set the text in the tapLabel() method). Then in your
showDetailVC set the text to your UILabel in the viewDidLoad()
I am attempting a very simple task of passing data between two view controllers. I have control-dragged the label into the proper class and allowed the segues to populate with the correct data. Upon clicking the row, the prepareForSegue action is as:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "individualChat" {
if let indexPath = tableView.indexPathForSelectedRow {
let friend: Friend
friend = mappedFriends[indexPath.row]
let controller = segue.destinationViewController as! IndividualChatController
controller.friendChat = friend
}
}
}
and into the residing view controller:
import UIKit
class IndividualChatController: UIViewController {
#IBOutlet weak var anotherTestLabel: UILabel!
var friendChat: Friend! {
didSet {
configureView()
}
}
func configureView() {
if let friendChat = friendChat {
anotherTestLabel.text = friendChat.name as? String
}
}
}
Yet, upon clicking the row, the error appears:
fatal error: unexpectedly found nil while unwrapping an Optional value
on line:
anotherTestLabel.text = friendChat.name as? String
How can this be fixed?
Additional Screen Capture:
When you execute some code in prepareForSegue the next view is not loaded yet so all your outlets are still set to nil; because of this when you try to implicitly unwrap the content of anotherTestLabel at
anotherTestLabel.text = friendChat.name as? String
you get that error.
You can check if your label has been loaded and only if that's the case set its text using
anotherTestLabel?.text = friendChat.name as? String
and then manually calling configureView() in viewDidLoad() of IndividualChatController to proper load the label.
The added ? will check if your label is nil, if so nothing will be done, else it will proceed unwrapping the label and setting the text.
This question already has answers here:
How do you share data between view controllers and other objects in Swift?
(9 answers)
Closed 6 years ago.
I have been able to pass data from one view to the next.
Now I need to expand a bit and pass this data via three view controllers.
I have three view controllers 1.MileageControler, 2.LocationsControler and 3.LocationChoiceController.
I need to be able to pass data from 1 to 2 then onto 3 and then back to 2.
Mileage Controler has a textbox and a series of numeric buttons that allow the user to enter a mileage. An Enter button passed the following on seque
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "segueLocationView") {
let passingData = segue.destinationViewController as! LocationViewController;
passingData.mileageToPass = DisplayStart.text
passingData.fuelAmountToPass = 0.00
passingData.startLocationToPass = "HOME"
passingData.endLocationToPass = ""
}
}
The LocationController has:
var mileageToPass: String!
var fuelAmountToPass: Double!
var startLocationToPass: String!
var endLocationToPass: String!
override func viewDidLoad() {
super.viewDidLoad()
labelStartMileage.text = "Start Mileage for this trip: " + mileageToPass
labelStartLocation.text = startLocationToPass
labelEndLocation.text = endLocationToPass
}
It also has...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "segueChoiceMade") {
let passingData = segue.destinationViewController as! LocationChoiceViewController;
passingData.mileageToPass = mileageToPass
passingData.fuelAmountToPass = fuelAmountToPass
passingData.startLocationToPass = labelStart.text
passingData.endLocationToPass = txtEndLocation.text
if (sender === but_StartLocation) {
passingData.senderToPass = "Start"
} else if (sender === but_Destination) {
passingData.senderToPass = "End"
}
}
}
So here I'm trying to pass the data on again to the next view (LocationChoice) where the user will choose a location and based on which senderToPass value was passed then the chosen location will be stored and passed back in either startLocationToPass or endLocationToPass. The mileageToPass and fuelAmmountToPass are not changed in LocationChoice just stored and passed back.
The problem I'm having are errors which just show up as... Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) Am I going about this the wrong way....
You could create a new singleton class to hold on to the set of data you are interested in.
Something like this:
import Foundation
class Data: NSObject {
static let sharedData = Data()
var mileage: String?
var fuelAmount: Int?
var startLocation: String?
var endLocation: String?
}
Then in your view controllers you can get and set the values like:
// Set fuel amount in one view controller
Data.sharedData.fuelAmount = 0.00
// And retrieve it in another
let fuelAmount = Data.sharedData.fuelAmount
You're using -prepareForSegue:sender: properly. Your case is pretty simple, so you can get away with setting the values like you're doing, which is great.
The error message provided is a pretty generic "something really bad happened", and there is no backtrace to work from. If you don't have any more info, I recommend setting a breakpoint in the -prepareForSegue:sender: method and stepping line by line until it crashes.
As an aside, I recommend wrapping up the 4 values you're passing around into a model object (with 4 immutable values) and just passing that around.
I have used dimissViewControllerAnimated to return the view from Spring Insert Variables back to Spring Element. At the same time, my input datas should be passed from Spring Insert Variables back to Sprint Element by the function unwindSecondView in Spring Element class.
But apparently the datas are not pass, my arrays: force and stiffness still do not contain value. Can anyone advice on this?
Code From Spring Insert Variables class
class springInsertVariables : UIViewController {
var forceVar = [Float] ()
var stiffVar = [Float] ()
#IBOutlet weak var forceEntered: UITextField!
#IBOutlet weak var stiffnessEntered: UITextField!
#IBAction func submit(sender: AnyObject) {
forceVar.append((forceEntered.text as NSString).floatValue)
stiffVar.append((stiffnessEntered.text as NSString).floatValue)
println(forceVar) //This doesn't print on my output
println(stiffVar) //This doesn't print on my output
dismissViewControllerAnimated(true, completion: nil)
}
}
Code From Spring Element Class
#IBAction func unwindSecondView(segue: UIStoryboardSegue) {
if let svcspringinsertvariables = segue.sourceViewController as? springInsertVariables {
self.force = svcspringinsertvariables.forceVar
self.stiffness = svcspringinsertvariables.stiffVar
println(force)
println(stiffness)
}
}
dismissViewControllerAnimated does not actually use the unwind segue. An unwind segue is like any other segue and must be performed using performSegueWithIdentifier.