WishKit allows your users to request and vote on features in your app!
- Setup (SwiftUI)
- Theming
- User Segmentation
- Control UI Elements
- Localization
- Privacy
- Migrating from v4 (UIKit)
https://github.com/wishkit/wishkit-ios.git
You can find your API key in your admin dashboard on wishkit.io.
import SwiftUI
import WishKit
struct ContentView: View {
init() {
WishKit.configure(with: "your-api-key")
}
...
}import SwiftUI
import WishKit
struct ContentView: View {
init() {
WishKit.configure(with: "your-api-key")
}
var body: some View {
WishKit.FeedbackListView()
}
}NOTE: On iOS and tvOS, FeedbackListView expects to live inside a NavigationStack. If it's not already inside one, add .withNavigation() to wrap it: WishKit.FeedbackListView().withNavigation(). On macOS, visionOS, and watchOS, FeedbackListView provides its own navigation container β no wrapping needed.
// Allow user to undo their vote
WishKit.config.allowUndoVote = true
// Shows full description of a feature request in the list.
WishKit.config.expandDescriptionInList = true
// Hide comment section
WishKit.config.commentSection = .hide
// Show the status badge of a feature request (e.g. pending, approved, etc.).
WishKit.config.statusBadge = .show
// Hide the segmented control.
WishKit.config.buttons.segmentedControl.display = .hide
// Show internal debug logs in the console (network requests, errors, etc.).
// Off by default so production builds stay quiet.
WishKit.config.showDebugLogs = true// Accent color for primary actions (Save button, active Vote-Button, comment send).
// Defaults to your app's accent color so WishKit blends in natively.
WishKit.theme.primaryColor = .yellow
// Set the secondary color (this is for the cells and text fields).
WishKit.theme.secondaryColor = .set(light: .orange, dark: .red)
// Set the tertiary color (this is for the background).
WishKit.theme.tertiaryColor = .set(light: .gray, dark: .black)// How much a user is paying per week or month or year.
// WishKit supports weekly, monthly and yearly payments.
WishKit.updateUser(payment: .monthly(7.99))By sharing the revenue of a user you will be able to see "how much money" is behind a feature request. This allows you to prioritize a feature with only 2 votes but $13 over a feature with 7 votes and $0.
// Email
WishKit.updateUser(email: "jobs@apple.com")
// Name
WishKit.updateUser(name: "Steve")
// If you manage user IDs yourself you can let WishKit prioritize it.
WishKit.updateUser(customID: "8AHD1IL03ACIP")| Language | Code |
|---|---|
| English | en |
| German | de |
| Spanish | es |
| French | fr |
| Japanese | ja |
| Korean | ko |
| Chinese (Simplified) | zh-Hans |
| Chinese (Traditional) | zh-Hant |
WishKit uses the language your app runs in β the app's language, not the device language, so your app needs to support a language for WishKit to display it. If the app's language isn't one of the supported ones, WishKit falls back to English. Translations use informal address where the language distinguishes (German "Du", Spanish "tΓΊ").
// Override the segmented control text for "Open".
WishKit.config.localization.open = "Offen"
// You can also assign NSLocalizedString.
WishKit.config.localization.cancel = NSLocalizedString("general.cancel", comment: "")When a feature request is written in a different language than your app runs in, WishKit shows a small "See translation" button below its description β both in the list and in the detail view. Tapping it translates the title and description on-device using Apple's Translation framework β nothing leaves the device β and toggles to "See original".
// Default is .automatic β the button only appears when the
// detected language of a request differs from the app's language.
WishKit.config.translateButton = .automatic
// Always show the button (covers texts too short to detect reliably).
WishKit.config.translateButton = .always
// Turn the feature off.
WishKit.config.translateButton = .hideWishKit ships a privacy manifest (PrivacyInfo.xcprivacy) that Xcode automatically includes in your app's aggregated privacy report. It declares what WishKit collects out of the box:
- User ID β an anonymous, randomly generated per-install identifier used to associate votes and feedback. Not linked to the user's identity, not used for tracking.
- Product interaction β votes and feedback activity. App functionality only.
- User content β the feature requests and comments users write. App functionality only.
WishKit does not track users and declares no tracking domains.
If you share additional user information via updateUser β email, name, or your own custom ID β that data is sent to WishKit too, but it is not part of WishKit's manifest since collecting it is your app's choice. In that case, cover it in your own app's privacy details in App Store Connect (e.g. "Email Address" / "Name" under app functionality).
- iOS 16+
- macOS 13+
- visionOS 1+
- watchOS 10+
- tvOS 17+
| Feature | iOS | macOS | visionOS | watchOS | tvOS |
|---|---|---|---|---|---|
| Browse wishes | Yes | Yes | Yes | Yes | Yes |
| Vote / undo vote | Yes | Yes | Yes | Yes | Yes |
| Create wish | Yes | Yes | Yes | β | β |
| Read comments | Yes | Yes | Yes | β | Yes |
| Post comments | Yes | Yes | Yes | β | β |
State filter (buttons.segmentedControl) |
Yes | Yes | Yes | Yes | Yes |
Done button (buttons.doneButton) |
Yes | Yes | Yes | β | β |
Add button (buttons.addButton) |
Yes | Yes | Yes | β | β |
Translate feedback (translateButton) |
iOS 18+ | β | β | β | β |
watchOS and tvOS are intentionally scoped to browse + vote. Config keys for unsupported features are silently ignored on those platforms. On tvOS, users dismiss the feedback view via the Siri Remote's Menu button (the standard system pattern), so buttons.doneButton is not exposed.
On watchOS and tvOS, the state filter is rendered as a single cycle button that loops through the available states on tap β a more remote- and crown-friendly affordance than a segmented control.
Checkout the example project to see how easy it is to set up WishKit!
WishKit 5 is SwiftUI-only. The WishKit.viewController entry point that existed in v4 has been removed. If you were presenting WishKit.viewController.withNavigation() from a UIViewController, switch to wrapping the SwiftUI view yourself:
import UIKit
import SwiftUI
import WishKit
class HomeViewController: UIViewController {
@objc func buttonTapped() {
let feedback = UIHostingController(rootView: WishKit.FeedbackListView().withNavigation())
present(feedback, animated: true)
}
}Everything else β WishKit.configure(_:), WishKit.config, WishKit.theme, WishKit.updateUser(...) β works the same as before.
