dismissViewControllerAnimated freezes with UIActivityViewController (facebook) on screen - ios

I have encountered an issue where an app will freeze when dismissViewControllerAnimated is called.
It is called on the root viewcontroller (something I find suspicious), and happen when the when the user has chosen Facebook from a UIActivityController. It doesn't affect Twitter or Mail (the only other activities exposed). It also only happens on device.
So the flow is:
User initiates the UIActivityViewController Controller which shows various avenues to share content.
User chooses "facebook"
Post to facebook view is shown
(essentially) [[UIWindow rootViewController] dismissViewControllerAnimated] called, which freezes before returning.
I was curious either -
if anyone has seen this behaviour and has a solution, or at least
what are things that could cause dismissViewControllerAnimated to freeze.

I don't have an answer to the source of the freeze, however I was able to resolve the issue by dismissing the UIActivityController directly. Previously it was dismissed via the controller that presented it.
The best hypothesis I can come up with is the facebook controller (unlike the ones for mail or twitter) adjusts the relationship of the UIActivityController so that its presenting controller changes. After that it cannot be dismissed the way I was (note: my knowledge is iOS UI is pretty scant so I could be terribly wrong). I don't get why that would cause it to freeze though

Related

Firebase Analytics Continuously Tracks UINavigationController

I'm transitioning from Google Analytics to Firebase Analytics. Unlike Google Analytics, Firebase automatically tracks screen views, which is great! But, instead of tracking the screen, it continuously attempts to track the UINavigationController. I get the following error log twice every time I navigate to a different view controller.
[Firebase/Analytics][I-ACS031006] View controller already
tracked. Class, ID: UINavigationController, -1770652405567491888
Is there some configuration required when you have a navigation controller? How do I get automatic screen tracking working in this scenario?
UPDATE: I haven't found a solution to this yet, but I at least found the cause of the problem. It looks like Firebase doesn't understand your view controller hierarchy if your initial view controller is a Tab Bar Controller. My initial view controller in my main story board is a Tab Bar Controller. If I take this out, I get good screen tracking reporting from my app.
UPDATE: It looks like I've found an OK solution to this, but I'm still wondering if someone has a better idea. Since Firebase sees all of the view controllers under my Tab Bar Controller as the same UINavigationController, I can call setScreenName manually in viewDidAppear for all of them.
Analytics.setScreenName(screenName, screenClass: screenClass)
This is OK because it's not any worse than Google Analyics, but it's not ideal because the system still tries to track the UINavigationController twice for every view controller and I'm also not getting the benefit of automatic screen tracking. I looked into trying to remove the Tab Bar Controller from Firebase as some folks seem to have done, but it looks like those methods have been removed from the current (v4.0.0) version of the Firebase SDK.
Screen is automatically tracked in Google Analytics for Firebase. You can see it in the user engagement card in the Main dashboard. This has been introduced in Firebase Version 3.8.0 for iOS. More in track screen doc. Quoting from the doc:
Events that occur on these screens are automatically tagged with the parameter firebase_screen_class (for example, menuViewController or MenuActivity) and a generated firebase_screen_id. If your app uses a distinct UIViewController or Activity for each screen, Analytics can automatically track every screen transition and generate a report of user engagement broken down by screen.
However, you can also track screens manually using the setCurrentScreen() method. The details on this screen should be available in the user engagement card when you select the ScreenName from the dropdown, all the screens manually tracked in the code should be displayed there with the breakdown of duration on average.
Please note that the setScreenName is not an event, it is rather an event parameter that goes with the event that is tracked in the logEvent() method call.

Loading the last View Controller Active after app goes into background, swift

