Updates on View Controller Disappear After Segue - ios

I've been working through a project and have gotten most of the functionality straight, however there's one part that continues to stump me. I am using MVC format, and I have a two view controllers (one profile and one game). When progress is made on the game, the proper changes to game view controller show (i.e. button disappears and UI background color changes colors). However, when I segue to profile view controller using 'back' on navigation bar and return to the game view controller, the game view controller returns to the default without reflecting game progress.
I've tried unwinding segue, making a delegate going from view controller to model responsible for game play, adjusting my game logic, and trying viewdidload and viewwillappear. I'm running out of ideas.
Here are samples of pertinent code:
Game View Controller
import UIKit
import Firebase
class GameController: UIViewController, BrainDelegate, UITextFieldDelegate {
#IBOutlet weak var dateUsername: UILabel!
#IBOutlet weak var questionText: UILabel!
#IBOutlet weak var scoreText: UILabel!
#IBOutlet weak var answerOne: UITextField!
var userListOne: [String] = []
#IBOutlet weak var answerTwo: UITextField!
#IBOutlet weak var answerThree: UITextField!
#IBOutlet weak var answerFour: UITextField!
#IBOutlet weak var enterOne: UIButton!
#IBOutlet weak var enterTwo: UIButton!
#IBOutlet weak var enterThree: UIButton!
#IBOutlet weak var enterFour: UIButton!
#IBOutlet weak var feedbackText: UILabel!
#IBAction func logoutPressed(_ sender: UIBarButtonItem) {
do {
try Auth.auth().signOut()
navigationController?.popToRootViewController(animated: true)
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
}
let db = Firestore.firestore()
var questions = [Question]()
var Brain = Brain()
var delegate: brainDelegate?
//variables for delegate with json data
var questioner : String?
var dater : String?
var a1 : String?
var a2 : String?
var a3 : String?
var a4 : String?
//For game play
var answer1: String?
var answer2: String?
var answer3: String?
var answer4: String?
var puzzlesCompleted = "0"
func didUpdateBrain(narratives: [Question]) {
self.questions = narratives
}
func didFailWithError(error: Error) {
print(error)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
minketBrain.delegate = self
answerOne.delegate = self
answerTwo.delegate = self
answerThree.delegate = self
answerFour.delegate = self
if let localData = minketBrain.readLocalJSONFile(forName: K.minketClues) {
minketBrain.parseJSON(jsonData: localData)
}
//Transfer data from Profile Controller
self.questionText.text = questioner
self.dateUsername.text = dater
self.answer1 = a1
self.answer2 = a2
self.answer3 = a3
self.answer4 = a4
}
override func viewWillAppear(_ animated: Bool) {
view.reloadInputViews()
answerOne.text = userListOne.last
}
//MARK: - Button Logic
#IBAction func enterPressed(_ sender: UIButton) {
enterPressedOne()
allRight()
}
//Other buttons replicate the same code above
//MARK: - Correct Answer Logic
func allRight(){
if answerOne.backgroundColor == UIColor.green && answerTwo.backgroundColor == UIColor.green && answerThree.backgroundColor == UIColor.green && answerFour.backgroundColor == UIColor.green {
feedbackText.text = K.congratsText
puzzlesCompleted = "1"
if let allAnswer = scoreText.text, let userName = Auth.auth().currentUser?.email{
db.collection(K.FStore.collectionName).addDocument(data: [K.FStore.userName:userName, K.FStore.clueDate:dateUsername.text!, K.FStore.totalPoints:allAnswer, K.FStore.completionField:puzzlesCompleted]) { (error) in
if let e = error{
print("There was an error in data collection to Firestore \(e)")
}else{
print("Successfully saved data")
}
}
}
}
}
//MARK: - Update UI-Each Question
func enterPressedOne(){
if let item = answerOne.text, item.isEmpty == true { // need to make sure we have something here
userListOne.append(item) // store it in our data holder
print(userListOne)
}
if answerOne.text == self.answer1!.lowercased(){
answerOne.backgroundColor = UIColor.green
enterOne.isHidden = true
answerOne.isEnabled = false
}else{
answerOne.backgroundColor = UIColor.red
}
}
}
Unwind segue function in my profile controller
#IBAction func unwindToDestinationViewController (sender: UIStoryboardSegue){
print("Unwind")
}

Related

Why am I not able to access global variables?

