使用 Sparkle 实现 macOS 应用自定义更新弹窗

简介

源码 源码1

Sparkle 是 macOS 平台上一个优秀的自动更新框架。虽然 Sparkle 提供了默认的更新界面,但有时我们需要自定义更新弹窗以匹配应用的设计风格。本文将介绍如何使用 Sparkle 实现自定义更新界面。

实现步骤

1. 创建自定义更新窗口

首先需要创建一个继承自 NSPanel 的自定义窗口类:

swift 复制代码
class UpdateNotificationWindow: NSPanel {
    private let stackView = NSStackView()
    private let titleLabel = NSTextField()
    private let messageLabel = NSTextField()
    private let progressBar = NSProgressIndicator()
    private var buttons: [NSButton] = []
    
    init() {
        super.init(contentRect: NSRect(x: 0, y: 0, width: 320, height: 240),
                  styleMask: [.nonactivatingPanel, .titled, .fullSizeContentView],
                  backing: .buffered,
                  defer: false)
        
        setupWindow()
        setupUI()
    }
}

2. 实现自定义用户驱动

需要创建一个实现 SPUUserDriver 协议的类来处理更新流程中的各个状态:

swift 复制代码
final class CustomUserDriver: NSObject, SPUUserDriver {
    private var notificationWindow: UpdateNotificationWindow?
    
    // 检查更新
    func showUserInitiatedUpdateCheck(cancellation: @escaping () -> Void) {
        ensureNotificationWindow().showChecking {
            cancellation()
        }
    }
    
    // 找到更新
    func showUpdateFound(with appcastItem: SUAppcastItem, state: SPUUserUpdateState) async -> SPUUserUpdateChoice {
        return await withCheckedContinuation { continuation in
            DispatchQueue.main.async {
                self.ensureNotificationWindow().showUpdateFound { updateChoice, type in
                    continuation.resume(returning: updateChoice)
                }
            }
        }
    }
    
    // 显示下载进度
    func showDownloadDidReceiveData(ofLength length: UInt64) {
        let progress = Double(currentProgress) / Double(downloadSize) * 100
        ensureNotificationWindow().showDownloadProgress(progress)
    }
}

3. 配置更新控制器

创建一个更新控制器类来管理 Sparkle 更新器:

swift 复制代码
final class UpdateController {
    static let shared = UpdateController()
    let updater: SPUUpdater
    private let userDriver: CustomUserDriver
    
    private init() {
        userDriver = CustomUserDriver()
        updater = SPUUpdater(hostBundle: Bundle.main,
                           applicationBundle: Bundle.main,
                           userDriver: userDriver,
                           delegate: nil)
        
        try? updater.start()
        updater.automaticallyChecksForUpdates = true
        updater.updateCheckInterval = 3600 * 6
        updater.setFeedURL(URL(string: "https://your-update-url/appcast.xml"))
    }
}

主要功能

1. 检查更新界面

  • 显示检查更新状态
  • 提供取消按钮

2. 发现更新界面

  • 显示新版本信息
  • 提供"立即安装"和"下次启动时安装"选项

3. 下载进度界面

  • 显示实时下载进度
  • 进度条可视化展示

4. 安装准备界面

  • 提示用户重启应用进行安装
  • 显示安装确认按钮

界面美化

为了让更新窗口更美观,我们可以:

  1. 添加圆角和阴影:
swift 复制代码
contentView?.layer?.cornerRadius = 12
hasShadow = true
  1. 自定义按钮样式:
swift 复制代码
private func createStyledButton(title: String, isPrimary: Bool = false) -> NSButton {
    let button = NSButton(title: title, target: nil, action: nil)
    button.layer?.cornerRadius = 6
    
    if isPrimary {
        button.contentTintColor = .white
        button.layer?.backgroundColor = NSColor.controlAccentColor.cgColor
    } else {
        button.contentTintColor = .black
        button.layer?.backgroundColor = NSColor.controlBackgroundColor.cgColor
    }
    
    return button
}

总结

通过自定义 Sparkle 的更新界面,我们可以:

  1. 提供与应用一致的设计风格
  2. 更好地控制更新流程
  3. 提供更好的用户体验

完整的实现代码可以参考上面的示例。通过这种方式,我们可以为 macOS 应用提供一个既美观又实用的自动更新体验。

注意事项

  1. 确保正确处理所有更新状态
  2. 适当处理错误情况
  3. 保持界面响应性
  4. 提供清晰的用户反馈

希望这篇文章对你实现自定义 Sparkle 更新界面有所帮助!

相关推荐
广州华水科技6 分钟前
单北斗GNSS变形监测在基础设施安全中的应用与维护
前端
星栈14 分钟前
Rust 全栈项目里,我写了一个不再重复造轮子的泛型表格组件
前端·前端框架·开源
008爬虫实战录14 分钟前
【码上爬】 题九:webpack调试 堆栈分析
前端·webpack·node.js
爱滑雪的码农15 分钟前
React Native 完整开发全流程(从零到上线)
javascript·react native·react.js
HwJack2020 分钟前
HarmonyOS APP开发中ArkTS/JS 类型错误全景拆解
javascript·华为·harmonyos
子琦啊25 分钟前
构造函数、this指向和原型链机制
javascript·算法·贴图
Maimai108081 小时前
React 多步骤表单工程化落地:从 Zod Schema、React Hook Form 到 Zustand 持久化
前端·javascript·react.js·前端框架·状态模式
程序员码歌1 小时前
我是怎么部署开源 AI 编程助手 OpenCode,并在两个真实场景使用起来的
前端·人工智能·后端
Maimai108081 小时前
React Query + Zustand 正确结合方式:不要把接口数据复制进 Store
前端·javascript·react.js·前端框架·web3·状态模式
天才熊猫君1 小时前
层叠上下文 z-index 的简单理解
前端