UICollectionViewFlowLayout with self sizing cells crash after reloadData - crash

I have a custom UICollectionViewFlowLayout with non-zero estimatedItemSize for cells. I add and remove cells inside the collectionView reloading the data and sometimes I get a crash when I try to scroll to a cell that has just been removed/added.
Using backtrace tool, I get the following result:
frame #9: 0x0000000185c8385c CoreFoundation-[__NSArrayM
objectAtIndex:] + 264 frame #10: 0x000000018aeabb18
UIKit-[_UIFlowLayoutSection setSize:forItemAtIndexPath:] + 708
frame #11: 0x000000018ae7cc5c UIKit-[UICollectionViewFlowLayout invalidationContextForPreferredLayoutAttributes:withOriginalAttributes:]
+ 156
frame #12: 0x000000018ae646c4 UIKit-[UICollectionView _checkForPreferredAttributesInView:originalAttributes:] + 212
frame #13: 0x000000018a844968 UIKit-[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:]
+ 776
I have a guess that my flow layout could cache the cells.
UPDATE:
My issue appears on iOS 8.3, but not on previous versions, 8.2 included.

Don't use self-sizing cell with non-zero estimatedItemSize. It's a trap.
Implement a UICollectionViewDelegateFlowLayout's method collectionView(collectionView:collectionViewLayout:indexPath:) where you calculate the size of the cell by calling systemLayoutSizeFittingSize on a custom instance of the cell.
You can find more detailed info in this question: UICollectionView - dynamic cell height?

Related

Unable to simultaneously satisfy constraints - UIScrollViewAutomaticContentSizeConstraint

I recently added constraints to my prototype cell on a uitableview view and have run in to this warning when running my application (no errors but see this exception in the console log). I am adding objects to a mutable array dynamically based on specific conditions and inserting to the top of my uitableview. The insert rows also have variable heights.
The issue doesn't occur when I initialize an array with predetermined value, only when I add objects dynamically and push new view controllers.
I understand uitableview contains a scroll view and I have constrained the uitableview, but for some reason the error below says I have a scroll view constraint issue. I am not quite sure what it means.
All of my constraints were created using storyboard on Xcode 5. Dynamically changing height or the cell is done programmatically.
<_UIScrollViewAutomaticContentSizeConstraint:0x17597a10 UITableView:0x18416000.contentHeight{id: 79} == -110.000000>"
)
Will attempt to recover by breaking constraint
<_UIScrollViewAutomaticContentSizeConstraint:0x17597a10 UITableView:0x18416000.contentHeight{id: 79} == -110.000000>
I had a similar error. I my case I solved it by returning UITableViewAutomaticDimension for the estimated row height, instead of a fixed number:
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
I found the solution here: Autolayout Error with UITableView
Same error with same circumstances, when i changed an entry of the MutableArray (_detailColumns). The error only appeared once, not at following changes!
I had included the Update in
[self.tableView beginUpdates];
[[_detailColumns objectAtIndex:tag]setValue:textViewText forKey:#"content"];
[self.tableView endUpdates];
After removing beginUpdates and endUpdates, the message was gone (?!)

UItextview not expanding with constraints as expected

I am working with interface builder to create a xib. This xib has a uiview that contains a uitextview. Both are supposed to resize as the text in the uitextview changes. The constraints look a lot like this:
The pink UITextView pushes on the blue superview. The blue uiView has a minimum width of 189 px and a trailing constraint of at least 8px.
For the most part this works. Really long sections of text resize the two views to the fullest extent allowed as intended and if there are only one or two words, the views stay small. However, the problem is when you have a short sentence.
In this case, the views only expand to about 189px, and the text moves to the second line even though there is space to expand.
Here is what it looks like when you only put a few words in:
and here is a fully expanded box:
I have tried to make the trailing constraint have a lower priority than the others, and I have tried modifying the content hugging and compression resistance properties in many ways without success.
How can I make the views expand so that they fit the text content with the fewest number of lines? There are no restrictions on height, only on width.
Any help would be appreciated!
Get the new size of the textView using this method
CGSize sz = [_textView.text sizeWithFont:_textView.font]
in case that didn't work very well with the height,get the width you just got from the preivous method and use in the next method to get the appropriate height you need
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString *)text andWidth:(CGFloat)width
{
UITextView *textView = [[UITextView alloc] init];
[textView performSelectorOnMainThread:#selector(setAttributedText:)
withObject:text
waitUntilDone:YES];
CGSize size = [textView sizeThatFits:CGSizeMake(width, FLT_MAX)];
return size.height;
}
The key is to set the preferredMaxLayoutWidth to something big (320px sounds good. Your right constraint is going to limit it anyways).
You can do that in your code as
[self.label setPreferredMaxLayoutWidth:320];
Or from the Interface Builder as follows:
This way you'll see the label expanding as expected:
Have you tried the solution presented in this post?
Dynamic expand UITextView on ios 7
I believe you have to set your UITextView to sizeToFit
[YourUITextView sizeToFit];

