Unexpectedly found 'nil' when running app on Swift - ios

I'm practicing creating a few different apps at the moment, and I'm trying to execute a simple pass data task from one storyboard to another.
I want to segue from a '+' button on the main view controller, to 'create a goal' on the 'Create Goal' view controller. (That works fine)
I then want the user to fill in three textfields on the CreateGoalViewController, and submit the data via a Create Goal button.
The user will then be segued back to the homepage main view controller, and the data from the three textfields entered into the three labels on the main view controller.
However, I'm getting the following error whenever I try to run the app:
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file /Users/jon/Desktop/Practice App Development/PracticePassDataOpna/PracticePassDataOpna/ViewController.swift, line 28
2020-04-18 16:15:05.181014+0100 PracticePassDataOpna[8267:419753] Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file /Users/jon/Desktop/Practice App Development/PracticePassDataOpna/PracticePassDataOpna/ViewController.swift, line 28
For reference, here are my View Controllers:
Main View Controller:
Create View Controller:
And here is my code:
Main VC:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var goalName: UILabel!
#IBOutlet weak var targetDate: UILabel!
#IBOutlet weak var longDescription: UILabel!
var goalText = ""
var dateText = ""
var descriptionText = ""
override func viewDidLoad() {
super.viewDidLoad()
goalName.text = goalText
targetDate.text = dateText
longDescription.text = descriptionText
}
#IBAction func createButton(_ sender: UIButton) {
performSegue(withIdentifier: "createSegue", sender: self)
}
}
Create VC:
import UIKit
class CreateGoalViewController: UIViewController {
#IBOutlet weak var goalInput: UITextField!
#IBOutlet weak var dateInput: UITextField!
#IBOutlet weak var descriptionInput: UITextField!
var goalAdd = ""
var dateAdd = ""
var descriptionAdd = ""
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func createGoalButton(_ sender: UIButton) {
self.goalAdd = goalInput.text!
self.dateAdd = dateInput.text!
self.descriptionAdd = descriptionInput.text!
performSegue(withIdentifier: "createGoalSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var vc = segue.destination as! ViewController
vc.goalText = self.goalAdd
vc.dateText = self.dateAdd
vc.descriptionText = self.descriptionAdd
}
}
And just to make things clearer - this is line 28:
goalName.text = goalText
Any help would be great - I don't understand why it's not working considering it's am empty string? Maybe that's the problem. Cheers!

The issue is in this function:
#IBAction func createGoalButton(_ sender: UIButton) {
self.goalAdd = goalInput.text!
self.dateAdd = dateInput.text!
self.descriptionAdd = descriptionInput.text!
performSegue(withIdentifier: "createGoalSegue", sender: self)
}
In this function you have force unwrapped an optional where there is no value. Force-unwrapping is very prone to errors so you should instead write the function as:
#IBAction func createGoalButton(_ sender: UIButton) {
let x = "You never added a goal"
let y = "You never added a date"
let z = "You never added a description"
self.goalAdd = goalInput.text ?? print(x)
self.dateAdd = dateInput.text ?? print(y)
self.descriptionAdd = descriptionInput.text ?? print(z)
performSegue(withIdentifier: "createGoalSegue", sender: self)
}

Related

Swift Segue unable to send the data between the controllers

