Google Analytics integration in iOS SWIFT - ios

It's my first time integrating google analytics in my app.I'm just following this official document here
I already have a tracker ID. I don't want to create configuration file.
How can I use that tracker ID and how can I integrate Google analytics?

Create extension of UIView Conroller.
extension UIViewController {
func setScreeName(name: String) {
self.title = name
self.sendScreenView()
}
func sendScreenView() {
let tracker = GAI.sharedInstance().defaultTracker
tracker.set(kGAIScreenName, value: self.title)
let builder = GAIDictionaryBuilder.createScreenView()
tracker.send(builder.build() as [NSObject : AnyObject])
}
func trackEvent(category: String, action: String, label: String, value: NSNumber?) {
let tracker = GAI.sharedInstance().defaultTracker
let trackDictionary = GAIDictionaryBuilder.createEventWithCategory(category, action: action, label: label, value: value)
tracker.send(trackDictionary.build() as [NSObject : AnyObject])
}
}
For Each view Controller viewdidload() you add following code
self.title = self.navigationItem.title!
self.sendScreenView()

Related

How can we detect if user press the "Send/Return" button on keyboard with MessageKit?

I would like the user to be able to send messages when he/she presses the "Send" button on the keyboard also. For a textField I know that can be achieved with the delagte. But could anyone please point me how can we do this in MessageKit? I was not able to find a proper method to use here.
Thanks in advance!
A detailed explanation can be found in the example project on the MessageKit repo.
The code snippet for implemeting sending the message on a Send button tap, can be seen below:
import UIKit
import MessageKit
import InputBarAccessoryView
class MyViewController: MessagesViewController {
override func viewDidLoad() {
super.viewDidLoad()
messageInputBar.delegate = self //set the delegate to receive notifications from the `InputBarAccessoryView`
}
}
extension MyViewController: InputBarAccessoryViewDelegate {
#objc internal func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
processInputBar(messageInputBar)
}
private func processInputBar(_ inputBar: InputBarAccessoryView) {
let components = inputBar.inputTextView.components
inputBar.inputTextView.text = String()
inputBar.invalidatePlugins()
inputBar.inputTextView.resignFirstResponder() // Resign first responder for iPad split view
DispatchQueue.global(qos: .default).async {
DispatchQueue.main.async { [weak self] in
guard let self = self else {return}
self.insertMessages(components)
self.messagesCollectionView.scrollToLastItem(animated: true)
}
}
}
private func insertMessages(_ data: [Any]) {
for component in data {
if let string = component as? String {
// Create a Message type and add it the the chat messages
// let message = Message(sender: currentUser, messageId: UUID().uuidString, kind: .text(string))
}
else if let image = component as? UIImage {
let item = ImageMediaItem(image: image)
// Create a Message type and add it the the chat messages
// let message = Message(sender: currentUser, messageId: UUID().uuidString, kind: .photo(item))
}
}
}
}

How to implement Paytm all in one sdk in react native giving error after adding AllInOneSDKSwiftWrapper.swift