I am new both to coding and to Xcode and am working on my first iOS app - so please forgive me if the answer to my question is obvious to experienced developers.
My app works but the design looks very "old fashioned" due to the buttons I have on each View Controller. I would prefer to use a more modern navigation system using Navigation Bars with Navigation buttons at the top of each screen. However, when I use this method of navigation the global variables entered on my first View Controller cannot be accessed on my final View Controller. I have double checked my Swift code files for each View Controller which, as explained, works fine if I have UIButtons instead of the Navbar.
The only difference I can see is that the Xcode 11 Navbar system uses the "Show" (Push) method of segues between View Controllers whereas my storyboard buttons use modal presentation.
I would be grateful if someone can steer me in the right direction - thanks!
The code showing the global variables from the first view controller is:
\\
import UIKit
var name = ""
var lastname = ""
var address = ""
var city = ""
var zip = ""
var email = ""
var phone = ""
var dateofbirth = ""
class ViewController1: UIViewController {
#IBOutlet weak var outlet: UITextField!
#IBOutlet weak var outlet2: UITextField!
#IBOutlet weak var outlet6: UITextField!
#IBOutlet weak var outlet3: UITextField!
#IBOutlet weak var outlet4: UITextField!
#IBOutlet weak var outlet5: UITextField!
#IBOutlet weak var outlet8: UITextField!
#IBOutlet weak var outlet7: UITextField!
#IBAction func submit(_ sender: Any) {
// Code for First Name
if (outlet.text != "")
{
name = outlet.text!
}
// Code for Last Name
if (outlet2.text != "")
{
lastname = outlet2.text!
}
// Code for Address
if (outlet3.text != "")
{
address = outlet3.text!
}
// Code for city
if (outlet4.text != "")
{
city = outlet4.text!
}
// Code for Zip
if (outlet5.text != "")
{
zip = outlet5.text!
}
// Code for Email
if (outlet6.text != "")
{
email = outlet6.text!
}
// Code for Phone
if (outlet7.text != "")
{
phone = outlet7.text!
}
// Code for Date of Birth
if (outlet8.text != "")
{
dateofbirth = outlet8.text!
}
// Dismissal of Keyboard
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
super.touchesBegan(touches, with: event)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
\\\\
The code for the final View Controller is:
\\\\
import UIKit
class ReportViewController: UIViewController {
//Client ID Label Outlets
#IBOutlet weak var Label: UILabel!
#IBOutlet weak var Label2: UILabel!
#IBOutlet weak var Label3: UILabel!
#IBOutlet weak var Label4: UILabel!
#IBOutlet weak var Label5: UILabel!
#IBOutlet weak var Label6: UILabel!
#IBOutlet weak var Label7: UILabel!
#IBOutlet weak var Label8: UILabel!
// Return function
#IBAction func unwindToReportVC (_sender:UIStoryboardSegue){
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated:Bool){
// Client ID Labels text using declared global variables
Label.text = name
Label2.text = lastname
Label3.text = address
Label4.text = city
Label5.text = zip
Label6.text = email
Label7.text = phone
Label8.text = dateofbirth
}
}

Problems Updating View with Swift 5

I am having some trouble updating my secondViewController view in Xcode using Swift 5. I want my app to add two numbers together and show the result in the second ViewController. Although it works the first time, if I return to my previous view and change the numbers, the view does not update.
I tried using viewWillAppear, viewWillDisappear, amongst others, including NSNotificationCenter addObserve, but I have had no luck whatsoever.
Do you have any recommendations? Am I missing something?
Please see below for the code and a screenshot of my ViewControllers:
//
// ViewController.swift
//
import UIKit
var result = ""
var resultFinal = Float(result)
let finalResult = resultFinal!
class ViewController: UIViewController {
#IBOutlet weak var firstNumber: UITextField!
#IBOutlet weak var secondNumber: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func getResult()-> Float{
guard let fNumber = firstNumber.text else {
return 0
}
let firstFloat = Float(fNumber)
guard let sNumber = secondNumber.text else {
return 0
}
let secondFloat = Float(sNumber)
let sumNumber: Float = firstFloat! + secondFloat!
return sumNumber
}
#IBAction func submitSum(_ sender: Any) {
resultFinal = getResult()
print(resultFinal!)
}
}
//
// secondViewController.swift
//
import UIKit
class secondViewController: UIViewController {
#IBOutlet weak var test: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.test.text!=""
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
test.text = String(finalResult)
}
}
Screenshot:
Thanks.
Your problem is with the global variables. It seems from your code that you expect these three to reevaluate every time one of them changes:
var result = ""
var resultFinal = Float(result)
let finalResult = resultFinal!
For example, if you set resultFinal = 4, then finalResult will equal 4. However, those variables only evaluate once––the first time. You can simplify your use of these variables significantly. Replace these three with:
var result: Float?
Then, in ViewController:
class ViewController: UIViewController {
#IBOutlet weak var firstNumber: UITextField!
#IBOutlet weak var secondNumber: UITextField!
func getResult() -> Float {
guard let number1 = Float(firstNumber.text ?? "0") ?? 0
guard let number2 = Float(secondNumber.text ?? "0") ?? 0
return number1 + number2
}
#IBAction func submitSum(_ sender: Any) {
result = getResult()
}
}
Note: I simplified getResult and made it treat empty fields as 0.
In SecondViewController:
class SecondViewController: UIViewController {
#IBOutlet weak var test: UITextField!
override func viewWillAppear(_ animated: Bool) {
test.text = String(result ?? 0)
}
}
Note: self.test.text!="" doesn't really do anything, so I removed it.

