Bundle.main does not find file in folder structure - ios

Overview
I want to find my core data model as part of the initialisation of NSPersistentContainers (local and cloud) to make sure that they share the same model.
Code
The code I planned to use to achieve this:
static let model: NSManagedObjectModel = {
guard let url = Bundle.main.url(forResource: "BookKeeping", withExtension: "momd") else {
fatalError("Failed to locate model file.")
}
guard let managedObjectModel = NSManagedObjectModel(contentsOf: url) else {
fatalError("Failed to load model file.")
}
return managedObjectModel
}()
However this does not do trick, as it fails with the first fatalError "Failed to locate model file".
I presume it has something to do with my folder structure, which is the following:
Folder Structure
In "Persistence" the initialisation takes places, "BookKeeping" is the name of the model file.
Approaches so far
I tried to add the file as Bundle Resource in the Build Phase, I checked that the target membership of the file is right, FileManager is in my opinion not the right approach to get it done and I looked into Bundle identifiers, however it seems to me they are used in different use cases.
If you could put me in the right direction I'd be happy.
Any help is greatly appreciated! Thanks!

Related

Beginner question: Reading a resource text file on MacOS using Swift

I'm new to this and apologize if it's basic. I have tried to research and I either get iOS posts, or old Xcode posts - none that are helping me with a basic need.
I want to have a bundled text file in my Swift/SwiftUI/MacOS app. It's just a text file, say sample.txt
I want to read it and do something with it.
I did the following:
Created a folder called "Resources" in my project
Added the text file - sample.txt
In my SwiftUI code, I did the following
if let filepath = Bundle.main.path(forResource: "sample", ofType: "txt") {
do {
print("...getting resource")
let contents = try String(contentsOfFile: filepath)
print(contents)
} catch {
// contents could not be loaded
print(error)
}
} else {
print("no such file!")
}
And I get no such file
I changed the forResource: "sample" to forResource: "Resource/sample" and that didn't help either.
What should I be doing?
Thank you
It's not necessary to create an extra folder Resources. Just add the file to the project. It will be moved into the Resources folder of the app while being built.
And make sure that the Target Membership checkbox of the file is checked.

Get Reference to SceneKit Catalog Swift

I must be missing something simple - I'm trying to iterate through the
files in an .scnassets folder but can't seem to get a reference to the
folder/catalog.
Here's the organizer:
Models.scnassets contains one file. I tried this:
let fileManager = FileManager.default
let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
let docsURL = urls.first
let assetFolderPath = docsURL?.appendingPathComponent("Models.scnassets").path
And I tried this and several other variants:
let modelPathString = "ARAds/Models.scnassets"
Then I attempted to count the files with both of the above paths:
do {
let modelPathDirectoryFiles = try fileManager.contentsOfDirectory(atPath: assetFolderPath!)
//or modelPathString
print(modelPathDirectoryFiles.count)
} catch {
print("error getting list of files")
}
I get the error message in all cases.
I assume there must be some special way to get a reference to a SceneKit
Catalog but I have not been able to find that in the Apple docs nor SO.
Any guidance would be appreciated. iOS 11.4 Xcode 10.0
Looking at your code, the first thing I see, is that you are referencing the Documents Directory which isn't actually where your folder is.
The Documents Directory is a folder on your actual device where you might save user data or other files which can be accessed at a latter date (for example via iTunes Sharing).
If you move your Models.scnassets folder under the yellow ArAds folder you should be able to access your content like so:
let myModelToLocate = SCNScene(named: "Models.scnassets/Phone_01.scn")
Hope it helps...
You've pointed me in a better direction, but I can already get the scn and make a node if I know the name. What I really want to do is populate a tableview with the options that are available - so I need to find the current list of scene files.
I think this will work:
let subdir = Bundle.main.resourceURL!.appendingPathComponent("Models.scnassets").path
do {
let modelPathDirectoryFiles = try fileManager.contentsOfDirectory(atPath: subdir)
print(modelPathDirectoryFiles.count) //this works
//then do my thing with the array
} catch {
print("error getting list of files")
}
This seems to work, whether I move the folder or not.

Swift 3 updating core data model causes crash for external testers

