Present GameCenter authenticationVC again - ios

I'm facing a little issue here and I hope someone will help me figure out what is wrong.
*The test project presented below can be find here : http://goo.gl/wz84aA (FR) or https://goo.gl/0m8LrZ (Mega.NZ) *
I'm trying to present to the user the authentification view controller proposed by apple for the GameCenter feature. More precisely, re-present it if he canceled it on the first time.
I have a game with a storyboard like that :
GameNavigationController :
class GameNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("showAuthenticationViewController"), name: PresentAuthenticationViewController, object: nil)
GameKitHelper.sharedInstance.authenticateLocalPlayer()
}
func showAuthenticationViewController() {
let gameKitHelper = GameKitHelper.sharedInstance
if let authenticationViewController = gameKitHelper.authenticationViewController {
self.topViewController.presentViewController(authenticationViewController, animated: true, completion: nil)
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
MenuViewController :
class MenuViewController: UIViewController {
#IBAction func didTapLeaderboardBTN() {
// TRY 2
//if ( !GameKitHelper.sharedInstance.gameCenterEnabled) {
GameKitHelper.sharedInstance.authenticateLocalPlayer()
//} else {
GameKitHelper.sharedInstance.showGKGameCenterViewController(self)
//}
}
}
GameKitHelper :
import GameKit
import Foundation
let PresentAuthenticationViewController = "PresentAuthenticationViewController"
let singleton = GameKitHelper()
class GameKitHelper: NSObject, GKGameCenterControllerDelegate {
var authenticationViewController: UIViewController?
var lastError: NSError?
var gameCenterEnabled: Bool
class var sharedInstance: GameKitHelper {
return singleton
}
override init() {
gameCenterEnabled = true
super.init()
}
func authenticateLocalPlayer () {
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = { (viewController, error) in
self.lastError = error
if viewController != nil {
self.authenticationViewController = viewController
NSNotificationCenter.defaultCenter().postNotificationName(PresentAuthenticationViewController, object: self)
} else if localPlayer.authenticated {
self.gameCenterEnabled = true
} else {
self.gameCenterEnabled = false
}
}
}
func showGKGameCenterViewController(viewController: UIViewController!) {
if ( !self.gameCenterEnabled ) {
println("Local player is not authenticated")
// TRY 1
//self.authenticateLocalPlayer()
return
}
let gameCenterViewController = GKGameCenterViewController()
gameCenterViewController.gameCenterDelegate = self
gameCenterViewController.viewState = .Leaderboards
viewController.presentViewController(gameCenterViewController, animated: true, completion: nil)
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
}
What is currently working :
if the user is previously logged-in (within the GameCenter app) then he's able to open the leaderboard view ;
if the user wasn't logged-in then he's prompted to log-in when the game navigation controller is loaded (and then open the leaderboard).
What is NOT currently working :
if he cancel three time the authentification, then the authentification won't appear anymore (even at launch) ; // Apparently a known "problem", not "fixable"
if the user cancel his authentification, when he tries to load the leaderboard the authentification won't appear again.
I tried 2-3 things as you can see in the commented code above, but none of them is working ; I can't make the authentification view appear again.
PS : My code is written in Swift, but help in Objective-C is welcomed as well.

As you have found out, if the Game Center authentication dialog is canceled 3 times, then you can't bring it back without resetting the device.
There is another "security feature" built into Game Center which does not allow an app to re-authenticate if the user has already canceled the dialog without leaving the app. So for your authentication dialog to show, the user must leave and then re-enter your app.
There is really no way around it. What I've done in a couple of projects is to display a message to the user:
Game Center not available. Please make sure you are signed in through the Game Center app
I will show that message after trying to authenticate and if Game Center isn't available or the user is not signed in.

If you want to be able to re-present this to your user then go to settings -> General -> Reset -> -> Reset Location & Privacy.
This will force iOS to forget preferences for apps for example whether they can use location services, send you push notifications and also game centre preferences. Keep in mind this will reset privacy settings for all apps.

Related

In Cognito on iOS, handling new password required doesn't ever reach didCompleteNewPasswordStepWithError

I'm trying to implement functionality to respond to FORCE_CHANGE_PASSWORD on my iOS app that uses AWS Cognito. I used this Stack Overflow question which references this sample code. Right now, my code opens a view controller like it's supposed to; however, once on that view controller, I can't get it to do anything. In the sample code, it seems that when you want to submit the password change request you call .set on an instance of AWSTaskCompletionSource<AWSCognitoIdentityNewPasswordRequiredDetails>, yet when I do this, the protocol function didCompleteNewPasswordStepWithError is never called. Interestingly, the other protocol function getNewPasswordDetails is called quickly after viewDidLoad and I can't tell why. I believe this shouldn't be called until the user has entered their new password, etc and should be in response to .set but I could be wrong.
My code is pretty identical to the sample code and that SO post, so I'm not sure what's going wrong here.
My relevant AppDelegate code is here:
extension AppDelegate: AWSCognitoIdentityInteractiveAuthenticationDelegate {
func startNewPasswordRequired() -> AWSCognitoIdentityNewPasswordRequired {
//assume we are presenting from login vc cuz where else would we be presenting that from
DispatchQueue.main.async {
let presentVC = UIApplication.shared.keyWindow?.visibleViewController
TransitionHelperFunctions.presentResetPasswordViewController(viewController: presentVC!)
print(1)
}
var vcToReturn: ResetPasswordViewController?
returnVC { (vc) in
vcToReturn = vc
print(2)
}
print(3)
return vcToReturn!
}
//put this into its own func so we can call it on main thread
func returnVC(completion: #escaping (ResetPasswordViewController) -> () ) {
DispatchQueue.main.sync {
let storyboard = UIStoryboard(name: "ResetPassword", bundle: nil)
let resetVC = storyboard.instantiateViewController(withIdentifier: "ResetPasswordViewController") as? ResetPasswordViewController
completion(resetVC!)
}
}
}
My relevant ResetPasswordViewController code is here:
class ResetPasswordViewController: UIViewController, UITextFieldDelegate {
#IBAction func resetButtonPressed(_ sender: Any) {
var userAttributes: [String:String] = [:]
userAttributes["given_name"] = firstNameField.text!
userAttributes["family_name"] = lastNameField.text!
let details = AWSCognitoIdentityNewPasswordRequiredDetails(proposedPassword: self.passwordTextField.text!, userAttributes: userAttributes)
self.newPasswordCompletion?.set(result: details)
}
}
extension ResetPasswordViewController: AWSCognitoIdentityNewPasswordRequired {
func getNewPasswordDetails(_ newPasswordRequiredInput: AWSCognitoIdentityNewPasswordRequiredInput, newPasswordRequiredCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityNewPasswordRequiredDetails>) {
self.newPasswordCompletion = newPasswordRequiredCompletionSource
}
func didCompleteNewPasswordStepWithError(_ error: Error?) {
DispatchQueue.main.async {
if let error = error as? NSError {
print("error")
print(error)
} else {
// Handle success, in my case simply dismiss the view controller
SCLAlertViewHelperFunctions.displaySuccessAlertView(timeoutValue: 5.0, title: "Success", subTitle: "You can now login with your new passowrd", colorStyle: Constants.UIntColors.emeraldColor, colorTextButton: Constants.UIntColors.whiteColor)
self.dismiss(animated: true, completion: nil)
}
}
}
}
Thank you so much for your help in advance and let me know if you need any more information.

Microblink : Scanning both side of ID card

I am trying to scan both side of national card with mircoblink, based on their documentation for scanning both side you have to use MBDocumentVerificationOverlayViewController for controller and MBBlinkIdCombinedRecognizer for recognizer. but only my front side scanning works well.
I am using demo serial key, I don't know is it related to to my serial key or not.
here is my code:
/** Create BlinkID recognizer */
blinkIdRecognizer = MBBlinkIdCombinedRecognizer()
/** Create BlinkID settings */
let settings : MBDocumentVerificationOverlaySettings = MBDocumentVerificationOverlaySettings()
/** Crate recognizer collection */
let recognizerCollection : MBRecognizerCollection = MBRecognizerCollection(recognizers: [blinkIdRecognizer!])
/** Create your overlay view controller */
let documentOverlayViewController : MBDocumentVerificationOverlayViewController = MBDocumentVerificationOverlayViewController(settings: settings, recognizerCollection: recognizerCollection, delegate: self)
/** Create recognizer view controller with wanted overlay view controller */
let recognizerRunneViewController : UIViewController = MBViewControllerFactory.recognizerRunnerViewController(withOverlayViewController: documentOverlayViewController)
/** Present the recognizer runner view controller. You can use other presentation methods as well (instead of presentViewController) */
present(recognizerRunneViewController, animated: true, completion: nil)
This is my delegate code:
extension MyVC: MBDocumentVerificationOverlayViewControllerDelegate {
func documentVerificationOverlayViewControllerDidFinishScanningFirstSide(_ documentVerificationOverlayViewController: MBDocumentVerificationOverlayViewController) {
print("First Side Scanned")
}
func documentVerificationOverlayViewControllerDidFinishScanning(_ documentVerificationOverlayViewController: MBDocumentVerificationOverlayViewController, state: MBRecognizerResultState) {
if (self.blinkIdRecognizer?.combinedResult.resultState == MBRecognizerResultState.valid) {
guard let result = blinkIdRecognizer?.combinedResult else {
return
}
DispatchQueue.main.async {
if self.blinkIdRecognizer?.combinedResult.scanningFirstSideDone == true {
} else {
documentVerificationOverlayViewController.dismiss(animated: true, completion: nil)
}
}
}
}
func documentVerificationOverlayViewControllerDidTapClose(_ documentVerificationOverlayViewController: MBDocumentVerificationOverlayViewController) {
self.dismiss(animated: true, completion: nil)
}
}
And scanning first side delegate never get called, but I see response in DidFinish
thanks for any help
What version of the SDK are you using?
In version 5.2, we have added scanning for both the front and backside of the German ID.
You can download the latest release here:
https://github.com/BlinkID/blinkid-ios/releases
Can you please test it now and let us know if that worked?
Milan
Last time I've worked with microblink was over a year ago but if I recall correctly documentVerificationOverlayViewControllerDidFinishScanningFirstSide is only available for the supported id cards.
If you're scanning an ID card from another country you'll need to implement that yourself.
For example:
func documentVerificationOverlayViewControllerDidFinishScanning(_ documentVerificationOverlayViewController: MBDocumentVerificationOverlayViewController, state: MBRecognizerResultState) {
if step == .first {
// Present another ViewController for the back
showBackScanner()
} else {
processData()
}
}

AdMob - Get Notify when isReady property changes

I trying implement Rewarded Ad - Rewarded Ads New APIs (Beta). Video is load and isReady property is changing to true in a couple of seconds.
I have a button on which user press and Rewarded Video appear
This is function which is fire when user press on button
func presentRewardAd(from viewController: UIViewController) {
if rewardedAd.isReady {
rewardedAd.present(fromRootViewController: viewController, delegate: self)
}
}
The problem is
I want to hide button until video isReady == true, and when it's ready show button. So i want to get notify when rewardedAd.isReady is changing.
What i try so far:
class CustomRewardAd: GADRewardedAd {
private var observation: NSKeyValueObservation?
override init(adUnitID: String) {
super.init(adUnitID: adUnitID)
observation = observe(\.isReady, options: [.old, .new]) { object, change in
print("isReady changed from: \(change.oldValue!), updated to: \(change.newValue!)")
}
}
}
Also i tried this Using Key-Value Observing in Swift but same result.
But changeHandler never gets called. Am i doing something wrong?
Thanks!
I found solution, not ideal but it's works! Maybe this is can help someone in future.
When new rewarded request finishes, isReady property is set to true or false depends what response is.
private func createAndLoadRewardedAd() -> GADRewardedAd {
let rewardedAd = GADRewardedAd(adUnitID: "ca-app-pub-3940256099942544/1712485313")
rewardedAd.load(GADRequest()) { [weak self] error in
guard let self = self else { return }
self.videoIsReady?(rewardedAd.isReady) // already set
}
return rewardedAd
}
You are welcome!

Display coach marks view when user launches app for the first time Swift 3

I need to launch a coach marks/tutorial view over a tableview when the user launches the app for the first time. The coach marks will never display again, unless the user deletes and reinstalls the app.
I scoured the internet, but was unable to find a simple solution. Oh, I'm fairly new to Swift, so please be gentle. :)
EDIT:
Here is the what I used. Works great!
override func viewDidAppear(_ animated: Bool) {
if !UserDefaults.standard.bool(forKey: "isSecondTime") {
let launchedBefore = UserDefaults.standard.bool(forKey: "isSecondTime")
if launchedBefore {
print("Not the first launch.")
} else {
let VC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "CoachMarksViewController")
self.present(VC, animated: true, completion: nil)
print("This the first launch.")
UserDefaults.standard.set(true, forKey: "isSecondTime")
}
}
}
Probably this is what you need?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated);
let kFirstLaunch = "First_launch"
if (UserDefaults.standard.object(forKey: kFirstLaunch) == nil) {
UserDefaults.standard.set(1, forKey: kFirstLaunch);
//Show your coach marks here
}
}
You should use "UserDefaults" to achieve this. In your "viewDidLoad" method check if a value let's say "isSecondTime == true" (This value you will access from "UserDefaults") then do nothing otherwise in else section show your tutorial and save the value "isSecondTime = true" in "UserDefaults". It will work according to your requirements. Check this code:
if(NSUserDefaults.standardUserDefaults().boolForKey("isSecondTime"))
{
// app already launched
}
else
{
// This is the first launch ever
// show your tutorial.....!
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isSecondTime")
NSUserDefaults.standardUserDefaults().synchronize()
}
For Swift 3:
let launchedBefore = UserDefaults.standard.bool(forKey: "isSecondTime")
if launchedBefore {
print("Not first launch.")
} else {
// // show your tutorial.....!
print("First launch, setting UserDefault.")
UserDefaults.standard.set(true, forKey: "isSecondTime")
}
You should use this little launches manager :)
enum AppLaunchManager {
// MARK: - Private Attributes
private static let launchesCountKey = "LaunchesCount"
// MARK: Public Enum Methods
static func launchesCount() -> Int {
guard let launched = UserDefaults.standard.value(forKey: launchesCountKey) as? Int else {
return 0
}
return launched
}
static func registerLaunch() {
UserDefaults.standard.setValue(launchesCount() + 1, forKey: launchesCountKey)
}
static func isFirstLaunch() -> Bool {
return launchesCount() <= 1
}
}
And in AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
AppLaunchManager.registerLaunch()
return true
}
Then, whenever you need just use:
if AppLaunchManager.isFirstLaunch() {
/* do whatever you need */
}
So you need a bool to indicate whether to show the coach marks/tutorial that persists across the app being terminated and restarted. The first time the app is run the bool will indicate that the coach marks must be displayed then the app will set the bool to indicate that they are no longer required.
To persist the bool you could use UserDefaults to store the bool and read it when the app starts. Here is the link to the Apple documentation on it.
p.s. it might be nice to give the user a way to display them again as they might forget something or just dismiss them without thinking.

