Segue call from viewDidAppear works, but not from viewWillAppear - ios

I have the following scenario with the given environment:
Xcode v7.3.1
Swift 2
From within a login viewController, I initially had the following segue call in viewWillAppear method to bypass login screen if the user is already logged in:
override func viewWillAppear(animated: Bool) {
if PFUser.currentUser() != nil {
self.performSegueWithIdentifier("login", sender: self)
}
}
The segue was not working, so I set a breakpoint where the segue call is being made and verified that it is indeed being hit. I also step through the call and verify no exceptions are thrown, but for some reason the segue is not performed.
However, I put the same call within the viewDidAppear method:
override func viewDidAppear(animated: Bool) {
if PFUser.currentUser() != nil {
self.performSegueWithIdentifier("login", sender: self)
}
}
Again, I verified that the segue call is being hit, but in this case the segue is actually performed. So I'm wondering why the exact same call doesn't work when it is invoked in the viewWillAppear method but does work as expected within viewDidAppear. The most puzzling part is that it appears that the call is being made, but the expected behavior is not observed. I tried Googling for anyone else running into this problem but was not able to find anything.
I guess I can live with it being called from viewWillAppear, but there is that brief appearance of the login screen before being redirected, which is slightly annoying. If anyone has a solution or suggestions on how I might be able to get it to work from viewWillAppear I would be greatly appreciative.

You cannot perform segues in the viewWillAppear method. This is because the view has not fully appeared yet, and it cannot transition over to another view. In this scenario, I would have a load image or something similar and have it fade away if the segue is not supposed to be performed. This, of course, would be done in the viewDidAppear method.

Related

Same view controller loads twice

I've read several posts on this issue but none of them solved my problem.
I'm coding an app where I have to click on a button ("Prepare") to go to the following ViewController. When the button is clicked, it also passes data between the two view controller.
The problem is, when I click the button, the following ViewController loads twice. Thus, if I want to go back I have to go back through two same ViewController.
I've checked the segue, the class names and files names but nothing fixes it.
I've also created a new project and rewritten all the code from the beginning but in the end, it still doesn't work.
However, I've noticed that the problem showed up when I added the prepare(forSegue:) function and the performSegue function. Without it the ViewController only loads once. But of course, I can't get the data passed between the views without it...
Here is the screenshot of my two view and the code of the two functions :
First view
Second view
// Prepare the segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "prepareSegue" {
let timerViewController = segue.destination as! CountdownViewController
timerViewController.timeInterval1 = waitingTime
timerViewController.timeInterval2 = shootingTime
timerViewController.lastSeconds = lastSeconds
timerViewController.currentTimeInterval = waitingTime
}
}
// Prepare the timer when the button is pushed
#IBAction func prepareTimer(_ sender: Any) {
performSegue(withIdentifier: "prepareSegue", sender: self)
}
Probably, when two ViewControllers appear it's because you have:
A segue on Storyboard which start directly from a Button
IBAction attached to the button where you call a performSegue of the same Segue
To fix this problem, you need to create a Segue which start directly from ViewController. After you can use the IBAction and the performSegue
You have added a seague but also an IBAction. If the seague is defined well in InterfaceBuilder it will perform and call your method. The IBAction is the alternative way for connecting an Action to a button. If you use both, you have two actions.
Without the rest of your project to check, one thing it could be is a completion closure which contains the performSegue(...) statement is called twice, so the performSegue statement is literally run twice. How might this happen?
If you have a networking call inside the closure which contains performSegue command, and the networking call has a closure which calls completion(...) twice, then you could get either the segue occuring twice or even recursively!
It's rare this will be the case, or in your instance, but this type of problem can be uncovered by using lots of breakpoints and following the "bouncing ball" to see the completion() calls occuring more than once.

How to run code before initial view opens?

