问题
From the Crashlytics, we are receiving a large amount of crash reports in the Google Login process. The crash report is as follows:
Fatal Exception: NSInvalidArgumentException
uiDelegate must either be a |UIViewController| or implement the |signIn:presentViewController:| and |signIn:dismissViewController:| methods from |GIDSignInUIDelegate|.
Fatal Exception: NSInvalidArgumentException
0 CoreFoundation 0x1837f2db0 __exceptionPreprocess
1 libobjc.A.dylib 0x182e57f80 objc_exception_throw
2 CoreFoundation 0x1837f2cf8 -[NSException initWithCoder:]
3 Hello English 0x100583d18 -[GIDSignIn assertValidUIDelegate] (GIDSignIn.m:512)
4 Hello English 0x1005861dc -[GIDSignIn signInWithOptions:] (GIDSignIn.m:961)
5 Hello English 0x10058a0e4 -[GIDSignInButton pressed] (GIDSignInButton.m:385)
6 UIKit 0x188988be8 -[UIApplication sendAction:to:from:forEvent:]
7 UIKit 0x188988b64 -[UIControl sendAction:to:forEvent:]
8 UIKit 0x188970870 -[UIControl _sendActionsForEvents:withEvent:]
9 UIKit 0x188988454 -[UIControl touchesEnded:withEvent:]
10 UIKit 0x188988084 -[UIWindow _sendTouchesForEvent:]
11 UIKit 0x188980c20 -[UIWindow sendEvent:]
12 UIKit 0x18895104c -[UIApplication sendEvent:]
13 UIKit 0x18894f628 _UIApplicationHandleEventQueue
14 CoreFoundation 0x1837a909c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
15 CoreFoundation 0x1837a8b30 __CFRunLoopDoSources0
16 CoreFoundation 0x1837a6830 __CFRunLoopRun
17 CoreFoundation 0x1836d0c50 CFRunLoopRunSpecific
18 GraphicsServices 0x184fb8088 GSEventRunModal
19 UIKit 0x1889ba088 UIApplicationMain
20 Hello English 0x10029b2cc main (AppDelegate.swift:26)
21 libdispatch.dylib 0x18326e8b8 (Missing)
We've tried so far:
- Moved
GIDSignIn.sharedInstance().uiDelegate = selffromviewDidLoad()toviewDidAppear(animated: Bool) - We've also implemented all three methods of
GIDSignInUIDelegatewith no success. - Our view controller was not direct child of
UIViewController, but now it is and the crashes are still counting. - We were using simple
UIButton, so we thought that might be cause (though illogical, but we tried) of crash; we changed it toGIDSignInButton, but no reduction in crash-count.
For now we have removed the Google Login process, but it is not the solution. And strange thing is that we are unable to reproduce this crash at our end; we are only getting these crashes on Crashlytics.
So Anyone with any solution ?
回答1:
Finally after putting much effort found a solution. My environment is
Swift 3,
iOS 10.3.x,
Xcode 8.3.2
Posting here a complete solution
import UIKit
import GoogleSignIn
class ViewController: UIViewController, GIDSignInUIDelegate, GIDSignInDelegate {
@IBOutlet weak var signInButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// set delegates
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
let buttonFrame : CGRect = CGRect.init(x: 0, y: 0, width: 100, height: 50)
let gdSignInButton = GIDSignInButton.init(frame:buttonFrame)
gdSignInButton.center = view.center
view.addSubview(gdSignInButton)
// gdSignInButton.addTarget(self, action: #selector(self.didTapSignOut(sender: self.gdSignInButton)), for: UIControlEvents.touchUpInside)
}
@IBAction func didTapSignOut(sender: GIDSignInButton) {
GIDSignIn.sharedInstance().signOut()
}
func sign(inWillDispatch signIn: GIDSignIn!, error: Error!) {
guard error == nil else {
print("Error while trying to redirect : \(error)")
return
}
print("Successful Redirection")
}
//MARK: GIDSignIn Delegate
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!)
{
if (error == nil) {
// Perform any operations on signed in user here.
let userId = user.userID // For client-side use only!
print("User id is \(userId)")
let idToken = user.authentication.idToken // Safe to send to the server
print("Authentication idToken is \(idToken)")
let fullName = user.profile.name
print("User full name is \(fullName)")
let givenName = user.profile.givenName
print("User given profile name is \(givenName)")
let familyName = user.profile.familyName
print("User family name is \(familyName)")
let email = user.profile.email
print("User email address is \(email)")
// ...
} else {
print("ERROR ::\(error.localizedDescription)")
}
}
// Finished disconnecting |user| from the app successfully if |error| is |nil|.
public func sign(_ signIn: GIDSignIn!, didDisconnectWith user: GIDGoogleUser!, withError error: Error!)
{
}
}
and my AppDelegate look like this.
import UIKit
import Google
import GoogleSignIn
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Initialize sign-in
var configureError: NSError?
GGLContext.sharedInstance().configureWithError(&configureError)
assert(configureError == nil, "Error configuring Google services: \(configureError)")
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return GIDSignIn.sharedInstance().handle(url as URL!,
sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as? String, annotation: options[UIApplicationOpenURLOptionsKey.annotation])
}
}
回答2:
you need to write these lines in viewWillAppear method of UIViewController.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().delegate = self
}
回答3:
you need to implement Delegate of the Google and firebase properly
add like this
class ViewController: UIViewController, GIDSignInDelegate,GIDSignInUIDelegate
in the ViewDidLoad add these lines
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().signIn()
回答4:
Simply include the following
func sign(_ signIn: GIDSignIn!, dismiss viewController: UIViewController!) {
print("dismissing Google SignIn")
}
func sign(_ signIn: GIDSignIn!, present viewController: UIViewController!) {
print("presenting Google SignIn")
}
回答5:
The solution for me (iOS 10.0) was assigning my view controller as ui delegate in its viewDidAppear: method...
-(void)viewDidAppear{
[super viewDidAppear];
[GIDSignIn sharedInstance].uiDelegate = self;
}
回答6:
I also encountered this error and I observed it in two cases:
I created an @IBAction for the GoogleSignIn button and I included both of these lines in it:
@IBAction func googleSignInButton(_ sender: Any) { GIDSignIn.sharedInstance().signIn() GIDSignIn.sharedInstance().uiDelegate = self }
But the assignment of the uiDelegate needs to be in viewDidLoad and not in the @IBAction:
GIDSignIn.sharedInstance().uiDelegate = self
- In my other case the uiDelegate is a UIViewController so I didn't need to specify the additional methods. And like other posters here I was sure that I followed all of the directions yet was still seeing this error whenever I clicked on the Google SignIn button.
The root cause was that I created a new UIViewController class "DoLoginVC.swift" but I did not map this custom class to the UIViewController in the story board! An oversight. Everything started working after I added the custom class mapping.
If you are seeing this error then go to your storyboard and go to the ViewController and verify that you have specified your custom class like in the attached screenshot.
回答7:
you need add this lines into viewDidAppear it works for me well
override func viewDidAppear(_ animated: Bool) {
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
}
回答8:
It simply means that you have to implement following delegate methods:
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
You can set this in viewDidLoad().
Special Case
If you have Google Sign-In in multiple ViewControllers (e.g. in Register and Login screens), then you have to update delegates when screen is changed. To avoid conflicts, I would suggest to set it in viewDidAppear(). This will update delegate references with current ViewController, if you are switching screens back and forth.
回答9:
This error also be shown if you forgot to set your custom view Controller class to UIViewController Took on Storyboard.
To Do this -> goto your Storyboard -> select the View Controller -> goto Identity Explorer and set your class if blank
See the image below for reference
View Image
来源:https://stackoverflow.com/questions/39888816/swift-uidelegate-must-either-be-a-uiviewcontroller-or-implement-the-signinpre