UITableViewCell not hiding progress bar - ios

I have a UITableView that is supposed to show progress bars at various times, and then hide them when finished. However, the progress bars aren’t disappearing when they’re supposed to.
Background
The app I’m working on is designed to allow users to create multimedia-heavy projects. One of the views shows a list of all the audio files that are currently available, and also gives users the option to import additional files from a number sources, including their music library.
Details
To handle the import from the music library, I’m using a variation of the code posted by Chris Adamson in his blog here. The user can queue up as many imports as necessary, and code will automatically import them in sequence. The read/write part is performed in the background, with notifications sent out to let the rest of the app know the status of the process.
All of the audio files available to the user are shown in a UITableView. The table view uses custom cells containing a label (to display the file’s name) and a progress bar (which is always hidden unless the song is in mid-import).
Here is the code for the cell:
#interface AudioTableCell()
#property (nonatomic, strong) NSString *importingFileName;
#end
#implementation AudioTableCell
// ---------------------------------------------------------------------------------------------------------------------
- (void) prepareForReuse
{
// remove notification listeners to avoid creating duplicates
[[NSNotificationCenter defaultCenter] removeObserver:self name:kNotifMusicLibMgrUpdateProgress object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kNotifMusicLibMgrFinishedImportingSong object:nil];
// reset cell properties to default values
self.isClickable = YES;
self.downloadProgress.hidden = YES;
}
// ---------------------------------------------------------------------------------------------------------------------
// called by view controller prior to deleting a cell
- (void) prepareCellForDeletion
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:kNotifMusicLibMgrUpdateProgress object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kNotifMusicLibMgrFinishedImportingSong object:nil];
}
// ---------------------------------------------------------------------------------------------------------------------
// called by the view controller's cellForRowAtIndexPath method
- (void) configureCell
{
// by default, cell should be clickable and the progress bar should be hidden
self.isClickable = YES;
self.downloadProgress.hidden = YES;
// listen to the import manager for updates on the import's progress
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(importWasUpdated:)
name:kNotifMusicLibMgrUpdateProgress
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(importWasFinished:)
name:kNotifMusicLibMgrFinishedImportingSong
object:nil];
// when the cell is scrolled out of view, the progress bar is hidden
// need to remember if depicted song was in process of being downloaded
if (self.importingFileName.length > 0) {
if ([self.trackLabel.text isEqualToString:[self.importingFileName stringByAppendingPathExtension:#"m4a"]]) {
self.downloadProgress.hidden = NO;
self.isClickable = NO;
}
}
}
// ---------------------------------------------------------------------------------------------------------------------
// triggered when receiving a notification that a song has been imported successfully
- (void) importWasFinished:(NSNotification *)notification
{
// the notification will pass the name of the song that was imported
NSString *fileName = [notification.userInfo objectForKey:#"audioFileName"];
// if the song that was imported matches the song being displayed by the cell, hide the progress bar
// and make the cell clickable
if ([self.trackLabel.text isEqualToString:[fileName stringByAppendingPathExtension:#"m4a"]]) {
self.downloadProgress.hidden = YES;
self.isClickable = YES;
}
// if the song that was imported matches the song that was "saved" by the cell, clear the saved song
if ([self.importingFileName isEqualToString:fileName]) {
self.importingFileName = #"";
}
}
// ---------------------------------------------------------------------------------------------------------------------
// triggered when receiving a an update notification (currently 1/second)
- (void) importWasUpdated:(NSNotification *)notification
{
// the notification will pass the name of the song being imported, its progress, and the number of songs queued
NSString *fileName = [notification.userInfo objectForKey:#"audioFileName"];
double completion = [[notification.userInfo objectForKey:#"progress"] doubleValue];
// if the cell is displaying the song being imported, disable the cell and show a progress bar
// also, store the name of the song - this way, if the cell is reused for a different song, it will still remember
// the song being downloaded and can immediately display the progress bar when brought back into view
if ([self.trackLabel.text isEqualToString:[fileName stringByAppendingPathExtension:#"m4a"]])
{
self.isClickable = NO;
self.downloadProgress.hidden = NO;
[self.downloadProgress setProgress:completion];
self.importingFileName = fileName;
} else {
self.downloadProgress.hidden = YES;
self.isClickable = YES;
}
}
#end
As you can see, the cell listens for notifications from the importer to determine when to hide/show the progress bar. If I only try to import a single song, there are no major issues (although there does seem to be slight delay between the time the song finishes and the time the progress bar hides). However, if I queue up multiple songs, the progress bar doesn’t go away at all until all of them are finished. Even worse, scrolling the cell out of view will cause the progress bar to appear for a different song when the cell gets reused.
I’ve tested the notification system using both NSLog statements and the debugger, and it’s working correctly (the “self.downloadProgressBar.hidden = YES;” section is getting called at the right time), but the progress bar remains visible. At this point I am completely out of ideas. Any and all help would be greatly appreciated.
Thanks in advance.

Please check that your code is executed on main thread. NSNotification can be posted on background thread so your code will be executed on background thread which won't update the UI.

Related

Pause other instances of AVPlayer when playButton is pushed

I have Table View with cells loading video objects from Parse.
When the video is loaded a playButton appears over a still image representation of the video. The play button is an outlet to a storyboard UIButton.
In my FeedCell.m:
- (IBAction)playButtonTapped:(id)sender {
[self.player play];
if (self.player.rate != 0 && (self.player.error == nil)) {
// player is playing
self.playButton.hidden = YES;
}
self.player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector: #selector (playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.player currentItem]];
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
self.playButton.hidden = NO;
[self.playerItem seekToTime:kCMTimeZero];
[self.player pause];
}
I would like to add to the functionality of this button so that when the playButton is tapped the AVPlayerItem will play and all other instances of AVPlayers will be paused.
Am I able to do this by setting up another NSNotification?
NSNotification is not the answer because it sends a broadcast message to all the receiver added as observer,in this case each cell containing the AVPlayers.
But there's a problem: as soon as you scroll the UITableView down, some UITableViewCells go out of screen and they won't receive any NSNotification even if they are added as observers.
So you could add all your AVPlayers to an NSMutableArray and when you want to stop all videos you could cycle the array and manually call the stop method on them.
Then remove them from the array.
Keep in mind that adding AVPlayers to an array cause their reference count to never reach 0 so they are not deallocated while the array is alive.
So put the NSMutableArray on the same controller which contains the UITableView so when it's deallocated,the array is also deallocated and then all AVPlayers are deallocated

Call function when Notification Center is dismissed

I am just coding my first iOS app using a today widget (using Swift). I was wondering if there is a function that is called whenever my app comes back to the foreground after dismissing the notification center.
I know I can use an Observer to check for UIApplicationWillEnterForegroundNotification but my function does not get called when pulling down the notification center while using my app and dismissing it again.
My problem is simple:
It is quite unlikely users will pull down the notification center to manipulate data I am using in the app, but I still have to consider what happens if they do. The user is supposed to be able to save his current location by pressing the today widget button.
If that happens while using my app, the app won't check for new data.
I used the following code for determining if the notification center was opened during the application's run time:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
{
BOOL notificationCenterCurrentlyDisplayed;
}
- (void) viewDidLoad
{
[super viewDidLoad];
notificationCenterCurrentlyDisplayed = false;
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self selector:#selector(onNotificationCenterDisplayed) name:UIApplicationWillResignActiveNotification object:nil];
[defaultCenter addObserver:self selector:#selector(onNotificationCenterDismissed) name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void) onNotificationCenterDisplayed
{
notificationCenterCurrentlyDisplayed = true;
NSLog(#"Notification center has been displayed!");
}
- (void) onNotificationCenterDismissed
{
// Reason for this check is because once the app is launched the UIApplucationDidBecomeActiveNotification is called.
if (notificationCenterCurrentlyDisplayed)
{
notificationCenterCurrentlyDisplayed = false;
NSLog(#"Notification center has been dismissed!");
}
}
#end
Also the notification center was displayed method will also be called when the user decides to close the application into the background.

Objective C: Changing tab bar controller index through NSNotificationCenter causes problems

I have a table view. when a certain row is selected (log out button), a check is done for any current sync going on between server and iPhone. If it is still syncing, I use an observer in viewDidLoad to notify me when it finishes. The observer then calls logMeOut. If it is not syncing, logMeOut is called immediately.
Changing tab index works perfectly fine when called in didSelectRowAtIndexPath. But when called from the NSNotificationObserver, I can see that both the tabs at index 0 and 3 are highlighted blue (which means somehow the system selected index 0, but failed to cancel index 3). The page is not changed; it still displays the same page at index 3. I can't go to index 0 by tapping the tab. I have to tap one of the indexes 1-3 and then tap index 0 again to navigate there.
Setting breakpoints and logging statements show that NSNotificationCenter is called correctly. I tested this on both iPhone and Xcode Simulator.
//When called from observer
//when called from didselectrowatindexpath
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(logMeOut) //does not work when called here
name:#"canLogOutNow"
object:nil];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.row == 0) {
SyncEngine *syncengine = [SyncEngine sharedEngine];
if (![syncengine syncInProgress]) {
[self logMeOut];//works fine when called here
} else {
syncengine.needToLogOut = YES; //tells sync engine to call notification center
}
}
}
- (void)logMeOut {
[PFUser logOut];
[self.parentViewController.tabBarController setSelectedIndex:0]; //Current index is 3
}
//snippet of syncengine.m
if (self.needToLogOut) {
NSNotification *notification = [NSNotification notificationWithName:#"canLogOutNow" object:nil];
[[NSNotificationCenter defaultCenter] postNotification:notification];
}

Multiple NSNotifications when screen rotates

I’m getting multiple UIKeyboardDidShowNotification and UIKeyboardDidHideNotification notifications when I rotate the device and I can’t figure out why.
I have an app that lets you edit the text for each picture. (It’s in a UITextView.) Most of the time I need to slide the text up so you can see it above the keyboard, then I slide it down when you are done editing. Then I do a database update to save the new text. I use notifications to tell me when the keyboard is displayed and when it goes away. It works fine on iPad when the user taps the keyboard close icon on the keyboard. It also works fine if the user swipes to the next page and iOS closes the keyboard. Since iPhones and iPods don’t have a keyboard close key, I wrote a method to close the keyboard when the picture or background is tapped. It works fine there too. However, when I rotate the device, I get multiple hide and show notifications. And I don’t know why.
Instead of getting one UIKeyboardDidHideNotification notification, I get a hide, a show, a hide, and then a show.
2:39:44.200 Picts for SLPs[16533:907] keyboardDidHide called. Keyboard showing flag is YES.
2:39:51.751 Picts for SLPs[16533:907] keyboardDidShow called. Keyboard showing flag is NO.
2:39:55.224 Picts for SLPs[16533:907] keyboardDidHide called. Keyboard showing flag is YES.
2:39:56.124 Picts for SLPs[16533:907] keyboardDidShow called. Keyboard showing flag is NO.
I posted the relevant code below. It is taken mostly from StackOverflow posts (Thanks guys).
In my class that displays the pictures I start notifications when it is initialized.
- (id)initWithParentView:(UIView *)parentview {
self = [super init];
if (self) {
_parentView = parentview;
if (ALLOW_DATABASE_EDITING) [self startNotifications];
}
return self;
}
- (void)startNotifications {
// Listen for keyboard appearances and disappearances
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidHide:)
name:UIKeyboardDidHideNotification
object:nil];
}
The View Controller calls the hideKeyboard method in the View when the user taps on the picture.
- (void)dismissKeyboard {
if (self.showArtic.keyBoardIsShowing) {
[self.showArtic hideTheKeyboard];
}
}
resignFirstResponder sends a notification that closes the keyboard
- (void)hideTheKeyboard {
id <ShowArticDelegate> SA_delegate = _delegate;
// Don't update the database when there is no text.
if ( ![self.editableTextView.text isEqualToString:#""] ) {
[SA_delegate updateTextInDatabase:self.editableTextView.text];
}
[self.editableTextView resignFirstResponder];
}
These methods respond to the notifications.
- (void)keyboardDidHide:(NSNotification *)notification {
NSLog(#"keyboardDidHide called. Keyboard showing flag is %#.", self.keyBoardIsShowing ? #"YES" : #"NO");
self.keyBoardIsShowing = NO;
// Move the text, update the database
}
- (void)keyboardDidShow:(NSNotification *)notification {
NSLog(#"keyboardDidShow called. Keyboard showing flag is %#.", self.keyBoardIsShowing ? #"YES" : #"NO");
self.keyBoardIsShowing = YES;
// Move the text
}
any chance you could manually dismiss the keyboard and clean up your database from within this method:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
and then if you want the keyboard brought back up once the rotation completes, manually call your method to display it from within:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
That may or may not fit your needs, but it's a pretty common and clean technique to "get out of the way" while Apple handles the rotation, and then get back to business once everything is back on solid ground again.

Dealing with two screens and one activity indicator in iOS

I have 3 screens on my app.First is login. Second is search and third is process the task.
On login i retrieve data from a web service. It returns data in XML format. So the data is considerably large. So i am doing that task on a background thread like this to stop Mainthread freezing up on me:
-(BOOL)loginEmp
{
.....some computation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self getAllCustomerValues];
});
}
-(void)getAllCustomerValues
{
....more computation.Bring the data,parse it and save it to CoreData DB.
//notification - EDIT
NSNotification *notification =[NSNotification notificationWithName:#"reloadRequest"
object:self];
[[NSNotificationCenter defaultCenter] postNotification : notification];
}
//EDIT
//SearchScreenVC.m
- (void)viewDidLoad
{
....some computation
[self.customerActIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(stopActivityIndicator)
name:#"reloadRequest"
object:nil];
}
- (void)stopActivityIndicator
{
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
So on condition that login was successful, i move to screen 2. But the background thread is still in process( i know because i have logs logging values) . I want an activity indicator showing up here (2nd screen)telling user to wait before he starts searching. So how do i do it?How can i make my activity indicator listen/wait for background thread. Please let me know if you need more info.Thanks
EDIT: so I edited accordingly but the notification never gets called. I put a notification at the end of getAllCustomerValues and in viewDidLoad of SearchScreen i used it. That notification on 2nd screen to stop animating never gets called. What is the mistake i am doing.?Thanks
EDIT 2: So it finally hits the method. I dont know what made it to hit that method. I put a break point. I wrote to stop animating but it wouldn't. I wrote hidesWhenStoppped and hidden both to YES. But it still keeps animating.How do i get it to stop?
Ok, if it is not the main thread, put the following in and that should fix it.
- (void)stopActivityIndicator
{
if(![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(stopActivityIndicator) withObject:nil waitUntilDone:NO];
return;
}
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
Could you put your background operation into a separate class and then set a delegate on it so you can alert the delegate once the operation has completed?
I havent tried this, its just an idea :)
You could use a delegate pointing to your view controller & a method in your view controller like:
- (void) updateProgress:(NSNumber*)percentageComplete {
}
And then in the background thread:
float percentComplete = 0.5; // for example
NSNumber *percentComplete = [NSNumber numberWithFloat:percentComplete];
[delegate performSelectorOnMainThread:#selector(updateProgress:) withObject:percentageComplete waitUntilDone:NO];

Resources