KatM
KatM

Reputation: 191

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

Upvotes: 1

Views: 549

Answers (1)

Shawn Frank
Shawn Frank

Reputation: 5193

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

Upvotes: 1

Related Questions