logo

WhatsNewKit

一个 Swift 包,可以轻松展示你的应用程序的新功能。
从底层设计,可以完全根据你的需求进行自定义。

Swift Version Platforms
Build and Test Status Documentation Twitter Mastodon

Example

import SwiftUI
import WhatsNewKit

struct ContentView: View {

    var body: some View {
        NavigationView {
            // ...
        }
        .whatsNewSheet()
    }

}

功能

安装

Swift Package Manager

要使用 Apple 的 Swift Package Manager 进行集成,请将以下内容作为依赖项添加到你的 Package.swift

dependencies: [
    .package(url: "https://github.com/SvenTiigi/WhatsNewKit.git", from: "2.0.0")
]

或者,导航到你的 Xcode 项目,然后选择 Swift Packages,点击 “+” 图标并搜索 WhatsNewKit

示例

查看示例应用程序,了解 WhatsNewKit 的实际应用。只需打开 Example/Example.xcodeproj 并运行 "Example" 方案。

Example Applications

用法

目录

手动呈现

如果你希望手动呈现 WhatsNewView,你可以使用 sheet(whatsNew:) 修饰符。

struct ContentView: View {

    @State
    var whatsNew: WhatsNew? = WhatsNew(
        title: "WhatsNewKit",
        features: [
            .init(
                image: .init(
                    systemName: "star.fill",
                    foregroundColor: .orange
                ),
                title: "Showcase your new App Features",
                subtitle: "Present your new app features..."
            ),
            // ...
        ]
    )

    var body: some View {
        NavigationView {
            // ...
        }
        .sheet(
            whatsNew: self.$whatsNew
        )
    }

}

自动呈现

自动呈现模式允许你通过 SwiftUI Environment 简单地声明你的新功能,并且 WhatsNewKit 将负责呈现相应的 WhatsNewView

首先,将 .whatsNewSheet() 修饰符添加到 WhatsNewView 应该被呈现的视图上。

struct ContentView: View {

    var body: some View {
        NavigationView {
            // ...
        }
        // Automatically present a WhatsNewView, if needed.
        // The WhatsNew that should be presented to the user
        // is automatically retrieved from the `WhatsNewEnvironment`
        .whatsNewSheet()
    }

}

.whatsNewSheet() 修饰符使用 WhatsNewEnvironment 来检索一个可选的 WhatsNew 对象,该对象应该为当前版本呈现给用户。因此,你可以通过 environment 修饰符轻松配置 WhatsNewEnvironment

extension App: SwiftUI.App {

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(
                    \.whatsNew,
                    WhatsNewEnvironment(
                        // Specify in which way the presented WhatsNew Versions are stored.
                        // In default the `UserDefaultsWhatsNewVersionStore` is used.
                        versionStore: UserDefaultsWhatsNewVersionStore(),
                        // Pass a `WhatsNewCollectionProvider` or an array of WhatsNew instances
                        whatsNewCollection: self
                    )
                )
        }
    }

}

// MARK: - App+WhatsNewCollectionProvider

extension App: WhatsNewCollectionProvider {

    /// Declare your WhatsNew instances per version
    var whatsNewCollection: WhatsNewCollection {
        WhatsNew(
            version: "1.0.0",
            // ...
        )
        WhatsNew(
            version: "1.1.0",
            // ...
        )
        WhatsNew(
            version: "1.2.0",
            // ...
        )
    }

}

WhatsNewEnvironment

WhatsNewEnvironment 将负责确定应该为当前版本呈现给用户的匹配的 WhatsNew 对象。

如前面的示例所示,你可以通过指定 WhatsNewVersionStore 并提供 WhatsNewCollection 来初始化 WhatsNewEnvironment

// Initialize WhatsNewEnvironment by passing an array of WhatsNew Instances.
// UserDefaultsWhatsNewVersionStore is used as default WhatsNewVersionStore
let whatsNewEnvironment = WhatsNewEnvironment(
    whatsNewCollection: [
        WhatsNew(
            version: "1.0.0",
            // ...
        )
    ]
)

// Initialize WhatsNewEnvironment with NSUbiquitousKeyValueWhatsNewVersionStore
// which stores the presented versions in iCloud.
// WhatsNewCollection is provided by a `WhatsNewBuilder` closure
let whatsNewEnvironment = WhatsNewEnvironment(
    versionStore: NSUbiquitousKeyValueWhatsNewVersionStore(),
    whatsNewCollection: {
        WhatsNew(
            version: "1.0.0",
            // ...
        )
    }
)

