I'm facing problem with saving UITableViewCell's state and can't figure out how to solve it. Hope somebody can help me.
Explanation:
There is an API on server and I get data from it and then store it inside NSMutableArray. Each object of an array contains property ready which can be 1 or 0. So I've no problems with populating UITableView with this data but not every data object is ready (i.e 0) and I need to get progress of completion at server and after that to show it in each cell is need it. I've UIProgressView in dynamic prototype of UITableViewCell and set progress after getting. There is no problem if such "not ready" object is only one. But if there are many objects I can't show progress and I don't understand why.
So here is my code.
cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"readyCell";
AVMMovieCell *cell = [self.readyTable dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil) {
cell = (AVMMovieCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
AVMFilmsStore *oneItem;
oneItem = [readyArray objectAtIndex:indexPath.row];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if (oneItem.ready==1){
cell.progressLabel.hidden = YES;
cell.progressLine.hidden = YES;
if ([selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
if (![cell.progressLabel.text isEqualToString:#""]&& ![cell.progressLabel.text isEqualToString:#"Success"] && ![cell.progressLabel.text isEqualToString:#"Creating"]){
cell.progressLabel.hidden = NO;
cell.progressLine.hidden = NO;
} else {
cell.progressLabel.hidden = YES;
cell.progressLine.hidden = YES;
}
}
else{
if(!oneItem.isProcessing && !cell.selected){
cell.progressLabel.hidden = YES;
cell.progressLine.hidden = YES;
}
}
} else { //if processing
if (![processingCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]]){
[processingCellsArray addObject:[NSString stringWithFormat:#"%#",rowNsNum]];
if (!cell.isSelected){
[cell setSelected:YES];
}
cell.progressLabel.hidden = NO;
cell.progressLine.hidden = NO;
NSArray * arrayOfThingsIWantToPassAlong =
[NSArray arrayWithObjects: cell, oneItem, indexPath, nil];
if(!isMaking){
[self performSelector:#selector(getProgress:)
withObject:arrayOfThingsIWantToPassAlong
afterDelay:0];
} else{
[self performSelector:#selector(getProgress:)
withObject:arrayOfThingsIWantToPassAlong
afterDelay:0.5];
}
isMaking = YES;
} else {
if (!cell.isSelected){
[cell setSelected:YES];
}
cell.progressLabel.hidden = NO;
cell.progressLine.hidden = NO;
NSArray * arrayOfThingsIWantToPassAlong =
[NSArray arrayWithObjects: cell, oneItem, indexPath, nil];
if(!isMaking){
[self performSelector:#selector(getProgress:)
withObject:arrayOfThingsIWantToPassAlong
afterDelay:0];
} else{
[self performSelector:#selector(getProgress:)
withObject:arrayOfThingsIWantToPassAlong
afterDelay:0.3];
}
isMaking = YES;
}
}
return cell;
}
and getProgress method:
-(void)getProgress:(NSArray*)args{
if (progManager == nil && !progStop){
__block AVMFilmsStore * oneItem = args[1];
if(!oneItem.isLocal){
__block AVMMovieCell * cell = args[0];
__block NSIndexPath *indexPath = args[2];
progManager = [AFHTTPRequestOperationManager manager];
__block NSString *token = [defaults objectForKey:#"token"];
__block NSString *header = [NSString stringWithFormat:#"Bearer %#",token];
__block NSDictionary *params = #{#"lang": NSLocalizedString(#"lang",nil),#"project":oneItem.fileId};
__block NSString *oneHundredPercent;
__block NSString *progIsText;
progManager.responseSerializer = [AFJSONResponseSerializer serializer];
[progManager.requestSerializer setValue:header forHTTPHeaderField:#"Authorization"];
if(cell.selected || isMaking) { //if I just check for "cell.selected" is always "NO"
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
[progManager POST:#"http://example.com/api/project/get-progress" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([[responseObject objectForKey:#"result"]isEqualToString:#"success"]){
progCreate = [responseObject objectForKey:#"progress"];
oneHundredPercent = #"100";
if ([progCreate intValue]==[oneHundredPercent intValue]){
if([processingCellsArray containsObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]]){
[processingCellsArray removeObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
[cell setSelected:NO];
}
[readyArray removeAllObjects];
[defaults setObject:#"false" forKey:#"isSomethingInSort"];
isMaking = NO;
[self getReadyMovies:progIsText nameLabel:oneItem.fileName];
} else{
if([progCreate intValue]>=50){
if([progCreate intValue]>=60){
self.navigationController.navigationItem.leftBarButtonItem.enabled = YES;
createMainButton.enabled = YES;
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"gotFiftyNote" object:#"50"];
[cell.progressLine setProgress:[progCreate floatValue]/100 animated:YES];
} else {
[cell.progressLine setProgress:progUploadLimit];
}
progManager = nil;
progManager.responseSerializer = nil;
progManager.requestSerializer = nil;
token = nil;
header = nil;
params = nil;
progIsText = nil;
oneItem = nil;
cell = nil;
indexPath = nil;
isMaking = YES;
progCreate = nil;
oneHundredPercent = nil;
[self getProgress:args];
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
NSLog(#"Error: %#", error.localizedDescription);
}];
}
}
}
}
Any suggestions will be helpful for me. I've a headache for two weeks with this problem.
I see your code but is kind of difficult to follow with those large methods. I wouldn't keep track of the processing cells in an array. Each cell has an object to represent, those object have a bool value of ready and a progress value, right?. So try something like this:
Make sure each of your cells have a progressView as a subview.
Your cell class should have a public method named styleForReady:(bool)isReady andProgress:(nsinteger)progress
Make the service call to see if they are done or not, for each model. Whenever that service call comes back, you just update the model objects in the array, and after they have the new progress values you do [self.tableView reloadData]. This would trigger numberOfRows (which should return arrayOfObjects.count) and cellForRowAtIndexPath:(which should dequeue the cell for that indexPath, grab the model representing that cell, something like arrayOfObjects[indexPath.row], and finally, call the cell to style itself based on that model doing [cell styleForReady:objectModel.ready andProgress:objectModel.progress])
Keep in mind that the controller should just keep track of the model objects, dequeue the cell and tell the cell to style passing the model. Don't put all the logic in the controller.
Related
This below code is fired when I press delete button after selecting messages I want to delete in chat room.
- (void)deleteButtonPressed:(id)sender {
if (arrayToDelete.count) {
for (NSString *str in arrayToDelete) {
NSLog(#"msgID --> %#",str);
[self.chatModel.dataSource removeObject:str]; //??? Remove data from the screen
[[FMDBManager sharedInstance] deleteMessageByMessageId:str]; //??? Delete data from database
}
[arrayToDelete removeAllObjects];
[self.chatTableView reloadData];
}
}
This line successfully removes selected messages from the chat room.
[self.chatModel.dataSource removeObject:str]; //??? Remove data from the screen
When I go out the chat room and re-enter, those messages still exist, so I have this line below.
[[FMDBManager sharedInstance] deleteMessageByMessageId:str]; //??? Delete data from database
I think the above line should delete those selected messages from the database but when I re-enter the chat room I still see those messages. Here below are related code to that.
- (void)deleteMessageByMessageId:(NSString *)messageId {
FMDatabase *db = [self getterDataBase];
[db open];
NSString *sqlString = [NSString stringWithFormat:#"DELETE FROM message WHERE messageId = '%#'",messageId];
BOOL status = [db executeUpdate:sqlString];
NSLog(#"Delete MessageById:%# Status:%d",messageId,status);
[db close];
}
I've found that when chat room calls viewDidLoad it will eventually call the method callBackGetChannelLogNew where server will sync-up data with chat room tableview and local database.
- (void)callBackGetChannelLogNew:(NSDictionary *)resultDataDic status:(enumAPI_STATUS)eAPI_STATUS {
if (isFirstTimeUpdate) {
}
if (eAPI_STATUS == API_STATUS_SUCCEE) {
NSString *readString=[NSString stringWithFormat:#"%#",resultDataDic[#"read_arr"]];
if ([readString isEqualToString:#""]) {
// NSLog(#"read_arr is empty");
}
else {
NSArray *read_arr=resultDataDic[#"read_arr"];
// Copy read_arr
self.readArray=[read_arr mutableCopy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
[self dealWithReadArray:read_arr];
});
}
NSArray *data = [resultDataDic objectForKey:#"msg"];
if (data.count > 0) {
apiHaveData = YES;
} else {
apiHaveData = NO;
self.loadIngView.hidden = YES;
isLoadingData = NO;
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
// Reverse order of data
NSArray* reversedArray = [[data reverseObjectEnumerator] allObjects];
NSMutableArray *messageFromOtherArray = [NSMutableArray new];
NSMutableArray *messageAllArray = [NSMutableArray new];
for (int i = 0; i < reversedArray.count; i++) {
NSDictionary *_dic = reversedArray[i];
NSString *fromId = [_dic objectForKey:#"fid"];
NSString *message = [NSString stringWithFormat:#"%#",[_dic objectForKey:#"say"]];
if ([ObjectManager getChatMessageKindWithString:message] == MessageTypeText) {
message = [ObjectManager decryptWithString:message];
}
NSString *messageId = [_dic objectForKey:#"mid"];
NSString *toId = [_dic objectForKey:#"tid"];
NSDateFormatter *_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = #"yyyy-MM-dd HH:mm:ss.SSS";
NSDate *date_t = [NSDate dateWithTimeIntervalSince1970:[[_dic objectForKey:#"t"] doubleValue]/1000.0]; //換算成日期
NSString *stringDate = [_formatter stringFromDate:date_t];
NSString *sendDate = stringDate;
NSString *lid = _dic[#"lid"];
NSMutableDictionary *myDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:
fromId,#"fromId",
message,#"message",
messageId,#"messageId",
sendDate,#"sendDate",
toId,#"toId",
lid,#"lid",
nil];
NSString *isRead;
if (_chatRoomType == ChatRoomTypePrivate) {
if ([_dic[#"r"] intValue]) {
isRead = #"1";
myDic[#"isRead"] = isRead;
lastReadMessageId = [NSString stringWithFormat:#"%#",messageId];
}
}
if (i == 0) {
if (lidForAPI != [_dic[#"lid"] intValue]) {
lidForAPI = [_dic[#"lid"] intValue];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
apiHaveData = NO;
self.loadIngView.hidden = YES;
isLoadingData = NO;
});
return ;
}
}
if (![myDic[#"fromId"] isEqualToString:[User sharedUser].account]) {
[messageFromOtherArray addObject:myDic];
}
if (_chatRoomType == ChatRoomTypeGroup) {
[myDic setObject:#"1" forKey:#"isGroupMessage"];
}
[myDic setObject:#"1" forKey:#"did_I_Read"];
[messageAllArray addObject:myDic];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self setupViewWithMessageArray:messageAllArray]; //???? Here server sync-up data with tableview
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
if (_chatRoomType == ChatRoomTypePrivate) {
if (messageFromOtherArray.count > 0 && isUplaodLastRead == NO) {
isUplaodLastRead = YES;
NSDictionary *lastReadMsgDic = messageFromOtherArray.lastObject;
[self callMsgReadAPI:lastReadMsgDic];
}
} else {
if (messageAllArray.count > 0 && isUplaodLastRead == NO) {
isUplaodLastRead = YES;
NSDictionary *lastReadMsgDic = messageAllArray.lastObject;
[self callMsgReadAPI:lastReadMsgDic];
}
}
self.chatModel.channelTopic = _topic;
NSArray *read_arr=resultDataDic[#"read_arr"];
[self dealMySendMessageReadedWithReadArray:read_arr AndMessageArray:messageAllArray];
[self saveMessageWithArray:messageAllArray]; //???? Here server sync-up data with local db
});
});
}
}
This lines will sync-up data from server to tableview
dispatch_async(dispatch_get_main_queue(), ^{
[self setupViewWithMessageArray:messageAllArray]; //???? Here server sync-up data with tableview
});
Here below is the method setupViewWithMessageArray
- (void)setupViewWithMessageArray:(NSArray *)messageAllArray {
if (!isFirstTimeUpdate) {
isFirstTimeUpdate = YES;
self.chatModel.dataSource = nil;
[self.chatTableView reloadData];
self.chatModel.dataSource = [[NSMutableArray alloc] init];
[self addMessageWithArray:messageAllArray];
[self.chatTableView reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.chatModel.dataSource.count-1 inSection:0];
[self.chatTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:NO];
} else {
[self addMessageWithArray:messageAllArray];
[self reloadTableViewWithoutMove];
}
self.loadIngView.hidden = YES;
isLoadingData = NO;
if (_chatRoomType == ChatRoomTypePrivate) {
if (lastReadMessageId) {
[self.chatModel setPrivateChatListAllReadFormMessageId:lastReadMessageId];
}
}
}
This line will sync-up data from server to local db
[self saveMessageWithArray:messageAllArray]; //???? Here server sync-up data with local db
Here below is the method saveMessageWithArray
- (void)saveMessageWithArray:(NSArray *)messageArray {
for (NSDictionary *myDic in messageArray) {
if (![[FMDBManager sharedInstance] didMessageExistWithMessageID:[myDic objectForKey:#"messageId"]]) {
[[FMDBManager sharedInstance] SaveMessage:myDic];
}
else {
NSString *mid=[NSString stringWithFormat:#"%#",myDic[#"messageId"]];
NSString *isRead = myDic[#"isReaed"];
if (isRead) {
[[FMDBManager sharedInstance] UpdateisReadWithMessageID:mid];
}
}
}
}
So I think now my question is how I can update messageAllArray with arrayToDelete before server sync-up?
I have 2 View Controllers Home and Home Details. In Home I have a table view in which I am showing thumbnail and duration of a video. When I click on a particular row it's details are shown in Home Details. On returning back I am updating that selected row. So for that in viewWillDisappear Method of Home Details I have written following code :
if ([self.delegate respondsToSelector:#selector(changeSelectedBucketData:)]) {
[self.delegate changeSelectedBucketData:_videoId];
}
Now in the Home Controller I have defined that method as:
-(void)changeSelectedBucketData:(NSString*)videoId {
NSString *dataStr = [NSString stringWithFormat:#"%#bucket_id=%#",kGetBucketById,videoId];
[[WebServiceCall sharedInstance] sendGetRequestToWebWithData:dataStr success:^(NSDictionary *json) {
if([[json valueForKey:#"ResponseCode"] integerValue] == 0) {
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[_arrayOfContent replaceObjectAtIndex:selectedIndex withObject:[json valueForKey:#"GetData"]];
if (_arrayOfContent.count) {
TableViewCellHome *cell = [self.mTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex inSection:0]];
[self fillDataForIndexPath:[NSIndexPath indexPathForRow:selectedIndex inSection:0] forCell:cell];
}
});
}
} failure:^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
});
}];
}
-(void)fillDataForIndexPath:(NSIndexPath*)indexPath forCell:(TableViewCellHome*)cell{
NSDictionary *dict = [_arrayOfContent objectAtIndex:indexPath.row];
NSURL *url = [NSURL URLWithString:[[_arrayOfContent objectAtIndex:indexPath.row] valueForKey:#"video_URL"]];
[self downloadDurationAtURL:url cellTag:indexPath];
}
Now I have used the following code to Download Duration of a video :
- (NSUInteger)videoDuration:(NSURL *)videoURL {
AVURLAsset *videoAVURLAsset = [AVURLAsset assetWithURL:videoURL];
CMTime durationV = videoAVURLAsset.duration;
return CMTimeGetSeconds(durationV);
}
- (NSString *)videoDurationTextDurationTotalSeconds:(NSUInteger)dTotalSeconds {
NSUInteger dHours = floor(dTotalSeconds / 3600);
NSUInteger dMinutes = floor(dTotalSeconds % 3600 / 60);
NSUInteger dSeconds = floor(dTotalSeconds % 3600 % 60);
if (dHours > 0) {
return [NSString stringWithFormat:#"%i:%02i:%02i",dHours, dMinutes, dSeconds];
} else {
return [NSString stringWithFormat:#"%02i:%02i",dMinutes, dSeconds];
}
}
-(void)downloadDurationAtURL:(NSURL *)videoURL cellTag:(NSIndexPath*)indexPath {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//retrive image on global queue
NSUInteger dTotalSeconds = [self videoDuration:videoURL];
NSLog(#"dTotalSeconds %i",dTotalSeconds);
if (dTotalSeconds > 0) {
NSString *videoDurationText = [self videoDurationTextDurationTotalSeconds:dTotalSeconds];
dispatch_async(dispatch_get_main_queue(), ^{
TableViewCellHome *cell = [self.mTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
[[_arrayOfContent objectAtIndex:indexPath.row] setObject : videoDurationText forKey:#"duration"];
cell.labelDuration.text = videoDurationText;
cell.labelDuration.hidden = false;
});
}
else {
dispatch_async(dispatch_get_main_queue(), ^{
TableViewCellHome *cell = [self.mTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
[[_arrayOfContent objectAtIndex:indexPath.row] setObject : #"" forKey:#"duration"];
cell.labelDuration.hidden = true;
cell.labelDuration.text = #"";
});
}
});
}
Now problem is that UI is getting blocked until the duration is changed in the cell. I am not able to select a particular row until duration is displayed on the cell. But it is working fine when I display the Home controller for the first time after calling the API. It only gets blocked when I call it from Home detail.
You need to load the duration asynchronously, like this:
- (void)videoDuration:(NSURL *)videoURL completion:(void (^)(CMTime))durationCallback {
AVURLAsset *videoAVURLAsset = [AVURLAsset assetWithURL:videoURL];
[videoAVURLAsset loadValuesAsynchronouslyForKeys:#[ #"duration"] completionHandler:^{
NSError *error;
if([videoAVURLAsset statusOfValueForKey:#"duration" error:&error]) {
NSLog(#"error getting duration: %#", error);
durationCallback(kCMTimeZero); // or something
} else {
durationCallback(videoAVURLAsset.duration);
}
}];
}
I'm using JSQMessage and am having a little difficulty with showing the placeholder for media until I have it correctly downloading, and then replacing with the media. I have everything working correctly as far as adding the messages and media to server, I just can't get it to replace the placeholders.
Currently, I have a function that queries my database and pulls an array of objects for messages and then loops through and calls this function for each object to output and add it to my message thread. I'm struggling to figure out why the section with "messageToAdd.isMediaMessage" is not replacing the placeholders with the actual media following it's download from the server. Does anyone know how I should be handling this to make sure it adds the message with a placeholder, and then replaces once the media is downloaded correctly?
- (void)addMessage:(PFObject *)object
{
id<JSQMessageMediaData> messageMedia = nil;
PFObject *user = object[#"messageSender"];
[users addObject:user];
NSString *name = #"";
if(user[#"profileFName"] && user[#"profileLName"])
name= [NSString stringWithFormat:#"%# %#",user[#"profileFName"],user[#"profileLName"]];
else
name= [NSString stringWithFormat:#"%# %#",user[#"consultantFName"],user[#"consultantLName"]];
if([object[#"messageFileType"] isEqual: #"video"]){
JSQVideoMediaItem *messageMedia = [[JSQVideoMediaItem alloc] init];
messageMedia.fileURL = nil;
messageMedia.isReadyToPlay = NO;
messageToAdd = [JSQMessage messageWithSenderId:user.objectId displayName:name media:messageMedia];
} else if ([object[#"messageFileType"] isEqual: #"image"]){
JSQPhotoMediaItem *messageMedia = [[JSQPhotoMediaItem alloc] init];
messageMedia.image = nil;
messageToAdd = [JSQMessage messageWithSenderId:user.objectId displayName:name media:messageMedia];
} else{
messageToAdd= [[JSQMessage alloc] initWithSenderId:user.objectId senderDisplayName:name date:object[#"sendDate"] text:object[#"messageContent"]];
}
if(isLoadMore)
[messages insertObject:messageToAdd atIndex:0];
else
[messages addObject:messageToAdd];
// NOT TRIGGERING THESE AFTER MEDIA DOWNLOADED
if (messageToAdd.isMediaMessage) {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
if ([object[#"messageFileType"] isEqual: #"image"]){
[object[#"messageMedia"] getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
JSQPhotoMediaItem *photoItem = [[JSQPhotoMediaItem alloc] initWithImage:[UIImage imageWithData:imageData]];
((JSQPhotoMediaItem *)messageMedia).image = [UIImage imageWithCGImage:photoItem.image.CGImage];
[self.collectionView reloadData];
}
}];
}
else if([object[#"messageFileType"] isEqual: #"video"]){
PFFile *videoFile = object[#"messageMedia"];
NSURL *videoURL = [NSURL URLWithString:videoFile.url];
((JSQVideoMediaItem *)messageMedia).fileURL = videoURL;
((JSQVideoMediaItem *)messageMedia).isReadyToPlay = YES;
[self.collectionView reloadData];
}
else {
NSLog(#"%s error: unrecognized media item", __PRETTY_FUNCTION__);
}
});
}
}
For others who come along with the same issue/question, I resolved how it was working by looking at the project NotificationChat here:https://github.com/relatedcode/NotificationChat/blob/master/NotificationChat/Classes/Chat/ChatView.m. It gives a really good overview of using the JSQMessage platform.
Here's my modified function so you can see the finished product.
- (void)addMessage:(PFObject *)object
{
PFObject *user = object[#"messageSender"];
[users addObject:user];
PFFile *mediaMessage = object[#"messageMedia"];
NSString *name = #"";
if(user[#"profileFName"] && user[#"profileLName"])
name= [NSString stringWithFormat:#"%# %#",user[#"profileFName"],user[#"profileLName"]];
else
name= [NSString stringWithFormat:#"%# %#",user[#"consultantFName"],user[#"consultantLName"]];
if([object[#"messageFileType"] isEqual: #"video"]){
JSQVideoMediaItem *mediaItem = [[JSQVideoMediaItem alloc] initWithFileURL:[NSURL URLWithString:mediaMessage.url] isReadyToPlay:YES];
mediaItem.appliesMediaViewMaskAsOutgoing = [user.objectId isEqualToString:self.senderId];
messageToAdd = [[JSQMessage alloc] initWithSenderId:user.objectId senderDisplayName:name date:object.createdAt media:mediaItem];
} else if ([object[#"messageFileType"] isEqual: #"image"]){
JSQPhotoMediaItem *mediaItem = [[JSQPhotoMediaItem alloc] initWithImage:nil];
mediaItem.appliesMediaViewMaskAsOutgoing = [user.objectId isEqualToString:self.senderId];
messageToAdd = [[JSQMessage alloc] initWithSenderId:user.objectId senderDisplayName:name date:object.createdAt media:mediaItem];
[mediaMessage getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error)
{
if (error == nil)
{
mediaItem.image = [UIImage imageWithData:imageData];
[self.collectionView reloadData];
}
}];
} else{
messageToAdd= [[JSQMessage alloc] initWithSenderId:user.objectId senderDisplayName:name date:object[#"sendDate"] text:object[#"messageContent"]];
}
if(isLoadMore)
[messages insertObject:messageToAdd atIndex:0];
else
[messages addObject:messageToAdd];
}
Based on the code I think one possible reason is you need reloadData on main(UI) thread after download data successfully and asynchronously on background thread
I've already asked the same question about saving pressed button state and I thought that I should do in the same way to save progress state on cells but my tries are unsuccessful.
What I'm doing now: I select some UICollectionViewCell's and then press "download" button and then downolad action starts. Every cell I selected shows UIProgressView and everything is ok untill I scroll my UICollectionView up or down. When I do it another cells have progress view too but they mustn't! I know that I must save indexPath of selected cells in NSMutableArray and then in cellForItemAtIndexPath check if current cell indexPath is in my array and then show or hide my cell's subviews. I do that but it only works with cell selection! What should I do to save progress view state on each cell this progress really there?
Here is my code:
In cellForItemAtIndexPath:
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ([selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
cell.selectedBG.hidden = NO;
cell.selectedImg.hidden = NO;
}
else
{
cell.selectedBG.hidden = YES;
cell.selectedImg.hidden = YES;
cell.progressView.hidden = YES;
}
In didSelectItemAtIndexPath:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
...
// Add the selected item into the array
[selectedIds addObject:selectedId];
[selectedVideos addObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( ![selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray addObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = NO;
collectionCell.selectedImg.hidden = NO;
[collectionCell setSelected:YES];
}
}
}
In didDeselectItemAtIndexPath:
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
...
// Delete the selected item from the array
[selectedIds removeObject:selectedId];
[selectedVideos removeObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( [selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray removeObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = YES;
collectionCell.selectedImg.hidden = YES;
[collectionCell setSelected:NO];
}
}
And this is how I show my progress:
-(NSString *)progress:(long long )val1 : (long long )val2 : (AVMVideoCell *)cell : (NSString *)name : (NSIndexPath *)path{
float progress = ((float)val1) / val2;
NSString *prog = [[NSNumber numberWithFloat:progress*100] stringValue];
if (prog != nil){
if(cell.isSelected){
cell.selectedImg.hidden = YES;
cell.progressView.hidden = NO;
}
}
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)path.row];
if ( [selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]])
{
[cell.progressView setProgress:progress animated:YES];
}
if([prog intValue]==100){
cell.progressView.hidden = YES;
}
return prog;
}
EDIT: AVMVideoCell.m
#import "AVMVideoCell.h"
#implementation AVMVideoCell
{
NSString *fullUrl;
}
#synthesize imageView;
#synthesize selectedBG;
#synthesize progressLabel;
#synthesize progressView;
#synthesize selectedImg;
#synthesize progLayer;
-(void) setVideo:(AVMDataStore *)video {
if(_video != video) {
_video = video;
}
NSString *durString = [NSString stringWithFormat:#"%#",[self timeFormatted:_video.duration]];
if((_video.filePreview != nil) && ![_video.filePreview isEqualToString:#""]){
fullUrl = [NSString stringWithFormat:#"http://example.com%#",_video.filePreview];
}
NSURL *imgURL = [NSURL URLWithString:fullUrl];
[self.imageView setImageWithURL:imgURL
placeholderImage:[UIImage imageNamed:#"yesterday.png"] options:0 usingActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
self.duration.text = durString;
}
- (NSString *)timeFormatted:(int)totalSeconds
{
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
return [NSString stringWithFormat:#"%02d:%02d:%02d",hours, minutes, seconds];
}
#end
EDIT 2: Explanation about progress
My progressView is not an IBOutlet it's a #property (nonatomic,strong) UIProgressView *progressView; (AVMVideoCell.h)
I allocate and initialize it in cellForItemAtIndexPath:
cell.progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0,0, 150.0f, 1.0f)];
cell.progressView.trackTintColor = [UIColor colorWithWhite:255 alpha:0.5f];
cell.progressView.progressTintColor = [UIColor whiteColor];
[cell.progLayer addSubview:cell.progressView];
cell.progressView.hidden = YES;
cell.progressView.tag = indexPath.row+500;
This is where I call progress change showing:
-(void)downloadStart:(NSString*)fileUrl : (NSString*)name : (AVMVideoCell *) cell : (NSIndexPath *)path{
NSURL *URL = [NSURL URLWithString:fileUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSString *fileName = [NSString stringWithFormat:#"%#.mp4",name]; //set full file name to save
AFHTTPRequestOperation *downloadRequest = [[AFHTTPRequestOperation alloc] initWithRequest:request]; //create download request
[downloadRequest setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSData *data = [[NSData alloc] initWithData:responseObject];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *pathToFile = [NSString stringWithFormat:#"%#/%#", [paths firstObject],fileName]; // path to 'Documents'
NSString *pathOfFile = [[paths objectAtIndex:0] stringByAppendingPathComponent:fileName];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:pathOfFile append:NO];
BOOL success = [data writeToFile:pathToFile atomically:YES];
if(success){
[self checkIfExists:name : cell :path];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"file downloading error : %#", [error localizedDescription]);
UIAlertView * alert=[[UIAlertView alloc]initWithTitle:#"Error" message:[NSString stringWithFormat:#"%#",error] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil ];
[alert show];
cell.progressView.hidden = YES;
}];
// Step 5: begin asynchronous download
[downloadRequest start];
[downloadRequest setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
[self progress:totalBytesRead :totalBytesExpectedToRead :cell :name : path]; //here I pass val1 and val 2
}];
}
When I select an items in collection view, I gather their model's objects, get each id and make array of urls, then in for..in loop i pass urls one by one and then start async download. You can see how I download and call progress method above.
I need to know if you have a reuse issue or a model issue.
So first try this :
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
...
if (![selectedIds.containsObject:selectedId])
{
// Add the selected item into the array
[selectedIds addObject:selectedId];
[selectedVideos addObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( ![selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray addObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = NO;
collectionCell.selectedImg.hidden = NO;
[collectionCell setSelected:YES];
}
}
else {
// Delete the selected item from the array
[selectedIds removeObject:selectedId];
[selectedVideos removeObject:selectedVideo];
AVMVideoCell *collectionCell = (AVMVideoCell*)[collectionView cellForItemAtIndexPath:indexPath];
NSNumber *rowNsNum = [NSNumber numberWithUnsignedInt:(unsigned int)indexPath.row];
if ( [selecedCellsArray containsObject:[NSString stringWithFormat:#"%#",rowNsNum]] )
{
[selecedCellsArray removeObject:[NSString stringWithFormat:#"%ld",(long)indexPath.row]];
collectionCell.selectedBG.hidden = YES;
collectionCell.selectedImg.hidden = YES;
[collectionCell setSelected:NO];
}
}
and remove the didDeselect delegate.
Let me know what happens then.
EDIT :
Ok try this now:
// Lazy instantiation of the progressView
- (UIProgressView *)progressView
{
if (!_progressView)
{
_progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(0,0, 150.0f, 1.0f)];
_progressView.trackTintColor = [UIColor colorWithWhite:255 alpha:0.5f];
_progressView.progressTintColor = [UIColor whiteColor];
_progressView.hidden = YES;
[self.contentView addSubview:_progressView];
}
return _progressView;
}
// Here we remove the progressView on reuse
-(void)prepareForReuse
{
[super prepareForReuse];
[self.progressView removeFromSuperview];
self.progressView = nil;
}
Also remove what you did with the progressView with in the cellForItemAtIndexPath method.
You can create class for cell's models, store these models in array in your viewController and use them to get/set all states from/for cells.
Something like this:
#interface CellModel : NSObject
#property(nonatomic) BOOL selected;
#property(nonatomic) NSUInteger progress;
#end
In viewController:
#interface MyViewController () <UITableViewDataSource, UITableViewDelegate>
#property (nonatomic) NSArray* models;
#end
I am developing a downloading application. I got stuck at a point, when the user comes back after device screen lock, then my UI gets stuck. Everything gets nil, whether its UITableview or NSMutableArray, everything related to UI becomes nil, but however my downloading still goes on.
This happens only when the device(iPhone/iPad) gets automatic screen lock or user forces screen lock via lock button. If the application went to background via home button then everything works fine. I am not able to figure it out whats possibly be missing. Here's the code that i am using
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
if (isComingFromBackGround == YES)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:[AppDelegate storyboardIdentifier] bundle:Nil];
ViewController *obj = [storyboard instantiateViewControllerWithIdentifier:#"ViewController"];
[obj calledWhenComingFromBackground];
}
}
-(void)calledWhenComingFromBackground
{
NSArray *downloadingArray = [[LibraryAPI sharedInstance] getDownloadingVideos];
if ([downloadingArray count] != 0)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:[AppDelegate storyboardIdentifier] bundle:Nil];
DownloadViewController *obj = [storyboard instantiateViewControllerWithIdentifier:#"DownloadViewController"];
[obj setIscomingFromBackground:YES];
[obj notificationsHandler:Nil];
}
}
-(void)notificationsHandler:(NSNotification *)notification
{
for (DownloadingVideo *objDV in currentDownloadingArray)
{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:tempStr] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
[httpClient.operationQueue setMaxConcurrentOperationCount:10];
AFDownloadRequestOperation *operation = [[AFDownloadRequestOperation alloc] initWithRequest:request targetPath:targetPath2 shouldResume:YES];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"\t -:Success Block CAlled:- ");
processSuccessBlock(operation, responseObject);
[self badgedValue];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (operation.isCancelled)
{
NSLog(#"the operation is cancelled...");
}
else
{
if(operation.response.statusCode!= 200)
{
NSLog(#"the Faliour Occurred!!!!!!");
processFailureBlock(operation, error);
}
}
[self badgedValue];
}];
[httpClient enqueueHTTPRequestOperation:operation];
[operation setProgressiveDownloadProgressBlock:^(AFDownloadRequestOperation *operation,NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile) {
NSString *tempStr = operation.request.URL.absoluteString;
for (DownloadingVideo *objDownloading in [[[LibraryAPI sharedInstance] getDownloadingVideos] mutableCopy])
{
NSDictionary *tempDic = [objDownloading dv_tableRepresentation];
if ([[tempDic objectForKey:#"Url"] isEqualToString:tempStr])
{
float tempPercentDone = [[tempDic objectForKey:#"ProgressStatus"] floatValue];
float percentDone = ((float)totalBytesReadForFile) / totalBytesExpectedToReadForFile;
NSString *str = [NSString stringWithFormat:#"%.2f",percentDone];
percentDone = [str floatValue];
float progressOffset1 = (float)totalBytesReadForFile/1024;
float progressOffset2 = (float)totalBytesExpectedToReadForFile/1024;
NSString *strProgressStatus;
NSString *strProgressLblValue;
NSString *strSizeLblValue;
NSString *strSize;
if (tempPercentDone < percentDone)
{
strProgressStatus = [NSString stringWithFormat:#"%0.2f", percentDone];
strProgressLblValue = [NSString stringWithFormat:#"%.0f%% Completed", percentDone*100];
}
else
{
strProgressStatus = [NSString stringWithFormat:#"%0.2f", tempPercentDone];
strProgressLblValue = [NSString stringWithFormat:#"%.0f%% Completed", tempPercentDone*100];
}
strSizeLblValue = [NSString stringWithFormat:#"%.2f/%.2f MB",progressOffset1/1024, progressOffset2/1024];
strSize = [NSString stringWithFormat:#"%.2f MB",progressOffset2/1024];
NSString *strTitle = [tempDic objectForKey:#"Title"];
NSString *strUrl = [tempDic objectForKey:#"Url"];
NSString *strVideoID = [tempDic objectForKey:#"VideoID"];
NSString *strThumbnail = [tempDic objectForKey:#"Thumbnail"];
NSString *strSmallerThumbnail = [tempDic objectForKey:#"SmallerThumbnail"];
NSString *strDuration = [tempDic objectForKey:#"Duration"];
NSString *strYoutubeValue = [tempDic objectForKey:#"YoutubeValue"];
NSString *strM3u8 = [tempDic objectForKey:#"m3u8"];
NSString *strIsDownloadingPaused= #"NO";
DownloadingVideo *newObjDownloading = [[DownloadingVideo alloc] initWithTitle:strTitle url:strUrl videoID:strVideoID thumbnail:strThumbnail smallerThumbnail:strSmallerThumbnail size:strSize duration:strDuration youtubeValue:strYoutubeValue m3u8:strM3u8 progressStatus:strProgressStatus progressLblvalue:strProgressLblValue sizeLblValue:strSizeLblValue isDownloadingPaused:strIsDownloadingPaused];
int newIndex = [[[LibraryAPI sharedInstance] getDownloadingVideos] indexOfObject:objDownloading];
[[LibraryAPI sharedInstance] updateVideoInDownloading:newObjDownloading atIndex:newIndex updateCoredata:NO];
DownloadingVideo *tempNewObjDV = [[[LibraryAPI sharedInstance] getDownloadingVideos] objectAtIndex:newIndex];
NSDictionary *newDic = [tempNewObjDV dv_tableRepresentation];
// Update the corresponding table view cell
NSInteger row = newIndex;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
UITableViewCell *cell = (UITableViewCell*)[self.tblview cellForRowAtIndexPath:indexPath];
[self setupCell:cell withAttributes:newDic];
break;
}
}
}];
}
[self badgedValue];
[self.tblview reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
}
But the cell value is nil, hence no update while the data is getting downloaded. And if we scroll the UITableview the cell is refreshed and the values are updated. Once the download is complete the application crashes stating
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
Edit
This error occurs at cellForRowAtIndexPath. here's the code
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier;
UITableViewCell * cell;
if (indexPath.section == 0)
{
MyIdentifier = #"Cell";
cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
DownloadingVideo *objDV = [[[LibraryAPI sharedInstance] getDownloadingVideos] objectAtIndex:indexPath.row];
NSDictionary *dic = [objDV dv_tableRepresentation];
[self setupCell:cell withAttributes:dic];
}
return cell;
}
Please help me as i am running out of ideas.