Created
January 15, 2025 21:20
-
-
Save simone-coelho/33c7594c8ddd0b99a69b3704911fcb21 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
// Import the Optimizely iOS SDK for Feature Experimentation | |
// import Optimizely | |
class CarouselViewController: 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 marketingMessage: 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 = "carousel_flag" | |
// This is a unique user ID. In production, use something that identifies the user | |
let userId = "user_12345" | |
// Decide which variation the user sees. | |
// Note: The actual method or function name may vary depending on the SDK version | |
let decision = optimizelyClient.decide(key: flagKey, userId: userId) | |
// 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 | |
} | |
// Retrieve the variable values from the decision object. | |
// Depending on the SDK version, you might access them differently: | |
// e.g. decision.variables["variable_key"]?.value, or helper functions. | |
// 1. Integer variable for numberOfSlides | |
let numberOfSlides = decision.variables["numberOfSlides"]?.value as? Int ?? 0 | |
// 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 = decision.variables["buttonText"]?.value as? String ?? "Default Button" | |
// 3. String variable for the color value for the button | |
let buttonColor = decision.variables["buttonColor"]?.value as? String ?? "#000000" // Default color black | |
// 4. String variable for the path or url of the background image | |
let backgroundImageUrl = decision.variables["backgroundImageUrl"]?.value as? String ?? "" | |
// 5. String variable for path or url to launch when the button is clicked | |
let buttonClickUrl = decision.variables["buttonClickUrl"]?.value as? String ?? "" | |
// 6. String variable for path or url to launch when the background image is clicked | |
let backgroundClickUrl = decision.variables["backgroundClickUrl"]?.value as? String ?? "" | |
// 7. String variable for the marketing message overlayed on the carousel | |
let marketingMessage = decision.variables["marketingMessage"]?.value as? String ?? "Welcome!" | |
// 8. Integer variable for button width | |
let buttonWidth = decision.variables["buttonWidth"]?.value as? Int ?? 100 | |
// 9. Integer variable for button height | |
let buttonHeight = decision.variables["buttonHeight"]?.value as? Int ?? 40 | |
// Create our slide model | |
let slide = CarouselSlide(backgroundImageUrl: backgroundImageUrl, | |
marketingMessage: marketingMessage, | |
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’d 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.marketingMessage | |
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 | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment