Swift - Accessing implicitly unwrapped variable gives a nil error - ios

I'm following a tutorial on CoreData and I've been following it exactly, yet when they run the app, everything works and saves correctly, yet I get a nil error. The tutorial is a few years old, so I'm not sure if something has been udpated in the way CoreData works. It's an app to save goals.
Here's the first view controller where you enter the text of the goal and if it is short or long term:
import UIKit
class CreateGoalViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var goalTextView: UITextView!
#IBOutlet weak var shortTermButton: UIButton!
#IBOutlet weak var longTermButton: UIButton!
#IBOutlet weak var nextButton: UIButton!
var userGoalType: GoalType = .shortTerm
override func viewDidLoad() {
super.viewDidLoad()
nextButton.bindToKeyboard()
shortTermButton.setSelectedColor()
longTermButton.setDeselectedColor()
print("\(userGoalType)")
goalTextView.delegate = self
}
#IBAction func nextButtonPressed(_ sender: Any) {
if goalTextView.text != "" && goalTextView.text != "What is your goal?" {
guard let finishVC = storyboard?.instantiateViewController(withIdentifier: "FinishVC") as? FinishGoalViewController else {return}
finishVC.initData(description: goalTextView.text!, type: userGoalType)
print("\(finishVC.goalType.rawValue) after next button pressed")
performSegue(withIdentifier: "goToFinish", sender: self)
}
}
#IBAction func longTermButtonPressed(_ sender: Any) {
userGoalType = .longTerm
longTermButton.setSelectedColor()
shortTermButton.setDeselectedColor()
print("\(userGoalType)")
}
#IBAction func shortTermButtonPressed(_ sender: Any) {
userGoalType = .shortTerm
shortTermButton.setSelectedColor()
longTermButton.setDeselectedColor()
print("\(userGoalType)")
}
#IBAction func backButtonPressed(_ sender: Any) {
dismiss(animated: true)
}
func textViewDidBeginEditing(_ textView: UITextView) {
goalTextView.text = ""
goalTextView.textColor = UIColor(ciColor: .black)
}
}
And here's the following view controller where you set the number of times you want to do that goal where the CoreData functions are:
import UIKit
import CoreData
class FinishGoalViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var createButton: UIButton!
#IBOutlet weak var pointsTextField: UITextField!
var goalDescription: String!
var goalType: GoalType!
func initData(description: String, type: GoalType) {
self.goalDescription = description
self.goalType = type
}
override func viewDidLoad() {
super.viewDidLoad()
createButton.bindToKeyboard()
pointsTextField.delegate = self
}
#IBAction func createGoalPressed(_ sender: Any) {
if pointsTextField.text != ""{
self.save { finished in
if finished {
dismiss(animated: true)
}
}
}
}
#IBAction func backButtonPressed(_ sender: Any) {
dismiss(animated: true)
}
func save(completion: (_ finished: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else {return}
let goal = Goal(context: managedContext)
goal.goalDescription = goalDescription
goal.goalType = goalType.rawValue
goal.goalCompletionValue = Int32(pointsTextField.text!)!
goal.goalProgress = Int32(0)
do{
try managedContext.save()
print("successfully saved data")
completion(true)
}catch{
debugPrint("Could not save: \(error.localizedDescription)")
completion(false)
}
}
}
I'm getting a nil error in the save function with the goalType.rawValue turning up nil. The goal type is set up in an enum file:
import Foundation
enum GoalType: String {
case longTerm = "Long Term"
case shortTerm = "Short Term"
}
I'm not sure why there's an error. Because in the CreateGoalViewController, I print the goalType.rawValue from the following view controller and it comes up with the correct string, either short or long-term. But when FinishGoalViewController loads, it is all of a sudden nil.

You are initiating and configuring your FinishGoalViewController in nextButtonPressed but you never use it. performSegue(withIdentifier: "goToFinish", sender: self) will create and push a new instance of FinishGoalViewController.
The most simple aproach would be to push your allready configured controller from your curent Controller. Remove performSegue(... and use.
self.navigationController?.pushViewController(finishVC, animated: true)
If you still want to use the segue, remove everything from the nextButtonPressed function, leaving just the performSegue(... line. After that add this function to your CreateGoalViewController controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToFinish" {
if let finishVC = segue.destination as? FinishGoalViewController {
// configure finshVC here
}
}
}

