UIAlertController actionsheet too slow to pop up [duplicate] - ios

I have a tab bar based app. There are navigation controllers in all 5 tabs with instances of custom view controller setup properly as root view controllers. This loads just fine. A couple of these view controllers contain table views. I want to show a modal view controller to the user when they select a row in the table view. The (relevant part of) didSelectRowAtIndexPath delegate method looks as follows:
SampleSelectorViewController *sampleVC = [[SampleSelectorViewController alloc] init];
[self presentViewController:sampleVC animated:YES completion:NULL];
The modal view controller appears BUT it appears after a very noticeable delay. At times it even requires the user to tap the row a second time. A few things that I have already verified are:
Table view's didSelectRowAtIndexPath method is called when the user taps the row
The didSelectRowAtIndexPath method does not contain any blocking calls. There are no network operations being performed and the modal view controller's setup does not involve any processing intensive task. The data it displays is static.
If I push the new view controller onto the navigation stack (everything else remaining exactly same), it behaves perfectly without any delays. It is only when presented modally that the delay is encountered.
Any ideas/suggestions?

It seems calling presentViewController:animated:completion from within tableView:didSelectRowAtIndexPath: is problematic. It's difficult to find anything that stands out when using the Time Profiler in Instruments, also. Sometimes my modal view comes up in less than a second and other times it takes 4s or even 9s.
I think it's related to the underlying UIPresentationController and layout, which is one of the few things I see when selecting the region of time between tapping on a row and seeing the modal presentation in the Time Profiler.
A Radar exists describing this issue: http://openradar.appspot.com/19563577
The workaround is simple but unsatisfying: delay the presentation slightly to avoid whatever contentious behavior is causing the slowdown.
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:nav animated:YES completion:nil];
});

If you call present(:animated:completion:) in tableView(:didSelectRowAt:), selectionStyle == .none for selected tableview cell and you’ve got this strange behavior then try to call tableView.deselectRow(at:animated:) before any operations in tableView(_:didSelectRowAt:).
Did it help?

Swift 4:
you can use as below.
DispatchQueue.main.async {
let popUpVc = Utilities.viewController(name: "TwoBtnPopUpViewController", onStoryboard: "Login") as? TwoBtnPopUpViewController
self.present(popUpVc!, animated: true, completion: nil)
}
It works for me.

I guess you also set the cell's selectionStyle to UITableViewCellSelectionStyleNone. I change to UITableViewCellSelectionStyleDefault and it work perfect.

You should display it modally from your root vc (e.g: customTabBarRootViewController).
save a reference, and use the reference controller to display it.

I have also had this strange delay when presenting from tableView:didSelectRowAtIndexPath: looks like an Apple bug.
This solution seems to work well though.
CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Fixes a bug where the main thread may be asleep, especially when using UITableViewCellSelectionStyleNone

Solution in Swift 3
In the SampleSelectorViewController(the presented view controller) use the below code
DispatchQueue.global(qos: .background).async {
// Write your code
}

The common problem with this behaviour is as follows:
one sets selectionStyle = .none for a cell in the tableView (it seems that it doesn't depend on UITableViewController subclassing as was written at http://openradar.appspot.com/19563577) and uses at the delegate method
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
animating deselect
tableView.deselectRow(at: indexPath, animated: true)
which implies animation for non-animating cell.
In this case the subsequent view controller presentation has a delay.
There are some workaround solutions including dispatch_async on main thread, but it is better not to call deselectRow even without animation on unselectable cells in your code.

As per #Y.Bonafons comment, In Swift, You can try like this, (for Swift 4.x & 5.0)
DispatchQueue.main.async {
self.showAction() //Show what you need to present
}

Try this For swift version 5.2 you can use below code:
DispatchQueue.main.async(execute:{self.present(nav, animated: true)})

Related

UITableView Header Snaps to the Top of the Screen on Certain iPhones

Sometimes (but not all the time!) on iPhones (and iPhone simulators) I notice my UITableView header has this 'snapping' behavior that, when I try to drag down from the top of the screen it snaps back up instead of fluidly moving back up like a tableView normally behaves.
I'm wondering if anyone knows of this bug, what causes it, or how I can fix it? I feel like it might have something to do with UITableViewHeader but I'm not sure.
Unfortunately, I cannot share the code, but I don't believe it is something in the code. I manually commented on almost every line of the code and the problem persists!
Here was the problem for me and I'm pretty sure its a bug in XCode/Swift.
At first my navigation controller in the storyboard has this setting:
In my code I have the following method declared:
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
let hideBar = (viewController == self)
navigationController.setNavigationBarHidden(hideBar, animated: animated)
}
Used to disable the navigation bar on the home screen. The combination of these two (if the method is declared on the home screen) causes a bug as shown above. I am able to repeat this bug in a new project.
The solution to this bug is to check the Show Navigation Bar box on the Navigation Controller
Since code is not provided for this question, I am answering based on my assumption.
Assumption 1 - Using manual layout.
If you have code in layoutSubviews(). Then you might want to check your calculation again. Make sure view frames are calculated one time in layoutSubview() method.
Assumption 2 - View animation is blocked/delayed by some other tasks on main thread.
Make sure to run no-UI/API code in background thread.
Reuse cell instances: for specific type of cell you should have only one instance, no more.
Don’t bind data at cellForRowAtIndexPath: method ‘cause at this time cell is not displayed yet. Instead use tableView:willDisplayCell:forRowAtIndexPath: method in the delegate of UITableView.
Hey, can you move your header view content in first cell of first section, so that you can avoid header view problem. Then check if snappy problem is occurring.

