'How to display the AirPlay Menu SwiftUI

Thanks to the airplay audio systemName emoji I made a nice icon

Button(action: {
        showAirplay()
}, label: {
    Image(systemName: "airplayaudio")
        .imageScale(.large)
})

func showAirplay() {
       ???
}

But I have no idea how to display the famous menu :



Solution 1:[1]

Wrapping the AirPlay UIView for SwiftUI seems to be the best and simplest solution.

struct AirPlayView: UIViewRepresentable {

    func makeUIView(context: Context) -> UIView {

        let routePickerView = AVRoutePickerView()
        routePickerView.backgroundColor = UIColor.clear
        routePickerView.activeTintColor = UIColor.red
        routePickerView.tintColor = UIColor.white

        return routePickerView
    }

    func updateUIView(_ uiView: UIView, context: Context) {
    }
}

Usage :

VStack {
  AirPlayView()
}

Solution 2:[2]

func showAirplay() {
    let rect = CGRect(x: 0, y: 0, width: 0, height: 0)
    let airplayVolume = MPVolumeView(frame: rect)
    airplayVolume.showsVolumeSlider = false
    UIApplication.shared.windows.first?.addSubview(airplayVolume)
    for view: UIView in airplayVolume.subviews {
      if let button = view as? UIButton {
        button.sendActions(for: .touchUpInside)
        break
      }
    }
    airplayVolume.removeFromSuperview()
  }

Solution 3:[3]

I have modified Max's answer to my needs and thought it could be useful to share. It adapts to the superview with autolayout and has a method to call its action.

import SwiftUI
import AVKit

struct AirPlayView: UIViewRepresentable {
    
    private let routePickerView = AVRoutePickerView()

    func makeUIView(context: UIViewRepresentableContext<AirPlayView>) -> UIView {
        UIView()
    }

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<AirPlayView>) {
        routePickerView.tintColor = .white
        routePickerView.activeTintColor = .systemPink
        routePickerView.backgroundColor = .clear
        
        routePickerView.translatesAutoresizingMaskIntoConstraints = false
        uiView.addSubview(routePickerView)

        NSLayoutConstraint.activate([
            routePickerView.topAnchor.constraint(equalTo: uiView.topAnchor),
            routePickerView.leadingAnchor.constraint(equalTo: uiView.leadingAnchor),
            routePickerView.bottomAnchor.constraint(equalTo: uiView.bottomAnchor),
            routePickerView.trailingAnchor.constraint(equalTo: uiView.trailingAnchor)
        ])
    }
    
    func showAirPlayMenu() {
        for view: UIView in routePickerView.subviews {
            if let button = view as? UIButton {
                button.sendActions(for: .touchUpInside)
                break
            }
        }
    }
}

You can still use it directly as a view, as in Max's answer, but also you can embed it in another view like a button and trigger the menu by hand, like I needed to:

@State private var airPlayView = AirPlayView()

var body: some View {
    VStack {
        // other views

        Button(action: {
            // other actions
            airPlayView.showAirPlayMenu()
        }) {
            HStack {
                Text("Show AirPlay menu")
                
                Spacer()
                
                airPlayView
                    .frame(width: 32, height: 32)
            }
        }
        .buttonStyle(PlainButtonStyle())
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Max
Solution 2 HatsuneMiku
Solution 3