Tuesday, 3 September 2024

Open deeplink in iOS Simulator

  • Open your project in Xcode.
  • Select your project icon from the left menu Project Navigator.
  • Select the app under Targets.
  • Select Info from the top menu.
  • Expand the URL Types section.
  • Click on the Add option (+) to add a URL type.
  • Fill in the following information to create a URL scheme:
  • Identifier$(PRODUCT_BUNDLE_IDENTIFIER)
  • URL Schemes: Your custom URL scheme ( com.myApp:). This must be unique. Avoid using protected schemes such as httphttps, or mailto.
  • Open terminal and run the app.
  • Then on the booted device run the following command replacing your url scheme(under Xcode project info list) and deeplink path. 

xcrun simctl openurl booted “com.myApp://AnyDeeplink”

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()

}