UINavigationController Interactive Pop Gesture only works first time

I have been working on an app for some time and just realized the swiping back in the detail view only returns me to the master view the first time. It also isn't smooth, even when it works on the first time. Instead of smoothly going to the master view, it jumps all at once, even when I swipe slowly. It used to work correctly, but I haven't been testing for this specifically, so I don't know when it stopped working and what I changed to cause this.
A little about how my app is setup...
I have a split view controller that is connected to my MasterTableViewController and DetailViewController.
Both of those are have TableViews and are embedded in Navigation Controllers.
I have set it up so that the app originally loads to the MasterTableViewController instead of going immediately to the DetailViewController, but even when I take this out, the interactive pop gesture doesn't work.
I don't believe I've messed with any of the back button controls. I have looked through my code and storyboard and can't find anywhere that I have. This is part of what is most confusing because these questions (1, 2, and 3) all seem to have problems stemming from changing the back button or can be fixed by entering the following line of code:
self.navigationController.interactivePopGestureRecognizer.delegate = nil
Adding that to my code seems to have no impact on how it behaves.
Here is a picture of how it is setup for reference:
I can usually figure out these things on my own, but this problem baffles me because it works the first time, but not any others. As far as I can tell, nothing changes between the first time and the others. I don't know if anybody else has had the same issue, but any help on why this might be happening would be greatly appreciated. I can provide code or answers to questions on how I am doing certain things if needed. I haven't put any in because there are so many different things controlling this piece that I don't know where to start.
When are you calling self.navigationController.interactivePopGestureRecognizer.delegate = nil?
Doing this will definitely disable interactive pop. It sounds like you may be calling this after a certain UIViewController appears.
What other modifications to UINavigationController are you making? Are you using appearance delegate?
Are you subclassing? If so, are you calling super in all of your method overrides?
Also check your overrides of viewWillAppear in child ViewControllers. This method gets called during an interactive pop. If you are doing a lot of computation (or synchronous calls) on the main thread within this method, it could cause frame drop, hence the choppy animation.
Hope this helps
From Alex Chase's answer : Also check your overrides of viewWillAppear in child ViewControllers. This method gets called during an interactive pop.
added it to viewWillAppear and it worked:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.interactivePopGestureRecognizer?.delegate = self
}

Swift - segue programmatically takes two seconds to show view controller the first time

I can't find why a segue programmatically takes about 2 seconds to show the view controller the first time. When I return to the view controller and try to perform the segue again, it doesn't take time to show the view controller. These view controllers I have opened from a modal segue.
I'm doing something like this:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("<segue identifier>", sender: indexPath)
}
}
And for the UITableView I disabled the delaysContentTouches option. And it still doesn't work, it just takes about two seconds to open, but only the first time.
Meanwhile I have an NSTimer fetching data from a web service, but I read that NSTimer executes on a different thread, so I think this is not the problem.
Am I missing something to make the segue show the view controller faster?
NSTimer can work on a background thread but does not necessarily do so. It depends on how you instantiate it. A simple repeating timer would typically use the main thread's runLoop which is closely tied to UI updates. That being said, I doubt that your timer could cause a delay in showing the view. It is more likely that some heavy processing is being performed while the UI is setting up.
The typical things that could induce such a one-time delay are:
- A large image on the view (in the storyboard or Nib)
- Giving firstResponder to a text field that needs to show the on-screen keyboard (the on-screen keyboard always takes a couple of seconds to show up the first time)
- Complex UI constraints that make auto-layout go through multiple passes to figure out the positions and sizes of subviews (but that would reoccur the next time around unless you're keeping the view controller alive and reusing it).
- Repeated reload on a UITableView or UICollectionView (this may happen if you're trying to show the data as it comes in) but this would also reoccur on subsequent calls to the segue unless you're caching something

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.

Unbalanced calls to begin/end appearance transitions for UIViewController when selecting cells in tableView

I know this has been asked a ton of times but I've tried some of the solutions in other questions and haven't been successful.
In my app I have four tabs, each with their own UINavigationController. The first - UserIndexController is a UIViewController that contains a table. When a cell is selected, I initialize a new UIViewController and push it onto its UINavigationController. Here's that code:
tableView.deselectRowAtIndexPath(indexPath, animated: true)
user = data[indexPath.row]
controller = UserViewController.alloc.initWithNibName(nil, bundle: nil)
controller.user = user
self.navigationController.pushViewController(controller, animated:true)
Once a cell is selected, almost as soon as the UserViewController is loaded I get:
Unbalanced calls to begin/end appearance transitions for UserIndexController.
When I go back in the NavigationController from UserViewController to UserIndexController, I get:
Unbalanced calls to begin/end appearance transitions for UserViewController.
Enter the insanity (at least to me): When my app loads, if I select any other tab before selecting a cell in my UserIndexController, then go back to my UserIndexController and select a cell, the unbalanced calls warning goes away.
So that's where I'm at. If anyone has any ideas or suggestions I'd love to hear them. Also I'm happy to provide more code if it helps.
Looks like my searching wasn't good enough. I found the answer in this question. Essentially, in my UITabController I'm calling viewWillAppear but didn't call super in it. Doing that fixed it.
In one of your 'animated:' put NO then it will solved your problem.

Resources