App runs slower when collection subview has a size increase? - iphone

I am utilizing a UICollectionView for my universal app. Inside of each collection view cell I have a custom view, which ultimately hold's an image. Each cell is representing a playing card, so when tapped it flip's from front to back.The issue that I am having is when I scale up the size of that subview. If I have it smaller within the cell then everything run's fine. There are no slow down's of any kind. If I then scale up that subview to be larger then there are serious slowdown's. Not only while I scroll through the collection view up to down, but also when I tap the individual cell's. The lag happen's when a row of cell's comes from off screen. There is also lag when I tap a 'card' and it flip's from front to back.
This problem appear's to only exist on the iPad version of the app. When I run it on my iPhone there are no problem's at all in regards to lag.
Is there any reason as to why this would be happening?
Edit to add code:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Card" forIndexPath:indexPath];
Card *card = [self.game getCardFromPlayAtIndex:indexPath.item];
[self updateCell:cell usingCard:card withAnimation:NO];
return cell;
}
Abstract method that is used above:
- (void)updateCell:(UICollectionViewCell *)cell usingCard:(Card *)card withAnimation:(BOOL)isAnimated
{
// Using introspection to make sure that our cell is a PlayingCardCollectionViewCell
if ([cell isKindOfClass:[PlayingCardCollectionViewCell class]]) {
// If it is we an take that cell and cast it to a PCCVC and then pull out it's
// outlet for the PlayingCardView
PlayingCardView *playingCardView = ((PlayingCardCollectionViewCell *)cell).playingCardView;
if ([card isKindOfClass:[PlayingCard class]]) {
PlayingCard *playingCard = (PlayingCard *)card;
playingCardView.rank = playingCard.rank;
playingCardView.suit = playingCard.suit;
playingCardView.faceUp = playingCard.isFaceUp;
playingCardView.alpha = playingCard.isUnplayable ? 0.3 : 1.0;
}
}
}

Related

Having trouble with CollectionView in Xcode - display's black screen

Basically I've been writing iOS apps for about 3 days (after reading a book to get the basic knowledge of design patterns and frameworks etc) and 1 entire day has been spent on this. I cannot get my CollectionView to display. I've got a storyboard that starts with a TabBarView and the two tabs (one is a TableView and the other is just a UIView) work, but the third tab (UICollectionView) just displays a black screen even after setting up.
How I set it up:
1) dragged a ViewController to the storyboard
2) made a relationship segue between my UITabBarController to the new ViewController
3) dragged a UICollectionView to the new ViewController
4) dragged 4 UICollectionViewCell's to the CollectionView
5) dragged 4 UILabels into said CollectionViewCell's
6) made a connection between the new CollectionView's delegate & data source and the header file of my CollectionView class (this might be where I went wrong, I did not make the connection to my ViewController.h class as I used this one for my UITableView and thought a new class was necessary)
7) declared the following methods in my CollectionView.m file
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
//Populates each cell using the data given (no data in this case).
//Should return a CollectionViewCell.
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
return cell;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
//Gets the number of cells in each section.
//Should return an integer value.
return 2;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
//Tells the collection view the number of sections it should have.
//Should return an integer value.
return 4;
}
I get the feeling there is something I missed out, as this logic makes sense to me as a programmer but, having such little experience in Xcode, maybe I forgot to link up this class to my CollectionView or something. Does anybody know where I went wrong?
BTW in case you're wondering, I'm using this CollectionView more of a navigation technique than to display wonderful images or anything, I am going to populate them with generic images later when they actually show up in the emulator.
thanks
It is not displaying anything because default color of label is black, you'll have to change the color of text to any light color to see text over cell.
Now,
->Add a new file which should be subclass of UICollectionViewCell, Ctrl+drag from label to this class.(for eg I took mainViewCell)and label property name I took labelText
->Declare reuseIdentifier for cell from Attribute Inspector,(mainCell, I took)
->Import cellClass to your UIViewController Class,
->Using this method to display any text
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
mainViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"mainCell" forIndexPath:indexPath];
cell.labelText.text = #"What-ever-you-want-display";
return cell;
}
hope this would help...
Did you tell your view controller(the one that contains the collection view) to use UICollectionViewDelegateFlowLayout? For example,
#interface RootViewController () <UICollectionViewDelegateFlowLayout, UICollectionViewDataSource>
Also, have you set the reuse identifier for your cell? In Interface Builder set this in the attributes inspector. Then go to the cellForItemAtIndexPath: method and change this line to use your identifier.
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCell" forIndexPath:indexPath];
Check out UICollectionViewDelegateFlowLayout in the documentation for methods used to size cells and set margins and spacing.
The step 6) might be inadequate. Have you ctrl-dragged the collectionView to the controller icon beneath the main view directly? You would have been prompted in a black dialog box for connecting dataSource and delegate. The connections are made in XCode this way.
Alternatively you could explicitly set the dataSource as self in the controller implementation.
Remove registerClass code from the default view controller code snippet
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];

