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.
Related
This is my first time working with UICollectionView.
I've got everything set up and laid out as it should be (as far as I know). I've had a little bit of difficulty with the way the dequeue function works for the UICollectionView, but I think I've gotten past that. It was tricky setting up my custom cell classes when I didn't know if initWithFrame would be called or prepareForReuse.
I'm pretty sure the problem lies within the prepareForReuse function, but where is the question.
What happens is, the cells will apparently randomly draw at the top-left of the collection view and some cells will not be where they belong in the grid. (see image attached)
When bouncing, scrolling, and zooming (so as to cause reuse to occur), the problem happens. Randomly a slide will appear in the top left, and other slides will randomly disappear from the grid.
( I need more rep to post an image. Ugh. :| If you can help me, I'll email you the image. bmantzey#mac.com )
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
Slide* thisSlide = [_presentation.slidesInEffect objectAtIndex:indexPath.row];
[BuilderSlide prepareWithSlide:thisSlide];
BuilderSlide* cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"PlainSlide" forIndexPath:indexPath];
return cell;}
I'm using a static method to set the Slide object, which contains the data necessary to either prepare the asynchronous download or retrieve the image from disk cache.
It's simply:
+(void)prepareWithSlide:(Slide*)slide{
if(s_slide)
[s_slide release];
s_slide = [slide retain];}
I'm not sure if it's a big no-no to do this but in my custom Cell class, I'm calling prepareForReuse in the initWithFrame block because I need that setup code to be the same:
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self)
{
[self prepareForReuse];
}
return self;}
Here's the prepareForReuse function:
-(void)prepareForReuse{
CGSize size = [SpringboardLayout currentSlideSize];
[self setFrame:CGRectMake(0, 0, size.width, size.height)];
self.size = size;
// First remove any previous view, so as not to stack them.
if(_builderSlideView)
{
if(_builderSlideView.slide.slideID == s_slide.slideID)
return;
[_builderSlideView release];
}
for(UIView* aView in self.contentView.subviews)
{
if([aView isKindOfClass:[BuilderSlideView class]])
{
[aView removeFromSuperview];
break;
}
}
// Then setup the new view.
_builderSlideView = [[BuilderSlideView alloc] initWithSlide:s_slide];
self.builderCellView = _builderSlideView;
[s_slide release];
s_slide = nil;
[self.contentView addSubview:_builderSlideView];
if([SlideCache isImageCached:_builderSlideView.slide.slideID forPresentation:_builderSlideView.slide.presentationID asThumbnail:YES])
{
[_builderSlideView loadImageFromCache];
}
else
{
[_builderSlideView loadView];
}}
Finally, when the slide image has been downloaded, a Notification is posted (I plan on changing this to a delegate call). The notification simply reloads the cell that has received an update. Here's the notification code:
-(void)didLoadBuilderCellView:(NSNotification*)note{
BuilderCellView* cellView = [[note userInfo] objectForKey:#"cell"];
BuilderSlideView* slideView = (BuilderSlideView*)cellView;
NSIndexPath* indexPath = [self indexPathForSlide:slideView.slide];
if(indexPath)
[self.collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];}
Note that the slide objects exist in the model.
Any ideas as to what may be causing this problem? Thanks in advance!
The problem that was causing the cells to draw top left and/or disappear was caused by infinite recursion on a background thread. Plain and simply, I wasn't implementing the lazy loading correctly and safely. To solve this problem, I went back to the drawing board and tried again. Proper implementation of a lazy loading algorithm did the trick.
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;
}
I have used GMGridView inside my app. When I change the orientation all is fine, but if I change it again and enter edit mode all the cells are shacking except the visible one in the lower-right corner.
The GmGridView is added as a subview to a controller and it also doesn't occupy the whole screen. I've tried destroying it and recreating it when a rotation notification occurs ... same behaviour. Also I have made a custom view with multiple buttons and labels that I have set as the the GMGridViewCell's contentView.
here's the code for the cellForItemAtIndex
- (GMGridViewCell *)GMGridView:(GMGridView *)gridView cellForItemAtIndex:(NSInteger)index {
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
GMGridViewCell *cell = [gmGridView dequeueReusableCell];
if (!cell) {
CGSize cellSize = [self GMGridView:gmGridView sizeForItemsInInterfaceOrientation:orientation];
cell = [[GMGridViewCell alloc] initWithFrame:CGRectMake(0, 0, cellSize.width, cellSize.height)];
cell.deleteButtonIcon = [UIImage imageNamed:#"delete_button"];
cell.deleteButtonOffset = CGPointMake(0, 0);
}
CustomGridViewCell *gridViewContent = [CustomGridViewCell getNewGridViewCellForOrientation:orientation];
ContentData *data = [wishlistContentArray objectAtIndex:index];
[gridViewContent setuprWithContent:data];
cell.contentView = gridViewContent;
if (!gridViewContent.delegate)
gridViewContent.delegate = self;
return cell;
}
There is a bug on GMGridView when it computes the number of visible cells.
In loadRequiredItems there are these two lines of code
NSRange rangeOfPositions = [self.layoutStrategy rangeOfPositionsInBoundsFromOffset: self.contentOffset];
NSRange loadedPositionsRange = NSMakeRange(self.firstPositionLoaded, self.lastPositionLoaded - self.firstPositionLoaded);
You have to change the range so it will return the smallest number that is a multiple of the number of items on your cell.
For example if you have 2 cells on a row, right now the code will return a range of {0,5} but it should be {0,6}.
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.
I have an UITable with just 6 custom cells. Each CustomCell have a horizontal scroll view which have custom Views into it. Each CustomView has a ImageView and Text on it.
So to put it all together it may look like this
UITable --> CustomCell ---> Horizontal ScrollView --> CustomView --> ImageView and Text
Here is the code for Cell in UITable
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MySecondIdentifier = #"MySecondIdentifier";
UITableViewCell *cell2 = [tableView dequeueReusableCellWithIdentifier:MySecondIdentifier];
if(cell2 == nil){
cell2 = [(CustomCell* )[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MySecondIdentifier target:self row:indexPath.row parent:self];
}
[cell2 setSelectionStyle:UITableViewCellSelectionStyleNone];
[cell2 setValueToCellViewItem:tempTitleString setOfImage:dictCatData];
return cell2;
}
where DictCatData = NSMutableArray of data nodes
and tempTitleString = Title string for the cell (using it for some other purpose)
Here is how I set a CustomCell values
- (void) setValueToCellViewItem:(NSString *)pTitle setOfImage:(NSMutableArray *)catData{
[the_pScrolView setContentSize:CGSizeMake([catData count] * 107 , 107)];
int counter = 0;
for(NSDictionary *tempDict in catData){
NSString *url = [[NSString alloc]init];
url = [tempDict objectForKey:#"main_img_url"];
url = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
UIImageView *mSdWebImage = [[UIImageView alloc] initWithFrame:CGRectMake(counter * 107, 0, 107, 107)];
[mSdWebImage setImageWithURL:[NSURL URLWithString:url] placeholderImage:nil];
[mSdWebImage setBackgroundColor:[UIColor grayColor]];
[the_pScrolView addSubview:mSdWebImage];
///Setting the title properties
UILabel *the_pLable = [[UILabel alloc] initWithFrame:CGRectMake((counter * 107) + 15, 85, 97, 22)];
the_pLable.textColor = [UIColor whiteColor];
the_pLable.font = [UIFont fontWithName:#"Helvetica" size:10.0];
the_pLable.numberOfLines = 1;
the_pLable.backgroundColor = [UIColor clearColor];
the_pLable.text = [tempDict objectForKey:#"title"];
[the_pScrolView addSubview:the_pLable];
counter++;
}
I am using SDWebImage for async downloading and caching as I think thats the best we have on net.
The ScrollView can contain images ranging from 0 to 30+ images
When I open this page on my iPhone, the images are getting downloaded an cached properly I guess, as I am able to see them with no difficulties
My Problem are
When I try to scroll up and down the table, the scrolling is not smooth. So how can I make it more smoother without effecting the background image downloading and caching
When I scroll the table up and down several times, the custom cells are redrawn I guess so the CustomCells with no images (i.e. no customViews in scrollView) show the images from other custom cells below/top.
Sometimes the app crashes, I guess this is issue of memory management.
How big are the images being downloaded? I had similar issues (without the extra horizontal scrolling) and I was able to fix it by using actual thumbnails of the images instead of the actual images (in the TableView).
You may want to try downloading, caching, and creating thumbnails of the images in a separate object and then letting the TableViewCell load those thumbnails of the images instead of the actual images.
This sped up scrolling perfectly for me.
To fix the reused cells showing wrong images, simply remove the images in cellForRowAtIndexPath before calling your code to display new images. That way if the images are delayed, at least the old ones have been removed or hidden.