Facebook Tweaks with Swift Tutorial

Learn how to use Facebook Tweaks, which allows you to modify parameters in your code whilst the app is running, and to enable or disable features on the fly. By .

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

How Does it Work?

FBTweak has properties to hold the default value and the current value. When the user changes the tweak value in the tweaks view controller, it modifies the current value and writes it to NSUserDefaults with the identifier as the key.

The next time you run the tweak, the current value is set from NSUserDefaults — this means you should always use different identifiers for the tweaks.

The rest of the classes present the tweaks view controller, store the tweaks categories and collections, and create tweaks using fancy macros in Objective-C. If you want to see some complex Objective-C macros, feel free to take a look in FBTweakInlineInternal.h.

A Helper in the Dark

If you thought the coin size tweak was cool, then prepare to be amazed – you’re going to add a lot more tweaks. Duplicating the code from your first tweak over and over again isn’t a wise thing to do, and as you already know Swift has no love for macros.

However, you can create a helper class to ease the workload.

Go to File\New\File… and chose iOS\Source\Swift File. Enter the name Tweaks.swift and click Create.

Replace the contents of the new file with the following:

import Foundation

class Tweaks {
  //1
  class func collectionWithName(collectionName: String, categoryName: String) -> FBTweakCollection {
     let store = FBTweakStore.sharedInstance()
        
     var category = store.tweakCategoryWithName(categoryName)
     if category == nil {
       category = FBTweakCategory(name: categoryName)
       store.addTweakCategory(category)
     }
        
     var collection = category.tweakCollectionWithName(collectionName)
     if collection == nil {
       collection = FBTweakCollection(name: collectionName)
       category.addTweakCollection(collection)
     }
     return collection
  }

  //2
  class func tweakValueForCategory<T:AnyObject>(categoryName: String, collectionName: String, name: String, defaultValue: T) -> T {
    
    let identifier = categoryName.lowercaseString + "." + collectionName.lowercaseString + "." + name
        
    let collection = collectionWithName(collectionName, categoryName: categoryName)
        
    var tweak = collection.tweakWithIdentifier(identifier)
    if tweak == nil {
      tweak = FBTweak(identifier: identifier)
      tweak.name = name
      tweak.defaultValue = defaultValue
            
      collection.addTweak(tweak)
    }
        
    return (tweak.currentValue ?? tweak.defaultValue) as! T
  }
  
}

This code essentially does the same thing as the code of your first tweak.

  1. This method returns a collection with the name collectionName in a category with the name categoryName. If there isn’t a collection and/or a category with the specified names, they’re created and added to the store. This method is a helper method for tweakValueForCategory(_:collectionName:name:defaultValue:).
  2. This adds a tweak to a collection with the specified name.

What does the mean? This is a generic. If you’re unfamiliar with generics, see the explanation here.

In short it means that the parameter with the type T can be any type as long as it conforms to the AnyObject protocol. The default value has to conform to AnyObject because in FBTweak.h it’s defined to be of type FBTweakValue, and that is an alias for id.

Now you can clean up the code of your first tweak. Go to touchesEnded(_:withEvent:) in ViewController.swift and replace these lines:

let identifier = "de.dasdev.nicejar.coinsize"

let store = FBTweakStore.sharedInstance()

var category = store.tweakCategoryWithName("Jar View")
if category == nil {
  category = FBTweakCategory(name: "Jar View")
  store.addTweakCategory(category)
}

var collection = category.tweakCollectionWithName("Coins")
if collection == nil {
  collection = FBTweakCollection(name: "Coins")
  category.addTweakCollection(collection)
}

var tweak = collection.tweakWithIdentifier(identifier)
if tweak == nil {
  tweak = FBTweak(identifier: identifier)
  tweak.name = "Size"
  tweak.defaultValue = CGFloat(50)

  collection.addTweak(tweak)
}

let coinDiameter = CGFloat((tweak.currentValue ??  tweak.defaultValue).floatValue)

With this cute one-liner:

let coinDiameter = CGFloat(Tweaks.tweakValueForCategory("Jar View", collectionName: "Coins", name: "Size", defaultValue: 50).floatValue)

Much better! Now the code to add a tweak won’t detract from the app’s main code.

Build and run. Make sure the tweak still works by following the same steps as before.

