I'm working UICollectionView and facing issue with contentOffset while reloading the data.
I already read multiple posts regarding this, but couldn't find any solution.
Issue:
UINavigationController -> ViewController1 (rootViewController)
ViewController1 contains a vertical UICollectionView that contains multiple UICollectionViewCells.
On tapping a particular UICollectionViewCell, ViewController2 is pushed into the navigation stack.
Now, on returning back to ViewController1, I'm reloading the UICollectionView which changes the previous contentOffset.
What is required?
I don't want the contentOffset of UICollectionView to be changed when I return back to ViewController1 from ViewController2.
try this code for reloading your collectionView:
let contentOffset = collectionView.contentOffset
collectionView.reloadData()
collectionView.layoutIfNeeded()
collectionView.setContentOffset(contentOffset, animated: false)
Related
I have a view controller with a tableview containing a list of chats, a search controller enbeded in the navigation item (iOS 11 feature)
let searchController = UISearchController(searchResultsController: nil)
searchController.dimsBackgroundDuringPresentation = false
navigationItem.searchController = searchController
definesPresentationContext = true
When the user taps a chat in the table view the app pushes a new view controller with another table view containing messages for that chat. That works like it is supposed to:
The problem is that when the user activates the search controller, find some chat and taps it, the pushed view controller containing table view with the chat messages does some really strange animation with the table view that should not happen:
I load the data before the actual navigation and bind it to the table view in viewDidLoad using just reload() on the table view. The problematic table view uses auto layout and custom cells.
The problem is very similar to UITableView has unwanted animation when reloadData is called but for me it only happens when the iOS 11 search controller is active.
Edit: If I remove tableView.rowHeight = UITableViewAutomaticDimension and use a fixed height using func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat the problem is still there
If you just hide the searchBar before pushing a new viewController then it may fix your problem.
You need to create a global variable for searchBarCancelButton and find the cancel button from its subviews when you search something
let buttons = searchController.searchBar.subviews.first?.subviews.filter { (view) -> Bool in
return NSStringFromClass(view.classForCoder) == "UINavigationButton"
} as? [UIButton]
searchBarCancelButton = buttons?.first
then you can manually cancel it.
self.searchBarCancelButton?.sendActions(for: .touchUpInside)
Personally, I would simply hide the searchView controller before presenting the new view controller.
( Using UIView.animates with a completion handler for example )
I would not try to investigate further because since iOS11, there is an esoteric problem in the safe area management. A bug ? :)
Even the launch screens layouts are not correctly handled.
So many majors logos miss their middle part at launch !
You can try to call cell.layoutIfNeeded() right after dequeuing and setting a content of the cell
iOS 11 completely revamped the safe area API, including scroll view inset adjustment behaviors, which can cause unwanted animation when ignored. Therefore, disable automatic content inset adjustment for the scroll view with the unwanted animation:
if #available(iOS 11.0, *) {
tableView.contentInsetAdjustmentBehavior = .never
} else {
// < iOS 11 logic
}
Do not invoke reloadData() method in your viewDidLoad or viewWillAppear. Instead of this reload your tableView with empty data in viewDidLoad so your tableView will show nothing and then in your viewDidAppear invoke reloadData() method to load all of your chats. This will restrict your tableView from loading with unwanted animation.
var shouldShowEmpty = true
func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
}
func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
shouldShowEmpty = false
tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if shouldShowEmpty {
return 0
}
return yourArray.count
}
As per attachment, It looks like due to auto layout, cell's element's height was zero (UILabel or UIImageView) and then suddenly when table view reloads it gets the data inside the cell which increase its height(automatic dimension or whatever) which cause this animation.
Try to push it without animation or try to set fix cell's element's height and width and check is it still showing this animation.
Did you check without invoking UISearchBar, If you select on any cell same animation is happening ?? or did you try to remove UISearchbar and select on the cell and check the animation part ?
Please share your code so we can see more clearly.
I have had a similar problem, and I believe that solution is the same. The keyboard is causing the problem, to be more correct, keyboardWillHideNotification trigger. You have text field at the bottom, that probably listens to notifications for keyboard show/hide, where layoutIfNeeded() is triggered if you animate bottom constraint so that your keyboard doesn't overlap your text field. So when you finish your search in the search text field, keyboardWillHideNotification gets triggered at an unwanted time. I solved my issue by calling:
resignFirstResponder()
for the text field that causes this event. In my case, that was after pressing the button, in your, I believe it's in didSelect tableView cell in the search table view.
If you are still active at this issue, please let me know if you manage to solve it. I broke my head trying to solve this issue, which apparently is so simple and straightforward.
In VC2 Try a delay function before reloading table
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
tableView.reloadData
}
or else
In VC1 in didSelect resign first responder of search controller before pushing to VC2
or
In VC1 in didSelect resign first responder of search controller and set a delay before pushing to VC2.
I have a childViewController on a UICollectionViewController. I have so my childViewController appears on the screen. But when I register a cell collectionView?.register(MyCustomCell.self, forCellWithReuseIdentifier: CellId) and use numberOfItemsInSection and cellForItemAt. The problem is that the cells wont appear, I have checked the code so it´s right. Can it be something with the childViewController?
Generally it's not preferred to add any child view or viewController to UICollectionViewController or UITableViewController as you will see un-expected results as both of them inherits from scrollview and make the added content scroll able or scattered any place, if you have then better create a custom viewController with a collectionView and the childViewController
I've read here that if I have UITextField in my table cells, the table scrolls automatically to the cell that contains them. This works perfectly.
Making a UITableView scroll when text field is selected
Is there a way to get this to work with a UIViewController? I'm using a UIViewController because I have a lot of custom UI that needs to be laid out on top of the table view. Thanks!
I would recommend splitting your viewController into several using containerViews.
That way, you can use a UITableViewController for your tableView and use a regular UIViewController for all the custom UI.
Another way would be to set a scrollView's contentOffset manually when a textField is in focus.
It would look something like that.
#IBAction func textField(didBeginEditing sender: UITextField) {
let point = CGPoint(x: 0, y: textField.frame.minY)
scrollView.setContentOffset(point, animated: true)
}
I got a table View and collection view on the each cell.
When I tapped the cell, following code will be invoked in cell's didSelectRowAt:IndexPath method.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
navigationController?.pushViewController(anotherViewController(), animated: true)
}
The anotherViewController is an empty view controller on storyboard for now. It only print "I'm coming" in viewDidLoad().
Here is the problem:
When I touched the cell, the push animation seems got stuck, pleas check the following GIF.
I'm really confused in what's happend.
Feel free to give any Advice.
The following Image shows the view hierarchy.
Cells on the table view.
Collection view and other components on the cell's content view.
At the end, image views on the collection view's content view.
The solution for me was to explicitly set the background color of the new view controller, even if it's black.
I had the same issue and since I'm not using storyboard, this was the solution for me.
I suspect the problem rests in the anotherViewController() reference. I was even able to reproduce the problem when I pushed to a UIViewController instance:
navigationController?.pushViewController(UIViewController(), animated: true)
But when I instantiate a scene from a storyboard, that worked fine. Obviously, you need a scene with the appropriate storyboard identifier:
let controller = storyboard!.instantiateViewController(withIdentifier: "Details")
navigationController?.pushViewController(controller, animated: true)
Or, if I defined segue between the two view controllers and performed the segue, that also worked:
performSegue(withIdentifier: "DetailsSegue", sender: self)
Short story:
Going from UIPageViewController (that presents proper DetailViewController) to UITableViewController, then changing (on didSelectRowAtIndexPath) the DetailViewController of the UIPageViewController and navigationController?.popViewControllerAnimated(true), displays proper UIPageViewController with changed DetailViewController but the whole view is moved down by the height of the navigationBar. Clicking on the screen sends it under the bar (where it should be).
Long story:
I embedded UIPageViewController into UINavigationController. Navigation bar translucent
I created a DetailViewController in my storyboard with a scrollView inside it.
I created custom function in the UIPageViewController to return desired UIViewController
func viewDetailViewController(index: Int) -> DetailViewController? {
if let storyboard = storyboard, page = storyboard.instantiateViewControllerWithIdentifier("DetailViewController") as? DetailViewController {
// Setting the page variables that it stores
return page
}
return nil
}
I set up a code (also in UIPageViewController):
override func viewWillAppear(animated: Bool) {
if let viewController = viewDetailViewController(currentIndex) {
setViewControllers(
[viewController],
direction: .Forward,
animated: false,
completion: nil)
}
}
Then I setup an bar button item to and in the storyboard I connected it to so it perform segue to UITableViewController. I want to pop a viewContorller on cell click so:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
self.pageViewController.currentIndex = indexPath.row
navigationController?.popViewControllerAnimated(true)
}
When the view is popped the new view of the UIPageViewController is replaced, the pages work well and everything is great except one thing. The whole view is moved down by the height of the navigationbar. What's even more strange is that (just) tapping on the screen sets the scrollView under the navigationbar (where it should be)
I have also noticed that not only the scrollView is moved down, but what is actually moved down is UIView that contains an UIView that contains the scrollView.
What might be causing this?