Perform segue not working inside api call - ios

I have login page that use API call for checking credentials at the end i present main page as modal with performSegue like this :
#IBAction func loginPressed(sender: UIButton) {
ApiHelper.userService { (service) -> Void in
let query = GTLUserController.queryForLoginUser(email,password)
service.executeQuery(query) { (ticket: GTLServiceTicket!, object: AnyObject!, error: NSError!) -> Void in
if(error != nil){
return
}
//Code...
self.performSegueWithIdentifier("mainSegue", sender: self)
}
}
}
The segue work fine when i launch the app for the first time, but when i terminate the app (using swipe) and login again the segue is not working i see the main page for 1s then it back to login.
N.B : the segue work fine when i use it outside the API call
Thank's in advance

Never update the UI on a background thread, instead perform the segue on the main thread like so
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("mainSegue", sender: self)
}

Related

Swift control flow

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
submitTapped()
if let scheduleController = segue.destination as? ScheduleController {
scheduleController.jsonObject = self.info
}
}
In submitTapped(), self.info is assigned a value. But when I run my app, self.info is reported as "nil". I tried setting breakpoints at each of the three lines, and it seems that submitTapped() doesn't execute until after this function is finished.
Why is this? Does it have to deal with threads? How can I get submitTapped() to execute before the rest? I'm just trying to move from one view controller to another while also sending self.info to the next view controller.
UPDATE:
I ended up figuring it out (for the most part) thanks to the answer below + my own testing.
#IBAction func submitTapped() {
update() { success in
if success {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "showScheduler", sender: nil)
}
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// I'll probably check the segue identifier here once I have more "actions" implemented
let destinationVC = segue.destination as! ScheduleController
destinationVC.jsonObject = self.info
}
public func update(finished: #escaping (Bool) -> Void) {
...
self.info = jsonObject //get the data I need
finished(true)
...
}
The network request is an asynchronous task that occurs in the background and takes some time to complete. Your prepareForSegue method call will finish before the data comes back from the network.
You should look at using a completionHandler and also only triggering the segue once you have the data.
so your submitTapped function (probably best to rename this to update or something) will make the network request and then when it gets the data back will set the self.info property and then call performSegueWithIdentifier.
func update(completion: (Bool) -> Void) {
// setup your network request.
// perform network request, then you'll likely parse some JSON
// once you get the response and parsed the data call completion
completion(true)
}
update() { success in
// this will run when the network response is received and parsed.
if success {
self.performSegueWithIdentifier("showSchedular")
}
}
UPDATE:
Closures, Completion handlers an asynchronous tasks can be very difficult to understand at first. I would highly recommend looking at this free course which is where I learnt how to do it in Swift but it takes some time.
This video tutorial may teach you basics quicker.

segue called twice when using DispatchQueue.main.async

I'm trying to perform a segue to a new view controller, but the segue is being called twice and the new view controller appears twice.I'm using a method that performs a GET request to an API to retrieve data.That method uses a completion handler.
func getSearchResultsForQuery(_ query: String, completionHandlerForSearchResultsForQuery: #escaping (_ success: Bool, _ error: NSError?) -> Void)
When the method completes successfully my segue is called, from within the main queue as is required.
I've set breakpoints so I could see what was going on and the execution jumps from the performSegue back up to the conditional that checks if the method was successful and then continues until the segue is called a second time. I've tried a purely programatic segue, but the result was the same.I also added a print statement, and if I comment out the segue the print statement is only called once.
I've used this same pattern a number of times before and never had a problem with it and I just can't figure out why this is happening.The only thing I'm doing different this time is using Swift 3 and using DispatchQueue.main.async instead of dispatch_async(dispatch_get_main_queue(). Here is the function which is giving me this problem:
#IBAction func search(_ sender: UIButton) {
let searchQuery = searchField.text
TIClient.sharedInstance().getSearchResultsForQuery(searchQuery!) { (success, error) in
if success {
print("Food items fetch successful")
DispatchQueue.main.async {
print("Perorming segue for food item: \(searchQuery)")
self.performSegue(withIdentifier: "showFoodItems", sender: self)
}
} else {
print("error: \(error)")
}
}
}
Edit: I never found out what the problem was, but completely deleting the story board and recreating it solved it.
I know this isn't a great way to fix this issue, Also I can't leave a comment due to low reputation but what happens if you wrap the whole if statement in DispatchQueue.main?
#IBAction func search(_ sender: UIButton) {
let searchQuery = searchField.text
TIClient.sharedInstance().getSearchResultsForQuery(searchQuery!) { (success, error) in
DispatchQueue.main.async {
if success {
print("Food items fetch successful")
self.performSegue(withIdentifier: "showFoodItems", sender: self)
} else {
print("error")
}
}
}
Would that yield a different result or still the same result? checking for Bool doesn't require too much processing power so I don't think putting it in a main queue is a bad thing but I'd do this to trouble shoot. Sorry I can't just comment on this.
Check in storyboard, maybe you set segue from your button action instead of controller.

whose view is not in the window hierarchy Facebook Login

