Swift分享扩展在Google News中分享时崩溃问题排查
问题描述
我开发的Swift分享扩展支持接收URL和文本内容,在Mail、Safari、NetNewsWire等应用中均可正常工作,但在iOS 17.2及更高版本的Google News中分享时会出现异常:扩展视图无法完全渲染,仅显示半透明背景,且会陷入卡死状态,必须强制关闭Google News才能恢复。
调试过程中遇到的问题:
- 添加Logger语句后无日志生成
- 实体设备上无法通过Xcode调试扩展
- iOS模拟器无法安装Google News,无法复现场景
即使在Safari等正常工作的场景中,扩展也会打印一条错误日志:
-[_EXSinkLoadOperator loadItemForTypeIdentifier:completionHandler:expectedValueClass:options:] nil expectedValueClass allowing {( _EXItemProviderSandboxedResource, NSData, ... )}
相关代码
分享扩展视图控制器
class ShareViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem, let itemProvider = extensionItem.attachments?.first else { self.close() return } if itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier) { itemProvider.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { urlCoded, error in if let error { self.close() return } if let url = urlCoded as? URL { DispatchQueue.main.async { self.renderView(url) } } else { self.close() return } } } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.text.identifier) { itemProvider.loadItem(forTypeIdentifier: UTType.text.identifier, options: nil) { providedText, error in if let error { self.close() return } guard let text = providedText as? String, let url = self.extractURL(text) else { self.close() return } DispatchQueue.main.async { self.renderView(url) } } } else { self.close() return } } private func renderView(_ url: URL) { self.saveLink(url: url) // host the SwiftU view let contentView = UIHostingController(rootView: ShareView(url: url)) self.addChild(contentView) self.view.addSubview(contentView.view) contentView.view.backgroundColor = .clear self.view.backgroundColor = .clear // set up constraints contentView.view.translatesAutoresizingMaskIntoConstraints = false contentView.view.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true contentView.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true contentView.view.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true contentView.view.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: { _ in self.close() }) } private func saveLink(url: URL) { // writes to a .plist file that's shared with the main app } private func close() { self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) } private func extractURL(_ text: String) -> URL? { do { let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) let matches = detector.matches( in: text, options: [], range: NSRange(location: 0, length: text.utf16.count) ) if let firstMatch = matches.first, let range = Range(firstMatch.range, in: text) { let url = String(text[range]) return URL(string: url) } } catch { print("Unable to extract URL from text: \(error)") } return nil } }
扩展激活规则(Info.plist)
<key>NSExtensionActivationRule</key> <dict> <key>NSExtensionActivationSupportsText</key> <true/> <key>NSExtensionActivationSupportsWebURLWithMaxCount</key> <integer>1</integer> </dict>
解决方案
1. 遍历所有附件,避免仅依赖第一个
Google News可能在extensionItem.attachments中返回多个资源,第一个未必是你需要的URL/文本,修改viewDidLoad逻辑遍历所有附件:
override func viewDidLoad() { super.viewDidLoad() guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else { close() return } // 遍历所有附件,找到第一个符合条件的资源 for itemProvider in extensionItem.attachments ?? [] { if itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier) { handleURLItemProvider(itemProvider) return } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.text.identifier) { handleTextItemProvider(itemProvider) return } } close() } // 拆分URL处理逻辑 private func handleURLItemProvider(_ provider: NSItemProvider) { provider.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] result, error in guard let self = self else { return } if let error { self.close() return } // 处理iOS 17+沙盒资源类型 var targetURL: URL? if let sandboxedResource = result as? NSObject, let resourceURL = sandboxedResource.value(forKey: "resourceURL") as? URL { targetURL = resourceURL } else if let directURL = result as? URL { targetURL = directURL } if let url = targetURL { DispatchQueue.main.async { self.renderView(url) } } else { self.close() } } } // 拆分文本处理逻辑 private func handleTextItemProvider(_ provider: NSItemProvider) { provider.loadItem(forTypeIdentifier: UTType.text.identifier, options: nil) { [weak self] result, error in guard let self = self else { return } if let error { self.close() return } guard let text = result as? String, let url = self.extractURL(text) else { self.close() return } DispatchQueue.main.async { self.renderView(url) } } }
2. 替换Timer为视图主动触发关闭
原代码中依赖Timer延迟关闭扩展,可能在Google News的环境下Timer无法正常触发,导致卡死。修改SwiftUI视图,添加完成回调:
// ShareView.swift struct ShareView: View { let url: URL let onComplete: () -> Void var body: some View { // 你的视图内容 Button("完成") { onComplete() } } }
然后在renderView中使用回调关闭扩展:
private func renderView(_ url: URL) { self.saveLink(url: url) let contentView = UIHostingController(rootView: ShareView(url: url, onComplete: { [weak self] in self?.close() })) self.addChild(contentView) self.view.addSubview(contentView.view) contentView.view.backgroundColor = .clear self.view.backgroundColor = .clear // 约束配置不变 contentView.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ contentView.view.topAnchor.constraint(equalTo: self.view.topAnchor), contentView.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor), contentView.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), contentView.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor) ]) contentView.didMove(toParent: self) }
3. 扩展激活规则,覆盖更多资源类型
修改Info.plist中的激活规则,添加更多URL相关的支持类型,确保Google News的资源能匹配到:
<key>NSExtensionActivationRule</key> <dict> <key>NSExtensionActivationSupportsText</key> <true/> <key>NSExtensionActivationSupportsWebURLWithMaxCount</key> <integer>1</integer> <key>NSExtensionActivationSupportsWebPageWithMaxCount</key> <integer>1</integer> <key>NSExtensionActivationSupportsURLWithMaxCount</key> <integer>1</integer> </dict>
4. 修复共享沙盒权限问题
如果saveLink方法写入共享plist时存在权限问题,会导致扩展卡死。确保主App和扩展加入同一个App Group,并且使用正确的共享目录路径:
private func saveLink(url: URL) { guard let groupURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "你的AppGroupID") else { return } let plistURL = groupURL.appendingPathComponent("SharedLinks.plist") // 写入逻辑 }
调试建议
- 使用
os.Logger替代print,确保日志能输出到设备日志:import os.log let logger = OSLog(subsystem: "你的扩展BundleID", category: "ShareExtension") os_log("提取URL失败: %@", log: logger, type: .error, error.localizedDescription) - 实体设备上,通过Xcode的
Window > Devices and Simulators选择设备,点击View Device Logs,搜索扩展BundleID查看崩溃日志。 - 给所有错误分支添加明确的
close()调用,避免扩展陷入无限等待状态。
内容的提问来源于stack exchange,提问作者Scarysize
相关产品推荐
相关产品推荐

