Picker's configuration is not a valid configuration, Swift - ios

I am working on new image picker API provided by Apple in WWDC2020 named as PHPicker. I am getting this error when I select the image form the picker second time. First time the code works perfectly. Second time when I click on the button to open the picker, app crashes with the following error. "Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Picker's configuration is not a valid configuration.'".
#IBAction func addVideo(_ sender: UIButton) {
presentPicker(filter: .videos)
}
private func presentPicker(filter: PHPickerFilter?) {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
// Set the filter type according to the user’s selection.
configuration.filter = filter
// Set the mode to avoid transcoding, if possible, if your app supports arbitrary image/video encodings.
configuration.preferredAssetRepresentationMode = .current
// Set the selection behavior to respect the user’s selection order.
configuration.selection = .ordered
// Set the selection limit to enable multiselection.
configuration.selectionLimit = 1
// Set the preselected asset identifiers with the identifiers that the app tracks.
configuration.preselectedAssetIdentifiers = selectedAssetIdentifiers
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
let existingSelection = self.selection
var newSelection = [String: PHPickerResult]()
for result in results {
let identifier = result.assetIdentifier!
newSelection[identifier] = existingSelection[identifier] ?? result
}
// Track the selection in case the user deselects it later.
selection = newSelection
selectedAssetIdentifiers = results.map(\.assetIdentifier!)
selectedAssetIdentifierIterator = selectedAssetIdentifiers.makeIterator()
if selection.isEmpty {
displayEmptyImage()
} else {
displayImage()
}
}
I am using the same code that is provide by apple on their website.
https://developer.apple.com/documentation/photokit/phpickerviewcontroller. I just have changed the selectionLimit to 1 from 0.

Apparently it is documented (just kidding, it's in a header file) that the preselectedAssetIdentifiers property is only valid when selectionLimit is greater than 1, which is the default.
Here's an answer from the Apple developer forum for reference: https://developer.apple.com/forums/thread/705493

There must be a problem with your code.
I've copy-pasted your solution and it works correctly on Simulator (iOS 15.0).

Related

Clearing UserDefaults.standard.data information doesn't actually delete it?

I saved a UIImage to UserDefaults via .data with this code, where the key equals "petPhoto1":
#IBAction func addPhotoButton(_ sender: Any) {
let picker = UIImagePickerController()
picker.allowsEditing = false
picker.delegate = self
picker.mediaTypes = ["public.image"]
present(picker, animated: true)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
image.storeInUserDefaults(for: "petPhoto\(activePet)")
UserDefaults.standard.set("yes", forKey: "doesImageExist\(activePet)")
}
dismiss(animated: true, completion: nil)
}
(unrelated stuff in between)
extension UIImage {
func storeInUserDefaults(with compressionQuality: CGFloat = 0.8, for key: String) {
guard let data = self.jpegData(compressionQuality: compressionQuality) else { return }
let encodedImage = try! PropertyListEncoder().encode(data)
UserDefaults.standard.set(encodedImage, forKey: key)
}
}
Now when I erase it like this:
UserDefaults.standard.set(nil, forKey: "petPhoto1")
I can still see that "Documents & Data" for my app under Settings is still full with the same size as the original image, indicating that it didn't actually delete it, even though it no longer displays when it gets loaded back from UserDefaults.
Can anyone figure out a way to fix this? Thanks!
By the way, in case it helps, here is other code related to this issue:
The code I use in the ImageViewController that I display the image after saving it:
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
activePet = UserDefaults.standard.string(forKey: "activePet")! // activePet = 1 (confirmed with debugging with other, unrelated code
imageView.image = try? UIImage.loadFromUserDefaults(with: "petPhoto\(activePet)")
}
extension UIImage {
static func loadFromUserDefaults(with key: String) throws -> UIImage? {
let activePet = UserDefaults.standard.string(forKey: "activePet")!
guard let data = UserDefaults.standard.data(forKey: "petPhoto\(activePet)") else {
return nil
}
do {
let decodedImageData = try PropertyListDecoder().decode(Data.self, from: data)
return UIImage(data: decodedImageData)
} catch let error {
throw error
}
}
}
When you do this:
UserDefaults.standard.set(nil, forKey: "petPhoto1")
The link between the key and the file saved will be removed synchronously. that means if you try to access the value for this key, it gives nil.
But this image needs to be cleared from storage too, that will be happening asynchronously [we don't have completion handler API support from apple to get this information].
Apple Documentation for reference:
At runtime, you use UserDefaults objects to read the defaults that
your app uses from a user’s defaults database. UserDefaults caches the
information to avoid having to open the user’s defaults database each
time you need a default value. When you set a default value, it’s
changed synchronously within your process, and asynchronously to
persistent storage and other processes.
What you can try:
First approch
Give some time for the file delete operation to get completed by OS. then try to access the image in the disk.
Second approch
Try observing to the changes in the directory using GCD. Refer: https://stackoverflow.com/a/26878163/5215474

PHPickerViewController add video editing screen aka (UIImagePickerController.allowsEditing = true)

I would like to edit/compress video after selection like it was in UIImagePickerController with allowsEditing = true.
Still the new PHPickerViewController doesn't have this property and Apple says that there is no such property anymore here. But there are apps in App Store that pushing "allowsEditing controller" after selecting asset from PHPickerViewController
Here is my PHPickerViewController implementation:
func openImagePicker() {
if #available(iOS 14, *) {
var configuration = PHPickerConfiguration()
configuration.preferredAssetRepresentationMode = .automatic
let picker = PHPickerViewController(configuration: configuration)
// Set the delegate
picker.delegate = self
// Present the picker
present(picker, animated: true)
} else {
// Fallback on earlier versions
imagePicker?.photoGalleryAsscessRequest()
}
}
extension EditController: PHPickerViewControllerDelegate {
#available(iOS 14, *)
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
self.dismiss(animated: true)
}
}
I investigated this issue but indeed there is no support for like as you mention allowsediting.
For example related to your question preset values for video edits was depreciated to UIImagePickerController.ImageURLExportPreset.compatible but There is no support for automatic compression. The picker will always pass the original video/image and it is up to the app to do the necessary compressions or edits.
You can check this Apple Document: imageExportPreset.
Apple specifically mentions that we should be using this new one instead of the older UIImagePickerViewController. If someone wonder more : Meet the new Photos picker

