Search code examples
iosairplay

Present AirPlay route picker from a custom UIButton


I have a stylized UIButton subclass that I want to present the standard AirPlay route selection interface on tap. I know AVRoutePickerView is available but it does not appear to offer any customizations except to configure its tint and active tint color on iOS.

Is there a way to present the route picker using your own button?

It appears this app was able to do just that:

enter image description here


Solution

  • Since the AVRoutePickerView is just a view you could add it to a custom button or add custom views on top of it. This is a very rough example

        let frame = CGRect(x: 50, y: 50, width: 100, height: 50)
        let routePickerView = AVRoutePickerView(frame: frame)
        routePickerView.backgroundColor = UIColor.clear
        view.addSubview(routePickerView)
        let label = UILabel(frame: routePickerView.bounds)
        label.backgroundColor = .black
        label.textColor = .white
        routePickerView.addSubview(label)
        label.text = "Rough"
    

    I spoke hastily with my examples of how you could make it work, button tap pass through is touchy as well as trying to use a gesture recognizer. However I came up with three alternative approaches that with some effort can be used. They all rely on having a view added to the picker as a subview as above. That view should have .isUserInteractionEnabled = true. I tested with a UILabel and just with a plain UIView, it probably needs to be something that doesn't have touch processing so avoid UIControls. Once you have this view in place you can use touchesBegan touchesCancelled and touchesEnded to do any visual adjustments or animation then pass along the touches. As always when fiddling with custom provided controls this may not always work but this is currently working for me and doesn't use private api just workarounds and a bit of luck.

    Using a custom UILabel as the subview

    class CustomLabel: UILabel {
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        transform = .init(scaleX: 0.75, y: 0.75)
        super.touchesBegan(touches, with: event)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        transform = .identity
        super.touchesEnded(touches, with: event)
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        transform = .identity
        super.touchesCancelled(touches, with: event)
    }
    
    }
    

    Using a subclass of AVRoutePickerView

    class CustomRoutePicker: AVRoutePickerView {
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        transform = .init(scaleX: 0.75, y: 0.75)
        super.touchesBegan(touches, with: event)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        transform = .identity
        super.touchesEnded(touches, with: event)
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        transform = .identity
        super.touchesCancelled(touches, with: event)
    }
    
    }
    

    Using UIViewController with a reference to a pickerView

        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        pickerView?.transform = .init(scaleX: 0.75, y: 0.75)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        pickerView?.transform = .identity
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        pickerView?.transform = .identity
    }