Segue from AlertController without action - ios

I have a loading indicator, implemented with UIAlertController. When i send auth request to server - i fire up loading indicator. When request is successful i have to go to another ViewController and hide loading indicator. Before i perform segue i need to wait until AlertController dismiss indication is completed. So i have such a logic:
private var loadingIndicator: UIAlertController?
func navigateToMonitoring() {
DispatchQueue.global(qos: .background).async {
if let indicator = self.loadingIndicator {
while !indicator.isBeingDismissed { continue }
DispatchQueue.main.async {
self.performSegue(withIdentifier: "Monitoring", sender: self)
}
}
}
}
But when this method executed i've got a message - Warning: Attempt to present on whose view is not in the window hierarchy! - and segue does not performed.
How can i fix this?
(Swift 3, Xcode 8)

You can maybe create the alert and store its reference, then show the alert and call the API, and in that API's completion block, you can dismiss it and perform the segue, hope this makes sense to you.

Related

How to make a button in an extension redirect the user to a certain page

Overview
I'm building a custom keyboard extension in swift and I want to make a button redirect the user to a certain page in its parent application.
What I'm doing
I created a button in my keyboard that is supposed to take the user to the application and it works.
#objc func openURL(_ url: URL) {
return
}
func openApp(_ urlstring:String) {
var responder: UIResponder? = self as UIResponder
let selector = #selector(openURL(_:))
while responder != nil {
if responder!.responds(to: selector) && responder != self {
responder!.perform(selector, with: URL(string: urlstring)!)
return
}
responder = responder?.next
}
}
#IBAction func openShop(_ sender: Any) {
openApp ("myapppurl")
parent?.performSegue(withIdentifier: "Shop", sender: self)//Shop is the identifier of a segue in my app.
}
And of course I added this to my the info.plist file in my app.
Problem
When I press the button, it takes me to the app but it shows the main vc, not the page i need.
Question
How do I make the button take me directly to the desired vc?
You can't performSegue to container app from extension. In order to performSegue to work view needs to be in foreground. When you are performing Segue, container App is in background and that's why it is not doing anything.
You will need to handle deep-linking in container App.
Add destination inside your openApp URL "myapppurl//shop"
This should trigger openURL[enter link description here][1] in container app.
and URL param should give you requested destination. There you can handle logic to deep-linking.
You can set USERDEFAULTS value , when opening app check it, and clear.

Proper way to end an Async process

So I have this code:
#IBAction func onStart(_ sender: Any) {
DispatchQueue(label: "SyncQueue").async{
for i in 0..<numberOfItemToDownload {
// download data from the internet this may takes longer depends on the connection speed and data being download
if isCancelled {
DispatchQueue.main.async { // dismiss the progress ViewController (displayed modally) }
break
}
}
}
}
#IBAction func onEnd(_ sender: Any) {
isCancelled = true
}
This works ok, but if the current item being download takes longer then the user taps the "End" button the "progress dialog" is not dismissed until the current item is done. This makes the "End" button not working in user's perspective.
In Android we can interrupt the AsyncTask process and end it. Is there another way to do it in Swift? like when the user taps the "End" the process should immediately stop and dismiss the "progress dialog".
GDC is not easily cancellable.
Use an NSOperation subclass then you can cancel easily and quickly.

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.

Presenting view controllers on detached view controllers is discouraged - screen view turns black

I have two view controllers: VC1 and VC2. From VC1, press a button will navigate app to VC2. VC2 has a network request function in viewDidLoad() which shows an alert if the request is failed.
If everything is fine, no request failure on VC2, when I move back to VC1, it would call to deinit function of VC2.
However, if the request failed and an error alert is shown, the deinit function (of VC2) wouldn't be called when I move back VC1. Moreover, it causes an error of "Presenting view controllers on detached view controllers is discouraged" while it's trying to show that alert after the screen displays VC1, screen then turns black except for navigation bar and the error alert of VC2 is shown on VC1 (The reason is when the VC2 is going to present the error alert, user suddenly press the back button on navigation bar to move back to previous screen). My alert is a global variable.
Here is the code I handle request and show alert on VC2:
func sendRegisterRequest() {
registerAPI.request(parameters: parameters) {
[weak self] (response) in
if let strongSelf = self {
strongSelf.handleResponse(response)
}
}
}
func handleResponse(response: Response<AnyObject, NSError>) {
let result = ResponseHandler.responseHandling(response)
if result.messageCode != MessageCode.Success {
// Show alert
handleResponseError(LocalizedStrings.registerFailedTitle, message: result.messageInfo, requestType: .Register)
return
}
}
func handleResponseError(title: String, message: String?, requestType: RequestType?) {
alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
let action = UIAlertAction(title: LocalizedStrings.okButton, style: UIAlertActionStyle.Default) { (action) -> Void in
self.handleAlertViewAction(requestType)
}
alert.addAction(action)
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(self.alert, animated: true, completion: nil)
})
}
I attach the screenshot here:
Could anyone have solution for this issue? Any help could be appreciated,
Lucy Nguyen.
I've got the same problem when I build my app. To solve this problem, I tried many changes and finally removed the error message.
I made an alert window in first VC to give user some notice. And I write the alert control code in - (void)viewDidLoad. I think you did it same or in - (void)viewWillAppear.
Just move your alert code to - (void)viewDidAppear. Then, error will be gone.

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