此外,WhatsNewEnvironment 还包括补丁版本的后备方案。例如,当用户安装版本 1.0.1 并且你只声明了版本 1.0.0WhatsNew 时,环境将自动回退到版本 1.0.0,并在需要时向用户呈现 WhatsNewView

如果你希望进一步自定义 WhatsNewEnvironment 的行为,你可以轻松地对其进行子类化并覆盖 whatsNew() 函数。

class MyCustomWhatsNewEnvironment: WhatsNewEnvironment {

    /// Retrieve a WhatsNew that should be presented to the user, if available.
    override func whatsNew() -> WhatsNew? {
        // The current version
        let currentVersion = self.currentVersion
        // Your declared WhatsNew objects
        let whatsNewCollection = self.whatsNewCollection
        // The WhatsNewVersionStore used to determine the already presented versions
        let versionStore = self.whatsNewVersionStore
        // TODO: Determine WhatsNew that should be presented to the user...
    }

}

WhatsNewVersionStore

WhatsNewVersionStore 是一个协议类型,负责保存和检索已呈现给用户的版本。

let whatsNewVersionStore: WhatsNewVersionStore

// Save presented versions
whatsNewVersionStore.save(presentedVersion: "1.0.0")

// Retrieve presented versions
let presentedVersions = whatsNewVersionStore.presentedVersions

// Retrieve bool value if a given version has already been presented
let hasPresented = whatsNewVersionStore.hasPresented("1.0.0")

WhatsNewKit 附带三个预定义的实现

// Persists presented versions in the UserDefaults
let userDefaultsWhatsNewVersionStore = UserDefaultsWhatsNewVersionStore()

// Persists presented versions in iCloud using the NSUbiquitousKeyValueStore
let ubiquitousKeyValueWhatsNewVersionStore = NSUbiquitousKeyValueWhatsNewVersionStore()

// Stores presented versions in memory. Perfect for testing purposes
let inMemoryWhatsNewVersionStore = InMemoryWhatsNewVersionStore()

如果你已经有一个用于存储用户相关设置的特定实现(如 Realm 或 Core Data),你可以轻松地将现有实现应用于 WhatsNewVersionStore 协议。

NSUbiquitousKeyValueWhatsNewVersionStore

如果你正在使用 NSUbiquitousKeyValueWhatsNewVersionStore,请确保在 Xcode 项目的 “Signing & Capabilities” 部分启用 iCloud 键-值存储 功能。

iCloud Key-value storage

WhatsNew

以下部分解释了如何初始化 WhatsNew 结构,以便描述你的应用程序给定版本的新功能。

let whatsnew = WhatsNew(
    // The Version that relates to the features you want to showcase
    version: "1.0.0",
    // The title that is shown at the top
    title: "What's New",
    // The features you want to showcase
    features: [
        WhatsNew.Feature(
            image: .init(systemName: "star.fill"),
            title: "Title",
            subtitle: "Subtitle"
        )
    ],
    // The primary action that is used to dismiss the WhatsNewView
    primaryAction: WhatsNew.PrimaryAction(
        title: "Continue",
        backgroundColor: .accentColor,
        foregroundColor: .white,
        hapticFeedback: .notification(.success),
        onDismiss: {
            print("WhatsNewView has been dismissed")
        }
    ),
    // The optional secondary action that is displayed above the primary action
    secondaryAction: WhatsNew.SecondaryAction(
        title: "Learn more",
        foregroundColor: .accentColor,
        hapticFeedback: .selection,
        action: .openURL(
            .init(string: "https://github.com/SvenTiigi/WhatsNewKit")
        )
    )
)

WhatsNew.Version

WhatsNew.Version 指定了将某些功能引入你的应用程序的版本。

// Initialize with major, minor, and patch
let version = WhatsNew.Version(
    major: 1,
    minor: 0,
    patch: 0
)

// Initialize by string literal
let version: WhatsNew.Version = "1.0.0"

// Initialize WhatsNew Version by using the current version of your bundle
let version: WhatsNew.Version = .current()

WhatsNew.Title

WhatsNew.Title 表示在功能上方呈现的标题文本。

