How to send and receive data in Today extensions - ios

I want to develop an app for iOS that have a Widget for notification center, but I don't know how I should send and receive data (pass data) between View Controller and And Today Extension.
I tried to use structs, but it doesn't work, and also I used app groups but I don't want to use this method.
let shared = NSUserDefaults(suiteName: "group.Demo.Share-Extension-Demo.mahdi")
shared?.setObject("Hello", forKey: "kkk")

Apart from NSUserDefaults, you can use the NSNotificationCenter to send or receive data anywhere.
You need to set the observer where you can receive the data like below:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "dataReceived:", name: "SpecialKey", object: nil)
}
Funciton to catch data:
func dataReceived(notification: NSNotification) {
//deal with notification.userInfo
println(notification.userInfo)
println("Received Data")
}
And you need to define NSNotificationCenter from where you need to send the data:
NSNotificationCenter.defaultCenter().postNotificationName("SpecialKey", object: nil, userInfo: ["MyData": "Demo"])
References:
The complete guide to NSNotificationCenter
Hope it helps!
http://moreindirection.blogspot.in/2014/08/nsnotificationcenter-swift-and-blocks.html

For the people who haven't found a way to implement calling function or button click from App Extension (Widget):
Note: This is using Swift
Note 2: Replace the names of NSNotification and methods with your implementations
First, create NotificationCenter post method (In Swift 2.0 - NSNotification Center)
Create methods in App Delegate class -
var scheme: String!
var host: String!
Then, add the following function on the bottom of the class (after the last one):
func application(_ app: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
scheme = url.scheme
host = url.host
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "NOTIFICATION_NAME"), object: nil)
return true
}
In your ViewController class, where you want to execute the function or the statement from clicking Widget, add the following in super.viewDidLoad():
NotificationCenter.default.addObserver(self,selector: #selector(self.YOUR_METHOD_NAME),
name: NSNotification.Name(rawValue: "NOTIFICATION_NAME"),
object: nil)
And the method you want to call:
func YOUR_METHOD_NAME(notification: NSNotification) {
let appDelegate =
UIApplication.shared.delegate as! AppDelegate
if appDelegate.scheme != nil {
startRecording()
}
}
I'm assuming that you have already created your widget target, and the view for it. Add this to your button in TodayViewController from which you want to handle the click:
#IBAction func openApp(_ sender: UIButton) {
openApp()
}
And the function to handle opening app by URl Scheme:
func openApp(){
let myAppUrl = NSURL(string: "YOUR_URL_SCHEME://YOUR_HOST_NAME")!
extensionContext?.open(myAppUrl as URL, completionHandler: { (success) in
if (!success) {
self.textView.text = "There was a problem opening app!"
}
})
}
For YOUR_URL_SCHEME, add your scheme that you have specified in Info.plist, if your don't, go to this link and follow instructions:
Add URL Scheme to Xcode
For YOUR_HOST_NAME, you can remove this, and only open app by URL Scheme.
Happy coding!

Related

How to execute a function from second ViewController after popViewController?

I am an Android developer trying to port my app to iOS and this is my first time working with Swift and Xcode, or anything Mac in general. The app is to scan for a QR code, if the QR code is invalid then I'd like it to go back to the previous screen and display a toast. I've already made a function that displays the toast stating the QR code was invalid.
func found(code: String) {
print(code)
// convert JSON to object
do {
let qrMessage = try JSONDecoder().decode(QrMessage.self, from: code.data(using: .utf8)!)
print("QrMessage object - device name: " + qrMessage.pcName)
}
catch let error {
print(error)
_ = navigationController?.popViewController(animated: true)
// call ShowInvalidQrToast() from the new VC that is now on top of the ViewController stack
}
EDIT:
I managed to figure out a solution using the NotificationCenter from the first answer in this thread: Calling function from another ViewController in swift. More info in my answer below. If you think there is a better way of doing this, please post your own answer.
My solution using NotificationCenter:
In ConnectViewController
override func viewDidLoad() {
super.viewDidLoad()
...
NotificationCenter.default.addObserver(self, selector: #selector(showInvalidQrCodeToast(_:)), name: Notification.Name(rawValue: "showInvalidQrCodeToast"), object: nil)
}
#objc func showInvalidQrCodeToast(_ notification: Notification) {
self.view.makeToast("Invalid QR code")
}
In ScannerViewController
func found(code: String) {
print(code)
// convert JSON to object
do {
let qrMessage = try JSONDecoder().decode(QrMessage.self, from: code.data(using: .utf8)!)
print("QrMessage object - device name: " + qrMessage.pcName)
}
catch let error {
print(error)
_ = navigationController?.popViewController(animated: true)
NotificationCenter.default.post(name: Notification.Name(rawValue: "showInvalidQrCodeToast"), object: nil)
}

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

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?
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.
Create Custom URL Sceheme
Check groups data.(are you setting correct or not)
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
}
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)
}
Check out custom url type:
https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Inter-AppCommunication/Inter-AppCommunication.html
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