How to retrieve PHAsset from PHPicker?

In WWDC20 apple introduced PHPicker - the modern replacement for UIImagePickerController.
I'm wondering if it's possible to retrieve PHAsset using the new photo picker?
Here is my code:
private func presentPicker(filter: PHPickerFilter) {
var configuration = PHPickerConfiguration()
configuration.filter = filter
configuration.selectionLimit = 0
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
}
I managed to find an answer from the developers of this framework on the apple forum:
Yes, PHPickerResult has the assetIdentifier property which can contain
a local identifier to fetch the PHAsset from the library. To have
PHPicker return asset identifiers, you need to initialize
PHPickerConfiguration with the library.
Please note that PHPicker does not extend the Limited Photos Library
access for the selected items if the user put your app in Limited
Photos Library mode. It would be a good opportunity to reconsider if
the app really needs direct Photos Library access or can work with
just the image and video data. But that really depend on the app.
The relevant section of the "Meet the new Photos picker" session
begins at 10m 20s.
Sample code for PhotoKit access looks like this:
import UIKit
import PhotosUI
class PhotoKitPickerViewController: UIViewController, PHPickerViewControllerDelegate {
#IBAction func presentPicker(_ sender: Any) {
let photoLibrary = PHPhotoLibrary.shared()
let configuration = PHPickerConfiguration(photoLibrary: photoLibrary)
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
let identifiers = results.compactMap(\.assetIdentifier)
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: identifiers, options: nil)
// TODO: Do something with the fetch result if you have Photos Library access
}
}

siri shortcut button (INUIAddVoiceShortcutButton) shows wrong title when have multiple shortcuts (NSUserActivity)