Note: You have to add a coin to the jar before you change the coin size, because the tweak is added to the tweak store in touchesEnded(_:withEvent:).

Note: You have to add a coin to the jar before you change the coin size, because the tweak is added to the tweak store in touchesEnded(_:withEvent:).

More Tweaks!

Now that you have a convenient way to add tweaks, go crazy and add a bunch more! Go to viewWillAppear(_:) in ViewController.swift and add the following lines right below the call to super:

//1
let elasticity = CGFloat(Tweaks.tweakValueForCategory("Jar View", collectionName: "Coins", name: "Elasticity", defaultValue: 0.35).floatValue)
propertyBehavior.elasticity = elasticity

//2
let labelConstaintConstant = CGFloat(Tweaks.tweakValueForCategory("Jar View", collectionName: "Label", name: "Y Offset", defaultValue: 102).floatValue)
verticalLabelConstraint.constant = labelConstaintConstant

//3
let showButton = Tweaks.tweakValueForCategory("Summary", collectionName: "General", name: "Show", defaultValue: false).boolValue
summaryButton.hidden = !showButton
  1. The first tweak lets you change the elasticity of the coins. Allowed values are between 0.0 and 1.0.
  2. The second tweak lets you change the vertical position of the coin label.
  3. The last tweak lets you show and hide the summary button. This is especially useful if you have view controllers you’re currently working on and they should only be visible to a select group of testers. For any other testers, you can simply disable this button and hide the unfinished parts of your app.

Build and run. Add some coins to the jar and go to Hardware\Shake Gesture. There are now two tweak categories: Jar View and Summary. Tap Jar View and you should see the following:

Three tweaks in the Jar View category

Three tweaks in the Jar View category

Three tweaks in the Jar View category

Change the Elasticity of the coins to 0.9 and the Y Offset of the label to 200. Tap Done and add coins to the jar.

Wow! That’s some serious bounce.

Forbidden Values

With the app running, open the tweaks window again (Hardware\Shake Gesture), then open Jar View and increase the elasticity to a value that makes no sense, for instance something over 2.0.

The fact you can do this is somewhat unfortunate. How can your testers possibly know what the allowed values are if they can enter anything they want? Don’t worry, Facebook has an answer to this; take a look at the following properties declared in FBTweak.h:

/**
  @abstract The minimum value of the tweak.
  @discussion Optional. If nil, there is no minimum.
    Should not be set on tweaks representing actions.
 */
@property (nonatomic, strong, readwrite) FBTweakValue minimumValue;

/**
  @abstract The maximum value of the tweak.
  @discussion Optional. If nil, there is no maximum.
    Should not be set on tweaks representing actions.
 */
@property (nonatomic, strong, readwrite) FBTweakValue maximumValue;

Now you just need to add support for those to your helper class.

Open Tweaks.swift and add the two parameters minimumValue: T? = nil and maximumValue: T? = nil to tweakValueForCategory(...). The declaration should now resemble the following:

class func tweakValueForCategory<T:AnyObject>(categoryName: String, collectionName: String, name: String, defaultValue: T, minimumValue: T? = nil, maximumValue: T? = nil) -> T

Take a look at the new parameters — they’re special. First, they’re optional and that means they can also be nil. In addition, they have default values of nil. A nice side effect of this is that you don’t have to change the call to this method in your tweaks; if the default values are okay for you, you can omit these parameters in the call to this method.

Build and run. Open the tweaks view controller and tap Reset to reset the tweaks to the default values. Now the app should behave as it did before.

Next you have to use those parameters in the creation of the tweak. Open Tweaks.swift again and add the following lines below tweak.defaultValue = defaultValue in tweakValueForCategory(...):

if minimumValue != nil && maximumValue != nil {
  tweak.minimumValue = minimumValue
  tweak.maximumValue = maximumValue
}

Go to viewWillAppear(_:) in ViewController.swift and change the elasticity tweak to the following:

let elasticity = CGFloat(Tweaks.tweakValueForCategory("Jar View", collectionName: "Coins", name: "Elasticity", defaultValue: 0.35, minimumValue: 0.0, maximumValue: 1.0).floatValue)

Build and run. Open the tweak view controller, go to Jar View category and try to increase the elasticity beyond 1.0. It doesn’t work! Nice!