Crash at layoutAttributesForItemAtIndexPath with index out of bounds - uicollectionview

I do have a crash issue in my app where I have implemented custom layout collection view to show book pages in iPhone. When I do re ordering of pages (ex - if I want to pace the last to 1st place in the book) the app some times crashes in my custom layout code (layoutAttributesForItemAtIndexPath). Looking deeper into the code found that API is receiving in indexPath which is out of bounds. The row value is too big which is converting to -1 and since there will be no data at this location in my layout attributes array, the app crashes. This happens on iOS 9.x and not on 10.x.
Does anyone know why I am getting this crash.
PS: Stacktrace attached.

You have to implement layoutAttributesForItemAtIndexPath():
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cache[indexPath.item]
}
I found this solution here and it solved my problem

Related

Potential Bug in UICollectionview in iOS14

I am encountering a strange behaviour in the way iOS14 handles a CollectionView. I have noticed that (changing from iOS13.5 to iOS14) the order in which the UICollectionViewCells are displayed is wrong. The pictures below are screenshot from exactly the same code (in xCode12), the only difference is that the correct ones are with a build for iOS13.5 and from a simulator with an iPhone 11 on iOS13.5, while the wrong ones are with a build for iOS14 and from a simulator with an iPhone 11 on iOS14. Everything else (code, settings, etc) is exactly the same:
Picture 1, how the collection should display (correct in iOS13.5):
If the same code is built with iOS14 and run on the same iPhone simulator with iOS14, the result is the following (not the order is wrong: it is reversed):
In the datasource (which is an array with the labels) all is correct and the cellForItemAt delegate works correctly.
There is definitely something being handled differently in iOS14. One last example is another screenshot where the sizes of the boxes are different (depending on the length of the labels):
Correct (building for iOS13.5) is:
While building for iOS14 yields the following:
Again, the code is exactly the same, I am just building the project for different iOS versions. It seems to me there may be a bug somewhere in the way collections have been modified for iOS14. Any help or thought would be welcome.
I have found how to solve this, and I think it points to some changes in how the management of the delegates for UICollectionView in iOS14 may have taken place.
The solution is simple: I have just added
cell.setNeedsDisplay()
in
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath)
before returning the cell.
No other change to the code. I noticed that in iOS14 draw() for the cell was not called as in iOS13.5. Thus adding the setNeedsDisplay() forced the redraw of the cell, which in iOS13.5 was happening without the new line of code.
I would be interested to know if anybody knows of any change. Thanks

UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes: and viewWillAppear: order

