Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you pass data from a custom url scheme to Views in SwiftUI?

Tags:

xcode

ios

swiftui

There's no shortage of tips and tutorials on handling custom URL schemes in iOS. What ALL fail to do is actually show you how to pass data parsed from those URLs to your app/views. Yea, I can use a global variable, but that's not the "right" way and plus if you want your Swift view to react to a change in that global variable, you can't.

For example, I have,

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>){
    let urlContext = URLContexts.first // Because I don't know how to properly deal with Sets
    if let url = urlContext?.url{
        guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
            let params = components.queryItems else {
                print("Invalid URL or album path missing")
                return
        }
        if let token = params.first(where: { $0.name == "token" })?.value {
            print("Token: \(token)")
            MyGlobalToken = token
        }
    }        
}

You'll see the MyGlobalToken option in there which works, but I can't respond to a change in that variable. Do I have to do something with the self.window?.rootViewController but I can't find any documentation on what to do. Or do you set up a "notification" so that you view responds? Or is this not implemented yet in SwiftUI?

FWIW I'm new to iOS development.

like image 936
Keoni Avatar asked Sep 24 '19 09:09

Keoni


2 Answers

Here is a great blog to learn about SceneDelegate in iOS 13.

First answer is not a great answer.

If you run your app from a completely inactive state -- i.e. when you run it from XCode aka when it's restarted or not running in the background -- the app will call the func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) method to initialize the scenes infrastructure.

If you are running your app in the background when you point to the URL in Safari the app will call the func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) method.

In order to be thorough, you will have to attempt to get the url parameters in both methods. You can pass this query to the AppDelegate for future use by ViewControllers in both methods as well.

NOTE: If the first ViewController the app opens needs the URL query information you will need to do a few extra steps. To get your ViewControllers to actually update with the information, you will need to use the sceneDidBecomeActive() method for when the app is run from an inactive state/in the background. This method will have to call a method in the ViewController in order for it to pull the variable from the app delegate when the user enters your app. In this case I used viewDidLoad() methods to pull the updated variable from the AppDelegate.

Below is the full code for reference:

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    let appDelegate = UIApplication.shared.delegate as! AppDelegate


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        appDelegate.query = connectionOptions.urlContexts.first?.url.query ?? "No query"
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        appDelegate.query = URLContexts.first?.url.query
    }

    func sceneDidDisconnect(_ scene: UIScene) {}

    // Only needed if first ViewController needs updated AppDelegate Variable
    func sceneDidBecomeActive(_ scene: UIScene) {
        self.window?.rootViewController?.viewDidLoad()
    }

    func sceneWillResignActive(_ scene: UIScene) {}

    func sceneWillEnterForeground(_ scene: UIScene) {}

    func sceneDidEnterBackground(_ scene: UIScene) {
        (UIApplication.shared.delegate as? AppDelegate)?.saveContext()
    }

}
like image 200
Michael Wlodawsky Avatar answered Oct 18 '22 22:10

Michael Wlodawsky


I had many trouble in this problem. I don't know many things in IOS. But There is no answer. I write it. If you don't use sceneDelegate, You may use your global variable. (I don't know why it doesn't work)

For this, I do like below.

  1. Delete scenedelegate.swift.
  2. delete sceneDelegate thing in Info.plist
  3. initialLize check variable

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            // Override point for customization after application launch.
            UserDefaults.check = ""
            return true
        }
    
  4. Add lines in Appdelegate for setting global variable.

    open func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 
        //set Global variable 
        // ... You should add check url for your application ...
        UserDefaults.check = "checked"
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
             if let uservc = self.storyboard?.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
             {
                 if #available(iOS 13.0, *) {
                    uservc.isModalInPresentation = true
                 }
                 uservc.debugText.text = "This is openUrl State"
                 self.present(uservc, animated: false) {
    
              }      
          }
    }
    
  5. Make IntroViewController and MainViewController

    IntroViewController is rootviewcontroller.

    and MainViewController is second view controller.

    if check variable is "checked", Viewdidload in IntroView is not excuted.

    and this time is application-open is excuted with safari or chrome.

    //IntroViewController
    class IntroViewController: UIViewController {
      override func viewDidLoad() {
      super.viewDidLoad()
      DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
        if UserDefaults.check.elementsEqual("") {
           if let uservc = 
              self.storyboard?.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
            {
                if #available(iOS 13.0, *) {
                    uservc.isModalInPresentation = true
                }
                uservc.debugText.text = "This is normal State"
                self.present(uservc, animated: false) {
                }
            }
        }
      }
    

6.Global Variable

    //UserDefaults.swift
    import Foundation
    fileprivate let keyCheck = "keyCheck"

    extension UserDefaults {
       static var check: String {
         get {
            return UserDefaults.standard.string(forKey: keyCheck) ?? ""
         }
         set(value) {
            UserDefaults.standard.set(value, forKey: keyCheck)
            UserDefaults.standard.synchronize()
         }
      }
    }

When I use this logic in scenedelegate, It didn't work well.(I couldn't check "check variable".)

like image 27
UdevApp Avatar answered Oct 18 '22 22:10

UdevApp