Related

Getting API data + Optional Problems

I'm struggling to get an optional type to a Label.text. It keeps on giving me "nil" value and won't change the text.
import UIKit
class VerifyPNViewController: UIViewController {
#IBOutlet weak var VerificationMessage: UILabel!
#IBAction func backButton(_ sender: Any) {
self.view.window!.rootViewController?.dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
func didSuccess(_ response: GetLoginVerificationMessage){
let Main = response.result!
DispatchQueue.main.async {
print(Main)
self.VerificationMessage?.text = Main
print(self.VerificationMessage?.text)
}
}
}
and I will get a
234443 << which is a response.result!
nil << print(self.VerificationMessage?.text)
I have no idea why this value won't go into the "self. Verification?.text" Does anyone have ideas?
Thank you.

How can I pass dictionary from one view controller class to another?SWIFT

I am trying to make a list of users and their passwords in one view controller, save that information in a dictionary, and send that dictionary to another view controller which asks the user to input their username/password combination to authorize the log in. (the key is the username and the value is the password). Is there a way I can send the dictionary from SecondVC to the FirstVC?
First View Controller
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var Username: UITextField!
#IBOutlet weak var Verification: UILabel!
#IBOutlet weak var Password: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
Username.delegate = self
Password.delegate = self
}
var usersDict = [String : String]()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let des = segue.destination as? AccountViewController {
des.usersDict = usersDict
}
}
#IBAction func Authorization(_ sender: Any) {
for ( key , value ) in usersDict{
let v = key.count
var start = 0
if start <= v{
if Username.text == key{
if Password.text == value{
Verification.text = "Looks Good"
}
}
else{
start += 1
}
}
else{
Verification.text = "Yikes"
}
}
}
}
Second View Controller
class AccountViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var CreateUsername: UITextField!
#IBOutlet weak var CreatePassword: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
CreateUsername.delegate = self
CreatePassword.delegate = self
// Do any additional setup after loading the view.
}
var usersDict = [ String : String ]()
#IBAction func MakeANewAccount(_ sender: Any) {
usersDict[CreateUsername.text!] = CreatePassword.text!
}
}
I have made there dictionary, but it will only send in the beginning and won't update after creating the original account. (dictionary it is sending is empty)
With a segue add this method inside ViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let des = segue.destination as? AccountViewController {
des.usersDict = yourDicHere
}
}
Here's a general pattern for making a controller work with data from some object it creates, in this case a second controller.
Try applying it to your situation and let me know if you run into problems.
protocol Processor {
func process(_ dict: [String : String])
}
class FirstController: UIViewController, Processor {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue.destination as? SecondController {
controller.delegate = self
} else {
print("Unexpected view controller \(segue.destination)")
}
}
func process(_ dict: [String : String]) {
}
}
class SecondController: UIViewController {
var delegate: Processor?
func someWork() {
if let processor = delegate {
processor.process(["Name" : "Pwd"])
} else {
print("Delegate not assigned")
}
}
}

data not showing up in firebase database

