How to pass data between view controllers - not working - swift

Im trying to pass an player score from VC1 to a view that displays the current scores of all players(4). the display view is on a separate view controller than where the player(s) score is defined and stored.
I have done the prepare(segue) and im able to pass other variables to the display VC(ThirdViewController). but when I try to assign the player score to the uilabel.text It tells me that I have unwrapped a nil value
I have even tried to just set the label text to a static string and still get the nil error.
class ViewController: UIViewController {
var name = String()
var player1Score = 1
var player2Score = 2
var player3Score = 3
var player4Score = 4
//MARK: ********* IBOutlets **********
#IBOutlet weak var playerSegmentOutlet: UISegmentedControl!
#IBOutlet weak var diceSegmentOutlet: UISegmentedControl!
#IBOutlet weak var targetScoreSliderOutlet: UISlider!
#IBOutlet weak var matchTargetSwitchOutlet: UISwitch!
#IBOutlet weak var targetScoreLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? SecondViewController {
VC.player1CurrentScore = player1Score
VC.player2CurrentScore = player2Score
VC.player3CurrentScore = player3Score
VC.player4CurrentScore = player4Score
}
}
Second view Controller
class SecondViewController: UIViewController {
#IBOutlet weak var CurrentRoundScoreLabel: UILabel!
#IBOutlet weak var CurrentPlayerScoreLabel: UILabel!
var player1CurrentScore = 1
var player2CurrentScore = 1
var player3CurrentScore = 1
var player4CurrentScore = 1
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? ThirdViewController {
VC.player1ScoreLabel.text = String(player1CurrentScore)
VC.player2ScoreLabel.text = String(player2CurrentScore)
VC.player3ScoreLabel.text = String(player3CurrentScore)
VC.player4ScoreLabel.text = String(player4CurrentScore)
}
}
Third View Controller
class ThirdViewController: UIViewController {
#IBOutlet var player1ScoreLabel: UILabel!
#IBOutlet var player2ScoreLabel: UILabel!
#IBOutlet var player3ScoreLabel: UILabel!
#IBOutlet var player4ScoreLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
no matter what I try to do with with UILabel.text it shows up as nil
I'm totally frustrated and I'm sure I am missing something simple because of my frustration, please someone help me.

This is an inefficient way to do this as you are passing data through 3 different objects. However, going on with this methodology, the problem is the label's are not created yet inside
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? ThirdViewController {
VC.player1ScoreLabel.text = String(player1CurrentScore)
VC.player2ScoreLabel.text = String(player2CurrentScore)
VC.player3ScoreLabel.text = String(player3CurrentScore)
VC.player4ScoreLabel.text = String(player4CurrentScore)
}
}
See, the label isn't created yet. So, you are setting text on aUILabel that isn't initialized. Therefore, you need to create variables for the labels inside ThirdViewController.
Third View Controller
class ThirdViewController: UIViewController {
#IBOutlet var player1ScoreLabel: UILabel!
#IBOutlet var player2ScoreLabel: UILabel!
#IBOutlet var player3ScoreLabel: UILabel!
#IBOutlet var player4ScoreLabel: UILabel!
var score0:Int!
var score1:Int!
var score2:Int!
var score3:Int!
override func viewDidLoad() {
super.viewDidLoad()
self.player1ScoreLabel.text = String(score0)
self.player2ScoreLabel.text = String(score1)
self.player3ScoreLabel.text = String(score2)
self.player4ScoreLabel.text = String(score3)
}
}
and change the segue in SecondViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let VC = segue.destination as? ThirdViewController {
VC.score0 = player1CurrentScore
VC.score1 = player2CurrentScore
VC.score2 = player3CurrentScore
VC.score3 = player4CurrentScore
}
}
Another Way:
Let's create a singleton called Game. Within this (assuming you only have 4 players) we can create 4 players that will never change. This allows us to create instances of players in one location and call upon them as necessary.
NOTE: A singleton can be misused EASILY.
https://cocoacasts.com/what-is-a-singleton-and-how-to-create-one-in-swift
https://cocoacasts.com/are-singletons-bad/
class Game {
static var score0:Int = 0
static var score1:Int = 0
static var score2:Int = 0
static var score2:Int = 0
}
Then, anywhere in your code you can access Game.score0, Game.score1.
CAUTION:
I would caution you to very carefully use singletons. You don't want everything with public access. You need to determine if this is good for you or not. Cheers.