How to accomplish resizing NSTableCellView based on the size of its NSTextField?

I know that in a view-based table view, the row class NSTableCellView subclasses from NSView. This class contains two properties, an NSTextField and an NSImageView. I am only using the NSTextField without an image view. However, some cells in my table view must contain multiple lines of text, while others may only contain one or two lines. I need to be able to resize individual NSTableCellView views depending on the size of their NSTextField textField property.
Therefore, I needed to do the following:
Get the frameSize of the NSTextField in the table cell view.
Set the frameSize of the NSTableCellView to the frameSize of the NSTextField (the one we got in set one)
However, this approach hasn't been working. I have begun to think that my approach to resize the NSTableCellView is incorrect. Here is the code that I have been using:
[tableCellView setFrameSize:[[tableCellView textField] frame].size];
[tableCellView setNeedsDisplay:YES];
Is there a problem with this approach? I would expect the cell to resize, but it doesn't? What is going wrong?
Thanks.
[edit] I should have started by commenting that the size of the textField has little to do with how large it would need to be to display all of its content.
I use this code to determine the height of a string based on the width of a table cell:
- (CGFloat) displayStringHeightWithWidth:(CGFloat)width
{
CGSize size = NSMakeSize(width,0);
NSRect bounds = [self.displayString boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading];
return bounds.size.height;
}
Ideally you can adapt that to finding the height of the textField.stringValue or textField.attributedStringValue. Not that the above is also from OSX, not iOS, so YMMV on some of the fluff.
So that changes your algorithm to:
Get the width of the table column
Get the height of the required bounding rect for the textfield's text
Tell the tableView that the row height is whatever you found in 2
Now. Regarding #3. I believe that you have to use the tableView:heightOfRow: in NSTableViewDelegate protocol as well as call the table's noteHeightOfRowsWithIndexesChanged: to have row heights change. The tableView's not otherwise aware that the height of your cell has changed. Note the discussion in the documentation. It could be your method would work without the delegate and just telling the table that the row heights for the rows that you are changing are dirty... but I wouldn't really expect it.

Selecting the Tag of NSSegmentedControl

