3

Conditions:

  • Swift 4, Xcode 9.3
  • Target: iOS 11.3
  • UI Done Programatically
  • Using Constraints
  • My Root View Controller is a Navigation

Situation:

I wanted to float an audio player that will be visible throughout the app. I did an AudioPlayer.swift class that contains the user interface of the audio player.

AudioPlayer.swift

import Foundation
import UIKit
import FRadioPlayer

class AudioPlayer: UIView {

    let screenSize: CGRect = UIScreen.main.bounds

    let playerImage: UIImageView = {
        let iv = UIImageView()
        iv.translatesAutoresizingMaskIntoConstraints = false
        iv.contentMode = .scaleAspectFill
        iv.layer.masksToBounds = true

        return iv
    }()

    let playerTitle: UILabel = {
        let l = UILabel()
        l.textColor = .darkGray
        l.font = UIFont.boldSystemFont(ofSize: 13)
        l.translatesAutoresizingMaskIntoConstraints = false

        return l
    }()

    let playerSeriesTitle: UILabel = {
        let l = UILabel()
        l.textColor = .darkGray
        l.font = UIFont.boldSystemFont(ofSize: 12)
        l.translatesAutoresizingMaskIntoConstraints = false

        return l
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        translatesAutoresizingMaskIntoConstraints = false
        setupAudioControls()
    }

    private func setupAudioControls(){

        let appDelegate = AppDelegate.sharedInstance
        self.backgroundColor = UIColor.init(hex: "#EBE4D3")

        self.addSubview(playerImage)
        self.addSubview(playerTitle)
        self.addSubview(playerSeriesTitle)

        self.heightAnchor.constraint(equalToConstant: 150).isActive = true
        self.bottomAnchor.constraint(equalTo: appDelegate().rootView ).isActive = true
        self.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor).isActive = true
        self.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor).isActive = true

        playerImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
        playerImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
        playerImage.widthAnchor.constraint(equalToConstant: 55).isActive = true
        playerImage.heightAnchor.constraint(equalToConstant: 55).isActive = true

        playerTitle.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
        playerTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
        playerTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
        playerTitle.heightAnchor.constraint(equalToConstant: 25).isActive = true

        playerSeriesTitle.topAnchor.constraint(equalTo: playerTitle.topAnchor, constant: 20).isActive = true
        playerSeriesTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
        playerSeriesTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
        playerSeriesTitle.heightAnchor.constraint(equalToConstant: 20).isActive = true

        UIView.animate(withDuration: 0.5, animations: {
            self.frame.origin.y -= 150
            self.playerImage.frame.origin.y -= 150
            self.playerTitle.frame.origin.y -= 150
            self.playerSeriesTitle.frame.origin.y -= 150
        }, completion: nil)


        self.setNeedsLayout()
        self.reloadInputViews()
    }
}

Problem:

How can I add this to the Root View Controller to stay on top in all view controllers that I have in my app? Wherever I navigate, the player must stay on the bottom part of every controller. As you can see, I need a reference to the rootviewcontroller to set the contraints for the AudioPlayer but I failed in so many attempts (like calling the rootviewcontroller using AppDelegate)

ruelluna
  • 787
  • 1
  • 11
  • 30

3 Answers3

4

I update it for you

  1. add singleton static let shared = AudioPlayer()
  2. add public func showAudioPlayer () --> to display Audio player
  3. add as subview to UIApplication.shared.keyWindow?
  4. TODO- add HideAudioPlayer()

Use like this

 AudioPlayer.shared.showAudioPlayer()

Here is updated code

import Foundation
import UIKit

class AudioPlayer: UIView {


    static let shared = AudioPlayer()

    let screenSize: CGRect = UIScreen.main.bounds

    let playerImage: UIImageView = {
        let iv = UIImageView()
        iv.translatesAutoresizingMaskIntoConstraints = false
        iv.contentMode = .scaleAspectFill
        iv.layer.masksToBounds = true

        return iv
    }()

    let playerTitle: UILabel = {
        let l = UILabel()
        l.textColor = .darkGray
        l.font = UIFont.boldSystemFont(ofSize: 13)
        l.translatesAutoresizingMaskIntoConstraints = false

        return l
    }()

