You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SwiftUI应用中如何正确展示新版Game Center仪表盘?

SwiftUI应用中如何正确展示新版Game Center仪表盘?

我之前在做SwiftUI项目集成Game Center的时候,也被这个新版仪表盘的坑折腾了好久!用UIViewControllerRepresentable硬套确实会出现各种奇怪的视觉问题——毕竟它本质是系统级的悬浮层,不是普通的可嵌入视图。后来摸索出了一套稳定的做法,分享给你:

核心思路

新版Game Center仪表盘必须通过UIKit的present(_:animated:)方法,依附在当前的顶层UIViewController上展示,不能强行嵌入SwiftUI的视图层级里。所以我们的关键是:在SwiftUI环境中拿到当前的顶层UIViewController,再用它来展示Game Center的VC

具体实现步骤

1. 先写一个获取顶层UIViewController的工具方法

这个方法能帮我们从SwiftUI的场景中找到当前活跃的顶层VC:

import UIKit

extension UIApplication {
    func topViewController(controller: UIViewController? = UIApplication.shared.connectedScenes
        .filter { $0.activationState == .foregroundActive }
        .first(where: { $0 is UIWindowScene })
        .flatMap({ $0 as? UIWindowScene })?.windows
        .first(where: \.isKeyWindow)?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(controller: navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(controller: selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(controller: presented)
        }
        return controller
    }
}

2. 实现Game Center的展示逻辑

在你的SwiftUI视图中,创建一个触发按钮,并用上面的方法获取顶层VC来展示仪表盘:

首先,我们需要一个代理类来处理Game Center的关闭回调(因为SwiftUI视图是结构体,不能直接实现NSObject协议):

import GameKit

class GameCenterDelegate: NSObject, GKGameCenterViewControllerDelegate {
    func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
        // 用户关闭仪表盘时,自动dismiss
        gameCenterViewController.dismiss(animated: true)
    }
}

然后在SwiftUI视图中使用:

import SwiftUI
import GameKit

struct GameCenterDemoView: View {
    // 持有代理实例
    private let gameCenterDelegate = GameCenterDelegate()
    
    var body: some View {
        Button("打开Game Center仪表盘") {
            // 先确保用户已登录Game Center
            guard GKLocalPlayer.local.isAuthenticated else {
                print("请先登录Game Center")
                // 如果未登录,触发登录流程
                GKLocalPlayer.local.authenticateHandler = { vc, error in
                    if let loginVC = vc {
                        UIApplication.shared.topViewController()?.present(loginVC, animated: true)
                    }
                }
                return
            }
            
            // 创建新版Game Center仪表盘VC
            let dashboardVC = GKGameCenterViewController(state: .dashboard)
            dashboardVC.gameCenterDelegate = gameCenterDelegate
            
            // 用顶层VC展示
            if let topVC = UIApplication.shared.topViewController() {
                topVC.present(dashboardVC, animated: true)
            }
        }
        .onAppear {
            // 视图加载时初始化Game Center登录
            setupGameCenter()
        }
    }
    
    private func setupGameCenter() {
        GKLocalPlayer.local.authenticateHandler = { viewController, error in
            if let loginVC = viewController {
                UIApplication.shared.topViewController()?.present(loginVC, animated: true)
            } else if let error = error {
                print("Game Center登录失败: \(error.localizedDescription)")
            }
        }
    }
}

// 预览(仅用于Xcode预览,真机测试需要配置Game Center能力)
struct GameCenterDemoView_Previews: PreviewProvider {
    static var previews: some View {
        GameCenterDemoView()
    }
}

3. 必要的配置

  • 打开Xcode,在你的项目Signing & Capabilities中添加Game Center能力
  • 测试必须使用真实iOS设备,模拟器的Game Center功能可能不全

为什么之前的方法会出问题?

新版iOS 16+的Game Center仪表盘是系统级的悬浮式界面,它的布局和生命周期完全由系统管理。如果用UIViewControllerRepresentable把它嵌入SwiftUI的视图层级,会导致系统的悬浮层和SwiftUI的视图渲染逻辑冲突,出现布局偏移、交互失效等bug。而直接用UIKit的顶层VC来present,就完全符合系统的设计预期,不会有这些问题。

额外小提示

  • 如果你需要展示特定的Game Center页面(比如成就榜、排行榜),只需要把GKGameCenterViewController(state: .dashboard)换成对应的state即可,比如.achievements.leaderboards
  • 记得在Info.plist中添加NSUserActivityUsageDescription(如果需要用到Game Center的用户活动功能)

我自己项目里用这个方法完全没问题,再也没出现过视觉bug,你可以试试!

火山引擎 最新活动