cannot access app group folder in intent handler - swift

I have the following code in an intent handler for a widget I am creating:
extension FileManager {
static var appGroupURLAsText: String {
`default`.containerURL(forSecurityApplicationGroupIdentifier: "group.com.emojiApp.EmojiWidget")!.absoluteString
}
}
extension IntentHandler: SelectEmojiIntentHandling {
func provideEmojiOptionsCollection(
for intent: SelectEmojiIntent,
with completion: #escaping (INObjectCollection<EmojiINO>?, Error?) -> Void
) {
print("stuff happening in intent handler")
print(FileManager.appGroupURLAsText)
let fm = FileManager.default
print("after declaring fm")
var items = [String]()
do {
print("inside of do")
items = try fm.contentsOfDirectory(atPath: FileManager.appGroupURLAsText)
print("after declaring items")
} catch {
print("Unexpected error: \(error).")
// failed to read directory – bad permissions, perhaps?
}
//I don't believe the code below is relevant to the error but I'm including it here in case
var emojiItems = [EmojiINO]()
for item in items {
let finalThing = EmojiINO(identifier: item, display: item)
emojiItems.append(finalThing)
}
completion(INObjectCollection(items: emojiItems), nil)
}
}
When I run the code in an iphone 13 pro simulator with ios 15.5, I get the following output:
stuff happening in intent handler
file:///Users/myname/Library/Developer/CoreSimulator/Devices/86836E5F-4CA8-4288-899F-0CA595F18525/data/Containers/Shared/AppGroup/4AD329B4-32C5-40EE-BEBF-BFC2BDDB34F9/
after declaring fm
inside of do
Unexpected error: Error Domain=NSCocoaErrorDomain Code=260 "The folder “4AD329B4-32C5-40EE-BEBF-BFC2BDDB34F9” doesn’t exist." UserInfo={NSUserStringVariant=(
Folder
),
It never gets to the after declaring items print statement so I know the issue is something with contentsOfDirectory . I know the folder is there though because appGroupUrl is returning a valid folder and I checked in my finder and the folder is there. How do I fix this?

So it turns out I should have used path instead of absoluteString.
So just change line 3 to this:
`default`.containerURL(forSecurityApplicationGroupIdentifier: "group.com.emojiApp.EmojiWidget")!.path

Related

Got an error when dragging files using NSEvent. (macOS)

I wanna drag files to my window and then perform actions.
I tried to use snippets below provided in this answer to distinguish whether you're dragging a file or a window.
// In my window controller
class MyWindowController: NSWindowController {
init() {
// Some initialization steps below are omitted
let win = NSWindow(...)
super.init(window: win)
let contentView = DropView(frame: win.frame)
win.contentView?.addSubview(contentView)
registerGlobalMouseEvent()
}
func registerGlobalMouseEvent() {
self.window?.acceptsMouseMovedEvents = true
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
// Codes below will cause errors
let pasteBoard = NSPasteboard(name: .drag)
guard let fileNames = pasteBoard.propertyList(forType: .init(rawValue: "NSFilenamesPboardType")) as? NSArray else { return }
let changeCount = pasteBoard.changeCount
if fileNames.count > 0 && lastChangeCount != changeCount {
lastChangeCount = changeCount
// My actions when dragging
}
})
}
}
Then I ran my codes and started dragging, I got three errors:
[sandbox] Failed to get a sandbox extension
[Framework] Failed to issue sandbox extension for /Users/roy/Downloads/test.txt with error 1
[default] Failed to issue sandbox token for URL: 'file:///Users/roy/Downloads/test.txt' with error: 'Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedDescription=Cannot issue a sandbox extension for file "/Users/roy/Downloads/test.txt": Operation not permitted}'
 
But when I just do
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
// My actions
})
, then everything went fine.
 
The first error seems harmless since it didn't prevent my app from running.
The second and the third ones are deadly and directly caused my app to crash.
I wonder if there are any problems in his code? Any useful thoughts would be great! :)
 
