3D Touch Quick Actions not working properly with SpriteKit - swift

I'm currently developing a game using Swift 3, SpriteKit, and Xcode 8 beta. I'm trying to implement static 3D Touch Quick Actions from the home screen through the info.plist. Currently, the actions appear fine from the home screen, but don't go to the right SKScene - goes to the initial scene or last opened scene (if app is still open) which means that the scene is not being changed. I've tried various ways of setting the scene inside the switch statement, but none seem to work properly for presenting an SKScene as the line window!.rootViewController?.present(gameViewController, animated: true, completion: nil) only works on UIViewController.
Various parts of this code are from various tutorials, but I'm fairly sure through my investigation that the broken part is the scene being presented (unless I'm wrong), because it shouldn't even load any scene if a part is broken.
Are there any ways I can present an SKScene from the AppDelegate or set the opening scene based of the switch statement?
AppDelegate
import UIKit
import SpriteKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
guard !handledShortcutItemPress(forLaunchOptions: launchOptions) else { return false } // ADD THIS LINE
return true
}
}
extension AppDelegate: ShortcutItem {
/// Perform action for shortcut item. This gets called when app is active
func application(_ application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
completionHandler(handledShortcutItemPress(forItem: shortcutItem))
}
}
extension AppDelegate: ShortcutItemDelegate {
func shortcutItem1Pressed() {
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(loadShopScene), userInfo: nil, repeats: false)
}
#objc private func loadShopScene() {
let scene = ShopScene(size: CGSize(width: 768, height: 1024))
loadScene(scene: scene, view: window?.rootViewController?.view)
}
func shortcutItem2Pressed() {
Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(loadGameScene), userInfo: nil, repeats: false)
}
#objc private func loadGameScene() {
let scene = GameScene(size: CGSize(width: 768, height: 1024))
loadScene(scene: scene, view: window?.rootViewController?.view)
}
func shortcutItem3Pressed() {
// do something else
}
func shortcutItem4Pressed() {
// do something else
}
func loadScene(scene: SKScene?, view: UIView?, scaleMode: SKSceneScaleMode = .aspectFill) {
guard let scene = scene else { return }
guard let skView = view as? SKView else { return }
skView.ignoresSiblingOrder = true
#if os(iOS)
skView.isMultipleTouchEnabled = true
#endif
scene.scaleMode = scaleMode
skView.presentScene(scene)
}
}
3DTouchQuickActions.swift
import Foundation
import UIKit
/// Shortcut item delegate
protocol ShortcutItemDelegate: class {
func shortcutItem1Pressed()
func shortcutItem2Pressed()
func shortcutItem3Pressed()
func shortcutItem4Pressed()
}
/// Shortcut item identifier
enum ShortcutItemIdentifier: String {
case first // I use swift 3 small letters so you have to change your spelling in the info.plist
case second
case third
case fourth
init?(fullType: String) {
guard let last = fullType.components(separatedBy: ".").last else { return nil }
self.init(rawValue: last)
}
public var type: String {
return Bundle.main.bundleIdentifier! + ".\(self.rawValue)"
}
}
/// Shortcut item protocol
protocol ShortcutItem { }
extension ShortcutItem {
// MARK: - Properties
/// Delegate
private weak var delegate: ShortcutItemDelegate? {
return self as? ShortcutItemDelegate
}
// MARK: - Methods
/// Handled shortcut item press first app launch (needed to avoid double presses on first launch)
/// Call this in app Delegate did launch with options and exit early (return false) in app delegate if this method returns true
///
/// - parameter forLaunchOptions: The [NSObject: AnyObject]? launch options to pass in
/// - returns: Bool
func handledShortcutItemPress(forLaunchOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
guard let launchOptions = launchOptions, let shortcutItem = launchOptions[UIApplicationLaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem else { return false }
handledShortcutItemPress(forItem: shortcutItem)
return true
}
/// Handle shortcut item press
/// Call this in the completion handler in AppDelegate perform action for shortcut item method
///
/// - parameter forItem: The UIApplicationShortcutItem the press was handled for.
/// - returns: Bool
func handledShortcutItemPress(forItem shortcutItem: UIApplicationShortcutItem) -> Bool {
guard let _ = ShortcutItemIdentifier(fullType: shortcutItem.type) else { return false }
guard let shortcutType = shortcutItem.type as String? else { return false }
switch shortcutType {
case ShortcutItemIdentifier.first.type:
delegate?.shortcutItem1Pressed()
case ShortcutItemIdentifier.second.type:
delegate?.shortcutItem2Pressed()
case ShortcutItemIdentifier.third.type:
delegate?.shortcutItem3Pressed()
case ShortcutItemIdentifier.fourth.type:
delegate?.shortcutItem4Pressed()
default:
return false
}
return true
}
}

Your code is not working because in your app delegate you create a new instance of GameViewController instead of referencing the current one
let gameViewController = GameViewController() // creates new instance
I am doing exactly what you are trying to do with 3d touch quick actions in 2 of my games. I directly load the scene from the appDelegate, dont try to change the gameViewController scene for this.
I use a reusable helper for this. Assuming you set up everything correctly in your info.plist. (I use small letters in the enum so end your items with .first, .second etc in the info.plist), remove all your app delegate code you had previously for the 3d touch quick actions.
Than create a new .swift file in your project and add this code
This is swift 3 code.
import UIKit
/// Shortcut item delegate
protocol ShortcutItemDelegate: class {
func shortcutItemDidPress(_ identifier: ShortcutItemIdentifier)
}
/// Shortcut item identifier
enum ShortcutItemIdentifier: String {
case first // I use swift 3 small letters so you have to change your spelling in the info.plist
case second
case third
case fourth
private init?(fullType: String) {
guard let last = fullType.componentsSeparatedByString(".").last else { return nil }
self.init(rawValue: last)
}
public var type: String {
return (Bundle.main.bundleIdentifier ?? "NoBundleIDFound") + ".\(rawValue)"
}
}
/// Shortcut item protocol
protocol ShortcutItem { }
extension ShortcutItem {
// MARK: - Properties
/// Delegate
private weak var delegate: ShortcutItemDelegate? {
return self as? ShortcutItemDelegate
}
// MARK: - Methods
func didPressShortcutItem(withOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
guard let shortcutItem = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem else { return false }
didPressShortcutItem(shortcutItem)
return true
}
/// Handle item press
#discardableResult
func didPressShortcutItem(_ shortcutItem: UIApplicationShortcutItem) -> Bool {
guard let _ = ShortcutItemIdentifier(fullType: shortcutItem.type) else { return false }
switch shortcutItem.type {
case ShortcutItemIdentifier.first.type:
delegate?.shortcutItemDidPress(.first)
case ShortcutItemIdentifier.second.type:
delegate?.shortcutItemDidPress(.second)
case ShortcutItemIdentifier.third.type:
delegate?.shortcutItemDidPress(.third)
case ShortcutItemIdentifier.fourth.type:
delegate?.shortcutItemDidPress(.fourth)
default:
return false
}
return true
}
}
Than in your app delegate create an extension with this method (you missed this in your code)
extension AppDelegate: ShortcutItem {
/// Perform action for shortcut item. This gets called when app is active
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
completionHandler(didPressShortcutItem(shortcutItem))
}
Than you need to adjust the didFinishLaunchingWithOptions method in your AppDelegate to look like this
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
...
return !didPressShortcutItem(withOptions: launchOptions)
}
And than finally create another extension confirming to the ShortcutItem delegate
extension AppDelegate: ShortcutItemDelegate {
func shortcutItemDidPress(_ identifier: ShortcutItemIdentifier) {
switch identifier {
case .first:
let scene = GameScene(size: CGSize(width: 1024, height: 768))
loadScene(scene, view: window?.rootViewController?.view)
case .second:
//
case .third:
//
case .fourth:
//
}
}
func loadScene(scene: SKScene?, view: UIView?, scaleMode: SKSceneScaleMode = .aspectFill) {
guard let scene = scene else { return }
guard let skView = view as? SKView else { return }
skView.ignoresSiblingOrder = true
#if os(iOS)
skView.isMultipleTouchEnabled = true
#endif
scene.scaleMode = scaleMode
skView.presentScene(scene)
}
}
The load scene method I normally have in another helper which is why I pass the view into the func.
Hope this helps.