UIDocumentInteractionController Open Menu Cancelled Callback

I am currently developing an application specifically for iOS7 that utilizes UIDocumentInteractionController open in menu and need a method that notifies me when a user cancels and does not choose an available option.
UIDocumentInteractionControllerDelegate offers:
- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *) controller
but this does not specify whether the user tapped one of the available options or cancel.
Any ideas?
NOTE: This will not work for iOS 8 anymore, only iOS7 and earlier
To determine whether the user has canceled the menu or selected an option, you have to make use of the following delegate methods:
1-
- (void)documentInteractionController:(UIDocumentInteractionController *)controller
didEndSendingToApplication:(NSString *)application
{
//get called only when the user selected an option and then the delegate method bellow get called
// Set flag here _isOptionSelected = YES;
_isOptionSelected = YES;
}
2-
- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller
{
//called whether the user has selected option or not
// check your flag here
if(_isOptionSelected == NO) {
//the user has canceled the menu
}
_isOptionSelected = NO;
}
iOS 8
For iOS 8 and above, use this method instead of the one in step 2:
- (void)documentInteractionController:(UIDocumentInteractionController *)controller
didEndSendingToApplication:(NSString *)application
This will work on iOS7 && iOS8
BOOL didSelectOptionFromDocumentController = NO;//**set this to "NO" every time you present your documentInteractionController too
-(void)documentInteractionController:(UIDocumentInteractionController *)controller willBeginSendingToApplication:(NSString *)application {
didSelectOptionFromDocumentController = YES;
}
-(void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller {
if (didSelectOptionFromDocumentController == NO) {//user cancelled.
}
}
This works for iOS8 & iOS9 for 3rd party apps AND System Apps!
It's not pretty but it works.
Can anyone tell me if this will pass App Review? Not sure as I'm referring to a class name which is not publicly accessible (_UIDocumentActivityViewController).
This is Swift 2.2!
NSObject Extension to get a string of the class name:
extension NSObject {
var theClassName: String {
return NSStringFromClass(self.dynamicType)
}
}
Your Viewcontroller where you're calling the UIDocumentInteractionController from:
var appOpened = false
var presentedVCMonitoringTimer: NSTimer!
var docController: UIDocumentInteractionController!
func openDocController() {
docController = UIDocumentInteractionController(URL: yourURL!)
docController.UTI = "your.UTI"
docController.delegate = self
docController.presentOptionsMenuFromRect(CGRectZero, inView: self.view, animated: true)
// Check the class of the presentedViewController every 2 seconds
presentedVCMonitoringTimer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: #selector(self.checkPresentedVC), userInfo: nil, repeats: true)
}
func checkPresentedVC() {
if let navVC = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController {
print(navVC.presentedViewController?.theClassName)
if navVC.presentedViewController != nil && (navVC.presentedViewController?.theClassName)! != "_UIDocumentActivityViewController" && (navVC.presentedViewController?.theClassName)! != self.theClassName {
// A system App was chosen from the 'Open In' dialog
// The presented ViewController is not the DocumentInteractionController (anymore) and it's not this viewcontroller anymore (could be for example the MFMailComposeViewController if the user chose the mail app)
appOpened = true
presentedVCMonitoringTimer?.invalidate()
presentedVCMonitoringTimer = nil
}
}
}
func documentInteractionControllerDidDismissOptionsMenu(controller: UIDocumentInteractionController) {
print("dismissedOptionsMenu")
presentedVCMonitoringTimer?.invalidate()
presentedVCMonitoringTimer = nil
if appOpened {
// Do your thing. The cancel button was not pressed
appOpened = false
}
else {
// Do your thing. The cancel button was pressed
}
}
func documentInteractionController(controller: UIDocumentInteractionController, willBeginSendingToApplication application: String?) {
// A third party app was chosen from the 'Open In' menu.
appOpened = true
presentedVCMonitoringTimer?.invalidate()
presentedVCMonitoringTimer = nil
}
For Swift 4, use this:
func documentInteractionControllerDidDismissOpenInMenu(_ controller: UIDocumentInteractionController) {
// this function get called when users finish their work,
// either for sharing thing within the same app or exit to other app will do
}
I use it when after users have shared image to Facebook and Instagram.

Resources