Refresh Current View Controller Data Request when XIB Button Clicked

I want to make error handling pages to all of my view controller through, so when error fetching data error, it's not only showing dialog, but showing a XIB files that contain error message and button to refresh. Like this:
Here's the XIB Class code:
import UIKit
class ErrorMessage: UIView {
#IBOutlet weak var imageViewError: UIImageView!
#IBOutlet weak var labelError: UILabel!
#IBOutlet weak var buttonTryAgain: UIButton!
static var message: String?
override func awakeFromNib() {
labelError.text = ErrorMessage.message
}
#IBAction func didTapTryAgain(_ sender: Any) {
Dialog.showProgressDialog(info: "")
}
}
Here's my base controller code, who handling all the problem.
import Foundation
class BaseViewController: UIViewController {
var uiView = UIView();
override func viewDidLoad() {
}
func getErrorMessage(message:String) {
super.viewDidLoad()
ErrorMessage.message = message
guard let viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as? ErrorMessage else { return}
self.view.addSubview(viewErrorMessage)
}
}
And here's how I call it in another class which I extend BaseViewController, so it can show the error problem globally, without I redeclared again the class:
func onFailedDeleteCart(errorMessage: String) {
getErrorMessage(message: errorMessage)
}
Right now I can pass the error message.
The problem is, I want the Refresh button refreshing current View Controller when I click it. Maybe calling current View Controller's viewDidLoad when I click it will be the nice logic but I don't know how to implement it in XIB class. Anyone can solve out this? Thank you!
Approach: 1
Step:1 Create closure for callback
typealias RefreshBlock = (()->())?
Step:2 Define closure in your UIView class
Step:3 Call closure if user tap refresh button
class ErrorMessage: UIView {
#IBOutlet weak var imageViewError: UIImageView!
#IBOutlet weak var labelError: UILabel!
#IBOutlet weak var buttonTryAgain: UIButton!
var refreshBlock:RefreshBlock!
static var message: String?
override func awakeFromNib() {
labelError.text = ErrorMessage.message
}
// Step : 3
#IBAction func didTapTryAgain(_ sender: UIButton) {
refreshBlock!()
}
}
Step:4 Assign value in closure when addSubview called
class BaseViewController: UIViewController {
override func viewDidLoad() {
}
func getErrorMessage(message:String) {
super.viewDidLoad()
ErrorMessage.message = message
guard let viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as? ErrorMessage else { return}
viewErrorMessage.refreshBlock = {()
self.viewDidLoad()
print("Refresh Contents")
}
self.view.addSubview(viewErrorMessage)
}
}
Approach: 2
Pass your current UIViewController Reference into UIView class. Refer below code.
class ErrorMessage: UIView {
#IBOutlet weak var imageViewError: UIImageView!
#IBOutlet weak var labelError: UILabel!
#IBOutlet weak var buttonTryAgain: UIButton!
var currentVC:UIViewController!
static var message: String?
override func awakeFromNib() {
labelError.text = ErrorMessage.message
}
#IBAction func didTapTryAgain(_ sender: UIButton) {
currentVC.viewDidLoad()
}
}
class BaseViewController: UIViewController {
override func viewDidLoad() {
}
func getErrorMessage(message:String) {
super.viewDidLoad()
ErrorMessage.message = message
guard let viewErrorMessage = Bundle.main.loadNibNamed("ErrorMessage", owner: self, options: nil)?.first as? ErrorMessage else { return}
viewErrorMessage.currentVC = self
self.view.addSubview(viewErrorMessage)
}
}

Swift: Help troubleshoot Segue data from one view to another