Related

Check if app is launched/openend by a SharePlay session

When a user is in a Facetime call and accepted the SharePlay request, I want to push a specific ViewController. So I thought it would be same as to get notification when the user tap "accept".
import UIKit
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// to perform action when notification is tapped
UNUserNotificationCenter.current().delegate = self
registerForPushNotifications()
return true
}
func registerForPushNotifications() {
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) {
[weak self] granted, error in
print("Permission granted: \(granted)")
guard granted else { return }
self?.getNotificationSettings()
}
}
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
extension AppDelegate : UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let application = UIApplication.shared
if(application.applicationState == .active){
print("user tapped the notification bar when the app is in foreground")
}
if(application.applicationState == .inactive)
{
print("user tapped the notification bar when the app is in background")
}
guard let rootViewController = (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController else {
return
}
// Do some work
completionHandler()
}
}
But this was not the case. Nothing were printed out in the logs. Is there another way to know when the app (in the background or terminated) is launched or openend by accepting a SharePlay request ?
So if one accepts the SharePlay request, one will join a session. After that you can switch to a specific ViewController. You can call this function for example in
func sceneWillEnterForeground(_ scene: UIScene) {
if #available(iOS 15, *) {
let _ = CoordinationManager.shared
}
}
and the CoordinationManager could look like this
class CoordinationManager {
static let shared = CoordinationManager()
private var subscriptions = Set<AnyCancellable>()
// Published values that the player, and other UI items, observe
#Published var groupSession: GroupSession<MovieWatchingActivity>?
#Published var enquedMovie: Movies?
private init() {
Task {
// await new sessions to watch movies together
for await groupSession in MovieWatchingActivity.sessions() {
//set the app's active group session
self.groupSession = groupSession
//remove previous subscriptions
subscriptions.removeAll()
//observe changes to the session state
groupSession.$state.sink { [weak self] state in
if case .invalidated = state {
// set the groupSession to nil to publish
// the invalidated session state
self?.groupSession = nil
self?.subscriptions.removeAll()
}
}.store(in: &subscriptions)
// join the session to participate in playback coordination
groupSession.join()
// navigate user to correct view controller to show videos
guard let windowScene = await UIApplication.shared.connectedScenes.first as? UIWindowScene,
let sceneDelegate = await windowScene.delegate as? SceneDelegate,
let navigationController = await sceneDelegate.window?.rootViewController as? UINavigationController else {
return
}
DispatchQueue.main.async {
navigationController.pushViewController(GroupMovieController(), animated: true)
}
// observe when the local user or a remote participant starts an activity
groupSession.$activity.sink { [weak self] activity in
// set the movie to enqueue it in the player
self?.enquedMovie = activity.movie
}.store(in: &subscriptions)
}
}
}
...
}