I've 2 siri shortcuts in my App.
I use NSUserActivity to donate these shortcuts. I've also created 2 NSUserActivityTypes in my info.plist.
There are 2 view controllers which handle these shortcuts (1 view controller for 1 shortcut).
If I add 1 siri shortcut from 1 view controller and then go to 2nd view controller the native siri shortcut button (INUIAddVoiceShortcutButton) on 2nd view controller automatically picks the first shortcut (created from 1st view controller) and shows "Added to Siri" with suggested phrase instead of showing "Add to Siri" button. I double checked that each NSUserActivity has different identifier but still somehow its picks the wrong shortcut.
View Controller 1:
let userActivity = NSUserActivity(activityType: "com.activity.type1")
userActivity.isEligibleForSearch = true
userActivity.isEligibleForPrediction = true
userActivity.title = shortcut.title
userActivity.suggestedInvocationPhrase = suggestedPhrase
let attributes = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
attributes.contentDescription = description
userActivity.contentAttributeSet = attributes
let shortcut = INShortcut(userActivity: userActivity)
let siriButton = INUIAddVoiceShortcutButton(style: .whiteOutline)
siriButton.translatesAutoresizingMaskIntoConstraints = false
siriButton.shortcut = shortcut
self.view.addSubview(siriButton)
View Controller 2:
let userActivity2 = NSUserActivity(activityType: "com.activity.type2")
userActivity2.isEligibleForSearch = true
userActivity2.isEligibleForPrediction = true
userActivity2.title = shortcut.title
userActivity2.suggestedInvocationPhrase = suggestedPhrase
let attributes = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
attributes.contentDescription = description
userActivity2.contentAttributeSet = attributes
let shortcut = INShortcut(userActivity: userActivity2)
let siriButton = INUIAddVoiceShortcutButton(style: .whiteOutline)
siriButton.translatesAutoresizingMaskIntoConstraints = false
siriButton.shortcut = shortcut
self.view.addSubview(siriButton)
A similar thing happens when I delete the App and reinstall without deleting the shortcuts from Phone's Settings App.
Seems like its an IOS bug. I figured out a workaround for this problem. You have to create a new siri button every time the user add/edit the siri shortcut. Before creating siri button do the following things
1- Get all the voice shortcuts from INVoiceShortcutCenter by calling the function. Note that this happens asynchronously, so you need to do it some time before you need the data (e.g. in your AppDelegate). You'll also need to re-load this whenever the user adds a Siri Shortcut (probably in the INUIAddVoiceShortcutViewControllerDelegate.addVoiceShortcutViewController(_:didFinishWith:error) method).
INVoiceShortcutCenter.shared.getAllVoiceShortcuts { (voiceShortcutsFromCenter, error) in
guard let voiceShortcutsFromCenter = voiceShortcutsFromCenter else {
if let error = error as NSError? {
os_log("Failed to fetch voice shortcuts with error: %#", log: OSLog.default, type: .error, error)
}
return
}
self.voiceShortcuts = voiceShortcutsFromCenter
}
2- In View Controller-1 check if the shortcut is already added or not by iterating all the voice shortcuts
let voiceShorcut = voiceShortcuts.first { (voiceShortcut) -> Bool in
if let activity = voiceShortcut.shortcut.userActivity, activity.activityType == "com.activity.type1" {
return true
}
return false
}
3- If your voice shortcut is registered then pass the INShortcut to siri button otherwise don't set it.
if voiceShorcut != nil {
let shortcut = INShortcut(userActivity: userActivity1)
siriButton.shortcut = shortcut
}
Do the same thing in Second View Controller.
It's iOS 12.0 bug.
You can fix it by update INUIAddVoiceShortcutButton.voiceShortcut with correct value.
Use KVO to observe "voiceShortcut" property and when it change assign correct value to it.
I've moved to intents setup now and I find that even having just one intent setup and working the INUIAddVoiceShortcutButton is not able to track my shortcut. Once phrase is recorded it shows the Added to Siri with phrase.
But every time the app relaunches the Add to Siri button shows up instead of the Added to Siri button with recorded phrase.
I have tried going by Bilal's suggestion and although I can see the INVoiceShortcutCenter showing me my shortcut as present it doesn't loaded it into the Siri button.
My code looks like this for the button itself.
private func addSiriButton() {
let addShortcutButton = INUIAddVoiceShortcutButton(style: .blackOutline)
addShortcutButton.delegate = self
addShortcutButton.shortcut = INShortcut(intent: engine.intent )
addShortcutButton.translatesAutoresizingMaskIntoConstraints = false
siriButtonSubView.addSubview(addShortcutButton)
siriButtonSubView.centerXAnchor.constraint(equalTo: addShortcutButton.centerXAnchor).isActive = true
siriButtonSubView.centerYAnchor.constraint(equalTo: addShortcutButton.centerYAnchor).isActive = true
}
I have all the protocols implement and I had a close look at the Soup app but just can't figure out what drives this inaccuracy.
Funny enough, even British Airways app developers have given up on that as their button has exactly the same fault behaviour.
Update: I've built another test project with minimal amount implementation for the Intent and the Add to Siri and Added to Siri works perfectly. I'm guessing at this point that there is something in my own apps codebase that is causing this unwanted behaviour.
update 2 Just wanted to let everyone know I have fixed the issue. Using intents works fine but there is definitely a little sensitivity in the Intents definition file itself. All I had to do is create a new intent which then was generated and that worked. Seems my initial intent was somehow corrupt but there were no errors. After creating another intent and re-assigning intent handling function to that it all worked as intended. (pun intended)
I encountered this error when I had an existing intent and working configuration, but added a new parameter. However, in my Intent configuration, I had not added the new parameter name to a supported combination under the Shortcuts app section.
For example, if I had two properties myId and myName, and specified them as such:
let intent = MyIntent()
intent.myId = 1234
intent.myName = "banana"
Then I would need a supported combination of myId, myName in my intents definition file. In my particular case, I had forgotten myName so the INUIAddVoiceShortcutButton was attempting to do a lookup using myId, myName but didn't know how.
I just fixed this issue myself by changing my implementation (originally based on the soupchef app) to this code sample provided by apple (https://developer.apple.com/documentation/sirikit/inuiaddvoiceshortcutbutton):
EDIT: I added code that shows how I create and pass in the shortcutObject (INShortcut) for both UserActivity and custom Intent shortcuts.
The Shortcut class is an enum that contains a computed property called intent that returns an instantiation of the custom intent.
private func addShortcutButton(shortcut: Shortcut, parentViewController: UIViewController, shortcutViewControllerDelegate: INUIAddVoiceShortcutViewControllerDelegate) {
guard let view = parentViewController.view else { return }
if let intent = shortcut.intent {
shortcutObject = INShortcut(intent: intent)
} else if let userActivity = view.userActivity {
shortcutObject = INShortcut(userActivity: userActivity)
}
self.shortcutViewControllerDelegate = shortcutViewControllerDelegate
addSiriButton(to: shortcutButtonContainer)
}
func addSiriButton(to view: UIView) {
let button = INUIAddVoiceShortcutButton(style: .whiteOutline)
button.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(button)
view.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true
view.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true
button.addTarget(self, action: #selector(addToSiri(_:)), for: .touchUpInside)
}
// Present the Add Shortcut view controller after the
// user taps the "Add to Siri" button.
#objc
func addToSiri(_ sender: Any) {
guard let shortcutObject = shortcutObject else { return }
let viewController = INUIAddVoiceShortcutViewController(shortcut: shortcutObject)
viewController.modalPresentationStyle = .formSheet
viewController.delegate = shortcutViewControllerDelegate
parentViewController?.present(viewController, animated: true, completion: nil)
}
So we can't use the default Siri button, you have to use custom UIButton.
The class VoiceShortcutsManager will check all the voice intents and then we can search that list check if exist one match if yes so we should suggest edition if not we should suggest adding.
public class VoiceShortcutsManager {
private var voiceShortcuts: [INVoiceShortcut] = []
public init() {
updateVoiceShortcuts(completion: nil)
}
public func voiceShortcut(for order: DeviceIntent, powerState: State) -> INVoiceShortcut? {
for element in voiceShortcuts {
guard let intent = element.shortcut.intent as? ToggleStateIntent else {
continue
}
let deviceIntent = DeviceIntent(identifier: intent.device?.identifier, display: intent.device?.displayString ?? "")
if(order == deviceIntent && powerState == intent.state) {
return element
}
}
return nil
}
public func updateVoiceShortcuts(completion: (() -> Void)?) {
INVoiceShortcutCenter.shared.getAllVoiceShortcuts { (voiceShortcutsFromCenter, error) in
guard let voiceShortcutsFromCenter = voiceShortcutsFromCenter else {
if let error = error {
print("Failed to fetch voice shortcuts with error: \(error.localizedDescription)")
}
return
}
self.voiceShortcuts = voiceShortcutsFromCenter
if let completion = completion {
completion()
}
}
}
}
And then implement in your ViewController
class SiriAddViewController: ViewController {
let voiceShortcutManager = VoiceShortcutsManager.init()
override func viewDidLoad() {
super.viewDidLoad()
contentView.btnTest.addTarget(self, action: #selector(self.testBtn), for: .touchUpInside)
}
...
#objc func testBtn() {
let deviceIntent = DeviceIntent(identifier: smartPlug.deviceID, display: smartPlug.alias)
//is action already has a shortcut, update shortcut else create shortcut
if let shortcut = voiceShortcutManager.voiceShortcut(for: deviceIntent, powerState: .off) {
let editVoiceShortcutViewController = INUIEditVoiceShortcutViewController(voiceShortcut: shortcut)
editVoiceShortcutViewController.delegate = self
present(editVoiceShortcutViewController, animated: true, completion: nil)
} else if let shortcut = INShortcut(intent: intentTurnOff) {
let addVoiceShortcutVC = INUIAddVoiceShortcutViewController(shortcut: shortcut)
addVoiceShortcutVC.delegate = self
present(addVoiceShortcutVC, animated: true, completion: nil)
}
}
}
#available(iOS 12.0, *)
extension SiriAddViewController: INUIAddVoiceShortcutButtonDelegate {
func present(_ addVoiceShortcutViewController: INUIAddVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {
addVoiceShortcutViewController.delegate = self
addVoiceShortcutViewController.modalPresentationStyle = .formSheet
present(addVoiceShortcutViewController, animated: true, completion: nil)
}
func present(_ editVoiceShortcutViewController: INUIEditVoiceShortcutViewController, for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {
editVoiceShortcutViewController.delegate = self
editVoiceShortcutViewController.modalPresentationStyle = .formSheet
present(editVoiceShortcutViewController, animated: true, completion: nil)
}
}
#available(iOS 12.0, *)
extension SiriAddViewController: INUIAddVoiceShortcutViewControllerDelegate {
func addVoiceShortcutViewController(_ controller: INUIAddVoiceShortcutViewController, didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) {
voiceShortcutManager.updateVoiceShortcuts(completion: nil)
controller.dismiss(animated: true, completion: nil)
}
func addVoiceShortcutViewControllerDidCancel(_ controller: INUIAddVoiceShortcutViewController) {
controller.dismiss(animated: true, completion: nil)
}
}
#available(iOS 12.0, *)
extension SiriAddViewController: INUIEditVoiceShortcutViewControllerDelegate {
func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) {
voiceShortcutManager.updateVoiceShortcuts(completion: nil)
controller.dismiss(animated: true, completion: nil)
}
func editVoiceShortcutViewController(_ controller: INUIEditVoiceShortcutViewController, didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) {
voiceShortcutManager.updateVoiceShortcuts(completion: nil)
controller.dismiss(animated: true, completion: nil)
}
func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) {
voiceShortcutManager.updateVoiceShortcuts(completion: nil)
controller.dismiss(animated: true, completion: nil)
}
}
}
This code was inspired/copy from this webpage:
https://www.nodesagency.com/test-drive-a-siri-shortcuts-intro/
My experience with solving this was a little different. Some intents added via the Add to Siri button worked, which adjusted to "Added to Siri", while others didn't. I realised the actions that worked didn't require parameters.
After setting default values for intents that exposed parameters, which are passed into INShortcut (and then assigned to INUIAddVoiceShortcutButton), all buttons updated their state correctly!

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

Resources