OAuth2: Returning to App After Logging in - ios

I'm using a cocoapod named (p2/OAuth2) in order to log in an account with GitHub. I'm just playing because I want to know how OAuth2 works.
The following is what I have so far in my view controller (UIViewController).
import UIKit
import p2_OAuth2
class ViewController: UIViewController {
// MARK: - Variables
let oauth2 = OAuth2CodeGrant(settings: [
"client_id": "xxxxxx",
"client_secret": "xxxxxxxx",
"authorize_uri": "https://github.com/login/oauth/authorize",
"token_uri": "https://github.com/login/oauth/access_token", // code grant only
"redirect_uris": ["myapp://oauth/callback"], // register your own "myapp" scheme in Info.plist
"scope": "user repo:status",
"secret_in_body": true, // Github needs this
"keychain": false, // if you DON'T want keychain integration
] as OAuth2JSON)
// MARK: - IBOutlet
// MARK: - IBAction
#IBAction func loginTapped(_ sender: UIBarButtonItem) {
oauth2.logger = OAuth2DebugLogger(.trace)
oauth2.authorize() { authParameters, error in
if let params = authParameters {
print("Authorized! Access token is \(self.oauth2.accessToken ?? "")")
print("Authorized! Additional parameters: \(params)")
}
else {
print("Authorization was cancelled or went wrong: \(String(describing: error))")
}
}
}
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
}
What I would like to know is how to get back to the view controller after I successfully log in my account with GitHub. So far, I will be redirected to the URL that I have registered at GitHub's developer website. I suppose I need to register a callback URL scheme under Info. This whole thing is totally new. So I'm not sure how to do it. Thanks.
UPDATE
I have added a set of URL types through info.plist. A URL scheme is set to myapp. And the following is what I have in AppDelegate.
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
if url.scheme == "myapp" && url.host == "oauth" {
//oauth2.handleRedirectURL(url)
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "HomeView") as! UINavigationController
let viewController = controller.topViewController as! ViewController
window?.rootViewController = viewController
}
window?.makeKeyAndVisible()
return true
}
But the app never calls the above.

I have learnt something new today.
At GitHub's developer website, I have set the call back URL as myapp://oauth/callback. (See below.)
I have created a set of URL schemes through Info.plist. An identifier is made with my reverse domain + a unique name. A URL Schemes item is myapp. (See below.)
Finally, I have added the following line of code to AppDelegate.
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
if url.scheme == "myapp" && url.host == "oauth" {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "HomeView") as! UINavigationController
let viewController = controller.topViewController as! ViewController
window?.rootViewController = viewController
}
window?.makeKeyAndVisible()
return true
}
If I log in, I get prompted as follows.
When I tap Open, I'm redirected to my view controller.

Related

Problems starting ViewController from a widget click

I have a widget and want to open a particular ViewController when clicking on it. I've read all the documentation and questions on SO regarding the topic, and can't figure out why it isn't working. When clicking the widget, it always opens the default ViewController.
Here's the code for the WidgetView.
struct WidgetAdapter : View {
let entry: TimeLine.Entry
#Environment(\.widgetFamily) var family
#ViewBuilder
var body: some View {
switch family {
case .systemSmall:
SmallView(...).widgetURL(URL(string: "fcv://open_activity"))
case .systemMedium:
MediumView(...).widgetURL(URL(string: "fcv://open_activity"))
default:
LargeView(...).widgetURL(URL(string: "fcv://open_activity"))
}
}
}
Here the AppDelegate method for managing URLs.
func application(_ application: UIApplication, open
url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool{
if url.scheme == "fcv"{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "WidgetActivity") as! WidgetActivityController
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
}
return true
}
I also tried implementing the respective method for the SceneDelegate, I added the url scheme to the URL Types in project info, I added the LSApplicationQueriesSchemes item to the info.plist, used Link instead of .widgetURL... And it didn't work even once. I also think that the method in the AppDelegate is not being called, however, I checked for the cases were that can happen and they don't come to case.
Any help would be appreciated.
I solved the same problem by finding current rootViewController and pushing the view I need:
let rootViewController = window!.rootViewController as! UINavigationController
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)let profileViewController = mainStoryboard.instantiateViewController(withIdentifier: "MainVC") as! CombinedMainVC
rootViewController.pushViewController(profileViewController, animated: false)
It is always UINavigationController in my case, so I push a new VC, in your case you could present it.
I use the same method in AppDelegate, so there could be a problem with your if statement. Where do you set a scheme for a widget URL? Maybe you could just check URL string:
if url.absoluteString.prefix(3) == "fcv" { }

How can I switch the Bool animated in Swift (in the override ViewWillAppear methode)