I have created this AllInOneSDKSwiftWrapper.swift class then xcode asked me for bridging header I allowed it after adding code which is given in paytm doc it is giving multiple errors in file. i have also added AllInOneSDKSwiftWrapper.m file. i am not ios developer so i don't know how to fix and what should i write in empty generated bridging header file.
Paytm react native support documentation for both iOS and Android is very poor as it doesn't have the complete set of information required to setup and run the code in native end.
Below is the implementation for iOS running Xcode v12.0, Swift 5, RN v0.63.2
Download AppInvokeSDK.framework
Copy and paste the framework inside "ios/YourApp" folder
Open your workspace and click on File -> Add Files
Navigate to the framework, select it and click on Add (This is important, don't drag and drop as mentioned in the Paytm documentation)
In TARGETS/YourApp under General tab, the framework should be linked in the section Frameworks, Libraries, and Embedded Content
Change the Embed option to Embed and Sign
Add the below path link in PROJECT -> YourApp -> Build settings. Search for Header Search Paths and add the below path
$(SRCROOT)/../node_modules/react-native/Libraries/LinkingIOS
Import headers in Bridging-Header.h
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
AllInOneSDKSwiftWrapper.h
#import <Foundation/Foundation.h>
#import "React/RCTBridgeModule.h"
#if __has_include("RCTEventEmitter.h")
#import "RCTEventEmitter.h"
#else
#import <React/RCTEventEmitter.h>
#endif
#interface RCT_EXTERN_MODULE(AllInOneSDKSwiftWrapper, RCTEventEmitter)
RCT_EXTERN_METHOD(openPaytm:(NSString *)mid
orderId:(NSString *)oid
transactionToken:(NSString *)txnTkn
amount:(NSString *)amt
callbackUrl:(NSString *)url
isStaging: (BOOL)isStaging)
#end
AllInOneSDKSwiftWrapper.swift
import Foundation
import AppInvokeSDK
import UIKit
class EventEmitter {
/// Shared Instance.
public static var sharedInstance = EventEmitter()
// ReactNativeEventEmitter is instantiated by React Native with the bridge.
private static var eventEmitter: AllInOneSDKSwiftWrapper!
private init() {}
// When React Native instantiates the emitter it is registered here.
func registerEventEmitter(eventEmitter: AllInOneSDKSwiftWrapper) {
EventEmitter.eventEmitter = eventEmitter
}
func dispatch(name: String, body: Any?) {
EventEmitter.eventEmitter.sendEvent(withName: name, body: body)
}
/// All Events which must be support by React Native.
lazy var allEvents: [String] = {
var allEventNames: [String] = ["responseIfNotInstalled", "responseIfPaytmInstalled"]
// Append all events here
return allEventNames
}()
}
#objc(AllInOneSDKSwiftWrapper)
class AllInOneSDKSwiftWrapper: RCTEventEmitter, AIDelegate {
private let handler = AIHandler()
var viewController = UIApplication.shared.windows.first?.rootViewController
override static func moduleName() -> String! {
return "AllInOneSDKSwiftWrapper"
}
override init() {
super.init()
EventEmitter.sharedInstance.registerEventEmitter(eventEmitter: self)
NotificationCenter.default.addObserver(self, selector: #selector(getAppInvokeResponse(notification:)), name: NSNotification.Name(rawValue: "appInvokeNotification"), object: nil)
}
#objc func getAppInvokeResponse(notification: NSNotification) {
if let userInfo = notification.userInfo {
let url = userInfo["appInvokeNotificationKey"] as? String
let response = self.separateDeeplinkParamsIn(url: url, byRemovingParams: nil)
let alert = UIAlertController(title: "Response", message: response.description, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.viewController?.present(alert, animated: true, completion: nil)
sendEvent(withName: "responseIfPaytmInstalled", body: response)
}
}
/// Base overide for RCTEventEmitter.
///
/// - Returns: all supported events
#objc open override func supportedEvents() -> [String] {
return EventEmitter.sharedInstance.allEvents
}
#objc override static func requiresMainQueueSetup() -> Bool {
return true
}
#objc(openPaytm:orderId:transactionToken:amount:callbackUrl:isStaging:)
func openPaytm(_ mid: String, orderId: String, transactionToken: String, amount: String, callbackUrl: String?, isStaging: Bool) {
DispatchQueue.main.async {
var env:AIEnvironment = .production
if isStaging {
env = .staging
} else {
env = .production
}
self.handler.openPaytm(merchantId: mid, orderId: orderId, txnToken: transactionToken, amount: amount, callbackUrl: callbackUrl, delegate: self, environment: env)
}
}
#objc func separateDeeplinkParamsIn(url: String?, byRemovingParams rparams: [String]?) -> [String: String] {
guard let url = url else {
return [String : String]()
}
/// This url gets mutated until the end. The approach is working fine in current scenario. May need a revisit.
var urlString = stringByRemovingDeeplinkSymbolsIn(url: url)
var paramList = [String : String]()
let pList = urlString.components(separatedBy: CharacterSet.init(charactersIn: "&?"))
for keyvaluePair in pList {
let info = keyvaluePair.components(separatedBy: CharacterSet.init(charactersIn: "="))
if let fst = info.first , let lst = info.last, info.count == 2 {
paramList[fst] = lst.removingPercentEncoding
if let rparams = rparams, rparams.contains(info.first!) {
urlString = urlString.replacingOccurrences(of: keyvaluePair + "&", with: "")
//Please dont interchage the order
urlString = urlString.replacingOccurrences(of: keyvaluePair, with: "")
}
}
}
if let trimmedURL = pList.first {
paramList["trimmedurl"] = trimmedURL
}
return paramList
}
func stringByRemovingDeeplinkSymbolsIn(url: String) -> String {
var urlString = url.replacingOccurrences(of: "$", with: "&")
// This may need a revisit. This is doing more than just removing the deeplink symbol.
if let range = urlString.range(of: "&"), urlString.contains("?") == false {
urlString = urlString.replacingCharacters(in: range, with: "?")
}
return urlString
}
func openPaymentWebVC(_ controller: UIViewController?) {
if let vc = controller {
DispatchQueue.main.async {[weak self] in self?.viewController?.present(vc, animated: true, completion: nil)}
}
}
func didFinish(with status: AIPaymentStatus, response: [String : Any]) {
sendEvent(withName: "responseIfNotInstalled", body: response)
let alert = UIAlertController(title: "(status)", message: String(describing: response), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
DispatchQueue.main.async { self.viewController?.present(alert, animated: true, completion: nil) }
}
}
AppDelegate.m
#import "RCTLinkingManager.h" // Import this header
// Add the below function
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
NSString *urlString = url.absoluteString;
NSDictionary *userInfo =
[NSDictionary dictionaryWithObject:urlString forKey:#"appInvokeNotificationKey"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"appInvokeNotification" object:nil userInfo:userInfo];
return [RCTLinkingManager application:app openURL:url options:options];
}
PaytmT.ts
import { NativeModules, NativeEventEmitter, Alert } from 'react-native'
export class PaytmT {
/** Call this function to invoke the Paytm Flow for iOS */
invoke() {
const allInOnePaytmSDK = NativeModules.AllInOneSDKSwiftWrapper
allInOnePaytmSDK.openPaytm('MERCHANT_ID', 'ORDER_ID', 'TXN_TOKEN', 'AMOUNT', 'CALLBACKURL', true) // If production environment, change it to false
const CounterEvents = new NativeEventEmitter(NativeModules.AllInOneSDKSwiftWrapper)
CounterEvents.addListener('responseIfNotInstalled', (response) => {
Alert.alert(PaytmT.name, JSON.stringify(response))
})
CounterEvents.addListener('responseIfPaytmInstalled', (response) => {
Alert.alert(PaytmT.name, JSON.stringify(response))
})
}
}

