Benzy Neez
Benzy Neez

Reputation: 21730

GKGameCenterViewController showing as black text on dark background on iPads running iPadOS 16

I have a SwiftUI app that shows a GKGameCenterViewController. I am hitting a problem which is specific to iPads running iPadOS 16.1 and above in light mode only. Under these circumstances, the child views of GKGameCenterViewController appear as black text on a dark background, which is very difficult to read - see screenshot for the leaderboard "Test" below.

Game Center blackout

The app runs with versions of iOS/iPadOS 14.0 and above. The problem is very specific to iPads running iPadOS 16.1 and above in light mode. The top-level parent view of GKGameCenterViewController (showing the title "Game Center" and a navigation menu) works fine, only the child views are affected. The problem is not seen on iPhones, nor on iPads running versions of iPadOS 14 or 15. The problem is also not seen on iPads running iPadOS 16.1 and above if dark mode is in operation when GKGameCenterViewController is first shown.

The GKGameCenterViewController is being presented using a UIViewControllerRepresentable as a layer in a ZStack. From the examples I could find, using UIViewControllerRepresentable seems to be the standard way to do it, as also described in How to display Game Center leaderboard with SwiftUI.

It is interesting to note that the problem is not seen when the Game Center dashboard is shown by tapping the Game Center access point. I assume that the access point shows the GKGameCenterViewController in a different way.

The following stripped-down code illustrates how the GKGameCenterViewController is being shown. This is a standalone app that can be used to reproduce the problem. However, for Game Center authentication to work and for the overview of leaderboards to show, the following needs to be done too:

import SwiftUI
import GameKit

@main
struct GameCenterBlackoutApp: App {
    var body: some Scene {
        WindowGroup {
            MyContentView()
        }
    }
}

struct MyContentView: View {

    @State private var showingGameCenter = false

    var body: some View {
        ZStack {
            Button("Show Game Center leaderboards") {
                showingGameCenter = true
            }
            if showingGameCenter {
                MyGameCenterView(showingGameCenter: $showingGameCenter)
                    .ignoresSafeArea()
            }
        }
        .onAppear {

            // Authenticate the local player
            GKLocalPlayer.local.authenticateHandler = handleAuthenticationOutcome
        }
    }

    private func handleAuthenticationOutcome(vc: UIViewController?, error: Error?) {
        if let error {
            #if DEBUG
            print("Failed to authenticate player: \(error)")
            #endif
        }
        // Prepare and show the GameCenter access point.
        // If authentication failed then the access point takes
        // the form of a button to sign in
        GKAccessPoint.shared.location = .topTrailing
        GKAccessPoint.shared.showHighlights = false
        GKAccessPoint.shared.isActive = true
    }
}

/// A Bridge between the Game Center view controller and its wrapper
final class MyCoordinator : NSObject, GKGameCenterControllerDelegate {

    @Binding private var showingGameCenter: Bool

    init(showingGameCenter: Binding<Bool>) {
        self._showingGameCenter = showingGameCenter
    }

    func gameCenterViewControllerDidFinish(
        _ gameCenterViewController: GKGameCenterViewController
    ) {
        gameCenterViewController.dismiss(animated:false)
        showingGameCenter = false
    }
}

/// A wrapper for GKGameCenterViewController
struct MyGameCenterView: UIViewControllerRepresentable {
    typealias Coordinator = MyCoordinator

    /// Binding to the state variable that controls the visibility of the Game Center layer
    @Binding private var showingGameCenter: Bool

    init(showingGameCenter: Binding<Bool>) {
        self._showingGameCenter = showingGameCenter
    }

    /// Factory function for the Bridge between the GKGameCenterViewController and this wrapper view
    func makeCoordinator() -> Coordinator {
        MyCoordinator(showingGameCenter: $showingGameCenter)
    }

    /// Creates the GKGameCenterViewController
    func makeUIViewController(
        context: UIViewControllerRepresentableContext<MyGameCenterView>
    ) -> GKGameCenterViewController {
        let result = GKGameCenterViewController(state: .leaderboards)
        result.gameCenterDelegate = context.coordinator
        return result
    }

    /// Stub implementation of protocol method
    func updateUIViewController(
        _ gameCenterViewController: GKGameCenterViewController,
        context: UIViewControllerRepresentableContext<MyGameCenterView>
    ) {
        // NOP
    }
}

I would be really grateful for any workaround that will resolve the black text issue as described and allow the child views of GKGameCenterViewController to be shown in a normal, readable foreground color. I have tried all of the following, none of which made any difference:

BTW, I also reported the issue to Apple, but they have not been able to help.

Upvotes: 0

Views: 171

Answers (0)

Related Questions