i want to run this code before the initial view controller even opens the view so the app knows where to redirect the user if the user is logged in. how can i do that? the code is here:
if let firstCheck = NSUserDefaults.standardUserDefaults().objectForKey("username"){
self.performSegueWithIdentifier("View1ToView2Segue", sender: nil)
}
i put the code after override func viewDidLoad() but it didnt work.
Unfortunately, you cannot do that. :( In this case, I would have a load image or something similar and have it fade away if the segue is not supposed to be performed. You would run this in the viewDidAppear method.

performSegueWithIdentifier not firing from unwind segue

Code used to work with Swift 1 but now does not and here is my issue. (did check other questions but this is slightly different, and enough so that the other solututions did not work.
So I am trying to present a logon screen which I do as follows
and it works fine, the user enters in their username and password. I then programmatically check they can log in and all still good and do the unwind as follows:
self.performSegueWithIdentifier("unwindGoToTeamPage", sender: self)
But the following code does not fire even though the func is called. I know that because I had a print statement in their for debuging and it shows on the console:
#IBAction func unwindGoToTeamPage(sender: UIStoryboardSegue){
print("In here")
self.performSegueWithIdentifier("goToTeamPage", sender: self)
}
Now I know its related to the same reason that at the start of the ViewController life cycle you can not call a
self.performSegueWithIdentifier("goToTeamPage", sender: self)
as it wont work in the
override func viewDidLoad() {
super.viewDidLoad()
}
or the
override func viewWillAppear(animated: Bool) {
}
but only in the
override func viewDidAppear(animated: Bool) {
if appDelegate.tkData[0] == "1"
{
self.performSegueWithIdentifier("goToTeamPage", sender: self)
}
}
In Swift 1 and Objective-C I used to put in a timer and all was good but that no longer works and I prefer to put in a solid solution that is not a hack. I think the solution involves waiting till the logon viewcontroller is completely dismissed but not sure the correct way to achieve it.
Could a dispatch to the main queue be an option. I will try that while I wait if someone has an answer and post an update. I also noted it works just fine on a iPhone or iPod but its the iPad where the issue is unsolved.
Appreciate any pointers to solve this problem. Thanks
Update, Yes I did try the UITextField solutions but that appears not to be the issue in this instance. I am slowly identifying the issue as one view controller not yet finished before the
self.performSegueWithIdentifier("goToTeamPage", sender: self)
fires.
I also apologies that i failed to mention the console message which is:
Warning: Attempt to present <new view controller> on <current view controller> whose view is not in the window hierarchy!
So I see my issue is as needing to sort of run the logon viewcontroller in such a way as to have the orignial viewcontroller get a message that the logon view conntroller has finished and only then fire the
self.performSegueWithIdentifier("goToTeamPage", sender: self)
SO is there are way to get a call back of some sort to tell the original view controller that the child view controller has completely finished so I can then call the next function only once the child view controller is finished.
Thanks for any help on this.
I have this sort of working but feel its really just another hack and though will get me out of trouble this time disappointed I could not find the correct way of doing it.
So the issue is mainly with the modally presentation of the logon screen where once it is dismissed it takes however many microseconds to be removed from the view hierarchy. Trying to move to another viewcontroller with
self.performSegueWithIdentifier("goToTeamPage", sender: self)
will not work which is my dilemma.
So to get around it I made the logon screen a full screen (not popover or modally) and then put the code
self.performSegueWithIdentifier("goToTeamPage", sender: self)
in the
override func viewDidAppear(animated: Bool) {
self.performSegueWithIdentifier("goToTeamPage", sender: self)
}
function.
Not the best solution as the logon screen is now full screen and while ok on the iphone not the best on the iPad.
So, sort of fixed but as a hack and work around and STILL KEEN if anyone has a solution where I can go back to the modally view for the logon screen.

performSegueWithIdentifier won't push view

I've just updated Xxode to work with Swift 2.0. As usual, a lot of new problems showed up.
In my app I have a view controller that checks whether the user is logged in and present either login screen or the app's home screen. It's a pretty simple VC:
class WelcomeViewController : UIViewController {
override func viewDidAppear(animated: Bool) {
if PFUser.currentUser() == nil {
self.performSegueWithIdentifier("segue-require-login", sender: self)
}
else {
self.performSegueWithIdentifier("segue-start-app", sender:self);
}
}
}
That used to work perfectly, but now it doesn't. The segue segue-require-login is of type "Present modally" and it works fine. The segue segue-start-app is "Show (e.g. Push)", but the view never gets pushed, even though the code is being executed (even prepareForSegue is called).
I've tried re-creating the segue, performing a Clean, cleaning the project's build folder but nothing seems to help.
Any thoughts?
There seems to be a bug in iOS 9 and Xcode 7 where if you have a UITextView with placeholder text, it prevents the segue from being triggered.
More explanation in the following answer:
iOS 9 Segue Causes App To Freeze (no crash or error thrown)
To fix it, try removing the placeholder text for the UITextView
I think I have the same problem: I have a UITabelView with cells created from a nib file, when a user tap a cell this method is called:
and when I have the following method prepareForSegue:: the application crashes:
if I delete the line 129 Everything is ok , the method prepareForSegue:: open the right view and the label contactName is shown with its default text.
If I modify the method as follows prepareForSegue:: get exactly what you expect, without having any type of error:
let me know if you also get the same result

performSegueWithIdentifier very slow when segue is modal

I have a simple table view where I handle the select action on the table view. This action follows a segue.
If the segue is a push segue, the next view shows immediately.
If the segue is a modal segue, the next view either:
takes 6 seconds or so to display
shows immediately if I tap again (second tap)
I tried looking around for some ideas, but none seem applicable to my situation. In particular:
I'm performing the segue on the main UI thread
My view is very simple (so there's no issue in viewDidLoad). Plus the fact that it shows up near instantaneous when the segue is push indicates that there is no problem loading the target view
I tried passing nil to the sender; same effect.
Does anyone have any ideas on this?
Trust me and try this. I have run into this problem a few times.
In Swift 2:
dispatch_async(dispatch_get_main_queue(),{
self.performSegue(withIdentifier:mysegueIdentifier,sender: self)
})
or for Swift 3:
DispatchQueue.main.async {
self.performSegue(withIdentifier: mysegueIdentifier,sender: self)
}
As discussed here and here.
It seems (to me...) that this problem happens only when the cell selectionType is not .none.
You may change it to any other option (at the storyboard Attribute inspector, set the Selection field) and it will work fine (working for me...).
The cons is that it messing up the cell UI.
You can call the segue in DispatchQueue.main.async{} block at the didSelect delegate function of UITableViewDelegate as people mention before.
I used the first solution and added at the cell itself -
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(false, animated: false)
}
This will make the cell 'highlight' at the tap, but it will return to its usual UI immediately and it fine for me...
There seem to be various situations when performing a segue will not work properly. For example, if you call performSegue from the action handler of an unwind segue, you will run into various issues, even though you are on the main thread. On my current project, I am calling performSegue from the didSelectRowAt method of a table view. This is one of the most basic segues there is and of course I am on the main thread, yet I was seeing the exact symptoms that the OP described.
I do not know why this happens in some cases and not others, but I have found that deferring the performSegue call using async fixes any potential issues. This used to seem like a hack and made me nervous, but at this point I have several mature projects using this approach and it now seems like the "right" way to do a manual segue.
Here is the Swift 3 version of the code (see the other posts for Swift 2 and Obj-C versions):
DispatchQueue.main.async {
self.performSegue(withIdentifier: "theIdentifier", sender: theSender)
}
The accepted solution worked for me. Code updated for Swift 2.0 below:
dispatch_async(dispatch_get_main_queue(),{
self.performSegueWithIdentifier(mysegueIdentifier, sender:self)
})
Hope help this yo can create programatically modal transition like this in Swift:
let myStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let modalViewController = myStoryboard.instantiateViewControllerWithIdentifier("myModalViewController") as! myModalViewController
modalViewController.modalTransitionStyle = UIModalTransitionStyle.CoverVertical
let navController = UINavigationController(rootViewController: accountManager)
dispatch_async(dispatch_get_main_queue(),{
self.presentViewController(navController, animated: true, completion: nil)
})
For developers organizing their code through subclassing, I've come to quite simple a solution I'd like to share (Swift 4):
import UIKit
class ABCViewController: UIViewController {
// ... Other useful methods like overriding deinit and didReceiveMemoryWarning
// Performs a segue on the main thread to ensure it will
// transition once a UI-slot is available
func performSegueOnMainThread(with identifier: String, sender: Any?) {
DispatchQueue.main.async {
self.performSegue(with: identifier, sender: sender)
}
}
}
Then, simply call it from your implementation:
myViewController.performSegueOnMainThread(with: "ShowDetailsSegue", sender: self)
For me it was the that I had too many "Clear" colored views in my next view, so when the segue was animated, it seemed like it was delaying because of this. I went through the view controller's UI hierarchy looking for clear colors and replacing them with solid black or white, and making sure the alpha is 1 (if it didn't need to be otherwise). My delay is now gone and my modal presentation is smooth.
I tried fixing this multiple ways including moving it to the main thread above. This worked for me:
In storyboard, select the table view in question, (select it in document outline to make sure you've got the right thing. Then in attributes inspector you will be able to see the attributes for the table view as well as the containing scrollview underneath it (all table views are based on scroll view). There is this stupid little box there called "delays content touches". Uncheck it. There is also one "allows cancellable touches" which I'll imagine you want to make sure is unchecked as well, as I think double touches were messing mine up.

Resources