SwiftUI使用ShareSheet出现控制台报错及iOS15适配问题
报错处理说明
你遇到的三个控制台日志都是iOS系统内部UIActivityViewController的实现问题,和你的业务代码无关,不需要额外处理,也不会影响App上线和功能正常运行:
- 第一个LayoutConstraints相关警告:是系统自己修改了UICollectionView管理的复用视图的自动布局属性触发的日志,社区大量开发者都遇到过该问题,苹果在后续iOS版本中已逐步修复这类日志输出。
- 第二个imageForImageDescriptor警告:是系统加载分享图标时内部调用了废弃方法产生的日志,属于系统级问题,无需业务侧处理。
- 第三个UICollectionViewRecursion日志:只是系统告知关闭了集合视图的递归触发日志打印,完全不影响功能逻辑。
实现方案优化建议
你选择用UIViewControllerRepresentable封装ShareSheet的思路是完全正确的,比直接获取rootViewController弹出的方式更符合SwiftUI的声明式开发规范,也天然避免了操作UIApplication.shared.windows的废弃问题,可从以下几个点做优化:
- 先修正你现有代码的语法错误:ListDataArray的dot数组中,DataArray初始化的cities参数后缺少逗号,且第二个DataArray的cities参数后多了右括号,修正后才能正常编译运行。
- 优先考虑系统原生方案:如果你的App最低支持iOS 16+,直接使用SwiftUI官方提供的
ShareLink组件即可,不需要自己封装任何UIKit组件,代码最简也不会产生额外兼容问题。 - 低版本兼容优化:如果需要兼容iOS 15及更低版本,只需在你现有的ShareSheet封装中补充iPad端的popover适配,避免iPad上出现显示异常或闪退问题。
优化后代码示例
语法错误修正
public struct ListDataArray { static let dot = [ DataArray( id: 1, cities: "Baltimore", name1: "John", name2: "Mike", isFavorite: false ), DataArray( id: 2, cities: "Frederick", name1: "Joe", name2: "Swift", isFavorite: false ), DataArray( id: 3, cities: "Catonsville", name1: "Susan", name2: "Oliver", isFavorite: false ) ] }
iOS 16+ 原生ShareLink实现
struct TestShareSheet3: View { var data: DataArray var body: some View { VStack { ShareLink( item: "\(data.cities)\n\(data.name1) - \(data.name2)", label: { Text("Share") .foregroundColor(.blue) .font(.title3) .padding() .background(Color.green) .cornerRadius(10) } ) } } }
iOS 15+ 兼容版ShareSheet实现
// 优化后的ShareSheet封装 struct ShareSheet: UIViewControllerRepresentable { typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void let activityItems: [Any] var applicationActivities: [UIActivity]? = nil var excludedActivityTypes: [UIActivity.ActivityType]? = nil var callback: Callback? = nil // 传入分享按钮的全局坐标,用于iPad端popover定位 var sourceRect: CGRect = .zero func makeUIViewController(context: Context) -> UIActivityViewController { let controller = UIActivityViewController( activityItems: activityItems, applicationActivities: applicationActivities ) controller.excludedActivityTypes = excludedActivityTypes controller.completionWithItemsHandler = callback // 适配iPad端弹出位置 if let popover = controller.popoverPresentationController, let rootView = UIApplication.shared.connectedScenes .filter({ $0.activationState == .foregroundActive }) .compactMap({ $0 as? UIWindowScene }) .first?.windows.first(where: \.isKeyWindow)?.rootViewController?.view { popover.sourceView = rootView popover.sourceRect = sourceRect } return controller } func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} } // 调用侧代码 struct TestShareSheet3: View { var data: DataArray @State private var showShareSheet = false @State private var buttonFrame = CGRect.zero var body: some View { VStack { Button { showShareSheet.toggle() } label: { Text("Share") .foregroundColor(.blue) .font(.title3) .padding() .background(Color.green) .cornerRadius(10) } // 读取按钮的全局坐标用于iPad定位 .background( GeometryReader { geo in Color.clear .onAppear { buttonFrame = geo.frame(in: .global) } .onChange(of: geo.frame(in: .global)) { newValue in buttonFrame = newValue } } ) .sheet(isPresented: $showShareSheet) { ShareSheet( activityItems: ["\(data.cities)\n\(data.name1) - \(data.name2)"], sourceRect: buttonFrame ) .presentationDetents([.medium, .large]) } } } }
全局弹出方式兼容iOS 15+
如果你确实需要使用全局弹出的方式,替换废弃的windows获取逻辑即可:
private func getRootViewController() -> UIViewController? { UIApplication.shared.connectedScenes .filter { $0.activationState == .foregroundActive } .compactMap { $0 as? UIWindowScene } .first?.windows .first(where: \.isKeyWindow)? .rootViewController }
内容的提问来源于stack exchange,提问作者Lian
相关产品推荐
相关产品推荐

