iosswiftavplayeravplayerviewcontrolleravplayerlayer

How to get textView in subview for AVPlayerViewController to scroll?


I am using AVPlayerViewController to play mp3 audio files. I can get the player to appear programmatically with no problems. I have also added subviews in order to set an overlay for an imageView and textView. This works fine but I cannot get the textView to scroll. I was able to test the same basic code using just a regular UIViewController and the textView scrolls fine. So, I'm wondering if there's an issue with scrolling using AVPlayerViewController or if I've missed something. Please note: This is NOT a duplicate of my other post about AVPlayerViewController. In the other post, I'm asking if it's possible to use Storyboard somehow to customize in this way. Any help/suggestions regarding scrolling here would be greatly appreciated. Code is as follows:

let url = URL(string:mystream)
    let player = AVPlayer(url: url!)
    
    let controller = AVPlayerViewController()
    
    controller.player = player
    controller.view.frame = self.view.frame

    controller.contentOverlayView!.backgroundColor = UIColor(red: 150/255, green: 51/255, blue: 251/255, alpha: 1)
  
    //.. view
    var myView = UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: view.bounds.height))

    //.. imageView
    var imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 325, height: 325))
    imageView.image = myImage
    imageView.contentMode = .scaleAspectFit
    imageView.translatesAutoresizingMaskIntoConstraints = false

    //.. textView
    var myTextView = UITextView(frame: CGRect(x: 0, y: 0, width: 350, height: 600))
    //myTextView.text = currentList
    myTextView.text = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. "
    myTextView.font = UIFont(name: "Chalkboard SE", size: 18)
    myTextView.textColor = UIColor.yellow
    myTextView.backgroundColor = UIColor.gray
    
    //..
    myTextView.contentMode = .scaleAspectFit
    
    myTextView.isScrollEnabled = true
    myTextView.isUserInteractionEnabled = true
    myTextView.isEditable = true
    myTextView.isSelectable = true
    myTextView.bounces = true
    myTextView.showsVerticalScrollIndicator = true
    myTextView.showsHorizontalScrollIndicator = true
    myTextView.contentSize = CGSize(width: 700, height: 700)
    
    myTextView.translatesAutoresizingMaskIntoConstraints = false
    
    myView.addSubview(imageView)
    myView.addSubview(myTextView)
    
    controller.contentOverlayView?.addSubview(myView)

    //.. @@@@@@@@@
    imageView.topAnchor.constraint(equalTo: myView.safeAreaLayoutGuide.topAnchor, constant: 50).isActive = true
    imageView.centerXAnchor.constraint(equalTo: myView.centerXAnchor, constant: 0).isActive = true
    imageView.widthAnchor.constraint(equalToConstant: 325).isActive = true
    imageView.heightAnchor.constraint(equalToConstant: 325).isActive = true

    myTextView.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 15).isActive = true
    myTextView.centerXAnchor.constraint(equalTo: myView.centerXAnchor, constant: 0).isActive = true
    myTextView.widthAnchor.constraint(equalToConstant: 325).isActive = true
    myTextView.heightAnchor.constraint(equalToConstant: 200).isActive = true
    
    //.. @@@@@@@@@
    
    present(controller, animated: true) {
            player.play()
    }

Here is a picture of what the presented player should look like... I need the textView to scroll...

enter image description here

UPDATE: So, after seeing Shawn's answer and thinking about this, I would like to try to also implement a tabBar type design like say Spotify where the user can navigate to various tabs, still have the "player" playing music, and have a "mini player" at the bottom of the screen. See my next screenshot. Best way to do this? AVPlayerViewController? AVPlayer?

enter image description here


