Tweak your iOS app without recompiling!



Adjust your iOS app on the fly without waiting to re-compile!

SwiftTweaks Icon

Your users won’t see your animation study, Sketch comps, or prototypes. What they will see is the finished product - so it’s really important to make sure that your app feels right on a real device!

Animations that look great on your laptop often feel too slow when in-hand. Layouts that looks perfect on a 27-inch display might be too cramped on a 4-inch device. Light gray text may look subtle in Sketch, but it’s downright illegible when you’re outside on a sunny day.

These animation timings, font sizes, and color choices are all examples of “magic numbers” - the constants that give your app its usability and identity. The goal of SwiftTweaks: allow you to fine-tune these magic numbers in the debug builds of your Swift project, without having to wait for Xcode to rebuild the app.


SwiftPM compatible Carthage compatible Version GitHub release Swift 5.0 platforms Build Status


Use a Tweak in place of a boolean, number, or color in your code. You can adjust that Tweak without having to recompile, which means you can play with animation timings, colors, and layouts without needing Xcode!

Currently, you can tweak the following types:

  • Bool
  • Int
  • CGFloat
  • Double
  • UIColor
  • TweakAction
  • String
  • StringOption

A Tweak looks like this:

public static let colorTint = Tweak("General", "Colors", "Tint", UIColor.blueColor())

There are also helpful TweakGroupTemplate types, so you can quickly declare commonly-used-together combos. They all have sensible defaults, but of course, you can set your own!

// Controls delay and duration for UIView.animate
// Use it with UIView.animate(basicTweakTemplate:...)
public static let basicAnimation = BasicAnimationTweakTemplate("Animation", "Basic Animation")

// Controls delay, duration, damping, and initial spring velocity for UIView.animate
// Use it with UIView.animate(springTweakTemplate:...)
public static let springAnimation = SpringAnimationTweakTemplate("Animation", "Spring Animation")

// Controls shadow color, radius, offset, and opacity for CALayer
// Use it with CALayer.apply(shadowTweakTemplate:...)
public static let shadowTweak = ShadowTweakTemplate("Shadows", "Button Shadow")

// Controls top/right/bottom/left for UIEdgeInsets
// Use it with UIEdgeInsets.init(edgeInsetsTweakTemplate)
public static let edgeInsets = EdgeInsetsTweakTemplate("Layout", "Screen Edge Insets")

Of course, you can create your own TweakGroupTemplate type if you'd like - they're handy whenever you have a cluster of tweaks that need to be used together to get a desired effect. They can be built out of any combination of Tweaks.



SwiftTweaks now supports closures, so you can perform actions that do not depend on data from SwiftTweaks. To do this, use TweakAction as a type in your TweakStore to create a Tweak that executes your custom closures:

public static let action = Tweak<TweakAction>("Actions", "Action", "Perform some action")

Later in the code you can add closures to that tweak, which are executed when a button in Tweaks window is pressed.

