UITextField and UIButton unresponsive after signOut segue swift - ios

In my Xcode project, after signing out and performing a segue, my text fields and buttons are unresponsive and won't show the keyboard, editing mark or allow me to edit. I am not getting an error anywhere, ​however.
ViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTappedAround()
imagePicker = UIImagePickerController()
imagePicker.allowsEditing = true
imagePicker.delegate = self
scrollView.isScrollEnabled = false
self.reset()
}
FeedVC.swift:
#objc func signOut(_ sender: AnyObject) {
KeychainWrapper.standard.removeObject(forKey: "uid")
do {
try Auth.auth().signOut()
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
performSegue(withIdentifier: "signOut", sender: nil)
}
Main.storyboard:
signOut Segue:

By the names you have picked, I’m guessing after signout what you really want to do is an unwind segue, basically returning to the original screen. The way you have it set up I believe, you’re actually creating a brand new view controller when you perform the segue from the tableview to the original view when really you want to go back to the one that has already been made.
What you actually want is an unwind segue, which will actually get you back to the original. This solves the problem mentioned by the other commenter (vicious cycle of segues). https://medium.com/#mimicatcodes/create-unwind-segues-in-swift-3-8793f7d23c6f

Related

iOS delegate is nil when trying to pass data back with Swift 5

I know this is a pretty common question but I've tried the various solutions offered here (that are not too old) and in numerous tutorials and I just can't seem to find out why it's still failing for me. Basically setting sendingViewController.delegate to self ends up being nil in sendingViewController. I understand this is very likely because the reference to the sendingViewController is being disposed of. But here is why I'm asking this again.
First, almost every tutorial and every other StackOverflow post is wiring up the mainViewController and the sendingViewController differently. I'm trying to make this work through a Navigation Controller, what one would think is the most common pattern for this.
In the app I'm building (which is more complex than the sample I'm going to show), the mainViewController calls the Settings viewController through a right navbar button. Then the user can select items from a list, which opens a controller with a searchBar and a tableView of items to select from. I need that third view controller to return the selected item from the table view to the settings screen. I'm using storyboards as well. I'm fairly new to Swift and I'm not ready to do all this "programmatically". Any way in the sending view controller, my delegate which should have been set in the calling view controller is nil and I can't invoke the protocol function in the main view controller to pass the data back.
I did a tutorial directly (not using Nav controllers) and I got that to work, but the moment I deviate away, it starts failing. I then put together a streamlined project with two view controllers: ViewController and SendingViewController. ViewController was embedded in a navigation controller and a right bar button was added to go to the SendingViewController. The SendingViewController has a single UI Button that attempts to call the protocol function and dismiss the SendingViewController. I'm not using Seque's, just a simple buttons and protocol/delegate pattern as I can.
My question is what am I missing to actually set the SendingViewController.delegate correctly?
Here's some code:
//ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var showDataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func fetchDataButton(_ sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SendingViewController") as! SendingViewController
controller.delegate = self
print("fetching data")
present(controller, animated: true, completion: nil)
}
}
extension ViewController: SendingViewControllerDelegate {
func sendData(value: String) {
print("got Data \(value)")
self.showDataLabel.text = value
}
}
and
// SendingViewController.swift
import UIKit
protocol SendingViewControllerDelegate {
func sendData(value: String)
}
class SendingViewController: UIViewController {
var delegate: SendingViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.navigationController?.popViewController(animated: true)
}
}
Here is a screenshot of the Storyboard:
The ChildViewController does have a storyboard id name of "ChildViewController". All buttons and labels have their appropriate IBOutlet and IBAction's set up.
Help!
i copy paste your code .. its working perfect .. i make just one change
instead of pop you need to use dismiss as you are presenting from your base viewController
#IBAction func sendDataButton(_ sender: UIButton) {
print("attempting to send data \(self)")
print("to \(self.delegate)")
self.delegate?.sendData(value: "Hello World")
self.dismiss(animated: true, completion: nil)
}
here is the project link we.tl/t-NUxm9D26XN
I managed to get this working. In the receiving/parent view controller that needs the data:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as! sendingViewController
controller.cityDelegate = self
}
Then in the sending view controller in my tableView did select row function:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
let city = filtered[indexPath.row]
searchBar.resignFirstResponder()
self.navigationController?.popViewController(animated: true)
self.cityDelegate?.addCity(city)
self.dismiss(animated: true, completion: nil)
}
I don't think I should be both popping the view controller and dismissing it, but it works. Also in the view controller I did this:
private var presentingController: UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentingController = presentingViewController
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
if parent == nil {
}
}
I don't know if I really need this didMove() or not since it doesn't really do anything.
But some combination of all this got it working.
In my other app I'm not using a navigation bar controller and the standard delegate/protocol method works like a charm.