Related

Passing boolean from one View controller to another, both controlled by a Tab Controller

I've been looking at other questions/answers around this and I can't seem to find an answer thats worked for me. I'm making a game where you click a pizza and you generate money. You start off with only being able to generate one dollar through clicking but as you purchase Upgrades this number gains in value. In theory, when you click the level 1 upgrade button it will send a true value to the main game view, making a click earn 5 dollars instead of 1.
import UIKit
class GameViewController: UIViewController {
var wallet: Int = 0
var totalIncome: Int = 0
var level1UpgradeUsed: Bool = false
#IBOutlet weak var pizzaButton: UIButton!
#IBOutlet weak var labelWallet: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
print(level1UpgradeUsed)
// Do any additional setup after loading the view.
}
#IBAction func pizzaClick(_ sender: Any) {
let upgrades = UpgradesViewController()
let level1UpgradeUsed = upgrades.level1used
if level1UpgradeUsed == true {
wallet = wallet + 4
}
wallet = wallet + 1
labelWallet.text = ("$" + String(wallet))
}
In the upgrades controller I'm trying to send the true value once the button is clicked
import UIKit
class UpgradesViewController: UIViewController {
public var level1used: Bool = false
public var level2used: Bool = false
public var level3used: Bool = false
public var level4used: Bool = false
var level5used: Bool = false
#IBOutlet weak var buttonLevel1: UIButton!
#IBOutlet weak var buttonLevel2: UIButton!
#IBOutlet weak var buttonLevel3: UIButton!
#IBOutlet weak var buttonLevel4: UIButton!
#IBOutlet weak var buttonLevel5: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let gameViewController = segue.destination as? GameViewController {
gameViewController.level1UpgradeUsed = self.level1used
}
}
#IBAction func Level1Click(_ sender: Any) {
if level1used == false{
buttonLevel1.backgroundColor = UIColor.gray
level1used = true
}
else {
}
}
Im geussing I'm not utilizing the TabBarController to pass data and not sure at all how to go about doing that. I'm new to swift and teaching myself everything so I'm sorry if this seems like a redundant question. It feels like it should be a lot simpler then it looks.

passing CoreData from UITableViewCell to viewcontroller

I'm trying to create the feature where when a user clicks on a specific cell in my UITableView, the Project will segue to a new ViewController and show all the information that has been saved in the CoreData. The Problem is that when I touch on a cell I get an error of
Unexpected nil while unwrapping optional value
Here is my code as it is right now within the ViewController that has the TableView
class ContactViewController: UIViewController, UITableViewDataSource, NSFetchedResultsControllerDelegate, UITableViewDelegate {
var selectName:String?
var selectPhone:String?
var selectComment:String?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectName = contact[indexPath.row].name
selectPhone = contact[indexPath.row].phone
selectComment = contact[indexPath.row].comments
performSegue(withIdentifier: "MySegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegue"{
if let destVC = segue.destination as? IndiViewController {
destVC.NameLabel = selectName
destVC.PhoneLabel = selectPhone
destVC.CommentLabel = selectComment
}
}
}
This is my code in IndiViewController (the VC in which I want the user to view the contact)
class IndiViewController: UIViewController {
#IBOutlet var NameLabel: UILabel!
#IBOutlet var PhoneLabel: UILabel!
#IBOutlet var CommentsLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
I've tried a few other methods but all still delivered the same error
I troubleshooted a bit to see which variable truly was causing the nil by doing this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegue"{
if let destVC = segue.destination as? IndiViewController {
destVC.NameLabel = "selectName" //I thought if perhaps the nil was the var selectName this would at least let me know
}
}
However even through this, the app crashes and gives the same error. So I think the issue is with the Labels in the IndiViewController.
So I tried creating an empty String and assigning it to NameLabel like this:
class IndiViewController: UIViewController {
#IBOutlet var NameLabel: UILabel!
var name = ""
override func viewDidLoad() {
super.viewDidLoad()
NameLabel.text = name
}
}
but still no luck.
What am I doing wrong?
Yet it can be an IBOutlet error, even for typo.
delete #IBOutlet var NameLabel: UILabel!
remove the link in the StoryBoard "referencing outlets" tab
click-drag a new outlet for the UILabel and do not name it with a capital letter
// #IBOutlet var NameLabel: UILabel!
#IBOutlet var nameLabel: UILabel!
Edit
Also try to pass your informations in two times :
create a new var in your destinationViewController like :
class IndiViewController: UIViewController {
#IBOutlet var nameLabel: UILabel!
var myText: String = ""
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = myText // assign the var to the labels text
}
}
In your prepareForSegue method assign myText and not the nameLabel.text
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegue"{
if let destVC = segue.destination as? IndiViewController {
destVC.myText = "selectName"
}
}
}
my guess is that you cant acces IBOutlets in prepare(for segue:) method. The destination ViewController is not built yet

