iOS SDK
Native iOS SDK written in Swift
iOS SDK
The Entrolytics/iOS package provides a native iOS SDK written in Swift for analytics tracking with automatic lifecycle management, crash reporting, and battery optimization.
Installation
# Podfile
pod 'Entrolytics/iOS'Info.plist Configuration
Add to Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>entrolytics.dev</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>Quick Start
Initialize SDK
// AppDelegate.swift
import UIKit
import Entrolytics
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// Initialize Entrolytics
Entrolytics.initialize(
config: Config.Builder()
.websiteId("your-website-id")
.apiKey("your-api-key")
.debug(isDebug)
.autoTrackScreens(true)
.trackCrashes(true)
.build()
)
return true
}
}Track Events
// ViewController.swift
import UIKit
import Entrolytics
class ViewController: UIViewController {
@IBOutlet weak var signupButton: UIButton!
@IBOutlet weak var featureCard: UIView!
override func viewDidLoad() {
super.viewDidLoad()
setupEventTracking()
}
private func setupEventTracking() {
// Track button click
signupButton.addAction(UIAction { _ in
Entrolytics.track("signup_button_click", properties: [
"screen": "ViewController",
"button_text": "Sign Up"
])
}, for: .touchUpInside)
// Track feature discovery
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(featureTapped))
featureCard.addGestureRecognizer(tapGesture)
}
@objc private func featureTapped() {
Entrolytics.track("feature_discovery", properties: [
"feature_name": "premium_analytics",
"source": "main_card"
])
}
}Track Screens
// BaseViewController.swift
import UIKit
import Entrolytics
class BaseViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Track screen view automatically
Entrolytics.screen(
name: getScreenName(),
properties: getScreenProperties()
)
}
// Override in subclasses
func getScreenName() -> String {
return String(describing: type(of: self))
}
func getScreenProperties() -> [String: Any] {
return [:]
}
}
// Usage
class ProfileViewController: BaseViewController {
override func getScreenName() -> String {
return "ProfileScreen"
}
override func getScreenProperties() -> [String: Any] {
return [
"user_authenticated": currentUser != nil,
"has_premium": currentUser?.isPremium ?? false
]
}
}Configuration
Configuration Builder
import Entrolytics
let config = Config.Builder()
.websiteId("your-website-id") // required
.apiKey("your-api-key") // optional
.host("https://entrolytics.dev") // custom host
.debug(isDebug) // debug mode
.autoTrackScreens(true) // auto-track screens
.trackCrashes(true) // track crashes
.trackAppLifecycle(true) // track app lifecycle
.batchSize(50) // batch size
.flushInterval(30.0) // flush interval (seconds)
.timeout(10.0) // request timeout (seconds)
.maxRetries(3) // max retry attempts
.batteryOptimization(true) // battery optimization
.offlineSupport(true) // offline support
.build()Configuration via Plist
<!-- Entrolytics.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>WebsiteId</key>
<string>your-website-id</string>
<key>ApiKey</key>
<string>your-api-key</string>
<key>Host</key>
<string>https://entrolytics.dev</string>
<key>Debug</key>
<false/>
<key>AutoTrackScreens</key>
<true/>
<key>TrackCrashes</key>
<true/>
<key>BatchSize</key>
<integer>50</integer>
<key>FlushInterval</key>
<real>30.0</real>
</dict>
</plist>// Load from plist
if let path = Bundle.main.path(forResource: "Entrolytics", ofType: "plist"),
let config = Config.fromPlist(at: path) {
Entrolytics.initialize(config: config)
}API Reference
Core Class
public class Entrolytics {
// Initialization
public static func initialize(config: Config)
public static func isInitialized() -> Bool
// Event tracking
public static func track(_ event: String, properties: [String: Any]? = nil)
public static func trackAsync(_ event: String, properties: [String: Any]? = nil)
// Screen tracking
public static func screen(name: String, properties: [String: Any]? = nil)
public static func screenAsync(name: String, properties: [String: Any]? = nil)
// User identification
public static func identify(userId: String, traits: [String: Any]? = nil)
public static func identifyAsync(userId: String, traits: [String: Any]? = nil)
// User properties
public static func setUserProperties(_ properties: [String: Any])
public static func setUserPropertiesAsync(_ properties: [String: Any])
// Batch operations
public static func trackBatch(_ events: [Event])
public static func trackBatchAsync(_ events: [Event])
// Control
public static func flush()
public static func flushAsync(completion: @escaping (Error?) -> Void)
public static func reset()
public static func setEnabled(_ enabled: Bool)
public static func isEnabled() -> Bool
// Configuration
public static func getConfig() -> Config
public static func updateConfig(_ config: Config)
// User information
public static func getUserId() -> String?
public static func getSessionId() -> String
}Data Structures
public struct Config {
public let websiteId: String
public let apiKey: String?
public let host: String
public let debug: Bool
public let autoTrackScreens: Bool
public let trackCrashes: Bool
public let trackAppLifecycle: Bool
public let batchSize: Int
public let flushInterval: TimeInterval
public let timeout: TimeInterval
public let maxRetries: Int
public let batteryOptimization: Bool
public let offlineSupport: Bool
}
public struct Event {
public let event: String
public let properties: [String: Any]?
public let timestamp: Date
public let userId: String?
public let sessionId: String
}
public struct Builder {
public func websiteId(_ websiteId: String) -> Builder
public func apiKey(_ apiKey: String?) -> Builder
public func host(_ host: String) -> Builder
public func debug(_ debug: Bool) -> Builder
public func autoTrackScreens(_ autoTrackScreens: Bool) -> Builder
public func trackCrashes(_ trackCrashes: Bool) -> Builder
public func trackAppLifecycle(_ trackAppLifecycle: Bool) -> Builder
public func batchSize(_ batchSize: Int) -> Builder
public func flushInterval(_ flushInterval: TimeInterval) -> Builder
public func timeout(_ timeout: TimeInterval) -> Builder
public func maxRetries(_ maxRetries: Int) -> Builder
public func batteryOptimization(_ batteryOptimization: Bool) -> Builder
public func offlineSupport(_ offlineSupport: Bool) -> Builder
public func build() -> Config
}Advanced Usage
View Controller Lifecycle Integration
// BaseViewController.swift
import UIKit
import Entrolytics
class BaseViewController: UIViewController {
private var viewStartTime: Date?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
viewStartTime = Date()
// Track screen automatically
Entrolytics.screen(
name: getScreenName(),
properties: getScreenProperties()
)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Track screen exit
if let startTime = viewStartTime {
let duration = Date().timeIntervalSince(startTime)
Entrolytics.track("screen_exit", properties: [
"screen_name": getScreenName(),
"duration_seconds": duration
])
}
}
// Override in subclasses
func getScreenName() -> String {
return String(describing: type(of: self))
}
func getScreenProperties() -> [String: Any] {
return [:]
}
}SwiftUI Integration
// AnalyticsViewModifier.swift
import SwiftUI
import Entrolytics
struct AnalyticsViewModifier: ViewModifier {
let screenName: String
let properties: [String: Any]?
func body(content: Content) -> some View {
content
.onAppear {
Entrolytics.screen(name: screenName, properties: properties)
}
}
}
extension View {
func trackScreen(name: String, properties: [String: Any]? = nil) -> some View {
modifier(AnalyticsViewModifier(screenName: name, properties: properties))
}
}
// Usage in SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("Welcome to Entrolytics")
Button("Sign Up") {
Entrolytics.track("signup_button_tapped", properties: [
"source": "main_screen"
])
}
}
.trackScreen(name: "MainScreen", properties: [
"user_authenticated": false
])
}
}User Authentication Integration
// AuthenticationManager.swift
import Foundation
import Entrolytics
class AuthenticationManager: ObservableObject {
func login(email: String, password: String) async throws -> User {
// Track login attempt
Entrolytics.track("login_attempt", properties: [
"method": "email",
"email_hash": email.hashValue.description
])
do {
let user = try await apiService.login(email: email, password: password)
// Identify user in analytics
Entrolytics.identify(userId: user.id, traits: [
"email": user.email,
"name": user.name,
"role": user.role,
"plan": user.subscriptionPlan
])
// Track successful login
Entrolytics.track("login_success", properties: [
"user_id": user.id,
"method": "email"
])
// Update user properties
Entrolytics.setUserProperties([
"login_count": user.loginCount + 1,
"last_login": Date().timeIntervalSince1970,
"is_premium": user.isPremium
])
return user
} catch {
// Track login failure
Entrolytics.track("login_failed", properties: [
"method": "email",
"error_type": String(describing: type(of: error)),
"error_message": error.localizedDescription
])
throw error
}
}
func logout() {
// Track logout
Entrolytics.track("logout", properties: [
"method": "explicit"
])
// Reset user session
Entrolytics.reset()
// Clear local data
clearUserData()
}
}E-commerce Tracking
// ProductViewController.swift
import UIKit
import Entrolytics
class ProductViewController: UIViewController {
private var product: Product!
override func viewDidLoad() {
super.viewDidLoad()
// Track product view
Entrolytics.track("product_view", properties: [
"product_id": product.id,
"product_name": product.name,
"category": product.category,
"price": product.price,
"currency": product.currency,
"in_stock": product.inStock,
"source": getNavigationSource()
])
setupUI()
}
private func setupUI() {
// Track add to cart
addToCartButton.addAction(UIAction { [weak self] _ in
guard let self = self else { return }
Entrolytics.track("add_to_cart", properties: [
"product_id": self.product.id,
"product_name": self.product.name,
"price": self.product.price,
"quantity": self.quantityPicker.value,
"currency": self.product.currency
])
self.addToCart(product: self.product, quantity: self.quantityPicker.value)
}, for: .touchUpInside)
// Track wishlist
addToWishlistButton.addAction(UIAction { [weak self] _ in
guard let self = self else { return }
Entrolytics.track("add_to_wishlist", properties: [
"product_id": self.product.id,
"product_name": self.product.name,
"category": self.product.category
])
self.addToWishlist(product: self.product)
}, for: .touchUpInside)
// Track share
shareButton.addAction(UIAction { [weak self] _ in
guard let self = self else { return }
Entrolytics.track("product_share", properties: [
"product_id": self.product.id,
"product_name": self.product.name,
"method": "native_share"
])
self.shareProduct(self.product)
}, for: .touchUpInside)
}
}
// CheckoutViewController.swift
class CheckoutViewController: UIViewController {
private func trackPurchase(order: Order) {
Entrolytics.track("purchase", properties: [
"order_id": order.id,
"total": order.total,
"currency": order.currency,
"payment_method": order.paymentMethod,
"items": order.items.map { item in
[
"product_id": item.productId,
"product_name": item.productName,
"quantity": item.quantity,
"price": item.price
]
}
])
// Update user properties
Entrolytics.setUserProperties([
"total_orders": getCurrentUser().orderCount + 1,
"total_spent": getCurrentUser().totalSpent + order.total,
"last_purchase": Date().timeIntervalSince1970
])
}
}Performance Monitoring
// PerformanceTracker.swift
import Foundation
import Entrolytics
class PerformanceTracker {
func trackOperation<T>(
operationName: String,
properties: [String: Any] = [:],
operation: () throws -> T
) rethrows -> T {
let startTime = Date()
do {
let result = try operation()
let duration = Date().timeIntervalSince(startTime)
Entrolytics.track("performance_operation", properties: [
"operation": operationName,
"duration_seconds": duration,
"success": true
].merging(properties) { _, new in new })
return result
} catch {
let duration = Date().timeIntervalSince(startTime)
Entrolytics.track("performance_operation", properties: [
"operation": operationName,
"duration_seconds": duration,
"success": false,
"error_type": String(describing: type(of: error))
].merging(properties) { _, new in new })
throw error
}
}
func trackOperationAsync<T>(
operationName: String,
properties: [String: Any] = [:],
operation: () async throws -> T
) async rethrows -> T {
let startTime = Date()
do {
let result = try await operation()
let duration = Date().timeIntervalSince(startTime)
await MainActor.run {
Entrolytics.track("performance_operation", properties: [
"operation": operationName,
"duration_seconds": duration,
"success": true
].merging(properties) { _, new in new })
}
return result
} catch {
let duration = Date().timeIntervalSince(startTime)
await MainActor.run {
Entrolytics.track("performance_operation", properties: [
"operation": operationName,
"duration_seconds": duration,
"success": false,
"error_type": String(describing: type(of: error))
].merging(properties) { _, new in new })
}
throw error
}
}
}
// Usage
class DataManager {
private let performanceTracker = PerformanceTracker()
func loadUserData(userId: String) async throws -> User {
return try await performanceTracker.trackOperationAsync(
operationName: "load_user_data",
properties: ["user_id": userId]
) {
try await apiService.getUser(id: userId)
}
}
}Error and Crash Tracking
// ErrorTracker.swift
import Foundation
import Entrolytics
import UIKit
class ErrorTracker {
static func setup() {
// Set up crash handler
NSSetUncaughtExceptionHandler { exception in
ErrorTracker.trackCrash(exception)
}
// Set up signal handlers
SignalHandler.setup()
}
static func trackCrash(_ exception: NSException) {
Entrolytics.track("app_crash", properties: [
"error_type": String(describing: type(of: exception)),
"error_reason": exception.reason ?? "Unknown",
"stack_trace": exception.callStackSymbols.joined(separator: "\n"),
"device_info": getDeviceInfo(),
"app_version": getAppVersion(),
"build_type": isDebug ? "debug" : "release"
])
}
static func trackHandledError(_ error: Error, context: String = "") {
Entrolytics.track("handled_error", properties: [
"error_type": String(describing: type(of: error)),
"error_message": error.localizedDescription,
"context": context,
"stack_trace": Thread.callStackSymbols.joined(separator: "\n")
])
}
private static func getDeviceInfo() -> [String: String] {
let device = UIDevice.current
return [
"model": device.model,
"system_name": device.systemName,
"system_version": device.systemVersion,
"name": device.name
]
}
private static func getAppVersion() -> String {
let bundle = Bundle.main
let version = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
let build = bundle.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
return "\(version) (\(build))"
}
}
// SignalHandler.swift
import Foundation
class SignalHandler {
static func setup() {
signal(SIGABRT, crashHandler)
signal(SIGILL, crashHandler)
signal(SIGSEGV, crashHandler)
signal(SIGFPE, crashHandler)
signal(SIGBUS, crashHandler)
signal(SIGPIPE, crashHandler)
}
private static func crashHandler(signal: Int32) {
ErrorTracker.trackCrash(NSException(
name: NSExceptionName(rawValue: "Signal \(signal)"),
reason: "App crashed with signal \(signal)",
userInfo: nil
))
// Call previous handler
let previousHandler = signal(signal, SIG_DFL)
if previousHandler != SIG_DFL {
previousHandler(signal)
}
}
}Testing
Unit Tests
// EntrolyticsTests.swift
import XCTest
@testable import Entrolytics
class EntrolyticsTests: XCTestCase {
override func setUp() {
super.setUp()
// Reset SDK before each test
Entrolytics.reset()
}
func testTrackEvent() {
// Given
let eventName = "test_event"
let properties = ["property": "value"]
// When
Entrolytics.track(eventName, properties: properties)
// Then
// In real tests, you would verify the event was queued
XCTAssertTrue(Entrolytics.isInitialized())
}
func testIdentifyUser() {
// Given
let userId = "user-123"
let traits = ["email": "user@example.com"]
// When
Entrolytics.identify(userId: userId, traits: traits)
// Then
XCTAssertEqual(Entrolytics.getUserId(), userId)
}
func testScreenTracking() {
// Given
let screenName = "TestScreen"
let properties = ["test": true]
// When
Entrolytics.screen(name: screenName, properties: properties)
// Then
// Verify screen was tracked
XCTAssertTrue(true) // Placeholder assertion
}
}UI Tests
// EntrolyticsUITests.swift
import XCTest
class EntrolyticsUITests: XCTestCase {
var app: XCUIApplication!
override func setUp() {
super.setUp()
continueAfterFailure = false
app = XCUIApplication()
app.launch()
}
func testButtonTracking() {
// Given
let button = app.buttons["Sign Up"]
// When
button.tap()
// Then
// In real UI tests, you might verify analytics were called
XCTAssertTrue(button.exists)
}
func testScreenNavigation() {
// Given
let profileButton = app.buttons["Profile"]
// When
profileButton.tap()
// Then
let profileTitle = app.staticTexts["Profile"]
XCTAssertTrue(profileTitle.waitForExistence(timeout: 5))
}
}Performance Optimization
Battery Optimization
// BatteryAwareTracker.swift
import UIKit
import Entrolytics
class BatteryAwareTracker {
func trackWithBatteryAwareness(
event: String,
properties: [String: Any] = [:]
) {
let batteryLevel = UIDevice.current.batteryLevel
if batteryLevel < 0.2 {
// Low battery mode - reduce tracking frequency
if Double.random(in: 0...1) < 0.1 { // Only track 10% of events
Entrolytics.track(event, properties: properties.merging([
"battery_saver": true,
"battery_level": batteryLevel
]) { _, new in new })
}
} else {
// Normal tracking
Entrolytics.track(event, properties: properties.merging([
"battery_level": batteryLevel
]) { _, new in new })
}
}
}Memory Management
// MemoryEfficientTracker.swift
import Foundation
import Entrolytics
class MemoryEfficientTracker {
private var eventQueue: [Event] = []
private let maxQueueSize = 100
private let queue = DispatchQueue(label: "analytics.queue", qos: .utility)
func addEvent(_ event: Event) {
queue.async { [weak self] in
guard let self = self else { return }
if self.eventQueue.count >= self.maxQueueSize {
// Remove oldest events to prevent memory issues
self.eventQueue.removeFirst()
}
self.eventQueue.append(event)
// Flush if queue is getting full
if self.eventQueue.count >= Int(Double(self.maxQueueSize) * 0.8) {
self.flushEvents()
}
}
}
private func flushEvents() {
guard !eventQueue.isEmpty else { return }
let events = eventQueue
eventQueue.removeAll()
Entrolytics.trackBatch(events)
}
func flush() {
queue.async { [weak self] in
self?.flushEvents()
}
}
}Troubleshooting
Best Practices
Migration Guide
From Other Analytics SDKs
// Before - Other analytics
import OtherAnalytics
OtherAnalytics.track("event_name", properties: ["property": "value"])
// After - Entrolytics
import Entrolytics
Entrolytics.track("event_name", properties: ["property": "value"])From Version 1.x
// Before - v1.x
Entrolytics.shared().track("event", properties: properties)
// After - v2.x
Entrolytics.track("event", properties: properties)iOS SDK for Entrolytics - First-party growth analytics for the edge