Referencing the bundle inside CocoaPod framework fails - ios

I have created a Swift framework and I'm trying to make it into a CocoaPod.
These are all the files in the framework.
I add all the source files plus the .json file in the podspec like so.
spec.source_files = "CountryPicker"
spec.resource = "CountryPicker/countries.json"
The files do get added.
Or so it seems. Because when I try to load the json file within the framework code like this
let path = Bundle(for: Self.self).path(forResource: "countries", ofType: "json")!
it keeps failing because it's returning nil every time.
I tried all the methods described here but it's not having any effect. Any idea what I'm doing wrong here?

I am able to do this for other assets, assume it will be the same for you.
First, find the name of your Pod's bundle. I have redacted a lot of this, but if you follow the arrows, you can see how to get it via Xcode.
The order would be:
Select the Pods project
Select the target associated with your pod
With General selected, locate the Bundle Identifier
Once you have that, you can then instantiate the Bundle:
let yourPodBundle = Bundle(identifier: "org.cocoapods.your_pod_sname_here")
Now, pull it all together
let path = yourPodBundle?.path(forResource: "countries", ofType: "json")!

Related

React Native Module, Cocoapod file not found

I am building a module for React Native, as a part of this, there is some native iOS code that is wrapped in a cocoapod. I have this working fine, and I can install the cocoapod and see the native classes from the RN side no problem. One of the native classes is trying to get the path to a local mp3 file inside a sub folder proj/Audio/file.mp3. Using the following ObjC code: NSString *path = [[NSBundle mainBundle] pathForResource:#"file" ofType:#"mp3" inDirectory:#"Audio"];
This is returning nil and as such causing an error when trying to get the NSURL for the file. I have the mp3 extension added to the podspec # s.source_files = "ios/**/*.{h,m,swift,mp3}" so it is moving the file over. However, I assume I have to be missing something as it ultimately cannot find the file.
I have gone into the source xcode project and added the mp3 file to the Copy files phase in build phases. I have also made sure that the file was linked to the target. I am not sure what else I could be missing at this point for it to not be able to be found.
Instead of putting the mp3 file in source_files of your podspec, try using resource_bundles; see the documentation here. Basically it lets you define resource bundle files that CocoaPods creates for you with the assets that you specify, for example:
s.resource_bundles = {
'Audio' => ['ios/**/*.mp3']
}
You can load the bundle file resource that CocoaPods created (create a NSBundle object from its URL by its name - Audio in the above example, and extension - bundle), then get a path from resources inside this bundle (the same method that you used on the mainBundle).
I know this is an old post, but I was struggling with this myself. I completely agree with Artal, to sum up you need to
let bundlePath = Bundle.main.path(forResource: "Audio", ofType: "bundle");
let bundle = Bundle(path: bundlePath!)
maybe guard your the path,

Load .html into WebView from iOS app Resources?

I have an HTML file (webview.html, for now) with relative links to images and .js files. I need to load it, and those linked resources into an iOS app's WKWebView. All the files are stored in Resources/Non-Localized/.
I'm attempting to load the files using the following code during viewDidLoad():
print("loadWebView: Bundle.main = ", Bundle.main); // This prints
if let htmlUrl = Bundle.main.url(forResource: "webview", withExtension: "html") {
print("htmlUrl = ", htmlUrl) // Doesn't print
webView.load(URLRequest.init(url: htmlUrl))
}
The app loads without error, but also without the contents in the webview.
I get the first print statement, but not the second, indicating something is wrong with my URL or resource bundle configuration.
What am I doing wrong? Do I need to do anything in XCode to add these files to the project? Where should I be looking for error messages that will hint me in the correct direction in the future?
I needed to add the file to the Project. I did this by simply dragging the Finder file icon to the project tree in Xcode.
Alternatively (and my final solution), I dragged the parent directory's icon, making a folder reference, and then added subdirectory parameter to .url(forResource...).
I'm still working on getting informative errors out of the WebView.

Unable to load images from framework in my project

My project structure is in such a way that I have 2 projects named as ProjectA and ProjectB. Now for these 2 projects, I have created a framework named as ProjFramework.
I have added common files of these 2 projects inside the framework and then added framework in the projects separately. Till now I am able to access all the files and vars. Now I added the images also in the framework and when I am trying to display them in UI then I am getting an error saying unable to read this .png.
As I know I need to include the framework somehow but I don't know how. Please help me in sharing common images b/w 2 XCode projects.
Thanks in advance.
An alternative to #arunjos007' method is to (a) create a bundle of your images, then (b) retrieve from the bundle.
Let's say you have a Framework called Kernel, and you wish to access two types of files - cikernel and png.
Creating a bundle:
Move all your files/images into a new folder on your desktop. Name it whatever you wish. In my example I named them cikernels and images.
Rename your folders, with a .bundle extension. In my example they became cikernels.bundle and images.bundle. You will see the warning below... choose "Add".
Drag the bundle into your framework project. You can expand the bundle to see the contents. Also, you can add/delete/edit the contents of the bundle.
Retrieving files from the bundle:
I've created two public functions, one to retrieve files and one to retrieve images. They are pretty much the same, except for (a) the return type and (b) error handling. (I probably should add some to the UIImage function - but since I have total control on the code - it's not going to be used by anyone else - it's not critical.)
public func returnImage(_ named:String) -> UIImage {
let myBundle = Bundle.init(identifier: "com.companyName.frameworkName")
let imagePath = (myBundle?.path(forResource: "images", ofType: "bundle"))! + "/" + named
let theImage = UIImage(contentsOfFile: imagePath)
return theImage!
}
public func returnKernel(_ named:String) -> String {
let myBundle = Bundle.init(identifier: "com.companyName.frameworkName")
let kernelPath = (myBundle?.path(forResource: "cikernels", ofType: "bundle"))! + "/" + named + ".cikernel"
do {
return try String(contentsOfFile: kernelPath)
}
catch let error as NSError {
return error.description
}
}
One last note: The identifier is defined in the Framework target's General tab. Typically it's in the form com.companyframework*. Change that line to your's.
Before Building and distributing your framework, you should copy files that need to carry with your framework.
To copy files, Click your framework project target, go to "Build Phases" tab and you can see a section called "Copy Files". Add your files here(See below screenshot)
Then build your framework project, after a successful build your distributable framework will be generated in your Products folder. It will look like YourFrameworkProjectName.framework.
Add this file to other projects which need your framework.
Note: If you just want to run your project by connecting with your framework project, you can be done it by adding build dependency. See StackOverflow question here

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.

Importing and querying through an SQLite database in Swift - iOS

I'm making an app that makes use of a big database with 5 different tables. I want to import that database to the app bundle and be able to query through the tables. The database won't be edited by the user, so adding and removing records is not required.
What would be the best way to add the database in the app?
The process is as follows:
Add the database to your bundle. When you drag it into your project, you can choose to add it to the target that represents your main app. Alternatively, review the target settings, click on "Build Phases" and confirm the database appears in the "Copy Bundle Resources" list.
Use a framework like FMDB to simplify your life. This is written in Objective-C, but works great in Swift, too. What you need to do is:
Copy the .h and .m files for FMDB into your project;
When prompted to create a "bridging header", do so;
Add the following line to your bridging header:
#import "FMDB.h"
By following those steps, you can use this framework developed in Objective-C into your Swift projects.
You can now write your Swift code, using the FMDB framework. For example, the Swift code to open the database, select columns x, y, and z from a table called test, would look like:
let path = NSBundle.mainBundle().pathForResource("test", ofType:"sqlite")
let database = FMDatabase(path: path)
if !database.open() {
print("Unable to open database")
return
}
if let rs = database.executeQuery("select * from test", withArgumentsInArray: nil) {
while rs.next() {
let x = rs.stringForColumn("x")
let y = rs.stringForColumn("y")
let z = rs.stringForColumn("z")
print("x = \(x); y = \(y); z = \(z)")
}
} else {
print("executeQuery failed: \(database.lastErrorMessage())")
}
database.close()
Swift only. No Objective-C files required.
Here is another solution which uses SQLite.swift instead of FMDB.
1.: Get a valid path to your database file
Don't save the database file in your Assets folder, but add it to your Copy Bundle Resources list in the Build Phases setting of your current target. If your file is called myDb.db you can then get a valid path like this:
let dbUrl = Bundle.main.url(forResource: "myDb", withExtension: "db")!
let dbPath = dbUrl.path
2.: Access your database (without copying it)
It is possible to access your database now without having to (manually?) copy it. Just use the SQLite.swift library:
db = try! Connection(dbPath)
Notes:
The only thing I haven't checked yet explicitly is writing to the database. At least a read-only access does work like a charm though.

Resources