You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在基于Storyboard的UIKit应用中实现SwiftUI式流程?

解决方案

一、在现有UIKit应用中实现状态监听与视图自动切换

1. 构建全局状态管理器

用Combine实现可观察的状态管理,统一控制引导页展示、权限状态等:

import Combine
import AVFoundation
import Photos

class AppState: ObservableObject {
    @Published var showedIntroView: Bool {
        didSet {
            UserDefaults.standard.set(showedIntroView, forKey: "showedIntroView")
        }
    }
    @Published var hasCameraPermission: Bool = false
    @Published var hasPhotoPermission: Bool = false
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        // 从UserDefaults初始化引导页状态
        showedIntroView = UserDefaults.standard.bool(forKey: "showedIntroView")
        // 初始检查权限
        checkPermissions()
        // 监听应用回到前台,重新检查权限
        NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)
            .sink { [weak self] _ in
                self?.checkPermissions()
            }
            .store(in: &cancellables)
    }
    
    // 检查相机/相册权限
    private func checkPermissions() {
        // 相机权限检查
        AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
            DispatchQueue.main.async {
                self?.hasCameraPermission = granted
            }
        }
        // 相册权限检查
        PHPhotoLibrary.requestAuthorization { [weak self] status in
            DispatchQueue.main.async {
                self?.hasPhotoPermission = status == .authorized
            }
        }
    }
}

2. 在UIKit中监听状态变化并切换视图

在根容器ViewController(比如主TabBarController或SceneDelegate关联的VC)中,订阅状态变化,根据条件切换根视图:

import UIKit
import Combine

class MainContainerViewController: UIViewController {
    private let appState = AppState()
    private var cancellables = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupStateObserving()
    }
    
    private func setupStateObserving() {
        // 组合多个状态,统一监听变化
        Publishers.CombineLatest3(
            appState.$showedIntroView,
            appState.$hasCameraPermission,
            appState.$hasPhotoPermission
        )
        .receive(on: DispatchQueue.main)
        .sink { [weak self] showedIntro, hasCamera, hasPhoto in
            self?.switchRootViewController(showedIntro: showedIntro, hasCamera: hasCamera, hasPhoto: hasPhoto)
        }
        .store(in: &cancellables)
    }
    
    private func switchRootViewController(showedIntro: Bool, hasCamera: Bool, hasPhoto: Bool) {
        guard let window = view.window ?? UIApplication.shared.windows.first else { return }
        
        if !showedIntro {
            // 展示SwiftUI引导页
            let introVC = UIHostingController(rootView: IntroView(appState: appState))
            window.rootViewController = introVC
        } else if !hasCamera || !hasPhoto {
            // 展示SwiftUI权限申请页
            let permissionVC = UIHostingController(rootView: PermissionsView(appState: appState))
            window.rootViewController = permissionVC
        } else {
            // 从Storyboard加载主内容页
            let mainVC = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()!
            window.rootViewController = mainVC
        }
        window.makeKeyAndVisible()
    }
}

3. SwiftUI视图绑定状态管理器

修改SwiftUI页面,接收AppState实例并更新状态:

struct IntroView: View {
    @ObservedObject var appState: AppState
    
    var body: some View {
        VStack {
            Text("引导页")
                .font(.title)
                .onTapGesture {
                    appState.showedIntroView = true
                }
        }
    }
}

struct PermissionsView: View {
    @ObservedObject var appState: AppState
    
    var body: some View {
        VStack(spacing: 20) {
            Text("请授予相机和相册权限")
            Button("前往设置") {
                UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

二、迁移至SwiftUI App结构体并保留Storyboard

1. 保留原代理逻辑

用UIApplicationDelegateAdaptor和UIWindowSceneDelegateAdaptor保留原有AppDelegate/SceneDelegate的业务代码:

@main
struct HybridApp: App {
    // 绑定原AppDelegate,保留初始化逻辑
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    // 绑定原SceneDelegate(如果需要)
    @UIWindowSceneDelegateAdaptor(SceneDelegate.self) var sceneDelegate
    
    @StateObject private var appState = AppState()
    
    var body: some Scene {
        WindowGroup {
            // 根据状态自动切换视图
            Group {
                if !appState.showedIntroView {
                    IntroView(appState: appState)
                } else if !appState.hasCameraPermission || !appState.hasPhotoPermission {
                    PermissionsView(appState: appState)
                } else {
                    // 包装Storyboard视图为SwiftUI可识别组件
                    StoryboardMainView()
                }
            }
        }
    }
}

// 包装Storyboard的主视图控制器
struct StoryboardMainView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        return UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()!
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

// 原AppDelegate保留(示例)
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 原有初始化逻辑:推送注册、第三方SDK初始化等
        return true
    }
}

// 原SceneDelegate保留(示例)
class SceneDelegate: NSObject, UIWindowSceneDelegate {
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // 原有场景连接逻辑
    }
}

2. 自动视图切换逻辑

通过@StateObject管理全局状态,SwiftUI会自动监听@Published属性变化并刷新视图;当需要加载Storyboard页面时,用UIViewControllerRepresentable将其包装为SwiftUI兼容的视图即可。

内容的提问来源于stack exchange,提问作者Deepak Sharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 00:26:26