Home » Swift » Perform action in host app from Today extension(Widget) Without opening app ios

Perform action in host app from Today extension(Widget) Without opening app ios

Posted by: admin November 30, 2017 Leave a comment

Questions:

I want to manage some action in containing app from today extension(Widget).

Full description:
in my containing app, some action (like play/pause audio) perform. And want to manage that action also from today extension(widget). An action continues to perform in background state as well.

In today extension, the same action will perform. so for that, if in the main containing app already starts an action and send it into background state, a user can pause action from a widget. and the user also can start/pause action any time from the widget (today extension).

For achieve this goal I used UserDefault with app Group capability and store one boolean value. when widget present it checks boolean value and set button state play/pause. it’s set correctly but when I press extension button action does not perform in host app.

code:

in main containing app code

override func viewDidLoad() {
    super.viewDidLoad()
    let objUserDefault = UserDefaults(suiteName:"group.test.TodayExtensionSharingDefaults")

    let objTemp = objUserDefault?.object(forKey: "value")

    self.btnValue.isSelected = objTemp

    NotificationCenter.default.addObserver(self, selector: #selector(self.userDefaultsDidChange), name: UserDefaults.didChangeNotification, object: nil)
}




func userDefaultsDidChange(_ notification: Notification) {

        let objUserDefault = UserDefaults(suiteName: "group.test.TodayExtensionSharingDefaults")
        objUserDefault?.synchronize()   
        let objTemp = objUserDefault?.object(forKey: "value")
        self.btnValue.isSelected = objTemp
  }

In Extension Class:

@IBAction func onPlayPause(_ sender: UIButton) {
       DispatchQueue.main.async {
       let sharedDefaults = UserDefaults(suiteName: "group.test.TodayExtensionSharingDefaults")

       if let isPlaying = sharedDefaults?.bool(forKey: "isPlaing") {

       sharedDefaults?.set(!isPlaying, forKey: "isPlaying")

       }else{

        sharedDefaults?.set(false, forKey: "isPlaying")
        }

        sharedDefaults?.synchronize()
}

notification was not fired when a user updates default. it’s updated value when the app restarts.

so how to solve this issue?

and the same thing wants to do in opposite means from containing app to a widget.
(easy to user single action object but how?)

And is any other way to perform a quick action in containing app from extension without opening App?

Answers:

Use MMWormhole (or its new and unofficial Swift version, just Wormhole). It’s very simple.

In the app’s view controller:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    let wormhole = MMWormhole(applicationGroupIdentifier: "group.test.TodayExtensionSharingDefaults",
                              optionalDirectory: "TodayExtensionSharingDefaults")

    wormhole.listenForMessage(withIdentifier: "togglePlayPause") { [weak self] _ in
        guard let controller = self else { return }
        controller.btnValue.isSelected = controller.btnValue.isSelected
    }
}

In the extension:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view from its nib.

    self.wormhole = MMWormhole(applicationGroupIdentifier: "group.test.TodayExtensionSharingDefaults", optionalDirectory: "TodayExtensionSharingDefaults")
}

@IBAction func onPlayPause(_ sender: UIButton) {
    guard let wormhole = self.wormhole else { extensionContext?.openURL(NSURL(string: "foo://startPlaying")!, completionHandler: nil) } // Throw error here instead of return, since somehow this function was called before viewDidLoad (or something else went horribly wrong)

    wormhole.passMessageObject(nil, identifier: "togglePlayPause")
}

Declare foo:// (or whatever else you use) in Xcode’s Document Types section, under URLs, then implement application(_:open:options:) in your AppDelegate so that the app starts playing music when the URL passed is foo://startPlaying.

how to add URL to Xcode

Questions:
Answers:
  1. Create Custom URL Sceheme

  2. Check groups data.(are you setting correct or not)

  3. Whenever you click on button, the host app will get called from Appdelegate, UIApplication delegate

    func application(_ application: UIApplication, open urls: URL, sourceApplication: String?, annotation: Any) -> Bool {
    
            let obj = urls.absoluteString.components(separatedBy: "://")[1]
            NotificationCenter.default.post(name: widgetNotificationName, object: obj)
            print("App delegate")
            return true
        }
    
  4. Fire your notification from there then observe it anywhere in your hostapp.

    Widget Button action code

    @IBAction func doActionMethod(_ sender: AnyObject) {
    
        let button = (sender as! UIButton)
        var dailyThanthi = ""
        switch button.tag {
        case 0:
            dailyThanthi = "DailyThanthi://h"
        case 1:
            dailyThanthi = "DailyThanthi://c"
        case 2:
            dailyThanthi = "DailyThanthi://j"
            //        case 3:
            //            dailyThanthi = "DailyThanthi://s"
            //        case 4:
            //            dailyThanthi = "DailyThanthi://s"
        default:
            break
        }
    
        let pjURL = NSURL(string: dailyThanthi)!
        self.extensionContext!.open(pjURL as URL, completionHandler: nil)
    
    }
    
  5. Check out custom url type:
    https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html

  6. Note:

    There is no direct communication between an app extension and its
    containing app; typically, the containing app isn’t even running while
    a contained extension is running. An app extension’s containing app
    and the host app don’t communicate at all.

    In a typical request/response transaction, the system opens an app extension on behalf of a host app, conveying data in an extension
    context provided by the host. The extension displays a user interface,
    performs some work, and, if appropriate for the extension’s purpose,
    returns data to the host.

    The dotted line in Figure 2-2 represents the limited interaction available between an app extension and its containing app. A Today
    widget (and no other app extension type) can ask the system to open
    its containing app by calling the openURL:completionHandler: method of
    the NSExtensionContext class. As indicated by the Read/Write arrows in
    Figure 2-3, any app extension and its containing app can access shared
    data in a privately defined shared container. The full vocabulary of
    communication between an extension, its host app, and its containing
    app is shown in simple form in Figure 2-3.

    https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionOverview.html