im attempting to add user data via a create account view controller which contains all UITextFields (password, confirm password, first name, last name, phone number). when the create account button is tapped, the users email shows up in the authentication section on the firebase website but the user information from the first name, last name and phone number text fields are not passed into the database. I'm new to iOS development and have never used firebase so im unsure what the issue is. the app runs without crashing.
below is my Create Account view controller
thanks in advance
import UIKit
import FirebaseAuth
import QuartzCore
import FirebaseDatabase
import Firebase
class CreateAccount: UIViewController {
var refUsers: DatabaseReference!
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var confirmPasswordTextField: UITextField!
#IBOutlet weak var firstNameTextField: UITextField!
#IBOutlet weak var lastNameTextField: UITextField!
#IBOutlet weak var phoneNumberTextField: UITextField!
#IBOutlet weak var alreadyHaveAccountLabel: UILabel!
#IBAction func loginButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "showLoginScreen", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
self.refUsers = Database.database().reference().child("Users");
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
if Auth.auth().currentUser != nil {
print("success")
self.presentMainScreen()
}
}
#IBAction func createAccountTapped(_ sender: Any) {
if let email = emailTextField.text, let password = passwordTextField.text {
Auth.auth().createUser(withEmail: email, password: password, completion:{ user, error in
if let firebaseError = error {
print(firebaseError.localizedDescription)
return
} else {
self.addUser()
print("this is the first name:", self.firstNameTextField.text!)
print("this is the last name:", self.lastNameTextField.text!)
print("this is the phone number" , self.phoneNumberTextField.text!)
print("success")
self.presentMainScreen()
}
})
}
}
func addUser(){
let key = refUsers.childByAutoId().key
let user = ["id":key,
"FirstName":firstNameTextField.text! as String,
"LastName":lastNameTextField.text! as String,
"PhoneNumber":phoneNumberTextField.text! as String
]
refUsers.child(key).setValue(user)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
func presentMainScreen(){
let mainstoryboard = UIStoryboard(name: "Main", bundle: nil)
let mainTabController = mainstoryboard.instantiateViewController(withIdentifier: "MainTabController") as! MainTabController
mainTabController.selectedViewController = mainTabController.viewControllers?[0]
self.present(mainTabController, animated: true, completion: nil)
//let storyboard:UIStoryboard = UIStoryboard(name:"Main", bundle:nil)
//let loggedInVC:LoggedInVC = storyboard.instantiateViewController(withIdentifier: "LoggedInVC") as! LoggedInVC
//self.present(loggedInVC, animated: true, completion: nil)
}
}
Try this:
Instead of set value use update value
let childUpdates = ["/user/\(key)": user]
refUser.updateChildValues(childUpdates)
Hope this helps :)

Add elements to search history?

I have a model - Movies.
and two controllers - first for search movie by title, second - for display result with poster, title and year.
Now i need to create some history search on my third controller
(searchHistoryController - TableView) where displayed all movies, and when i tapped on cell with movie's title show movie info.
How I can build it?
I tried create array in my model. And write resutl in it, but each time when i use search it rewrite array, not add new element.
Maybe use realm
Need some help:)
Movie.swift
import Foundation
import UIKit
import Alamofire
import AlamofireImage
protocol MovieDelegate {
func updateMovieInfo()
}
class Movie {
private let omdbUrl = "http://www.omdbapi.com/?"
var title: String?
var filmYear: String?
var poster: String?
var delegete: MovieDelegate!
var historyMovie = [Movie]()
func getMovieInfo(title: String, completion: #escaping ()->()){
let params = ["t": title]
Alamofire.request(omdbUrl, method: .get, parameters: params).validate(statusCode: 200..<300).validate(contentType: ["application/json"]).responseJSON { (response) in
switch response.result {
case .success(let JSON):
let response = JSON as! NSDictionary
let status = response["Response"] as! String
if status == "True" {
self.title = (response["Title"] as! String)
self.filmYear = (response["Year"] as! String)
self.poster = (response["Year"] as! String)
// self.delegete.updateMovieInfo()
completion()
} else {
self.title = (response["Error"] as! String)
completion()
}
case .failure(let error):
print (error)
}
}
}
}
SearchVC
import UIKit
class SearchViewController: UIViewController {
var movie = Movie()
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
#IBOutlet weak var searchTextField: UITextField!
#IBOutlet weak var searchButton: UIButton!
#IBAction func searchButtonTapped(_ sender: UIButton) {
activityIndicator.startAnimating()
DispatchQueue.main.async {
self.movie.getMovieInfo(title: self.searchTextField.text!, completion: {
self.activityIndicator.stopAnimating()
self.performSegue(withIdentifier: "movieInfo", sender: self)
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondVC = segue.destination as! DetailInfoViewController
secondVC.movieTitle = movie.title!
}
}
DetailVC
class DetailInfoViewController: UIViewController, MovieDelegate {
#IBAction func showHistory(_ sender: UIButton) {
performSegue(withIdentifier: "showHistory", sender: self)
}
#IBOutlet weak var posterImageView: UIImageView!
#IBOutlet weak var filmNameLabel: UILabel!
#IBOutlet weak var filmYearLabel: UILabel!
var movie = Movie()
var movieTitle = ""
override func viewDidLoad() {
super.viewDidLoad()
self.movie.getMovieInfo(title: movieTitle ) {
self.updateMovieInfo()
}
self.movie.delegete = self
}
func updateMovieInfo() {
getPoster(link: movie.poster)
filmNameLabel.text = movie.title
filmYearLabel.text = movie.filmYear
}
func getPoster(link: String?) {
if link != nil {
guard let url = URL(string: link!) else { return }
DispatchQueue.global().async {
if let data = try? Data(contentsOf: url) {
DispatchQueue.main.async {
self.posterImageView.image = UIImage(data: data)
}
}
} } else {
self.posterImageView.image = #imageLiteral(resourceName: "Image")
}
}
}
First of all, movieHistory should not be part of your Movie class, but part of your SearchViewController class.
Second of all, unless you want to persist your data, you don't need Realm for this.
Just save the movies in SearchViewController into an array once the search button has been tapped and send it to your other view controller in the segue. Like so
#IBAction func searchButtonTapped(_ sender: UIButton) {
activityIndicator.startAnimating()
DispatchQueue.main.async {
self.movie.getMovieInfo(title: self.searchTextField.text!, completion: {
self.activityIndicator.stopAnimating()
movieHistory.append(movie)
self.performSegue(withIdentifier: "movieInfo", sender: movieHistory)
})
}
}
Also, modify prepare(for segue:...) like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondVC = segue.destination as! DetailInfoViewController
secondVC.movieTitle = movie.title!
secondVC.movieHistory = movieHistory
}
In detailVC override prepare(for segue:...) as well and send movieHistory to searchHistoryController the same way it is done in the previous VC.

