iOS 11UICollectionView scroll to top gets stuck with large titles - ios

The title is a mouthful, but here is what is happening. In iOS 11 when you have a navigation bar with large titles then you scroll down a ways and tap the status bar it will scroll to the top. When it scrolls to the top it gets stuck scrolling past the top.
It looks super messed up, here is an example after it scrolled to the top. It scrolled so far it started pulling the refresh control!
Has anyone seen this and been able to fix it? Mail has a large title and doesn't have the problem, though it is likely not a UICollectionView
Here is a gif of it happening:
As far as code goes it is as simple as I can make it:
extension ViewController: UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "fakeCell", for: indexPath) as? UICollectionViewCell
cell?.backgroundColor = [UIColor.black, .blue, .red, .green, .yellow][indexPath.row % 5]
return cell!
}
}

Please try this solution:
in viewDidLoad - set extendedLayoutIncludesOpaqueBars = true
in storyboard - pin collectionView top constraint to superview top with constant = 0 (collectionView will be under navigationbar when translucent)
After that if you tap on statusbar, collectionView stops in the right place.

I had the same problem as you!
In my case I managed to solve this problem by making the navigation bar transluent :
Do not ask me why it works... I have no idea
(I've got the idea there https://stackoverflow.com/a/50639773)

I had similar problem like this. In my case the table view was added to a view controller and the table view's top was assigned to safe area. I changed the table view to super view's top. It was working as expected. This may help in ur case if the collection view is added to the view controller.
Thanks.

Related

In UICollectionView, collapse animation is imperfect, with no height shrinking animation for the hidden item

Based on the problem described in
How to achieve smooth expand/ collapse animation in UICollectionView with dynamic cell height?
and the improvement mentioned in
Change default StackView animation
I had made the following changes
Added an inner UIView, for collapse and expand purpose
The inner UIView will have attribute Clips to Bounds set to true
A vertical UIStackView placed in the inner UIView, with bottom constraint set to high.
I really don't know. For unknown reason, the bottom constraint of the parent UIStackView need to be low, in order for the UI to stay at top, during collapse animation.
This is my outcome.
As you can see,
The expand animation works just fine!
For collapse animation, the inner UIView will disappear immediately, without any animation.
Do you have any idea, why there is no hidden animation during collapse?
Here's the code which perform collapse/ expand.
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return shops.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let collectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CollectionViewCell else {
fatalError()
}
let shop = shops[indexPath.item]
collectionViewCell.title.text = shop.title
collectionViewCell._description.text = shop.description
if isExpanded[indexPath.item] {
collectionViewCell.innerView.isHidden = false
} else {
collectionViewCell.innerView.isHidden = true
}
return collectionViewCell
}
}
extension ViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
for i in (0..<isExpanded.count) {
if i == indexPath.item {
// ensure always visible
isExpanded[i] = true
} else {
// set all other rows to false
isExpanded[i] = false
}
if let c = collectionView.cellForItem(at: IndexPath(item: i, section: 0)) as? CollectionViewCell {
c.innerView.isHidden = !isExpanded[i]
}
}
collectionView.performBatchUpdates(nil, completion: nil)
}
}
What I have tried so far is
Use a zero height constraint to activate/ deactivate, to replace isHidden
Use UIView.animate
But, the inner UIView will disappear immediately, without any height shrinking animation.
Do you have idea how I can fix this? Thanks.
Here's the code to illustrate the problem - https://github.com/yccheok/shop-dialog/tree/c399bca163096ad27de7de866af5d2de370a8afb
As I mentioned in a comment to you on another question, rarely do we find a "one size fits all" solution.
Instead of fighting with the default behaviors when setting .isHidden on a stack view's arranged subviews, here's a different approach.
Use two bottom constraints:
One from the bottom of the "top / always-visible" UI elements
One from the bottom of the "show/hide" elements (their container view)
Set the .priority of the second constraint to 750 (.defaultHigh).
When you want the cell to be "collapsed" set the .priority of the first constraint to 751 (.defaultHigh + 1).
When you want the cell to be "expanded" set the .priority of the first constraint to 749 (.defaultHigh - 1).
To animate the expand/collapse effect, wrap performBatchUpdates in a UIView.animate block.
I forked your GitHub repo here - https://github.com/DonMag/shop-dialog - and added this approach as "V2" so you can inspect it and see the differences.

How to save the status of a cell so when I scroll or leave the page it doesn't refresh the cells?