UICollectionView Custom Layout - Cells are not properly arranged

In my iPhone app,I want to use custom lay out in UICollectionview.There will different heights for Collection view cells.I want to set a lay out like that.
I tried many things and at last,made that height adjustment.(I am getting height for each object from server)
But the layout is not in a proper way.There are lots of spaces in between the items.
How can I achieve a proper layout.
Now I am getting a view like this
What I want is -
Here is my code -
-(CGFloat)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout heightForItemAtIndexPath:(NSIndexPath*)indexPath
{
return 60;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return array.count; // getting from server
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"CollectionCell" forIndexPath:indexPath];
cell.imageBG.image = [imagearray objectAtIndex:indexPath.item];
return cell;
}
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGSize retval;
// I'm taking height of each images into HeightArray
retval = CGSizeMake(100, [[HeightArray objectAtIndex:indexPath.item]floatValue ]+50);
return retval;
}
From your screenshots it seems you are trying to use standard UICollectionViewFlowLayout (or its subclass). Unfortunately its limitation is that each row will always occupy the same height - it will be the same as in highest cell in that row, so you it is not suitable for your task and you have to implement your own layout.
Check UICollectionViewWaterfallLayout class which, although has its limitations (supports only 1 section, no decoration views) provides layout you need and at least can be a good starting point if you won't be able to use it as it is now.

Subview of a reused table view cell is nil for a specific row only