How to call performSegueWithIdentifier in Swift

I have created a prepareForSegue method and I am trying to call it from a button that I created by using the performSegueWithIdentifier method. The app is crashing when I load the simulator and it's not getting me a complete error message. Can someone please lead me in the right direction?
import Foundation
import UIKit
import Alamofire
import FBSDKCoreKit
import FBSDKShareKit
import FBSDKLoginKit
class PageContentViewController: UIViewController {
#IBOutlet weak var logoImageView: UIImageView!
#IBOutlet weak var contentLabel: UILabel!
#IBOutlet weak var backgroundImageView: UIImageView!
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var facebookButton: UIButton!
var index : Int = 0
var logoFile: String = ""
var content: String = ""
var backgroundFile: String = ""
let facebookReadPermissions = ["public_profile", "email", "user_friends"]
override func viewDidLoad() {
super.viewDidLoad()
pageControl.currentPage = index
facebookButton.hidden = (index == 3 ) ? false : true
pageControl.hidden = (index == 3) ? true: false
logoImageView.image = UIImage(named: logoFile)
contentLabel.text = content
backgroundImageView.image = UIImage(named: backgroundFile)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if let destinationController = segue.destinationViewController as? PaymentSubViewController
where segue.identifier == "payment" {
// Do something with `destinationController`
}
}
#IBAction func test(sender: AnyObject) {
self.performSegueWithIdentifier("payment", sender: self)
}
#IBAction func fbTouched(sender: AnyObject) {
FBSDKLoginManager().logInWithReadPermissions(self.facebookReadPermissions, handler: { (result:FBSDKLoginManagerLoginResult!, error:NSError!) -> Void in
if error != nil {
//According to Facebook:
//Errors will rarely occur in the typical login flow because the login dialog
//presented by Facebook via single sign on will guide the users to resolve any errors.
// Process error
FBSDKLoginManager().logOut()
} else if result.isCancelled {
// Handle cancellations
FBSDKLoginManager().logOut()
} else {
let fbToken = result.token.tokenString
Alamofire.request(Router.FacebookAuth(fbToken)).validate(statusCode: 200 ..< 300).responseJSON(completionHandler: { (request, response, JSON, error) in
if let json = JSON as? Dictionary<String, AnyObject> {
if let token = json["token"] as? String {
Router.OAuthToken = token
self.performSegueWithIdentifier("showHomeFeed", sender: self)
}
}
})
}
})
}
}
Because you are force unwrapping the destinationViewController using as!, if that value is nil or not a PaymentSubViewController, the app will crash.
The better way to implement this is with an optional binding (if let) and a conditional downcast (as?):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if let destinationController = segue.destinationViewController as? PaymentSubViewController
where segue.identifier == "payment" {
// Do something with `destinationController`
}
}
Of course, that'll stop the crash but won't answer the question of why segue.destinationViewController is nil or of another type. Make sure that you segue is configured properly in interface builder and that the destination view controller actually has segue.destinationViewController for its Class value in the identity inspector tab.
I had a map object on the storyboard and I did not add an outlet for the object which was creating an error message.

Resources