Transfer TextField data between ViewControllers

I have ViewController, which has a textfield and a button. The user enters his name into the textfield and hits the DONE button. When he hits the button, he is segued to GifteeDetails, which is a different view controller. There is a label in that viewController that is supposed to display his name. But, his name doesn't show up. I don't receive an error.
Here's ViewController:
#IBOutlet weak var textGifteeName: UITextField!
#IBAction func toDetails(_ sender: Any) {
performSegue(withIdentifier: "toDetails", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destDetails: GifteeDetails = segue.destination as! GifteeDetails
destDetails.nametext = textGifteeName.text!
destDetails.agetext = "\(Int(age)! - 2000 + 17)"
destDetails.locationstext = labelGifteeLocationsPreview.text!
destDetails.intereststext = labelGifteeInterestsPreview.text!
}
Here's GifteeDetails:
var nametext = String()
var agetext = String()
var locationstext = String()
var intereststext = String()
#IBOutlet weak var labelGifteeName: UILabel!
#IBOutlet weak var labelGifteeAge: UILabel!
#IBOutlet weak var labelGifteeLocations: UILabel!
#IBOutlet weak var labelGifteeInterests: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
nametext = labelGifteeName.text!
agetext = labelGifteeAge.text!
locationstext = labelGifteeLocations.text!
intereststext = labelGifteeInterests.text!
}
Sorry about all the !. Swift gives me an error unless I have them.
You are updating the strings nametext and others, which are not connected to your labels.
You need to replace this piece of code:
destDetails.nametext = textGifteeName.text!
destDetails.agetext = "\(Int(age)! - 2000 + 17)"
destDetails.locationstext = labelGifteeLocationsPreview.text!
destDetails.intereststext = labelGifteeInterestsPreview.text!
With:
destDetails.labelGifteeName.text = textGifteeName.text!
destDetails.labelGifteeAge.text = "\(Int(age)! - 2000 + 17)"
destDetails.labelGifteeLocations.text = labelGifteeLocationsPreview.text!
destDetails. labelGifteeInterests.text = labelGifteeInterestsPreview.text!
nametext is a String object, and it is different from labelGifteeName.text which is the String of the label you want to update.
If you use segue then pls remove it, and then create action method of that button in the FirstviewController and use pushViewController to move on the SecondviewController.
Swift 3
Example:
#IBAction func toDetails(_ sender: Any)
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "SecondView") as! SecondView
UserDefaults.standard.set(self. textGifteeName.text, forKey: "savedStringKey")
UserDefaults.standard.synchronize()
self.navigationController?.pushViewController(vc,
animated: true)
}
In SecondView:
override func viewDidLoad() {
super.viewDidLoad()
self.labelGifteeName.text = UserDefaults.standard.string(forKey: "savedStringKey")!
}
Please Update your func viewDidLoad() method in GifteeDetailsVC with labelGifteeName.text! = nametext , labelGifteeAge.text!= agetext instead of your code because you have already assigned the value in to strings i.e, nametext and agetext in ViewController, you need to display string value in label

unexpectedly found nil while unwrapping an Optional value prepareForSegue