change UITabBar.appearance().tintColor at runtime

I want to allow the user to change the whole App-Color.
But whenever I select a color it is only changed after the App will be restarted.
Is there any option to change the color at runtime?
I've set up a Button with a function like this to create a ColorPicker
#IBAction func colorChange(_ sender: UIButton) {
// Initializing Color Picker
let picker = UIColorPickerViewController()
// Setting the Initial Color of the Picker
picker.selectedColor = UIColor(named: "MyGreen")!
// Setting Delegate
picker.delegate = self
// Presenting the Color Picker
self.present(picker, animated: true, completion: nil)
}
And when the user picked a color, I make the changes in this function
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
defaults.set(viewController.selectedColor, forKey: "myColor")
UITabBar.appearance().tintColor = viewController.selectedColor
}
To change the color at startup I've implemented this in AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
var myColor = defaults.color(forKey: "myColor")
if myColor == nil {
myColor = UIColor(named: "MyGreen")
}
UITabBar.appearance().tintColor = myColor
return true
}
Someone gave me an Article to read how it can be done.
https://zamzam.io/protocol-oriented-themes-for-ios-apps/
I've ended up doing it like this:
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
defaults.set(viewController.selectedColor, forKey: "myColor")
apply(for: UIApplication.shared, color: viewController.selectedColor)
}
by adding this function
func apply(for application: UIApplication, color: UIColor) {
UITabBar.appearance().tintColor = color
application.windows.reload()
}
and these extensions
public extension UIWindow {
/// Unload all views and add back.
/// Useful for applying `UIAppearance` changes to existing views.
func reload() {
subviews.forEach { view in
view.removeFromSuperview()
addSubview(view)
}
}
}
public extension Array where Element == UIWindow {
/// Unload all views for each `UIWindow` and add back.
/// Useful for applying `UIAppearance` changes to existing views.
func reload() {
forEach { $0.reload() }
}
}
If it's good or not I don't know - but it worked for me.
have you try using UITabBar.appearance().barTintColor ?