NSUbiquityIdentityDidChangeNotification does not fire with cloud kit

This is my current code
func applicationDidBecameActive(notification:NSNotification) {
println("Application is active")
NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleIdentityChange:", name: NSUbiquityIdentityDidChangeNotification, object: nil)
}
func applicationBecameInactive(notification:NSNotification) {
println("Application is inactive")
NSNotificationCenter.defaultCenter().removeObserver(self, name: NSUbiquityIdentityDidChangeNotification, object: nil)
}
func handleIdentityChange(notification:NSNotification) {
println("this is working")
let fileManager = NSFileManager()
if let token = fileManager.ubiquityIdentityToken {
println("New token is \(token)")
}
else {
println("User has logged out of iCloud")
}
}
"Application is active" & "Application is inactive" works properly. There is no problem there.
I could not get it fire "This is working". By logging into different iCloud account or logging out of iCloud account.
I tried on simulator & on actual device.
Please help me fix this or suggest alternative method to achieve same goal(change in iCloud account).
In Swift I have sometimes needed to add #objc in front of a func for NSNotificationCenter to find it.
So instead of
func handleIdentityChange(notification:NSNotification) {
I'd try
#objc func handleIdentityChange(notification:NSNotification) {
Have a look at my comment in this question. I never received a NSUbiquityIdentityDidChangeNotification either. In iOS your app gets killed when you change accounts. In tvOS you can use NSUbiquitousKeyValueStoreAccountChange.

How trigger background process from Watch on iPhone (trigger: Watch)?

I'd like to add to my Watch app functionality which send to iPhone app a Local Notification (while iPhone app is on the background or iPhone is locked).
I know how to create Local Notification itself.
What Im asking for is way, how to trigger background process (which contains also Local Notification) on iPhone by (for example) tapping on button on Apple Watch.
WKInterfaceController.openParentApplication is the official way to communicate with the iPhone. Documentation.
You pass parameters in the userInfo dictionary and retrieve results via the reply block.
On the iPhone the request is handled by appDelegate's handleWatchKitExtensionRequest method. Documentation
Code in my InterfaceController.swift:
#IBAction func btn() {
sendMessageToParentApp("Button tapped")
}
// METHODS #2:
func sendMessageToParentApp (input:String) {
let dictionary = ["message":input]
WKInterfaceController.openParentApplication(dictionary, reply: { (replyDictionary, error) -> Void in
if let castedResponseDictionary = replyDictionary as? [String:String], responseMessage = castedResponseDictionary["message"] {
println(responseMessage)
self.lbl.setText(responseMessage)
}
})
}
Next i made new method in my AppDelegate.swift:
func application(application: UIApplication, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]?, reply: (([NSObject : AnyObject]!) -> Void)!) {
if let infoDictionary = userInfo as? [String:String], message = infoDictionary["message"] {
let response = "iPhone has seen this message." // odešle se string obsahující message (tedy ten String)
let responseDictionary = ["message":response] // tohle zase vyrobí slovník "message":String
NSNotificationCenter.defaultCenter().postNotificationName(notificationWatch, object: nil)
reply(responseDictionary)
}
}
As you can see I use Notification to get iOS app know that button has been tapped. In ViewController.swift I have Notification Observer and function which is executed every time observer catch notification that user tapped on button on watch ("notificationWatch" is global variable with notification key). Hope this will help to anybody.

NSNotification not working on Swift translation (from Objective-C)

After converting my Objective-C code to Swift, I cannot get my NSNotifications to work. After an hour of searching in the web, I finally gave up. Consider the following example:
func getToUrl(url:String, timeoutInterval:Float) -> Bool {
println("Starting HTTP GET to: \(url)")
// Fire a notification
NSNotificationCenter.defaultCenter().postNotificationName("StartNotification", object: self)
[...]
}
func getJsonFromServer() {
// Add an observer which should fire the method test when desired
NSNotificationCenter.defaultCenter().addObserver(self, selector: "test:", name: "StartNotification", object: self)
// Calls the function
getToUrl("http://www.stackoverflow.com", timeoutInterval: 10)
}
func test(sender: AnyObject) {
println("I am here!")
}
I cannot find the error, I would really appreciate if someone else could!
The code runs, but the test method is never called.
Change in this, self to nil (in order to hear all objects)
func getJsonFromServer() {
// Add an observer which should fire the method test when desired
NSNotificationCenter.defaultCenter().addObserver(self, selector: "test:", name: "StartNotification", object: nil)
// Calls the function
getToUrl("http://www.stackoverflow.com", timeoutInterval: 10)
}

Resources