I am beginner in swift and working on one project where I am using collectionView. From collectionView, I want to transfer some values to details view but I am getting the above mentioned error. Values are not nil but somehow, it is giving this error while performing segue. Anybody help me, I am badly stuck here.
//In my CollectionView Controller.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "DetailsView")
{
if let vc:DetailsViewController = segue.destinationViewController as? DetailsViewController
{
vc.details.text = self.description
vc.line.text = self.subText
vc.startTime.text = self.formatted_time
}
}
}
//DetailsViewController
import UIKit
class DetailsViewController: UIViewController {
#IBOutlet var startTime: UILabel!
#IBOutlet var line: UILabel!
#IBOutlet var details: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
This basically means your IBOutlets are not yet initialised.
You should set strings and then in the viewDidLoad set you labels.
So to sum up:
Add string properties in your DetailsViewController
Set these string properties in your preparForsegue function
in the viewDidLoad of your DetailsViewController, set your labels
Your code should look like something like this :
//In my CollectionView Controller.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "DetailsView")
{
if let vc:DetailsViewController = segue.destinationViewController as? DetailsViewController
{
vc.detailsString = self.description
vc.lineString = self.subText
vc.startTimeString = self.formatted_time
}
}
}
//DetailsViewController
import UIKit
class DetailsViewController: UIViewController {
#IBOutlet var startTime: UILabel!
#IBOutlet var line: UILabel!
#IBOutlet var details: UILabel!
var startTimeString: String?
var lineString: String?
var detailsString: String?
override func viewDidLoad() {
super.viewDidLoad()
startTime.text = tmpStartTimeString
line.text = tmpLineString
details.text = tmpDetailsString
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Double check my code, I've wrote it very quickly ;)
The IBOutlets in a destination view controller aren't set until some time after prepareForSegue finishes
In DetailsViewController, add three instance variables:
var startTime: String?
var line: String?
var details: String?
Then in prepareForSegue, set those three values:
vc.details = self.description
vc.line = self.subText
vc.startTime = self.formatted_time
Then in viewDidLoad of DetailsViewController
detailsLabel.text = self.details
lineLabel.text = self.line
startTimeLabel.text = self.startTime
As the two answer above are point out when you create an instance of a UIViewController like for example in the prepareForSegue when you call segue.destinationViewController as? DetailsViewController this not mean that the #IBOutlet's are injected or initialized yet. The #IBOutlet's are initialized when the view is fully loaded, so you can do two of the following options:
Create variables in your UIViewController in which you can save the values after the init of the UIViewController in the prepareForSegue and then in the viewDidLoad() of the another UIViewController you set the values for the #IBOutlet's.
Another option is call the view (e.g let _ = vc.view) property when you create the instance of the UIViewController, in this way you can force the view to load load fully and you can set your #IBOutlet's from the prepareForSegue.
I hope this help you.

passing a core data object in pieces instead of one object

normally
in segues i pass coredata object instances in their entirety e.g.
if segue.identifier == "postCall" {
let vc: PostCallViewController = segue.destinationViewController as PostCallViewController
let indexPath = tableView.indexPathForSelectedRow()
let cell = fetchedResultsController.objectAtIndexPath(indexPath!) as Contacts
vc.contact = cell
}
but
this time i want to load vc.contact with data from different sources
#IBOutlet weak var pic: UIImageView!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "numberSelected" {
let vc: EditViewController = segue.destinationViewController as EditViewController
vc.contact.status = "newCall"
vc.contact.created = NSDate()
vc.contact.phone = selectedNumber
vc.contact.phoneType = selectedType
vc.contact.name = nameLabel.text!
vc.contact.photo = UIImageJPEGRepresentation(pic.image,1)
vc.contact.memo = ""
}
}
editcontroller
class EditViewController: UIViewController {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var phoneLabel: UILabel!
#IBOutlet weak var numField: UITextField!
#IBOutlet weak var userPic: UIImageView!
#IBOutlet weak var memoArea: UITextView!
var img:NSData!
var contact: Contacts!
let appDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
nameField.text = contact?.name
phoneLabel.text = contact?.phoneType
numField.text = contact?.phone
memoArea.text = contact?.memo
img = contact?.photo
userPic.image = UIImage(data: img)
}
the error
i keep on getting optional is nil errors on
vc.contact.phone = selectedNumber
and the rest of lines in that block even though none of them are optional variables.
question
is this type of passing data with a coredata object broken into its attribute allowed or not?
i think i could just pass variables individually without using core data, but there is another segue that leads to the same controller which uses the coredata contact entity and i would like to keep it consistent