SwiftUI中如何将Presentation Hosting Controller背景设为透明?
问题描述
我正在开发一个FullScreenModalView,该视图可接收任意固定宽高的视图并以模态形式展示。目前功能运行正常,但无法将Presentation Hosting Controller的背景设置为透明。尝试过设置自定义透明视图作为背景等方案,但均未生效。期望实现半透明背景的模态弹窗效果,然而在视图层级中可见Presentation Hosting Controller的背景为白色,需要将其改为透明色。
解决方案
问题核心是SwiftUI的fullScreenCover默认模态呈现样式会强制背景不透明,依赖视图层级hack的方案不稳定且易失效。以下是可靠的解决方式:
- 自定义
ViewModifier,直接修改UIKit层面的模态呈现样式与背景色,从底层确保透明效果 - 在
fullScreenCover的内容视图上应用该modifier,替代原有hack方案 - 移除
FullScreenModalView中冗余的透明背景设置
修改后的完整代码
ContentView
import SwiftUI struct ContentView: View { @State private var isShowingCommonMenu = false var body: some View { VStack { Button { isShowingCommonMenu.toggle() } label: { Text("GO") } .fullScreenCover(isPresented: $isShowingCommonMenu) { NavigationStack { GeometryReader { geometry in FullScreenModalView(isShowingCommonMenu: $isShowingCommonMenu, childViewWidth: (geometry.size.width - geometry.size.width/10), childViewHeight: geometry.size.height) { TheViewToBeLoadedView() } } } .clearModalBackground() // 应用自定义透明背景modifier } } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.orange) .padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
FullScreenModalView
import SwiftUI struct FullScreenModalView<Content: View>: View { @Binding var isShowingCommonMenu: Bool var childViewWidth: CGFloat var childViewHeight: CGFloat let content: () -> Content var body: some View { ZStack { Color.black.opacity(0.4) .edgesIgnoringSafeArea(.all) .onTapGesture { dismiss() } VStack { content() } .frame(width: childViewWidth, height: childViewHeight) .background(Color.white) .cornerRadius(10) .overlay( Button { dismiss() } label: { DismissButton() }, alignment: .topTrailing) } .ignoresSafeArea(.keyboard) } private func dismiss() { isShowingCommonMenu = false } } struct FullScreenModalView_Previews: PreviewProvider { static var previews: some View { FullScreenModalView(isShowingCommonMenu: .constant(true), childViewWidth: 200, childViewHeight: 200) { Text("This is a custom preview view") } } } struct DismissButton: View { var body: some View { ZStack { Circle() .frame(width: 30, height: 30) .foregroundColor(.white) .opacity(0.6) Image(systemName: "xmark") .imageScale(.medium) .frame(width: 44, height: 44) .foregroundColor(Color(.label)) } } } // 自定义透明背景Modifier struct ClearBackgroundViewModifier: ViewModifier { func body(content: Content) -> some View { content .presentationBackground(.clear) // SwiftUI层面设置透明背景 .onAppear { // 获取顶层模态控制器,设置UIKit层面的呈现样式 guard let topController = UIApplication.shared.topViewController else { return } topController.modalPresentationStyle = .overFullScreen topController.view.backgroundColor = .clear } } } extension View { func clearModalBackground() -> some View { self.modifier(ClearBackgroundViewModifier()) } } // UIApplication扩展:获取顶层控制器 extension UIApplication { var topViewController: UIViewController? { let keyWindow = connectedScenes .filter({$0.activationState == .foregroundActive}) .map({$0 as? UIWindowScene}) .compactMap({$0}) .first?.windows .filter({$0.isKeyWindow}).first return keyWindow?.rootViewController?.topMostViewController } } // UIViewController扩展:递归获取最顶层控制器 extension UIViewController { var topMostViewController: UIViewController { if let presented = presentedViewController { return presented.topMostViewController } return self } }
TheViewToBeLoadedView
import SwiftUI struct TheViewToBeLoadedView: View { var body: some View { GeometryReader { geometry in Text("TheViewToBeLoadedView") .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) .background(Color.yellow) } } } struct TheViewToBeLoadedView_Previews: PreviewProvider { static var previews: some View { TheViewToBeLoadedView() } }
关键说明
- 同时在SwiftUI和UIKit两层设置透明背景,避免单一层面设置的局限性
- 通过顶层控制器获取逻辑,替代依赖视图层级的不稳定hack
- 代码结构更简洁,后续维护性更强
内容的提问来源于stack exchange,提问作者Tulon
相关产品推荐
相关产品推荐