let idenfitier = ExampleTweaks.action.addClosure {
	/// Some complicated action happens here
	print("We're all done!")

If you want to, you can also always remove closure using unique idenfitier that addClosure method provides.

ExampleTweaks.action.removeClosure(with: idenfitier)

Wait, what about Facebook Tweaks?

Good question! I’m glad you asked. The whole reason SwiftTweaks exists is because we love the stuffing out of FBTweaks. We’re long-time fans of FBTweaks in our Objective-C projects: Replace the magic numbers with an FBTweak macro, and you’re all set! You can leave an FBTweak macro in your production code, because it’s replaced at compile-time with the tweak’s default value.

But Swift doesn’t support this macro-wizardry, so FBTweaks is burdensome to use in Swift code. Our app is nearly all Swift, so we wanted to see if we could make something that was a little easier!

Steps to Tweaking

There are three steps to add SwiftTweaks to your project:

  1. Create a TweakLibraryType, which contains a set of Tweaks and a TweakStore to persist them.
  2. Reference that TweakLibraryType in your code to use a Tweak.
  3. In your AppDelegate, make the TweakWindow the window of your app (there are other options, but this is the most straightforward! More on that later.)

Now build-and-run, then shake your phone to bring up the Tweaks UI! Adjust tweaks, and when you’re satisfied with what you’ve got, share your tweaks with others from within the Tweaks UI.

Step One: Make your TweakLibrary

A tweak library is responsible for listing out a bunch of public static tweaks, and building a TweakStore. A tweak library looks like this:

public struct ExampleTweaks: TweakLibraryType {
	public static let colorTint = Tweak("General", "Colors", "Tint",
	public static let marginHorizontal = Tweak<CGFloat>("General", "Layout", "H. Margins", defaultValue: 15, min: 0)
	public static let marginVertical = Tweak<CGFloat>("General", "Layout", "V. Margins", defaultValue: 10, min: 0)
	public static let font = Tweak<StringOption>("General", "Layout", "Font", options: ["AvenirNext", "Helvetica", "SanFrancisco"])
	public static let featureFlagMainScreenHelperText = Tweak<Bool>("Feature Flags", "Main Screen", "Show Body Text", true)

	public static let buttonAnimation = SpringAnimationTweakTemplate("Animation", "Button Animation")

	public static let defaultStore: TweakStore = {
		let allTweaks: [TweakClusterType] = [colorTint, marginHorizontal, marginVertical, featureFlagMainScreenHelperText, buttonAnimation]

		let tweaksEnabled = TweakDebug.isActive

		return TweakStore(
			tweaks: allTweaks,
			enabled: tweaksEnabled

Let’s break down what happened here:

  • We have five tweaks in ExampleTweaks: a tint color, two CGFloats for layout, a StringOption for font choice, and a Bool that toggles an in-development feature.
  • The compiler can get confused between Int, CGFloat, and Double - so you might find it necessary to tell the Tweak<T> what type its T is - as we do here with our margin tweaks.
  • We create a defaultStore by creating a TweakStore, which needs to know whether tweaks are enabled, and a list of all tweaks.
  • The enabled flag on TweakStore exists so that SwiftTweaks isn’t accessible by your users in production. You can set it however you like; we enjoy using the DEBUG flag from our project’s Build Settings.

Step Two: Using Your TweakLibrary

To use a tweak, you replace a number or UIColors in your code with a Tweak reference, like this:

Here’s our original code:

button.tintColor =

assign returns the current value of the tweak:

button.tintColor = ExampleTweaks.assign(ExampleTweaks.colorTint)

bind calls its closure immediately, and again each time the tweak changes:

ExampleTweaks.bind(ExampleTweaks.colorTint) { button.tintColor = $0 }

bindMultiple calls its closure immediately, and again each time any of its tweaks change:

// A "multipleBind" is called initially, and each time _any_ of the included tweaks change:
let tweaksToWatch: [TweakType] = [ExampleTweaks.marginHorizontal, ExampleTweaks.marginVertical]
ExampleTweaks.bindMultiple(tweaksToWatch) {
	let horizontal = ExampleTweaks.assign(ExampleTweaks.marginHorizontal)
	let vertical = ExampleTweaks.assign(ExampleTweaks.marginVertical)
	scrollView.contentInset = UIEdgeInsets(top: vertical, right: horizontal, bottom: vertical, left: horizontal)

For more examples, check out the example project’s ViewController.swift file.

Step Three: Set TweakWindow as your Root View Controller

By default, SwiftTweaks uses a shake gesture to bring up the UI, but you can also use a custom gesture!


Swift Package Manager

SwiftTweaks is available via the Swift Package Manager; add it to Xcode with this URL:

It's also listed in the (excellent) Swift Package Index!


To add SwiftTweaks to your application, add it to your Cartfile:

github "Khan/SwiftTweaks"

In addition, add -DDEBUG to Other Swift Flags in your project's Build Settings for your Debug configuration.


pod 'SwiftTweaks'

# Enable DEBUG flag in Swift for SwiftTweaks
post_install do |installer|
    installer.pods_project.targets.each do |target|
        if == 'SwiftTweaks'
            target.build_configurations.each do |config|
                if == 'Debug'
                    config.build_settings['OTHER_SWIFT_FLAGS'] = '-DDEBUG'


Do I have to set TweakWindow as the root of my app?

Nope! Wherever/however you prefer, just create a TweaksViewController like so:

let tweaksVC = TweaksViewController(tweakStore: ExampleTweaks.defaultStore, delegate: self)

Can I have multiple TweakLibraryTypes in my app?

Sure! You’d initialize their defaultStores with a unique storeName identifier, like so:

public struct FirstTweaksLibrary: TweakLibraryType {
	// ...

	public static let defaultStore: TweakStore = {
		let allTweaks: [TweakClusterType] = //...

		return TweakStore(
			tweaks: allTweaks,
			storeName: "FirstTweaksLibrary", 	// Here's the identifier
			enabled: tweaksEnabled

Why can’t any type be used for a Tweak?

While Tweak<T> is generic, we have to restrict T to be TweakableType so that we can guarantee that each kind of T can be represented in our editing interface and persisted on disk. More types would be awesome, though! It’d be neat to support dictionaries, closures, and other things.

If you’d like to extend TweakableType, you’ll need to extend some internal components, like TweakViewDataType, TweakDefaultData, TweakViewData, and TweakPersistency. Feel free to open a pull request if you’d like to add a new type!

How do I create a new TweakGroupTemplate?

Maybe you’re using a different animation framework, or want a template for CGRect or something like that - great! As long as the tweakable “components” of your template conform to TweakableType then you’re all set. Create a new TweakGroupTemplateType, and take a look at the existing templates for implementation suggestions. (You’ll probably want to use SignedNumberTweakDefaultParameters too - they’re very helpful!)

If you think your TweakGroupTemplateType would help out others, please make a pull request!

  • Are bound Tweaks leaking the observation blocks?

    Are bound Tweaks leaking the observation blocks?

    We're using SwiftTweaks in a project and while using bind for a few Tweaks I was wondering when those observer closures are released. I skimmed through the source code and it appears to me that the closure given to bind is saved in a dictionary where it is strongly references forever.

    If I'm not mistaken these bindings dictionaries are never cleaned up neither there is any API to unregister the binding. So everytime my ViewController is started I bind a new closure that will be around forever, this looks like a memory leak to me.

    Is there anything I'm missing that resolved this memory issue? Or is this an issue and do we need to add some kind of unbind call to remove the closure from the list.

    opened by mac-cain13 11
  • Feature/callback tweaks

    Feature/callback tweaks

    I've added support for actions inside Tweaks. There are times when we want to execute some custom code on test builds. Currently, we bind to some Bool tweak and toggle it to run the code, but I wanted a cleaner, more extendable solution.

    The implementation isn't as clean as I would like but I didn't want to refactor the whole architecture of Tweaks to remove certain assumptions that each tweak has meaningful value or to provide a separate data source for actions inside Tweaks.

    It's a good start, a working solution that somewhat still makes sense within Tweaks world and can be improved in the future when more non-data types will be supported.

    opened by pendowski 10
  • Add a

    Add a "list" TweakType

    Sometimes, you want to have a feature flag that has multiple options - for example, you might have 3 environments to point against (e.g. test, stage, production) and want to switch between them.

    A couple of ideas for implementing this:

    1. A protocol, TweakableEnum, that can be used in a tweak easily. The protocol would probably require a static var caseNames: [String] to list out the options in the Tweaks UI. We'd also probably want a failable-init that returns the correct enum case for a given String (so that the enum can "look up" a persisted stringly-typed value.) This might depend on us building #14 first (adding String as a TweakableType).
    2. Use a tweak template to support lists.
    3. Make a tweak that simply holds a [String] and optionally has a default value (asserting if the default value doesn't exist in the array, and if no default given, uses the first value in the array). Leave it to the implementer to switch on the current value of the string - they can always build a string-based enum and init with rawValue: to get away from stringly-based stuff.

    Anyhow, I've not yet tried building this out yet, so I dunno what will be easy-to-understand-and-implemented here. One goal for SwiftTweaks is to keep tweak libraries simple and straightforward - so while I'm inclined to do the "most precise and Swifty thing", it's even more important that SwiftTweaks remains easy-to-use for new folks, so that'll be a big criteria in evaluating which one of these will work!

    opened by bryanjclark 8
  • Use a dynamic size for the string list cell title, rather than a fixed size

    Use a dynamic size for the string list cell title, rather than a fixed size

    There was a change to the behavior of the stringList type so that both the string and stringList use a fixed size for the cell title in the Tweaks menu, whereas awhile back the stringList type dynamically computed a size for its title.

    This change is great for the string type, which allows the user to input text, and have that text scale down as needed.

    However, for the stringList type, our use-case is that we have lists of API request names and use stringLists to enable runtime modification of local server responses to trigger for each of those requests. The request names are generally longer than one word, so with the fixed size cell titles, we end up with almost all of the titles truncated.

    This PR proposes keeping the fixed title width for the string type (where the user-provided value of text is more likely to be longer), but adjusting the stringList layout so that the title can take up as much space as possible after allowing room for the value to be displayed.

    Do you have any concerns with this approach?

    opened by johntmcintosh 7
  • Changing a tweak with a value > 100 causes the tweak to return to 100

    Changing a tweak with a value > 100 causes the tweak to return to 100

    If the initial Int tweak value is set to say 150, and you hit the plus button, the value changes to 101. If you hit the minus button, it changes to 99. Setting a defaultValue while creating the tweak doesn't fix it either.

    opened by anuragt84 5
  • EXC_BAD_ACCESS in testHexToColor

    EXC_BAD_ACCESS in testHexToColor

    Not sure what's going on in testHexToColor, but there's an EXC_BAD_ACCESS for this test case:

    HexToColorTestCase(string: "Hello", expectedColor: nil),

    Any ideas?

    opened by bryanjclark 5
  • Make date type tweakable

    Make date type tweakable

    This is based upon my previous PR from yesterday, which represents all Int8-64 and UInt8-64 types as TweakableType. I thought about separating this out, but the number of conflicts I would have gotten trying to merge them back together kept me from doing so.

    What is added here is now the Date type is also a TweakableType. Added is a new DatePickerViewController, which is shown to the user when selecting the correct date. Dates are represented in the current locale for choosing, and the timezone is properly shown by the date formatter's long time format down below to make things as clear as possible.

    Also @bryanjclark if you need some help reviewing PR's here, let me know.

    opened by brianm998 4
  • Tweaks for Android

    Tweaks for Android

    Hey! Thank you so much for sharing this wonderful library! I love the API and the UI is great!

    Do you happen to know if there is some similar libraries for Android? Are you guys using anything similar on Android in Khan Academy?

    opened by hfossli 4
  • Remove decimal value for `Int` tweak

    Remove decimal value for `Int` tweak

    Hi this patch includes these:

    • Remove decimal value for Int tweak by using the TweakViewData internal value representation
    • Fix some inconsistencies in test case

    Related issue #76

    opened by sendyhalim 4
  • Changed UIColor extension because it can conflict with other frameworks.

    Changed UIColor extension because it can conflict with other frameworks.

    Changed to a struct with static functions. Using and extension with functions such as UIColor(hex:) is a very common pattern and can conflict with other frameworks. This change takes out the extension and uses a struct with a ‘Tweaks’ prefix on the struct name.

    opened by richy486 4
  • Add `StringList` tweak type

    Add `StringList` tweak type

    This renders a list of strings as a UISegmentedControl.



    let title = Tweak<StringOption>.stringList("Text", "Text", "Title",
        options: ["SwiftTweaks", "Welcome!", "Example"])

    defaultValue is optional, and defaults to the first item in options.

    TODO in the future:

    • for lots of strings, make a version that renders the list vertically, or that uses a picker view
    • make an enumList tweak type, for choosing between options of a enum
    opened by jaredly 4
  • [CRASH] SwiftTweaks crashes when integrated via Cocoapods with the new Resource bundles change

    [CRASH] SwiftTweaks crashes when integrated via Cocoapods with the new Resource bundles change

    • [x] I have read through

    Describe the bug When integrating via Cocoapods (using the new resource bundles), SwiftTweaks crashes when navigating to a sub-menu.

    To Reproduce Steps to reproduce the behavior:

    1. Integrate SwiftTweaks through Cocoapods
    2. Run the app and open the SwiftTweaks menu
    3. Navigate to a submenu
    4. Notice the crash

    Expected behavior The submenu is displayed.

    Additional context The code that's causing the issue in question is the following:

    	// NOTE (bryan): if we just used UIImage(named:_), we get crashes when running in other apps!
    	// (Why? Because by default, iOS searches in your app's bundle, but we need to redirect that to the bundle associated with SwiftTweaks
    	private convenience init?(inThisBundleNamed imageName: String) {
    		self.init(named: imageName, in: Bundle.module, compatibleWith: nil)
    		self.init(named: imageName, in: Bundle(for: TweakTableCell.self), compatibleWith: nil) // NOTE (bryan): Could've used any class in SwiftTweaks here.

    Note that because the resource bundle now is a named bundle and not directly part of the SwiftTweaks framework itself, Bundle(for: TweakTableCell.self) will no longer work. This should be updated to reference the new resource bundle directly, though this might also impact other integrations.

    opened by JoeSzymanski 2
  • New Release request

    New Release request


    It's been a while since last release, and some interesting modification has been merged. for example

    Can we prepare a new release for SwiftTweaks?

    opened by atom-wintermute 3
  • v4.1.2(Jan 19, 2021)

  • v4.1.1(Jan 14, 2021)

    Now SwiftTweaks looks/works much better when Dark Mode is enabled. (#151)

    Also, uh, "bug fixes and performance improvements" like:

    • fixed a crasher (#152)
    • fixed retain cycle (#127)


    Source code(tar.gz)
    Source code(zip)
  • v4.1.0(Jan 13, 2021)

    Lots to enjoy in this release!

    • Added support for SwiftPM (#143, thanks @ejensen!)
    • Fixed some UI bugs on notched devices (thanks @abekert!)
    • Add support for initializing TweakWindow with a UIWindowScene (#136, thanks @johntmcintosh!)
    • Updated documentation (thank you @warpling @ignazioc!)
    • Support for app groups (#129, thank you @msmollin!)

    This release changes the minimum-supported iOS version to iOS 10.

    Source code(tar.gz)
    Source code(zip)
  • v4.0.3(Jan 3, 2019)

  • v4.0.2(Jan 2, 2019)

    Hey there! v4.0.2 includes:

    • Layout Improvements to the FloatingTweakWindow, including a "re-open Tweaks" button
    • Haptics for "action/closure" tweaks (to help you know that something happened)
    • iPad layout improvements (using a modal form sheet)
    Source code(tar.gz)
    Source code(zip)
  • v.4.0.1(Oct 11, 2018)

    (Creating a new point release to include relevant Travis/Cocoapods changes!)

    This release includes support for Swift 4.2 and Xcode 10 (thank you, @mjarvis!), as well as support for String as a tweakable type (thanks @ikesyo!)

    Source code(tar.gz)
    Source code(zip)
  • v3.0(Jun 1, 2018)

    Hey there!

    It’s been a while since we did a release -- things have been improving on the master branch, but it’s been a long time since we did a good Cocoapods update. So! Here’s what’s in this release:

    • Swift 4.0 syntax/support
    • Tweak<StringOption> aka “choose from [String]” (we use this at Khan Academy when we need a more-complex feature flag than Tweak<Bool>)
    • Add TweakWindow.GestureType.twoFingerDoubleTap for an easy alternative to .shake-ing your device; useful in apps that use a shake gesture for undo, React Native menu, etc.
    Source code(tar.gz)
    Source code(zip)
  • v2.0-beta.1(Oct 30, 2016)

    This beta release includes support for Swift 3!

    There aren't any API changes (aside from Swift 3 syntax), but since this is a breaking change for syntax, we're bumping the version number.

    Source code(tar.gz)
    Source code(zip)
  • v1.1(Oct 5, 2016)

    This release includes support for Swift 2.3.

    (After this, we'll keep a swift2.3 branch around to help out projects that haven't made the jump to Swift 3.0. master will focus on Swift 3.0 development, with a v2.0 release to indicate it's a breaking change to Swift 3.0)

    Source code(tar.gz)
    Source code(zip)
  • v1.0(May 9, 2016)

  • v0.1.1(Nov 21, 2015)

  • v0.1(Nov 21, 2015)

Khan Academy
Working to make a free, world-class education available for anyone, anywhere.
Khan Academy
Ethereum Wallet Toolkit for iOS - You can implement an Ethereum wallet without a server and blockchain knowledge.

Introduction EtherWalletKit is an Ethereum Wallet Toolkit for iOS. I hope cryptocurrency and decentralized token economy become more widely adapted. H

Sung Woo Chang 136 Dec 25, 2022
👷‍♀️ Closure-based delegation without memory leaks

Delegated 2.0 Delegated is a super small package that helps you avoid retain cycles when using closure-based delegation. New Medium post here. Origina

Oleg Dreyman 703 Oct 8, 2022
Swift code to programmatically execute local or hosted JXA payloads without using the on-disk osascript binary

Swift code to programmatically execute local or hosted JXA payloads without using the on-disk osascript binary. This is helpful when you have Terminal access to a macOS host and want to launch a JXA .js payload without using on-disk osascript commands.

Cedric Owens 20 Sep 27, 2022
Project shows how to unit test asynchronous API calls in Swift using Mocking without using any 3rd party software

UnitTestingNetworkCalls-Swift Project shows how to unit test asynchronous API ca

Gary M 0 May 6, 2022
RNH Tracker is a GPS logger for iOS (iPhone, iPad, iPod) Track your location and send your logs to RNH Regatta :-)

RNH Tracker for iOS + WatchOS RNH Tracker is a GPS logger for iOS (iPhone, iPad, iPod) with offline map cache support. Track your location, add waypoi

Ed Cafferata 0 Jan 23, 2022
Record your position and export your trip in GPX with GPS Stone on iOS.

GPS Stone Register your trips and export them as GPX files. Notes We currently have included a UIRequiredDeviceCapabilities with a location-services v

Frost Land 11 Sep 24, 2022
Automatically set your keyboard's backlight based on your Mac's ambient light sensor.

QMK Ambient Backlight Automatically set your keyboard's backlight based on your Mac's ambient light sensor. Compatibility macOS Big Sur or later, a Ma

Karl Shea 29 Aug 6, 2022
LibAuthentication will simplify your code when if you want to use FaceID/TouchID in your tweaks.

LibAuthentication will simplify your code when if you want to use FaceID/TouchID in your tweaks.

Maximehip 6 Oct 3, 2022
A way to easily add Cocoapod licenses and App Version to your iOS App using the Settings Bundle

EasyAbout Requirements: cocoapods version 1.4.0 or above. Why you should use Well, it is always nice to give credit to the ones who helped you ?? Bonu

João Mourato 54 Apr 6, 2022
A utility that reminds your iPhone app's users to review the app written in pure Swift.

SwiftRater SwiftRater is a class that you can drop into any iPhone app that will help remind your users to review your app on the App Store/in your ap

Takeshi Fujiki 289 Dec 12, 2022
Generate a privacy policy for your iOS app

PrivacyFlash Pro To easily run PrivacyFlash Pro get the latest packaged release. Learn more about PrivacyFlash Pro in our research paper. PrivacyFlash

privacy-tech-lab 141 Dec 22, 2022
Versions tracker for your iOS, macOS, and tvOS app

VersionTrackerSwift VersionTrackerSwift is a versions / builds tracker to know which version has been installed by a user. Usage In your ApplicationDe

Tom Baranes 82 Oct 5, 2022
CipherCode - iOS App to decode your secret message

CipherCode IOS App to decode/encode your secret message App App consist of welco

Doston Rustamov 0 Jan 15, 2022
FancyGradient is a UIView subclass which let's you animate gradients in your iOS app. It is purely written in Swift.

FancyGradient is a UIView subclass which let's you animate gradients in your iOS app. It is purely written in Swift. Quickstart Static gradient let fa

Derek Jones 11 Aug 25, 2022
Add “Launch at Login” functionality to your macOS app in seconds

LaunchAtLogin Add “Launch at Login” functionality to your macOS app in seconds It's usually quite a convoluted and error-prone process to add this. No

Sindre Sorhus 1.3k Jan 6, 2023
WhatsNewKit enables you to easily showcase your awesome new app features.

WhatsNewKit enables you to easily showcase your awesome new app features. It's designed from the ground up to be fully customized to your needs. Featu

Sven Tiigi 2.8k Jan 3, 2023
Checks if there is a newer version of your app in the AppStore and alerts the user to update.

YiAppUpdater Checks if there is a newer version of your app in the AppStore and alerts the user to update. Installation YiAppUpdater is available thro

coderyi 4 Mar 17, 2022
An app that converts the temperature in Fahrenheit your input, and then changes to Celsius.

Swift Temperature Converter An app that converts the temperature in Fahrenheit your input, and then changes to Celsius. Things you need to do ?? clone

Tsuen Hsueh 1 Aug 16, 2022
A framework to provide logic designed to prompt users at the ideal moment for a review of your app/software

ReviewKit ReviewKit is a Swift package/framework that provides logic designed to prompt users at the ideal moment for a review of your app. At a basic

Simon Mitchell 25 Jun 7, 2022