[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-11405":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":15,"stars7d":15,"stars30d":16,"stars90d":14,"forks30d":14,"starsTrendScore":17,"compositeScore":18,"rankGlobal":9,"rankLanguage":9,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":9,"pushedAt":9,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":14,"starSnapshotCount":14,"syncStatus":27,"lastSyncTime":28,"discoverSource":29},11405,"notelet","mykolaharmash\u002Fnotelet","mykolaharmash","SwiftUI component for displaying rich release notes inside an app",null,"Swift",487,19,3,0,6,67,18,3.9,"MIT License",false,"main",true,[],"2026-06-12 02:02:31","\u003Cp align=\"center\">\n  \u003Cimg src=\"Assets\u002Flogo.png\" alt=\"Notelet logo\" width=\"84\" height=\"84\" \u002F>\n\u003C\u002Fp>\n\n\u003Ch3 align=\"center\">Notelet\u003C\u002Fh3>\n\n\u003Cp align=\"center\">SwiftUI package for showing rich release notes in iOS apps\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n    \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FiOS-17.0+-2980b9.svg\" \u002F>\n    \u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FiPadOS-17.0+-8e44ad.svg\" \u002F>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n  \u003Cvideo src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fdd636105-82bb-4fd2-8d5a-ada9e9e4c38c\" controls muted playsinline height=\"600px\" width=\"auto\">\u003C\u002Fvideo>\n\u003C\u002Fp>\n\n> [!TIP]\n> Once you're done here, check my other project [AppView](https:\u002F\u002Fappview.dev) to get an optimized website for your app. It will boost downloads coming from web search and AI suggestions.\n\n## Installation (Swift Package Manager)\n\n1. In Xcode, open your app project.\n2. Go to **File → Add Package Dependencies...**.\n3. Enter the repo URL https:\u002F\u002Fgithub.com\u002Fmykolaharmash\u002Fnotelet into the search.\n4. Click on \"Add package\".\n5. Add `Notelet` to your app target.\n\n\n## Usage\n\nHere is a full usage example with comments to get started quickly. Below are more detailed explanations of how the component works and all available APIs.\n\n```swift\nimport SwiftUI\n\u002F\u002F Import Notelet\nimport Notelet\n\n\u002F**\n* Release notes for different versions of the app.\n* There are three supported note types: .list, .media(kind: .image) and .media(kind: .video).\n*\u002F\nprivate let notes: [NoteletVersionNotes] = [\n    .init(\n        version: \"1.2.0\",\n        items: [\n            .list(\n                title: \"What's new\",\n                rows: [\n                    .init(\n                        symbolSystemName: \"wand.and.stars\",\n                        title: \"New editor tools\",\n                        description: \"More formatting options with less taps.\"\n                    ),\n                    .init(\n                        symbolSystemName: \"lock.shield.fill\",\n                        title: \"Privacy update\",\n                        description: \"Sensitive data handling is now stricter.\"\n                    )\n                ]\n            ),\n            .media(\n                kind: .image,\n                url: URL(string: \"https:\u002F\u002Fexample.com\u002Fnotes-image.jpg\")!,\n                title: \"Updated UI\",\n                description: \"Refreshed visuals across key screens.\"\n            ),\n            .media(\n                kind: .video,\n                url: URL(string: \"https:\u002F\u002Fexample.com\u002Fnotes-video.mp4\")!,\n                title: \"Quick walkthrough\",\n                description: \"A short clip showing the new flow.\"\n            )\n        ]\n    ),\n    .init(\n        version: \"1.2.1\",\n        items: [\n            .media(\n                kind: .image,\n                url: URL(string: \"https:\u002F\u002Fexample.com\u002Farchive.jpg\")!,\n                title: \"Journal Archive\",\n                description: \"Archive entries without deleting them permanently\"\n            )\n        ]\n    )\n]\n\nstruct ContentView: View {\n    var body: some View {\n        Text(\"Hello, World!\")\n        \u002F**\n        * Add the sheet to the view hierarchy.\n        * It takes the full list of notes for all versions\n        * and the version you'd like to present to the user.\n        * There are more parameters, described below.\n        *\u002F\n        .noteletSheet(\n            notes: notes,\n            \u002F**\n            * `version: .current` takes the current version from the app's bundle,\n            * and tries to show notes for it if they are present inside `notes`.\n            * It will also automatically save this version as \"viewed\" when\n            * the sheet is dismissed.\n            *\u002F\n            version: .current\n        )\n    }\n}\n```\n\n## Release notes for the current version\n\nThe most common use case is to show release notes for the latest app update. Attach `noteletSheet()` to your view and use `version: .current`.\n\n```swift\n.noteletSheet(\n    notes: [...],\n    version: .current\n)\n```\n\nNotelet will automatically get the current app version from the bundle and show release notes. When the user dismisses the sheet, it will mark the current version as seen, so the user doesn't see the same release notes again.\n\n> [!IMPORTANT]\n> Notelet reads the current app version from your app bundle, specifically the `CFBundleShortVersionString` property. It's the version you see in Xcode inside the \"General\" tab for a target. Make sure to use this app version when adding it to the `notes` list; it might be different from the version you have in App Store Connect.\n\nWhen specifying version directly as `version: .current`, the release notes sheet will appear automatically when the view hierarchy renders. You can also trigger the sheet manually via a State property.\n\n```swift\nstruct ContentView: View {\n    @State private var presentedVersion: NoteletPresentedVersion? = nil\n\n    var body: some View {\n        VStack {\n            Text(\"Hello, world!\")\n\n            Button(\"Show Release Notes\") {\n                presentedVersion = .current\n            }\n        }\n        .noteletSheet(\n            notes: notes,\n            version: presentedVersion\n        )\n    }\n}\n```\n\n## Manually show notes for a specific version\n\nYou may also want to show release notes for app versions other than the current one, for example, to give users access to a historical changelog in app settings.\n\n```swift\n .noteletSheet(\n    notes: notes,\n    version: .v(\"1.2.1\")\n)\n```\n\n## Defining release notes\n\nThe list of release notes for each app version is an array of `NoteletVersionNotes` structs. Each struct specifies the `version` and `items`.\n\nYou can have as many notes inside `items` for each version as needed, they will be arranged in a paging view with navigation either using the \"Next\" button or by swiping.\n\nThe array can be a global constant or a static property on some top level view. I personally keep it in a global constant.\n\n```swift\nlet RELEASE_NOTES: [NoteletVersionNotes] = [\n    .init(\n        version: \"1.2.0\",\n        items: [ ... ] \u002F\u002F \u003C-- release notes go here\n    )\n]\n```\n\n> [!TIP]\n> `NoteletVersionNotes` and all its nested types conform to `Codable`, so you can load notes remotely at runtime.\n\n## Note types\n\nThe `items` property for each app version is an array of `NoteletVersionNoteItem` enum cases. There are three supported note types:\n\n* `.list`\n* `.media(kind: .image)`\n* `.media(kind: .video)`\n\n### List\n\nShows a title and a set of rows, where each row has an SF Symbol, a title, and a description.\n\nThis type of note is ideal for showing the user a quick summary of the update.\n\n```swift\nlet RELEASE_NOTES: [NoteletVersionNotes] = [\n    .init(\n        version: \"1.2.0\",\n        items: [\n            .list(\n                title: \"What's new\",\n                rows: [\n                    .init(\n                        symbolSystemName: \"sparkles\",\n                        title: \"Polished details\",\n                        description: \"Small UI upgrades throughout the app.\"\n                    )\n                ]\n            )\n        ]\n    )\n]\n```\n\n> [!TIP]\n> All text properties are defined internally as `LocalizedStringResource` so they automatically end up in the string catalog ready for localization.\n\n### Image\n\nLoads and shows an image along with a title and description. Great for updates that need a bit more visual context.\n\nThe image will be wrapped in a square container. Parts of the image will be cropped to fill the container if it has an aspect ratio other than 1:1. It's best to use square images or at least have the main subject in the center so it's not affected by the cropping.\n\n```swift\nlet RELEASE_NOTES: [NoteletVersionNotes] = [\n    .init(\n        version: \"1.2.0\",\n        items: [\n            .media(\n                kind: .image,\n                url: URL(string: \"https:\u002F\u002Fexample.com\u002Fpreview.jpg\")!,\n                title: \"UI preview\",\n                description: \"A quick look at the redesign.\"\n            )\n        ]\n    )\n]\n```\n\n### Video\n\nLoads and plays a video along with a title and description. Great for cases when a feature needs mini-onboarding, or when you just want to highlight it for the user.\n\nSame as with images, the video will be wrapped in a square container, so parts of it might be cropped if it has a non-square aspect ratio.\n\n```swift\nlet RELEASE_NOTES: [NoteletVersionNotes] = [\n    .init(\n        version: \"1.2.0\",\n        items: [\n            .media(\n                kind: .video,\n                url: URL(string: \"https:\u002F\u002Fexample.com\u002Fdemo.mp4\")!,\n                title: \"Feature demo\",\n                description: \"See the flow in action.\"\n            )\n        ]\n    )\n]\n```\n\n## Sheet dismiss callback\n\n`.noteletSheet()` has an optional `onDismiss` parameter similar to the standard `.sheet()`.\n\n> [!TIP]\n> `onDismiss` is a good place to ask users for a review right after you showed them the app updates.\n\n```swift\n@Environment(\\.requestReview) private var requestReview\n\n...\n\n.noteletSheet(\n    notes: notes,\n    version: .current,\n    onDismiss: {\n        requestReview()\n    }\n)\n```\n\n## Additional configuration\n\n`.noteletSheet()` accepts an optional `NoteletConfiguration` struct where you can customize button labels and accent color.\n\n```swift\n.noteletSheet(\n    notes: notes,\n    version: .current,\n    configuration: .init(\n        nextButtonLabel: \"Continue\",\n        doneButtonLabel: \"Got it\",\n        accentColor: .orange\n    )\n)\n```\n\n## Latest viewed version storage\n\nWhen using `version: .current`, Notelet automatically marks the current version as seen by saving it to `UserDefaults`.\n\nIn some cases, you might also want to save it manually. For example, I don't want new users to see the release notes sheet right after onboarding, so when onboarding is done, I manually mark the current version as seen, and they only see release notes on the next update.\n\n```swift\nNoteletStorage.markCurrentVersionAsSeen()\n```\n\nFor debugging purposes, you might also want to reset the storage value:\n\n```swift\nNoteletStorage.resetSeenVersion()\n```\n","Notelet 是一个 SwiftUI 组件，用于在 iOS 应用内展示丰富的版本更新日志。它支持多种内容类型，包括列表、图片和视频，使得开发者能够以更直观的方式向用户传达新功能和改进。该组件易于集成，通过 Swift Package Manager 即可快速添加到项目中，并且兼容 iOS 17.0 及以上版本。适用于任何希望增强用户体验、提高透明度的应用场景，特别是在频繁更新或有重要功能发布的应用中尤为适用。",2,"2026-06-11 03:31:46","CREATED_QUERY"]