App resources not available when UI testing in Xcode 7 - ios

I'm trying to extend the new UI testing functionality in Xcode 7 by snapshotting the current screen elements (labels, images, buttons) and saving their accessibility information to json files.
The idea is that when running the UI tests later, a current screen snapshot can be taken and compared to the existing one, the test will fail if additional or missing elements are found.
Unfortunately the app resources don't seem available during UI testing, even with the correct target, so the json files can't be loaded for comparison. The following standard code fails to load a resource:
guard let resourcePath = NSBundle.mainBundle ().pathForResource ("StartScreenShapshot", ofType:"json") else {
XCTFail ("can't load resource StartScreenShapshot")
return
}
I can understand why Apple have taken this sandbox approach, as UI testing should be based on what's happening on the screen, and access to the workings of app shouldn't be needed, but not having access to the resource bundle is a pain.
So is there a way to load local resources from the app, or some other way locally, during Xcode 7 UI testing?
Saving the files locally (automatically) would also be a huge plus, would save creating them manually.

Thanks to #sage444
For unit testing the mainBundle() method doesn't work for retrieving a resource path, using a class does.
guard let resourcePath = NSBundle (forClass: self.dynamicType).pathForResource (contentName, ofType:"json") else {
XCTFail ("can't load resource \(contentName)")
return
}

Thanks #danfordham
Updated for Swift 3
1) Copy bundle resources
2) Reference new bundle this way,
guard let path = Bundle(for: type(of: self)).path(forResource: contentName, ofType: "json") else {
XCTFail ("can't load resource \(contentName)")
return
}

Related

Reading files from external storage in iOS 13

I have an iOS app that is trying to read files from an external storage device without importing them into the App's sandbox.
I have followed Apple's documentations outlined here to do this --
Providing Access to Directories
I'm able to retrieve the selected directory ( which is on an external storage device connected via the Lightning port ) and enumerate the files inside the directory.
However, when I try to do something with those files as per the recommended pattern, I get a failure and basically get permission errors on the file.
let shouldStopAccessing = pickedFolderURL.startAccessingSecurityScopedResource()
defer {
if shouldStopAccessing {
pickedFolderURL.stopAccessingSecurityScopedResource()
}
}
var coordinatedError:NSError?
NSFileCoordinator().coordinate(readingItemAt: pickedFolderURL, error: &coordinatedError) { (folderURL) in
let keys : [URLResourceKey] = [.isDirectoryKey]
let fileList = FileManager.default.enumerator(at: pickedFolderURL, includingPropertiesForKeys: keys)!
for case let file as URL in fileList {
if !file.hasDirectoryPath {
do {
// Start accessing a security-scoped resource.
guard file.startAccessingSecurityScopedResource() else {
// Handle the failure here.
//THIS ALWAYS FAILS!!
return
}
// Make sure you release the security-scoped resource when you are done.
defer { file.stopAccessingSecurityScopedResource() }
I should add that this works JUST FINE if the files are on iCloud Drive via Simulator. It fails both on external devices and iCloud Drive on a real device.
Here is a full working project that demonstrates the failure.
Running on simulator accesses iCloud Drive files just fine. But running on device fails.
Running on device to access USB drive fails.
So, this seems like a documentation issue with the link posted above. When the user selects a folder, all files and folders are recursively granted access and automatically security scoped. The line guard file.startAccessingSecurityScopedResource() always returns false.
The trick to getting this work is NOT to try to security scope individual files, but to ensure that this code snippet does not run BEFORE you access files.
defer {
if shouldStopAccessing {
pickedFolderURL.stopAccessingSecurityScopedResource()
}
}
As long as you continue to access files while the pickedFolderURL is inside security scope, you will be successful.
Hope this helps somebody.

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.

How Does Apple's On Demand Resource System download files to the App's Main Bundle?

I am currently trying to implement Apple's API for On Demand Resource Management for AWS Cloudfront because Apple's ODR is somehow too unreliable.
I noticed that when I tag images inside of Assets.scnassets/ with an ODR resource tag, I can access that image using
UIImage(name: resourceName)
once it has been downloaded by a NSBundleRequest object. Because I can access the downloaded resource as a UIImage, I know that the resource is located in the app's main bundle but I thought this was impossible because Bundles were read-only. How did apple do this? The most important aspect is being able to create UIImages using this incredibly simple interface.
I know that the resource is located in the app's main bundle but I thought this was impossible because Bundles were read-only. How did apple do this?
It’s an illusion. ODR files are downloaded to a directory outside the app bundle, and calls like UIImage init(named:) are swizzled to look in that directory.
Not sure how it is done technically, but I am pretty certain that attributing ODR to a particular bundle (typically main) is done on purpose, so you can rely on it.
I am using RxOnDemandResources library to fetch hundreds of MP3 from dozens of tags, and it is done on a per bundle basis -
Bundle
.main
.rx
.demandResources(withTags: tags) // tags: Set<String>
.subscribeOn(Scheduler.concurrentUser) // declare your scheduler first
.observeOn(Scheduler.concurrentMain) // declare your scheduler first
.subscribe { event in
switch event {
case .next(let progress):
self.showProgress(progress: Float(progress.fractionCompleted)) // declare your handler first
case .error(let error):
break // handle appropriately
case .completed:
if let url = Bundle.main.url(forResource: file, withExtension: "") {
os_log("url: %#", log: Log.odr, type: .info, "\(url)")
// TODO use your resource
}
}
}
.disposed(by: self.disposeBag)
Back to your question: yes you can access ODR resources via specific bundle as usual but with all preconditions (checking availability, fetching if needed) - i.e. not always.

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.

Resources