I have "Tinder" like swipping view that is located in a CardViewController. The card View Controller is accessed by moving through two other view controllers. i.e. Load App -> FirstViewController -> SecondViewController - > CardViewController.
When I am in the Card ViewController and I go into background mode, the app launches on the FirstViewController and on going to the cards, they are loaded from the first card in a stack of about 10?
Is there anyway to load the app from the last Card I had swipped and in the CardViewController without having to navigate from the FirstView Controller again?
I would really appreciate the help as it's horribly affecting some of my users.
An example of a Tinder like card view is shown!
The problem, from the sound of it, is not what happens when the app goes into the background — that would leave it in exactly the same state when it reactivates. The problem is what happens when the app goes into the background and quits. Your app is then relaunched from scratch, which is why you find yourself in the first view controller. What's upsetting you is the difference between the app's behavior in these two situations.
Apple provides a solution to this situation: UIViewController, along with the App Delegate, has methods permitting you to save and restore state. When the app goes into the background, the current configuration (what view controller's view is showing) is saved. That way, even when the app quits, when it relaunches it can get back to that configuration before it appears to the user. Thus, coming back from background-and-quit looks just like coming back from mere backgrounding.
For full details, see Apple's documentation. This is a good place to start:
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/PreservingandRestoringState.html

UIActivityViewController: Choosing an activity for which permissions are denied leads to a dead end

I am using a UIActivityViewController to implement image sharing.
UIActivityViewController *shareVC = [[UIActivityViewController alloc] initWithActivityItems:#[image] applicationActivities:nil];
[shareVC setCompletionHandler:^(NSString *activityType, BOOL completed){
NSLog(#"completed image export activity: %# - %d", activityType, completed);
}];
[self presentViewController:shareVC animated:true completion:nil];
In testing, I have noticed that if the user selects, for example, "Assign to Contact", but then denies the permission in the ensuing dialog, then they are taken to a screen that says "This app does not have access to your contacts. You can enable access in Privacy Settings.", from which there is no way to back out. The only way for them then to get back to the actual app is to manually restart it.
I'm fairly happy to leave this behaviour as is for now since I don't anticipate any users will be particularly bothered by it, but I'd like to know if there is a sensible way to work around it, and if it is indeed the expected behaviour.
Hmm, maybe you could check if a segue is called when the new "This app does not have access to your contacts. You can..."-Viewcontroller is shown. Then you could manipulate the destinationViewController property of the segue and add for example a button, with an action enabling the user to go back.
All highly theroretical.
I am working on the iPad platform, and in that context presenting a UIActivityViewController directly is wrong according to the documentation, which states that
When presenting the view controller, you must do so using the appropriate means for the current device. On iPad, you must present the view controller in a popover. On iPhone and iPod touch, you must present it modally.
In most ways, the behaviour of the modally presented view controller on iPad seemed perfectly fine, but this case illustrates why it is not appropriate. I don't know how the equivalent case is handled on iPhone / iPod touch as I haven't really looked at those platforms just yet.
Now that I am presenting it in a UIPopoverController, the view explaining that the app doesn't have access to contacts appears within the popup, and is dismissed as the popup is dismissed. Unfortunately it appears that the completion handler is not called in this case; I'm not sure if that's an iOS bug or if I lack understanding of something. It is a slight problem for me at the moment, so I'll have to work around it.
Unfortunately, it seems relatively unlikely that anyone experiencing a related problem will be helped by my question / answer; maybe I'll try to edit the question.

ios: programmatically ask for Game Center sign-in?

I have a simple question, but I’ve looked through Apple’s documentation and done some searching and I can’t find the answer to it.
Is it possible to programmatically pull up Game Center’s sign-in view? I have a UIButton that requires Game Center, and if the client does not sign in when the app is opened (iOS pulls up the sign-in view at launch), I want to provide a second chance for the user to sign in.
I'm assuming you're calling this GKLocalPlayer method on launch: -setAuthenticateHandler: (>= iOS7) or -authenticateWithCompletionHandler: (<= iOS6)
If the user cancels the presented login screen, calling these methods again does nothing, or rather, the completion handler is called with an error. The user will then need to login to GameCenter through the GameCenter app or through the Settings app. (While testing, you can login through the GameCenter app, then logout. After that the screen can be presented in your own app again.) You can show an UIAlertView telling the user to login through the GameCenter app.
Alternatively, and I don't know if this is allowed/approvable, but in iOS7 the authenticateHandler has a viewController parameter holding the login screen. If you store this login view controller in an instance variable and the user cancels login, you can present the login screen again later using a UINavigationController.
If you try to present the saved login view controller with -presentViewController:animated:completion: the view controller's Cancel button no longer works, but using a UINavigationController hides the Cancel button and allows navigation back to your own view controller.
You'll also need to hide the login screen manually after the user logs in by responding to GKPlayerAuthenticationDidChangeNotificationName. It doesn't seem like developers were intended to be able to do this, so it may not pass approval, but it works!

iOS: Hiding sensitive information on the screen when app is backgrounded

When a foreground app gets backgrounded (e.g. Home button gets pressed), how can I change elements on the topmost view controller prior to when iOS takes a snapshot of it and starts the animation to show the next screen?
I ask because I'm writing an app requiring HIPAA compliance, and I am concerned that the snapshot that the OS takes in order to do this animation sometimes contains sensitive data which should not be visible even for a split second when the app gets foregrounded later.
I'm aware that view controllers have lifecycle methods such as viewWillDisappear which might be usable, but I have a lot of controllers and I'd rather just have something in my App Delegate to handle this (e.g. by adding an opaque full-screen UIImageView overlay) rather than having to write custom code for this in every last controller.
I tried putting overlay-generating code in applicationWillResignActive, and I've been digging with Apple's docs and Google, but it's not working. I suspect the screenshot gets taken before the app has a chance to update the screen.
Thanks!
Not sure about HIPAA's requirements about backgrounding and possibly leaving the user logged in for someone else to resume, but the safest sounds like it would be to add a key UIApplicationExitsOnSuspend with a boolean value of YES to info.plist.
That will prevent the app from backgrounding entirely, and restarts it (possibly triggering a login procedure) every time you go back to it.
Most (if not all) mobile banking applications I've tested do this for safety reasons.
I believe the answer is to not concern oneself with changing what's on the screen before the backgrounding animation begins, but to simply modify what's displayed on the screen once the app enters the background (i.e. inside of applicationDidEnterBackground: in your App Delegate.) This solved my problem.
My UIImageView overlay idea worked here, although I decided just to pop to the root view controller instead. Simpler that way. My root view doesn't have any sensitive info.
Here's what it looks like:
-(void)applicationDidEnterBackground:(UIApplication *)application {
UINavigationController *navigationController =
(UINavigationController *)self.window.rootViewController;
[navigationController popToRootViewControllerAnimated:NO];
...
}

Resources