如何获取SwiftUI按钮精确sourceRect以在UIKit中展示Popover
解决SwiftUI按钮Popover位置偏差问题
问题根源
- 你直接传入全局坐标系的
frame,但Popover的sourceRect是基于sourceView(当前UIViewController的view)的本地坐标系,全局坐标会包含状态栏、导航栏等额外高度,导致位置偏移。 - 包裹按钮的
GeometryReader会占据整个List单元格的空间,它的frame并非按钮的真实点击区域,而是单元格的大小,自然会让Popover指向错误位置。
解决方案
方案1:坐标转换修正(快速修改)
直接在UIViewController的showActivityVC方法中,把传入的全局坐标转换为当前view的本地坐标:
func showActivityVC(sourceRect: CGRect) { let activityVC = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) if let popOver = activityVC.popoverPresentationController { popOver.sourceView = self.view! // 将全局坐标系的rect转换为当前view的本地坐标系 let localRect = self.view.convert(sourceRect, from: nil) popOver.sourceRect = localRect } present(activityVC, animated: true) }
方案2:获取按钮真实frame(更精准)
用PreferenceKey捕获按钮的实际frame,替代GeometryReader的单元格frame:
- 定义PreferenceKey:
struct ButtonFrameKey: PreferenceKey { static var defaultValue: CGRect = .zero static func reduce(value: inout CGRect, nextValue: () -> CGRect) { value = nextValue() } }
- 修改SwiftUI按钮代码(在EDWrapperView中添加
@State var buttonFrame: CGRect?):
List { Section(header: Text("Export")) { Button(action: { guard let frame = buttonFrame else { return } // 转换为全局坐标后传给UIKit控制器 let globalFrame = UIApplication.shared.windows.first?.convert(frame, from: hostingVC.view) ?? .zero exportDestinationVC.showActivityVC(sourceRect: globalFrame) }, label: { HStack(alignment: .firstTextBaseline) { Spacer() Image(systemName: "square.and.arrow.up") Text("Export") Spacer() } }) .contentShape(Rectangle()) // 用背景GeometryReader捕获按钮本地frame .background( GeometryReader { geometry in Color.clear .preference(key: ButtonFrameKey.self, value: geometry.frame(in: .local)) } ) .onPreferenceChange(ButtonFrameKey.self) { newFrame in buttonFrame = newFrame } } }
方案3:直接指定sourceView为HostingView(最优简化)
既然HostingView是UIViewController的子视图,直接把Popover的sourceView设为hostingVC.view,此时只需传入按钮在HostingView本地坐标系的frame:
// UIViewController中的方法 func showActivityVC(sourceRect: CGRect) { let activityVC = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) if let popOver = activityVC.popoverPresentationController { popOver.sourceView = self.hostingVC.view popOver.sourceRect = sourceRect // 无需坐标转换,直接用按钮在HostingView的本地frame } present(activityVC, animated: true) } // SwiftUI按钮中直接传捕获到的buttonFrame Button(action: { guard let frame = buttonFrame else { return } exportDestinationVC.showActivityVC(sourceRect: frame) }, label: { /* ... */ })
内容的提问来源于stack exchange,提问作者vomi
相关产品推荐
相关产品推荐