I am using segue to pass the data between the controllers. I have three view controller .I was able to push the the data from first view controller to second one without any error . But I tried to use same process from second to third , It is showing following errors in build time . For third controller
file:///Users/test/Desktop/XcodeProject/HomeWork/PraticeSession/MovieDBHomeWork/MovieDBHomeWork/Base.lproj/Main.storyboard: error: Illegal Configuration: The view3 outlet from the DetailsViewController to the UILabel is invalid. Outlets cannot be connected to repeating content.
file:///Users/test/Desktop/XcodeProject/HomeWork/PraticeSession/MovieDBHomeWork/MovieDBHomeWork/Base.lproj/Main.storyboard: error: Illegal Configuration: The view2 outlet from the DetailsViewController to the UILabel is invalid. Outlets cannot be connected to repeating content.
file:///Users/test/Desktop/XcodeProject/HomeWork/PraticeSession/MovieDBHomeWork/MovieDBHomeWork/Base.lproj/Main.storyboard: error: Illegal Configuration: The view1 outlet from the DetailsViewController to the UIImageView is invalid. Outlets cannot be connected to repeating content.
Here is the screenshot of the properties not be repeated .
Here is the screenshot for third controller .
I do not see the same property of the repeated again from trigger .I do not why it is compelling.
Here is the code in first view controller.
class ViewController: UIViewController {
#IBOutlet weak var text: UITextField!
var nameText = ""
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func logIn(_ sender: Any) {
// guard text.text != nil else {return}
self.nameText = text.text!
performSegue(withIdentifier: "show", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var vc = segue.destination as! NewViewController
vc.finalname = self.nameText
}
}
Here is the code seconds view controller.
class NewViewController: UIViewController {
var mc : MovieViewCell!
#IBOutlet weak var tableView: UITableView!
private var presenter: MoviePresenter!
#IBOutlet weak var name: UILabel!
var finalname = ""
var movieTitleDetail = ""
var movieOverviewDeatil = ""
var movieImageDetail : UIImage?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
name.text = "Hello: " + finalname
setUpUI()
// configure presenter
presenter = MoviePresenter(view: self)
presenter.getMovies()
}
#IBAction func showDetail(_ sender: Any) {
self.movieTitleDetail = mc.movieTtile.text!
self.movieOverviewDeatil = mc.movieOverview.text!
self.movieImageDetail = mc.movieImage.image
performSegue(withIdentifier: "details", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var vc = segue.destination as! DetailsViewController
vc.finalmovieTitle = self.movieTitleDetail
vc.finalmovieOverview = self.movieOverviewDeatil
vc.finalmovieImage = self.movieImageDetail
}
}
Here is the third View controller code .
import UIKit
class DetailsViewController: UIViewController {
var finalmovieTitle = ""
var finalmovieOverview = ""
var finalmovieImage: UIImage?
#IBOutlet weak var view2: UILabel!
#IBOutlet weak var view1: UIImageView!
#IBOutlet weak var view3: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
view3.text = finalmovieTitle
view2.text = finalmovieOverview
view1.image = finalmovieImage
}
}

Why isn't the score showing up after segue on different view controller?

