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

SwiftUI中如何将Presentation Hosting Controller背景设为透明?

问题描述

我正在开发一个FullScreenModalView,该视图可接收任意固定宽高的视图并以模态形式展示。目前功能运行正常,但无法将Presentation Hosting Controller的背景设置为透明。尝试过设置自定义透明视图作为背景等方案,但均未生效。期望实现半透明背景的模态弹窗效果,然而在视图层级中可见Presentation Hosting Controller的背景为白色,需要将其改为透明色。

解决方案

问题核心是SwiftUI的fullScreenCover默认模态呈现样式会强制背景不透明,依赖视图层级hack的方案不稳定且易失效。以下是可靠的解决方式:

  1. 自定义ViewModifier,直接修改UIKit层面的模态呈现样式与背景色,从底层确保透明效果
  2. 在fullScreenCover的内容视图上应用该modifier,替代原有hack方案
  3. 移除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:12:54