Showing posts with label swiftui. Show all posts
Showing posts with label swiftui. Show all posts

Sunday, 25 August 2024

Track screen Name Firebase from iOS app using single file and single line. Using Objective C feature: Method Swizzling

Only add logger function in app delegate once and get logs from every screen:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // Override point for customization after application launch.

        // Ensure swizzling happens at app launch to log screens

        UIViewController.swizzleViewDidAppear
//// rest of your code...........


Create a logger File "ViewContronller+Extension.swift"

import UIKit 
import Foundation

import FirebaseAnalytics


extension UIViewController {

    /// Tracks the screen view event using Firebase Analytics.

    /// - Parameters:

    ///   - screenName: Custom name for the screen. If not provided, the view controller's class name is used.

    ///   - screenClass: Optional custom class name. Defaults to the view controller's class.

    func trackScreenView(screenName: String? = nil, screenClass: String? = nil) {

        let name = screenName ?? String(describing: type(of: self))

        let className = screenClass ?? String(describing: type(of: self))


        Analytics.logEvent(AnalyticsEventScreenView, parameters: [

            AnalyticsParameterScreenName: name,

            AnalyticsParameterScreenClass: className

        ])

    }

    /// Returns the name of the current screen, which is the view controller's class name.

     var currentScreenName: String {

         return String(describing: type(of: self))

     }


     /// Returns the class name of the current screen.

     var currentScreenClass: String {

         return NSStringFromClass(type(of: self))

     }

    /// Logs the screen view event using Firebase Analytics.

      func logScreenViewEvent() {

          print("FireBase Log: CSreenName \(currentScreenName), CScreenClass: \(currentScreenClass)")

          Analytics.logEvent(AnalyticsEventScreenView, parameters: [

              AnalyticsParameterScreenName: currentScreenName,

              AnalyticsParameterScreenClass: currentScreenClass

          ])

      }

}


extension UIViewController {

    // Swizzle the `viewDidAppear` method

    static let swizzleViewDidAppear: Void = {

        let originalSelector = #selector(UIViewController.viewDidAppear(_:))// original fuinction

        let swizzledSelector = #selector(swizzled_viewDidAppear(_:))// Swizzled Function

        guard let originalMethod = class_getInstanceMethod(UIViewController.self, originalSelector),

              let swizzledMethod = class_getInstanceMethod(UIViewController.self, swizzledSelector) else { return}

        method_exchangeImplementations(originalMethod, swizzledMethod)// Swicth the functions

    }()

    

    // New implementation of `viewDidAppear`

    @objc func swizzled_viewDidAppear(_ animated: Bool) {

        swizzled_viewDidAppear(animated)// Call the original `viewDidAppear` (actually the swizzled method)

        logScreenViewEvent()// Log the screen view event

    }

}


// Ensure swizzling happens at app launch

extension UIApplication {

    private static let runOnce: Void = {

        UIViewController.swizzleViewDidAppear

    }()

    

    override open var next: UIResponder? { // calling original functionality after logging

        UIApplication.runOnce

        return super.next

    }

}

Friday, 16 August 2024

Swift UI app to exchange currency using openexchangerates.org Api using MVVM

 

import SwiftUI

import Combine


// View

struct CurrencyConverterView1: View {

    @StateObject private var viewModel = CurrencyConverterView1Model()


    var body: some View {

        VStack {

            Picker("Select Currency", selection: $viewModel.selectedCurrency) {

                ForEach(viewModel.currencies, id: \.self) { currency in

                    Text(currency).tag(currency)

                }

            }

            .pickerStyle(MenuPickerStyle())

            

            TextField("Enter Amount", text: $viewModel.amount)

                .keyboardType(.decimalPad)

                .padding()

                .border(Color.gray)


            if let results = viewModel.convertedResults {// list view if data added

                List(results, id: \.currency) { result in

                    HStack {

                        Text(result.currency)

                        Spacer()

                        Text(String(format: "%.2f", result.amount))

                    }

                }

            }

        }

        .onAppear {

            viewModel.loadRates() // call api to fetch data 

        }

    }

}


// View Model

class CurrencyConverterView1Model: ObservableObject {

    @Published var currencies: [String] = []

    @Published var selectedCurrency: String = "USD"

    @Published var amount: String = "1"

    @Published var convertedResults: [ConversionResult1]?


    private var rates: [String: Double] = [:]

    private let dataService = ExchangeRatesService()


    func loadRates() {// Check if data outdated

        if dataService.isDataStale() { // last time check

            dataService.fetchRates { [weak self] rates in

                DispatchQueue.main.async {

                    self?.rates = rates

                    self?.currencies = Array(rates.keys)

                    self?.convertAmount()

                }

            }

        } else// Load from local storage

            rates = dataService.loadRatesFromStorage()

            currencies = Array(rates.keys)

            convertAmount()

        }

    }


    func convertAmount() {

        guard let inputAmount = Double(amount) else { return }

        convertedResults = rates.map { currency, rate in

            ConversionResult1(currency: currency, amount: inputAmount * rate)

        }

    }

}

//Model

struct ConversionResult1 {

    let currency: String

    let amount: Double

}


//Service class that handles fetching and local storage

class ExchangeRatesService {

    private let apiURL = "https://openexchangerates.org/api/latest.json"

    private let appID = "Get yOur own key from the website"

    private let lastFetchKey = "LastFetchDate"

    private let storageKey = "SavedRates"


    func fetchRates(completion: @escaping ([String: Double]) -> Void) {

        guard let url = URL(string: "\(apiURL)?app_id=\(appID)") else { return }

        

        URLSession.shared.dataTask(with: url) { data, response, error in

            if let data = data {

                if let json = try? JSONDecoder().decode(RatesResponse.self, from: data) {

                    self.saveRatesToStorage(json.rates)

                    self.saveLastFetchDate()

                    completion(json.rates)

                }

            }

        }.resume()

    }


    func isDataStale() -> Bool {// chekc if data is old

        if let lastFetch = UserDefaults.standard.object(forKey: lastFetchKey) as? Date {

            return Date().timeIntervalSince(lastFetch) > 1800 // 30 minutes  = 60*30secs

        }

        return true

    }


    func loadRatesFromStorage() -> [String: Double] { // load existing data from userdefults

        return UserDefaults.standard.dictionary(forKey: storageKey) as? [String: Double] ?? [:]

    }


    private func saveRatesToStorage(_ rates: [String: Double]) {

        UserDefaults.standard.set(rates, forKey: storageKey)

    }


    private func saveLastFetchDate() {

        UserDefaults.standard.set(Date(), forKey: lastFetchKey)

    }

}


struct RatesResponse: Codable {

    let rates: [String: Double]

}


#Preview {

    CurrencyConverterView1()

}