How do I make navigation bar to show in root viewcontroller A the second time it's presented? (A>B>A)

I'm developing a shopping list like app where I have a Navigation Controller and the root view controller is the screen where the user can search for products (SearchViewController). When the user selects a product it segues to DetailViewController. This view has an option to check out or add more products. If users click on "Add more products" I have to segue to SearchViewController so they can search for more products. I want to present this VC again but I want the Nav Bar to show this time since I want to be able to go back if I decide not to add any other products.
Right now I'm sending the shoppingContext in the segue to determine from the SearchVC if I come from "DetailsVC" or not.
I think there's a problem with the way I'm adding view controllers to the navigation stack, but I've never encountered a problem like this and don't know what else to try.
With my current implementation (performSegue from DetailsVC to SearchVC) any time I click on a new item it segues twice to the Details screen, which I suspect may also be caused by the same navigation stack issue.
I tried creating a new object of SearchVC and pushing it to the stack instead of performing the segue but it didn't work either.
What can I do to fix it?
Basically, in detailsVC I do the following:
let segueAction = SegueAction(name: "segueToSearch", preparer: {
destinationVC in
if
let activeVC = destinationVC as? SearchViewController
{
activeVC.shoppingList = self.shoppingViewModel.shoppingList
}
})
performSegue(withIdentifier: segueAction.name, sender: segueAction)
The segue "segueToSearch" is a Show (push) type segue.
Then in the SearchVC I check if shoppingList != nil and if so do:
navigationController?.setNavigationBarHidden(false, animated: false)
If I check if the navigation bar is hidden it returns false but I still don't see it.
Hi it's pretty straight forward. Answer can be found here:
Navigation bar show/hide
[[self navigationController] setNavigationBarHidden:NO animated:YES];
And I would put a property to check in the viewWillApear.
-- EDIT: --
TESTED: I added it to a button action, works also in the viewDidAppear when dismiss back from to detail.
Hope it helps.
class ViewController: UIViewController {
var didHideNav: Bool = false
#IBAction func changeHidden(_ sender: UIButton) {
if !didHideNav {
print("Should Be Hidden")
self.navigationController?.setNavigationBarHidden(true, animated: true)
didHideNav = true
}else{
print("Should Be Visible")
self.navigationController?.setNavigationBarHidden(false, animated: true)
didHideNav = false
}
}
override func viewDidAppear(_ animated: Bool) {
if !didHideNav {
print("Should Be Hidden")
self.navigationController?.setNavigationBarHidden(true, animated: true)
didHideNav = true
}else{
print("Should Be Visible")
self.navigationController?.setNavigationBarHidden(false, animated: true)
didHideNav = false
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Swift perform segue without clicking button

I'm making a small game in SpriteKit where I have a bool value stored that checks if it's the first time the user plays my game. If it is, I want the app to redirect him to another viewcontroller where he enters his character-name.
However, I'm having trouble getting my app to do so.
In my "GameViewController.swift" I have this code:
override func viewDidLoad() {
super.viewDidLoad()
checkGame()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
func checkGame() {
//Check game
let firstPlay:Bool = appData.bool(forKey: "\(GameData.firstPlay)")
print(firstPlay)
if firstPlay == false {
print("Heading for setup")
self.performSegue(withIdentifier: "GoToSetup", sender: nil)
}
}
However, the "setup viewcontroller" isn't loaded. I can manage to tricker the segue with a button-click, but that's not what I want. I want it to happen as soon as the view is loaded.
Any ideas?
I don't know what you meant by "setup viewcontroller" isn't loaded", but usually we don't perform segues in the viewDidLoad. At this time, the view has just been loaded into memory and has not appeared yet. Performing a segue at this time is kind of a weird thing to do.
I recommend you to perform the segue in viewDidAppear():
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated: animated)
performSegue(withIdentifier: "GoToSetup", sender: nil)
}
You can programmatically call a segue with the following:
self.performSegueWithIdentifier("segueId", sender: self);
As it was said, in viewDidLoad stage UIViewController can't be done any UI-interaction, and performSegue to, because view is not at the view hierarchy yet. You can do this at viewDidAppear(animated:Bool).

Swift 3 - view doesn't update when prepare for segue is called

I have an unwind segue which takes a few seconds to complete while it saves images to disk. I want to display an activity indicator until the view is dismissed but the view doesn't update.
This is my function, which is called from the view controller before it's dismissed:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "saveRecord" {
print("indicator")
let indicator = UIActivityIndicatorView()
indicator.frame = self.view.frame
indicator.activityIndicatorViewStyle = .whiteLarge
indicator.color = UIColor.blue
indicator.hidesWhenStopped = true
indicator.startAnimating()
self.view.addSubview(indicator)
self.view.layoutSubviews()
print("laid out subviews")
}
}
The two print statements execute, and the debugger shows that the indicator has been added to the main view as a subview, but it doesn't appear on screen. Am I missing something?
I know the position of the indicator isn't a problem because running the same code in viewDidLoad correctly shows it in the middle of the screen.
UPDATE
I've recreated the segue functions using a delegate and it's saving everything correctly, but the problem remains. Still no activity indicator.
#IBAction func saveRecord(_ sender: Any) {
print("indicator")
let indicator = UIActivityIndicatorView()
indicator.frame = self.view.frame
indicator.activityIndicatorViewStyle = .whiteLarge
indicator.color = UIColor.blue
indicator.hidesWhenStopped = true
indicator.startAnimating()
self.view.addSubview(indicator)
self.view.layoutSubviews()
print("laid out subviews")
saveImages()
print("saved images")
self.delegate?.saveRecord(name: name!, category: category)
print("saved record")
self.navigationController?.popViewController(animated: true)
}
UPDATE 2
I'm really confused now! This starts the indicator:
#IBAction func saveRecord(_ sender: Any) {
print("indicator")
indicator.startAnimating()
//saveImages()
//print("images saved")
//performSegue(withIdentifier: "saveRecord", sender: self)
}
but this doesn't:
#IBAction func saveRecord(_ sender: Any) {
print("indicator")
indicator.startAnimating()
saveImages()
print("images saved")
performSegue(withIdentifier: "saveRecord", sender: self)
}
The problem is that the UI doesn't update until the saveRecord function has completed, but then the segue is called so the view controller is immediately dismissed. I solved it using dispatch queues - another new skill I've learned today!:
#IBAction func saveRecord(_ sender: Any) {
indicator.startAnimating()
DispatchQueue.global(qos: .userInitiated).async {
self.saveImages()
DispatchQueue.main.async {
self.performSegue(withIdentifier: "saveRecord", sender: self)
}
}
}
I think you should use popViewController instead of an unwind segue. Then you have more control with a custom function on the back button.
What you can do then is:
when you click on the back button (or save image button), add you indicator
then save the image
when the image is saved remove the indicator
pop the view controller
You can't update your views in prepare(for:); the segue is already underway.
Rather than triggering the unwind segue directly from the UI element, trigger an action method that shows the activity indicator and performs the save and then calls performSegue(withIdentifier:"yourUnwindSegueIdentifier", sender:self) to actually perform the unwind.

