Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

The availability of features may depend on your plan type. Contact your Customer Success Manager if you have any questions.

Dev guideRecipesAPI ReferenceChangelog
Dev guideAPI ReferenceUser GuideLegal TermsGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev guide

Flag variable example with an image carousel

A example iOS application that walks through creating and implementing flag variables in Optimizely Feature Experimentation to create an image carousel that updates based on what variation the user receives.

This example demonstrates how to create an iOS application featuring an image carousel (also called an image slider) to promote new products.

Each slide in the carousel includes the following:

  • A product image.
  • A marketing tagline.
  • A button with configurable text, color, and size that, when tapped, launches a web page or application.
  • An optional click action on the product image that goes to a different URL or page.

For this example, use Feature Experimentation to define the image carousel's flag variables, create three possible variations, and configure an A/B test rule. In the iOS application, the Swift SDK decides which variation a user sees, and the flag variable values dynamically configure and build the image carousel.

Create flag variables in Feature Experimentation

Using the Feature Experimentation UI or the Create a New Flag API endpoint, create a flag named image_carousel_flag.

Next, define the following flag variables with the specified keys using the UI or the Create Variable Definition API endpoint:

  • numberOfSlides – Integer – Defines how many slides the image carousel has.
  • buttonText – String – The text caption shown on the button (for example, "Submit" or "Learn More").
  • buttonColor – String – The color value for the button, which could be a hex string (for example, #FF0000) or any format your application supports.
  • backgroundImageUrl – String – The URL of the background image for each slide.
  • buttonClickUrl – String – The URL or path to launch when the button is clicked.
  • backgroundClickUrl – String – The URL or path to launch when the background image is clicked.
  • marketingTagline – String – The text message overlayed on the image carousel slide.
  • buttonWidth – Integer – The width (in points) of the button.
  • buttonHeight – Integer – The height (in points) of the button.

Your flag's Variable page should display similarly to the following:

Variables set in Optimizely

These variables correspond to the following in the application:

Create variations in Feature Experimentation

First, use the Feature Experimentation UI or the Delete a Variation API endpoint to delete the default On variation. Then, using the Feature Experimentation UI or Create a New Variation API endpoint, configure the following variations:

📘

Note

The only difference between the variations in this example is the buttonColor. You can create the control variation and then click Duplicate variation and update the Name, Key and buttonColor variable accordingly to quickly create the three variations.

Control

  • Name – Control
  • Key – control
  • Variables
    • backgroundClickUrl – https://atticandbutton.com/collections/all/athletic
    • backgroundImageUrl – https://atticandbutton.com/cdn/shop/products/5-panel-hat_medium_630ad61c-e050-4046-bdbb-efa5151351af_1024x1024.jpeg?v=1437449607
    • buttonClickUrl –https://atticandbutton.com/products/5-panel-hat
    • buttonColor – #FFFFFF
    • buttonHeight – 40
    • buttonText – Buy
    • buttonWidth – 100
    • marketingTagline – Hats on sale now!
    • numberOfSlides – 3

      🚧

      Important

      The numberOfSlides variable must be the same for all variations, otherwise the application may not render correctly.

Variation 1 - #00CCFF

  • Name – Blue button
  • Key – blueButton
  • Variables
    • backgroundClickUrl – https://atticandbutton.com/collections/all/athletic
    • backgroundImageUrl – https://atticandbutton.com/cdn/shop/products/5-panel-hat_medium_630ad61c-e050-4046-bdbb-efa5151351af_1024x1024.jpeg?v=1437449607
    • buttonClickUrl – https://atticandbutton.com/products/5-panel-hat
    • buttonColor – #00CCFF
    • buttonHeight – 40
    • buttonText – Buy
    • buttonWidth – 100
    • marketingTagline – Hats on sale now!
    • numberOfSlides – 3

Variation 2 – #861DFF

  • Name – Purple button
  • Key – purpleButton
  • Variables
    • backgroundClickUrl – https://atticandbutton.com/collections/all/athletic
    • backgroundImageUrl – https://atticandbutton.com/cdn/shop/products/5-panel-hat_medium_630ad61c-e050-4046-bdbb-efa5151351af_1024x1024.jpeg?v=1437449607
    • buttonClickUrl – https://atticandbutton.com/products/5-panel-hat
    • buttonColor – #861DFF
    • buttonHeight – 40
    • buttonText – Buy
    • buttonWidth – 100
    • marketingTagline – Hats on sale now!
    • numberOfSlides – 3

Your Variations page should display similarly.

Variations set in Optimizely

Create an A/B test rule in Feature Experimentation

Create an A/B Test rule in your Development environment using the Feature Experimentation UI. Configure your primary metric as the increase in unique conversions of a click event on the button. Add the three variations created in the Create variations in Feature Experimentation section and distribute traffic evenly between them.

After creating your rule, click Run on your rule and image_carousel_flag flag.

Completed image_carousel_flag

Run example code

The following Swift example code creates an image carousel that can update its appearance based on the variables in Feature Experimentation and updates the button's background color based on what variation the user is bucketed into.

Before running the Swift example code, install the Feature Experimentation Swift SDK in your Xcode project. Next, copy and paste the example code into your ViewController. Then, replace <YOUR_SDK_KEY> in the OptimizelyClient initialization by copying and pasting your SDK key from the Optimizely UI for your Development environment.

import UIKit
// Import the Optimizely iOS SDK for Feature Experimentation
import Optimizely


class ViewController: UIViewController {
    // MARK: - Properties
    
    // Replace this with the real OptimizelyClient initialization
    let optimizelyClient = OptimizelyClient(sdkKey: "<YOUR_SDK_KEY>")
    
    // Example: A simple model for each slide in the carousel
    struct CarouselSlide {
        let backgroundImageUrl: String
        let marketingTagline: String
        let buttonText: String
        let buttonColor: String
        let buttonClickUrl: String
        let backgroundClickUrl: String
        let buttonWidth: Int
        let buttonHeight: Int
    }
    
    var slides: [CarouselSlide] = []
    
    // MARK: - View Lifecycle
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Start the Optimizely client to fetch the datafile
        optimizelyClient.start { [weak self] result in
            switch result {
            case .failure(let error):
                print("Error starting Optimizely Client: \(error)")
                // Fallback logic could go here
            case .success:
                // Successfully started the client, now decide which variation this user sees
                self?.configureCarousel()
            }
        }
    }
    
    // MARK: - Carousel Configuration
    
    func configureCarousel() {
        // The flag key as defined in your Optimizely project
        let flagKey = "image_carousel_flag"
        
        // This is a unique user ID. In production, use something that identifies the user
        let user = optimizelyClient.createUserContext(userId: "user123")
        
        // Decide which variation the user sees.
        // Note: The actual method or function name may vary depending on the Swift SDK version
        let decision = user.decide(key: "image_carousel_flag")
        
        // Check if the flag is enabled; if not, use fallback or default logic
        guard decision.enabled == true else {
            print("Flag '\(flagKey)' is not enabled. Using default configuration.")
            return
        }
        
        // all variable values
        let allVarValues: OptimizelyJSON = decision.variables
        print(allVarValues)
        
        // Retrieve the variable values from the decision object.
        // Depending on the SDK version, you might access them differently:
        
        // 1. Integer variable for numberOfSlides
        let numberOfSlides: Int? = decision.variables.getValue(jsonPath: "numberOfSlides")

        // Initialize an array to store slides
         var tempSlides: [CarouselSlide] = []

        // Loop over numberOfSlides to build our slides
        for _ in 0..<numberOfSlides! {
            
            // 2. String variable for the text caption for the button
            let buttonText: String? = decision.variables.getValue(jsonPath: "buttonText") ?? "Default Button"
           
            // 3. String variable for the color value for the button
            let buttonColor: String? = decision.variables.getValue(jsonPath: "buttonColor") ?? "#000000"
            // Default color black
            
            // 4. String variable for the path or url of the background image
            let backgroundImageUrl: String? = decision.variables.getValue(jsonPath: "backgroundImageUrl") ?? ""
            
            // 5. String variable for path or url to launch when the button is clicked
            let buttonClickUrl: String? = decision.variables.getValue(jsonPath: "buttonClickUrl") ?? ""
            
            // 6. String variable for path or url to launch when the background image is clicked
            let backgroundClickUrl: String? = decision.variables.getValue(jsonPath: "backgroundClickUrl") ?? ""
            
            // 7. String variable for the marketing message overlayed on the carousel
            let marketingTagline: String? = decision.variables.getValue(jsonPath: "marketingTagline") ?? "Welcome!"
            
            // 8. Integer variable for button width
            let buttonWidth: Int? = decision.variables.getValue(jsonPath: "buttonWidth") ?? 100
            
            // 9. Integer variable for button height
            let buttonHeight: Int? = decision.variables.getValue(jsonPath: "buttonHeight") ?? 40
            
            // Create our slide model
            let slide = CarouselSlide(backgroundImageUrl: backgroundImageUrl!,
                                      marketingTagline: marketingTagline!,
                                      buttonText: buttonText!,
                                      buttonColor: buttonColor!,
                                      buttonClickUrl: buttonClickUrl!,
                                      backgroundClickUrl: backgroundClickUrl!,
                                      buttonWidth: buttonWidth!,
                                      buttonHeight: buttonHeight!)
            
            tempSlides.append(slide)
        }

        // Assign the slides array to our class property
        self.slides = tempSlides

        // Build the UI for the carousel
        DispatchQueue.main.async {
            self.setupCarouselUI()
        }
    }
    
    // MARK: - Setup Carousel UI
    
    func setupCarouselUI() {
        // This function is responsible for laying out the UI elements of the carousel.
        // For simplicity, let's assume we just build a vertical stack of "slides" (though
        // a real carousel might use a UIScrollView, UICollectionView, or a custom component).

        let scrollView = UIScrollView(frame: self.view.bounds)
        scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.view.addSubview(scrollView)

        var yOffset: CGFloat = 0
        for (index, slide) in slides.enumerated() {

            // Container for the slide
            let slideView = UIView(frame: CGRect(x: 0, y: yOffset, width: self.view.frame.width, height: 300))
            slideView.backgroundColor = .white

            // Background image (in a real app, you would load asynchronously)
            if let bgUrl = URL(string: slide.backgroundImageUrl),
               let data = try? Data(contentsOf: bgUrl),
               let image = UIImage(data: data) {

                let backgroundImageView = UIImageView(image: image)
                backgroundImageView.contentMode = .scaleAspectFill
                backgroundImageView.frame = slideView.bounds
                backgroundImageView.isUserInteractionEnabled = true

                // Background click gesture
                let bgTap = UITapGestureRecognizer(target: self, action: #selector(backgroundTapped(_:)))
                backgroundImageView.addGestureRecognizer(bgTap)
                backgroundImageView.tag = index // store the slide index

                slideView.addSubview(backgroundImageView)

                // Overlaid marketing message
                let messageLabel = UILabel(frame: CGRect(x: 20,
                                                         y: slideView.bounds.height/2 - 20,
                                                         width: slideView.bounds.width - 40,
                                                         height: 40))
                messageLabel.text = slide.marketingTagline
                messageLabel.textColor = .white
                messageLabel.textAlignment = .center
                messageLabel.font = UIFont.boldSystemFont(ofSize: 18)
                backgroundImageView.addSubview(messageLabel)
            }

            // Button
            let button = UIButton(type: .system)
            button.setTitle(slide.buttonText, for: .normal)

            // Convert color string to UIColor if your app supports hex parsing
            // For simplicity, assume a function hexStringToUIColor exists
            button.backgroundColor = hexStringToUIColor(hex: slide.buttonColor)

            // Use the buttonWidth and buttonHeight from the flag variables
            button.frame = CGRect(x: 20,
                                  y: slideView.bounds.height - CGFloat(slide.buttonHeight) - 20,
                                  width: CGFloat(slide.buttonWidth),
                                  height: CGFloat(slide.buttonHeight))

            button.setTitleColor(.white, for: .normal)
            button.layer.cornerRadius = 5

            // Button click action
            button.tag = index // store the slide index
            button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)

            slideView.addSubview(button)

            scrollView.addSubview(slideView)

            yOffset += slideView.frame.height
        }

        scrollView.contentSize = CGSize(width: self.view.frame.width, height: yOffset)
    }
    
    // MARK: - Actions
    
    @objc func buttonTapped(_ sender: UIButton) {
        let slide = slides[sender.tag]
        // Launch the buttonClickUrl when the button is tapped
        if let url = URL(string: slide.buttonClickUrl) {
            // In a real app, consider SFSafariViewController or an in-app browser
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        }
    }
    
    @objc func backgroundTapped(_ sender: UITapGestureRecognizer) {
        guard let bgView = sender.view else { return }
        let slide = slides[bgView.tag]
        // Launch the backgroundClickUrl
        if let url = URL(string: slide.backgroundClickUrl) {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        }
    }
    
    // MARK: - Helper
    
    func hexStringToUIColor(hex: String) -> UIColor {
        // Very basic hex to UIColor converter
        var cString = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
        
        // Remove '#' if present
        if cString.hasPrefix("#") {
            cString.removeFirst()
        }
        
        // If not 6 or 8 characters, return gray
        if cString.count != 6 && cString.count != 8 {
            return UIColor.gray
        }
        
        // If 8 characters, assume ARGB
        if cString.count == 8 {
            let aString = String(cString.prefix(2))
            cString.removeFirst(2)
            
            var a: UInt64 = 0
            Scanner(string: aString).scanHexInt64(&a)
            
            // For brevity, ignoring alpha logic
            return UIColor.gray
        }
        
        var rgbValue: UInt64 = 0
        Scanner(string: cString).scanHexInt64(&rgbValue)
        
        return UIColor(
            red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
            blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
            alpha: 1.0
        )
    }


}

Run the code. In this instance, the user was bucketed into the purpleButton variation and their button displays in purple.

Conclusion

While this example focuses on an image carousel, the underlying principle of using flag variables to dynamically configure UI elements is highly adaptable. You can apply the same approach to almost any interface component or feature to control its content, style, and behavior directly from Feature Experimentation.

For example

  • Promotional banner – Configure headline text, subtext, background color, and button link.
  • Popup modal – Control title text, description text, button style (color and size), and trigger conditions.
  • Dynamic form – Adjust form fields, validation messages, and submission endpoint URL.
  • Navigation bar – Set menu item labels, icon visibility, background color, and click behavior for each item.
  • Product recommendations widget – Select which products to display, customized call-to-action text, and style fonts or layout.

By using flag variables, you can standardize and manage these UI changes without modifying or redeploying the underlying application code.