4

I have a ZStack that I set the color to black and then add a VideoPlayer. When I rotate the device there are still flashes of white around the player. I have played with all sorts of ideas and background colors, foreground colors, opacity and nothing has worked. I just want the background to be black so it looks like a smooth rotation. Anybody have any suggestions or fixes? Here's my code:

import Foundation
import SwiftUI
import AVKit

struct VideoDetail: View {
    
var videoIDString: String
var videoThumbURL: String
@State var player = AVPlayer()

var body: some View {
    
    ZStack {

        Color.black
            .edgesIgnoringSafeArea(.all)
        
        let videoURL: String = videoIDString

        VideoPlayer(player: player)
            //.frame(height: 200)
            .edgesIgnoringSafeArea(.all)
            .onAppear {
                
                player = AVPlayer(url: URL(string: videoURL)!)
                player.play()
            }
            .onDisappear {
                
                player.pause()
            }
    }
    .navigationBarHidden(true)
    .background(Color.black.edgesIgnoringSafeArea(.all))
    }
}
johnnelm9r
  • 201
  • 2
  • 9
  • This is almost certainly a bug. Please file a feedback report with Apple. You can recreate the issue by creating a new SwiftUI Xcode project and setting the body property of the ContentView.swift file to `Color.red.ignoresSafeArea()`. You'll see white flashes around the perimeter of the view during device rotation. – Tricky Jun 20 '22 at 20:26
  • 1
    Hey! Did you find any another solution? Thanks! – Alexander Khitev Feb 21 '23 at 10:19

3 Answers3

3

I feel your pain. This is a SwiftUI bug. The way that SwiftUI currently works is that it contains your view tree within a UIKit view. For the most part SwiftUI and UIKit cooperate with one another pretty well, but one particular area that struggles seems to be synchronising UIKit and SwiftUI animations.

Therefore, when the device rotates, it's actually UIKit driving the animation, so SwiftUI has to make a best guess of where it might be on the animation curve but its guess is pretty poor.

The best thing we can do right now is file feedback. Duplicated bug reports are how Apple prioritise what to work on, so the more bug reports from everyone the better. It doesn't have to be long. Title it something like 'SwiftUI animation artefacts on device rotation', and write 'Duplicate of FB10376122' for the description to reference an existing report on the same topic.

Anyway, in the meantime, we can at least grab the UIKit view of the enclosing window and set the background colour on there instead. This workaround is limited as 1) it doesn't change the apparent 'jumpiness' of the above mentioned synchronisation between the UIKit and SwiftUI animations, and 2) will only help if your background is a block colour.

That said, here's a WindowGroup replacement and view modifier pair that ties together this workaround to play as nicely as possible with the rest of SwiftUI.

Example usage:

import SwiftUI

@main
struct MyApp: App {
    
    var body: some Scene {
        // Should be at the very top of your view tree within your `App` conforming type
        StyledWindowGroup {
            ContentView()
                // view modifier can be anywhere in your view tree 
                .preferredWindowColor(.black)
        }
    }
}

To use, copy the contents below into a file named StyledWindowGroup.swift and add to your project:

import SwiftUI

/// Wraps a regular `WindowGroup` and enables use of the `preferredWindowColor(_ color: Color)` view modifier
/// from anywhere within its contained view tree. Use in place of a regular `WindowGroup`
public struct StyledWindowGroup<Content: View>: Scene {
    
    @ViewBuilder let content: () -> Content
    
    public init(content: @escaping () -> Content) {
        self.content = content
    }
    
    public var body: some Scene {
        WindowGroup {
            content()
                .backgroundPreferenceValue(PreferredWindowColorKey.self) { color in
                    WindowProxyHostView(backgroundColor: color)
                }
        }
    }
}

// MARK: - Convenience View Modifer

extension View {
    
    /// Sets the background color of the hosting window.
    /// - Note: Requires calling view is contained within a `StyledWindowGroup` scene
    public func preferredWindowColor(_ color: Color) -> some View {
        preference(key: PreferredWindowColorKey.self, value: color)
    }
}

// MARK: - Preference Key

fileprivate struct PreferredWindowColorKey: PreferenceKey {
    static let defaultValue = Color.white
    static func reduce(value: inout Color, nextValue: () -> Color) { }
}

// MARK: - Window Proxy View Pair

fileprivate struct WindowProxyHostView: UIViewRepresentable {
    
    let backgroundColor: Color
    
    func makeUIView(context: Context) -> WindowProxyView {
        let view = WindowProxyView(frame: .zero)
        view.isHidden = true
        return view
    }
    
    func updateUIView(_ view: WindowProxyView, context: Context) {
        view.rootViewBackgroundColor = backgroundColor
    }
}

fileprivate final class WindowProxyView: UIView {
    
    var rootViewBackgroundColor = Color.white {
        didSet { updateRootViewColor(on: window) }
    }
    
    override func willMove(toWindow newWindow: UIWindow?) {
        updateRootViewColor(on: newWindow)
    }
    
    private func updateRootViewColor(on window: UIWindow?) {
        guard let rootViewController = window?.rootViewController else { return }
        rootViewController.view.backgroundColor = UIColor(rootViewBackgroundColor)
    }
}
Tricky
  • 7,025
  • 5
  • 33
  • 43
1

I was having the same issue and came across a solution: you can set the background color of the hosting window's root view controller's view. You don't have direct access to this within SwiftUI, so in order to do this you can use a method described in this answer.

Just copy the withHostingWindow View extension including HostingWindowFinder somewhere and use the following code in your view to set the background color to black:

    var body: some View {
        ZStack {
            // ...
        }
        .withHostingWindow { window in
            window?.rootViewController?.view.backgroundColor = UIColor.black
        }
    }

After this, the white corners when rotating should be gone!

robbertkl
  • 76
  • 1
  • 4
  • 2
    yes, this solved the problem when nothing else did. as an alternative to .withHostingWindow, you can use ```if let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first``` in an ```onAppear()``` method. – Chris McElroy May 26 '22 at 23:01
1

Just add

ZStack{
    ...
}.preferredColorScheme(ColorScheme.dark)
Dmitry L.
  • 1,572
  • 1
  • 12
  • 10