I released an app using core data to store some important information.
Recently I decided to re-do my data model to bring it up to date and make it easier to use.
I added some entities to the data model, and it runs fine in the simulator - however when I released it to the beta testers as soon as it tries to do anything with core data it is crashing.
I did not create a new version of the data model.
I have read here and here about how to deal with this error but both answers reference code that I do not have anywhere in my app, but they seem to have built in - they also talk about lightweight data migration? A lot of the answers reference a NSPersistentStoreCoordinator, which I do not have/know how to implement.
The code I have in the app delegate dealing with the persistentContainer is:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "App_Name")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
The other answers also referenced crashes in the simulator which required re-installation - I don't think I got these, but I may not have noticed.
What is the best way for me to update my data-model such that my users won't get crashes?
EDIT:
I have updated the persistentContainer to this:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "App_Name")
let myFileManager = FileManager()
do {
let docsurl = try myFileManager.url(for:.documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let myUrl = docsurl.appendingPathComponent("UserDataTA")
if try myFileManager.contentsOfDirectory(atPath: docsurl.path).contains("UserDataTA") == false {
try myFileManager.createDirectory(at: myUrl, withIntermediateDirectories: false, attributes: nil)
try container.persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: myUrl, options: nil)
}
} catch {
print(error)
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalErrorText = error.debugDescription
firstFuncs.errorAlert(error: fatalErrorText)
}
})
return container
}()
however now the error message is "Error Domain=NSCocoaErrorDomainCode=134140 'Persistent store migration failed, missing mapping model.'"
Your crash sounds like it's happening because your data model changed, and any existing data that exists on a device can't be mapped 1:1 to the new model. This is where data mapping and migration comes in. Apple's docs are a good place to start. The very high-level overview is:
Open your xcdatamodel or xcdatamodeld file in Xcode, then select "Add Model Version...".
If necessary, save your model as an xcdatamodeld file. (The d suffix indicates that it's versioned.)
Note that your xcdatamodeld file is really a folder, and it includes multiple xcdatamodel files. All versions after the first one will include a numeric version number in the filename.
Make your model changes in the new model version.
If the changes are relatively simple (such as renaming an entity or adding or removing attributes), then it's considered a lightweight migration, and core data can figure out how to do the migration itself.
If your changes are more involved (such as splitting an entity type into two), then you must create a mapping model.
Add the code to perform the migration at startup time.
Obviously there's a lot more to this process, but this may give you a decent start.

Using an existing database in an iMessage app (Swift)

Currently building an iMessage app, and would like to experiment with using a database. I have a database that I would like to use in the app, and have included it in my project, and verified the target membership is correct. Using SQLite.Swift.
Whenever I try opening the connection to the database in simulator, I always get an error (unexpected nil) for the path of the database.
I've tried an image file the same way with no avail.
let imagePath = Bundle.main.path(forResource: "db", ofType: ".sqlite")
do {
let db = try Connection(imagePath!, readonly: true)
} catch {
}
I believe the issue is more related to what an iMessage "app" is - which is actually an extension, not a true app. There's no initial VC, thus no real Bundle.main to get to.
One (maybe soon a second) app of mine has a Photo Editing Extension - basically what I always have called a "shell connection" to an Apple app. You really have either a "do nothing" app with a connection to one of their apps, or you have a stand-alone app an share the code with the extension.
My solution for sharing code is to use a Framework target. Yes, a third project. (App, extension, shared code.) I found a technique that I think should work for you - basically, for images, scripts (my apps use .cikernel files) you add them into the framework project and return what you need in a function call.
You may be able to streamline this with a need for a Framework target. YMMV. The basics are this:
Someplace in Xcode you have a "Bundle Identifier". Something like *"com.company.projectname".
Put your files into a folder, maybe on your desktop. Add an extension to this folder called ".bundle". macOS will give you a warning, accept it. All you are really doing is creating your bundle.
Drag this into your Xcode project.
Code to get to this bundle, and the files inside it. (I'm not sure if need a framework here - try to drag this into your "MessagesExtension" target first.
So lets say you have images you wish to share between projects, extensions, whatever. After moving them into a folder called "images", andrenaming the folder with a ".bundle" at the end, and finally dragging it into your Xcode project, you pretty much need to add this function:
public func returnImage(_ named:String) -> UIImage {
let myBundle = Bundle.init(identifier: "com.company.project")
let imagePath = (myBundle?.path(forResource: "images", ofType: "bundle"))! + "/" + named
let theImage = UIImage(contentsOfFile: imagePath)
return theImage!
}
For a text file you want:
public func returnKernel(_ named:String) -> String {
let myBundle = Bundle.init(identifier: "com.company.project")
let kernelPath = (myBundle?.path(forResource: "cikernels", ofType: "bundle"))! + "/" + named + ".cikernel"
do {
return try String(contentsOfFile: kernelPath)
}
catch let error as NSError {
return error.description
}
}
Usage, for an image called "Camera.png" which is part of a bundle called "images.bundle":
let cameraImage = returnImage("Camera")
Since I don't work with SQLite files I don't have the exact code, but I think this should work. Remember to change "com.company.project" to what you have for the bundle identifier.

Storing Sounds In a Folder (Swift File Path) [duplicate]

I have a set of audio files inside a folder. I am able to access the file when the file is placed at main bundle, but if the files are moved inside the folder I am not able to access the files.
Code:
let audioFileName:String = "audioFiles/" + String(index)
let audioFile = NSBundle.mainBundle().pathForResource(audioFileName, ofType: "mp3")!
I have an audio file inside the folder audioFiles and I would want to get its path.
Error:
fatal error: unexpectedly found nil while unwrapping an Optional value
First make sure when you drag your folder audioFiles to your project to check copy items if needed and select create folder references. Make sure it shows a blue folder if your project.
Also NSBundle method pathForResource has an initialiser that you can specify in which directory your files are located:
let audioFileName = "audioName"
if let audioFilePath = Bundle.main.path(forResource: audioFileName, ofType: "mp3", inDirectory: "audioFiles") {
print(audioFilePath)
}
If you would like to get that file URL you can use NSBundle method URLForResource(withExtension:, subdirectory:)
if let audioFileURL = Bundle.main.url(forResource: audioFileName, withExtension: "mp3", subdirectory: "audioFiles") {
print(audioFileURL)
}
The answer by Leo should work, the critical step is checking "Copy Items if Needed". However, what do you do if you already created a file or forgot that critical step?
Its easy to fix.
Go to Project -> Build Phases -> Copy Bundle Resources
Here you will see a handy list of all the files you've added to your bundle (including all your xcassets!)
Click the add button, and find your missing unlinked file.
Your code will now work (I built my own example, so it won't be same as yours in name):
if let url = Bundle.main.url(forResource: "testData2", withExtension: "txt") {
do {
let myData = try Data(contentsOf: url)
print(myData.count)
} catch {
print(error)
}
}
So why weren't we finding the file to begin with? Simple. We were asking the bundle. "Hey where is my file". Bundle was like... I don't know about that file, and if you checked the list of what was part of it (the Bundle Resources that are being copied) it wasn't there. taDa!

Resources