Pipify for SwiftUI

repository·main·Indexed 42 days ago

https://github.com/getsidetrack/swiftui-pipify

A library that enables SwiftUI views to be displayed in a Picture-in-Picture (PiP) overlay using the .pipify(isPresented:) modifier. It converts SwiftUI views into a video stream via AVKit to keep information visible while the app is in the background.

Tokens
993
Snippets
3
Records
6
Agent score
3%

What's inside Pipify for SwiftUI

  1. How Pipify works (Implementation Concept)

    main

    Pipify works by creating a video stream from a SwiftUI view. The library takes screenshots of the view whenever it updates and feeds them through Apple's AVKit functions to create a continuous video stream. This stream is then used to launch the standard system Picture-in-Picture mode.

    From the user's perspective, the view appears in a floating window above the application, which can be minimized or closed using standard system controls.

  2. Configure Background Modes for Picture in Picture

    main

    To enable Picture in Picture functionality, you must configure your project entitlements:

    1. Open your project's entitlements window.
    2. Enable Background Modes.
    3. Specifically, check the option for Audio, AirPlay and Picture in Picture.

    Without this setting, the Picture in Picture mode will fail to launch.

  3. Check device compatibility and test on physical hardware

    main

    Pipify will not launch on unsupported devices and will log an error to the debug console.

    • Compatibility Check: Use PipifyController.isSupported (returns Bool) to determine if the device can run PiP. You can use this to conditionally show or hide PiP options in your UI.
    • Testing Requirement: You must test on physical devices. Simulators are unreliable for this library and may exhibit inconsistencies, lack of support, or bugs.
  4. Use the `pipify` modifier to enable Picture in Picture

    main

    Apply the .pipify(isPresented:) modifier to a SwiftUI view to enable Picture in Picture. You can either present the view itself or provide a custom view to be shown in the overlay.

    Key Behaviors:

    • Default Behavior: If no custom view is provided, the view the modifier is attached to is used. It uses Apple's 'morph' transition to animate the view into the PiP controller.
    • Custom View: If a custom view is provided, it is rendered offscreen, causing the PiP window to fade in.

    Important Constraints for Pipify Views:

    • Lifecycle: The view is rendered invisibly when created; task and onAppear closures may trigger unexpectedly.
    • Interactions: Most user interactions (buttons, tap gestures, etc.) are not supported.
    • Animations: Transitions and animations may result in unexpected behavior.
    @State var isPresented = false
    
    var body: some View {
        yourView
            .pipify(isPresented: $isPresented) // presents `yourView` in PIP
            
        // or
    
        yourView
            .pipify(isPresented: $isPresented) {
                SomeOtherView() // presents `SomeOtherView` in PIP
            }
    }
  5. Access PipifyController and monitor render size

    main

    If you provide a custom view for the PiP overlay, you can access the PipifyController as an @EnvironmentObject within that specific view to get metadata like the window size.

    Note: You cannot use the PipifyController EnvironmentObject in the view that defines the .pipify modifier; it must be used inside the custom view passed to the modifier.

    Alternatively, you can use the .onPipRenderSizeChanged modifier on your PiP view to respond to size changes.

    // Option 1: Using EnvironmentObject in the custom PiP view
    @EnvironmentObject var controller: PipifyController
    // Access controller.renderSize
    
    // Option 2: Using the closure on the view
    yourPipifyView
        .onPipRenderSizeChanged { size in
            // size has changed
        }