Unsupported object CPInformationTemplate

The error I am getting is the following.
Thread 1: "Unsupported object <CPInformationTemplate: 0x6000012de010> <identifier: 3444D3F1-ECFF-4953-B543-459286E11371, userInfo: (null), tabTitle: (null), tabImage: (null), showsTabBadge: 0> passed to setRootTemplate:animated:completion:. Allowed classes: {(\n CPTabBarTemplate,\n CPListTemplate,\n CPGridTemplate,\n CPAlertTemplate,\n CPVoiceControlTemplate,\n CPNowPlayingTemplate\n)}"
All I am trying to do so, far is show a simple basic text. - I am following https://adapptor.com.au/blog/enhance-existing-apps-with-carplay
Any suggestions would be helpful.
import Foundation
import CarPlay
class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
func templateApplicationScene(_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController) {
if #available(iOS 14.0, *) {
let screen = CPInformationTemplate(title: "Root", layout: .leading, items: [CPInformationItem(title: "Hello", detail: "CarPlay")], actions: [])
interfaceController.setRootTemplate(screen, animated: true, completion: { _,_ in
// Do nothing
})
} else {
// Fallback on earlier versions
}
}
}
My AppDelegate is:
//
// AppDelegate.swift
// vscroll
//
// Created by Russell Harrower on 17/8/20.
// Copyright © 2020 Russell Harrower. All rights reserved.
//
import UIKit
import Flurry_iOS_SDK
import AVKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
MusicPlayer.shared.startBackgroundMusic(url:"https://api.drn1.com.au:9000/station/DRN1", type: "radio")
setupNotifications()
Flurry.startSession("GJV665GWWF25GPCD25W8", with: FlurrySessionBuilder
.init()
.withCrashReporting(true)
.withLogLevel(FlurryLogLevelAll))
return true
}
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
if(connectingSceneSession.role == UISceneSession.Role.carTemplateApplication) {
let scene = UISceneConfiguration(name: "CarPlay", sessionRole: connectingSceneSession.role)
// At the time of writing this blog post there seems to be a bug with the info.plist file where
// the delegateClass isn't set correctly. So we manually set it here.
if #available(iOS 14.0, *) {
scene.delegateClass = CarPlaySceneDelegate.self
} else {
// Fallback on earlier versions
}
return scene
} else {
let scene = UISceneConfiguration(name: "Phone", sessionRole: connectingSceneSession.role)
return scene
}
}
// MARK: UISceneSession Lifecycle
/* func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}*/
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
//CUSTOM CODE
func setupNotifications() {
// Get the default notification center instance.
let nc = NotificationCenter.default
nc.addObserver(self,
selector: #selector(handleInterruption),
name: AVAudioSession.interruptionNotification,
object: nil)
}
#objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let interruptionTypeRawValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let interruptionType = AVAudioSession.InterruptionType(rawValue: interruptionTypeRawValue) else {
return
}
switch interruptionType {
case .began:
print("interruption began")
case .ended:
MusicPlayer.shared.player?.play()
print("interruption ended")
default:
print("UNKNOWN")
}
}
}
Which entitlements you have defined in your project? The templates availability depends on defined app entitlements.
Usage of the unsupported template (CPInformationTemplate) cause your error.
For example from documentation:
You can’t use CPInformationTemplate in apps with the audio
entitlement.