Hi I am very new to Swift. I have watched several different videos and tutorials on youtube and I am unable to identify what I am doing wrong.
I am trying to take the data from a check-in view and segue it to the summary view. The code runs, and I am able to move through the app, but the data does not move. Below is the code for the two different swift files. Any insight would be greatly appreciated.
Check-In:
import Foundation
import UIKit
class VieNewCheckIN : UIViewController {
#IBOutlet var SWR: UILabel!
#IBOutlet var ModelNumber: UITextField!
#IBOutlet var SerialNumber: UITextField!
#IBOutlet var Notes: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let viewnewsummary = segue.destination as? ViewNewSummary else { return }
viewnewsummary.NotesValue = Notes.text
viewnewsummary.SerialNumberValue = SerialNumber.text
viewnewsummary.ModelNumberValue = ModelNumber.text
}
}
Summary:
import Foundation
import UIKit
class ViewNewSummary : UIViewController {
#IBOutlet var FirstNameLabel: UILabel!
var FirstNameValue : String!
#IBOutlet var LastNameLabel: UILabel!
var LastNameValue : String!
#IBOutlet var IDLabel: UILabel!
var BaylorIDValue : String!
#IBOutlet var EmailLabel: UILabel!
var BaylorEmailValue : String!
#IBOutlet var PhoneNumberLabel: UILabel!
var PhoneNumberValue : String!
#IBOutlet var ModelNumberLabel: UILabel!
var ModelNumberValue : String!
#IBOutlet var SerialNumberLabel: UILabel!
var SerialNumberValue : String!
#IBOutlet var NotesLabel: UILabel!
var NotesValue : String!
override func viewDidLoad() {
super.viewDidLoad()
FirstNameLabel.text = FirstNameValue
LastNameLabel.text = LastNameValue
BaylorIDLabel.text = BaylorIDValue
BaylorEmailLabel.text = BaylorEmailValue
PhoneNumberLabel.text = PhoneNumberValue
ModelNumberLabel.text = ModelNumberValue
SerialNumberLabel.text = SerialNumberValue
NotesLabel.text = NotesValue
}
}
Verify prepare(for:sender:) is called, e.g. using breakpoints or print/caveman debugging;
verify ViewNewSummary.viewDidLoad() is called, too;
verify prepare is called before viewDidLoad.
My suspicion is that viewDidLoad is called before prepare(for:sender:) is called; that'd make sense since you need an initialized view controller object to prepare the segue with.
So you will want to add viewWillAppear(_:) as a quick fix:
extension ViewNewSummary {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
FirstNameLabel.text = FirstNameValue
LastNameLabel.text = LastNameValue
BaylorIDLabel.text = BaylorIDValue
BaylorEmailLabel.text = BaylorEmailValue
PhoneNumberLabel.text = PhoneNumberValue
ModelNumberLabel.text = ModelNumberValue
SerialNumberLabel.text = SerialNumberValue
NotesLabel.text = NotesValue
}
}
If that doesn't scale well for all cases, you may want to look into property observers, like
var PhoneNumberValue : String! {
didSet {
guard isViewLoaded else { return }
PhoneNumberLabel.text = PhoneNumberValue
}
}

How to access IBoutlet of one view controller in another view controller using swift?

How can I change the label text to textfield text?
This source code change the label text to textfield text which are in the same class, but I want to change the label in different class.
label is in the ViewController.swift and textfield is in the ProfileViewController.swift
This is the source code:
ViewController.swift
import UIKit
import Social
class ViewController: UIViewController, SideBarDelegate {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var hedgeImage: UIImageView!
#IBOutlet weak var hideView: UIView!
var sideBar:SideBar = SideBar()
override func viewDidLoad() {
}
ProfileViewController.swift
class ProfileViewController: UIViewController {
#IBOutlet weak var ouput: UILabel!
#IBOutlet weak var name: UITextField!
#IBAction func nameChange(sender: AnyObject) {
ouput.text = name.text
}
}
One of the many ways to do this: You can add an observer to textField and update a variable that holds the name text which then you can send to the new vc in prepare for segue. Here is a sample
class ViewController: UIViewController {
var nameRecieved:String?
#IBOutlet weak var name: UILabel!{
didSet{
if nameRecieved != nil { name.text = nameRecieved }
}
}
}
class ProfileViewController: UIViewController, UITextFieldDelegate {
var nameHolder:String?
#IBOutlet weak var name: UITextField! { didSet{ name.delegate = self } }
//notification
var nameTextFieldObserver: NSObjectProtocol?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let center = NSNotificationCenter.defaultCenter()
let q = NSOperationQueue.mainQueue()
nameTextFieldObserver = center.addObserverForName(UITextFieldTextDidChangeNotification, object: name, queue: q){
notification in
self.nameHolder = self.name.text
}
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if nameTextFieldObserver != nil { NSNotificationCenter.defaultCenter().removeObserver(nameTextFieldObserver!) }
}
//Here you can pass the value of nameHolder to the other vc's var nameRecieved in your prepare for segue
}
You need to catch hold on to your ViewController object and pass the values. Something like this:
#IBAction func nameChange() {
ouput.text = name.text
// given both your views have a same parent
// and this view is the first child
let myViewController = parentViewController!.childViewControllers[1]
myViewController.name.text = name.text
myViewController.printLabelText()
}

Resources