observables and the UItableview visible cells - ios

I am writing an IOS chat app.
I have a tableview where each cell contains a textbox, upon loading each cell I subscribe to a chat channel on pubnub.com. I have an observable in the viewdidLoad watching for incoming messages. The object receieved from the observable contains the channel name and the message text and date.
I want to display messages to their appropriate cells.
I'm not sure where to capture a fully loaded cell when its in view and subscribe to the channel. Then in the observable how do I compare the channel name to the cell currently in view on the screen?
I tried the isVisible but I'm getting more than whats visible on the screen. The thing is I want to only show messages to cells that are currenty in view, kind of how vine starts to playa video when the user has stopped on that cell even if they dont click it..
See code below
- (void)viewDidLoad
{
[super viewDidLoad];
appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
self.messages = [NSMutableDictionary dictionary];
self.configuration = [PNConfiguration defaultConfiguration];
[self load_DEMO_DATA];
[self setClient];
[self connectToServer];
//Observable
[[PNObservationCenter defaultCenter] addMessageReceiveObserver:self
withBlock:^(PNMessage *message) {
NSDateFormatter *dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = #"HH:mm:ss MM/dd/yy";
PNChannel *channel = message.channel;
NSString *messages = [self.messages valueForKey:channel.name];
if (messages == nil) {messages = #"";}
messages = [messages stringByAppendingFormat:#"<%#> %#\n",[dateFormatter stringFromDate:message.receiveDate.date],message.message];
//Get TextBox & Set Caption
UITextView *caption = (UITextView *)[[(UITableViewCell *)[(UITableView *)self.tableView cellForRowAtIndexPath:CurrentIndexPath] contentView] viewWithTag:105];
caption.text = [NSString stringWithFormat:#"%#%#", caption.text, messages];
[caption scrollRangeToVisible:NSMakeRange([caption.text length], 0)];
}];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"TimelinePostCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil)
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
// Configure the cell...
NSDictionary *post = [posts objectAtIndex:indexPath.item];
NSDictionary *user = [post objectForKey:#"user"];
//Set Current Channel
self.currentChannel = [PNChannel channelWithName:[post objectForKey:#"channelName"] shouldObservePresence:YES];
//Subscribe to Chat
[self subscribeToChannel:self.currentChannel.name];
self.currentPost = post;
//Get Channel History
[self ChannelHistory];
return cell;
}

Well, first of all, -tableView:cellForRowAtIndexPath: shouldn't be used to initiate any time consuming operations. To keep high performance, you should return prepared UITableViewCell from that method less then in 160ms or you will see "lag". This method will be called few times right after table has been shown (as many as you have cells with values).
You should use –scrollViewDidEndDragging:willDecelerate: (with decelerate NO) and –scrollViewDidEndDecelerating: as appropriate place and time when you should initiate subscriptions to the channel and any other manipulation with PubNub client.
You can subscribe on all channels at once - it will be less network overhead than subscribing for every single channel one-by-one. If you want to preserve resources and keep pricing low by keeping client subscribed on few channels, than you should use same methods to unsubscribe from previous channels (same as were suggested to detect current cell and store current channel and so on).
Also just suggestion about how you feed cell with model: move model processing inside custom cell class (there is no reason for controller to know something about structure of cell's views and which data should be shown there).

Related

Parse Event Title from Event Kit

So, I would like to read in my array of events from a calendar I am subscribed to.
The events all have a similar format, for example with two modules, 'Meaning of Life' and 'Running long distances':
Talk: Meaning of Life
Talk: Running long distances
Talk: Meaning of Life
Talk: Meaning of Life
Talk: Running long distances
I would like to be able to use something like
[[event.title componentsSeparatedByString:#"Talk: "] objectAtIndex:i];
So that in my table view I can have a 'Meaning of life' cell and a 'Running long distances' one. The idea would then be to select one and you could see all the 'Talks' for that module. There are other categories too like 'Workshops' and 'Practicals'.
Currently my code to just display all the events is as follows (ignoring the getting events of selected calendar and producing an array titled arrEvents)
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TableIDCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"TableIDCell"];
}
// Get each single event.
EKEvent *event = [self.arrEvents objectAtIndex:indexPath.row];
// Set its title to the cell's text label.
cell.textLabel.text = event.title;
// Get the event start date as a string value.
NSString *startDateString = [self.appDelegate.eventManager getStringFromDate:event.startDate];
// Get the event end date as a string value.
NSString *endDateString = [self.appDelegate.eventManager getStringFromDate:event.endDate];
// Add the start and end date strings to the detail text label.
cell.detailTextLabel.text = [NSString stringWithFormat:#"%# - %#", startDateString, endDateString];
if (indexPath.row%2 == 0) {
UIColor *altCellColor = [UIColor colorWithWhite:0.7 alpha:0.1];
cell.backgroundColor = altCellColor;
}
return cell;
}
Thanks for taking the time to read :)
I sorted it in the end by modifying the arrEvents in the class where it is generated. I was able to check if it contained string "Talk: " and them remove that bit and also check if arrEvents already contained a title and then not to add duplicates.

PFQueryTableViewController jump on fetch

Edit 1
To be clear, [self loadObjects] is not my method it is a method on the PFQueryTableViewController class supplied by parse to pull in new data.
I suspect this might be being caused by the table drawing code, as the tablecellview is configured to be auto-adjust it's height.
Here is the table drawing code:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
object:(PFObject *)object
{
//Setup estimated heights and auto row height
self.tableView.estimatedRowHeight = 68.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
//Give the cell a static identifier
static NSString *cellIdentifier;
socialPost* post = object;
//Check to see what sort of cell we should be creating, text, image or video
if (object[#"hasImage"] != nil) {
cellIdentifier = #"posts_with_image";
} else {
cellIdentifier = #"posts_no_image";
}
//Create cell if needed
hashtagLiveCellView *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[hashtagLiveCellView alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier];
}
// Configure the cell to show our imformation, loading video and images if needed
cell.postTitle.text = [NSString stringWithFormat:#"#%#",object[#"userName"]];
cell.postText.text = [NSString stringWithFormat:#"%#",
object[#"text"]];
[cell.userImage setImageWithURL:[NSURL URLWithString:post.userImageURL]];
[cell.postImage setImageWithURL:[NSURL URLWithString:post.imageURL]];
//Set ID's on the custom buttons so we know what object to work with when a button is pressed
cell.approveButtonOutlet.stringID = object.objectId;
[cell.approveButtonOutlet addTarget:self action:#selector(approvePostCallback:) forControlEvents:UIControlEventTouchUpInside];
cell.deletButtonOutlet.stringID = object.objectId;
[cell.deletButtonOutlet addTarget:self action:#selector(deletePostCallback:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
Original
I have a PFQueryTableViewController that i am loading with object from parse.
I have a scheduled task set to run every 20 seconds that calls:
[self loadObjects]
To fetch any new objects, or any changed to objects that have happened.
That all works fine, however if i am scrolled halfway down the tableview when the loadObjects is called the page jumps back to the top. Even if there are no new or changed data available.
Is there an easy way around this, before i start looking into hacky ways to catch the reload and force the table to stay where it is.
Thanks
Gareth
When you're calling loadObjects you load the objects from start. And there for you get the first results again.
Try to change [self loadObjects]; to [self.tableView reloadData];.

Don't reload cell when scroll back

I have an issue that i didn't find anywhere on the web. I have to display some custom cell loaded from a Nib. I download the informations from my DB on a separated thread and allocate it on a new MutableArray.
I have also images that are allocated in a separate array and called when necessary, but not downloaded from the web.
My table view is "lagging" when scrolled down, that is (i guess) because it has to place the things on the correct cell, but when i scroll back it lags again and it reloads again all informations.
I see that Facebook app loads cells when scrolling down (but not so slowly) and when scrolled back it doesn't reload anything and cells are already loaded (no matter how many). How can i do something like this? My table is very slow and i have (at the moment) only 3 cells.. But when the application is finished these would be 100 or 200.
Can anyone help me?
This is my code: (this on viewDidLoad)
NSString *strURL = [NSString stringWithFormat:#"..(myurl)..];
// to execute php code
NSData *dataURL = [NSData dataWithContentsOfURL:[NSURL URLWithString:[strURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
// to receive the returend value
NSString *strResult = [[NSString alloc] initWithData:dataURL encoding:NSUTF8StringEncoding];
if(![strResult isEqualToString:#"0"]) {
posts = [strResult componentsSeparatedByString:#"^"];
}
immProfilo = [NSMutableArray array];
for (int i = 0; i < [posts count]; i++) {
NSArray *datiPost = [[posts objectAtIndex:i] componentsSeparatedByString:#"/"];
FBProfilePictureView *fotoProfiloFB = [[FBProfilePictureView alloc] initWithFrame:CGRectMake(22, 22, 55, 55)];
fotoProfiloFB.profileID = [datiPost objectAtIndex:1];
[immProfilo addObject:fotoProfiloFB];
}
[self.postTab reloadData];
And that is my tableview code:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)postTab {
return [posts count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = [NSString stringWithFormat:#"%ld_%ld",(long)indexPath.section,(long)indexPath.row];
PostTabCell *cell = (PostTabCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:#"PostTabCell" owner:self options:nil];
cell = [topLevelObjects objectAtIndex:0];
}
NSString *annuncio = [posts objectAtIndex:indexPath.section];
datiAnnuncio = [annuncio componentsSeparatedByString:#"/"];
[cell addSubview:[immProfilo objectAtIndex:indexPath.section]];
cell.nome.text = [datiAnnuncio objectAtIndex:0];
cell.location.text = [self getAddressFromLatLon:[[datiAnnuncio objectAtIndex:2] floatValue] withLongitude:[[datiAnnuncio objectAtIndex:3] floatValue]];
cell.testoPost.text = [datiAnnuncio objectAtIndex:4];
return cell;
}
The reason why your table view is so laggy is because each time the table view asks the delegate for a cell (your cellForRowAtIndexPath method), you perform a synchronous network request with getAddressFromLatLon, blocking the main thread.
An immediate fix would be to -at least- store these texts in some kind of array, so that next time the table view asks for the same cell you don't have to perform a network request again.
This would solve the problem of the tableview being laggy when you scroll back up, but not when you scroll down the first time. One general rule you can always consider true, is that you shouldn't ever block the main thread with network requests.
You've got two options now: load all of these texts at the very beginning on a secondary thread while showing a spinner (easy but presents several problems, such as it wouldn't scale up very well with the number of cells). Or you would have to design an asynchronous loader that will show a placeholder string, such as loading address..., until the address is actually loaded.
Also Totumus has a point in his answer, but that is not the main cause of your lag (although it will be a big problem once the number of your cells increase).
The lag you describe is caused by the imageview you keep adding to your reusable cell. Everytime the cell reloads an imageview is added to your cell again.
Prevent functions like addSubview: in your cellForRowAtIndexPath:.
Instead you should create a custom cell that already has this UIImageView you need for your profilepictures.
The reason you load information when you scroll is that you probably load information (getAddressFromLatLon: ?) each time a cell is being created/reused. This is fine but should be done in a seperate thread (and thus the response should be handled asynchronically).
The reason you see facebook not loading anymore when you scroll back up again is because they cache their data when it is loaded into the application. Probably with CoreData.

UITableViewCell ImageView not updating

I have a tableview in my app for messaging with custom tableviewcells. This tableview is populated with an array from JSON. I have a UIImageView in the cell that shows a blue dot image if the message is unread.
Here's some code:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:YES];
messagesArray = [self getMessages];
[messagesTableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = #"MessagesCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
NSDictionary *messagesDictionary = [messagesArray objectAtIndex:indexPath.row];
UILabel *nameLabel = (UILabel *)[cell viewWithTag:100];
nameLabel.text = [messagesDictionary objectForKey:#"fromUserName"];
UIImageView *readImage = (UIImageView *)[cell viewWithTag:103];
NSNumber *boolNumber = [messagesDictionary valueForKey:#"readFlag"];
BOOL read = [boolNumber boolValue];
if (!read)
readImage.image = [UIImage imageNamed:#"Message Read Circle"];
return cell;
}
When the message is selected, I send a message to the server to let it know that the message is read, but when I go back, it still has the unread image. If I quit the app in the simulator and reload the app, the unread image is gone from the message that I selected, so I know the mark as read message is going through. Why won't [messagesTableView reloadData] work?
Since table view cells are reused, you should set the image in any case,
and not only if read == NO. Something like:
if (read)
readImage.image = [UIImage imageNamed:#"Message Read Circle"];
else
readImage.image = [UIImage imageNamed:#"Message Unread Circle"];
It doesn't look like you are actually calling reloadData on your tableview in your viewDidAppear like you said you are doing.
Also, like Mike Z asked above, you may be having issues with timing of your getMessages call. Is this method synchronous, or asynchronous? Posting some of that code may help as well.
Also, you need to make sure that you set your readImage to nil if your message has been read. Remember, these cells are dequeued, so if you don't set the imageView for both the true and false state of the read property, you may get erroneous results.

updating a UI table view cell with upload status - iOS

Hell everyone :)
My experience with the UITablewView Controller in iOS is unfortunately quite limited. What I need in my application is a UI table view which contains one custom cell for each active upload currently being uploaded to a webserver (videos, audio, etc).
Each of these uploads run asynchrounously in the background, and should all be able to update things such as UILabels in their respective cells saying something about the update progress in percentage, etc.
Now I have found a solution which works. The problem is I do not know if it is actually secure or not. Based on my own conclusion I don't really think that it is. What I do is simply to retrieve a reference of the UIViews from a cell which is getting created, and then store those references in the upload objects, so they can change label text and so on themselves.
My Own Solution
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CustomCellIdentifier = #"CustomCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CustomCellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"UploadCellView" owner:self options:nil];
if ([nib count] > 0)
{
cell = customCell;
}
else
{
NSLog(#"Failed to load CustomCell nib file!");
}
}
NSUInteger row = [indexPath row];
UploadActivity *tempActivity = [[[ApplicationActivities getSharedActivities] getActiveUploads] objectAtIndex:row];
UILabel *cellTitleLabel = (UILabel*)[cell viewWithTag:titleTag];
cellTitleLabel.text = tempActivity.title;
UIProgressView *progressbar = (UIProgressView*)[cell viewWithTag:progressBarTag];
[progressbar setProgress:(tempActivity.percentageDone / 100) animated:YES];
UILabel *cellStatusLabel = (UILabel*)[cell viewWithTag:percentageTag];
[cellStatusLabel setText:[NSString stringWithFormat:#"Uploader - %.f%% (%.01fMB ud af %.01fMB)", tempActivity.percentageDone, tempActivity.totalMBUploaded, tempActivity.totalMBToUpload]];
tempActivity.referencingProgressBar = progressbar;
tempActivity.referencingStatusTextLabel = cellStatusLabel;
return cell;
}
As you can see, this is where I think I'm doing something which isn't quite good enough:
tempActivity.referencingProgressBar = progressbar;
tempActivity.referencingStatusTextLabel = cellStatusLabel;
The upload activities get a reference to the controls stored in this cell, and can then update them themselves. The problem is that I do not know whether this is safe or not. What if the cell they are refering to gets re-used or deleted from memory, and so on?
Is there another way in which you can simply update the underlying model (my upload activites) and then force the UI table view to redraw the changed cells? Could you eventually subclass the UITableViewCell and let them continously check up against an upload and then make them upload themselves?
EDIT
This is how the upload activity objects calls their referencing UI controls:
- (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten
totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
if (referencingProgressBar != nil)
{
[referencingProgressBar setProgress:(percentageDone / 100) animated:YES];
}
if (referencingStatusTextLabel != nil)
{
[referencingStatusTextLabel setText:[NSString stringWithFormat:#"Uploader - %.f%% (%.01fMB ud af %.01fMB)", percentageDone, totalMBUploaded, totalMBToUpload]];
}
}
My only concern is that, since these objects run asynchrounously, what if at some given point the UI table view decides to remove or re-use the cells which these upload objects are pointing to? It doesn't seem very secure at all.
There are two possibilities, assuming you have a background process that is uploading:
The tableview is a delegate and implements some uploadProgress
function
The tableview listens for uploadProgress NSNotifications
The second is easier to implement, just put the listeners start/stop in viewdidappear/viewdiddissappear. Then in your upload you can track progress and emit a notification with attached userinfo that gives an integer value to progress. The table has a function that handles this notification being received and redraws the cells. Here is how to add data to the userinfo part of an NSNotification.
If you wanted to be fancier you could have an upload id and map this to a cell index, and only redraw that particular cell. Here's a question and answers that explain how to do this.
Disgusting Pseudocode Since I don't have access to my IOS dev env right now
upload function:
uploadedStuff{
upload_id = ... // unique i, maps to row in table somehow
byteswritten = ...
bytestotal = ....
userinfo = new dict
userinfo["rowid] = upload_id
userinfo["progress"] = (int)byteswritten/bytestotal
sendNotification("uploadprogress",userinfo)
}
tableview.m:
viewdidappear{
listenForNotification name:"uploadprogress" handledBy:HandleUploadProgress
}
viewdiddisappear{
stoplisteningForNotification name:"uploadprogess"
}
HandleUploadProgess:NSNotification notification {
userinfo = [notification userinfo]
rowId = [userinfo getkey:"rowId"]
progress = [userinfo getkey:"rowId"]
// update row per the link above
}

Resources