About the callback of SKStoreReviewController.requestReview()

If the review popup initiated from a view controller shows up, there isn't a way to switch the window focus back to the view controller when the popup is dismissed due to lack of callback function of SKStoreReviewController.requestReview().
I would like to make a call to becomeFirstResponder() when the review popup is dismissed. Any idea?
Is there a way to extend the SKStoreReviewController and add a callback somehow?
Warning this will probably break at some point.
Step 1: add this code to your didFinishLaunchingWithOptions
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let windowClass: AnyClass = UIWindow.self
let originalSelector: Selector = #selector(setter: UIWindow.windowLevel)
let swizzledSelector: Selector = #selector(UIWindow.setWindowLevel_startMonitor(_:))
let originalMethod = class_getInstanceMethod(windowClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(windowClass, swizzledSelector)
let didAddMethod = class_addMethod(windowClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
if didAddMethod {
class_replaceMethod(windowClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
return true
}
Step 2: add this class
class MonitorObject: NSObject {
weak var owner: UIWindow?
init(_ owner: UIWindow?) {
super.init()
self.owner = owner
NotificationCenter.default.post(name: UIWindow.didBecomeVisibleNotification, object: self)
}
deinit {
NotificationCenter.default.post(name: UIWindow.didBecomeHiddenNotification, object: self)
}
}
Step 3: Add this UIWindow extension
private var monitorObjectKey = "monitorKey"
private var partialDescForStoreReviewWindow = "SKStore"
extension UIWindow {
// MARK: - Method Swizzling
#objc func setWindowLevel_startMonitor(_ level: Int) {
setWindowLevel_startMonitor(level)
if description.contains(partialDescForStoreReviewWindow) {
let monObj = MonitorObject(self)
objc_setAssociatedObject(self, &monitorObjectKey, monObj, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
Step 4: add this to ViewDidLoad of your controller where you want this
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeHiddenNotification(_:)), name: UIWindow.didBecomeHiddenNotification, object: nil)
}
Step 5: add the callback for the notification and check that the associated object is a match
#objc func windowDidBecomeHiddenNotification(_ notification: Notification?) {
if notification?.object is MonitorObject {
print("hello")
}
}
Now when a review dialog is closed the notification is triggered and 'print("hello") will be called.
Sometimes iOS app is losing the responder chain, like in the above example of showing StoreKit prompt. What we can do is to detect such events in UIApplication.sendAction and reactivate the first responder chain via becomeFirstResponder. UIKit will reestablish the first responder chain and we can resend the same event.
class MyApplication: UIApplication {
func reactivateResponderChainWhenFirstResponderEventWasNotHandled() {
becomeFirstResponder()
}
override func sendAction(_ action: Selector, to target: Any?, from sender: Any?, for event: UIEvent?) -> Bool {
let wasHandled = super.sendAction(action, to: target, from: sender, for: event)
if wasHandled == false, target == nil {
reactivateResponderChainWhenFirstResponderEventWasNotHandled()
return super.sendAction(action, to: target, from: sender, for: event)
}
return wasHandled
}
}
This works for me on iOS 13 and does not require any private API access.

iOS firebase: FIRAuthUIDelegate.authUI not being called

I am trying to launch google login from AppDelegate.swift and then launch my app's main screen upon login success.
I am able to
show the google login button as shown above
the user is sent to google to sign in
the user is sent back to original (step 1)
After step 3. I'd like to send the user to my app's main page.
My code is below. The problem I'm having is that authUI is not being called.
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, FIRAuthUIDelegate {
var window: UIWindow?
var authUI: FIRAuthUI?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
authUI = FIRAuthUI.defaultAuthUI()
authUI?.delegate = self
let providers: [FIRAuthProviderUI] = [FIRGoogleAuthUI()]
authUI?.providers = providers
// show google login button
let authViewController = authUI?.authViewController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = authViewController
self.window?.makeKeyAndVisible()
return true
}
func application(application: UIApplication, openURL url: NSURL, options: [String: AnyObject]) -> Bool {
return GIDSignIn.sharedInstance().handleURL(url, sourceApplication: options[UIApplicationOpenURLOptionsSourceApplicationKey] as? String, annotation: options[UIApplicationOpenURLOptionsAnnotationKey])
}
func authUI(authUI: FIRAuthUI, didSignInWithUser user: FIRUser?, error: NSError?) {
// launch main view controller
}
}
EDIT: This appears to be a duplicate of another question. The other question's title is quite general and only gets to the details a few lines deep. In any case, I believe Chris's answer is more thorough than the one there. I think both the question and answers here are clearer, more pointed and more thorough so it would be a mistake to just direct people here to go there as would happen if this was marked as a duplicate.
I think your problem lies here, in the - (void)signInWithProviderUI:(id<FIRAuthProviderUI>)providerUI method.
The delegate method is called in the dismissViewControllerAnimated:completion: completion block.
[self.navigationController dismissViewControllerAnimated:YES completion:^{
[self.authUI invokeResultCallbackWithUser:user error:error];
}];
As you can see from the Apple docs, this method is expected to be called on a modally presented viewController. You are displaying it as a root view controller. Try displaying it with a modal from a UIViewController, and things should work out. To debug this try and set a breakpoint at line 193 to see that it won't get hit. I would be very surprised if this doesn't work when you display the authController modally.
To come up with a possible solution to your problem (I am assuming you want to ensure a user is signed in before using your app). The below is a simplification of what I am using in an app currently.
EDIT: Updated for the new 1.0.0 FirebaseUI syntax.
class MainTabController: UITabBarController, FIRAuthUIDelegate {
let authUI: FUIAuth? = FUIAuth.defaultAuthUI()
override func viewDidLoad() {
super.viewDidLoad()
var authProviders = [FUIFacebookAuth(), FUIGoogleAuth()]
authUI.delegate = self
authUI.providers = authProviders
//I use this method for signing out when I'm developing
//try! FIRAuth.auth()?.signOut()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isUserSignedIn() {
showLoginView()
}
}
private func isUserSignedIn() -> Bool {
guard FIRAuth.auth()?.currentUser != nil else { return false }
return true
}
private func showLoginView() {
if let authVC = FUIAuth.defaultAuthUI()?.authViewController() {
present(authVC, animated: true, completion: nil)
}
}
func authUI(_ authUI: FUIAuth, didSignInWith user: FIRUser?, error: Error?) {
guard let user = user else {
print(error)
return
}
...
}
It must be a problem of reference.
class AppDelegate: UIResponder, UIApplicationDelegate, FIRAuthUIDelegate {
var window: UIWindow?
let authUI = FIRAuthUI.defaultAuthUI()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
FIRApp.configure()
authUI.delegate = self
let providers: [FIRAuthProviderUI] = [FIRGoogleAuthUI()]
authUI.providers = providers
// show google login button
let authViewController = authUI.authViewController()
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window?.rootViewController = authViewController
self.window?.makeKeyAndVisible()
return true
}
}
Try this. AppDelegate will hold the reference of authUI and its delegate.