Iv created a quiz app, that tracks the user's score till the user gets to the end. There is an if statement - when it reaches the end of the quiz, a results View controller pops up. My Question is I want to get the same score value presented at the end on the resultsViewContoller. I have connected it via segue and the identifier has been placed. The app runs but when i get to the end the segue opens up the page but the score doesnt change?
How can i solve this problem?
The user finishes the app. I call the segue method and the screen comes up.
Results View Controller
import UIKit
class ResultsViewController: UIViewController {
var fruitness = Fruitness()
#IBOutlet weak var finalScoreLabel: UILabel!
var finalScore: String!
override func prepare(for: UIStoryboardSegue, sender: Any?) {
func viewWillAppear(_ animated: Bool) {
finalScoreLabel.text = finalScore
if finalScore != nil {
finalScoreLabel.text = "FINALSCORE:\(String(describing: Int(finalScore!)))"
}
}
}
}
Game View Controller
import UIKit
class GameViewController: UIViewController {
#IBOutlet weak var progressBar: UIProgressView!
#IBOutlet private weak var fruitLabel: UILabel!
#IBOutlet private weak var scoreLabel: UILabel!
#IBOutlet weak var replayGame: UIButton!
#IBOutlet weak var optionButton0: UIButton!
#IBOutlet weak var optionButton1: UIButton!
#IBOutlet weak var optionButton2: UIButton!
#IBOutlet weak var optionButton3: UIButton!
#IBOutlet weak var optionButton4: UIButton!
#IBOutlet weak var optionButton5: UIButton!
#IBOutlet weak var optionButton6: UIButton!
#IBOutlet weak var optionButton7: UIButton!
#IBOutlet weak var optionButton8: UIButton!
#IBOutlet private var fruitButtons: [UIButton]!
var score = 0
var fruitness = Fruitness()
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
#IBAction func replayGame(_ sender: UIButton) {
fruitness.restartGame() //Calling restart
updateUI()
}
#IBAction func touchButton(_ sender: UIButton) {
let userAnswer = sender.currentTitle!
//The userGotItRight is = to the checkAnswer function which goes through the fruitOptions array to make sure the answer is corrct. T/F
let userGotItRight = checkAnswer(userAnswer: userAnswer)
if userGotItRight {
//Depending if the user got the answer correct the button turns green/red
sender.backgroundColor = UIColor.green
} else {
sender.backgroundColor = UIColor.red
}
nextFruit() //Calling the next Fruit untill all the fruit items have been displayed.
//This timer is responsible for the UIColors green, red and clear. Without this timer the color drags onto the next fruit.
Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(updateUI), userInfo:nil, repeats: false)
}
// This check answer method needs an input to work. The input is the answer the user-choose (String).
func checkAnswer(userAnswer: String) -> Bool {
// Checks if the user got the answer correct. T/F
if userAnswer == fruitness.fruitOptions[fruitness.fruitNumber].fruit {
fruitness.score += 1 //We increase the value of score when we get the answer right.
// fruitness.finalScorez = fruitness.score
return true
} else {
return false
}
}
//Checks to make sure the eveytime it hits 0 it will shuffle.
func nextFruit() {
if fruitness.fruitNumber == 0 {
fruitness.fruitOptions.shuffle()
}
// print(fruitness.fruitOptions[fruitness.fruitNumber]) //Only gets printed in the consol
if fruitness.fruitNumber + 1 < fruitness.fruitOptions.count {
fruitness.fruitNumber += 1
} else {
self.performSegue(withIdentifier: "goToResultsVC", sender: self) //To call Segue
}
}
//Connecting and controlling oF the Segue and from GameView COntroller -> Results view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToResultsVC" {
let destinationVC = segue.destination as! ResultsViewController
destinationVC.finalScore = scoreLabel.text //Printing the final score at the end.
}
}
#objc func updateUI() {
//Controlls the background. Clearing ability between T/F answers.
optionButton0.backgroundColor = UIColor.clear
optionButton1.backgroundColor = UIColor.clear
optionButton2.backgroundColor = UIColor.clear
optionButton3.backgroundColor = UIColor.clear
optionButton4.backgroundColor = UIColor.clear
optionButton5.backgroundColor = UIColor.clear
optionButton6.backgroundColor = UIColor.clear
optionButton7.backgroundColor = UIColor.clear
optionButton8.backgroundColor = UIColor.clear
//The fruit name available that the user needs to match.
fruitLabel.text = fruitness.getFruitText()
//Displaying the progress of the user till they reach the end.
progressBar.progress = fruitness.getProgress()
//Displaying the score at all times
scoreLabel.text = "SCORE: \(fruitness.score)"
}
}
You have a number of problems with your code.
First off, the prepare(for:sender:) method gets called on the source view controller that triggers the segue, not the destination view controller.
Second, your ResultsViewController has a viewWillAppear(_:) method nested inside its prepare(for:sender:) method. Don't do that. The viewWillAppear(_:) method needs to be a top-level method of your view controller or it won't get called.
Also, viewWillAppear(_:) methods should call super.viewWillAppear(animated).
Your ResultsViewController's viewWillAppear method should look like this:
func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
finalScoreLabel.text = finalScore
}
The prepare(for:sender:) method in your GameViewController should set finalScore in your ResultsViewController so that when its viewWillAppear is called, finalScore has a value.
Edit:
(It looks like you got that last part correct. Your GameViewController's prepare(for:sender) does appear to set finalScore. Note, however, that you are using scoreLabel to hold your finalScoreValue. You should not save state in view objects. You should have a var in GameViewController that holds your score. You could make GameViewController set your score var, and have a didSet() method on the score var that installs the updated score value into the scoreLabel.)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToResultsVC" {
let destinationVC = segue.destination as! ResultsViewController
destinationVC.finalScore = scoreLabel.text //Printing the final score at the end.
}
}
Maybe you wish to refactor your repetitive code to this one:
let optionButtonArray = [optionButton0,optionButton1 ...]
optionButtonArray.forEach { $0.backgroundColor = UIColor.clear }

