ARAnalytics
ARAnalytics copied to clipboard
DSL with Swift
Would be great to have support for the aspect-oriented DSL style tracking in Swift.
I can get tracked screens and some events to come through, but for events, some selectors don't seem to work and can't figure out how to properly type the Properties callback.
The following is working for me for now:
let trackedEvents = [
[
ARAnalyticsClass: AccountEditVC.self,
ARAnalyticsDetails: [
[
ARAnalyticsEventName: "Account Edit Save Button",
ARAnalyticsSelectorName: "saveChangesBtnTapped:"
],
]
]
]
let trackedScreens = [
[
ARAnalyticsClass: FeaturesVC.self,
ARAnalyticsDetails: [[ARAnalyticsPageName: "Slideshow"]]
]
]
Can't figure out how to do properties though. And sometimes selectors that aren't IBOutlets are never called.
@ashfurrow - this needs the vars / selectors /classes to be classed as dynamic
/ @objc
in swift right?
Correct; subclasses of NSObject don't need @objc since it is already implied, but properties still must be marked as dynamic.
Thanks for letting me know. So all of my tests have been on UIViewControllers so they do inherit from NSObject. However, regular old methods that aren't IBActions weren't being swizzled. Do I need to declare them like this:
public dynamic func foobar()
Also how would one do the ARAnalyticsDetails to add event properties? I couldn't figure out the closure signature.
Thanks for the help.
That's strange, that should work. Can you double-check that you're following the DSL structure strictly? It's the most common source of problems. The closure is the same as Objective-C:
(controller: MyViewController, parameters: NSArray) -> NSDictionary
I think I'm close getting Swift to work with the DSL. Screen tracking is working, but I don't think I'm hooking into the event selectors correctly.
Can anyone spot what I'm missing here?
let trackedEvents = [
[
ARAnalyticsClass: LoadingViewController.self,
ARAnalyticsDetails: [
[
ARAnalyticsEventName: "LandingRegisterButtonTapped",
ARAnalyticsSelectorName: "tapRegister:sender:"
],
[
ARAnalyticsEventName: "LandingLoginButtonTapped",
ARAnalyticsSelectorName: "tapLogin:sender:"
],
[
ARAnalyticsEventName: "QuickTest",
ARAnalyticsSelectorName: "quicktest:"
],
]
],
]
let trackedScreens = [
[
ARAnalyticsClass: LoadingViewController.self,
ARAnalyticsDetails: [[ARAnalyticsPageName: "Landing"]]
],
[
ARAnalyticsClass: RegisterViewController.self,
ARAnalyticsDetails: [[ARAnalyticsPageName: "Registration"]]
],
[
ARAnalyticsClass: LoginViewController.self,
ARAnalyticsDetails: [[ARAnalyticsPageName: "Login"]]
]
]
ARAnalytics.setupWithAnalytics(providers, configuration: [
ARAnalyticsTrackedEvents: trackedEvents,
ARAnalyticsTrackedScreens: trackedScreens
])
class LoadingViewController: UIViewController {
@IBAction func tapLogin(sender: AnyObject) {
self.quicktest()
}
@IBAction func tapRegister(sender: AnyObject) {
ARAnalytics.event("manualtapRegister", withProperties: ["property.testing": "123456"])
self.quicktest()
}
dynamic func quicktest() {
print("hi")
}
}
Much appreciated!
"tapLogin:sender:"
implies a function like: func tapLogin(thing:Thing, sender:OtherThing)
- you want "tapLogin:"
same for the rest of them, and quickTest:
has no arguments, so it's just quickTest
Thanks. I did have that at first, I should have specified. But then quicktest: should have worked, no?
I've updated, but still issues. I've also tried just "quicktest"
let trackedEvents = [
[
ARAnalyticsClass: LoadingViewController.self,
ARAnalyticsDetails: [
[
ARAnalyticsEventName: "LandingRegisterButtonTapped",
ARAnalyticsSelectorName: "tapRegister:"
],
[
ARAnalyticsEventName: "LandingLoginButtonTapped",
ARAnalyticsSelectorName: "tapLogin:"
],
[
ARAnalyticsEventName: "QuickTest",
ARAnalyticsSelectorName: "quicktest:"
],
]
],
]
Some more info. I don't think it's the selectors.
In addEventAnalyticsHook, the RSSWReplacement block is never called when trying to swizzle alloc on my VC
It does get through to the originalIMP = class_replaceMethod(classToSwizzle, selector, newIMP, methodType);
line in static void swizzle(Class classToSwizzle, SEL selector, RSSwizzleImpFactoryBlock factoryBlock)
but never seems to fire
So then I thought it might be the way I'm instantiating LoadingViewController in the storyboard, So I added a definition for RegisterViewController which I instantiate in code
In that example, I do see getting into NSObjectRACSignalForSelector but failing if (targetMethod == NULL)
and getting to class_replaceMethod
with the right selector string
Also, on this class though, I never get into the RSSWReplacement block
I was able to verify my selector strings if I can figure this part out
let loadingVC = LoadingViewController()
let canRegister = loadingVC.respondsToSelector(Selector("tapRegister:"))
let canLogin = loadingVC.respondsToSelector(Selector("tapLogin:"))
let canTest = loadingVC.respondsToSelector(Selector("quicktest"))
print("canRegister \(canRegister)")
print("canRegister \(canLogin)")
print("canRegister \(canTest)")
let registerVC = RegisterViewController()
let canPhoto = registerVC.respondsToSelector(Selector("tapAddPicture:"))
print("canPhoto \(canPhoto)")
unable to figure out a proper way to add ARAnalyticsProperties callback
let someClassEvents: NSDictionary = [
ARAnalyticsClass : NSClassFromString("ViewController")!,
ARAnalyticsDetails : [
ARAnalyticsEventName : "SomeEvent",
ARAnalyticsSelectorName : "viewDidLoad",
ARAnalyticsProperties : {(controller: UIViewController, parameters: NSArray) -> NSDictionary in
return ["somekey" : "somevalue"]
}
]
]
I am getting the following error on the closure. Any suggestions?
Contextual type 'AnyObject' cannot be used with dictionary literal
Remove the : NSDictionary
- the examples above don't have them.
After removing : NSDictionary, I am getting error on the return statement
'(dictionaryLiteral: (NSCopying, AnyObject))' is not convertible to '(dictionaryLiteral: (NSString, NSString)...)'
Could you help us with a Swift Example, if possible?
"viewDidLoad"
should be "viewDidLoad:"
because it has a parameter. Can you try that?
Oh wait, no it doesn't >.<
@ashfurrow the error is on compile time. @orta I have updated to the following, now the error changes
let someClassEvents = [
ARAnalyticsClass : NSClassFromString("ViewController")!,
ARAnalyticsDetails : [
ARAnalyticsEventName : "SomeEvent",
ARAnalyticsSelectorName : "viewDidLoad",
ARAnalyticsProperties : {(controller: AnyObject!, parameters:[AnyObject]!) -> NSDictionary in
var someDict = NSMutableDictionary()
someDict["someKey"] = "someValue"
return someDict
}
] as NSDictionary
]
Error is while inserting the block to the dictionary.
Value of type '(AnyObject!, [AnyObject]!) -> NSDictionary' does not conform to expected dictionary value type 'AnyObject'
I got it working by adding another ObjC class called AREvents in my project.
@implementation AREvents
+(NSDictionary *)eventWithName:(NSString *)name selector:(NSString *)selector properties:(NSDictionary *(^)(id, NSArray *))propertiesBlock
{
return @{
@"event" : name,
@"selector" : selector,
@"properties" : propertiesBlock
};
}
@end
I tried importing ARDSL.h to use the externs, but was getting some error, will resolve that later. This serves my needs as of now.
We had a similar issue with putting Swift closures in NSDictionaries. Our solution involves an unsafeBitCast
from the closure to AnyObject
, which makes the compiler happy but you need to be careful. We wrapped the whole thing up into this abstraction, so we would call toBlock()
on the closure when it had to be turned into an AnyObject
for NSDictionary
use. I think that solution would work here.
Ideally we'd provide some mechanism in ARAnalytics to do this for you, but that would mean introducing the first Swift code into it, which has implications around which versions of iOS we can support, etc. Maybe we could put something in another library? Open to suggestions on that. In the meantime @icios, try that out and see if it works :cake:
OK, this caught me, after some debugging I've figured out that the alloc
function isn't called when you initialise view controllers from swift, so:
Sale *sale = [Sale modelWithJSON:@{}];
SaleViewModel *model = [[SaleViewModel alloc] initWithSale:sale saleArtworks:@[]];
id viewController = [[AuctionInformationViewController alloc] initWithSaleViewModel:model];
[self pushViewController:viewController animated:YES];
sets the VC up for analytics
func userDidPressInfo(titleView: AuctionTitleView) {
let auctionInforVC = AuctionInformationViewController(saleViewModel: saleViewModel)
auctionInforVC.titleViewDelegate = self
[...]
Does not.
Whoa.
Any news for an update of the DSL for Swift?
No, we've been adding them inline in swift ATM due to time constraints, you're welcome to have a think / PR about it though
Yea, I'll check if I can come up with something. Is it even possible to do it in Swift or would it only work with classes inheriting from NSObject?
Only NSObject is possible I'm afraid.
Ok, thanks!