Query about UICollectionView - ios

First of all, sorry for the silly question, but i am new to collectionview and little confuse too.
I am working on UICollectionView. I am passing image to it from my plist file. I have 6 to 8 items of array, and every array has 6 items(NSString).
I am using this code..
static NSString *identifier = #"Cell";
CollectionViewCell *cell = (CollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
NSArray *temp = [list objectAtIndex:indexPath.item];
UIImage *cellImage;
cellImage = [UIImage imageNamed:[temp objectAtIndex:2]];
cell.imageView.image = cellImage;
Now, I want to change index to objectAtIndex:5 when i get flag 1 or 0, and it should be for perticular item.
UIImage *cellImage;
if (myInt == 1) {
cellImage = [UIImage imageNamed:[temp objectAtIndex:2]];
}
else{
cellImage = [UIImage imageNamed:[temp objectAtIndex:5]];
}
cell.imageView.image = cellImage;
If I am doing this then either i get index 2 or index 5 for every item. Is it possible to get index 2 for some item and index 5 for some item based on flag ?? or Is there any other way to do so ?
Any suggestions will be appreciated. Thank you.
Edit :
My collectionview has 3 images(items) in a row. If I tap any icon, it will redirect to the new view to read some documents. There is a done button in that view on bar, which gives a flag value (0/1). Now if I get flag 1 then I want to change collectionview icon for that case only. But here, By this given code all icon replaced by index 5, but I want only first icon from index 5 and other 2 icon from index 2. like Just to differentiate that first doc is reviewed.
sorry if it confuse.

I don't think you want to use myInt that way in this situation. Instead you want to create an array in your collection view controller to keep track of any items that were selected. So, if you had a property called reviewedDocs (pointing to an NSMutableArray), you would add the indexPath of the cell you selected.
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self.reviewedDocs addObject:indexPath];
}
Then, in your cellForItemAtIndexPath: method, you would select the image based on whether the current indexPath matches one in the reviewedDocs array.
UIImage *cellImage;
if ([self.reviewedDocs containsObject:indexPath]) {
cellImage = [UIImage imageNamed:[temp objectAtIndex:5]];
}
else{
cellImage = [UIImage imageNamed:[temp objectAtIndex:2]];
}
The image won't change until you come back to the collection view controller, and you reloadData. I wasn't sure what you're using the myInt flag for, but if, for example, you had a done button and a cancel button, you might return 1 or 0 respectively for those two actions. In the viewDidAppear method of you collection view controller, you could then remove the last object from reviewedDocs, if myInt is 0, indicating that the user just cancelled out rather than reviewing the doc (and then call reloadData on the collection view).

Related

iOS Adding dynamic subview into uitableviewcell

