I have a tableView which gets data from the database. Some of the cells in my TableView contain videos in them and when we come across a video it displays properly.
My issue is that if someone keeps scrolling then that video still keeps playing. I would like to change that and if the user watches part of a video then keeps scrolling then I would like to end the video right there. I think I'm close to solving that issue but not quite there yet.
This is my code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Home", for: indexPath) as! Home
let filename = streamsModel.video_image[indexPath.row] as NSString
if filename.pathExtension == "mov" {
let movieURL = URL(string: streamsModel.video_image[indexPath.row])
cell.videoCapHeight.constant = CGFloat(Float(cell.pic_height!))
cell.playerView = AVPlayer(url: movieURL!)
cell.MyAVPlayer.player = cell.playerView
cell.MyAVPlayer.showsPlaybackControls = false
cell.MyAVPlayer.view.frame = cell.videoView.bounds
cell.videoView.addSubview(cell.MyAVPlayer.view)
controller.addChildViewController(cell.MyAVPlayer)
cell.playerView?.isMuted = false
cell.MyAVPlayer.player?.play()
}
return cell
}
That code above simply checks to see if the streamsModel.video_image has a .mov extension (JSON). If it does, then it gets the movie/video and displays it in a AVPlayer.
As you can see from the code, the Play() method is used so videos start playing automatically. The problem is that if a video is, for instance, 1 minute long and you watch 20 seconds of it and you scroll down, you still hear that video playing and I would like to stop the video as soon as the user scrolls away.
This is my scrollView code now I just need to stop the videos here on scroll or maybe in UITableViewCell:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if (self.lastContentOffset > scrollView.contentOffset.y + 0) {
print("scrolling up")
}
else if (self.lastContentOffset < scrollView.contentOffset.y) {
print("scrolling down")
}
self.lastContentOffset = scrollView.contentOffset.y
}
Dan's suggestion of using the table view's delegate method would work well for you here meaning you can pause the video as the cell moves off screen.
Something like this should work:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// Dequeue the cell
let cell = tableView.dequeueReusableCell(withIdentifier: "Home", for: indexPath) as! Home
// Check the player object is set (unwrap it)
if let player = cell.MyAVPlayer.player {
// Check if the player is playing
if player.rate != 0 {
// Pause the player
player.pause()
}
}
}
I would suggest that automatically pausing and playing again could be a little bit laggy depending on if there is buffering going on etc... so you may want to consider allowing the user to play the videos on a tap.
Related
I'm trying to load the components using the default lazy load implementation of Apple: Basically I'm loading the components from my server in the cellForRowAtIndexPath method. Before the entire collectionView loads, I pre-load some of the components by calling them from the server. The thing is that if I pre-load all the elements, it takes a long time ti finish loading the elements.
The problem I'm facing is that, when I scroll down, it gets a little jumpy because it's waiting for the components to load. What chances can I do to improve the load?
Here is the code I'm using to load the elements as the user scrolls:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
do {
if expComponents[indexPath.row].items == nil {
let componentItemsResponse = try `await`(interactor!.lazyloadItems(with: expSection!.id!, componentId: expComponents[indexPath.row].id!))
expComponents[indexPath.row].items = componentItemsResponse.result
}
let cell: ComponentTableViewCell? = getCell(
forIndexPath: indexPath,
componentType: expComponents[indexPath.row].componentType!)
if let mCell = cell {
mCell.configure(withComponent: self.expComponents[indexPath.row], delegate: self)
return mCell
}
} catch {
print(error)
}
return UITableViewCell()
}
My scenario, I am recording audio and save into Coredata then listing into tableView with customcell play/pause single button. I cant able to do below things
Detect end of playback and change audio play cell button image pause to play
While first row cell audio playing time, If user click second row play button need to stop first row cell audio play and change image pause to play
then second row cell button play to pause
How to do above two operations?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! CustomCell
// Configure the cell...
cell.likeBtn.addTarget(self, action: #selector(TableViewController.liked), for: .touchUpInside)
return cell
}
#objc func liked(sender: UIButton) {
// Here I need to change button icon and handle row
}
Is kind of difficult to explain without knowing more about the rest of the app. It's important to give a little more context. I'll try to give you an answer assuming certain things.
First I'll asume you have Song objects that looks like this:
public struct Song: Equatable {
let name: String
let fileName: String
}
Your database has the following public methods and property:
class DB {
func getSong(_ position: Int) -> Song?
func getPosition(_ song: Song) -> Int?
var count: Int
}
To make it easy for this sample code on the init some predefine data is initialize.
Also there is a Player object that manages playing audio with the following public methods:
class Player {
func currentlyPlaying() -> Song?
func play(this song: Song)
func stop()
}
Now with this previously defined I created a custom cell to display the name and a button for each Song in the database. The definition is this:
class CustomCell : UITableViewCell {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var button: UIButton!
}
And looks like:
Next let's define the tableview's datasource methods. In each cell a target is added for each button's touchUpInside event (as you defined it in the question).
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return DB.shared.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell",
for: indexPath) as! CustomCell
cell.label.text = DB.shared.getSong(indexPath.row)!.name
cell.button.addTarget(self, action: #selector(buttonTapped(_:)),
for: .touchUpInside)
return cell
}
Next let's define a helper method to locate a UIVIew inside a TableView. With this method we can get the IndexPath of any control inside any cell in a TableView. The return value is optional in order to return nil if not found.
func getViewIndexInTableView(tableView: UITableView, view: UIView) -> IndexPath? {
let pos = view.convert(CGPoint.zero, to: tableView)
return tableView.indexPathForRow(at: pos)
}
Another helper method was define to change the image of a button with an animation:
func changeButtonImage(_ button: UIButton, play: Bool) {
UIView.transition(with: button, duration: 0.4,
options: .transitionCrossDissolve, animations: {
button.setImage(UIImage(named: play ? "Play" : "Stop"), for: .normal)
}, completion: nil
}
A method was necessary to stop any current playing song. The first thing is to check wether a song is playing, if so call Player's stop method. Then let's localize the position of the Song in the database, which in my case corresponds to the position in the TableView; having this let's create a IndexPath to get the corresponding cell, finally call changeButtonImage with the cell's button to change the image.
func stopCurrentlyPlaying() {
if let currentSong = Player.shared.currentlyPlaying() {
Player.shared.stop()
if let indexStop = DB.shared.getPosition(currentSong) {
let cell = tableView.cellForRow(at: IndexPath(item: indexStop, section: 0)) as! CustomCell
changeButtonImage(cell.button, play: true)
}
}
}
The buttonTapped method which starts playing a song have some logic inside. First the method signature needs #objc in order to be used in the addTarget method. The logic is a follows:
Localize the button's IndexPath in the table view using the helper method.
Localize the song in the database, the row number in the table corresponds to the order in the database.
If there is a song currently playing and is the same as the one localize for the button tapped it means we just want to stop the song, so stopCurrentlyPlaying is called and the method returns.
If is not the same song or nothing is playing let's call: stopCurrentlyPlaying, start playing the new song and change the tapped button's image to a Stop image.
The code looks like this:
#objc func buttonTapped(_ sender: UIButton) {
// Let's localize the index of the button using a helper method
// and also localize the Song i the database
if let index = getViewIndexInTableView(tableView: tableView, view: sender),
let song = DB.shared.getSong(index.row) {
// If a the song located is the same it's currently playing just stop
// playing it and return.
guard song != Player.shared.currentlyPlaying() else {
stopCurrentlyPlaying()
return
}
// Stop any playing song if necessary
stopCurrentlyPlaying()
// Start playing the tapped song
Player.shared.play(this: song)
// Change the tapped button to a Stop image
changeButtonImage(sender, play: false)
}
}
Here is a little video of the sample app working: sample app
Im working on a project where I load list of youtube videos to a UITableView. As to load and play them I use the YouTube-Player-iOS-Helper pod. Based on my understanding I use "playerViewDidBecomeReady" delegate method to determine if the video is loaded and once that calls I update my label with the video duration. However for some reason it doesnt get update all the time. My code as bellow. What am I missing
func playerViewDidBecomeReady(_ playerView: YTPlayerView) {
print("player is ready to play")
self.updateLabel(cell:cell,videoView:YTPlayer)
}
static func updateLabel(cell:UITableViewCell,videoView:YTPlayerView) {
var videoView = cell.viewWithTag(TABLE_CELL_TAGS.webView) as! YTPlayerView!
let durationSecs = String(describing: videoView?.duration())
var time = videoView?.duration()
cell.textLabel.text = time
}
My UITableViewDelegates as bellow
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:Utils.isIpad() ? "iPadVideosCell":"videosCell", for: indexPath)
self.cell = cell
var videoView = cell.viewWithTag(5) as! YTPlayerView!
self.YTPlayer = videoView
videoView?.delegate = self
return cell
}
I have a slight UI bug I am having trouble solving.
Scenario: I have 3 horizontal video thumbnails. The one in the middle has a lock icon signalling "only watchable when subscribed". The other thumbnails have no lock icons because they are free to watch. Upon logging in or out, I notice the 3 thumbnails shuffle for a split second. They then shuffle back to their intended positions but now the 3rd video has a lock icon. And if I repeat, the first has a lock icon.
A Notification Center post alerts when the user has signed in or out and we reload our data:
func reloadCollection() {
// reload data
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let section = self.sections[indexPath.section]
let data = section.items[self.isInfinityScrolling ? (indexPath.row % self.sections[indexPath.section].items.count) : indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
cell.configWithItem(data)
return cell
}
func configWithItem(_ item: CollectionLabeledItem) {
// get images, titles, etc
if item.lockStyle != nil {
self.lockStyle = item.lockStyle!
}
// nil = empty || no lock icon
// lockStyle = locked or unlocked
}
When I breakpoint my reloadCollection(), the method it calls inside works properly:
if video.subscriptionRequired {
if isLoggedIn() {
self.lockStyle = .unlocked
}
else {
self.lockStyle = .locked
}
}
I also am using prepareForReuse:
var prepareForReuseCallback: (() -> Void)!
func prepareForReuse(){
if self.prepareForReuseCallback != nil {
self.prepareForReuseCallback()
}
}
I have a suspicion the shuffling of the cells cause some misaligned logic during the reload, but I am unable to find any solutions thus far. Thank you in advance, looking forward to hear from the wonderful community!
I am new to Swift and have spent days researching, but I can't seem to figure out why my code will only play the selected song about 75% of the time when the app first "starts up". The app has 3 VC's: Artist, Album, Songs. When you get to the songs VC, the user clicks a song but it won't always play. Most of the time it will (and at this moment, there isn't anything else playing).
class ViewController: UIViewController {
var qrySongs = MPMediaQuery()
var myMPMusicPlayerController = MPMusicPlayerController()
override func viewDidLoad() {
super.viewDidLoad()
self.myMPMusicPlayerController = MPMusicPlayerController.systemMusicPlayer()
// Query songs
let predicateByAlbumTitle = MPMediaPropertyPredicate(value: selectedAlbumTitle, forProperty: MPMediaItemPropertyAlbumTitle)
qrySongs = MPMediaQuery.songsQuery()
qrySongs.addFilterPredicate(predicateByAlbumTitle)
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let myCollectionToPlay = MPMediaItemCollection(items: qrySongs.items!)
self.myMPMusicPlayerController.setQueueWithItemCollection(myCollectionToPlay)
self.myMPMusicPlayerController.nowPlayingItem = myCollectionToPlay.items[indexPath.row]
print ("Starting to play selectedSongIndexNum = ", indexPath.row)
self.myMPMusicPlayerController.prepareToPlay()
self.myMPMusicPlayerController.play()
if self.myMPMusicPlayerController.playbackState != .Playing {
self.myMPMusicPlayerController.play() // Try again
}
print (myMPMusicPlayerController.nowPlayingItem?.title)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "songIdCell")
let rowItem = qrySongs.collections![indexPath.row]
cell.textLabel!.text = rowItem.items[0].title
}
}
Output when it doesn't work:
Starting to play selectedSongIndexNum = 7
nil
Output when it does work (plus the song is playing):
Starting to play selectedSongIndexNum = 7
Optional("Last Child")
Keep in mind that if you click again, it will ALWAYS work (no matter which song you click on). If you go back to Artists and then forward to Songs, it will ALWAYS work. There are no other players playing in the background. Thanks ahead of time for any help.