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
Related
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")!
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.
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.
I'm trying to get my iPhone app to load text from a file into a string array, with 1 line from the file per array element.
I've created an input file as a text file using sublime text. I dragged the file (which is located inside of a folder inside of my project directory) into xCode into a folder in the same location in the project heirarchy.
I also tried adding it as a bundle (by copying the folder and renaming it with the .bundle extension), to no avail. Currently, my app has the file in 2 places (Obviously I plan to delete the unneeded version, but I'm not sure how this will work so I've left it for now).
I've written a function that I want to read my file, and assemble its contents into an array:
func readFromFile(filename: String) -> [String]? {
guard let theFile = Bundle.main.path( forResource: fileName, ofType: "txt") else {
return nil // ALWAYS returns nil here: Seems 'filename' can't be found?????
}
do { // Extract the file contents, and return them as a split string array
let fileContents = try String(contentsOfFile: theFile)
return fileContents.components(separatedBy: "\n")
} catch _ as NSError {
return nil
}
}
As it stands, the function always returns nil at the location commented in the code.
I've been working on this for ~6hrs (and tried every suggestion I could find on StackOverflow, google etc) and I'm just getting more and more confused by the differences between the various versions of Swift and intricacies of iOS development. I can't seem to find a consistent answer anywhere. I've checked the apple documentation but it's too high level with no example code for me to understand at my swift beginner level.
I also tried naming the file with a ".txt" extension but that didn't help either.
The file must certainly be named alert01.txt if you are going to refer to it as forResource: "alert01", ofType: "txt".
Loading from a bundle will not work. The file needs to be part of your project as shown in the first entry.
However, your code is not going to work because you have created a folder reference. That means the folder PanicAlertFiles is being copied with all its contents into your bundle. Your code will need to dive into that folder in order to retrieve your file. Use path(forResource:ofType:inDirectory:) to do that, or (if you don't want to have to code the file name explicitly) get the folder and then use the FileManager to examine its contents.
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.