Swift WatchConnectivity app context as Dictionary

I am working on my first Apple Watch app (an extension to my iOS app). I am facing a small problem in sending data from one WKInterfaceController to another.
My First Controller (InterfaceController.swift) has didReceiveMessage where it receives data from my iOS app.
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
let myQrValue = message["qrCode"] as! String
let myQrImage = message["qrCodeImageData"] as! Data
var myData: [AnyHashable: Any] = ["myQrValue": myQrValue, "myQrImage": myQrImage]
if myQrValue.isEmpty == false {
WKInterfaceController.reloadRootControllers(withNames: ["QrScreen"], contexts: [myData])
}
}
Then in my Second Controller (QrInterfaceController.swift), I am having below to fetch the data sent from the first controller -
override func awake(withContext context: Any?) {
super.awake(withContext: context)
print("context \(context)")
if let myData = context {
print("myData \(myData)")
// userQrNumber.setText(myData)
}
if let myQrImage = myQrImage {
userQrImage.setImageData(myQrImage)
}
if let myQrLabel = myQrLabel {
userQrNumber.setText(myQrLabel)
}
self.setTitle("")
}
I am stuck (could be simple/silly question) as how to parse my data from the context in the second controller?
Also, the didReceiveMessage works only the second time when I launch my ViewController where the sendMessage code is placed. Is it normal?
First, you might want to redeclare myData as this:
var myData: [String: Any] = ...
which makes it a bit simpler. Then, in the awake function, you’d go ahead like this:
if let myData = context as? [String: Any] {
if let myQrImage = myData["myQrValue"] as? Date {
...
Does this show you the right direction?

How to track all screens in an iOS App using Google Analytics?

What are the different possible approaches of doing it?
There are two ways of doing it:
Inheriting GAITrackedViewController for all the viewcontrollers and then setting screenName property.
Extension of UIViewController with the following function:
func trackScreenView() {
let tracker = GAI.sharedInstance().defaultTracker
tracker?.set(kGAIScreenName, value: NSStringFromClass(type(of: self)))
tracker?.send(GAIDictionaryBuilder.createScreenView().build() as [NSObject : AnyObject]?)
}

Issue with Google Analytics in Swift 2 or 3

I have a problem with Swift 2 (Swift 3) and Google Analytics.
This is the line with the problem:
tracker.send(GAIDictionaryBuilder.createScreenView().build())
Xcode tell's me:
Cannot invoke 'send' with an argument list of type '(NSMutableDictionary!)'
Update for Swift 3 (2016.10.19)
let tracker = GAI.sharedInstance().defaultTracker
let build = (GAIDictionaryBuilder.createScreenView().build() as NSDictionary) as! [AnyHashable: Any]
tracker?.send(build)
Still an ugly approach, let me know if there's an cleaner conversion.
Original
Same here, struggling to resolve tons of errors.
What I did (deprecated):
var build = GAIDictionaryBuilder.createAppView().build() as [NSObject : AnyObject]
tracker.send(build)
Edit (2015)
Thanks to #George Poulos. . Recently they updated the options, now createAppView is deprecated, should use createScreenView instead.
var build = GAIDictionaryBuilder.createScreenView().build() as [NSObject : AnyObject]
tracker.send(build)
In addition to the accepted answer:
Changed this:
tracker.send(GAIDictionaryBuilder.createEventWithCategory("UX", action: "User sign in", label: nil, value: nil).build())
To this:
tracker.send(GAIDictionaryBuilder.createEventWithCategory("UX", action: "User sign in", label: nil, value: nil).build() as [NSObject : AnyObject])
This might be a bit of an overkill, but I prefer creating a short extension and not need to type the castings every time:
In any swift file, paste the following code:
extension GAIDictionaryBuilder
{
func buildSwiftCompatible() -> [NSObject:AnyObject]
{
return self.build() as [NSObject:AnyObject]
}
}
Then you can call buildSwiftCompatible() instead of the usual build():
tracker.send(GAIDictionaryBuilder.createScreenView().buildSwiftCompatible())
Have fun.
This is a solution I came up with.. Maybe it could help some of you. It's a struct you need to instantiate in every UIViewController, but it helps with the boilerplate.
import UIKit
struct Analytics {
fileprivate let viewController: UIViewController
fileprivate let tracker = GAI.sharedInstance().defaultTracker
init (forScreen viewController: UIViewController) {
self.viewController = viewController
}
func startTracking () {
let screenView = GAIDictionaryBuilder.createScreenView().build() as NSDictionary
guard
let tracker = tracker,
let build = screenView as? [AnyHashable: Any]
else { return }
tracker.set(kGAIScreenName, value: String(describing: viewController))
tracker.send(build)
}
}
class HomeViewController: UIViewController {
lazy var analytics: Analytics = {
return Analytics(forScreen: self)
}()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear() {
super.viewWillAppear()
analytics.startTracking()
}
}
For swift 3:
let build:NSObject = GAIDictionaryBuilder.createScreenView().build()
tracker?.send(build as! [AnyHashable: Any])
let build = GAIDictionaryBuilder.createScreenView().build() as [NSObject : AnyObject]
tracker?.send(build)

Resources