I am trying to add the Call Directory Extension to my app to build a simple call blocker, and when I add it to my project and build the app it does not show up in Call Blocking & Identification. I am following along with this tutorial Github CallKit Tutorial
It seems like my extension doesn't exist at all. When reloading my extension I am getting this error
Error reloading extension: The operation couldn’t be completed. (com.apple.CallKit.error.calldirectorymanager error 1.)
Which from I read means the extension does not exist. Here is my reload function
#IBAction func reloadTapped(_ sender: UIButton) {
CXCallDirectoryManager.sharedInstance.reloadExtension(withIdentifier: "com.Unifye.Extension", completionHandler: { (error) in
if let error = error {
print("Error reloading extension: \(error.localizedDescription)")
}
})
}
I am sure I am using the right identifier Image of Identifier
And lastly, my beginRequest function is never being called
class CallDirectoryHandler: CXCallDirectoryProvider {
private let callerData = CallerData()
private func callers(blocked: Bool, includeRemoved: Bool = false, since date: Date? = nil) throws -> [Caller] {
let fetchRequest:NSFetchRequest<Caller> = self.callerData.fetchRequest(blocked: blocked, includeRemoved: includeRemoved, since: date)
let callers = try self.callerData.context.fetch(fetchRequest)
return callers
}
override func beginRequest(with context: CXCallDirectoryExtensionContext) {
context.delegate = self
let defaults = UserDefaults.standard
if let lastUpdate = defaults.object(forKey: "lastUpdate") as? Date, context.isIncremental {
addOrRemoveIncrementalBlockingPhoneNumbers(to: context, since: lastUpdate)
addOrRemoveIncrementalIdentificationPhoneNumbers(to: context, since: lastUpdate)
} else {
addAllBlockingPhoneNumbers(to: context)
addAllIdentificationPhoneNumbers(to: context)
}
defaults.set(Date(), forKey:"lastUpdate")
context.completeRequest()
}
Is there anything else I could be forgetting about or missing? Any help would be greatly appreciated! (I am a big noob so there is a high chance it is something basic and stupid)
Fixed
I simply wiped all my changes, redownloaded the zip, changed the identifiers like Paul mentioned and it started working! Thanks to everyone who helped!
Related
I have a NSManagedObject called Event that is shared between the host app and today extension. (In Target Membership, both the main app and the widget are checked).
The host app and widget have the same App Group identifier and both share Data Model(In Target Membership, both the main app and the widget are checked).
When I launch(run) the widget in Xcode, it shows all of the app events (Event) that are already saved in the host app. However, when I add a new event, it appears in the host app but NOT in today-widget. If I relaunch the widget, all the events are shown including the last event that previously was not.
This is the method that fetches events. It is defined in TodayViewController of the widget.
private func fetchEvents(date: Date) {
let predicates = NSCompoundPredicate(andPredicateWithSubpredicates: [
NSPredicate(format: "date = %#",Date().startOfDay as CVarArg),
NSPredicate(format: "startTime >= %#", Date() as CVarArg)
])
if let ev = try? TPEvent.fetchAll(predicates: predicates, in: persistentManager.context) {
events = ev
}
}
This event is called in viewWillAppear and widgetPerformUpdate.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchEvents(date: Date())
self.tableView.reloadData()
}
func widgetPerformUpdate(completionHandler: (#escaping (NCUpdateResult) -> Void)) {
self.fetchEvents(date: Date() )
self.tableView.reloadData()
completionHandler(NCUpdateResult.newData)
}
persistentManaged.context is PersistentManager.shared.context (see code below).
By the way, both of the methods above are called when I view today-widget. I have a lot of time figuring out this issue but could not do so.
What could be the issue and how to fix it?
Please just comment should you need more info or have any question.
Update
I have a singleton PersistentManager. Use viewContext both in the host app and widget.
public final class PersistentManager {
init() {}
public static let shared = PersistentManager()
public lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentCloudKitContainer(name: "Event")
guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.event.data") else {
fatalError("Shared file container could not be created.")
}
let storeURL = fileContainer.appendingPathComponent("Event.sqlite")
let storeDescription = NSPersistentStoreDescription(url: storeURL)
container.persistentStoreDescriptions = [storeDescription]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
}
return container
}()
public lazy var context = persistentContainer.viewContext
// MARK: - Core Data Saving support
public func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
The issue is that the main app and the app extension work as two different processes on iOS.
CoreData works with NotificationCenter which sends notifications only within the main app process. Thus, you have to send interprocess notification here.
One hidden way to send interprocess notification on iOS is to use KVO on the UserDefaults object.
In NSUserDefaults.h header file Apple states that
/*!
NSUserDefaultsDidChangeNotification is posted whenever any user defaults changed within the current process, but is not posted when ubiquitous defaults change, or when an outside process changes defaults. Using key-value observing to register observers for the specific keys of interest will inform you of all updates, regardless of where they're from.
*/
Having this specified, one can assume that by using KVO on the particular key of UserDefaults, the value change will be propagated from the app to the extension, and vice versa.
So, the approach can be that on each change in the main app you save the current timestamp of the change into the UserDefaults:
/// When the change is made in the main app:
let defaults = UserDefaults(suiteName: "group.<your bundle id>")
defaults["LastChangeTimestamp"] = Date()
defaults.synchronize()
In the app extension:
let defaults = UserDefaults(suiteName: "group.<your bundle id>")
func subscribeForChangesObservation() {
defaults?.addObserver(self, forKeyPath: "LastChangeTimestamp", options: [.new, .initial], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// Process your changes here.
}
deinit {
defaults?.removeObserver(self, forKeyPath: "LastChangeTimestamp")
}
I'm trying out the Snap Kit Framework from Snapchat using their sample code but when running the code nothing happens and no error is thrown.
import SCSDKCreativeKit
let snap = SCSDKNoSnapContent()
snap.sticker = SCSDKSnapSticker(stickerImage: UIImage(named: "story_share.png")!)
snap.caption = "Snap on Snapchat!"
snap.attachmentUrl = profileURL.absoluteString
SCSDKSnapAPI().startSending(snap) { (error: Error?) in
print(error)
}
The logs don't indicate an error either:
myapp[853:121028] [SnapKit] Dynamic config update status: success
Things I've tried or insured are correct:
My app is registered online with the correct bundle id, my test snapchat username and the creative kit is selected
the development key is in the Info.plist file as SCSDKClientId
snapchat is in the list of LSApplicationQueriesSchemes
the code runs on the main thread
using their userInteractionEnabled trick doesn't make a difference
I'm using carthage and embed the core and creative framework as usual
What could be the reason why this doesn't work?
I found the disappointing answer that it only works using the deprecated method call:
SCSDKSnapAPI(content: snap).startSnapping() { (error: Error?) in
...
}
I hope that Snapchat fixes their terrible framework in the future. If anyone knows an actual solution, please let me know and I'll accept your answer.
SCSDKSnapAPI() needs to be defined as a class variable. Your code doesn't show the implementation, but I had the same issue when I instantiated SCSDKSnapAPI() in a func() instead of at the Class level.
Here's an implementation that I found which works as intended:
class CreateViewController: UIViewController {
fileprivate lazy var snapAPI = {
return SCSDKSnapAPI()
}()
#IBAction func sendSnap2(_ sender: Any) {
//self.snapAPI = SCSDKSnapAPI() <<DO NOT DO THIS>>
let snap2 = SCSDKNoSnapContent()
view.isUserInteractionEnabled = false
snapAPI.startSending(snap) { [weak self] (error: Error?) in
self?.view.isUserInteractionEnabled = true
if (error != nil) {
print("Error Unknown", error!)
}
else {
print("no error")
}
}
}
I am trying to add(move forward) 10 second song duration or minus(move backward) 10 second in Spotify player but i am really confused how to add or minus.
When i m trying to use this code the song is not changed duration
// forward button action
#IBAction func moveFrdBtnAction(_ sender: Any) {
SpotifyManager.shared.audioStreaming(SpotifyManager.shared.player, didSeekToPosition: TimeInterval(10))
}
// spotify delegate method seekToPosition
func audioStreaming(_ audioStreaming: SPTAudioStreamingController!, didSeekToPosition position: TimeInterval) {
player?.seek(to: position, callback: { (error) in
let songDuration = audioStreaming.metadata.currentTrack?.duration as Any as! Double
self.delegate?.getSongTime(timeCount: Int(songDuration)+1)
})
}
We are making a music application using the same SDK in both the platforms (Android & iOS), the seekToPosition method of the Spotify SDK is working correctly in the Android version, however, it is not working in the iOS one.The delegate method calls itself but the music stops.
Can you kindly let us know why this scenario is happening, and what should we do to run it on the iOS devices as well.
Can someone please explain to me how to solve this , i've tried to solve this but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
I don't use this API so my answer will be based your code and Spotify's reference documentation.
I think there are a few things wrong with your flow:
As Robert Dresler commented, you should (approximately) never call a delegate directly, a delegate calls you.
I'm pretty sure your action currently results in jumping to exactly 10 seconds, not by 10 seconds.
(As an aside, I'd suggest changing the name of your function moveFrdBtnAction to at least add more vowels)
Anyway, here's my best guess at what you want:
// forward button action
#IBAction func moveForwardButtonAction(_ sender: Any) {
skipAudio(by: 10)
}
#IBAction func moveBackButtonAction(_ sender: Any) {
skipAudio(by: -10)
}
func skipAudio(by interval: TimeInterval) {
if let player = player {
let position = player.playbackState.position // The documentation alludes to milliseconds but examples don't.
player.seek(to: position + interval, callback: { (error) in
// Handle the error (if any)
})
}
}
// spotify delegate method seekToPosition
func audioStreaming(_ audioStreaming: SPTAudioStreamingController!, didSeekToPosition position: TimeInterval) {
// Update your UI
}
Note that I have not handled seeking before the start of the track, nor after the end which could happen with a simple position + interval. The API may handle this for you, or not.
You could take a look at the examples here: spotify/ios-sdk. In the NowPlayingView example they use the 'seekForward15Seconds', maybe you could use that? If you still need 10s I have added a function below. The position is in milliseconds.
"position: The position to seek to in milliseconds"
docs
ViewController.swift
var appRemote: SPTAppRemote {
get {
return AppDelegate.sharedInstance.appRemote
}
}
fileprivate func seekForward15Seconds() {
appRemote.playerAPI?.seekForward15Seconds(defaultCallback)
}
fileprivate seekBackward15Seconds() {
appRemote.playerAPI?.seekBackward15Seconds(defaultCallback)
}
// TODO: Or you could try this function
func seekForward(seconds: Int){
appRemote.playerAPI?.getPlayerState({ (result, error) in
// playback position in milliseconds
let current_position = self.playerState?.playbackPosition
let seconds_in_milliseconds = seconds * 1000
self.appRemote.playerAPI?.seek(toPosition: current_position + seconds_in_milliseconds, callback: { (result, error) in
guard error == nil else {
print(error)
return
}
})
})
}
var defaultCallback: SPTAppRemoteCallback {
get {
return {[weak self] _, error in
if let error = error {
self?.displayError(error as NSError)
}
}
}
}
AppDelegate.swift
lazy var appRemote: SPTAppRemote = {
let configuration = SPTConfiguration(clientID: self.clientIdentifier, redirectURL: self.redirectUri)
let appRemote = SPTAppRemote(configuration: configuration, logLevel: .debug)
appRemote.connectionParameters.accessToken = self.accessToken
appRemote.delegate = self
return appRemote
}()
class var sharedInstance: AppDelegate {
get {
return UIApplication.shared.delegate as! AppDelegate
}
}
Edit1:
For this to work you need to follow the Prepare Your Environment:
Add the SpotifyiOS.framework to your Xcode project
Hope it helps!
Since iOS 11 I have encountered the following error every time I am creating a new document using UIDocument API:
[ERROR] Could not get attribute values for item /var/mobile/Containers/Data/Application/XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX/Documents/myDoc-XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX.myFile (n).
Error: Error Domain=NSFileProviderInternalErrorDomain Code=1
"The reader is not permitted to access the URL."
UserInfo={NSLocalizedDescription=The reader is not permitted to access the URL.}
Unlike similar questions (1, 2, 3) on SO on this, I am not using UIDocumentBrowserViewController. I am simply creating a UIDocument and call save() to the Documents directory myself. The closest question I found uses UIManagedDocument. However, in my case, the file is still created and written successfully despite the error message.
Here's the gist of my save routine:
#IBAction func createDoc(_ sender: Any) {
let uuid = UUID().uuidString
let doc = Document(baseName: "myDoc-\(uuid)")
doc.save(to: doc.fileURL, for: .forCreating) { (completed) in
if (completed) {
doc.close(completionHandler: nil)
self.verifyNumberOfFiles()
}
}
}
My UIDocument subclass is also almost blank for simplicity of this question:
class Document: UIDocument {
let fileExtension = "myFile"
override init(fileURL url: URL) {
super.init(fileURL: url)
}
/// Convenience method for `init(fileURL:)`
convenience init(baseName: String) {
self.init(fileURL: documentsDirectory.appendingPathComponent(baseName).appendingPathExtension(Document.fileExtension))
}
override func contents(forType typeName: String) throws -> Any {
return NSData()
}
override func load(fromContents contents: Any, ofType typeName: String?) throws {
}
}
I'm always writing to Documents folder, and my lookup routine can verify that my files are successfully created:
public var documentsDirectory: URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!
}
func loadFileURLs(from dirURL: URL) -> [URL]? {
return try? FileManager().contentsOfDirectory(at: dirURL, includingPropertiesForKeys: nil)
}
What I have discovered so far:
The error appears even when I set up my UTI already. See this and this.
I can verify that my UTI works when I send a "myFile" to my device over AirDrop and it correctly triggers my app to open.
The error appears on iOS 11 only. The same code doesn't reproduce the error on iOS 10, like in the question above.
I tried adding UISupportsDocumentBrowser key although I'm not using the browser but it's not dissolve the error.
What is happening? Is this just a "noise" error message on iOS 11?
Here's 🔨 my GitHub code online if anyone is interested.
A workaround for this is to create the file by saving its data to the disk, and then open it as you would with an existing file.
Your createDoc(_:) method would then like this:
#IBAction func createDoc(_ sender: Any) {
let uuid = UUID().uuidString
let baseName = "myDoc-\(uuid)"
let url = documentsDirectory
.appendingPathComponent(baseName)
.appendingPathExtension(Document.fileExtension)
do {
let emptyFileData = Data()
try emptyFileData.write(to: url)
let document = Document(fileURL: url)
document.open() { completed in
guard completed else {
// handle error
return
}
doc.close(completionHandler: nil)
self.verifyNumberOfFiles()
}
} catch {
// handle error
}
}
In Xcode 9.3 it is possible to specify a new item in info.plist:
Supports Document Browser (YES)
This enables access to the application's documents directory (for example, /var/mobile/Containers/Data/Application/3C21358B-9E7F-4AA8-85A6-A8B901E028F5/Documents on a device). Apple Developer doc here.
I'm trying to open, modify, and save a file in iCloud Drive using UIDocument. When I call save(to:for:completionHandler:) with the file location and using .forOverwriting for the UIDocumentSaveOperation, it completes with a status of success = true. However, the iCloud file (as seen in both desktop and iOS file browser) does not update, and when reopening the file, the changes are not shown. I've verified that contents(forType:) returns the correct (modified) file contents when saving.
(Note: I've already looked at this question, but it wasn't very helpful 😕)
Here are the relevant sections of code:
MainViewController.swift:
var saveFile: SBDocument?
#IBAction func bbiOpen_pressed(_ sender: UIBarButtonItem) {
if saveFile == nil {
let importMenu = UIDocumentMenuViewController(documentTypes: self.UTIs, in: .import)
importMenu.delegate = self
importMenu.popoverPresentationController?.barButtonItem = bbiOpen
self.present(importMenu, animated: true, completion: nil)
} else {
willClose()
}
}
func willClose(_ action: UIAlertAction?) {
if saveFile!.hasUnsavedChanges {
dlgYesNoCancel(self, title: "Save Changes?", message: "Would you like to save the changes to your document before closing?", onYes: doSaveAndClose, onNo: doClose, onCancel: nil)
} else {
doSaveAndClose(action)
}
}
func doSaveAndClose(_ action: UIAlertAction?) {
saveFile?.save(to: saveFileURL!, for: .forOverwriting, completionHandler: { Void in
self.saveFile?.close(completionHandler: self.didClose)
})
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
saveFile = SBDocument(fileURL: url)
saveFile!.open(completionHandler: { success in self.finishOpen(didCompleteSuccessfully: success) })
}
func finishOpen(didCompleteSuccessfully result: Bool) {
if result {
print(saveFile!.localizedName)
saveFileURL = saveFile!.fileURL
saveFileName = saveFile!.localizedName
self.navTitleBar.prompt = saveFileName
bbiOpen.title = NSLocalizedString("titleClose", comment: "Close")
bbiOpen.style = .plain
} else {
saveFile = nil
}
}
#IBAction func bbiSave_pressed(_ sender: UIBarButtonItem) {
self.saveFile!.save(to: self.saveFileURL!, for: .forOverwriting, completionHandler: self.didSave)
}
func didSave(_ success: Bool) {
guard success else {
print("Error saving soundboard file to \(String(describing: saveFileURL))")
return
}
print("File saved successfully")
}
SBDocument.swift:
class SBDocument: UIDocument {
override var fileType: String? { get { return "com.whitehatenterprises.SoundBoardFX.sbd" } }
override var savingFileType: String? { get { return "com.whitehatenterprises.SoundBoardFX.sbd" } }
override init(fileURL url: URL) {
super.init(fileURL: url)
}
override func contents(forType typeName: String) throws -> Any {
let arr = NSArray(array: SoundEffects)
let data: NSData = NSKeyedArchiver.archivedData(withRootObject: arr) as NSData
return data
}
}
Update:
I really need help with this, and I've tried everything I can think of to fix this. Any assistance you could give me would be greatly appreciated.
The way the initial file generation works for me is:
let doc = YourUIDocumentClass(fileURL: fileURL)
doc.save(to: fileURL, for: .forCreating) { success in
...
}
Then modify the file and then do:
doc.save(to: fileURL, for: .forOverwriting) { success in
...
}
when done. And subsequent accesses to the file are done by:
doc.open() { success in
...
}
doc.close() { success in
...
}
You might also need to do a:
doc.updateChangeCount(.done)
while the file is open to tell the document there are unsaved changes. Just setting this will cause a save after a few seconds. You don't even need the close to do that.
The ... means that you either have to nest all these or make sure there is enough time between them so they are completed.
In addition to the above answers, another cause of this can be that there's an error during the save process unrelated to contents(forType:).
For example, if you implement fileAttributesToWrite(to:for:) and throw an error, then this can cause a UIDocumentState.savingError even though contents(forType:) returns the correct data.
So according to
https://developer.apple.com/reference/uikit/uidocument
It looks like the save function isn't actually for saving a document. My understanding from reading it is that save is only for creating a new document. I understand that you are using the .forOverwriting to just save over it but there may be something in iCloud that wont let the complete overwrite happen.
In your doSaveAndClose method try calling
self.saveFile?.close(completionHandler: self.didClose)
by itself. You may have to do some type of if query where you check if the file exist. If it doesn't then call the .save(), else call the .close function. It seems that no matter what when the document it closed it saves changes.