I have this warning I don't understand where is my error in my code
using code 7.2 and iOS 9.2
Error:
warning: Attempt to present < VidcustCustomAppIos.VCTabBarController:
0x7ff838f50790 > on < FBSDKContainerViewController: 0x7ff838d8ceb0 >
whose view is not in the window hierarchy!
#IBAction func facebookLoginDidTouch(sender: AnyObject) {
let loginManager = FBSDKLoginManager()
loginManager.logInWithReadPermissions(["public_profile", "email"], fromViewController: self) { (result:FBSDKLoginManagerLoginResult!, error:NSError!) -> Void in
if (result.isCancelled) {
print("Cancelled")
} else {
print("Logged in")
let token = FBSDKAccessToken.currentAccessToken()
VCClusterManager.sharedInstance.connectWithFacebook(token.tokenString, completion: { (isConnected, error) -> () in
if error == nil && isConnected == true {
self.performSegueWithIdentifier(self.segueIdentifier, sender: nil)
}
})
}
}
}
This is not with the case of Facebook Login
You will get this warning when performing a segue from a view controller that is embedded in container. The solution is to use segue from the parent of container, not from container's view controller.
Error:
Developers can receive this warning when performing a segue from a view controller that is embedded in container.
Solution:
Use segue from the parent of container, not from container's view controller (eg. back button, next button or any buttons that you want to perform an action to transition to another view controller)

EXC_BAD_ACCESS while passing array to new ViewController

I'm loading some XML from a webservice (car data), create some car objects and would like to display them in a TableViewController.
When the user has selected start and destination location, I'm making an async call to the webservice, show an activity indicator and as soon as the data is loaded, I go to a new view. So I have something like this:
class NewReservationViewController : UIViewController {
#IBAction func searchCarsClicked(sender: UIBarButtonItem) {
//show load cars activity indicator
loadingCarsActivityIndicator.startAnimating()
//load available cars from webservice asyncronously
DataManager.getAvailableCars([...parameter list...], carsLoadedCallback: carsLoaded)
}
func carsLoaded(loadedCars: [Car]) {
//dismiss the waiting widget
//trigger the segue and advance to the next screen
loadingCarsActivityIndicator.stopAnimating()
print("stopped cars loading activity indicator")
print("cars loaded callback called")
print("loaded \(loadedCars.count) distinct cars")
self.cars = loadedCars
performSegueWithIdentifier("showAvailableCars", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showAvailableCars" {
let carTableViewController = segue.destinationViewController as! CarTableViewController
carTableViewController.cars = self.cars
presentViewController(carTableViewController, animated: false, completion: nil)
}
}
}
class DataManager {
class func getAvailableCars([...parameter list...], carsLoadedCallback: ([Car]) -> Void){
Webservice.getAvailability([...parameter list...], completionHandler: { (data, response, error) in
//parse xml
//in the end I get an Array of Car objects
var cars: [Car] = ...
carsLoadedCallback(cars)
})}
}
}
When I populate the TableView with some DummyData I create within the CarTableViewController class, it works fine. However when I try to pass the car arrays to my TableViewController I get a EXC_BAD_ACCESS code = 2 exception. As far as I know this is some kind of Memory exception that is usually caused by a corrupt pointer. So I guess that the car array I created in my static DataManager class within the static method I called gets destroyed. However I'm not sure about that because automatic reference counting should avoid that.
The table view even displays the data but then immediately crashes with the EXC_BAD_ACCESS exception. I tried to set a general breakpoint in the XCode's breakpoints tab but however I don't get a reasonable error message on why the app crashes.
Do you have any ideas on why this happens. How can I get a better error message?
Thanks for your help in advance.
First of all check this function:
func carsLoaded(loadedCars: [Car])
It is returned as callback - what thread is it running? main or backround?
You should call on main thread
dispatch_async(dispatch_get_main_queue(), {
performSegueWithIdentifier("showAvailableCars", sender: self)
})
If doesn't help - provide the line, where it breaks in debugger, so I can help and see more.
UPD: Didn't notice, why do you do manual present?
presentViewController(carTableViewController, animated: false, completion: nil)
Your segue automatically shows view controller, you can change it's style (modal, push) in storyboard.
There are two potential issues that I can see. The first is that you are triggering your segue from a method that might not be on the main thread, you need to ensure that this is done on the main thread. The other issue is that in your prepare for segue you are unwrapping the new ViewController without checking it, so try this instead:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showAvailableCars" {
if let carTableViewController = segue.destinationViewController as? CarTableViewController {
carTableViewController.cars = self.cars
presentViewController(carTableViewController, animated: false, completion: nil)
}
}
}
As you said the table view displays the data and then immediately crashes I would assume that the problem is in your CarTableViewController. One thing you could check is when your cells are rendered, if you are trying to access some information that is null from the Car objects

Swift performSegueWithIdentifier not working

