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

SwiftUI View嵌入UIKit后Toolbar不显示问题求助

SwiftUI Toolbar在UIKit Popover中不显示的解决思路

问题描述

在SwiftUI中实现了RoomDetailsEditView,将其以Popover形式嵌入UIKit的RoomInformationViewController时,视图主体可正常显示,但包含取消、保存按钮的Toolbar完全不显示。

关键代码片段

SwiftUI视图实现

import SwiftUI
import PhotosUI

struct RoomDetailsEditView: View {
    
    @StateObject var viewModel: RoomDetailEditModel
    @State private var selectedItem: PhotosPickerItem? = nil

    
    @ToolbarContentBuilder
    private var toolbar: some ToolbarContent {
        ToolbarItem(placement: .cancellationAction) {
            Button("Abbrechen") {
                dismiss()
            }
        }
        
        ToolbarItem(placement: .confirmationAction) {
            Button("Speichern") {
                viewModel.saveProfileData()
                dismiss()
            }.disabled(!viewModel.viewGotEdited)
        }
    }

    
    private var topicSection: some View {...}
    
    private var avatar: some View {...}

    
    private var roomNameSection: some View {...}
    
    var body: some View {
        Form {
            avatar
            roomNameSection
            topicSection
        }
        .scrollDismissesKeyboard(.immediately)
        .navigationTitle("Raum bearbeiten")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar { toolbar }
    }
}

UIKit宿主ViewController

import Foundation
import UIKit
import SwiftUI

class RoomInformationViewController: UIViewController {
    
    var collectionView: UICollectionView! = nil
    var dataSource: UICollectionViewDiffableDataSource<RoomInformationViewSections, RoomInformationElement>! = nil
    var room: Room! = nil
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.initNavigationBar()
        self.initCollectionView()
        self.loadDataSource()
    }
    
   ...
    
    @objc func editRoomData() {
        let viewModel = RoomDetailEditModel(room: self.room, isProfileEdit: false)
        let editRoomView = RoomDetailsEditView(viewModel: viewModel)
        let wrapperViewController = ViewControllerWrapper(rootView: editRoomView)
        
        let navController = UINavigationController(rootViewController: wrapperViewController)
        navController.modalPresentationStyle = .popover
        navController.modalTransitionStyle = .coverVertical
        
        self.present(navController, animated: true, completion: nil)
    }
    ...
}

自定义视图包装器

class ViewControllerWrapper<Content: View>: UIViewController {

    private var hostingController: UIHostingController<Content>!
    
    init(rootView: Content) {
        super.init(nibName: nil, bundle: nil)
        hostingController = UIHostingController(rootView: rootView)
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        addChild(hostingController)
        view.addSubview(hostingController.view)
        hostingController.view.frame = view.bounds
        hostingController.didMove(toParent: self)
        print(hostingController.navigationItem.title)
        
        navigationController?.navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(title: "a", style: .plain, target: nil, action: nil)
    }
}

问题根源

SwiftUI的.toolbar和.navigationTitle依赖自身的导航容器(NavigationStack/旧版NavigationView)或与UIKit导航系统的映射关系来渲染。当前的自定义ViewControllerWrapper阻断了UIHostingController的navigationItem与UIKitUINavigationController的关联,导致SwiftUI定义的Toolbar无法被UIKit导航栏识别。

修复方案

方案1:移除自定义Wrapper,直接使用UIHostingController

不需要额外的包装类,直接将SwiftUI视图交给UIHostingController,再传入UINavigationController:

@objc func editRoomData() {
    let viewModel = RoomDetailEditModel(room: self.room, isProfileEdit: false)
    let editRoomView = RoomDetailsEditView(viewModel: viewModel)
    // 直接实例化UIHostingController,跳过自定义Wrapper
    let hostingController = UIHostingController(rootView: editRoomView)
    
    let navController = UINavigationController(rootViewController: hostingController)
    navController.modalPresentationStyle = .popover
    navController.modalTransitionStyle = .coverVertical
    
    self.present(navController, animated: true, completion: nil)
}

这种方式下,SwiftUI的.toolbar和.navigationTitle会自动映射到UIKit导航栏的对应元素,无需额外配置。

方案2:保留Wrapper,同步NavigationItem

如果必须保留自定义ViewControllerWrapper,需要将hostingController的navigationItem同步到Wrapper自身,让UIKit导航栏能获取到SwiftUI定义的内容:

class ViewControllerWrapper<Content: View>: UIViewController {
    private var hostingController: UIHostingController<Content>!
    
    init(rootView: Content) {
        super.init(nibName: nil, bundle: nil)
        hostingController = UIHostingController(rootView: rootView)
        // 同步hostingController的navigationItem到当前Wrapper
        self.navigationItem = hostingController.navigationItem
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        addChild(hostingController)
        view.addSubview(hostingController.view)
        // 添加入局自动适配,避免屏幕尺寸变化时布局错乱
        hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        hostingController.didMove(toParent: self)
        // 移除多余的backBarButtonItem设置,避免干扰SwiftUI Toolbar渲染
    }
}

额外注意事项

  • 若使用SwiftUI 4+,确保.toolbar的placement参数(.cancellationAction、.confirmationAction)能正确映射到UIKit导航栏的对应位置;
  • 测试Popover的尺寸适配,确保Toolbar和内容区域都能完整显示。

内容的提问来源于stack exchange,提问作者Patrick Kuro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 06:18:13