Swift FileManager.ContentsOfDirectory returns nil - swift

I am very new to Apple development and I am writing a simple app where i am trying to access certain images inside my project. To do this I made a simple function which returns (in theory) the file names in an array.
class Tools{
public func GetFiles(filePath : String) -> Array<NSString>{
let fm = FileManager.default
let path = Bundle.main.resourcePath! + filePath
let items = try? fm.contentsOfDirectory(atPath: path)
return items! as Array<NSString>;
}
}
And in my main class I am calling this function and passing along the directory where it can find my files
class LoadingView: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let tool = Tools()
var logoAnim = tool.GetFiles(filePath: "/Path/To/My/Files");
}
}
That is all I'm doing code-wise... Now here is how i have my asset structure
And I also made sure to include each file as a target member of this product
When I debug this it returns nil every time. Can someone help me understand what am I doing wrong? Is there a special way I need to reference the path, or, am I missing an extra step?

Related

Can I Fetch Data from API while using a variable to change the url target? [SwiftUI]

I have a question. I am trying to make a API call to fetch data for a specific item. To do so, I just need to modify 1 parameter of the URL that is making the call.
This is the call on the item page to fetch the data. I feel like I should be able to pass the item # like this. But im having trouble passing it correctly.
struct ProductPage: View {
#ObservedObject var relatedsectionNetwork = RelatedSectionAPI()
...
...
.onAppear() {
relatedsectionNetwork.fetchData(prID: "1824085")
print("relatedSection Loaded")
}
This is the actual class and function that I am using to make the call. You can see what my thought process was here: create a var that could be used to change the prID within the URL.
class RelatedSectionAPI: ObservableObject {
var prID : String = ""
func fetchData() {
print("fetchData3 -start 􀋂")
if let url = URL(string:"https://www.*****.com/****/******?***=***=&***=&***=***&pr_id=\(prID)&***") {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error == nil {
let decoder = JSONDecoder()
I cut this part short to not confuse anyone.
If any one can please help me out, or point me in the right direction, it would be greatly appreciated!
Thank you
Add the parameter to the function call func fetchData(prID: String = ""). BTW you should use #StateObject vs #ObservedObject when you initialize an ObservableObject in a View

Sharing data between swift classes

I have this class called AudioController() and it has a variable called sources which is an array of Strings.
AudioViewController() code:
import Foundation
class AudioController {
static let shared = AudioController()
var sources = [String]()
init() {
print("Sources: \(sources)")
let controller = RemoteCommandController()
player = QueuedAudioPlayer(remoteCommandController: controller)
player.remoteCommands = [
.stop,
.play,
.pause,
.togglePlayPause,
.next,
.previous,
.changePlaybackPosition
]
try? audioSessionController.set(category: .playback)
try? player.add(items: sources, playWhenReady: false) // fatal error here because sources is nil
}
}
But. On my other viewcontroller when trying to pass sources:
AudioController().sources = ["Shakira"]
I get:
Fatal error: Unexpectedly found nil while unwrapping an Optional value
and the print("Sources: \(sources)") returns sources [].
Actually, there are strange things here: why you have a shared instance if you're not using it? And then, you're trying to play an empty array, maybe this is the problem. Try to give values before starting the player
class AudioController {
static let shared = AudioController()
var sources: [String]
init(sources: [String]) {
self.sources = sources
print("Sources: \(sources)")
let controller = RemoteCommandController()
player = QueuedAudioPlayer(remoteCommandController: controller)
player.remoteCommands = [
.stop,
.play,
.pause,
.togglePlayPause,
.next,
.previous,
.changePlaybackPosition
]
try? audioSessionController.set(category: .playback)
try? player.add(items: self.sources, playWhenReady: false) // fatal error here because sources is nil
}
}
// then instantiate the controller with sources
AudioController(sources: ["Shakira"])
However I suggest you to review your design. Maybe it's not a good idea start playing in the init.
If you want to use the shared instance (which I think you do), use something like this:
AudioController.shared.sources = ["Shakira"]
You should also probably add private init() {} to prevent accidentally initializing the AudioController. This will make a private initializer that cannot be used outside of the AudioController.
The print statement will always print [] because when the init function is called, nothing is in the array yet.

Swift / Cocoa: How to watch folder for changes?

I'm writing a small macOS app, where I want to be able to watch a folder for changes. It doesn't need to watch subfolder, I only want to receive a notification if a file is added to the folder or removed.
It looks like NSFileCoordinator and/or NSFilePresenter could be used to achieve this, but I was not able to understand how to use them to achieve this.
Ideally this can be solved without having to include a third party framework.
You can do this using NSFilePresenter.
The observing class must conform to NSFilePresenter as shown below.
The presentedItemURL would point to the folder you want to observe.
If there is a change in the folder presentedSubitemDidChangeAtURL get called. The code snipped below could give you an idea how it can work.
class ObservingClass: NSObject, NSFilePresenter {
lazy var presentedItemOperationQueue = NSOperationQueue.mainQueue()
var presentedItemURL:NSURL?
func presentedSubitemDidChangeAtURL(url: NSURL) {
let pathExtension = url.pathExtension
if pathExtension == "png"{
refreshImages()
}
}
func refreshImages(){
let path = snapshotPath
var isDirectory: ObjCBool = ObjCBool(false)
if NSFileManager.defaultManager().fileExistsAtPath(path!, isDirectory: &isDirectory){
if isDirectory{
do {
let list = try NSFileManager.defaultManager().contentsOfDirectoryAtPath(path!) as Array<String>
for filePath in list {
if filePath.hasSuffix(".png"){
if let snapshot = snapshotAtPath(path! + "/" + filePath){
newSnapshotArray += [snapshot]
}
}
}
} catch {
// error handling
}
}
}
}
}
Best wishes.
Marc T's answer still works and seems to be the easiest solution for this.
To make it work I needed to add the following line (could be at the init() of ObservingClass):
NSFileCoordinator.addFilePresenter(self)

The following code works when I manually pass the value but fails when I unwrap the value

I just started learning Swift as my first coding language. My current challenge is trying to automate the transitions from a current level setup as LevelOne.sks to another level also created in Xcode level editor as LevelTwo.sks. What I'm attempting to do is trigger a transition to the next level with the following set of code.
In my base scene I have this function to send the player to the next level
private func goToNextLevel(nextLevel: String) {
//When hard coding the arguments as...
//loadScene(withIdentifier: .levelTwo)
//The level two scene loads and is playable...however,
//When trying to change the level argument in code
// by passing a nextLevel variable
// the optional unwraps nil and crashes the app.
loadScene(withIdentifier: SceneIdentifier(rawValue: nextLevel)!)
}
This is then passed to a SceneLoadManager File
enum SceneIdentifier: String {
case levelOne = "LevelOne"
case levelTwo = "LevelTwo"
// case levelThree = "LevelThree"
}
private let sceneSize = CGSize(width: 768, height: 1024)
protocol SceneManager { }
extension SceneManager where Self: SKScene {
func loadScene(withIdentifier identifier: SceneIdentifier) {
let reveal = SKTransition.flipHorizontal(withDuration: 0.5)
let nextLevel = SKScene(fileNamed: identifier.rawValue)
nextLevel?.scaleMode = .aspectFill
self.view?.presentScene(nextLevel!, transition: reveal)
}
I think this has something to do with how I'm trying to set nextLevel. Currently I'm setting this up as follows
let nxtLvl = String?
nxtLvl = ".levelOne"
goToNextLevel(nextLevel: nxtLvl)
Hopefully you can make sense of what I'm trying to achieve and that I'm at least close to being on the right track here. Any help would be greatly appreciated. Thanks!
What could really help you to solve :
SceneIdentifier.levelOne.rawValue returns this -> "LevelOne"
BUT
SceneIdentifier(rawValue : "LevelOne") returns -> SceneIdentifier.levelOne
See the difference ?
In the first you get the string , in that case what you want
And in the second you get the "left member" (I don't know the therm)
An example to see clearer if you have an enum :
enum SceneIdentifier: String {
case case1 = "LevelOne"
case case2 = "LevelTwo"
}
SceneIdentifier(rawValue : "LevelOne") !
returns this : SceneIdentifier.case1
and SceneIdentifier.case1.rawValue
returns this : "levelOne"
Since you are initializing with a raw value, you need to actually use the raw value.
let nxtLvl = String?
nxtLvl = "LevelOne" //this is the rawValue of the .levelOne case
goToNextLevel(nextLevel: nxtLvl)
Alternatively, you could change your API so that goToNextLevel simply accepts a SceneIdentifier like this.
//tip: use the parameter name as part of the method name
private func goTo(nextLevel: SceneIdentifier) {
//Then calling the function
goTo(nextLevel: .levelOne)
Though, that might not be appropriate in the larger scope of your API.

PLIST read/write with generic dictionary in Swift

I'm trying to add load/save functions to a generic caching class, like this:
class Cache<T:Hashable, U:AnyObject> {
private var cache: [T:U] = [:]
private var url: NSURL!
func load() {
if let path = self.url where NSFileManager().isReadableFileAtPath(path.path!) {
if let dict = NSDictionary(contentsOfURL: path) {
// The next line is rejected by the compiler as always failing the cast
cache = (dict as? DictType)!
}
}
}
func save() {
if let path = self.url where NSFileManager().isWritableFileAtPath(path.path!) {
// The next line is rejected by the compiler as the method not existing
let result = cache.writeToURL(path, atomically: false)
}
}
.
.
.
}
If I use concrete types this works, but translating to generics apparently takes away enough information that the compiler is no longer willing to accept it. Is there a way to do this?
I also tried looping through the NSDictionary, but for example the loop in the load routine then complains it doesn't have a type for the key (and won't accept what's returned from allKeys); nor can I cast / coerce to the type T.