I want to present one Storyboard after loading the App if the User is logged in and an other if the user have to log In.
I did it by overwriting the ViewWillAppear method, which now contains the code that is routed to the other storyboard when the user is logged in.
override func viewWillAppear(_ animated: Bool) {
let userdefaults = UserDefaults.standard
if userdefaults.string(forKey: "autoemail") != nil {
let email = userdefaults.string(forKey: "autoemail")
let password = userdefaults.string(forKey: "autopass")
Auth.auth().signIn(withEmail: email!, password: password!) { (user, error) in
self.performSegue(withIdentifier: "SignInSeguettt", sender: nil)
}
}
}override func viewWillAppear(_ animated: Bool) {
let userdefaults = UserDefaults.standard
if userdefaults.string(forKey: "autoemail") != nil {
let email = userdefaults.string(forKey: "autoemail")
let password = userdefaults.string(forKey: "autopass")
Auth.auth().signIn(withEmail: email!, password: password!) { (user, error) in
self.performSegue(withIdentifier: "SignInSeguettt", sender: nil)
}
}
}
But now the screen that would otherwise appear for a second still comes. How can I avoid that? I thought maybe by setting animated to false. I don't know how I can do this, because if I do it like that i get this Error:
Method does not override any method from its superclass
Are there other ways to get around this brief appearance of the wrong controller?
I don't think loading a view controller then making a decision whether to display it or load another view controller is the best approach to what you are trying to do.
The way I determine whether to allow the user to proceed into the app, or if they are not logged in display the login screen is done in the SceneDelegate.swift class (or could be done in the AppDelegate class if your app doesnt have a SceneDelegate)
It's done like this:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
override init() {
FirebaseApp.configure() // Configure Firebase
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
var vc: UIViewController // Create a viewcontroller ready to return as the viewcontroller we are going to show to the user
if (Auth.auth().currentUser) != nil { // The users' auth token is still good, they are logged in
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil) // get a storyboard reference
// Make our holding view controller an instance of the Main storyboard's initial view controller
vc = mainStoryboard.instantiateInitialViewController()!
} else { // the user is not logged in
// Has the user seen the onboarding screen before?
if Session.HasShownOnboarding { // Yes has seen the onboardinng screens
let mainStoryboard = UIStoryboard(name: "Account", bundle: nil) // get reference to the Account storyboard
// Make our holding view controller an instance of the Account storyboard's Log In view controller
vc = mainStoryboard.instantiateViewController(identifier: "LoginViewController")
} else { // No the user has not been shown the onboarding screens yet
let launchStoryboard = UIStoryboard(name: "Onboarding", bundle: nil) // get reference to the Onboarding storyboard
// Make our holding view controller an instance of the Onboading storyboard's Onboarding view controller
vc = launchStoryboard.instantiateViewController(identifier: "OnboardingViewController")
Session.HasShownOnboarding = true
}
}
// Make the app's default view controller to display, whatever we set from the logic above, either Main, or Login, or Onboarding
self.window?.rootViewController = vc
}
// ...
}

How can I create a button in this extension which opens up a specific view in the main app without using the action buttons?

I am setting up a notification content extension in IOS and would like to add a button which opens up a specific view in the main app.
I can do this through the action buttons (at the bottom) but would like to add a button to the exentsion view itself.
#IBAction func btn_openapp(_ sender: Any) {
let mystring = "mydrop://" + String(campaign_id)
self.extensionContext?.open(URL(string: mystring)!, completionHandler: nil)
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool
{
d_me["single"] = url.host
print(url.host,url.scheme)
if url.scheme == "mydrop://"
{
let rootViewController = self.window!.rootViewController as! UINavigationController
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let targerView = mainStoryboard.instantiateViewController(withIdentifier: "CampaignDetailViewController")
rootViewController.pushViewController(targerView, animated: false)
}
return true
}
You shouldn't try to add a stored property at the extension. If you need a stored property, in this case a button, you should create a UIView or UIViewController, according to your use case, and associate it to another UIView or UIViewController.
You should also try to share some code, because it is not clear about what you are asking for.

how to check is user is log in in different view controllers in firebase