Passing an object with data from one view to another view in Swift using Segue

I am trying to pass an object with data from one view to another view using Swift4. However, I get this problem when trying to print.
Here is the message that I get:
2018-03-21 21:47:03.542578-0400 Sudoku[64427:1070575] <UIView:
0x7fd28bc0b4c0; frame = (0 0; 414 736); autoresize = W+H; layer =
<CALayer: 0x604000038500>>'s window is not equal to
<Sudoku.setupSudoku: 0x7fd28bf256c0>'s view's window!
Here is the picture of my View. Identifier for segue: setupScreen
Here is the code in setPlayer:
class setPlayer: UIViewController {
var singleUser:playerInfor=playerInfor()
var randomGame:Int = -1
var playerName:String=""
#IBOutlet weak var txtPlayerName: UITextField!
var sizeOfSudoku:Int = 9 //for project 1, let take one size of sudoku
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "Communication-Network-Vector-Illustration.jpg")!)
self.txtPlayerName.layer.borderWidth=2.0
self.txtPlayerName.layer.borderColor=UIColor.blue.cgColor
// Do any additional setup after loading the view.
}
func randomNumber(){
self.randomGame=Int(arc4random_uniform(3)+1)
}
func initialize(){
//if(size)
if txtPlayerName.text?.isEmpty ?? true {
self.playerName="Player1"
} else {
self.playerName=self.txtPlayerName.text!
}
self.singleUser=playerInfor(playerName:self.playerName,size:self.sizeOfSudoku,time:0, randomSudoku:self.randomGame)
//print (self.singleUser.size)
}
#IBAction func startButton(_ sender: Any) {
initialize()
performSegue(withIdentifier: "setupScreen", sender: self.singleUser)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "setupScreen"{
if let destination=segue.destination as?setupSudoku{
destination.objectPlayer=sender as?playerInfor
print(singleUser.playerName)
}
}
}
#IBAction func size9(_ sender: Any) {
self.sizeOfSudoku=9
}
#IBAction func Size6(_ sender: Any) {
self.sizeOfSudoku=9
}
Here is playerInfor
import Foundation
import UIKit
class playerInfor{
var playerName:String=""
var size:Int=0
var time:Int=var randomSudoku:Int=0
init(playerName:String,size:Int,time:Int,randomSudoku:Int) {
self.playerName=playerName
self.size=size
self.time=time
self.randomSudoku=randomSudoku
}
}
Here is setupSudoku
class setupSudoku: UIViewController , UICollectionViewDelegate, UICollectionViewDataSource{
#IBOutlet weak var stackViewButtons: UIStackView!
#IBOutlet weak var myCollectionView: UICollectionView!
var newNumber=0
var objectPlayer:playerInfor!
override func viewDidLoad() {
super.viewDidLoad()
print(self.objectPlayer?.time)
}
}
You have setup your segue incorrectly in your storyboard, and it is attempting to segue twice. To confirm this is the case, select your segue in your storyboard and check if your button highlights, like this:
To resolve, delete this segue and create a new one, by control-dragging from the view controller (not the button) to the destination, like so:
Make sure to reset the new segues identifier back to setupScreen. Then you should be good to go!

how to pass a UI ImageView from one view controller to another? swift 3