a quick question for any of you who might have an idea:
I recently encounter a bug in on of my app, and that have raised a little question.
The bug was caused by a piece of code trying to access to an array not yet set.
The funny buisness here was that the same code worked absolutely fine on an iPad Air, and crashed on an iPad Pro.
Indeed, i was trying to access the array in the collectionView:cellForItemAtIndexPath: method of my controller, and the array was initialized in the viewWillAppear: method of the same controller.
In any device that i have tried, exepted on the iPad Pro, the collectionView:cellForItemAtIndexPath: method was always called after the viewWillAppear:, but on the iPad Pro, it is the other way.
I easily fixed the issue, but i'm still wondering why the iPad Pro have a different cycle than the other. Anyone have a clue about that?
(I'm on Objective-C, iOS 11.0)
Change the collectionView:cellForItemAtIndexPath method for conditions like following
if (array has data || data is downloading) {
show activity indicator
} else {
show data
}
Reload collection view when data loads

ios10: viewDidLoad frame width/height not initialized correctly

Since upgrading to XCode8 GM and ios10, all of my views created via Interface Builder are not being initialized correctly until much much later than expected. This means in viewDidLoad, cellForRowAtIndexPath, viewWillAppear, etc, the frame size is set to {1000,1000} for every view. At some point they seem to correct, but its far too late.
The first problem encountered is with common rounding of corners failing across the board:
view.layer.cornerRadius = view.frame.size.width/2
Further problems are showing for anything that relies on frame size to do calculations in the code.
cellForRowAtIndexPath
For cellForRowAtIndexPath, frame size fails on initial table display, but then works fine once you scroll it. willDisplayCell:forRowAtIndexPath does not have the correct frame size either.
I've hardcoded a few values but obviously this is very bad code practice, as well as quite numerous in my projects.
Is there a way or place to get correct frame sizes?
EDIT
I've discovered that using the height/width constraint instead of frame width height is more reliable. This may add the overhead of needing lot of new IBOutlets to link the height/width constraints on items though.
For now I've created a UIView category that lets me access a View's height/width constraints directly without the IBOutlets. For minimal use the small loop shouldn't be a big deal. Results not guaranteed for IB items without the width/height constraints created yet obviously. Probably returns 0 at best for the constant, or worse. Also, if you don't have a height/width constraint and your view is sized dynamically based on leading/trailing constraints, this won't work.
-viewDidLoad appears to have correct frame size, but will often result in a visual change to the UI if you do modifications here.
UIView+WidthHeightConstraints.h
#interface UIView (WidthHeightConstraints)
-(NSLayoutConstraint*)widthConstraint;
-(NSLayoutConstraint*)heightConstraint;
-(NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute;
#end
UIView+WidthHeightConstraints.m
#import "UIView+WidthHeightConstraints.h"
#implementation UIView (WidthHeightConstraints)
-(NSLayoutConstraint*)widthConstraint{
return [self constraintForAttribute:NSLayoutAttributeWidth];
}
-(NSLayoutConstraint*)heightConstraint {
return [self constraintForAttribute:NSLayoutAttributeHeight];
}
-(NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute {
NSLayoutConstraint *targetConstraint = nil;
for (NSLayoutConstraint *constraint in self.constraints) {
if (constraint.firstAttribute == attribute) {
targetConstraint = constraint;
break;
}
}
return targetConstraint;
}
#end
EDIT 2
The category above has proven only partially effective. Mainly because ios appears to auto add a couple extra height/width constraint duplicates, that are of type NSContentSizeLayoutConstraint, which are actually not the same size as the normal constraint. The NSContentSizeLayoutConstraint is also a private class so I can't do isKindOfClass to filter those out. I haven't found another way to effectively test for those yet. This is annoying.
The most common issues you describe are appearing in iOS 10 only and can be solved by adding this line (if necessary):
self.view.layoutIfNeeded()
just above the code, that is responsible for changing constraint, layer.cornerRadius etc.
OR
place your code related to frames / layers into viewDidLayoutSubviews() method:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
view.layer.cornerRadius = self.myView.frame.size.width/2
view.clipsToBounds = true
... etc
}
We created a radar (28342777 (marked as duplicate for 28221021 but Open)) for the similar problem and the reply that we got was as below:
"Thank you for reporting the issue. Could we get more information about the profile image view? In Xcode 8, a fully constraint, non-misplaced view no longer saves out a frame to minimize diffs and support automatically update frames in IB. At runtime, these views get decoded with a placeholder size of 1000x1000, but are resolved after first layout. Could the image be assigned before initial layout, and would assigning the image to the image view after first layout address this case? Please send a sample to help us further analyze. thanks!"
At present we have provided them the sample project. My observations:
The problem that we had used to happen for XIBs that are converted from Xcode 7.x to Xcode 8.x
If we intentionally break the constraint in XIB then viewDidLoad will get expected height and width and not 1000x1000.
For us it was a UIImageView on which we were apply some layering for making it circular and using masksToBounds. If we set masksToBounds = NO then we everything was working fine.
Though Apple claims that it is going to be a standard from Xcode 8 that views will be set to 1000x1000, the behavior doesn't seem to be consistent.
Hope this helps.
I encountered the same issue and try to solve it without luck by referring above suggestions.
Seems it should be a bug for Apple to solve. I finally find a solution by changing to save my XIB document back to Xcode 7.x format and my UI back to normal.
Until Apple releasing a fix, I don't want to spend my time on hacking it.
What about doing this:
- (NSLayoutConstraint*)widthConstraint{
return [self constraintForAttribute:NSLayoutAttributeWidth];
}
- (NSLayoutConstraint*)heightConstraint {
return [self constraintForAttribute:NSLayoutAttributeHeight];
}
- (NSLayoutConstraint*)constraintForAttribute:(NSLayoutAttribute)attribute {
NSLayoutConstraint *targetConstraint = nil;
for (NSLayoutConstraint *constraint in self.constraints) {
//NSLog(#"constraint: %#", constraint);
if (![constraint isKindOfClass:NSClassFromString(#"NSContentSizeLayoutConstraint")]) {
if (constraint.firstAttribute == attribute) {
targetConstraint = constraint;
break;
}
}
}
return targetConstraint;
}
You should never rely on the timing of when a view is layed out. If that worked for you before, then out of pure luck. There are very little guarantees about this in UIKit. If you rely on something adopting to the size of your view, the right thing to do is override layoutSubviews in that view and adjust your stuff there.
Even after your view is fully rendered on screen, there are still so many conditions that could cause the size of the view to change. For example: Double height status bar, multitasking on iPad, device rotation, just to name a few. So it never is a good idea to do frame related layout changes at a particular point in time.
I was having the exact same problem. I had custom UITableViewCell subclasses and was using clipsToBounds = YES and self.iconView.layer.cornerRadius = self.iconView.frame.size.width/2 to give myself a circular image. Tried calling my cell configuration method from cellForRowAtIndexPath and willDisplayCell and neither worked.
Here is what works:
Move your layering code into the cell's -layoutSubviews method like this:
-(void)layoutSubviews {
[super layoutSubviews];
self.iconView.clipsToBounds = YES;
self.iconView.layer.cornerRadius = self.iconView.frame.size.width/2;
}
After this the images should load properly and your layering code should also work.
Only Update frame in your autolayout box .

iOS8 how to understand interface orientation from UIViewControllerTransitionCoordinator?

I'm debugging a project that was working fine in iOS7.1, but does not lay out it's content properly in iOS 8.0. I've narrowed the issue down to this method:
[self orientRootViewControllerForOrientation:rootViewController.interfaceOrientation];
iOS8 no longer returns correct UIInterfaceOrientation from rootViewController.interfaceOrientation, instead it returns "upside down".
Reading the documentation, I'm confronted with a cryptic message:
Use viewWillTransitionToSize:withTransitionCoordinator: to make
interface-based adjustments.
Reading documentation on UIViewControllerTransitionCoordinator, I don't see it having any properties. How can I modify my method that expects an interface orientation to get it's orientation from UIViewControllerTransitionCoordinator?
Basically you are supposed now to layout your content based on the available size, no more on the rotation. Which results in big pains to support older rotation-based code.
The view controller transition coordinator provides a UIViewControllerTransitionCoordinatorContext that has a rotation factor. This could give you the hint you need ?
This blog post mentions this too.
You also have the usual device ([UIDevice currentDevice].orientation) or status bar orientation.
In case you are working on iOS 6 rotation methods, this post has relevant informations too.

Display waiting screen when user selects UITable row

When the user selects a row in my table, I need to load a bunch of data with CoreData. This takes several seconds (at least when running on the simulator - haven't tested on a device yet, but I imagine it will still be pretty long). I want to display a loading popover screen (I'm developing for iPad) when the user selects a row, then have it disappear once the data is loaded.
When working with UIButtons, I've always done this by triggering both a TouchDown and TouchUp method. I put the code to display the popover on the TouchDown, then do all my actual work (so in this case loading from CoreData) in the TouchUp. Then at the end of TouchUp, I close the popover.
Is there a way to split up touches in a table view the same way as ones on a button?
So while I was typing up the question I came across something in the Apple Docs for willSelectRowAtIndexPath and it looks like that is there to address this very issue. I didn't see any other topics on this on SO, so I figured I'd make use of this "Answer own question - Q&A" feature on here, so maybe people looking for this in the future will find it a little easier.
Huge FYI block:
I should note that willSelectRowAtIndexPath is actually supposed to return an NSIndexPath, unlike didSelectRowAtIndexPath (which returns void). However, I originally had
-(void)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//stuff
}
Note the void return type. Xcode (Version 4.5) allowed this without any errors or warnings (and even had it for me in the autocomplete list). When I ran the code this way, I always hit an invisible breakpoint in libobjc.A.dylib'objc_autorelease: (maybe becaues I have the "All Exceptions" breakpoint enabled?). I couldn't get past this breakpoint, I never got any debug information in the debugger output console, and the app never terminated. I happened to look at the Apple Doc again and notice that it was supposed to return an NSIndexPath (specifically, the index path the code should interpret as being the one that was selected), but if I didn't happen to see it there, I never would have known what the problem was.