{"id":3090,"url":"https://github.com/SvenTiigi/WhatsNewKit","last_synced_at":"2025-08-06T16:32:24.718Z","repository":{"id":37622986,"uuid":"134316697","full_name":"SvenTiigi/WhatsNewKit","owner":"SvenTiigi","description":"Showcase your awesome new app features 📱","archived":false,"fork":false,"pushed_at":"2024-10-10T10:54:11.000Z","size":8386,"stargazers_count":3942,"open_issues_count":3,"forks_count":193,"subscribers_count":38,"default_branch":"main","last_synced_at":"2024-12-03T06:03:42.519Z","etag":null,"topics":["ios","macos","swift","swift-package","swiftui","whatsnew"],"latest_commit_sha":null,"homepage":"https://sventiigi.github.io/WhatsNewKit/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SvenTiigi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"SvenTiigi"}},"created_at":"2018-05-21T19:39:47.000Z","updated_at":"2024-12-01T03:17:14.000Z","dependencies_parsed_at":"2023-12-24T00:27:19.449Z","dependency_job_id":"2a266f60-e217-4fa9-b85d-30fdeddcd458","html_url":"https://github.com/SvenTiigi/WhatsNewKit","commit_stats":{"total_commits":572,"total_committers":16,"mean_commits":35.75,"dds":"0.045454545454545414","last_synced_commit":"6157c77e8be9b3d2310bc680681b61a8d9e290ac"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SvenTiigi%2FWhatsNewKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SvenTiigi%2FWhatsNewKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SvenTiigi%2FWhatsNewKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SvenTiigi%2FWhatsNewKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SvenTiigi","download_url":"https://codeload.github.com/SvenTiigi/WhatsNewKit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228923759,"owners_count":17992574,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ios","macos","swift","swift-package","swiftui","whatsnew"],"created_at":"2024-01-05T20:16:31.103Z","updated_at":"2024-12-09T16:31:20.524Z","avatar_url":"https://github.com/SvenTiigi.png","language":"Swift","funding_links":["https://github.com/sponsors/SvenTiigi"],"categories":["Walkthrough / Intro / Tutorial","Libs","Introduction","Whats-New","Swift","HarmonyOS","2、技术示范","Utilities and Extensions","Utility [🔝](#readme)","UI Component"],"sub_categories":["Web View","Utility","Content","Windows Manager","2.2 开发必备的非官方控件"],"readme":"\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"Assets/logo.png\" width=\"30%\" alt=\"logo\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003e\n    WhatsNewKit\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n    A Swift Package to easily showcase your new app features.\n    \u003cbr/\u003e\n    It's designed from the ground up to be fully customized to your needs.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n   \u003ca href=\"https://swiftpackageindex.com/SvenTiigi/WhatsNewKit\"\u003e\n    \u003cimg src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FSvenTiigi%2FWhatsNewKit%2Fbadge%3Ftype%3Dswift-versions\" alt=\"Swift Version\"\u003e\n   \u003c/a\u003e\n   \u003ca href=\"https://swiftpackageindex.com/SvenTiigi/WhatsNewKit\"\u003e\n    \u003cimg src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FSvenTiigi%2FWhatsNewKit%2Fbadge%3Ftype%3Dplatforms\" alt=\"Platforms\"\u003e\n   \u003c/a\u003e\n   \u003cbr/\u003e\n   \u003ca href=\"https://github.com/SvenTiigi/WhatsNewKit/actions/workflows/build_and_test.yml\"\u003e\n       \u003cimg src=\"https://github.com/SvenTiigi/WhatsNewKit/actions/workflows/build_and_test.yml/badge.svg\" alt=\"Build and Test Status\"\u003e\n   \u003c/a\u003e\n   \u003ca href=\"https://sventiigi.github.io/WhatsNewKit/documentation/whatsnewkit/\"\u003e\n       \u003cimg src=\"https://img.shields.io/badge/Documentation-DocC-blue\" alt=\"Documentation\"\u003e\n   \u003c/a\u003e\n   \u003ca href=\"https://twitter.com/SvenTiigi/\"\u003e\n      \u003cimg src=\"https://img.shields.io/badge/Twitter-@SvenTiigi-blue.svg?style=flat\" alt=\"Twitter\"\u003e\n   \u003c/a\u003e\n   \u003ca href=\"https://mastodon.world/@SvenTiigi\"\u003e\n      \u003cimg src=\"https://img.shields.io/badge/Mastodon-@SvenTiigi-8c8dff.svg?style=flat\" alt=\"Mastodon\"\u003e\n   \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cimg align=\"right\" width=\"315\" src=\"Assets/example.png\" alt=\"Example\"\u003e\n\n```swift\nimport SwiftUI\nimport WhatsNewKit\n\nstruct ContentView: View {\n\n    var body: some View {\n        NavigationView {\n            // ...\n        }\n        .whatsNewSheet()\n    }\n\n}\n```\n\n## Features\n\n- [x] Easily present your new app features 🤩\n- [x] Automatic \u0026 Manual presentation mode ✅\n- [x] Support for SwiftUI, UIKit and AppKit 🧑‍🎨\n- [x] Runs on iOS, macOS and visionOS 📱 🖥 👓\n- [x] Adjustable layout 🔧\n\n## Installation\n\n### Swift Package Manager\n\nTo integrate using Apple's [Swift Package Manager](https://swift.org/package-manager/), add the following as a dependency to your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/SvenTiigi/WhatsNewKit.git\", from: \"2.0.0\")\n]\n```\n\nOr navigate to your Xcode project then select `Swift Packages`, click the “+” icon and search for `WhatsNewKit`.\n\n## Example\n\nCheck out the example application to see WhatsNewKit in action. Simply open the `Example/Example.xcodeproj` and run the \"Example\" scheme.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg width=\"95%\" src=\"Assets/example-app.png\" alt=\"Example Applications\"\u003e\n\u003c/p\u003e\n\n## Usage\n\n### Table of contents\n\n- [Manual Presentation](https://github.com/SvenTiigi/WhatsNewKit/tree/main#manual-presentation)\n- [Automatic Presentation](https://github.com/SvenTiigi/WhatsNewKit/tree/main#automatic-presentation)\n- [WhatsNewEnvironment](https://github.com/SvenTiigi/WhatsNewKit/tree/main#whatsnewenvironment)\n- [WhatsNewVersionStore](https://github.com/SvenTiigi/WhatsNewKit/tree/main#whatsnewversionstore)\n- [WhatsNew](https://github.com/SvenTiigi/WhatsNewKit/tree/main#whatsnew)\n- [Layout](https://github.com/SvenTiigi/WhatsNewKit/tree/main#layout)\n- [WhatsNewViewController](https://github.com/SvenTiigi/WhatsNewKit/tree/main#whatsnewviewcontroller)\n\n### Manual Presentation\n\nIf you wish to manually present a `WhatsNewView` you can make use of the `sheet(whatsNew:)` modifier.\n\n```swift\nstruct ContentView: View {\n\n    @State\n    var whatsNew: WhatsNew? = WhatsNew(\n        title: \"WhatsNewKit\",\n        features: [\n            .init(\n                image: .init(\n                    systemName: \"star.fill\",\n                    foregroundColor: .orange\n                ),\n                title: \"Showcase your new App Features\",\n                subtitle: \"Present your new app features...\"\n            ),\n            // ...\n        ]\n    )\n\n    var body: some View {\n        NavigationView {\n            // ...\n        }\n        .sheet(\n            whatsNew: self.$whatsNew\n        )\n    }\n\n}\n```\n\n### Automatic Presentation\n\nThe automatic presentation mode allows you to simply declare your new features via the SwiftUI Environment and WhatsNewKit will take care to present the corresponding `WhatsNewView`.\n\nFirst add a `.whatsNewSheet()` modifier to the view where the `WhatsNewView` should be presented on.\n\n```swift\nstruct ContentView: View {\n\n    var body: some View {\n        NavigationView {\n            // ...\n        }\n        // Automatically present a WhatsNewView, if needed.\n        // The WhatsNew that should be presented to the user\n        // is automatically retrieved from the `WhatsNewEnvironment`\n        .whatsNewSheet()\n    }\n\n}\n```\n\nThe `.whatsNewSheet()` modifier is making use of the `WhatsNewEnvironment` to retrieve an optional WhatsNew object that should be presented to the user for the current version. Therefore you can easily configure the `WhatsNewEnvironment` via the `environment` modifier.\n\n```swift\nextension App: SwiftUI.App {\n\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n                .environment(\n                    \\.whatsNew,\n                    WhatsNewEnvironment(\n                        // Specify in which way the presented WhatsNew Versions are stored.\n                        // In default the `UserDefaultsWhatsNewVersionStore` is used.\n                        versionStore: UserDefaultsWhatsNewVersionStore(),\n                        // Pass a `WhatsNewCollectionProvider` or an array of WhatsNew instances\n                        whatsNewCollection: self\n                    )\n                )\n        }\n    }\n\n}\n\n// MARK: - App+WhatsNewCollectionProvider\n\nextension App: WhatsNewCollectionProvider {\n\n    /// Declare your WhatsNew instances per version\n    var whatsNewCollection: WhatsNewCollection {\n        WhatsNew(\n            version: \"1.0.0\",\n            // ...\n        )\n        WhatsNew(\n            version: \"1.1.0\",\n            // ...\n        )\n        WhatsNew(\n            version: \"1.2.0\",\n            // ...\n        )\n    }\n\n}\n```\n\n## WhatsNewEnvironment\n\nThe `WhatsNewEnvironment` will take care to determine the matching WhatsNew object that should be presented to the user for the current version.\n\nAs seen in the previous example you can initialize a `WhatsNewEnvironment` by specifying the `WhatsNewVersionStore` and providing a `WhatsNewCollection`.\n\n```swift\n// Initialize WhatsNewEnvironment by passing an array of WhatsNew Instances.\n// UserDefaultsWhatsNewVersionStore is used as default WhatsNewVersionStore\nlet whatsNewEnvironment = WhatsNewEnvironment(\n    whatsNewCollection: [\n        WhatsNew(\n            version: \"1.0.0\",\n            // ...\n        )\n    ]\n)\n\n// Initialize WhatsNewEnvironment with NSUbiquitousKeyValueWhatsNewVersionStore\n// which stores the presented versions in iCloud.\n// WhatsNewCollection is provided by a `WhatsNewBuilder` closure\nlet whatsNewEnvironment = WhatsNewEnvironment(\n    versionStore: NSUbiquitousKeyValueWhatsNewVersionStore(),\n    whatsNewCollection: {\n        WhatsNew(\n            version: \"1.0.0\",\n            // ...\n        )\n    }\n)\n```\n\nAdditionally, the `WhatsNewEnvironment` includes a fallback for patch versions. For example when a user installs version `1.0.1` and you only have declared a `WhatsNew` for version `1.0.0` the environment will automatically fallback to version `1.0.0` and present the `WhatsNewView` to the user if needed.\n\nIf you wish to further customize the behaviour of the `WhatsNewEnvironment` you can easily subclass it and override the `whatsNew()` function.\n\n```swift\nclass MyCustomWhatsNewEnvironment: WhatsNewEnvironment {\n\n    /// Retrieve a WhatsNew that should be presented to the user, if available.\n    override func whatsNew() -\u003e WhatsNew? {\n        // The current version\n        let currentVersion = self.currentVersion\n        // Your declared WhatsNew objects\n        let whatsNewCollection = self.whatsNewCollection\n        // The WhatsNewVersionStore used to determine the already presented versions\n        let versionStore = self.whatsNewVersionStore\n        // TODO: Determine WhatsNew that should be presented to the user...\n    }\n\n}\n```\n\n## WhatsNewVersionStore\n\nA `WhatsNewVersionStore` is a protocol type which is responsible for saving and retrieving versions that have been presented to the user.\n\n```swift\nlet whatsNewVersionStore: WhatsNewVersionStore\n\n// Save presented versions\nwhatsNewVersionStore.save(presentedVersion: \"1.0.0\")\n\n// Retrieve presented versions\nlet presentedVersions = whatsNewVersionStore.presentedVersions\n\n// Retrieve bool value if a given version has already been presented\nlet hasPresented = whatsNewVersionStore.hasPresented(\"1.0.0\")\n```\n\nWhatsNewKit comes along with three predefined implementations:\n\n```swift\n// Persists presented versions in the UserDefaults\nlet userDefaultsWhatsNewVersionStore = UserDefaultsWhatsNewVersionStore()\n\n// Persists presented versions in iCloud using the NSUbiquitousKeyValueStore\nlet ubiquitousKeyValueWhatsNewVersionStore = NSUbiquitousKeyValueWhatsNewVersionStore()\n\n// Stores presented versions in memory. Perfect for testing purposes\nlet inMemoryWhatsNewVersionStore = InMemoryWhatsNewVersionStore()\n```\n\nIf you already have a specific implementation to store user related settings like Realm or Core Data you can easily adopt your existing implementation to the `WhatsNewVersionStore` protocol.\n\n### NSUbiquitousKeyValueWhatsNewVersionStore\n\nIf you are making use of the `NSUbiquitousKeyValueWhatsNewVersionStore` please ensure to enable the [iCloud Key-value storage](https://developer.apple.com/library/archive/documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesigningForKey-ValueDataIniCloud.html) capability in the \"Signing \u0026 Capabilities\" section of your Xcode project.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"Assets/icloud-key-value-storage.png\" alt=\"iCloud Key-value storage\"\u003e\n\u003c/p\u003e\n\n## WhatsNew\n\nThe following sections explains how a `WhatsNew` struct can be initialized in order to describe the new features for a given version of your app.\n\n```swift\nlet whatsnew = WhatsNew(\n    // The Version that relates to the features you want to showcase\n    version: \"1.0.0\",\n    // The title that is shown at the top\n    title: \"What's New\",\n    // The features you want to showcase\n    features: [\n        WhatsNew.Feature(\n            image: .init(systemName: \"star.fill\"),\n            title: \"Title\",\n            subtitle: \"Subtitle\"\n        )\n    ],\n    // The primary action that is used to dismiss the WhatsNewView\n    primaryAction: WhatsNew.PrimaryAction(\n        title: \"Continue\",\n        backgroundColor: .accentColor,\n        foregroundColor: .white,\n        hapticFeedback: .notification(.success),\n        onDismiss: {\n            print(\"WhatsNewView has been dismissed\")\n        }\n    ),\n    // The optional secondary action that is displayed above the primary action\n    secondaryAction: WhatsNew.SecondaryAction(\n        title: \"Learn more\",\n        foregroundColor: .accentColor,\n        hapticFeedback: .selection,\n        action: .openURL(\n            .init(string: \"https://github.com/SvenTiigi/WhatsNewKit\")\n        )\n    )\n)\n```\n\n### WhatsNew.Version\n\nThe `WhatsNew.Version` specifies the version that has introduced certain features to your app.\n\n```swift\n// Initialize with major, minor, and patch\nlet version = WhatsNew.Version(\n    major: 1,\n    minor: 0,\n    patch: 0\n)\n\n// Initialize by string literal\nlet version: WhatsNew.Version = \"1.0.0\"\n\n// Initialize WhatsNew Version by using the current version of your bundle\nlet version: WhatsNew.Version = .current()\n```\n\n### WhatsNew.Title\n\nA `WhatsNew.Title` represents the title text that is rendered above the features.\n\n```swift\n// Initialize by string literal\nlet title: WhatsNew.Title = \"Continue\"\n\n// Initialize with text and foreground color\nlet title = WhatsNew.Title(\n    text: \"Continue\",\n    foregroundColor: .primary\n)\n\n// On \u003e= iOS 15 initialize with AttributedString using Markdown\nlet title = WhatsNew.Title(\n    text: try AttributedString(\n        markdown: \"What's **New**\"\n    )\n)\n```\n\n### WhatsNew.Feature\n\nA `WhatsNew.Feature` describe a specific feature of your app and generally consist of an image, title, and subtitle.\n\n```swift\nlet feature = WhatsNew.Feature(\n    image: .init(\n        systemName: \"wand.and.stars\"\n    ),\n    title: \"New Design\",\n    subtitle: .init(\n        try AttributedString(\n            markdown: \"An awesome new _Design_\"\n        )\n    )\n)\n```\n\n### WhatsNew.PrimaryAction\n\nThe `WhatsNew.PrimaryAction` allows you to configure the behaviour of the primary button which is used to dismiss the presented `WhatsNewView`\n\n```swift\nlet primaryAction = WhatsNew.PrimaryAction(\n    title: \"Continue\",\n    backgroundColor: .blue,\n    foregroundColor: .white,\n    hapticFeedback: .notification(.success),\n    onDismiss: {\n        print(\"WhatsNewView has been dismissed\")\n    }\n)\n```\n\n\u003e Note: HapticFeedback will only be executed on iOS\n\n### WhatsNew.SecondaryAction\n\nA `WhatsNew.SecondaryAction` which is displayed above the `WhatsNew.PrimaryAction` can be optionally supplied when initializing a `WhatsNew` instance and allows you to present an additional View, perform a custom action or open an URL.\n\n```swift\n// SecondaryAction that presents a View\nlet secondaryActionPresentAboutView = WhatsNew.SecondaryAction(\n    title: \"Learn more\",\n    foregroundColor: .blue,\n    hapticFeedback: .selection,\n    action: .present {\n        AboutView()\n    }\n)\n\n// SecondaryAction that opens a URL\nlet secondaryActionOpenURL = WhatsNew.SecondaryAction(\n    title: \"Read more\",\n    foregroundColor: .blue,\n    hapticFeedback: .selection,\n    action: .open(\n        url: .init(string: \"https://github.com/SvenTiigi/WhatsNewKit\")\n    )\n)\n\n// SecondaryAction with custom execution\nlet secondaryActionCustom = WhatsNew.SecondaryAction(\n    title: \"Custom\",\n    action: .custom { presentationMode in\n        // ...\n    }\n)\n```\n\n\u003e Note: HapticFeedback will only be executed on iOS\n\n## Layout\n\nWhatsNewKit allows you to adjust the layout of a presented `WhatsNewView` in various ways.\n\nThe most simple way is by mutating the `WhatsNew.Layout.default` instance.\n\n```swift\nWhatsNew.Layout.default.featureListSpacing = 35\n```\n\nWhen using the automatic presentation style you can supply a default layout when initializing the WhatsNewEnvironment.\n\n```swift\n.environment(\n    \\.whatsNew,\n    .init(\n        defaultLayout: WhatsNew.Layout(\n            showsScrollViewIndicators: true,\n            featureListSpacing: 35\n        ),\n        whatsNew: self\n    )\n)\n```\n\nAlternatively you can pass a `WhatsNew.Layout` when automatically or manually presenting the WhatsNewView\n\n```swift\n.whatsNewSheet(\n    layout: WhatsNew.Layout(\n        contentPadding: .init(\n            top: 80,\n            leading: 0,\n            bottom: 0,\n            trailing: 0\n        )\n    )\n)\n```\n\n```swift\n.sheet(\n    whatsNew: self.$whatsNew,\n    layout: WhatsNew.Layout(\n        footerActionSpacing: 20\n    )\n)\n```\n\n## WhatsNewViewController\n\nWhen using `UIKit` or `AppKit` you can make use of the `WhatsNewViewController`.\n\n```swift\nlet whatsNewViewController = WhatsNewViewController(\n    whatsNew: WhatsNew(\n        version: \"1.0.0\",\n        // ...\n    ),\n    layout: WhatsNew.Layout(\n        contentSpacing: 80\n    )\n)\n```\n\nIf you wish to present a `WhatsNewViewController` only if the version of the WhatsNew instance has not been presented you can make use of the convenience failable initializer.\n\n```swift\n// Verify WhatsNewViewController is available for presentation\nguard let whatsNewViewController = WhatsNewViewController(\n    whatsNew: WhatsNew(\n        version: \"1.0.0\",\n        // ...\n    ),\n    versionStore: UserDefaultsWhatsNewVersionStore()\n) else {\n    // Version of WhatsNew has already been presented\n    return\n}\n\n// Present WhatsNewViewController\n// Version will be automatically saved in the provided\n// WhatsNewVersionStore when the WhatsNewViewController gets dismissed\nself.present(whatsNewViewController, animated: true)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSvenTiigi%2FWhatsNewKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSvenTiigi%2FWhatsNewKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSvenTiigi%2FWhatsNewKit/lists"}