I'm trying to make a store for a game where you can buy different colors of balls. I'm using a UICollectionView with all white balls to begin with, when I click a cell, it changes the white ball image to a colored ball image (EDIT: an image from a pre made array of colored images). when I scroll down and scroll back up, the cells I selected are reset to the white ball image. I don't want this obviously.
I've tried using the method already built into the UICollectionView class with didSelectItemAt but when I scroll down and back up it gets all messed up (When i select a cell a different one's image is changed not the correct one). I've tried using isSelected in the collectionViewCell class but I can't get the indexpath in here so I can't save which cells are selected.
override var isSelected: Bool{
didSet{
if self.isSelected
{
textImage.image = images[indexPath.item] // I don't know what to put here I don't have the indexPath
}
else
{
textImage.image = #imageLiteral(resourceName: "circleWhite")
}
}
}
Any help is great, I am fairly new to coding in Xcode so some explanation of what to do here is very much appreciated.
EDIT: I have an array of images that should be the store, not just one different color, multiple colors. When I click on a cell, it should access the image in the corresponding index in the array and use that image to replace the white circle.
Did the same thing in our code.Below is the solution for this.
1.Take an array of tuple to maintain selected status and specific colors or what ever you want.
var arrColor = [(isSelected:Bool,color:UIColor)]()
2. Now do the below code on cellForItemAt.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let collectionViewCell = self.iconCollectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? EditDeviceCollectionViewCell else { return UICollectionViewCell() }
if arrColor[indexPath.item].isSelected{
arrColor[indexPath.item].color = .white
}else {
arrColor[indexPath.item].color = .black
}
return collectionViewCell
}
3.Now write the data source method and use below for color
//MARK:- UICollectionViewDelegate
extension yourViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
arrColor[indexPath.item].isSelected = true
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
arrColor[indexPath.item].isSelected = false.
}
}
Happy Coding 😊
When you scroll off the screen the cells get prepareForReuse called on them. What you need to do is store the state of the color somewhere else - like on the collectionView or a viewModel. And when cellForRow is called you pull the color to show for that row from the saved state variable.
Essentially what’s happening is the cells are being reused when they go off screen to save memory. So when you scroll back to them they are re-created often with the state of another cell since cells are reused.

UICollectionViewCell layout issues iOS9

