I'm a Swift beginner and I'm writing an app that will show a list of things. When you click on one of them, you'll get detailed information about it and a photo. However, I've got one problem - I've got code to show the image, but when I click in simulator it crashes.
Text that is on the bottom of xcode when the app crashes:
fatal error: unexpectedly found nil while unwrapping an Optional value
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var detail = segue.destinationViewController as! detailSkinsViewController
detail.skinNameLabel = self.skin
if (self.skin == "AK47 | Wasteland Rebel") {
detail.skinImg.image = UIImage(named: "s495fn")
}
}
Thanks!
You should look at the crash stack to see the actual line. There are a couple of places you could have trouble:
var detail = segue.destinationViewController as! detailSkinsViewController
This requests a crash if destinationViewController is not of class detailSkinsViewController. (Swift classes should always begin with a capital letter. This should also be a let, not var. You never modify it.) Using if-let here would be much safer.
detail.skinImg.image = UIImage(named: "s495fn")
It's very unclear what these are, but if skinImg is a UIImageView!, then you would expect this to crash if it the destination NIB has not been loaded yet (which is likely). You generally should never reach into other objects IBOutlets for exactly this reason. (This is also a good reason to use ? rather than ! for IBOutlets. That would lead to just "nothing happens" rather than a crash.)
Rather than messing with another view controllers outlets, you should create a UIImage? property on the view controller itself. In its didSet, update the UIImageView if the view is loaded (isViewLoaded). During viewDidLoad, initialize the UIImageView using the property. This way, you have a clear API for others to set the image that doesn't expose your internal subviews (which are implementation details).
As an example:
class ViewController: UIViewController {
#IBOutlet private var imageView: UIImageView?
var image: UIImage? {
didSet(newImage) {
self.imageView?.image = newImage
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.imageView?.image = self.image
}
}
You should check your optional values,
i did not see your skin variable definition but i think that code below will solve your problem
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let detail = segue.destinationViewController as! detailSkinsViewController {
detail.skinNameLabel = self.skin
if (self.skin == "AK47 | Wasteland Rebel") {
detail.skinImg.image = UIImage(named: "s495fn")
}
}
}
This Error comes normally when trying to unwrap a value of an optional variable which has no value at all.
First check your optional variables and debug line by line to see if any of the Optional variables has no value at all which you had been trying to unwrap
Related
I've created a new window controller that hosts a tabViewController inside my app. I've added classes to the window controller, and have the same class across all the view controllers in the tab view.
I can connect buttons and give them an action and it works perfectly, however, I try to connect an outlet and attempt to change something via the outlet, it returns nil and crashes the program. For example, this exact code works in the pre-made viewController of my app, but returns:
Unexpectedly found nil while implicitly unwrapping an Optional value
when running it through the new tab view controller I created.
What's weird to me is I can use a regular view controller and the outlets connect fine, but if I want to use a tab view controller, these nil errors are happening.
I made sure that the nil was not related to grabbing the inputs for the button by printing the audio devices, and the audio devices are there and able to be printed. It seems as if the button is not there even though it is connected.
I have also tried to simply change an NSTextField color after connecting it to an outlet and this returns the same nil error.
Any idea what I might be doing wrong here? Thanks so much for the help.
class FirstLaunchViewController: NSViewController {
var FLWindow: FirstLaunchWindowController?
var selectedAudioDevice = [String]()
#IBOutlet weak var deviceListPopUpButton: NSPopUpButton!
#IBOutlet weak var visualizeButton: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
self.preferredContentSize = NSMakeSize(self.view.frame.size.width, self.view.frame.size.height)
populateDeviceList()
}
#IBAction func CloseStartupGuide(_ sender: Any) {
self.view.window?.windowController?.close()
}
#IBAction func popUpDidChange(_ sender: Any) {
print("changed")
}
//grab inputs for button
fileprivate func populateDeviceList() {
deviceListPopUpButton.removeAllItems()
for device in AudioDevice.allInputDevices() {
var teststring = ""
teststring = device.uid!
print(device.name)
deviceListPopUpButton.addItem(withTitle: device.name)
deviceListPopUpButton.lastItem?.tag = Int(device.id)
selectedAudioDevice.append(device.uid!)
}
}
}
}
I have a segue named "hydrogenSegue" from a "hydrogenBoxButton" to a "Hydrogen" view controller. However, I also wanted to implement a table view so I could search for an element. I tried to make the code so when the cell is clicked it will segue over to the element's view. I used hydrogen as an example here.
In my main ViewController.swift file, I have this to transfer the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//If identifier equals the hydrogen element go to the elements Swift file
if segue.identifier == "hydrogenSegue" {
let hydrogenAtomicNumberPassing = segue.destination as! hydrogenViewController
hydrogenAtomicNumberPassing.hydrogenAtomicNumberPassed = hydrogenAtomicNumber
let hydrogenAtomicMassPassing = segue.destination as! hydrogenViewController
hydrogenAtomicMassPassing.hydrogenAtomicMassPassed = hydrogenAtomicMass
}
}
In the hydrogenViewController.swift file I have this:
import UIKit
class hydrogenViewController: UIViewController {
var hydrogenAtomicNumberPassed: Int!
var hydrogenAtomicMassPassed: Float!
#IBOutlet weak var hydrogenInformationLabel: UILabel!
#IBOutlet weak var hydrogenAtomicNumberLabel: UILabel!
#IBOutlet weak var hydrogenAtomicMassLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//Setting the background color
self.view.backgroundColor = UIColor.gray
//Converting hydrogen's atomic number from an Int to a String
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
hydrogenAtomicNumberLabel.text = "Atomic Number: \(hydrogenAtomicNumberString)"
//Converting hydrogen's atomic mass from a Float to a String
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
hydrogenAtomicMassLabel.text = "Atomic Mass: \(hydrogenAtomicMassString)"
}
}
I am getting the error at:
let hydrogenAtomicNumberString = String("\(hydrogenAtomicNumberPassed!)")
I'm assuming it would happen to this line also if I fix only that line:
let hydrogenAtomicMassString = String("\(hydrogenAtomicMassPassed!)")
I have this code in my "searchViewController" (the .swift file used for the table view):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("row selected : \(indexPath.row)")
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",
bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as!
hydrogenViewController
self.navigationController?.pushViewController(hydrogenSearchSegue,
animated:true)
}
}
When I click on the "Hydrogen" cell in the table view it crashes to this error:
Hydrogen cell
The crash
When I click on the "H" button in this image it will take me to the hydrogen view controller:
Image of the Hydrogen Button in the simulator (Top Left)
Image of the Hydrogen View Controller
I want the hydrogen cell to segue over to the hydrogen view controller just like the button can.
When this same issue came up earlier I just had an issue with the name of the segue in the storyboard. However, because there is no visible segue from the table view, I don't know how to fix the issue.
I've tried this:
performSegue(withIdentifier: "hydrogenSegue", sender: nil)
I was thinking that I could just reuse the "hydrogenSegue" from the button to the view controller but I get a SIGABRT error. It just says that there is no segue with the name "hydrogenSegue." It would be best if I could just reuse that segue in a way because everything is already connected but I now found out that the "searchViewController" can't recognize the segue. Any help is appreciated and my main goal is to just get the cell that is clicked on to move over to the element's designated view. I tried to provide as much information as possible without making it to long and if there is any more information needed, I should be able to provide it.
well. first answer
in your hydrogenViewController try with this lines.
var hydrogenAtomicNumberPassed: Int?
var hydrogenAtomicMassPassed: Float?
override func viewDidLoad(){
super.viewDidLoad()
self.viewBackgroundColor = .gray
}
override func viewWillAppear(){
super.viewWillAppear()
if let number = hydrogenAtomicNumberPassed
{
hydrogenAtomicNumberLabel.text = "Atomic Number: \(number)"
}
if let mass = hydrogenAtomicMassPassed
{
hydrogenAtomicMassLabel.text = "Atomic Mass: \(mass)"
}
}
Now, the segues only "lives" between a couple viewControllers, if you have a third view controller, the last will not recognize him.
other thing, you are using segues and navigation controller, from my point of view, it's a bad idea mixed both, I mean, there are specific apps that can use both ways to present views, only is a advice.
if you want to pass data with pushviewcontroller only use this line
if indexPath.row == 0 {
let hydrogenSearchSegue = UIStoryboard(name:"Main",bundle:nil).instantiateViewController(withIdentifier: "hydrogenView") as! hydrogenViewController
hydrogenSearchSegue.VAR_hydrogenViewController = YOURVAR_INYOURFILE
self.navigationController?.pushViewController(hydrogenSearchSegue, animated:true)
}
tell me if you have doubts, and I will try to help you.
I am creating an app that allows the user to see a random quote everyday. In this app, the user is asked 3 questions before being able to actually use the app. The last question is a simple "What is your favorite category/topic". With this prompt, the user will tap a cell and be brought to a Tab Bar Controller with the first "Child" view controller being the quote itself.
Problem:
I want the user to be able to tap a UITableViewCell and the one they tap effects which TabBarController they are brought to.
That is the photo with the errors I am running into so far. Here is the code.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "bookSegue")
{
let bookQuoteTabBar = segue.destinationViewController as! UITabBarController
let bookQuoteScreen = bookQuoteTabBar.viewControllers?[0] as? bookQuoteScreen
}
else if(segue.identifier == "businessSegue") {
let businessQuoteTabBar: UITabBarController = segue.destinationViewController as! UITabBarController
let businessQuoteScreen = businessQuoteTabBar.viewControllers?[0] as? businessQuoteScreen
}
}
Eventually, there will be more topics, meaning more segues. But for now, I'm starting with two
The segues for each TabBarController are:
"bookSegue"
"businessSegue"
The Tab Bars are:
"bookQuoteTabBar" and "businessQuoteTabBar"
The First "Child" View controllers are:
"bookQuoteScreen"
"businessQuoteScreen"
Should I have written something else? Did I correctly name the Segues, identities, and classes of each object? If you need more information or references, comment what I should add and I will add it within minutes. Thank you in advance!
---------Recent edits---------
BooksQuoteScreen:
import Foundation
import UIKit
class BooksQuoteScreen: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
BusinessQuoteScreen:
import Foundation
import UIKit
import Social
class BusinessQuoteScreen: UIViewController {
//============================//
//********** Outlets *********//
//============================//
let utility = Utility()
#IBOutlet weak var quoteDisplay: UILabel!
#IBOutlet weak var authorDisplay: UILabel!
#IBOutlet weak var quoteBackground: UIImageView!
...
}
The errors in your screenshot ("Use of undeclared type ....") indicate that Xcode does not recognise bookQuoteScreen and businessQuoteScreen as valid types. In the highlighted lines, eg.
let bookQuoteScreen = bookQuoteTabBar.viewControllers?[0] as? bookQuoteScreen
the type (specified after "as? ") must match up with the class name defined in your .swift files. Check very carefully that the names used match the class names (presumably) defined in "BusinessQuoteScreen.swift" and "BooksQuoteScreen.swift". Without seeing the contents of those files, I can't be certain, but I suspect the leading character needs to be upper case (it should be for class names), and you might need an "s" in
"BooksQuoteScreen":
let bookQuoteScreen = bookQuoteTabBar.viewControllers?[0] as? BooksQuoteScreen
and
let businessQuoteScreen = businessQuoteTabBar.viewControllers?[0] as? BusinessQuoteScreen
I have a game where I store the value of the high score of a player as "highScore", in the first view controllers.
What I want to do is to try to transfer this variable data, to the second view controller.
I entered in the following code in the first view controller's storyboard.
Btw, highscore2, is the variable that I delcared in the second view controller, to store the data from the first highscore variable.:
override func prepareForSegue(segue: UIStoryboardSegue!, sender:AnyObject!)
{
if (segue.identifier == "segueTest")
{
var svc = segue!.destinationViewController as! viewTwo;
svc.highScore2 = "High Score \(highScore)"
}
}
Now here is the code in my second view controller (viewTwo):
class viewTwo: UIViewController
{
#IBOutlet var highScoretwolbl: UILabel!
var highScore2:String!
override func viewDidLoad() {
highScoretwolbl.text = highScore2
}
}
For some reason, the code compiles, but the high score is not displayed, and is "nil".
Avoid forced unwrapping.
if let svc = segue.destinationViewController as? viewTwo{
svc.highScore2 = "High Score \(highScore)"
}
Then put in a breakpoint and make sure you got to all of these lines.
As stated in other answers, viewDidLoad only gets fired once. Since you
are using a forced unwrapped string, it is initially nil. It (highScore2) won't get a value until you set it which happens after viewDidLoad. Add some print statements so you can see the sequence of events.
Your best option is to use viewWillAppear. This will get fired every time
your view appears. This is what you want.
You don't need to use a force unwrapped variable. Just make a string a
make it empty initially.
override func prepareForSegue(segue: UIStoryboardSegue, sender:AnyObject?)
{
if segue.identifier == "segueTest"
{
if let svc = segue.destinationViewController as? viewTwo {
print("setting score in prepareForSegue")
svc.highScore2 = "High Score \(highScore)"
}
else {
print("something bad happened")
}
}
}
class viewTwo: UIViewController
{
#IBOutlet var highScoretwolbl: UILabel!
// The following uses forced upwrapping which means it could be nil
// if you never set it and your app will crash.
// var highScore2: String!
// Instead, do this:
var highScore2: String = ""
override func viewWillAppear(animated: Bool) {
// putting this here will ensure that every time the screen
// is shown, the label will get set from the string.
highScoretwolbl.text = highScore2
}
}
If I recall correctly, the viewDidLoad method is called only once when the view controller is first initialized. So you are setting the highScoretwolbl variable before highScore2 actually has any data, this is why it is nil.
I would recommend setting the text inside viewWillAppear so it updates every time the segue happens.
So something like
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
highScoretwolbl.text = highScore2
}
Here is a reference to the lifecycle of all the methods of a view controller. View controller lifecycle
I'm working on my first iOS app using swift. I'm trying to load the a value from one viewController into another. I am using a protocol, but I can't get it to execute properly. I have searched around both stack overflow and elsewhere but haven't been able to find an answer that works in my situation.
Here's the VC I'm trying to pull the value from:
protocol AddHelperVCDelegate {
func didFinishAddingHelper(controller: AddHelperViewController)
}
class AddHelperViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
#IBOutlet weak var tableView: UITableView!
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
var fetchedResultsController:NSFetchedResultsController = NSFetchedResultsController()
var delegate:AddHelperVCDelegate! = nil
var helperBonus:NSNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
fetchedResultsController = getFetchResultsController()
fetchedResultsController.delegate = self
fetchedResultsController.performFetch(nil)
// Do any additional setup after loading the view.
}
}
And here is where I am trying to move the value (and the view) back to a proceeding VC.
// UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var indexPathForRow = tableView.indexPathForSelectedRow()
println("indexPath for selected row is: \(indexPathForRow)")
let thisUser = fetchedResultsController.objectAtIndexPath(indexPath) as UserModel
var cell:UserCell = tableView.dequeueReusableCellWithIdentifier("helperCell") as UserCell
helperBonus = thisUser.effectiveCombat
helperBonus = Int(helperBonus)
println("helperBonus is: \(helperBonus)")
delegate.didFinishAddingHelper(self)
}
If I make the delegate an optional (delegate?.didFinishAddingHelper(self)) then nothing happens. If I do not, I get a crash with the error message:
indexPath for selected row is: Optional(<NSIndexPath: 0xc000000000018016> {length = 2, path = 0 - 3})
helperBonus is: 0
fatal error: unexpectedly found nil while unwrapping an Optional value
Now, I know I'm declaring delegate as nil, but that's the limit to my understanding of what's going on. I need to add the value in the proceeding VC in this function:
func didFinishAddingHelper(controller: AddHelperViewController) {
self.effectiveCombat = Int(controller.helperBonus)
controller.navigationController?.popViewControllerAnimated(true)
}
The crash is happening because AddHelperViewController's delegate property is nil. This is because you aren't setting it.
Wherever you create the AddHelperViewController, set its delegate on the next line:
let addHelperVC = AddHelperViewController()
addHelperVC.delegate = self
Then when you call the delegate property, it will point back to the view controller that created it.
If your AddHelperViewController is created using a storyboard, set delegate in the prepareForSegue(_:sender:) of the method that is about to show the new controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let vc = segue.destinationViewController as? AddHelperViewController {
// If we got here, vc is an AddHelperViewController
vc.delegate = self
}
}
var delegate:AddHelperVCDelegate! = nil
Should be
var delegate:AddHelperVCDelegate?
It is failing because you are using !, which means that you guarantee that, while it can't be initialized during the classes init, it will not be nil by the time you want to use it.
Aaron Brager is absolutely correct when he says you need to set the delegate property.
With your delegate declared as optional (with ?), you can call it only if the object is not nil (ignored otherwise):
delegate?.didFinishAddingHelper(self)
On a side note, you might also consider making the delegate property weak in order to help prevent retain cycles.