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.
Related
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 looking for some clarification and hope someone can help me understand.
My app is completely built programmatically and I’m not using storyboards at all, therefore I don’t have any segues.
I’m trying to pass a CoreData object from one VC to another via the didSelectRowAt method of a tableView.
I have the object being returned at the indexPath correctly, but appear to have issues in passing that object across to the new VC.
In this code extract, my only concern at the moment is to get the CoreData object passed so I can then access all of the associated data.
ie: when the place is passed, I would like to print the name, latitude, longitude.
This will allow me to continue on with the app and use that data in the new VC.
Thanks in advance.
// PlacesVC
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let placeOnMap = fetchedResultsController.object(at: indexPath)
let destVC = MapVC()
destVC.placePassed = placeOnMap
self.navigationController?.pushViewController(destVC, animated: true)
print(placeOnMap.placeName!)
}
// MapVC
import UIKit
class MapVC: UIViewController {
var placePassed = PlaceEntity?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
// print(placePassed.placeName!)
// print(placePassed.latitude!)
// print(placePassed.longitude!)
}
}
Updated Information
In the PlacesVC(), the following is occurring.
If I option click on the let placeOnMap, I get…
Declaration: let placeOnMap: PlaceEntity
The line of code… destVC.placePassed = placeOnMap, gives the following error.
Cannot assign value of type 'PlaceEntity' to type 'PlaceEntity?.Type'
On the MapVC()
If I option click on the placePassed var, I get…
Declaration: let placePassed: PlaceEntity?.Type
Also, this line of code… var placePassed = PlaceEntity?, gives the following error.
Expected member name or constructor call after type name
and If I uncomment the print statement on the destVC, this error pops up…
Type 'PlaceEntity?' has no member 'placeName'
I believe my issues are something to do with the way I am setting the var on the receiving VC because it is adding the .Type to the declared var.
This is the part I am not understanding. Thanks
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 have the following two ViewControllers (one is TableViewController) that
CategoryTableViewController has a delegate method which will be called when Table Cell is selected. Then CreateRecipeViewController implements this delegate method and handles the selected category string.
I'm getting fatal error: unexpectedly found nil while unwrapping an Optional at the line delegate.categoryController(self, didSelectCategory: categoryCell)
print(categoryCell) correctly prints the string value of selected table cell so I'm not sure why I'm getting this error.
I'm new to implementing protocol so there may be a high chance I'm doing this wrong. I'd much appreciate if some one can give me a clue of this error.
CategoryTableViewController (choose category)
protocol CategoryTableViewControllerDelegate: class {
func categoryController(controller: CategoryTableViewController, didSelectCategory category: String)
}
class CategoryTableViewController: UITableViewController {
...
weak var delegate: CategoryTableViewControllerDelegate!
...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let categoryCell = recipeCategory[indexPath.row].name {
print(categoryCell)
delegate.categoryController(self, didSelectCategory: categoryCell)
}
}
}
CreateRecipeViewController (receives selected category)
extension CreateRecipeViewController: CategoryTableViewControllerDelegate {
func categoryController(controller: CategoryTableViewController, didSelectCategory category: String) {
let selectedCategory = category
dismissViewControllerAnimated(true, completion: nil)
let cell = RecipeCategoryCell()
cell.configureSelectedCategoryCell(selectedCategory)
recipeCategoryTableView.reloadData()
}
}
UPDATE
The problem is that you forgot to assign a value to the property delegate. In other words, delegate is nil.
Since delegate is an implicitly unwarped optional type, you don't need to add a question mark or an exclamation mark to unwrap it. This is why you didn't see the mistake.
To solve this, just put a question mark after delegate:
delegate?.categoryController(self, didSelectCategory: categoryCell)
But that doesn't solve the problem! This way, categoryController(:didSelectCategory:) will never be called!
Unfortunately, I cannot think of a way to pass data between view controllers using delegation. But there is a simpler way to do that.
For simplicity, let's call CategoryTableViewController the sender and CreateRecipeViewController the receiver.
This is what I guess you want to do. You want the user to select a category in the sender and pass the selected category to the receiver. So in the sender, you need to call performSegueWithIdentifier, right?
When the view is going to perform a segue, prepareForSegue is called. You need to override that method:
override func prepareForSegue(segue: UIStoryBoardSegue) {
if segue.identifier == "your segue's identifier" {
let destinationVC = segue.destinationViewController as! reciever
destinationVC.selectedCategory = self.selectedCategory
}
}
There is some stuff that is undefined in the above code. Let's define them now.
The first thing is destination.selectedCategory. You add a property in the receiver:
var selectedCategory: String!
In the viewDidLoad of the receiver, you can use this property to know what the user selected. In other words, the data is passed to this property.
The second is self.selectedCategory. Let's define that as well, in the sender:
var selectedCategory: String!
In the tableView(:didSelectRowAtIndexPath:indexPath:) of sender, you need to assign self.selectedCategory the category selected:
self.categorySelected = recipeCategory[indexPath.row].name
And BOOM! That's it!
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