You need to know about Bookmarks and Security Scoped URLs when working with sandbox . A dragged URL gives your app process permission just once to read or read/write a “user selected file” depending on how you configure entitlements.
You can save a bookmark (blob of data) to keep access over subsequent sessions as long as the file isn’t updated by another process at which point the bookmark becomes stale and you will need to encourage the user to select the file again.
Handing a URL to another process across an XPC boundary like sharing requires that you own the file so may involve a copy to your sandbox cache.
e.g:
let dragurl = url_of_dragged_file //at this point you have at-least read access
let cachepath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last!
let cachedir = URL(fileURLWithPath: cachepath)
let cacheurl = cachedir
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension(dragurl.pathExtension)
try FileManager.default.copyItem(at: dragurl, to: cacheurl)
At this point you have a copy in your local sandbox cache that can be handed off to a share sheet.
So I finally got a solution for this. :)
It appears that it indeed have something to do with the snippets I mentioned above, and here's the correction:
NSEvent.addGlobalMonitorForEvents(matching: .leftMouseDragged, handler: { [self] event in
let pasteboard = NSPasteboard(name: .drag)
let changeCount = pasteboard.changeCount
if lastChangeCount != changeCount {
lastChangeCount = changeCount
if pasteboard.canReadObject(forClasses: [NSURL.self], options: [:]) {
/// actions
}
}
})
In this way, I got no errors and my codes run perfectly!

Xcode Trace/BPT trap: 5

Situation
Hi there,
I am developing an iOS app and while building my project I run into the following error message:
Error: Trace/BPT trap: 5
I didn't find anything online to fix this problem, so I wanted to know, if anyone here might be able to help.
I also had issues with Cocoapods and my Silicon Mac, so I want to list my steps I've tried fixing:
Setup
M1 MacBook Pro, macOS 11.1
XCode Version 12.4
Cocoapods with Pods for Firebase Swift, Auth, Firestore and Storage
Steps I tried fixing
cmd + shift + k for cleaning the build folder
closing XCode and opening Terminal using Rosetta
delete ~/Library/Developer/Xcode/Derived Data - Folder
pod deintegrate in project directory
delete Podfile.lock, app.xcworkspace, Pods directory
pod install
in app and pods projects build settings setting Excluded Architectures for any iOS Simulator SDK to arm64
setting Build Active Architecture Only to yes
convert Pods Project to Swift 5
build Pods Project
build app project
And then the following error occurs:
Log
Log enty from Merge swiftmodule (x86_64):
https://pastebin.com/MiSKGxB7
(Log way to long, exceeds character limit).
Code
As the error somewhere tells, it occured while trying to serialize the class BaseViewModel, here's the code from the Base.swift file I wrote containing that class:
import SwiftUI
import Firebase
import FirebaseFirestore
import Combine
protocol BaseModel: Identifiable, Codable {
var id: String? { get set }
var collection: String { get }
init()
}
class BaseViewModel<T: BaseModel>: ObservableObject, Identifiable, Equatable {
#Published var model: T
var id: String {
didSet {
self.model.id = id
}
}
var cancellables = [AnyCancellable]()
private var db = Firestore.firestore()
required init(){
let model = T.init()
self.model = model
self.id = model.id ?? UUID().uuidString
}
required init(id: String) {
var model = T.init()
model.id = id
self.model = model
self.id = id
}
init(model: T) {
self.model = model
self.id = model.id ?? UUID().uuidString
}
static func ==(lhs: BaseViewModel<T>, rhs: BaseViewModel<T>) -> Bool {
lhs.model.id == rhs.model.id
}
func load(completion: #escaping (Bool) -> Void = {finished in}){
if let id = model.id {
self.id = id
db.collection(model.collection).document(id).getDocument { docSnapshot, error in
guard let doc = docSnapshot else {
print("Error fetching document: \(error!)")
return
}
do {
guard let data = try doc.data(as: T.self) else {
print("Document empty \(type(of: self.model)) with id \(id)")
return
}
self.model = data
self.loadSubData {finished in
if finished{
completion(true)
}
}
} catch {
print(error.localizedDescription)
}
}
}
}
func loadSubData(completion: #escaping(Bool) -> Void = {finished in}) {
fatalError("Must be overridden!")
}
func loadDataByIDs<T, S>(from list: [String], appender: #escaping (T) -> Void) where T: BaseViewModel<S>, S: BaseModel {
for id in list {
let viewModel = T.init(id: id)
viewModel.load{finished in
if finished {
appender(viewModel)
}
}
}
}
func save(){
do {
let _ = try db.collection(model.collection).addDocument(from: model)
} catch {
print(error)
}
}
func update(){
if let id = model.id {
do {
try db.collection(model.collection).document(id).setData(from: model)
} catch {
print(error.localizedDescription)
}
}
}
func delete(){
if let id = model.id {
db.collection(model.collection).document(id).delete() { error in
if let error = error {
print(error.localizedDescription)
}
}
}
}
}
I had the same problem, I am solved it after I updated Quick/Nimble.
I guess some pod project with x86 files meed to update to support M1
Well for the record, anybody who is experiencing these odd bugs on M1 must read exactly inside the excode compilation error.
If they are saying a specific class it means xcode can't compile your code and you should just remove the code and try to compile line by line.
I know that's very strange, looks like programming PHP and refreshing a webpage but I'm sure this type of bug can be related to the platform migration.
In my situation, I had a class that was OK, I started refactoring and instead of xcode showing me the compilation errors, it gave this cryptic BPT5, at reading exactly the description inside of the IDE I could find that my class was the root cause.
Just remove the code all over you changed and try to compile it again...
Sorry for the late update on that. But in my case it was either CocoaPods in general or the Firebase Pods, which were not compatible with Apple Silicon at that time.
I was just using Swift Package Manager and with that, it worked.
I do not know though, if the problem still exists, because I didn't build another app on the M1 MacBook.
I run into this issue after I accidently extract a view as variable without awareness. You shall check your recently committed code to figure it out.

