如何在APP的今日扩展组件中调用Safari打开输入框内的URL
解决Today Extension传递URL到主APP并打开内部浏览器的问题
我来帮你搞定这个需求——要把Today Extension输入框里的内容作为URL,传递到主APP并在内部打开浏览器,我们需要调整URL Scheme的传参逻辑,同时在主APP里处理这个传入的参数。下面是具体的实现步骤和修改后的代码:
一、修改Today Extension的代码,传递URL参数
首先我们要在goButton方法里,把输入框的内容处理成合法URL,然后通过自定义URL Scheme传递给主APP。这里要注意两个关键点:
- 验证并补全URL的协议头(比如用户输入
google.com时,自动补成https://google.com) - 对URL进行编码,避免特殊字符导致传参失败
修改后的TodayViewController.swift代码如下:
import UIKit import SafariServices import NotificationCenter // 移除粘贴内容中的空格的扩展 extension String { func replace(string:String, replacement:String) -> String { return self.replacingOccurrences(of: string, with: replacement, options: NSString.CompareOptions.literal, range: nil) } func removeWhitespace() -> String { return self.replace(string: " ", replacement: "") } // 新增:补全URL协议头的方法 func addHTTPSchemeIfNeeded() -> String { let trimmedString = self.trimmingCharacters(in: .whitespacesAndNewlines) if trimmedString.lowercased().hasPrefix("http://") || trimmedString.lowercased().hasPrefix("https://") { return trimmedString } return "https://\(trimmedString)" } } class TodayViewController: UIViewController, NCWidgetProviding { var clearNumber: String? override func viewDidLoad() { super.viewDidLoad() } func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) { // 执行更新视图所需的任何设置 // 若遇到错误,使用NCUpdateResult.Failed // 若无更新必要,使用NCUpdateResult.NoData // 若有更新,使用NCUpdateResult.NewData completionHandler(NCUpdateResult.newData) } @IBOutlet weak var textBox: UITextField! @IBAction func clearNumber(_ sender: Any) { if textBox.hasText == true { textBox.text = "" }else{ return } } @IBAction func pasteNumber(_ sender: Any) { if let myString = UIPasteboard.general.string { let pasteNumber = myString.removeWhitespace() textBox.insertText(pasteNumber) }else{ return } } @IBAction func goButton(_ sender: Any) { guard let inputText = textBox.text?.removeWhitespace().addHTTPSchemeIfNeeded(), let encodedURL = inputText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let appURL = URL(string: "main-screen://open-url?url=\(encodedURL)") else { // 输入内容无法转为合法URL,可添加提示 print("Invalid URL input") return } extensionContext?.open(appURL, completionHandler: { (success) in if (!success) { print("error: failed to open app from Today Extension") } }) } }
二、主APP中处理传入的URL并打开内部浏览器
接下来需要在主APP里接收这个URL参数,然后用SFSafariViewController打开对应的网页。根据你使用的是UIKit的AppDelegate还是SceneDelegate,选择对应的处理方式:
情况1:使用AppDelegate(iOS 12及以下,或兼容模式)
在AppDelegate.swift中添加以下方法:
import UIKit import SafariServices @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { // 解析URL参数 if url.scheme == "main-screen", url.host == "open-url", let urlParam = url.queryParameters?["url"], let targetURL = URL(string: urlParam) { // 打开SFSafariViewController let safariVC = SFSafariViewController(url: targetURL) safariVC.modalPresentationStyle = .fullScreen // 获取当前活跃的ViewController if let topVC = UIApplication.shared.windows.first?.rootViewController?.topMostViewController() { topVC.present(safariVC, animated: true) } return true } return false } } // 新增:获取最顶层ViewController的扩展 extension UIViewController { func topMostViewController() -> UIViewController { if let presented = self.presentedViewController { return presented.topMostViewController() } if let navigation = self as? UINavigationController { return navigation.visibleViewController?.topMostViewController() ?? navigation } if let tab = self as? UITabBarController { return tab.selectedViewController?.topMostViewController() ?? tab } return self } } // 新增:解析URL查询参数的扩展 extension URL { var queryParameters: [String: String]? { guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), let queryItems = components.queryItems else { return nil } return queryItems.reduce(into: [String: String]()) { result, item in result[item.name] = item.value } } }
情况2:使用SceneDelegate(iOS 13+)
在SceneDelegate.swift中添加以下方法:
import UIKit import SafariServices class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) { guard let url = URLContexts.first?.url else { return } // 解析URL参数 if url.scheme == "main-screen", url.host == "open-url", let urlParam = url.queryParameters?["url"], let targetURL = URL(string: urlParam) { // 打开SFSafariViewController let safariVC = SFSafariViewController(url: targetURL) safariVC.modalPresentationStyle = .fullScreen if let topVC = window?.rootViewController?.topMostViewController() { topVC.present(safariVC, animated: true) } } } } // 同样添加获取最顶层ViewController和解析URL参数的扩展,和上面AppDelegate里的一样 extension UIViewController { func topMostViewController() -> UIViewController { if let presented = self.presentedViewController { return presented.topMostViewController() } if let navigation = self as? UINavigationController { return navigation.visibleViewController?.topMostViewController() ?? navigation } if let tab = self as? UITabBarController { return tab.selectedViewController?.topMostViewController() ?? tab } return self } } extension URL { var queryParameters: [String: String]? { guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), let queryItems = components.queryItems else { return nil } return queryItems.reduce(into: [String: String]()) { result, item in result[item.name] = item.value } } }
三、额外配置:确保URL Scheme已正确注册
最后别忘了在主APP的Info.plist里注册自定义的URL Scheme(main-screen):
- 打开
Info.plist,右键选择"Add Row" - 键名选择
URL types,类型为Array - 在Array里添加一个Dictionary,其中:
URL Schemes(Array类型)添加字符串main-screenURL Identifier可以填你APP的Bundle ID,比如com.yourcompany.yourapp
这样整个流程就通了:用户在Today Extension输入框里输入URL(或粘贴),点击Go按钮,Extension会把编码后的URL通过自定义Scheme传递给主APP,主APP解析参数后打开内部浏览器加载该URL。
内容的提问来源于stack exchange,提问作者cs4alhaider
相关产品推荐
相关产品推荐

