BLUF: Google Analytics iOS v2 beta 4 misses tracking my first UIViewController that is loaded while hitting successfully on subsequent UIViewControllers (subclassed with GAITrackedViewController)
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Optional: automatically send uncaught exceptions to Google Analytics.
[GAI sharedInstance].trackUncaughtExceptions = YES;
// Optional: set Google Analytics dispatch interval to e.g. 20 seconds.
[GAI sharedInstance].dispatchInterval = 0;
// Optional: set debug to YES for extra debugging information.
[GAI sharedInstance].debug = YES;
// Create tracker instance.
[[GAI sharedInstance] trackerWithTrackingId:#"UA-########-1"]; // id<GAITracker> tracker =
return YES;
}
MainViewController.m
- (void)viewDidLoad
{
NSLog(#"viewDidLoad");
[super viewDidLoad];
self.trackedViewName = #"First Screen";
//[[GAI sharedInstance] dispatch];
...
}
I do not call dispatch manually on the working UIViewController's, and I have tried both calling and not calling on the non-working. But it is a mystery. No logs are generated if I don't call it manually. When I do call dispatch manually I receive the following:
GoogleAnalytics 2.0b4 -[GAIDispatcher initiateDispatch:retryNumber:]
(GAIDispatcher.m:481) DEBUG: No pending hits.
Thanks in advance!
Turns out that you should really make sure that if you override any methods, you should make sure to call super on them as well...
I had written my own viewDidAppear method but had not called [super viewDidAppear:animated];. I guess that is where GoogleAnalytics does their magic. Hopefully this will help someone else not do this as well.
in my Case , I did call super method , but it did not work at some pages .
and I just change the order and let GA before the super method , It did work now .
- (void)viewDidAppear:(BOOL)animated {
self.screenName = #"mainMenu";
[super viewDidAppear:animated];
}
Contrary to Shu Zhang answer: super should be called first and then you should add other code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.screenName = #"YourViewControllerName";
//your implementation here
}
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
///your implementation here...
}
Related
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if ([HKHealthStore isHealthDataAvailable]){
NSSet *writeDataTypes = [self dataTypesToWrite];
NSSet *readDataTypes = [self dataTypesToRead];
[self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) {
NSLog(#"%s",__func__);
if (!success) {
NSLog(#"You didn't allow HealthKit to access these read/write data types. In your app, try to handle this error gracefully when a user decides not to provide access. The error was: %#. If you're using a simulator, try it on a device.", error);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// Update the user interface based on the current user's health information.
NSLog(#"=========================== %s",__func__);
});
}];
}
}
requestAuthorizationToShareTypes does not calling back completion method.
I had a similar issue with the permissions box not appearing and hadn't set up the HKHealthStore properly, putting this beforehand fixed it for me
self.healthStore = [[HKHealthStore alloc] init];
Here is a sample implementation that returns types instead of strings as described in the comment section.
-(NSSet*)datatypesToWrite {
NSArray *quantityTypes =
#[HKQuantityTypeIdentifierHeartRate,
HKQuantityTypeIdentifierBodyTemperature,
HKQuantityTypeIdentifierBloodPressureSystolic,
HKQuantityTypeIdentifierBloodPressureDiastolic,
HKQuantityTypeIdentifierRespiratoryRate];
NSMutableArray *hkTypes = [[NSMutableArray alloc] init];
for (NSString *identifier in quantityTypes) {
HKQuantityType *quantType =
[HKObjectType quantityTypeForIdentifier:identifier];
[hkTypes addObject:quantType];
}
// Make sure the types are of the correct style (Quantity, Category, Etc.)
HKCategoryType *catType =
[HKObjectType categoryTypeForIdentifier:HKCategoryTypeIdentifierSleepAnalysis];
[hkTypes addObject:catType];
return [[NSSet alloc] initWithArray:hkTypes];
}
Each time you request new types for the first time the modal permissions dialog will show up (but it won't show up again if you re-prompt for permissions not granted). Apple's guidelines are to prompt for everything you might need, but it feels a bit against best practices to me to request 12 types up front if I know somebody only asked to save into a few of them.
Recently i have started working on iOS application development. These days i am working on a Music Player which must have following functionality:
-Online buffering of the song from php web service.
-Play, pause, stop, next song, previous song.
-two sliders, one is for volume control and other is for showing the play time of the song.
-shuffle, repeat song.
I have tried these things with AVPlayer and AVAudioPlayer but in AVAudioPlayer it is not possible to stream the data from url i think because i have tried a lot then i done this by using AVplayer but it is not supporting options like volume control etc. and even the buffering is also not proper like a have to press play button again if the buffering stops at some point. I need an urgent help for this Audio Player any tutorial any example which i can understand easily as i am new in this field.
Here is the code
NSURL *url = [[NSURL alloc] initWithString:#"http://www.androidmobiapps.com/extrememusic/app/songs/1.mp3"];
[self setupAVPlayerForURL:url];
I am calling this song at viewdidload
-(void) setupAVPlayerForURL: (NSURL*) url {
AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
AVPlayerItem *anItem = [AVPlayerItem playerItemWithAsset:asset];
audioPlayer = [AVPlayer playerWithPlayerItem:anItem];
[audioPlayer addObserver:self forKeyPath:#"status" options:0 context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == audioPlayer && [keyPath isEqualToString:#"status"]) {
if (audioPlayer.status == AVPlayerStatusFailed) {
NSLog(#"AVPlayer Failed");
} else if (audioPlayer.status == AVPlayerStatusReadyToPlay) {
NSLog(#"AVPlayer Ready to Play");
} else if (audioPlayer.status == AVPlayerItemStatusUnknown) {
NSLog(#"AVPlayer Unknown");
}
}
}
I've done functions you mentioned above as a class, and open source it. Whatever you wanna use the class directly or treat it as an AVFoundation beginner examples. Good luck!
Watch this repo on GitHub here
I am implementing an Universal application. In my application I need to Autorotate the screen for the iPad only not for the iPhone. How can I do that? I tried with the following code but it is not working.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return NO;
}
Finally I made it by making changes to the properties in plist file.
After struggling to set in UIViewController's shouldAutorotate and supportedInterfaceOrientation methods, with no success in iOS6, I found the most effective is to set it in app delegate.
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
return UIInterfaceOrientationMaskPortrait;
}
However returning UIInterfaceOrientationMaskPortraitUpsideDown was crashing my app. I don't know what I was doing wrong!
When using the authenticateHandler in iOS 6, game center won't present the login view if the user cancels it. I realize game center will auto lockout an app after 3 cancel attempts, but I'm talking about just 2 attempts. If they cancel the login, they have to leave the app and come back before game center will present the login even through the authenticateHandler is getting set again. Any ideas on how to handle this case in iOS 6?
It works fine when using the older authenticateWithCompletionHandler method:
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_6_0
GKLocalPlayer.localPlayer.authenticateHandler = authenticateLocalPlayerCompleteExtended;
#else
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:authenticateLocalPlayerComplete];
#endif
The reason this is important for my app is that it requires Game Center for multi-player. The app tries to authenticate to game center on launch, but if the user cancels we don't ask them at launch again so they won't get nagged. What we do is show a Game Center Login button if they aren't logged in when they select multi-player. The login button forces a game center login by calling authenticateWithCompletionHandler (and now by setting GKLocalPlayer.localPlayer.authenticateHandler again).
Better use runtime checks (instancesRespondToSelector:) instead of preprocessor #if statements, so that you can use recommended methods where they are available and depreciated ones elsewhere. I actually found I need to distinguish three cases before setting the invite handler, as the authentication handler might also get called with a nil view controller:
-(void)authenticateLocalPlayer
{
if ([[GKLocalPlayer class] instancesRespondToSelector:#selector(setAuthenticateHandler:)]) {
[[GKLocalPlayer localPlayer] setAuthenticateHandler:^(UIViewController *gameCenterLoginViewController, NSError *error) {
if (gameCenterLoginViewController) {
[self.presentedViewController presentViewController:gameCenterLoginViewController
animated:YES
completion:^{
[self setInviteHandlerIfAuthenticated];
}];
} else {
[self setInviteHandlerIfAuthenticated];
}
}];
} else { // alternative for iOS < 6
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error) {
[self setInviteHandlerIfAuthenticated];
}];
}
}
Yet more cases must be distinguished within the invite handler, as matchForInvite:: is new in iOS6 as well and avoids yet another round through game center view controllers:
-(void)setInviteHandlerIfAuthenticated
{
if ([GKLocalPlayer localPlayer].isAuthenticated) {
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
if (acceptedInvite) {
if ([GKMatchmaker instancesRespondToSelector:#selector(matchForInvite:completionHandler:)]) {
[self showInfoAnimating:YES completion:NULL];
[[GKMatchmaker sharedMatchmaker] matchForInvite:acceptedInvite
completionHandler:^(GKMatch *match, NSError *error) {
// ... handle invited match
}];
} else {
// alternative for iOS < 6
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:acceptedInvite] autorelease];
mmvc.matchmakerDelegate = self;
// ... present mmvc appropriately
// ... handle invited match found in delegate method matchmakerViewController:didFindMatch:
}
} else if (playersToInvite) {
// ... handle match initiated through game center
}
};
}
}
Let me know if this helps.
I dont' think this is possible in iOS 6.0. There were API calls to do this in the early SDK builds that were removed before release.
In the WWDC 2012 Video: Session 516 - Integrating Your Games with Game Center [8:30] They actually show code where you call an authenticate method:
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticationHandler = //handle the callback...
[localPlayer authenticate];
This method is now private API but you can see it in action by calling:
[[GKLocalPlayer localPlayer] performSelector:#selector(_authenticate)];
It does exactly what you want, but can't be used because it's now private.
You can also trigger the authentication process by posting the UIApplicationWillEnterForegroundNotification notification:
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]];
I assume it would be unadvisable to do this in live code.
I'm struggling with file handling on iOS.
I could already assign my file type to iOS and I can launch my app from mail with a special file.
My app is launching and I'm firing this method:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
if([url isFileURL])
{
NSString *fileConts = [[NSString alloc] initWithContentsOfFile:[NSString stringWithFormat:#"%#", url] encoding:NSUTF8StringEncoding error:nil];
[self.viewController openFile:fileConts];
fileConts = nil;
}
return YES;
}
The openFile:(NSString) method is declared in the viewController and sets the value of a textView (for now). This method works fine. I tested it via [self.viewController openFile:#"test"];.
But when my application launches with file attached, the textView keeps empty.
It seems that it doesn't adopt the string value or that it can't read the string value.
handleOpenURL will be called only if application already running (in the background).
To make sure you correctly dispatch incoming files, you also need to check it on the app launch:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
// Process url here
}
It's good idea to have 1 URL dispatcher called both from handleOpenURL and didFinishLaunchingWithOptions.
I could solve my problem.
My mistake was to initWithContentsOfFile:(NSString *)
I updated my code with
NSString *fileConts = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
Now I'm happy!
Thanks for help.