Some files in Documents subfolders cannot be access while device is locked / screen is off

Some files in Documents subfolders cannot be accessed while the device is locked.
AVAudioFile(forReading: currentPlaylistURLs[index!])
The above line will throw an error when the device is locked or screen is off and the URL is to some file in some subfolder in the Documents folder. Not all subfolders cause an error (Why??). When a device is unlocked there is no error. I noticed this behavior in iOS 13.2.3 so it was working fine prior to this version.
Error is:
[AVAudioFile.mm:134:AVAudioFileImpl: (ExtAudioFileOpenURL((CFURLRef)fileURL, &_extAudioFile)): error -54
Error Domain=com.apple.coreaudio.avfaudio Code=-54 "(null)" UserInfo={failed call=ExtAudioFileOpenURL((CFURLRef)fileURL, &_extAudioFile)}
Has anyone experience this?
You need to set your file's file protection key permission to .none and its parent folder as well:
extension URL {
var parentDirectory: URL? { try? resourceValues(forKeys: [.parentDirectoryURLKey]).parentDirectory }
var fileProtection: URLFileProtection? { try? resourceValues(forKeys: [.fileProtectionKey]).fileProtection }
func disableFileProtection() throws { try (self as NSURL).setResourceValue(URLFileProtection.none, forKey: .fileProtectionKey) }
}
let fileURL = URL.init(...)
if let parentDirectory = fileURL.parentDirectory {
do {
try parentDirectory.disableFileProtection()
try fileURL.disableFileProtection()
}
catch { print(error) }
}

Firebase Storage Upload Error: FIRStorageErrorDomain Code=-13000

I am trying to upload an image to Firebase and I actually succeeded several times yesterday, but today I'm getting this error:
Optional
- Some : Error Domain=FIRStorageErrorDomain Code=-13000 "An unknown error occurred, please check the server response." UserInfo={ResponseErrorDomain=FIRStorageErrorDomain, object=ProfileImages/ascascasc ascas.jpg, error_name=ERROR_USER_NOT_FOUND, bucket=ichallenge-c52ae.appspot.com, ResponseErrorCode=-13020, NSLocalizedDescription=An unknown error occurred, please check the server response.}
I want to reiterate: nothing was changed in the code between yesterday and today, it just stopped working. This is the code, I've highlighted the line where it happens with a comment:
#IBAction func signUpButtonPressed(sender: AnyObject)
{
// If textfields have more than 3 characters
if firstNameTextField.text!.characters.count > 3 && passwordTextField.text!.characters.count > 3 && emailTextField.text!.containsString("#")
{
createUser()
//Goes to Main Storyboard
parseOutdated.signUpInBackgroundWithBlock { (success: Bool, error: NSError?) in
NSNotificationCenter.defaultCenter().postNotificationName("Login", object: nil)
}
}
else
{
firstNameShake.shakeAnimation()
lastNameShake.shakeAnimation()
passwordShake.shakeAnimation()
emailShake.shakeAnimation()
}
}
func createUser()
{
//Unwrap optionals before pushing to Firebase Database
let name: String = "\(self.firstNameTextField.text!) \(self.lastNameTextField.text!)"
storeProfileImage(name)
}
func storeProfileImage(name: String)
{
let profileImageData = UIImageJPEGRepresentation(self.profileImageView.image!, 1.0)
// Create a reference to the file you want to upload
let profileImageRef = storageRef.child("ProfileImages/\(name).jpg")
// Upload the file to the path defined above
profileImageRef.putData(profileImageData!, metadata: nil) { metadata, error in
if (error != nil) //ERROR HAPPENS HERE
{
print("Image not stored: ", error?.localizedDescription)
}
else
{
//Stores the profile image URL and sends it to the next function
let downloadURL = metadata!.downloadURL()
self.storeUserData(name, profileImageURL: downloadURL!)
}
}
}
Here's a screenshot of the breakpoint in XCode:
Any help provided would be deeply appreciated.
If you have been testing with the same user witout signing out on your app, your authentication token might have expired. Try signing out firs

swift OSX: serially generating files using GCD

I am trying to generate .aiff files using NSSpeechSynthesizer.startSpeakingString() and am using GCd using a serial queue as NSSpeechSynthesizer takes in a string and creates an aiff file at a specified NSURL address. I used the standard for loop method for a list of strings in a [String:[String]] but this creates some files which have 0 bytes.
Here is the function to generate the speech:
func createSpeech(type: String, name: String) {
if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){
do{
try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes: nil)
let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff")
print("Attempting to save speech \(name).aiff")
self.synth.startSpeakingString(name, toURL: URL)
}catch{
print("error occured")
}
}
}
And here is the function that traverses the dictionary to create the files:
for key in self.nodeLibrary.keys{
dispatch_sync(GlobalBackgroundQueue){
let type = self.nodeLibrary[key]?.0
let name = key.componentsSeparatedByString("_")[0]
if !speechCheck.contains(name){
mixer.createSpeech(type!, name: name)
}
}
}
The globalBackgroundQueue is an alias to the GCD queue call _T for readability.
The routine runs fine, creates folders and subfolders as required by another external function then synthesizes the speech but in my case I always get one or some which don't load properly, giving 0 bytes or a too small number of bytes which makes the file unuseable.
I read the following post and have been using these GCD methods for a while but I'm not sure where I'm wrong here:
http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1
Any help greatly appreciated as usual
edit: Updated with completion closure and found possibly a bug
I have created a closure function as below and use it in another helper method which checks for any errors such as sourceFile.length being 0 once loaded. However, all files exhibit a 0 length which is not possible as I checked each file's audio properties using finder's property command+i.
func synthesise(type: String, name: String, completion: (success: Bool)->()) {
if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){
do{
try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes: nil)
let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff")
let success = self.synth.startSpeakingString(name, toURL: URL)
completion(success: success)
}catch{
print("error occured")
}
}
}
func loadSpeech(type: String, name: String){
synthesise(type, name: name, completion: {(success: Bool)->Void in
if success{
print("File \(name) created successfully with return \(self.synthSuccess), checking file integrity")
let URL = NSURL(fileURLWithPath: "\(self.dataPath)\(type)/\(name)/\(name).aiff")
do{
let source = try AVAudioFile(forReading: URL)
print("File has length: \(source.)")
}catch{
print("error loading file")
}
}else{
print("creation unsuccessful, trying again")
self.loadSpeech(type, name: name)
}
})
}
The files are generated with their folders and both the method startSpeakingString->Bool and the delegate function I have in my class which updates the synthSuccess property show true. So I load an AVAudioFile to check its length. All file lengths are 0. Which they are not except for one.
When I say bug, this is from another part of the app where I load an AVAudioEngine and start loading buffers with the frameCount argument set to sourceAudioFile.length which gives a diagnostic error but this is out of context right now.
startSpeakingString(_:toURL:) will start an asynchronous task in the background. Effectively, your code starts a number of asynchronous tasks that run concurrently. This may be the cause of the problem that you experience.
A solution would need to ensure that only one task is active at a time.
The problem with startSpeakingString(_:toURL:) is, that it starts an asynchronous task - but the function itself provides no means to get notified when this task is finished.
However, there's a delegate which you need to setup in order to be notified.
So, your solution will require to define a NSSpeechSynthesizerDelegate.
You may want to create your own helper class that exposes an asynchronous function which has a completion handler:
func exportSpeakingString(string: String, url: NSURL,
completion: (NSURL?, ErrorType?) -> ())
Internally, the class creates an instance of NSSpeechSynthesizer and NSSpeechSynthesizerDelegate and implements the delegate methods accordingly.
To complete the challenge, you need to search for an approach to run several asynchronous functions sequentially. There are already solutions on SO.
Edit:
I setup my own project to either confirm or neglect a possible issue in the NSSpeechSynthesizer system framework. So far, may own tests confirm that NSSpeechSynthesizer works as expected.
However, there are few subtleties worth mentioning:
Ensure you create a valid file URL which you pass as an argument to parameter URL in method startSpeakingString(:toURL:).
Ensure you choose an extension for the output file which is known by NSSpeechSynthesizer and the system frameworks playing this file, for example .aiff. Unfortunately, the documentation is quite lacking here - so I had to trial and error. The list of supported audio file formats by QuickTime may help here. Still, I have no idea how NSSpeechSynthesizer selects the output format.
The following two classes compose a simple easy to use library:
import Foundation
import AppKit
enum SpeechSynthesizerError: ErrorType {
case ErrorActive
case ErrorURL(message: String)
case ErrorUnknown
}
internal class InternalSpeechSynthesizer: NSObject, NSSpeechSynthesizerDelegate {
typealias CompletionFunc = (NSURL?, ErrorType?) -> ()
private let synthesizer = NSSpeechSynthesizer(voice: nil)!
private var _completion: CompletionFunc?
private var _url: NSURL?
override init() {
super.init()
synthesizer.delegate = self
}
// CAUTION: This call is not thread-safe! Ensure that multiple method invocations
// will be called from the same thread!
// Only _one_ task can be active at a time.
internal func synthesize(input: String, output: NSURL, completion: CompletionFunc) {
guard _completion == nil else {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
completion(nil, SpeechSynthesizerError.ErrorActive)
}
return
}
guard output.path != nil else {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
completion(nil, SpeechSynthesizerError.ErrorURL(message: "The URL must be a valid file URL."))
}
return
}
_completion = completion
_url = output
if !synthesizer.startSpeakingString(input, toURL: output) {
fatalError("Could not start speeaking")
}
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
willSpeakWord characterRange: NSRange,
ofString string: String)
{
NSLog("willSpeakWord")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
willSpeakPhoneme phonemeOpcode: Int16)
{
NSLog("willSpeakPhoneme")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
didEncounterErrorAtIndex characterIndex: Int,
ofString string: String,
message: String)
{
NSLog("didEncounterErrorAtIndex")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
didFinishSpeaking finishedSpeaking: Bool)
{
assert(self._url != nil)
assert(self._url!.path != nil)
assert(self._completion != nil)
var error: ErrorType?
if !finishedSpeaking {
do {
error = try self.synthesizer.objectForProperty(NSSpeechErrorsProperty) as? NSError
} catch let err {
error = err
}
}
let url: NSURL? = NSFileManager.defaultManager().fileExistsAtPath(self._url!.path!) ? self._url : nil
let completion = self._completion!
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
if url == nil && error == nil {
error = SpeechSynthesizerError.ErrorUnknown
}
completion(url, error)
}
_completion = nil
_url = nil
}
}
public struct SpeechSynthesizer {
public init() {}
private let _synthesizer = InternalSpeechSynthesizer()
public func synthesize(input: String, output: NSURL, completion: (NSURL?, ErrorType?) -> ()) {
_synthesizer.synthesize(input, output: output) { (url, error) in
completion(url, error)
}
}
}
You can use it as shown below:
func testExample() {
let expect = self.expectationWithDescription("future should be fulfilled")
let synth = SpeechSynthesizer()
let url = NSURL(fileURLWithPath: "/Users/me/Documents/speech.aiff")
synth.synthesize("Hello World!", output: url) { (url, error) in
if let url = url {
print("URL: \(url)")
}
if let error = error {
print("Error: \(error)")
}
expect.fulfill()
}
self.waitForExpectationsWithTimeout(1000, handler: nil)
// Test: output file should exist.
}
In the code above, check the result of the call to synth.startSpeakingString(name, toURL: URL), which can return false if the synthesiser could not start speaking. If it fails, find out why, or just retry it.
Plus, add [NSSpeechSynthesiserDelegate][1], and look for the speechSynthesizer:didFinishSpeaking: callbacks there. When the synthesiser thinks it has finished speaking, check the file size. If it is zero, retry the operation.