I am trying to switch view controllers after a user successfully logs in to their account, but it is not working correctly. I cant use a segue directly because if the login button is clicked it will go to that view controller regardless if the information is correct or not. I have tried everything that I know of with no success. This is the code I am trying.
#IBAction func loginTapped(sender: AnyObject) {
let username = usernameField.text
let password = passwordField.text
if username.isEmpty || password.isEmpty {
var emptyFieldsError:UIAlertView = UIAlertView(title: "Please try again", message: "Please fill in all the fields we can get you logged in to your account.", delegate: self, cancelButtonTitle: "Try again")
emptyFieldsError.show()
}
PFUser.logInWithUsernameInBackground(username, password:password) {
(user: PFUser?, error: NSError?) -> Void in
if user != nil {
self.performSegueWithIdentifier("Klikur", sender: self)
} else {
if let errorString = error!.userInfo?["error"] as? String {
self.errorMessage = errorString
}
self.alertView("Please try again", message: "The username password combiation you have given us does not match our records, please try again.", buttonName: "Try again")
}
}
}
I have the storyboard ID set to "Test" and it is not switching view controller when the correct information is entered. Can somebody help me resolve my problem?
[Assuming that your code is not crashing, but rather just failing to segue]
At least one problem is:
self.performSegueWithIdentifier("Test", sender: self)
should be:
dispatch_async(dispatch_get_main_queue()) {
[unowned self] in
self.performSegueWithIdentifier("Test", sender: self)
}
Remember that all UI operations must be performed on the main thread's queue. You can prove to yourself you're on the wrong thread by checking:
NSThread.isMainThread() // is going to be false in the PF completion handler
ADDENDUM
If there's any chance self might become nil, such as getting dismissed or otherwise deallocated because it's not needed, you should capture self weakly as [weak self] not unowned, and use safe unwrapping: if let s = self { s.doStuff() } or optional chaining: self?.doStuff(...)
ADDENDUM 2
This seems to be a popular answer so it's important to mention this newer alternative here:
NSOperationQueue.mainQueue().addOperationWithBlock {
[weak self] in
self?.performSegueWithIdentifier("Test", sender: self)
}
Note, from https://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift:
NSOperation vs. Grand Central Dispatch (GCD)
GCD [dispatch_* calls] is a lightweight way to represent units of work that are going to be executed concurrently.
NSOperation adds a little extra overhead compared to GCD, but you can add dependency among various operations and re-use, cancel or suspend them.
ADDENDUM 3
Apple hides the single-threaded rule here:
NOTE
For the most part, use UIKit classes only from your app’s main thread.
This is particularly true for classes derived from UIResponder or that
involve manipulating your app’s user interface in any way.
SWIFT 4
DispatchQueue.main.async(){
self.performSegue(withIdentifier: "Test", sender: self)
}
Reference:
https://developer.apple.com/documentation/uikit
Make sure you're putting your:
self.performSegue(withIdentifier: ..., ...)
in viewDidAppear or later. It won't work in viewWillAppear or viewDidLoad.
I've got the same problem with login issue. probably we do the same tutorial. After naming your segue identifier you need to replace:
performSegueWithIdentifier("Klikur", sender: self)
with:
dispatch_async(dispatch_get_main_queue()){
self.performSegueWithIdentifier("Klikur", sender: self)
}
type of seque needs to be set as "show (e.g. Push)" in the storyboard segue.
Hope it will work.
The segue identifier that you pass to performSegueWithIdentifier(_:sender:) must exactly match the ID you've given the segue in the storyboard. I assume that you have a segue between the login view controller and the success view controller, which is as it should be; if not, ctrl+drag from the first to the second view controller, then select the segue's icon in the storyboard and set its ID to Klikur. Don't perform the navigation on the button click, as one commenter said, because that defeats the main purpose of having segues, which is to give a visual indication of the application flow in the storyboard.
EDIT: Here's the code for a login view controller:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var usernameField: UITextField!
#IBOutlet weak var passwordField: UITextField!
#IBAction func attemptLogin(sender: AnyObject) {
if !usernameField!.text!.isEmpty && !passwordField!.text!.isEmpty {
performSegueWithIdentifier("Klikur", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if "Klikur" == segue.identifier {
// Nothing really to do here, since it won't be fired unless
// shouldPerformSegueWithIdentifier() says it's ok. In a real app,
// this is where you'd pass data to the success view controller.
}
}
}
And a screenshot of the segue properties that I'm talking about:
swift 3.x
DispatchQueue.main.async(){
self.performSegue(withIdentifier: "Klikur", sender: self)
}
DispatchQueue.main.async() {
self.performSegue(withIdentifier: "GoToHomeFromSplash", sender: self)`
}
Check to make sure you are running the perform segue on a visible view controller.
This is an edge case, but my perform segue failed when I attempted to run it on the view controller belonging to my UIPageViewController that was not currently visible. It also failed if I attempted to do the segue on all view controllers belonging to my UIPageViewController, including the view controller currently visible. The fix was to track which view controller was currently visible in my UIPageViewController, and only perform the segue on that view controller.
An example in a login. When you have success in your login after clicking a button (Action) you can use:
self.performSegue(withIdentifier: "loginSucess", sender: nil)
But if you are launching the app and you got the credentials from your keychain you need to use this as a part of the theard:
DispatchQueue.main.async(){
self.performSegue(withIdentifier: "sessionSuccess", sender: nil)
}

Resources