I'm attempting to implement an NSSegmentedControl button in my IB.
I have it connected to - (IBAction)editCart:(id)sender;
Also, it is connected to NSSegmentedControl *editCartButton;
The first "segment" is a "-" button to decrease the cart value.
The second "segment" is a "+" button to increase the cart value.
When I attempt to use the "sender" value like so: [sender selectedSegment], I get an error:
-[NSTableView selectedSegment]: unrecognized selector sent to instance 0x100622aa0
My button is located inside an NSTableView.
I've also tried: [[editCartButton cell] selectedTag]
When I run it through my conditions, it always returns a value of (null).
I would like to retrieve the specific tags of 0 and 1 I'm expecting to get, but can't find the right actions.
Help appreciated greatly, thanks.
This:
-[NSTableView selectedSegment]: unrecognized selector
sent to instance 0x100622aa0
basically tells you that the sender is not the NSSegmentedControl you think it is. The sender is an NSTableView. So either you wired things up the wrong way, or you have a severe memory management problem where the NSSegmentedControl is deallocated, and an NSTableView is currently to be found at its memory location.
In -(IBAction)editCart:(id)sender you could add a line:
NSLog(#"editCart, sender = %#",sender);
to confirm this. You can drop NSLog lines like this in other places in your code, to verify your ideas about what should be happening.
in IBAction try replacing (id) with (UISegmentedControl *)
I had a similar problem when working on updating Seashore (A port of GIMP to native OS X APIs).
First, you have to get NSSegmentedControl's cell object:
NSSegmentedControl *segControl = ...
NSSegmentedCell *segCell = [segControl cell];
Then, you set the tag for the segment you want to modify:
[segCell setTag:200 forSegment:2];
More info is available in Apple's documentation.

How do I make sense of the error message in objective c?

I know I have a bug somewhere... I a very simple bug indeed, it's just an index out of bounds error. I used to hunt and place breakpoints in my project to see where it is. But today I decided to be smart and to actually learn to read the error message, because I know it's all there! But I don't know how to read it, I tried googling and none seems to give me a good explanation!
Can anyone help me read this error message?
2011-02-01 00:28:04.952 FBDiary[44083:207] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSMutableArray objectAtIndex:]: index 26 beyond bounds [0 .. 25]'
*** Call stack at first throw:
(
0 CoreFoundation 0x011dabe9 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x0132f5c2 objc_exception_throw + 47
2 CoreFoundation 0x011d06e5 -[__NSArrayM objectAtIndex:] + 261
3 FBDiary 0x00007035 -[FBFriendsList tableView:heightForRowAtIndexPath:] + 101
4 UIKit 0x004a3334 -[UISectionRowData refreshWithSection:tableView:tableViewRowData:] + 2888
5 UIKit 0x004a1e8b -[UITableViewRowData(UITableViewRowDataPrivate) _ensureSectionOffsetIsValidForSection:] + 152
6 UIKit 0x004a0a0c -[UITableViewRowData numberOfRows] + 145
7 UIKit 0x003578c2 -[UITableView noteNumberOfRowsChanged] + 132
8 UIKit 0x003642b8 -[UITableView reloadData] + 773
9 UIKit 0x04e5d1f0 -[UITableViewAccessibility(Accessibility) reloadData] + 60
EDIT: So a few minutes after posting the question I found the bug... As you said it's in the tableView:heightForRowAtIndexPath: function. However what I really wanted to know is, can I infer the line where it's causing problem from this stack trace?
there's not much to read. and as usual the stacktrace alone won't help you to fix the bug.
For this bug you need understanding of the UITableViewDatasource protocol.
the error is caused by -[FBFriendsList tableView:heightForRowAtIndexPath:], where you most likely try to access an element of the data array because you want to return the corresponding height.
So combine your knowledge about UITableViewDatasource with your exception.
Your knowledge about the datasource should tell you that a tableview asks for heightForRowAtIndexPath for all cells currently visible.
It should also tell you that you've told the tableview how many rows it should access.
If you combine this knowledge and your exception you should come to the conclusion that the value you return in - (NSInteger)tableView:(UITableView *)tableView_ numberOfRowsInSection:(NSInteger)section is wrong, and most likely it's 26 where it should be 25.
This is the most common bug that happens with UITableView.
The interesting part is [FBFriendsList tableView:heightForRowAtIndexPath:] which is the (one and only) part of your code wich is part of this stack trace, so you have to look there to find the error.
The lines above are the access to the array and the resulting progress of throwing the exception. The lines below are UIKit's base classes that "host" your application code and are responsible for calling the right parts at the right time.
It looks like the callback for the FBFriendsList height for row is passing an NSIndexPath that is out of bounds of your array. Where ever you have your FBFriendsList you need to put a breakpoint and check the number of items in your array and also check the NSIndexPath that is getting passed.