    let playerSeriesTitle: UILabel = {
        let l = UILabel()
        l.textColor = .darkGray
        l.font = UIFont.boldSystemFont(ofSize: 12)
        l.translatesAutoresizingMaskIntoConstraints = false

        return l
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        translatesAutoresizingMaskIntoConstraints = false
       // setupAudioControls()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

     public func showAudioPlayer (){
        self.setupAudioControls()
    }

    private func setupAudioControls(){

        self.backgroundColor = .red

        self.addSubview(playerImage)
        self.addSubview(playerTitle)
        self.addSubview(playerSeriesTitle)
        UIApplication.shared.keyWindow?.addSubview(self)

        if let  layoutGuide  = UIApplication.shared.keyWindow?.layoutMarginsGuide {
            self.heightAnchor.constraint(equalToConstant: 150).isActive = true
            self.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor ).isActive = true
            self.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
            self.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
        }


        playerImage.topAnchor.constraint(equalTo: self.topAnchor, constant: 10).isActive = true
        playerImage.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10).isActive = true
        playerImage.widthAnchor.constraint(equalToConstant: 55).isActive = true
        playerImage.heightAnchor.constraint(equalToConstant: 55).isActive = true

        playerTitle.topAnchor.constraint(equalTo: self.topAnchor, constant: 5).isActive = true
        playerTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
        playerTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
        playerTitle.heightAnchor.constraint(equalToConstant: 25).isActive = true

        playerSeriesTitle.topAnchor.constraint(equalTo: playerTitle.topAnchor, constant: 20).isActive = true
        playerSeriesTitle.leadingAnchor.constraint(equalTo: playerImage.trailingAnchor, constant: 10).isActive = true
        playerSeriesTitle.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 10).isActive = true
        playerSeriesTitle.heightAnchor.constraint(equalToConstant: 20).isActive = true

        UIView.animate(withDuration: 0.5, animations: {
            self.frame.origin.y -= 150
            self.playerImage.frame.origin.y -= 150
            self.playerTitle.frame.origin.y -= 150
            self.playerSeriesTitle.frame.origin.y -= 150
        }, completion: nil)


        self.setNeedsLayout()
        self.reloadInputViews()
    }
}
Abdelahad Darwish
  • 5,969
  • 1
  • 17
  • 35
  • It worked. This is the most detailed answer. But there's a catch, 1) stretch to the side edges of the screen? 2) I'm using SideMenu from cocoapods, everytime I switch to another menu, my audio player disappears. – ruelluna May 09 '18 at 05:25
  • don't worry from this catch because your code excuted after window.makeKeyAndVisible() – Abdelahad Darwish May 09 '18 at 05:28
  • I solved the edges of the screen using `safeAreaLayoutGuide` instead of `layoutMarginsGuide`. – ruelluna May 09 '18 at 05:32
  • Have you used [SideMenu](https://github.com/jonkykong/SideMenu)? Whenever I switch to another View Controller, my audioplayer dissapears. – ruelluna May 09 '18 at 05:36
  • 1
    will check for you may be you need to add change `Zposition` – Abdelahad Darwish May 09 '18 at 05:38
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/170648/discussion-between-ruelluna-and-abdelahad-darwish). – ruelluna May 09 '18 at 05:39
1

If you want to show view in each view controller then as per view hierarchy you must have to add in UIWindow. UIWindow is base of all screen.

AppDelegate.shared.window?.addSubview(AudioPlayer)
Rakesh Patel
  • 1,673
  • 10
  • 27
0

You can add your view to UIWindow. I am doing the same thing with below method in AppDelegate.

var window: UIWindow?

func addPlayerViewAtBottom()  {
  var bottomView : PlayerBottomView!
  bottomView = PlayerBottomView(frame: CGRect(x: 0, y: UIScreen.main.bounds.size.height - 60, width: UIScreen.main.bounds.width, height: 60))
  self.window?.addSubview(bottomView)
  self.window?.bringSubview(toFront: bottomView)
}
Taimoor Suleman
  • 1,588
  • 14
  • 29