How can I switch to another viewController in my swift app when user presses the button?

I'm working on authorization based on google accounts to my app. I have a settings view that contains the following code:
import UIKit
import Google
class Settings: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let background = CAGradientLayer().greenBlue()
background.frame = self.view.bounds
self.view.layer.insertSublayer(background, atIndex: 0)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func didTapSignOut(sender: AnyObject) {
GIDSignIn.sharedInstance().signOut()
}
}
and in the gui I have a button. When user clicks it then the session gets destroyed by this call: GIDSignIn.sharedInstance().signOut(). But, so far, nothing else happens and user is still in settings menu. I want to transfer him to my ViewController that contains log in form.
I tried to add the following code:
let sb = UIStoryboard(name: "Main", bundle: nil)
if let tabBarVC = sb.instantiateViewControllerWithIdentifier("TabController") as? TabController {
window!.rootViewController = tabBarVC
}
but then the window is unresolved. How can I do it?
In the storyboard create a segue from the "Settings" ViewController to the "TabController" and set its identifier to "settingsToTabController". Then use the code self.performSegueWithIdentifier("settingsToTabController", sender: self) in the didTapSignOut function. I do not have a ton of experience with this so this may not be the best way of doing it, but it works fine for me.
If you want to do it the original way you posted using the window variable you need to grab an instance of that window instance from your delegate. You can get it by doing let window = UIApplication.sharedApplication().delegate?.window! and then setting the UIStoryboard to it as you did in your code above. Now your window object will not be unresolved.

Resources