I am creating UITableViewCell like this. In that screen, I might have 1 quiz, 2 quiz, etc and 1 poll, 2 polls, etc. It will be dynamic.
As a result, when user scroll up and down, based on data received on my cell, I keep removing previous UIViews and recreating again and again. (I know it is super bad. Now my scrolling got issue.)
NSArray *quizzez = self.cellData[SERVER_QUIZZES];
NSArray *polls = self.cellData[SERVER_POLLS];
NSMutableArray *combinedQuizPoll = [NSMutableArray array];
[combinedQuizPoll addObjectsFromArray:quizzez];
[combinedQuizPoll addObjectsFromArray:polls];
for (UIView *vw in self.quizPollViewCollection) {
[vw removeFromSuperview];
}
for (NSDictionary *quizPollDict in combinedQuizPoll)
{
QuizPollSubView *vwQuizPoll = [QuizPollSubView loadFromNibWithType:QuizPollSubViewNoViewRelated andNavType:self.navType];
[vwQuizPoll setW:CGRectGetWidth(self.frame)];
[vwQuizPoll setDelegate:self];
[vwQuizPoll setData:muQuizPollDict];
[vwQuizPoll setX:0 andY:offset];
[self.contentView addSubview:vwQuizPoll];
offset = CGRectGetMaxY(vwQuizPoll.frame) + 4;
[self.quizPollViewCollection addObject:vwQuizPoll];
}
How shall I make to improve performance? I have studied other similar question in StackOverflow also.
How to make a UITableViewCell with different subviews reusable?
1) I need to have dynamic quiz, poll view (number of quiz, poll will be different for each cell)
2) How can I reference to those view that I created?
First of all I have to say that your approach to use the same cell to put things in a vertical is not the best one. For this kind of situation you should use more than one cell. Something like:
...
DecriptionCell
QuizCell
QuizCell
PollCell
PollCell
PollCell
...
Anyway I'm going to propose you a solution that could help you without change the structure of your UITableView.
Actually I had the same problem a few weeks ago, and I found a very good solution for that.
Basically the main concept is, to Reuse UITableViewCell you shouldn't add or remove views in the configure of the cell because the performance will be affected.
So, the solution that I have used was, use different reuse identifier for each kind of configuration that the cell can have.
The unique requirement is not to have a Nib file for the cell.
If I understood properly your cell can have dynamics Quizs and Polls. Let's go to say that a maximum of 10 Quizs and a Maximum of 10 Polls. Although I'm watching that both have the same View, QuizPollSubView. So let's put a maximum of 20 subviews per cell.
So in the method where you are registering the cells I would do the next:
Class myClass = [CustomTableViewCell class];
NSString *classID = NSStringFromClass(myClass);
for (NSUInteger index = 0; index < 20; index++) {
NSString *identifier = [classID stringByAppendingString:[#(index) stringValue]];
[self.tableView registerClass:myClass forCellReuseIdentifier:identifier];
}
Then in the CellForRow you must dequeue the cell with the properIdentifier, for instance:
NSString *cellID = NSStringFromClass([CustomTableViewCell class]);
NSUInteger numberOfQuizsAndPolls = 3 + 2; //This is 3 quizs and 2 polls, I gess that you can read from the DataModel
NSString *identifier = [cellID stringByAppendingString:[#(numberOfQuizsAndPolls) stringValue]];
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
//then configure the cell
Next, in the initWithStyle:reuseIdentifier: you should create the subviews with empty value, extracting the info from the identifier
NSString *stringNumber = [reuseIdentifier stringByReplacingOccurrencesOfString:NSStringFromClass([self class])
withString:#""];
NSUInteger numberOfSubviews = [stringNumber integerValue];
//here you should add all of your QuizPollSubView with emtpy content.
for (NSUInteger index = 0; index < numberOfSubviews; index++) {
QuizPollSubView *vwQuizPoll = [QuizPollSubView loadFromNibWithType:QuizPollSubViewNoViewRelated andNavType:self.navType];
[vwQuizPoll setW:CGRectGetWidth(self.frame)];
[vwQuizPoll setDelegate:self];
//[vwQuizPoll setData:muQuizPollDict]; YOU CAN NOT SET THE DATA HERE BECAUSE YOU DONT HAVE IT
[vwQuizPoll setX:0 andY:offset];
[self.contentView addSubview:vwQuizPoll];
offset = CGRectGetMaxY(vwQuizPoll.frame) + 4;
[self.quizPollViewCollection addObject:vwQuizPoll];
}
Finally you must to set the proper information in the configure of the cell. Something like:
- (void)configureWithQuizPollDict:(NSDictionary *)combinedQuizPoll
{
for (NSDictionary *quizPollDict in combinedQuizPoll)
{
//get the proper index in the quizPollViewCollection.
QuizPollSubView *vwQuizPoll = self.quizPollViewCollection[index];
[vwQuizPoll setData:muQuizPollDict];
}
}
I hope that it helps you!!
Thanks
PD: If you want to use a Cell with Nib probably we need to subclass the UITableView to add custom dequeue

UITableView thumbnail image performance

My image data is being loaded from core data into a UITableView cell. These images are automatically scaled by the OS (as far as I know) in cellForRowAtIndexPath:indexPath:. Not surprisingly, this causes a lot of lag while scrolling through the table view.
Similarly, I have a UICollectionViewController that loads all the same images into a collection view similar to the iOS Photos app and again, the images are scaled in cellForItemAtIndexPath:indexPath. Scrolling is laggy, but it also takes a very long time for the VC to load.
What is the best way to optimize performance in this scenario?
I've done some research and have come up with a couple possible solutions:
Initialize a "thumbnailArray" in viewDidLoad:animated:, scaling all the images I need before the table/collection view is loaded, then use this new array as the data source for the views. I figure this will solve the scrolling issue, but not the collection view loading issue.
Create new properties for thumbnail data in my image wrapper class. This data would be created when the image wrapper object is created (i.e. when the user adds an image) and saved in core data. I think this would be preferred over option #1.
Is option two the best way to go, or is there a better solution I am unaware of?
Here are my cellForRowAtIndexPath:indexPath and cellForItemAtIndexPath:indexPath: methods:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CMAEntryTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"entriesCell" forIndexPath:indexPath];
CMAEntry *entry = [self.entries objectAtIndex:indexPath.item];
cell.speciesLabel.text = [entry.fishSpecies name];
cell.dateLabel.text = [self.dateFormatter stringFromDate:entry.date];
if (entry.location)
cell.locationLabel.text = [entry locationAsString];
else
cell.locationLabel.text = #"No Location";
if ([entry.images count] > 0)
cell.thumbImage.image = [[entry.images objectAtIndex:0] dataAsUIImage];
else
cell.thumbImage.image = [UIImage imageNamed:#"no_image.png"];
if (indexPath.item % 2 == 0)
[cell setBackgroundColor:CELL_COLOR_DARK];
else
[cell setBackgroundColor:CELL_COLOR_LIGHT];
return cell;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"thumbnailCell" forIndexPath:indexPath];
UIImageView *imageView = (UIImageView *)[cell viewWithTag:100];
[imageView setImage:[[self.imagesArray objectAtIndex:indexPath.item] dataAsUIImage]];
return cell;
}
Thanks in advance!
Cohen
Thanks for your input, guys, but this is the solution that worked really well for me. The only thing I don't like is that now I store an NSData instance of the full image, and two different sized thumbnail images in core data; however, that doesn't seem to be a problem.
What I did was add a couple attributes to my NSManagedObject subclass to store the thumbnail images. The thumbnail data is initialized when the image is selected by the user, then saved in core data along with the original image.
Then, I load the thumbnails into a collection asynchronously in the view controllers I need them.
Works great. Gets rid of all issues I was experiencing.
1 I think the best approach is to load images in a background thread so the tableview loads quickly.
2 Also you can use coredata feature of batchsize to load only necessary data.
3 Perhaps using some type of cache in memory for the images may help
In one of my project i faced same kind of issue and i solved it by using AsyncImageView for loading the thumbimage for my tableview cell.It will load the image asynchronously.
#property (nonatomic,strong)AsyncImageView * thumbImageView;
self.thumbImageView =[[AsyncImageView alloc] init];
cell.thumbImageView.imageURL =[NSURL fileURLWithPath:UrlString];

Reused cell has UIView from previous cell

I have a tableview in my view-controller and each tableview cell is associated with an audio file. I'm using custom tableview cells that have a UIView inside. The UIView for each cell has a unique UIBezierPath that is stroked in blue. When the cell is tapped, didSelectRowForIndexPath initiates playing the audio file and the blue UIBezierPath is stroked over with a red UIBezierPath in relation to the progress of the audio file.
float progress = playTime/duration;
self.redWave.strokeEnd = self.progress;
This is working just fine. What I noticed is that when I tap the first cell, the audio will start playing and the red path will begin to get stroked - which is suppose to happen, but if I scroll down while this is happening, the 5th cell down from the one I tapped has the same UIBezierPath and it is also stroking the red path over the blue path! Its clear that the cell is being reused. Also, this isn't just happening when I tap a cell - the UIBezierPath's of the first 5 cells are replicated in the next set of 5 cells by default when my tableview loads. I don't know how to get around this. Any ideas?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* identifier = #"audioTableCell";
OSAudioTableCell *cell = [self.audioTable dequeueReusableCellWithIdentifier:identifier];
if(cell == nil)
{
cell = [[OSAudioTableCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: identifier];
}
//Pull data from NSFetchResultsController
Recording * recording = [self.managedDocument.frc objectAtIndexPath:indexPath];
cell.waveView.minAmpl = [recording.minAmpl floatValue];
//UIBezierPath Data
NSData * pathData = recording.waveData;
//set path for UIView (waveView is custom UIView class)
cell.waveView.path = [NSKeyedUnarchiver unarchiveObjectWithData:pathData];
[cell setImageSize:cell.waveView.bounds.size];
cell.tag = indexPath.row;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
//tableCell textfield
NSString * track = #"Track";
NSInteger row = indexPath.row;
row = [self.managedDocument.frc.fetchedObjects count] - row;
track = [track stringByAppendingFormat:#" %li",(long)row];
cell.trackLabel.text = track;
[cell.textField setFont:[UIFont fontWithName:#"Raleway-SemiBold" size:12]];
cell.textField.delegate = self;
if([recording.trackTitle isEqualToString:#""])
{
cell.textField.text = track;
}
else
{
cell.textField.text = recording.trackTitle;
}
UIColor * circleColor = [UIColor colorWithRed:107/255.0f green:212/255.0f blue:231/255.0f alpha:1.0f];
cell.trackIcon.backgroundColor = circleColor;
[cell.trackIcon.layer setCornerRadius:cell.trackIcon.bounds.size.width/2];
cell.trackIcon.layer.masksToBounds = YES;
return cell;
}
didSelectRowAtIndexPath calls the following method in my tableViewCell class:
-(void) rowSelected2: (NSURL*) url
{
self.audioPlayer = [OSTablePlayerController getInstance];
NSData *data=[NSData dataWithContentsOfURL:url];
[self.audioPlayer playAudio:data];
self.audioPlayer.isPlayerPlaying = YES;
self.timer = [NSTimer timerWithTimeInterval:0.01 target:self selector:#selector(updateProgress:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
Table views are designed to reuse the rows. It's a feature that can improve the performance of your app, especially if your table view has a lot of elements in it.
Since table views reuse their rows, you need to manually clear your rows before they are recycled. If you implemented your cells by creating a UITableViewCell subclass, then you can override its
- (void)prepareForReuse
method. This gets called each time a cell is about to be reused, and it is the perfect location for doing any clean up that might need to happen.
Many apps that play media in tableview cells (e.g. tumblr) stop the playback as the cells are scrolled away (probably using prepareForReuse).
If you want to continue the playback, you'll need to keep all of the state associated with that playback outside of the table cell (in your model) including the playback progress. Your custom cell should be prepared to indicate playback progress starting at any point (from 0-99%). When a cell comes into view (in your datasource cellForRowAtIndexPath), you'll be required to check your model to see if that cell's media is playing and have it draw the current progress (hopefully it has a method setProgress:(float)progress that changes a progress property and tells that bezier drawing view to setNeedsDisplay).
This also means that if there's a timer set to check the media playback progress, it's target cannot be the cell, but rather the view controller that maintains the model.

Border of each UIView

I have to create something similar to above mentioned image. I have a view and i need border after eat view like {breakfast, brunch,etc..}
for giving border I was have following code.
CALayer *bottomBorder = [CALayer layer];
bottomBorder.frame = CGRectMake(0.0f, 43.0f, menuHeadView.frame.size.width, 1.0f);
bottomBorder.backgroundColor = [UIColor colorWithWhite:0.8f
alpha:1.0f].CGColor;
[menuHeadView.layer addSublayer:bottomBorder];
it works fine for single view but if I have an array for those views and I want to have border after each view using indexPath it doesn't work since it doesn't allow
[array objectAtIndex:i].layer;
help please! Thanks in advance.
If you have array of views you probably should cast:
Change :
[array objectAtIndex:i].layer;
To:
((UIView*)[array objectAtIndex:i]).layer;
EDIT:
Based on #Mischa comment:
Casting is telling the array what object is in it. If you have different kind of objects in array (you really shouldn't!) or to be super safe you can change casting to assign:
id myUnknownObject = [array objectAtIndex:i];
if([myUnknownObject isKindOfClass:[UIView class]]) {
((UIView*)myUnknownObject).layer...
} else {
NSLog(#"%#",[myUnknownObject class]);
}
But base on your comment you contain NSStrings in your array so you can't add layer to this objects. Please check your console, it's probably NSString.
Of course since you mentioned indexPath, and if you're using TableView with rows. You should use the good stuff from reusability and it method:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSDictionary *cellDic = [self.heartbeats objectAtIndex:indexPath.row];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
//here goes reusability part like styling and stuff, this will calling only once for every reusable cell. So for example for cells height 44 this will called only 13 times for iPhone screen.
//HERE you should manipulate with layer!
}
//here goes your custom text of every cell - like "Breakfast", "Brunch" and so on. This will calls for every row which is about to show up.
//HERE you should manipulate with text
return cell;
}

UICollectionView Data being Reordered

I am using a UICollectionView with a modified version of LXReorderableCollectionViewFlowLayout and data within my last two cells are being mixed up.
2 separate images are displayed in each cell, with a label beneath, and when I leave the viewController and return, I call reloadData. Every time this is called, the images in the cells swap. The labels, however, stay in the same place.
Here is the code I use to draw the cells which I have the issue with.
SettingsTile *setTileD = (SettingsTile *)[collectionView dequeueReusableCellWithReuseIdentifier:#"settingsReuse" forIndexPath:indexPath];
NSString *songTitle = [colors objectAtIndex:indexPath.row];
MPMediaQuery *query = [MPMediaQuery songsQuery];
[query addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:songTitle forProperty:MPMediaItemPropertyTitle]];
if ([query items].count > 0)
{
MPMediaItemArtwork *art = [[[query items] objectAtIndex:0] valueForProperty:MPMediaItemPropertyArtwork];
UIImage *im = [art imageWithSize:CGSizeMake(145, 145)];
setTileD.titleLabel.text = songTitle;
setTileD.titleLabel.textColor = [UIColor whiteColor];
UIImageView *imView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 145, 145)];
imView.image = im;
[setTileD addSubview:imView];
[setTileD sendSubviewToBack:imView];
return setTileD;
}
else
{
return nil;
}
The first time the app is run the images are mixed up, likely because viewDidAppear is also called when the viewController is first displayed, and when the user comes back to it, the images aren't mixed up. This process repeats itself.
reloadData is called as normal (e.g [collectionView reloadData]; ).
I'm new to UICollectionView, and it's accompanying classes, so please be kind. :)
I fixed the problem by putting the UIImageViews in a backgroundView, rather than adding them directly as subviews.
I can only imagine this problem is caused either by the way UICollectionView's cellForRowAtIndexPath: method grabs images as I call them, or the way LXReorderableCollectionViewFlowLayout works.

Resources