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.
Related
When I try to back out of my Navigation stack using either the back button or swiping back from the left side of the screen, the navigation bar changes but does not dismiss the ViewController (or, in the case of swiping, the navbar animation is not interruptible). Please see the gif below.
I'm implementing my NavigationController with storyboard. It's just a UIViewController embedded into a UINavigationController. I've tried detaching the UIViewController and reattaching it to a different NavControl, manually embedding it, removing the TabBar controller that was also embedded originally. All of these have led to the same result.
Edit: Also relevant is how I'm pushing these ViewControllers to the nav stack. To present these VCs, I'm just using navigationController?.pushViewController(vc, animated: true).
present(_: UIViewController,animated: Bool) gives a modal presentation which is not what I'm looking for.
Any ideas as to what would be causing this odd behavior?
I had the following in my rootViewController's viewDidLoad from some earlier experimentation that wasn't being used:
// Setting as delegate might not be necessary right now,
// could become useful in the future?
navigationController?.navigationBar.delegate = self
Evidently it did not become useful in the future. Seems like doing this causes the delegate to become responsible for some of the work navBar usually does for free? Removing it immediately fixed the issue.
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
}
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)})
I'm trying to implement the following behavior:
Long press on a collection view brings a full-window view (call it LetterView) to the front
Subsequent gestures/touches are only processed by the LetterView.
(edit: I should mention that I want a transparency effect of seeing the collectionview items beneath LetterView)
I seem to be running into behavior that everyone else is trying to implement, though - my touches get processed by both the LetterView and the collection view. I.e. I can scroll the collection view AND have hits processed by my topmost view. Showing the view hierarchy in XCode clearly shows LetterView at the front, and both the UICollectionView and the LetterView are subviews of UICollectionWrapperView.
LetterView is a UIView subclass with a UIViewController subclass. It's added to the view hierarchy programmatically, inside my UICollectionViewController subclasses's viewDidLoad method, like so:
super.viewDidLoad()
letterDrawingViewController = LetterDrawingViewController()
let viewFrame : CGRect = self.collectionView!.frame
letterDrawingViewController.view = LetterDrawingView.init(frame:viewFrame)
letterDrawingView = letterDrawingViewController.view
self.addChildViewController(letterDrawingViewController)
letterDrawingViewController.didMoveToParentViewController(self)
collectionView?.addSubview(letterDrawingView)
It doesn't appear to be a first responder issue, as I tried overriding canBecomeFirstResponder in LetterView and assigning it first responder status when I move it to the front
I tried setting userInteractionEnabled=FALSE on the CollectionView, but keeping it true on the LetterView after I moved LetterView to the front. This disabled all touch events for both views
I tried setting exclusiveTouch=True for LetterView when I moved it to the front. This didn't appear to do anything.
Aside from any specific tips, are there any general techniques for debugging hit-testing like this? According to the docs on hit-testing in iOS, iOS should prefer the "deepest" subview that returns yes for hitTest:withEvent:, which, since LetterView is a subview of collectionview, and in front of all it's cells, should be the front? Is there any logging I can enable to see a hit test over the view hierarchy in action?
Thank you!
Nate.
If letterView is full screen, you probably don't want to add it as a subview of the collection view like you are. Maybe try adding it to the application's window instead and see how that does. At least in that instance it should intercept all the touch events.
Another method, although admittedly a more fragile feeling one, would be to enable and disable user interaction on the collectionView as you present and dismiss letterView.
So, when letterView is about to be presented, you can call
self.collectionView.userInteractionEnabled = NO;
and if you also know when that view is about to be dismissed you can call
self.collectionView.userInteractionEnabled = YES;
The only thing here to worry about is that you don't get into a bad state where your letterView is not presenting and your collectionView is also ignoring a user's touch. That will feel totally broken.
Whilst I think you can deal with your issue somewhat easily I think you are making a design mistake. It feels like you are trying to code this thinking like a web developper by adding a child view to your view and trying to intercept the touches there like one would do in a modern JavaScript single page app. In iOS I think this is bad design. You should segue or present the new viewController using the methods provided by apple.
So your code should look soothing like:
letterDrawingViewController = LetterDrawingViewController()
self.presentViewController(letterDrawingViewController, animated: true, completion: nil)
iOS8 has the added benefit of allowing you to have awesome custom transitions. Take a look at this : http://www.appcoda.com/custom-segue-animations/
I have two UIViewController. One is ViewController, and another is GameplayViewController.
ViewController will execute
let gameplayViewCtrl = GameplayViewController()
self.presentViewController(gameplayViewCtrl, animated: true, completion: nil)
in viewDidAppear() function. So it shows then load another UIViewController.
GameplayViewController has UICollectionView as its member. It extends/conforms to UIViewController, UICollectionViewDelegate, UICollectionViewDataSource. I've linked delegate, datasource, and UICollectionView variable as #IBOutlet properly in storyboard.
With above setup, I tested it. It shows white screen for a sec, then shows black screen. The cells in UICollectionView in GameplayViewController are never get loaded (no call in code checked via breakpoint). But if I changed the entry point (an arrow in storyboard) to point at GameplayViewController, then it loads properly and show all data in cells. Things work fine.
What's the problem here? I guess it's about event chain that never get caught in non-default UIViewController.
FYI: I do this in xcode 6 beta 3
Updated
I did some more research, and found out there're at least two possible ways to do this. One is my approach, and another is to use segue.
Segue works, you can find how to do it How do I use performSegueWithIdentifier: sender:?. I tested it.
I also tested to switch from GameplayViewController to ViewController (reverse from my original test). It showed black screen similar to the first test. But it should show white screen as for ViewController, it has white background color. This triggers me to think that something's wrong creeping inside.
For now, I use segue approach. But I still want to know why self.presentViewController() won't do the job for us, and it shows black screen and nothing called or happened.