I am making an app where user can upload files on cloud and retrieve later, but I am stuck.
My question is how to check if user is logged in or not,
if login page should be my view controller and every time a user opens the app they have to login or is there some way we can skip this procedure?
I tried making home page an initial view controller and checking in view didload if there is any user or not using auth.auth().currentuser.uid but I don't feel good about using it please any help would be appreciated.
I am new to firebase
if Auth.auth().currentuser.uid != nil {
//success code
}
in AppDelegate in didFinishLaunchingWithOptions
if Auth.auth().currentUser?.uid != nil {
self.window?.rootViewController = UINavigationController.init(rootViewController: viewController1())
} else {
self.window?.rootViewController = UINavigationController.init(rootViewController: viewController2())
}
to check if user is logged in with firebase, you need to implement this in the didFinishLaunchingWithOptions in the appDelegate
if Auth.auth().currentUser != nil {
// the user is logged in, you can now redirect to another view controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "YourStoryboardVCName") as! YourViewController
self.window?.rootViewController = vc
}
I think the below code might help you.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
configureFirebase()
configureApplicationScreen()
return true
}
// To determine which screen will be displayed when application is launched
func configureApplicationScreen() {
guard let rootNav = window?.rootViewController as? UINavigationController else {
return
}
// Might need to change the code inside if let depending your requirement
if let _ = Auth.auth().currentUser, UserDefaults.isUserLoggedIn {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let sideMenuVC = storyboard.instantiateViewController(withIdentifier: "SideMenuController")
rootNav.navigationBar.isHidden = true
rootNav.addChild(sideMenuVC)
}
}
extension UserDefaults {
static var isUserLoggedIn: Bool {
get {
return UserDefaults.standard.bool(forKey: "IsUserLoggedIn")
}
set {
UserDefaults.standard.set(newValue, forKey: "IsUserLoggedIn")
UserDefaults.standard.synchronize()
}
}
}
Once user is logged in you need to save the status in your UserDefault like
"UserDefaults.isUserLoggedIn = true" and you need to check it when the application is launched in the AppDelegate class as stated above.

Does anyone have good idea to handle the deeplink with the id which is supposed to show the single page?

Does anyone have good idea to handle the deeplink?
I want to push a single page view which needs id from the HomeViewcontroller(or anything is ok) to the single page with the id that I get from the deeplink.
My current situation is that I could get the deeplink and the id inside of that in AppDelegate file by the way like below.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
if let incomingURL = userActivity.webpageURL {
let linkHandled = DynamicLinks.dynamicLinks()!.handleUniversalLink(incomingURL) { [weak self](dynamiclink, error) in
guard let strongSelf = self else { return }
if let dynamiclink = dynamiclink, let _ = dynamiclink.url {
strongSelf.handleIncomingDynamicLink(dynamicLink: dynamiclink)
}
}
return linkHandled
}
return false
}
func handleIncomingDynamicLink(dynamicLink: DynamicLink) {
guard let pathComponents = dynamicLink.url?.pathComponents else {
return
}
if pathComponents.count > 1 {
for (i, value) in pathComponents.enumerated() {
if i == 1 {
// define whether the deeplink is for topic or post
UserDefaults.standard.set(value, forKey: "deepLinkType")
print(value)
} else if i == 2 {
UserDefaults.standard.set(value, forKey: "deepLinkId")
print(value)
}
}
}
}
And then viewDidAppear in the HomeViewController
if (self.isViewLoaded && (self.view.window != nil)) {
let us = UserDefaults.standard
if let deepLinkType = us.string(forKey: "deepLinkType"), let deepLinkId = us.string(forKey: "deepLinkId"){
us.removeObject(forKey: "deepLinkType")
us.removeObject(forKey: "deepLinkId")
if deepLinkType == "topic" {
let storyboard = UIStoryboard(name: "Topic", bundle: nil)
let nextVC = storyboard.instantiateViewController(withIdentifier: "SingleKukaiVC") as! TopicViewController
nextVC.topicKey = deepLinkId
self.navigationController?.pushViewController(nextVC, animated: true)
} else if deepLinkType == "post" {
}
}
}
this works fine when the app is neither in foreground nor background I mean if it's not instanced. However, while the app is instanced, this doesn't work because viewDidAppear is not going to be read. Or even the HomeViewController itself is not might be called if user had opened another view.
So my question is that what is the best way to handle the deeplink which has id for the single page? I appreciate some examples.
Thanks in advance.
Don't write your code to push Topic view controller in the HomeViewController.
Inside the App Delegate's handleIncomingDynamicLink method, get the top most view controller, then create the view controller (as in your code) and then push it from the top most view controller.
Your code to create Topic View Controller:
let storyboard = UIStoryboard(name: "Topic", bundle: nil)
let nextVC = storyboard.instantiateViewController(withIdentifier: "SingleKukaiVC") as! TopicViewController
nextVC.topicKey = deepLinkId
Check URL to see how to fetch top most view controller
Two ways you could handle it:
For the ViewController that you want presented upon handling of the deeplink set an initializer that takes info contained in the deeplink. And set whatever you need to set to handle that info(you seem to have done that). Now in your handleIncomingDynamicLink() instantiate the ViewController we mentioned and make it to be presented. How you are going to make it present itself depends on the navigation logic that you have set.
AppDelegate receives link->Handles it->Instatiates VC and presents it->Does things accordingly
In your handleIncomingDynamicLink() use NotificationCenter to post a notification containing the info. In your ViewController add an observer for that notification and define whatever you need to be done when the notification is received.
AppDelegate receives link->Handles it->Fires notification->VC listens notifications->Does things accordingly

Resources