3

也就是说,Apple规定的惯用方法是什么?对于任何建议,请解释应该如何做和/或提供官方指南的链接。这应该是一个足够常见的场景,但我只能找到解决方法。

从另一端接近这一点,我知道UserDefaults(suiteName:)并且Keychain services可以从包含的应用程序中使用它来与扩展程序共享有关经过身份验证的用户的信息,但是如果用户安装了应用程序并直接尝试使用其扩展程序共享内容而没有曾经登录(或注册)?

  1. 要求用户登录包含的应用程序?(在自定义视图中?扩展默认是模态的。

  2. 在扩展中重新实现身份验证?(或者通过自定义框架共享?这可能吗?)

  3. 切换到包含应用程序然后返回?除了 Today 扩展外,这似乎不受支持,但文档中描述的机制已用于解决方法(SO 线程:123)。


使用 Firebase在此答案中的第 2 项的(丑陋)示例实现。

4

2 回答 2

1

我找不到任何官方指南,但下面的解决方案确实有效,并且也被 App Store 接受。可能底线正是:(1)它不应该崩溃,(2)应该能够通过审查过程。

[FirebaseUI 身份验证[( https://github.com/firebase/FirebaseUI-iOS ) 的解决方案:

动画

相关代码部分:

import UIKit
import Social
import Firebase
import FirebaseAuthUI

class ShareViewController: SLComposeServiceViewController {

    var authUI: FUIAuth?

    /* Using shared container to communicate between extension
       and containing app. Keychain would probably work too.
    */
    let defaults = UserDefaults.init(suiteName: "your-app-group")!

    override func presentationAnimationDidFinish() {

        /* https://stackoverflow.com/questions/37910766/
        */
        if FirebaseApp.app() == nil {
            FirebaseApp.configure()
        }

        self.authUI = FUIAuth.defaultAuthUI()
        self.authUI?.delegate = self

        if self.defaults.bool(forKey: "userLoggedIn") == false {
            let fuiSignin     = 
                FUIPasswordSignInViewController(
                    authUI: FUIAuth.defaultAuthUI()!,
                    email: nil)
            let navController = 
                UINavigationController(rootViewController: fuiSignin)

            self.present(navController, animated: true)
        }
    }

/* FirebaseAuthUI delegate to handle sign-in
*/
extension ShareViewController: FUIAuthDelegate {
    func authUI(_ authUI: FUIAuth, didSignInWith user: User?, error: Error?) {
        if error != nil {
            fatalError()
        }
        if user != nil {
            self.defaults.set(true, forKey: "userLoggedIn")
        }
    }
}

成功登录也会通过共享容器被记住(即,打开包含的应用程序不会要求登录)。

github项目中的相关commit: https ://github.com/society-for-the-blind/Access-News-Reader-iOS/commit/e752b1c554f79ef027818db35c11fceb1ae817e0


问题

我第一次运行它,表格出现了,但不接受任何输入。Product > Clean并,重新启动XcodeProduct > Clean Build Folder ...和模拟器,它工作。它也适用于旧 iPad (iOS 10.3.3)。

于 2018-04-15T22:44:03.510 回答
0

对我有用的步骤:

  1. 将 Firebase 添加到应用扩展程序

  2. 通过 App Group 设置共享容器(注册的 URL 方案等,此处不详述

  3. 使用UserDefaults同步登录信息(或任何其他数据)


第 1 步:将 Firebase 添加到应用扩展程序

Firebase将应用扩展程序视为一个单独的应用程序,因此需要将其添加到主 Firebase 项目。

步骤

  1. 将新的 iOS 应用添加到您现有的 Firebase 项目
  2. 将新的GoogleService-Info.plist拖到 Xcode 中的扩展中
  3. 将新目标添加到您的Podfile
  4. 安装依赖项 ( pod install)
  5. 在您的扩展程序中配置 Firebase 应用程序对象

请参阅此 SO 答案中的详细步骤。

步骤 2. 通过 App Group 设置共享容器

Firebase 项目中的应用相互隔离。例如,从应用程序扩展登录到项目的用户也必须登录包含的应用程序。检查Auth.auth().currentUser只会产生特定上下文的结果。

Apple 官方指南(App Extension Programming Guide: Sharing Data with Your Containing App)展示了如何做到这一点,并用插图给出了很好的解释。

脚步:

  1. 创建新的应用组并配置 iOS 应用以使用它

  2. 在 Xcode 中为两个目标(即扩展和包含应用程序) 启用“应用程序组”功能

比如我们的主app是“ Access News ”:

在此处输入图像描述

共享扩展名为“ Access-News-Uploader ”:

在此处输入图像描述

步骤 3. 使用UserDefaults同步登录信息(或任何其他数据)

确保将用户 ID 保存为默认值!Firebase 中的用户在项目级别进行处理,这使得用户可以通过项目中的任何应用程序登录(例如,一个用于包含应用程序,一个用于扩展程序),但这些状态仅用于实际保存正在使用的应用程序。例如,如果用户登录共享扩展并打开包含应用程序,如果包含应用程序Auth.auth().currentUser.uid在任何时候调用它可能会产生nil.

要使用单例和应用程序组共享数据UserDefaults,请按照您需要它们的任何课程中的步骤操作:

  1. let defaults = UserDefaults.init(suiteName: "group.your-app-group-id")!

  2. 使用其中一种功能设置默认值UserDefaults.set(...)

  3. 使用特定的getter查询值UserDefaults


例子

当我们的项目启动时,包含应用程序的 ( Access News ) 根视图控制器 ( NVC ) 检查"user-logged-in"布尔UserDefaults值(而不是 Auth.auth().currentUser因为它只会显示包含应用程序的 Firebase 应用程序状态)。

(可以只保存用户 ID,并检查它是否存在,而不是同时使用 aBoolStringkey。)

//  NVC.swift

import UIKit
import FirebaseAuth

class NVC: UINavigationController {

    /* Step 1 */
    let defaults = UserDefaults.init(suiteName: "group.org.societyfortheblind.access-news-reader-ag")!
    /**********/

    override func viewDidLoad() {
        super.viewDidLoad()

        /* Step 3 */
        if self.defaults.bool(forKey: "user-logged-in") == false {
        /**********/
            let storyboard = UIStoryboard(name: "Main", bundle: .main)
            let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
            loginViewController.navigationItem.hidesBackButton = true
            self.pushViewController(loginViewController, animated: false)
        }
    }

如果没有用户登录,LoginViewController则加载true成功登录时将密钥设置为。(在 中设置为false注销SessionStartViewController。)

//  ViewController.swift

import UIKit
import FirebaseAuth

class LoginViewController: UIViewController {

    /* Step 1 */
    let defaults = UserDefaults.init(suiteName: "group.org.societyfortheblind.access-news-reader-ag")!
    /**********/

    // ...

    @IBAction func tapSignInButton(_ sender: Any) {

       // ...
       Auth.auth().signIn(withEmail: username.text!, password: password.text!) {
            (user, error) in
            if error != nil {
                // ...
            } else {
                /* Step 2 */
                self.defaults.set(true, forKey: "user-logged-in")

                // !!!
                self.defaults.set(Auth.auth().currentUser.uid, forKey: "user-id")
                /**********/
                self.navigationController?.popViewController(animated: false)
            }
        }
    }
}

在应用程序扩展中,在主导航控制器中检查了密钥,如果没有用户登录它,它将LoginViewController从包含的应用程序加载。

//  UploaderNavigationViewController.swift

import UIKit
import Firebase

class UploaderNavigationViewController: UINavigationController {

    /* Step 1 */
    let defaults = UserDefaults.init(suiteName: "group.org.societyfortheblind.access-news-reader-ag")!
    /**********/

    override func viewDidLoad() {
        super.viewDidLoad()

        if FirebaseApp.app() == nil {
            FirebaseApp.configure()
        }

        /* Step 3 */
        if self.defaults.bool(forKey: "user-logged-in") == false {
        /**********/
            let storyboard = UIStoryboard(name: "Main", bundle: .main)
            let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
            loginViewController.navigationItem.hidesBackButton = true
            self.pushViewController(loginViewController, animated: false)
        }
    }

这是我们在提交时设置的项目

于 2018-11-28T22:33:34.223 回答