I've been trying to resolve this issue for quite some time now and I still can't figure it out. What I have is a custom table view cell in story board. In the cell, I added 6 views and each view has an imageView subview. I set the views' tag so that I can access them later on. This table serves as a thumbnail view in my app. The problem is that, in a specific row, the last thumbnail container view no longer have the imageview as subview resulting to a crash.
Below is my code in setting up the images for the table. Any help would be greatly appreciated. Thank you!
NSString *CellIdentifier = #"ThumbnailCell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//loops through each thumbnails
for (int i=1; i<=kNumberOfThumbnails; i++)
{
//get index of current thumbnail- starting value is 0
int index=((indexPath.row *kNumberOfThumbnails)+i)-1;
NSLog(#"index %d",index);
//create an indexpath given the computed index and cell section
NSIndexPath *currentIndexPath=[NSIndexPath indexPathForRow:index inSection:indexPath.section];
//get number of sections
NSArray *sections = [self.fetchedResultsController sections];
NSInteger count=0;
id <NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:indexPath.section];
//get number of objects for given section
count = [sectionInfo numberOfObjects];
NSLog(#" i: %d number of subviews ni thumn container %d",i,thumbContainer.subviews.count);
//get view container for thumbnails
UIView *thumbContainer=(UIView *)[cell.contentView viewWithTag:i];
UIImageView *imageView=[thumbContainer.subviews objectAtIndex:0];// this is where the app crashes.. thumbContainer no longer have a subview (for a specific row only)so it throws out an nsrangeexception
if (index<count)
{
//get file using the created indexpath
File *imageFile=[self.fetchedResultsController objectAtIndexPath:currentIndexPath];
//set image
imageView.image=[UIImage imageNamed:imageFile.thumbnail];
thumbContainer.backgroundColor=[UIColor grayColor];
//set tag for image view for the system to know what file is tapped
imageView.tag=(currentIndexPath.section*kImageTagMultiplier)+currentIndexPath.row;
//add tap gesture to thumbnail container view
UITapGestureRecognizer *tap=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(resultTapped:)];
tap.numberOfTapsRequired=1;
[thumbContainer addGestureRecognizer:tap];
}
else {
imageView.image=[UIImage imageNamed:#""];
thumbContainer.backgroundColor=[UIColor clearColor];
for (UIGestureRecognizer *gest in thumbContainer.gestureRecognizers) {
[thumbContainer removeGestureRecognizer:gest];
}
}
return cell;
What I'm trying to do here is that I have an array of objects and each object is represented by a thumbnail image, i used a tableview to show these thumbnails.., for each row there is a fixed number of thumbnails. In the storyboard I have a cell that has 6 square views and each view has an imageview inside. The reason why i added a container view instead of just putting an imageview is that each thumbnails is tappable and i need to know what object is tapped by getting the tapgesture's view's(the container view) subview's(which is the imageview) tag.
By the way, I have put a comment where i encounter a crash
You should use the new iOS 6 UICollectionView as it is designed for things like this.

How to know the UITableview row number

I have a UITableViewCell with UISwitch as accessoryview of each cell. When I change the value of the switch in a cell, how can I know in which row the switch is? I need the row number in the switch value changed event.
Tags, subclasses, or view hierarchy navigation are too much work!. Do this in your action method:
CGPoint hitPoint = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *hitIndex = [self.tableView indexPathForRowAtPoint:hitPoint];
Works with any type of view, multi section tables, whatever you can throw at it - as long as the origin of your sender is within the cell's frame (thanks rob!), which will usually be the case.
And here it is in a UITableView Swift extension:
extension UITableView {
func indexPath(for view: UIView) -> IndexPath? {
let location = view.convert(CGPoint.zero, to: self)
return self.indexPathForRow(at: location)
}
}
If you set the tag property to the row number (as suggested by other answers), you have to update it every time in tableView:cellForRowAtIndexPath: (because a cell can be reused for different rows).
Instead, when you need the row number, you can walk up the superview chain from the UISwitch (or any other view) to the UITableViewCell, and then to the UITableView, and ask the table view for the index path of the cell:
static NSIndexPath *indexPathForView(UIView *view) {
while (view && ![view isKindOfClass:[UITableViewCell class]])
view = view.superview;
if (!view)
return nil;
UITableViewCell *cell = (UITableViewCell *)view;
while (view && ![view isKindOfClass:[UITableView class]])
view = view.superview;
if (!view)
return nil;
UITableView *tableView = (UITableView *)view;
return [tableView indexPathForCell:cell];
}
This doesn't require anything in tableView:cellForRowAtIndexPath:.
in cellForRowAtIndexPath:, set the tag property of your control to indexPath.row
Accepted solution is a clever hack.
However why do we need to use hitpoint if we can utilize already available tag property on UIView? You would say that tag can store only either row or section since its a single Int.
Well... Don't forget your roots guys (CS101).
A single Int can store two twice-smaller size integers.
And here is an extension for this:
extension Int {
public init(indexPath: IndexPath) {
var marshalledInt: UInt32 = 0xffffffff
let rowPiece = UInt16(indexPath.row)
let sectionPiece = UInt16(indexPath.section)
marshalledInt = marshalledInt & (UInt32(rowPiece) << 16)
marshalledInt = marshalledInt + UInt32(sectionPiece)
self.init(bitPattern: UInt(marshalledInt))
}
var indexPathRepresentation: IndexPath {
let section = self & 0x0000ffff
let pattern: UInt32 = 0xffff0000
let row = (UInt32(self) & pattern) >> 16
return IndexPath(row: Int(row), section: Int(section))
}
}
In your tableView(_:, cellForRowAt:) you can then:
cell.yourSwitch.tag = Int(indexPath: indexPath)
And then in the action handler you would can:
func didToogle(sender: UISwitch){
print(sender.tag.indexPathRepresentation)
}
However please note it's limitation: row and section need to be not larger then 65535. (UInt16.max)
I doubt your tableView's indexes will go that high but in case they do, challenge yourself and implement more efficient packing scheme. Say if we have a section very small, we don't need all 16 bits to represent a section. We can have our int layout like:
{section area length}{all remaining}[4 BITS: section area length - 1]
that is our 4 LSBs indicate the length of section area - 1, given that we allocate at least 1 bit for a section. Thus in case of our section is 0, the row can occupy up to 27 bits ([1][27][4]), which definitely should be enough.
I prefer using subviews, if you know your layout it's generally super simple and 1 line short...
NSIndexPath *indexPath = [tableView indexPathForCell:(UITableViewCell *)[[sender superview] superview]];
Thats it, if its more nested, add in more superviews.
Bit more info:
all you are doing is asking for the parent view and its parent view which is the cell. Then you are asking your tableview for the indexpath of that cell you just got.
One common way to do this is to set the tag of the control (in your case the switch) to something that can be used to identify the row or represented object.
For example, in tableView:cellForRowAtIndexPath: set the tag property of the switch to the indexPath.row and in your action method you can get the tag from the sender.
Personally, I don't like this approach and prefer subclassing UITableViewCell.
Also, it may be a good idea to add an "offset" to the tag to prevent any conflicts with the tags of other views.
The accepted answer on this post is perfectly fine. I'd like to suggest to readers that the following, derived from #robmayoff on this post, is also perfectly fine:
- (NSIndexPath *)indexPathForView:(UIView *)view inTableView:(UITableView *)tableView {
while (view && ![view isKindOfClass:[UITableViewCell class]])
view = view.superview;
UITableViewCell *cell = (UITableViewCell *)view;
return [tableView indexPathForCell:cell];
}
Some have asserted that this approach contains too much computational work because of the while loop. The alternative, convert the view's origin to table view coordinate space and call indexPathForRowAtPoint:, hides even more work.
Some have asserted that this approach is unsafe relative to potential SDK changes. In fact, Apple has already changed the tableview cell hierarchy once, adding a contentView to the cell. This approach works before and after such a change. As long as view ancestors can be found via a chain of superviews (which is as fundamental as anything in UIKit), this is good code.
A colleague suggested the following, which I made into a UITableView category:
+(UITableViewCell*)findParentCellForSubview:(UIView*)view
{
while (([view isKindOfClass:[UITableViewCell class]] == NO) && ([view superview] != nil))
view = [view superview];
if ([view superview] != nil)
return (UITableViewCell*)view;
return nil;
}
Still hackly - but it works.
One more variant of using superView. Works like category for UIView.
- (UITableViewCell *)superCell
{
if (!self.superview) {
return nil;
}
if ([self.superview isKindOfClass:[UITableViewCell class]]) {
return (UITableViewCell *)self.superview;
}
return [self.superview superCell];
}
i dont know about the multiple sections but i can give you for the one section...
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger index=indexPath.row;
NSString *string=[[NSString alloc]initWithFormat:#"%ld",(long)index];
}
from this you can get the row number and you can save it to the string....

When an UITableView is empty, show an UIImage

This is related to another question of mine which wasn't answered in a helpful way (message when a UITableView is empty).
I'm trying to show an UIImage graphic that says You haven't saved any bookmarks over an UITableView when it's empty. I have NSNotification set-up so that when bookmarks are added or deleted, a message is sent so that the UITableView can be updated.
I've been trying to do it with this code. Why won't this work?
- (void)bookmarksChanged:(NSNotification*)notification
{
[self.tableView reloadData];
UIImageView* emptyBookmarks = [[UIImageView alloc] initWithFrame:CGRectMake(75, 100, 160, 57)];
emptyBookmarks.alpha = 1;
emptyBookmarks.image = [UIImage imageNamed:#"emptyBookmark.png"];
[self.view addSubview:emptyBookmarks];
[emptyBookmarks release];
if ([self.dataModel bookmarksCount] == 0)
{
emptyBookmarks.alpha = 1;
}
else
{
emptyBookmarks.alpha = 0;
}
}
I'm probably approaching this the wrong way... But if salvageable, what am I doing wrong?
When I initially have an empty bookmarks tableview, there's no image displayed. After I add a bookmark and then delete it, the image shows. Grrh.
Another way (and IMO the correct way) to do this is to manipulate the backgroundView property on the UITableView.
While making a single cell with a custom image cell would certainly works, I think it overly complicates the logic of your UITableViewController's data source. It feels like a kludge.
According to UITableView documentation:
A table view’s background view is automatically resized to match the
size of the table view. This view is placed as a subview of the table
view behind all cells , header views, and footer views.
Assigning an opaque view to this property obscures the background color
set on the table view itself.
While you probably don't want to just set it to your UIImageView, it is very easy to make a UIView that contains the UIImageView that you want.
Well first off if you were going to do it that way, you would need to reload the tableView after updating the image or model etc. and not before.
But you are probably making things more complicated than they need to be!
Why not just check to see if the data for section 0 and indexPath.row 0 are empty and if so in cellForRowAtIndexPath display a text message accordingly.
// First make sure there is always one row returned even if the dataModel is empty.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger numRows = 0;
if ([self.dataModel lastObject]) {
// Return the number of rows in the section.
numRows = [self.dataModel count]; // etc.
}
if (numRows < 1) numRows = 1;
return numRows;
}
// Then display the data if there is some, otherwise a message if empty.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
if ([self.dataModel lastObject]) {
// setup the cell the normal way here.
} else { // the datasource is empty - print a message
cell.textLabel.text = nil;
cell.detailTextLabel.text = NSLocalizedString(#"You haven't saved any bookmarks", #"");
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.7];
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Are you sure [self.dataModel bookmarksCount] is equal to 0 ?
While I agree that you are probably going about this the wrong way,
your image is allocated and added in your bookmark changed, your notification does not trigger when there are no bookmarks initially. Hence you don't see the image. Call the bookmar changed when your table view inits or appears.
Probably the best way to achieve this is to perform a check in your numberOfRowsInSection method to return 1 if your data source is empty. Then in cellForRowAtIndexPath check if your data source is empty and if it is, create a custom cell that contains whatever you want. In heightForRowAtIndexPath you need to return your custom cell height if your datasource is empty, but only if you want the cell larger than the default. At least that is how I would approach it.
when bookmarks count is nil add one to your row method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
int c;
c = bookmarks.count;
if(c == 0){
c = 1;
}
return c;
}
and then the same check again in your cellforrowatindexpath.
Another thing to be aware of in this situation is that if you're using core data and you're datasource is feeding off an entity, you will want to make sure your model matches. You can get some weird side-effect behavior in certain situations. This is especially true if you allow editing and core data has an empty model but you're tableview is still showing a cell.