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

如何在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):

  1. 打开Info.plist,右键选择"Add Row"
  2. 键名选择URL types,类型为Array
  3. 在Array里添加一个Dictionary,其中:
    • URL Schemes(Array类型)添加字符串main-screen
    • URL Identifier可以填你APP的Bundle ID,比如com.yourcompany.yourapp

这样整个流程就通了:用户在Today Extension输入框里输入URL(或粘贴),点击Go按钮,Extension会把编码后的URL通过自定义Scheme传递给主APP,主APP解析参数后打开内部浏览器加载该URL。

内容的提问来源于stack exchange,提问作者cs4alhaider

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:10:50