Native iOS Integration
Integrate Loyalteez into your native iOS app using Swift.
Prerequisites
- Xcode 14+ installed
- iOS 15.0+ target
- CocoaPods or Swift Package Manager
- Your Loyalteez Brand ID
Quick Start
1. Install Dependencies
Using CocoaPods:
# Podfile
pod 'Privy', '~> 1.0'
pod 'Alamofire', '~> 5.8'
pod install
Using Swift Package Manager:
Add dependencies in Xcode:
- Privy:
https://github.com/privy-io/privy-ios-sdk - Alamofire:
https://github.com/Alamofire/Alamofire
2. Configure Info.plist
<key>LSApplicationQueriesSchemes</key>
<array>
<string>loyalteez</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>yourapp</string>
</array>
</dict>
</array>
3. Create Loyalteez Service
// LoyalteezService.swift
import Foundation
import Alamofire
class LoyalteezService {
static let shared = LoyalteezService()
private let apiURL = "https://api.loyalteez.app"
private let gasRelayerURL = "https://relayer.loyalteez.app"
private let brandId = "YOUR_BRAND_ID"
// MARK: - Event Tracking
func trackEvent(
event: String,
email: String,
metadata: [String: Any] = [:],
completion: @escaping (Result<EventResponse, Error>) -> Void
) {
var params: [String: Any] = [
"event": event,
"email": email,
"metadata": metadata.merging([
"platform": "mobile",
"os": "iOS",
"appVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0"
]) { (_, new) in new }
]
AF.request(
"\(apiURL)/loyalteez-api/manual-event",
method: .post,
parameters: params,
encoding: JSONEncoding.default
)
.validate()
.responseDecodable(of: EventResponse.self) { response in
completion(response.result)
}
}
// MARK: - Gasless Transactions
func executeTransaction(
privyToken: String,
to: String,
data: String,
userAddress: String,
completion: @escaping (Result<TransactionResponse, Error>) -> Void
) {
let params: [String: Any] = [
"to": to,
"data": data,
"userAddress": userAddress,
"value": "0",
"gasLimit": 500000
]
let headers: HTTPHeaders = [
"Authorization": "Bearer \(privyToken)",
"Content-Type": "application/json"
]
AF.request(
"\(gasRelayerURL)/relay",
method: .post,
parameters: params,
encoding: JSONEncoding.default,
headers: headers
)
.validate()
.responseDecodable(of: TransactionResponse.self) { response in
completion(response.result)
}
}
// MARK: - Health Check
func checkHealth(completion: @escaping (Bool) -> Void) {
AF.request("\(apiURL)/loyalteez-api/debug")
.validate()
.response { response in
completion(response.error == nil)
}
}
}
// MARK: - Models
struct EventResponse: Codable {
let success: Bool
let ltzDistributed: Int?
let transactionHash: String?
let walletAddress: String?
}
struct TransactionResponse: Codable {
let success: Bool
let transactionHash: String
}
4. Create Reward Notification
// RewardNotificationView.swift
import SwiftUI
struct RewardNotificationView: View {
let amount: Int
@Binding var isShowing: Bool
var body: some View {
VStack(spacing: 8) {
Text("🎉")
.font(.system(size: 40))
Text("You earned \(amount) LTZ!")
.font(.system(size: 20, weight: .bold))
.foregroundColor(.white)
Text("Keep up the great work")
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.9))
}
.padding(20)
.background(Color(hex: "8CBC99"))
.cornerRadius(12)
.shadow(radius: 10)
.opacity(isShowing ? 1 : 0)
.animation(.easeInOut, value: isShowing)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
isShowing = false
}
}
}
}
extension Color {
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (255, 0, 0, 0)
}
self.init(
.sRGB,
red: Double(r) / 255,
green: Double(g) / 255,
blue: Double(b) / 255,
opacity: Double(a) / 255
)
}
}
5. Create Main View
// ContentView.swift
import SwiftUI
import Privy
struct ContentView: View {
@StateObject private var authManager = AuthManager()
@State private var isLoading = false
@State private var showReward = false
@State private var rewardAmount = 0
@State private var errorMessage: String?
var body: some View {
ZStack {
ScrollView {
VStack(spacing: 20) {
// Header
VStack(alignment: .leading, spacing: 8) {
Text("Loyalteez Demo")
.font(.system(size: 28, weight: .bold))
.foregroundColor(.white)
Text("Earn rewards for your actions")
.font(.system(size: 16))
.foregroundColor(.white.opacity(0.9))
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(20)
.background(Color(hex: "8CBC99"))
// Content
if !authManager.isAuthenticated {
loginSection
} else {
accountSection
actionsSection
}
}
}
// Reward notification overlay
if showReward {
VStack {
RewardNotificationView(amount: rewardAmount, isShowing: $showReward)
.padding(.top, 60)
Spacer()
}
}
// Loading overlay
if isLoading {
Color.black.opacity(0.5)
.edgesIgnoringSafeArea(.all)
ProgressView()
.scaleEffect(1.5)
.tint(.white)
}
}
}
// MARK: - Login Section
private var loginSection: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Get Started")
.font(.system(size: 20, weight: .bold))
Text("Login to start earning LTZ rewards")
.font(.system(size: 14))
.foregroundColor(.gray)
Button(action: handleLogin) {
Text("Login with Email")
.font(.system(size: 16, weight: .semibold))
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color(hex: "8CBC99"))
.cornerRadius(8)
}
.disabled(isLoading)
}
.padding(20)
.background(Color.white)
.cornerRadius(12)
.shadow(radius: 5)
.padding(.horizontal, 16)
}
// MARK: - Account Section
private var accountSection: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Your Account")
.font(.system(size: 20, weight: .bold))
VStack(alignment: .leading, spacing: 8) {
Text("Email:")
.font(.system(size: 12))
.foregroundColor(.gray)
Text(authManager.user?.email ?? "")
.font(.system(size: 16))
}
VStack(alignment: .leading, spacing: 8) {
Text("Wallet:")
.font(.system(size: 12))
.foregroundColor(.gray)
Text(authManager.walletAddress ?? "")
.font(.system(size: 14))
.lineLimit(1)
.truncationMode(.middle)
}
Button(action: authManager.logout) {
Text("Logout")
.font(.system(size: 16, weight: .semibold))
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color(hex: "6C33EA"))
.cornerRadius(8)
}
}
.padding(20)
.background(Color.white)
.cornerRadius(12)
.shadow(radius: 5)
.padding(.horizontal, 16)
}
// MARK: - Actions Section
private var actionsSection: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Earn Rewards")
.font(.system(size: 20, weight: .bold))
Text("Complete actions to earn LTZ")
.font(.system(size: 14))
.foregroundColor(.gray)
Button(action: handleCompleteAction) {
Text(isLoading ? "Processing..." : "Complete Action")
.font(.system(size: 16, weight: .semibold))
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color(hex: "8CBC99"))
.cornerRadius(8)
}
.disabled(isLoading)
}
.padding(20)
.background(Color.white)
.cornerRadius(12)
.shadow(radius: 5)
.padding(.horizontal, 16)
}
// MARK: - Actions
private func handleLogin() {
isLoading = true
authManager.login { success in
isLoading = false
if success, let email = authManager.user?.email {
trackLoginEvent(email: email)
}
}
}
private func trackLoginEvent(email: String) {
isLoading = true
LoyalteezService.shared.trackEvent(
event: "mobile_login",
email: email,
metadata: ["source": "ios_app"]
) { result in
isLoading = false
switch result {
case .success(let response):
if let amount = response.ltzDistributed {
rewardAmount = amount
showReward = true
}
case .failure(let error):
errorMessage = error.localizedDescription
}
}
}
private func handleCompleteAction() {
guard let email = authManager.user?.email else { return }
isLoading = true
LoyalteezService.shared.trackEvent(
event: "action_completed",
email: email,
metadata: ["actionType": "button_click", "screen": "home"]
) { result in
isLoading = false
switch result {
case .success(let response):
if let amount = response.ltzDistributed {
rewardAmount = amount
showReward = true
}
case .failure(let error):
errorMessage = error.localizedDescription
}
}
}
}
// MARK: - Auth Manager
class AuthManager: ObservableObject {
@Published var isAuthenticated = false
@Published var user: PrivyUser?
@Published var walletAddress: String?
func login(completion: @escaping (Bool) -> Void) {
// Implement Privy login
// This is pseudo-code - use actual Privy SDK methods
// Privy.shared.login { [weak self] result in
// switch result {
// case .success(let user):
// self?.isAuthenticated = true
// self?.user = user
// self?.walletAddress = user.wallet?.address
// completion(true)
// case .failure:
// completion(false)
// }
// }
}
func logout() {
isAuthenticated = false
user = nil
walletAddress = nil
}
}
struct PrivyUser {
let email: String?
let walletAddress: String?
}
6. Add Push Notifications (Optional)
// NotificationService.swift
import UserNotifications
class NotificationService {
static let shared = NotificationService()
func requestAuthorization(completion: @escaping (Bool) -> Void) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
completion(granted)
}
}
func showRewardNotification(amount: Int) {
let content = UNMutableNotificationContent()
content.title = "You earned \(amount) LTZ! 🎉"
content.body = "Keep up the great work"
content.sound = .default
let request = UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: nil
)
UNUserNotificationCenter.current().add(request)
}
}
Testing
- Build and run:
Cmd + R - Test login flow
- Complete action and verify reward
- Check Partner Portal analytics
Next Steps
- Add more screens - Wallet view, perks list
- Implement deep linking - Handle
loyalteez://URLs - Add analytics - Firebase or Mixpanel
- Submit to App Store - Prepare for production
Support
- iOS Development: Apple Developer Docs
- Privy iOS SDK: Privy Documentation
- Loyalteez API: API Reference
- Email: support@loyalteez.app