Solution

  • There are a lot of gesture recognizers already present on an AVPlayerViewController to pan, pinch etc to either interact with the media and / or dismiss the AVPlayerViewController once presented.

    Because of this, I believe your UITextView does not receive it's interaction events and so nothing happens when the user tries to scroll.

    Infact, Apple documents that the contentOverlayView of the AVPlayerViewController should be used for non interactive views.

    contentOverlayView

    A view that displays between the video content and the playback controls.

    Discussion

    Use the content overlay view to add noninteractive custom views, such as a logo or watermark, between the video content and the controls.

    What I suggest is the following like I suggested in your other question:

    1. Create a custom UIViewController
    2. Add the AVPlayerViewController as a child view to this custom view controller
    3. Add the UIImageView and the UITextView to this custom UIViewController and not to the AVPlayerViewController

    Here is how I would do that is as follows:

    import UIKit
    import AVKit
    
    class CustomPlayerVC: UIViewController
    {
        var image: UIImage?
        var streamURL: URL?
        var metaText = ""
        
        private let imageView = UIImageView()
        private let myTextView = UITextView()
        private let playerViewController = AVPlayerViewController()
        
        // Bool to check if your player view has been configured
        // so that you configure only once
        private var isPlayerConfigured = false
        
        // Custom init
        init(withStreamURL streamURL: URL?,
             image: UIImage?,
             metaText: String)
        {
            self.streamURL = streamURL
            self.image = image
            self.metaText = metaText
            
            super.init(nibName: nil, bundle: nil)
        }
        
        // Storyboard support if you want to add this view controller
        // in your storyboard
        required init?(coder: NSCoder)
        {
            super.init(coder: coder)
        }
        
        override func viewDidLayoutSubviews()
        {
            super.viewDidLayoutSubviews()
            
            // Check if the player is already configured
            if !isPlayerConfigured
            {
                isPlayerConfigured = true
                configurePlayer()
                configureImageView()
                configureTextView()
            }
        }
        
        private func configurePlayer()
        {
            if let streamURL = streamURL
            {
                let player = AVPlayer(url: streamURL)
                
                playerViewController.player = player
                
                // Add the AVPlayerController as a child of the current
                // view controller
                self.addChild(playerViewController)
                
                // Configure the player view
                let playerView = playerViewController.view
                playerView?.backgroundColor = .clear
                playerView?.frame = self.view.bounds
                
                // Add the AVPlayerViewController's view as a subview
                // of the current view
                self.view.addSubview(playerView!)
                playerViewController.didMove(toParent: self)
                
                // Start playing the content
                playerViewController.player?.play()
                
                // Add this to hide the default quick time player logo
                let contentBGView = UIView(frame: view.bounds)
                contentBGView.backgroundColor
                    = UIColor(red: 150/255, green: 51/255, blue: 251/255, alpha: 1)
                playerViewController.contentOverlayView!.addSubview(contentBGView)
            }
        }
        
        // Configure your image view with auto-layout
        private func configureImageView()
        {
            imageView.translatesAutoresizingMaskIntoConstraints = false
            imageView.image = image
            imageView.contentMode = .scaleAspectFill
            view.addSubview(imageView)
            
            imageView.topAnchor
                .constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 50)
                .isActive = true
            
            imageView.centerXAnchor
                .constraint(equalTo: view.centerXAnchor, constant: 0)
                .isActive = true
            
            imageView.widthAnchor
                .constraint(equalToConstant: 325)
                .isActive = true
            
            imageView.heightAnchor
                .constraint(equalToConstant: 325)
                .isActive = true
            
            imageView.layer.cornerRadius = 20
            imageView.clipsToBounds = true
        }
        
        // Configure your text view with auto-layout
        private func configureTextView()
        {
            myTextView.translatesAutoresizingMaskIntoConstraints = false
            
            myTextView.text = metaText
            myTextView.font = UIFont.systemFont(ofSize: 18)
            myTextView.textColor = UIColor.yellow
            myTextView.backgroundColor = UIColor.gray
            view.addSubview(myTextView)
            
            // Your auto layout constraints
            myTextView.topAnchor
                .constraint(equalTo: imageView.bottomAnchor, constant: 15)
                .isActive = true
            
            myTextView.centerXAnchor
                .constraint(equalTo: view.centerXAnchor, constant: 0)
                .isActive = true
            
            myTextView.widthAnchor
                .constraint(equalToConstant: 325)
                .isActive = true
            
            myTextView.heightAnchor
                .constraint(equalToConstant: 200)
                .isActive = true
        }
    }
    

    And then when you want to transition to your player, this is what you do:

    private func playAudio()
    {
        let text = "your long text goes here"
        
        let streamURL = URL(string: "http://stream.radiojar.com/nhq0vcqwuueuv")
        
        // Initialize the custom player view controller
        let customPlayerVC
            = CustomPlayerVC(withStreamURL: streamURL,
                             image: UIImage(named: "art"),
                             metaText: text)
        
        // This is important to avoid seeing the full screen player button
        customPlayerVC.modalPresentationStyle = .fullScreen
        
        present(customPlayerVC, animated: true) {
            // do what you want
        }
    }
    

    The end result is a player which plays your audio stream, displays the image and gives you a text view which can be scrolled.

    Here is a glimpse of the user interaction:

    AVPlayerViewController sub view customize UITextView swift iOS interaction scrolls