Since my move to the new iOS9 and Xcode 7 I have stumbled upon an issue with one of the UICollectionView in my app.
Apparently, the UICollectionView doesn't seem to update the UICollectionViewCell layout and constraints properly, only until it is reused.
Pictures speak better than words -- this is how it looks like when the UIViewController is first seen:
However this isn't the correct layout, and easily enough when I swipe the UICollectionView horizontally to the left, I get the right layout of the newly appeared cells:
When I swipe back, the old cells that weren't correct, are now reused and look good.
Now, as it was prior to upgrading to iOS9 and Xcode 7, my wanted effect is that the cells have the correct layout even when first appearing.
For your convenience, here are more details on how the UICollectionView is set up and it's constraints in the XIB:
In the code, it is pretty standard:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell : MatchmakersCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionCell", forIndexPath: indexPath) as! MatchmakersCollectionViewCell
cell.imageView.backgroundColor = UIColor.lightGrayColor()
cell.bottomName.text = "StackOverflow"
return cell
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.matchmakers.count
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
And every time I updated the datasource (self.matchmakers), I called self.collectionView.reloadData()
One last thing I had noticed which was very strange, when debugging with Xcode the debug view hierarchy, the UICollectionViewCell never presented the subviews properly and just gave me a default UIView in their stead:
To solve this problem:
Creat your custom UICollectionViewCell using Nib(.xib) files.
In your custom UICollectionViewCell ,override didMoveToSuperView() method to add this
self.setNeedLayout()
self.layoutIfNeeded()
3.In your UIViewController viewDidLoad() method
self.collectionView.registerNib(UINib(nibName: "yourNibName", bundle: nil), forCellWithReuseIdentifier: "Cell")
--------update 20150923
just need step 2, override didMoveToSuperView method to add
self.setNeedLayout()
self.layoutIfNeeded()
in your custom UICollectionViewCell class.
Thanks #macayer
I didn't have to create my own XIB for my cell, as long as I have my custom cell and have it linked into my storyboard, I just added those line in (MyCustomCollectionViewCell.m):
- (void)didMoveToSuperview{
[super didMoveToSuperview];
[self setNeedsLayout];
[self layoutIfNeeded];
}
I had same bug in iOS9 too. Perhaps you've found that it's all ok after you reloaded the collection view. So my solution: set a timer for iOS9 to reload the cells.
if #available(iOS 9, *) {
self.refreshTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("refreshDataTimer"), userInfo: nil, repeats: true)
}
And reload them :
func refreshDataTimer(){
self.collectionView?.reloadData()
}
Even though this solution is little stupid but it works in few lines.
or you can reload each cell :
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
....
self.collectionView?.reloadItemsAtIndexPaths([indexPath])
return cell
}
But it's inefficient.
I had same bug in iOS9. I would like center x and center y image to UICollectionView Cell. It doesnt work, but now I added in storyboard Align Top and Align Leading, then I added outlets NSLayoutConstraint and in
(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath. I am changing NSLayoutConstraint depending on the resolution.
I am pretty positive this is an iOS9 bug. Instead of setting up the UICollectionViewCell UI and constraints in the UICollectionView in the Storyboard, I created it its own XIB. Then added to the UIViewController:
self.collectionView.registerNib(UINib(nibName: "MatchmakersCollectionViewCell", bundle: NSBundle.mainBundle()), forCellWithReuseIdentifier: "CollectionCell")
Now it works well.
I had one part of the issue the OP described...
One last thing I had noticed which was very strange, when debugging
with Xcode the debug view hierarchy, the UICollectionViewCell never
presented the subviews properly and just gave me a default UIView in
their stead
I spent ages bashing my head against a wall trying to work out what was up, none of the suggested answers made a difference. In the end I ended up deleting all the constraints in my cells XIB and re-adding them and it fixed the problem. I guess the constraints must've corrupted at some point.
Hope it might help someone.
In my case disabling size classes in .xib file did the trick.
Here is the appropriate Swift 2.0 code needed to solve this issue in Xcode 7. Note that the correct code is 'setNeedsLayout' and not 'SetNeedLayout' as stated above. Make sure to call it in the UICollectionViewCell file.
override func didMoveToSuperview() {
self.setNeedsLayout()
self.layoutIfNeeded()
}

didSelectItemAtIndexPath not called with multiple collection views on same screen

I have 2 collection views on the same screen, and I have the data source and delegate implemented for both in the same view controller. However, the delegate methods such as didSelectItemAtIndexPath is only called for one.
Other info:
Both collection views have a cell with an image.
AllowSelection and UserInteractionEnabled is set to true in both collection views
User interaction enabled on the images
Delegate and data source are set on the storyboard from both collection views to the view controller
Collection views are displayed properly
Both collection views have the same setup, yet only one delegate works. Would you have any clues what could be setting this up?
The collection views are inside a view that has a scroll view. Could this be somehow related?
EDIT 2:
Project with the same problem: https://github.com/iagomr/ProblemWithAutoLayout
EDIT 1:
Somehow this has to do with autolayout constraints, because if I pin the bottom collection view to the bottom of the screen instead of the bottom of other collection view, it starts working.
This is all due to the fact that I need to build a tall screen, and added everything into a view inside a scroll view 1000 points tall.
Code:
//MARK: - CollectionView Delegate
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("Called")
}
//MARK: - CollectionView DataSource
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == thisCV {
return 1
} else if collectionView == thisOtherCV{
return 1
}
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if collectionView == "thisCV" {
if let thisCell = collectionView.dequeueReusableCellWithReuseIdentifier("thisCell", forIndexPath: indexPath) as? thisCollectionViewCell {
thisCell.image = image
return thisCell
}
} else if collectionView == "thisOtherCV"{
if let thisOtherCell = collectionView.dequeueReusableCellWithReuseIdentifier("thisOtherCell", forIndexPath: indexPath) as? OtherCollectionViewCell {
thisOtherCell.image = image
return thisOtherCell
}
}
return UICollectionViewCell()
}
I can confirm that didSelectItem is not getting called. If constant for top-bottom constraint between two collection views is changed from 501 to 0 it is working.
This problem is most likely related to the fact that you have two scroll views (collection views) inside of another scroll view. Overall, I would say, that you should modify your UI. I would suggest two ways of fixing it
Using single collection view
Use just one collection view with different sections for different content. Also, do NOT embed it in the scroll view - collection view already has a scroll view so you should be able to scroll easily. You can also dequeue different class of cells for different sections so you should be able to do everything which you want to do now.
If you want a starting point, here is a good tutorial which should help you with that.
Using scroll view
If you want to setup your UI in Interface Builder remove both collection views and simply add all of your UI inside of scroll view. Place UIButton in places where you want clicking to produce action.
You can even assign the same action to each button and then differentiate which one was triggered by assigning custom tags to each of them.

SelectedBackgroundView of UICollectionViewCell visible when it should not be

I've got a UICollectionView. With some cells inside with a white background color. I've set the selectedBackgroundView to a basic purple view.
My CollectionView has a constraint with a height of 0 and when I hit a button I update the constraint to 80. When I'm doing that, during the animation i can see the purple background on the screen until the end on the animation and i cannot understand why or how prevent this ?
Everything else working fine, it's just a "visual" bug.
Any suggestion about how to fix this ?
Gif of the bug where you can see the purple appearing during the animation
Here is my cell construction if it can be of any help :
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("WidgetMenuCellIdentifier", forIndexPath: indexPath) as UICollectionViewCell
cell.removeSubviews()
// some code setup
cell.selectedBackgroundView = UIView()
cell.selectedBackgroundView.backgroundColor = UIColor.purpleColor()
return cell
}
Subclass your UICollectionViewCell
Do
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// your code
cell.selectedBackgroundView.hidden = true
return cell
}
Then in your subclass :
override var selected:Bool {
willSet {
self.selectedBackgroundView.hidden = false
}
}
It should work.
It seems like this code is being executed within an animation, causing unexpected behavior at times based on how various properties animate. Another complicating factor is that, because cells are reused, it won't reproduce if a reused cell is already configured correctly (i.e. there is nothing to animate). Adding the following after styling the selectedBackgroundView was the least hacky solution I could think of.
[cell.selectedBackgroundView.layer removeAllAnimations];
Depending on what your cells are like you may also want to consider removing animations on other layers as well. For example:
[cell.backgroundView.layer removeAllAnimations];

Resources