I am currently developing an app for iOS 5 and above in which a video is played inside a custom UITableViewCell using an instance of AVQueuePlayer.
There are 'n' number of such custom cells playing 'n' number of videos.
I want to implement a functionality which disables the player from playing the video after a given time.
I have a countdown timer which displays the time left in a UILabel for disabling the player beneath the instance of AVQueuePlayer.
I have to update the timer after a minute(suppose) to show the time left for the disabling to take place. e.g. "5 mins left".
I am using NSTimer for this purpose. But, I dont know how to only reload or update the UILabel instance of the custom UITableViewCell. I have seen in some threads the use of the following method
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
But, if I do so when the video is playing, it stops and gets reloaded again.
Is there a better solution to this issue?
You could either save a reference to your cell when you create it (sounds like you're only making one of each cell type, even though you have a tableview); or grab the cell from the table and pull the label out of it.
I'd be tempted to set the tag of the label, then use that to get the label back.
When you create the label (or set it in interface-builder) call [myLabel setTag:LABEL_TAG];
Then later you can:
UITableViewCell * myCell = self.myTableView cellForRowAtIndexPath:LABLE_CELL_POSITION];
UILabel * myLabel = [cell viewWithTag:LABEL_TAG];
myLabel.setText:#"my new value";
In the above LABLE_CELL_POSITION would be position of the cell in your tableview (0 to ...); and LABEL_TAG is any number you want to use to denote that view, maybe 1234.
Related
I have a UITableView with cells that display an audio waveform image and a playback button. Tapping the button causes the audio to be played back, of course. In order to reduce memory usage, I have a single instance of AVAudioPlayer declared in my table view controller. I defined a protocol that has playAudio(url:URL) and stopPlayingAudio() methods and my table view controller conforms to this protocol. Anytime a new cell is dequeued, I assign the table view controller as the delegate for the cell so that when the user taps on the playback button in the cell UI, it calls the playAudio(url) delegate method. This seems to be working well enough but I've run into a problem now.
I'm calculating a percent complete value as the audio is playing and I'd like to update the table view cell UI with this value but I'm not sure how to reference the correct cell from the table view controller. It seems like the cell that was tapped on to start the audio playback could end up getting recycled if it scrolls off the screen (unless I'm misunderstanding how cells get dynamically dequeued). Is there a way to do this?
If you know what row of the table you're looking for, you can ask the table view for the corresponding cell:
guard let cell = tableView.cellForRow(at: indexPath) as? WaveformCell {
cell.fractionComplete = ...
}
There are a few ways in which you can achieve what you intend to achieve,
If you are maintaining a datasource to create a cell from(which you should if you are not), maintain the state of the cell, this can include the percentage played of the cell's url and the state whether the item isPlaying, which will be false by default.
Once the states are in place, you need to now update this state, so you will have to add create a protocol(say AudioStateObserverProtocol) for sending this data to the cell, this protocol may have a method which periodically updates the cell UI as the player plays (something like, updatePlayDuration: or something of this sort), this will make sure that you get the value of how much of the asset has played. So when the user taps the play button instead of calling playAudio(url:URL) you can update the protocol method to playAudio(url:URL, stateObserver: TheTableViewCell), which the table view controller will set to as the delegate of type AudioStateObserverProtocol.
protocol AudioStateObserverProtocol {
func updatePlayDuration(to time: CMTime)
}
Add another protocol method stopObserving(cell: TheTableViewCell) to the protocol you have defined with playAudio(url:URL) and stopPlayingAudio()
The next step is how to make sure that the cell on reuse does not still receive/use the update, to do this you can make sure that when you setup the cell in your cellForRow datasource methods you first call the stopObserving(cell: TheTableViewCell). In your implementation of this method inside the tableview controller check for the instance of the cell against the param of type AudioStateObserverProtocol and if same, set it to nil so that this cell does not get the updates again.
One important thing to keep in mind here is that, if your audio is still playing then you need to make sure that when the cell for that index is getting created it show updates, this is when you will check the isPlaying state of the datasource and if it is true set the cell as the observer of type AudioStateObserverProtocol
I've found some similar questions already on SO, but nothing that seems to address this specific problem.
I'm using a UITableView with around 25 dynamic cells. Each cells contains a hidden UIProgressView. When the user taps on the download button within the cell to download that item, the UIProgressView is displayed and it indicates the progress of the download.
I've achieved this by assigning a tag to each cell which is equivalent to its corresponding downloadItemID. Each instance of MyCell has its own progressBar property.
This works fine as long as the table view is not scrolled during the download. It works also when the user is downloading multiple items at the same time. The code looks like this:
UIProgressView *theProgressBar;
for (MyCell *cell in self.tableView.visibleCells)
{
if (cell.tag == downloadItemID) theProgressBar = cell.progressBar;
}
float progressPercentage = (float)completedResources / expectedResources;
[theProgressBar setProgress:progressPercentage animated:YES];
The problem is that when the user scrolls the table view, the cell and progress view are transferred to another cell. It's simple enough to reset and hide the progress view for the new cell, but when the original/downloading cell is scrolled back into view, no progress is visible.
I've tried caching active progress bars into a dictionary and reallocating them back to the original cell in cellForRowAtIndexPath, but this is giving me the same result: no visible progress after scrolling the cell off and on the screen. Even if I can get them to show up, I'm doubtful I can get this to work seamlessly by rolling my own caching method.
What I need is to keep cells in memory. But can I work around dequeueReusableCellWithIdentifier? This whole problem has arisen because I had to switch to a dynamic system of allocating the cells, but it is too dynamic. Can I create the cells dynamically, but keep them in memory all the time once created, or at least keep the ones that are currently downloading?
(I understand the reasons why cell reuse is the preferred way to go with table views.)
You are working against the framework. As vadian says in the comment, you need to separate the logic and state information from the cells and put them elsewhere.
Here is one way of doing it.
Create a class to hold each download state, like a download ongoing flag, download progress, title, URL, etc.
In your view controller, create and add all your download objects to an array when the view controller is created. The first object corresponds to the first row in the table.
Whenever you dequeue a cell, populate it with data from the array. The NSIndexPath row property serves as the index into the array.
All your updates (download progress) updates the download objects in the array, then update the cell content using the updated object. You can use UITableView cellForRowAtIndexPath to get the cell for a specific array index, if you get nil there is no need to update it.
I have a table view full of songs, each cell has a Play button. Once you press the Play button the song plays and the Play button in the individual cell turns to a Stop button. Now this cell was done using View Tags, so when you scroll down further and as cells are reused, random cells that come into the view have a Stop button even though they were never selected. What is the best way to prevent this reuse from happening? Should I refactor my code into a custom UITableViewCell class and prevent reuse on the button? Or is there a quicker work around here?
You should remember playing state for song and update button status when reusing the cell (the same way you're updating other properties like title, artist etc).
For example, you can save playing song index in controller private variable (initially set it to -1) and compare it to indexPath.row when reusing the cell.
Is it possible to use a UITableView in such a way that it's cells update their content indepedantly from the CellForRowAtIndexPath method?
Example: Showing cells with a timer which includes milliseconds.
Refreshing the table view every (say) 10 milliseconds in order to update the timer's display value for each cell will surely not work, well...
Tricky if you use re-usable cells. But if you know that you don't have too many rows, then you could sub-class UITableViewCell and do whatever you wanted to....
Just create a customCell and make it observe to the timer change using NSNotification. Now whenever the timer changes, simply post a notification with the value. Let the cells observe and update by themselves.
How can i highlight a uitableviewcell only for a specific period of time without selecting it? I need to keep a cell highlighted for suppose 3 seconds and it then get dehighlighted. Basically i am developing a book type application where cells are in synch with audio. i have populated the tableviewcells with text and also I have timings for how long to keep a cell highlighted.
The API for setting a UITableViewCell's highlight state is setHighlighted:animated: so assuming you just need a simple highlight the work is half done.
Using the UITableViewDelegate method tableView:willDisplayCell:forRowAtIndexPath:, notify the cell that it has become visible through some method you define in the subclass. That method will cause the cell to highlight itself and start a timer for as long as you want the cell to remain highlighted. When the timer fires, cause the cell to remove the highlight.
You will of course have to guard against timers remaining active if a cell scrolls out of view, so invalidate timers and reset the highlight state during prepareForReuse:
I think you should make custom UITableViewCell class. When cell will be initialized, use a NSTimer with a specific time interval. Make a custom method and pass it in below NSTimer method as selector (in custom cell class)--
[NSTimer timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats]
In selector method you can set the tableViewCell background colour to show it highlighted.