mit platform SPM compatible Tests codebeat badge

🔔 更新提示 (UpgradeAlert)

轻松更新您的应用程序

目录

问题

解决方案

警告
isRequired = true 设置为 true 会使应用程序瘫痪,直到更新为止。

截图

适用于 iOS

ios

适用于 macOS

ios

示例

import UpgradeAlert

// Skip checking for updates if the app is running in beta (e.g., simulator or TestFlight)
guard !Bundle.isBeta else {
    Swift.print("App is beta or simulator, skip checking for update")
    return
}

// Configure the alert
UpgradeAlert.config = UAConfig(
    isRequired: false, // Require users to update
    alertTitle: "Update required", // Alert title
    alertMessage: { appName, version in "Version \(version) is out!" }, // Alert message
    laterButtonTitle: "Later", // Skip button title
    updateButtonTitle: "Update Now" // Go to App Store button
)

// Check Apple endpoint to see if there is a new update
UpgradeAlert.checkForUpdates { outcome in
    switch outcome {
    case .error(let error):
        Swift.print("Error: \(error.localizedDescription)")
    case .notNow:
        Swift.print("User chose to update later.")
    case .updateNotNeeded:
        Swift.print("App is up-to-date.")
    case .didOpenAppStoreToUpdate:
        Swift.print("Redirected user to App Store for update.")
    }
}

用于调试

// UA prompt alert test. so we can see how it looks etc.
UpgradeAlert.showAlert(appInfo: .init(version: "1.0.1", trackViewUrl: "https://apps.apple.com/app/id/com.MyCompany.MyApp"))

常见问题 (FAQ)

问: 什么是强制更新墙 (Upgrade-Wall)?
答: 强制更新墙 (Upgrade-Wall) (或 更新墙 (Update-Wall)) 是一种系统,可以阻止移动应用程序用户使用旧版本的应用程序。 它确保所有用户都在应用程序的最新版本上运行。

问: 为什么我们需要强制更新墙 (Upgrade-Wall)?
答: 当您需要用户由于重大更改、安全问题或推广新功能而更新到新版本时,强制更新墙 (Upgrade-Wall) 是必要的。例如

在这些情况下,强制更新墙 (Upgrade-Wall) 确保用户更新到最新版本,从而提供一致且安全的体验。

问: 如何实施强制更新墙 (Upgrade-Wall)?
答: 可以使用两种策略实施强制更新墙 (Upgrade-Wall):强制更新墙 (Hard Upgrade-Wall) 和 强制更新墙 (Soft Upgrade-Wall)。

两种策略都涉及在打开应用程序时向用户显示弹出窗口或提示。您可以通过利用提供强制更新墙 (Upgrade-Wall) 功能的现有解决方案来简化此过程。

注意事项 (Gotchas)

待办事项 (Todo)

public final class UpgradeAlert {
    public static func checkForUpdates(completion: @escaping (Result<Void, UAError>) -> Void) {
        DispatchQueue.global(qos: .background).async {
            getAppInfo { result in
                switch result {
                case .success(let appInfo):
                    let needsUpdate = ComparisonResult.compareVersion(
                        current: Bundle.version ?? "0",
                        appStore: appInfo.version
                    ) == .requiresUpgrade
                    guard needsUpdate else {
                        completion(.success(()))
                        return
                    }
                    DispatchQueue.main.async {
                        showAlert(appInfo: appInfo, completion: completion)
                    }
                case .failure(let error):
                    completion(.failure(error))
                }
            }
        }
    }
}
private static func getAppInfo(completion: @escaping (Result<AppInfo, UAError>) -> Void) {
    guard let url = requestURL else {
        completion(.failure(.invalidURL))
        return
    }
    let task = URLSession.shared.dataTask(with: url) { data, _, error in
        if let error = error {
            completion(.failure(.invalidResponse(description: error.localizedDescription)))
            return
        }
        guard let data = data else {
            completion(.failure(.invalidResponse(description: "No data received")))
            return
        }
        do {
            let result = try JSONDecoder().decode(LookupResult.self, from: data)
            if let appInfo = result.results.first {
                completion(.success(appInfo))
            } else {
                completion(.failure(.invalidResponse(description: "No app info available")))
            }
        } catch {
            completion(.failure(.invalidResponse(description: error.localizedDescription)))
        }
    }
    task.resume()
}
extension NSAlert {
    internal static func present(
        messageText: String,
        informativeText: String,
        style: NSAlert.Style,
        buttons: [String],
        completion: ((NSApplication.ModalResponse) -> Void)? = nil
    ) {
        let alert = NSAlert()
        alert.messageText = messageText
        alert.informativeText = informativeText
        alert.alertStyle = style
        buttons.forEach { alert.addButton(withTitle: $0) }
        if let window = NSApplication.shared.windows.first {
            alert.beginSheetModal(for: window, completionHandler: completion)
        } else {
            print("Error: No window available to present alert.")
        }
    }
}
import SwiftUI

@available(iOS 13.0, macOS 10.15, *)
public struct UpgradeAlertView: View {
    @State private var isPresented = false
    public var body: some View {
        Text("") // Placeholder
            .alert(isPresented: $isPresented) {
                Alert(
                    title: Text(config.alertTitle),
                    message: Text(config.alertMessage(nil, appInfo.version)),
                    primaryButton: .default(Text(config.updateButtonTitle), action: {
                        // Handle update action
                    }),
                    secondaryButton: config.isRequired ? nil : .cancel(Text(config.laterButtonTitle))
                )
            }
            .onAppear {
                isPresented = true
            }
    }
}