I am trying to pass this UI Image View from one view controller to another on the same storyboard. I have already passed a UI TextField to a UI Label and UI Button. Now I need to do it with a UI Image View.
Here are my two view controllers.
import UIKit
class PhotoShareViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var contentTextView: UITextView!
#IBOutlet weak var thatTextField: UITextField!
#IBOutlet weak var thisTextField: UITextField!
var presenter: PhotoShareModuleInterface!
var image: UIImage!
#IBAction func thisUploadPhoto(_ sender: Any) {
if thisTextField.text != "" && thatTextField.text != ""
{
performSegue(withIdentifier: "segue", sender: nil)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var photoShareLabelViewController = segue.destination as! PhotoShareLabelViewController
photoShareLabelViewController.thisString = thisTextField.text!
photoShareLabelViewController.thatString = thatTextField.text!
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
imageView.image = image
}
override var prefersStatusBarHidden: Bool {
return true
}
#IBAction func didTapCancel(_ sender: AnyObject) {
presenter.cancel()
presenter.pop()
}
/* #IBAction func didTapDone(_ sender: AnyObject) {
guard let message = thatTextField.text, !message.isEmpty else {
return
}
guard let messageOne = thisTextField.text, !messageOne.isEmpty else {
return
}
presenter.finish(with: image, content:message)
presenter.dismiss()
}
}
*/
}
extension PhotoShareViewController: PhotoShareViewInterface {
var controller: UIViewController? {
return self
}
}
if someone could tell me how to send the image from one view controller into another that would be awesome!
You didn't show PhotoShareLabelViewController, but presumably it has:
var thisString: String?
Add
var thisImage: UIImage?
And then pass it along the same way as thisString
photoShareLabelViewController.thisImage = imageView.image
And pick it up in the viewDidLoad of PhotoShareLabelViewController (which is probably where you pick up thisString). Something like:
self.imageView.image = self.thisImage
But, using whatever variable name you are using.
In short, just copy what you are doing for thisString, but use a UIImage as the type instead of String.
NOTE: You are not passing a UIImageView from one VC to another (that is impossible). You are passing the image from an imageview from one VC to another.

Accessing variables in other ViewControllers Swift 3

I have a problem I can't seem to solve myself, I have two view controllers, the first one contains three variables that stores integers. On my second view controller I have 3 sliders which manipulates a label under each slider with a number.
I want the numbers from these 3 sliders to replace the numbers that were set in the three variables on my first view controller when I click a button on the second view controller but when I when I type in the variable name it doesn't show up in the second view controller?
Can somebody explain what I may be doing wrong as I thought the variables were public and globally accessible throughout my app but I'm struggling to figure out what I'm doing wrong.
Here is some of my code:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var timer = Timer()
var softTime = 180
var mediumTime = 300
var hardTime = 600
var chosenTime = 0
I want softTime, mediumTime & hardTime to be changed from the button in the next view controller:
import UIKit
class SettingsViewController: UIViewController {
#IBOutlet weak var softLabel: UILabel!
#IBOutlet weak var softSliderValue: UISlider!
#IBAction func softSlider(_ sender: Any) {
let currentValue = Int(softSliderValue.value)
softLabel.text = "\(currentValue)"
}
#IBOutlet weak var mediumLabel: UILabel!
#IBOutlet weak var mediumSliderValue: UISlider!
#IBAction func mediumSlider(_ sender: Any) {
let currentValue = Int(mediumSliderValue.value)
mediumLabel.text = "\(currentValue)"
}
#IBOutlet weak var hardLabel: UILabel!
#IBOutlet weak var hardSliderValue: UISlider!
#IBAction func hardSlider(_ sender: Any) {
let currentValue = Int(hardSliderValue.value)
hardLabel.text = "\(currentValue)"
}
#IBAction func setTimesButton(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
//in Second VC
protocol PassDataDelegte: class {
func your method(first: String, second: String, third: String)
}
weak var delegate: PassDataDelegte?
func youction button() {
delegate?.yourmethod(first, timeString: second, third: date)
}
// in First VC
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "DeadlineSegue" {
let dvc = segue.destinationViewController as! YourSecondViewController
dvc.delegate = self
}
}
extension YourFirstViewController: PassDataDelegte {
func sendDateTime((first: String, second: String, third: String) {
print(first)
print(second)
print(third)
}

Resources