// Initialize by string literal
let title: WhatsNew.Title = "Continue"

// Initialize with text and foreground color
let title = WhatsNew.Title(
    text: "Continue",
    foregroundColor: .primary
)

// On >= iOS 15 initialize with AttributedString using Markdown
let title = WhatsNew.Title(
    text: try AttributedString(
        markdown: "What's **New**"
    )
)

WhatsNew.Feature

WhatsNew.Feature 描述了你的应用程序的特定功能,通常由图像、标题和副标题组成。

let feature = WhatsNew.Feature(
    image: .init(
        systemName: "wand.and.stars"
    ),
    title: "New Design",
    subtitle: .init(
        try AttributedString(
            markdown: "An awesome new _Design_"
        )
    )
)

WhatsNew.PrimaryAction

WhatsNew.PrimaryAction 允许你配置用于关闭呈现的 WhatsNewView 的主按钮的行为。

let primaryAction = WhatsNew.PrimaryAction(
    title: "Continue",
    backgroundColor: .blue,
    foregroundColor: .white,
    hapticFeedback: .notification(.success),
    onDismiss: {
        print("WhatsNewView has been dismissed")
    }
)

注意:HapticFeedback 仅在 iOS 上执行

WhatsNew.SecondaryAction

WhatsNew.SecondaryAction 显示在 WhatsNew.PrimaryAction 之上,可以在初始化 WhatsNew 实例时选择性地提供,并允许你呈现一个额外的视图、执行一个自定义操作或打开一个 URL。

// SecondaryAction that presents a View
let secondaryActionPresentAboutView = WhatsNew.SecondaryAction(
    title: "Learn more",
    foregroundColor: .blue,
    hapticFeedback: .selection,
    action: .present {
        AboutView()
    }
)

// SecondaryAction that opens a URL
let secondaryActionOpenURL = WhatsNew.SecondaryAction(
    title: "Read more",
    foregroundColor: .blue,
    hapticFeedback: .selection,
    action: .open(
        url: .init(string: "https://github.com/SvenTiigi/WhatsNewKit")
    )
)

// SecondaryAction with custom execution
let secondaryActionCustom = WhatsNew.SecondaryAction(
    title: "Custom",
    action: .custom { presentationMode in
        // ...
    }
)

注意:HapticFeedback 仅在 iOS 上执行

布局

WhatsNewKit 允许你以各种方式调整呈现的 WhatsNewView 的布局。

最简单的方法是改变 WhatsNew.Layout.default 实例。

WhatsNew.Layout.default.featureListSpacing = 35

当使用自动呈现样式时,你可以在初始化 WhatsNewEnvironment 时提供一个默认布局。

.environment(
    \.whatsNew,
    .init(
        defaultLayout: WhatsNew.Layout(
            showsScrollViewIndicators: true,
            featureListSpacing: 35
        ),
        whatsNew: self
    )
)

或者,你可以在自动或手动呈现 WhatsNewView 时传递一个 WhatsNew.Layout

.whatsNewSheet(
    layout: WhatsNew.Layout(
        contentPadding: .init(
            top: 80,
            leading: 0,
            bottom: 0,
            trailing: 0
        )
    )
)
.sheet(
    whatsNew: self.$whatsNew,
    layout: WhatsNew.Layout(
        footerActionSpacing: 20
    )
)

WhatsNewViewController

当使用 UIKitAppKit 时,你可以使用 WhatsNewViewController

let whatsNewViewController = WhatsNewViewController(
    whatsNew: WhatsNew(
        version: "1.0.0",
        // ...
    ),
    layout: WhatsNew.Layout(
        contentSpacing: 80
    )
)

如果你希望仅在 WhatsNew 实例的版本尚未呈现时才呈现 WhatsNewViewController,你可以使用方便的可失败的初始化程序。

// Verify WhatsNewViewController is available for presentation
guard let whatsNewViewController = WhatsNewViewController(
    whatsNew: WhatsNew(
        version: "1.0.0",
        // ...
    ),
    versionStore: UserDefaultsWhatsNewVersionStore()
) else {
    // Version of WhatsNew has already been presented
    return
}

// Present WhatsNewViewController
// Version will be automatically saved in the provided
// WhatsNewVersionStore when the WhatsNewViewController gets dismissed
self.present(whatsNewViewController, animated: true)