import UIKit /// 悬浮窗实现类 /// 使用 UIWindow 创建应用内悬浮窗口 class FloatingWindow: UIWindow { // MARK: - 属性 private weak var delegate: FloatingWindowDelegate? private var floatingViewController: FloatingViewController? private var isMinimized = true private var lastPosition = CGPoint(x: 100, y: 100) // MARK: - 初始化 init(delegate: FloatingWindowDelegate) { self.delegate = delegate // 创建窗口 super.init(frame: CGRect(x: 100, y: 100, width: 300, height: 120)) setupWindow() setupViewController() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - 私有方法 /// 设置窗口属性 private func setupWindow() { // 设置窗口层级,确保在最顶层显示 windowLevel = UIWindow.Level.alert + 1 backgroundColor = UIColor.clear // 设置窗口为可见但不成为主窗口 isHidden = true // 设置圆角 layer.cornerRadius = 12 layer.masksToBounds = true } /// 设置视图控制器 private func setupViewController() { floatingViewController = FloatingViewController() floatingViewController?.delegate = self rootViewController = floatingViewController } // MARK: - 公共方法 /// 显示悬浮窗 func show() { isHidden = false makeKeyAndVisible() // 添加显示动画 alpha = 0 UIView.animate(withDuration: 0.3) { self.alpha = 1 } delegate?.floatingWindowDidChangeState(isMinimized ? "minimized" : "expanded") } /// 隐藏悬浮窗 func hide() { UIView.animate(withDuration: 0.3, animations: { self.alpha = 0 }) { _ in self.isHidden = true self.resignKey() } delegate?.floatingWindowDidChangeState("hidden") } /// 更新内容 func updateContent(_ data: FloatingWindowContentData) { floatingViewController?.updateContent(data) } /// 设置位置 func setPosition(x: Double, y: Double) { let newPosition = CGPoint(x: x, y: y) lastPosition = newPosition UIView.animate(withDuration: 0.3) { self.frame.origin = newPosition } } /// 切换最小化/展开状态 func toggleMinimized() { isMinimized.toggle() let newSize: CGSize if isMinimized { newSize = CGSize(width: 120, height: 60) } else { newSize = CGSize(width: 300, height: 120) } UIView.animate(withDuration: 0.3) { self.frame.size = newSize } floatingViewController?.setMinimized(isMinimized) delegate?.floatingWindowDidChangeState(isMinimized ? "minimized" : "expanded") } } // MARK: - FloatingViewControllerDelegate extension FloatingWindow: FloatingViewControllerDelegate { func floatingViewControllerDidTap() { toggleMinimized() delegate?.floatingWindowDidClick() } func floatingViewControllerDidPan(translation: CGPoint) { let newOrigin = CGPoint( x: lastPosition.x + translation.x, y: lastPosition.y + translation.y ) // 限制在屏幕范围内 let screenBounds = UIScreen.main.bounds let constrainedX = max(0, min(newOrigin.x, screenBounds.width - frame.width)) let constrainedY = max(0, min(newOrigin.y, screenBounds.height - frame.height)) frame.origin = CGPoint(x: constrainedX, y: constrainedY) } func floatingViewControllerDidEndPan() { lastPosition = frame.origin } func floatingViewControllerDidRequestClose() { hide() delegate?.floatingWindowDidClose() } } /// 悬浮窗视图控制器 private class FloatingViewController: UIViewController { // MARK: - 属性 weak var delegate: FloatingViewControllerDelegate? // UI 组件 private let containerView = UIView() private let sourceTextLabel = UILabel() private let translatedTextLabel = UILabel() private let languageInfoLabel = UILabel() private let timestampLabel = UILabel() private let closeButton = UIButton(type: .system) // 手势识别器 private var panGestureRecognizer: UIPanGestureRecognizer! private var tapGestureRecognizer: UITapGestureRecognizer! private var isMinimized = true // MARK: - 生命周期 override func viewDidLoad() { super.viewDidLoad() setupUI() setupGestures() } // MARK: - 私有方法 /// 设置 UI private func setupUI() { view.backgroundColor = UIColor.clear // 容器视图 containerView.backgroundColor = UIColor.black.withAlphaComponent(0.8) containerView.layer.cornerRadius = 12 containerView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(containerView) // 源文本标签 sourceTextLabel.textColor = .white sourceTextLabel.font = UIFont.systemFont(ofSize: 14) sourceTextLabel.numberOfLines = 2 sourceTextLabel.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(sourceTextLabel) // 翻译文本标签 translatedTextLabel.textColor = .systemBlue translatedTextLabel.font = UIFont.systemFont(ofSize: 14, weight: .medium) translatedTextLabel.numberOfLines = 2 translatedTextLabel.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(translatedTextLabel) // 语言信息标签 languageInfoLabel.textColor = .lightGray languageInfoLabel.font = UIFont.systemFont(ofSize: 10) languageInfoLabel.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(languageInfoLabel) // 时间戳标签 timestampLabel.textColor = .lightGray timestampLabel.font = UIFont.systemFont(ofSize: 10) timestampLabel.textAlignment = .right timestampLabel.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(timestampLabel) // 关闭按钮 closeButton.setTitle("×", for: .normal) closeButton.setTitleColor(.white, for: .normal) closeButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .bold) closeButton.translatesAutoresizingMaskIntoConstraints = false closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside) containerView.addSubview(closeButton) setupConstraints() } /// 设置约束 private func setupConstraints() { NSLayoutConstraint.activate([ // 容器视图 containerView.topAnchor.constraint(equalTo: view.topAnchor), containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), // 关闭按钮 closeButton.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 4), closeButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -4), closeButton.widthAnchor.constraint(equalToConstant: 20), closeButton.heightAnchor.constraint(equalToConstant: 20), // 源文本标签 sourceTextLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 8), sourceTextLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8), sourceTextLabel.trailingAnchor.constraint(equalTo: closeButton.leadingAnchor, constant: -4), // 翻译文本标签 translatedTextLabel.topAnchor.constraint(equalTo: sourceTextLabel.bottomAnchor, constant: 4), translatedTextLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8), translatedTextLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8), // 语言信息标签 languageInfoLabel.topAnchor.constraint(equalTo: translatedTextLabel.bottomAnchor, constant: 4), languageInfoLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8), languageInfoLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -4), // 时间戳标签 timestampLabel.topAnchor.constraint(equalTo: translatedTextLabel.bottomAnchor, constant: 4), timestampLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8), timestampLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -4) ]) } /// 设置手势识别器 private func setupGestures() { // 拖拽手势 panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) view.addGestureRecognizer(panGestureRecognizer) // 点击手势 tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) view.addGestureRecognizer(tapGestureRecognizer) } // MARK: - 手势处理 @objc private func handlePan(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: view.superview) switch gesture.state { case .changed: delegate?.floatingViewControllerDidPan(translation: translation) case .ended, .cancelled: delegate?.floatingViewControllerDidEndPan() default: break } } @objc private func handleTap(_ gesture: UITapGestureRecognizer) { delegate?.floatingViewControllerDidTap() } @objc private func closeButtonTapped() { delegate?.floatingViewControllerDidRequestClose() } // MARK: - 公共方法 /// 更新内容 func updateContent(_ data: FloatingWindowContentData) { sourceTextLabel.text = data.sourceText.isEmpty ? "正在识别..." : data.sourceText translatedTextLabel.text = data.translatedText.isEmpty ? "翻译中..." : data.translatedText languageInfoLabel.text = "\(data.sourceLanguage) → \(data.targetLanguage)" // 格式化时间戳 if data.timestamp > 0 { let date = Date(timeIntervalSince1970: TimeInterval(data.timestamp / 1000)) let formatter = DateFormatter() formatter.dateFormat = "HH:mm:ss" timestampLabel.text = formatter.string(from: date) } // 中间结果的视觉提示 translatedTextLabel.alpha = data.isIntermediate ? 0.7 : 1.0 } /// 设置最小化状态 func setMinimized(_ minimized: Bool) { isMinimized = minimized // 在最小化状态下隐藏部分元素 sourceTextLabel.isHidden = minimized languageInfoLabel.isHidden = minimized timestampLabel.isHidden = minimized closeButton.isHidden = minimized // 调整翻译文本标签的约束 if minimized { translatedTextLabel.numberOfLines = 1 translatedTextLabel.font = UIFont.systemFont(ofSize: 12, weight: .medium) } else { translatedTextLabel.numberOfLines = 2 translatedTextLabel.font = UIFont.systemFont(ofSize: 14, weight: .medium) } } } /// 悬浮窗视图控制器代理协议 private protocol FloatingViewControllerDelegate: AnyObject { func floatingViewControllerDidTap() func floatingViewControllerDidPan(translation: CGPoint) func floatingViewControllerDidEndPan() func floatingViewControllerDidRequestClose() }