Swift / Cocoa: How to watch folder for changes? - swift

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)

Related

How to get macOS to present a dropped .stl file as public.standard-tesselated-geometry-format?

I'm trying to add some drag-and-drop support to my SwiftUI macOS app to allow the user to drop .stl files onto the window. I’ve got the drop working, but I only seem to be able to validate against public.file-url, rather than the specific public.standard-tesselated-geometry-format (provided by ModelIO as kUTTypeStereolithography).
In my code, I do something like this:
func
validateDrop(info inInfo: DropInfo)
-> Bool
{
debugLog("STL: \(kUTTypeStereolithography)")
let has = inInfo.hasItemsConforming(to: [kUTTypeFileURL as String, kUTTypeData as String])
return has
}
func
performDrop(info inInfo: DropInfo)
-> Bool
{
inInfo.itemProviders(for: [kUTTypeFileURL as String, kUTTypeData as String, kUTTypeStereolithography]).forEach
{ inItem in
debugLog("UTIs: \(inItem.registeredTypeIdentifiers)")
inItem.loadItem(forTypeIdentifier: kUTTypeFileURL as String, options: nil)
{ (inURLData, inError) in
if let error = inError
{
debugLog("Error: \(error)")
return
}
if let urlData = inURLData as? Data,
let url = URL(dataRepresentation: urlData, relativeTo: nil)
{
debugLog("Dropped '\(url.path)'")
}
}
}
return true
}
This works well enough if I look for kUTTypeFileURL, but not if I look for kUTTypeStereolithography when I drop a .stl file onto the view.
I tried declaring it as an imported type but a) that didn’t seem to work and b) shouldn’t be necessary, since the existence of a symbolic constant implies macOS know about the type. I also don’t really know what it conforms to, although I tried both public.data and public.file-url.
I’d really like to be able to validate the type so that the user isn’t mislead by the drop UI. Currently, it indicates that my view will accept the drop if it’s not an .stl file, which I don’t want it to do.

RxSwift how to refresh BehaviourSubject better way?

I have BehaviourSubject and I want to refresh items with last emitted value. I can do it example below,
func refreshCurrent() {
do {
let items = try currentMatchList.value()
if !(items.first?.items ?? []).isEmpty {
refreshItems(sportId: try currentSport.value())
}
} catch {
LOG.error(error.localizedDescription)
return
}
}
But I was wondering is there any built in RxSwift functionality I might use for same task?
I've found there was a Variable() type once upon a time but now it's gone and it's recommended to use BehaviourSubject it seems.
Thanks in advance.
After searching all the issues in the official github repo I've found long discussion about same problem here and it's closed.
BUT good news is as freak4pc states we can use RxCocoa class BehaviourRelay and it has a direct value access method
example("BehaviorRelay") {
let disposeBag = DisposeBag()
let subject = BehaviorRelay<String>(value: "🚨")
print(subject.value)
subject.addObserver("1").disposed(by: disposeBag)
subject.accept("🐶")
subject.accept("🐱")
print(subject.value)
}
Don't know if I get you correctly, but it seems like you want to store your value within a BehaviourSubject.
let foo = BehaviourSubject<[Something]>(value: [])
print(foo.value) //Empty Array
foo.accept([Something(), Something()])
print(foo.value) //Array of two somethings

Swift FileManager.ContentsOfDirectory returns nil

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?

Iterating over and modifying a collection of structs in Swift

Suppose you have some structs like:
struct Tattoo {
var imageTorso:UIImage?
var imageTorsoURL:URL?
var imageArms:UIImage?
var imageArmsURL:URL?
}
struct Player {
var name:String = ""
var tattoos:[Tattoo] = []
}
struct Team {
var name:String = ""
var players:[Player] = []
}
Now imagine that you have a method that was passed in a Team value with some players. You have to iterate thru the players and their tattoos, then download the images and add them into the images variables on the tattoos.
If you use a for in loop, then it won't work because each part of the loop gets a copy of the members of the array it's iterating over. I.e.:
for player in team.players {
for tattoo in player.tattoos {
if let url = tattoo.imageTorsoURL {
MyNetFramework.requestImage(from: url, completion: { image in
tattoo.imageTorso = image
}
}
}
}
After doing all the iterations and completion blocks, still, the original team variable is identical to what it was prior to doing any of this. Because each tattoo that the inner loop got was a copy of what is in the player's tattoos array.
Now I know you can use & to pass structs by reference in Swift but it's highly discouraged. As well I know you can use inout so they don't get copied when they come into functions, which is also discouraged.
I also know these could be made classes to avoid this behavior.
But supposing I don't have a choice in the matter -- they are structs -- it seems the only way to do this is something like:
for p in 0...team.players.count-1 {
for t in 0...team.players[p].tattoos.count-1 {
if let url = team.players[p].tattoos[t].imageTorsoURL {
MyNetFramework.requestImage(from: url, completion: { image in
team.players[p].tattoos[t].imageTorso = image
}
}
}
}
This feels ugly and awkward, but I don't know how else to get around this thing where for in loops give you a copy of the thing you're iterating through.
Can anyone enlighten me, or is this just how it is in Swift?
I think you already got the point: "When your requirement will be modifying the data, you better to use class instead."
Here is the question reference link for you. Why choose struct over class
struct is fast and you can use them to prevent creating a huge, messy class. struct provided the immutable feature and make us easier to follow the Function Programming
The most significant benefit of immutable data is free of race-condition and deadlocks. That because you only read the data and no worries about the problems caused by changing data.
However, to answer your question, I have few ways to solve it.
1. Renew whole data.
// First, we need to add constructors for creating instances more easier.
struct Tattoo {
var imageTorso:UIImage?
var imageTorsoURL:URL?
var imageArms:UIImage?
var imageArmsURL:URL?
init(imageTorso: UIImage? = nil, imageTorsoURL: URL? = nil, imageArms: UIImage? = nil, imageArmsURL: URL? = nil) {
self.imageTorso = imageTorso
self.imageTorsoURL = imageTorsoURL
self.imageArms = imageArms
self.imageArmsURL = imageArmsURL
}
}
struct Player {
var name:String
var tattoos:[Tattoo]
init() {
self.init(name: "", tattoos: [])
}
init(name: String, tattoos: [Tattoo]) {
self.name = name
self.tattoos = tattoos
}
}
struct Team {
var name:String
var players:[Player]
init() {
self.init(name: "", players: [])
}
init(name: String, players: [Player]) {
self.name = name
self.players = players
}
}
for player in team.players {
for tattoo in player.tattoos {
if let url = tattoo.imageTorsoURL {
// Catch old UIImage for matching which Tattoo need to be updated.
({ (needChangeImage: UIImage?) -> Void in
MyNetFramework.requestImage(from: url, completion: { image in
// Reconstruct whole team data structure.
let newPlayers = team.players.map { (player) -> Player in
let newTattos = player.tattoos.map { (tattoo) -> Tattoo in
if needChangeImage == tattoo.imageTorso {
return Tattoo(imageTorso: image)
} else {
return tattoo
}
}
return Player(name: player.name, tattoos: newTattos)
}
team = Team(name: team.name, players: newPlayers)
})
})(tattoo.imageTorso)
}
}
}
These codes are ugly, right? And there will not only be awful performance issue caused by going through whole data every network response; another problem is that might causes the retain cycle.
2. Don't hold UIImage in the data array.
Redesign your data structure, and use Kingfisher to help us download image synchronously.
Kingfisher is useful third party library. It provides clean and simple methods to use, and it's highly flexible.
let url = URL(string: "url_of_your_image")
imageView.kf.setImage(with: url)
However, I think the best way for you if you don't want to use Kingfisher is to change your declaration from struct to class.
Unfortunately that's the nature of struct and Swift doesn't offer a way for you modify the collection in-place while iterating over it. But can user enumerated() to get both the index and the element when iterating:
for (p, player) in team.players.enumerated() {
for (t, tattoo) in player.tattoos.enumerated() {
if let url = tattoo.imageTorsoURL {
MyNetFramework.requestImage(from: url, completion: { image in
team.players[p].tattoos[t].imageTorso = image
}
}
}
}

Modifying struct instance variables within a Dispatch closure in Swift

I'm using the DEVELOPMENT-SNAPSHOT-2016-06-06-a version of Swift. I cannot seem to get around this issue, I've tried using #noescape in various places, but I still have the following error:
Closure cannot implicitly capture a mutating self parameter
To better explain, here is a simple example:
public struct ExampleStruct {
let connectQueue = dispatch_queue_create("connectQueue", nil)
var test = 10
mutating func example() {
if let connectQueue = self.connectQueue {
dispatch_sync(connectQueue) {
self.test = 20 // error happens here
}
}
}
}
Something must have changed in these Swift binaries that is now causing my previously working code to break. A workaround I want to avoid is making my struct a class, which does help in fixing the issue. Let me know if there is another way.
I cannot test it, because I'm not using a build with that error, but I'm pretty sure by capturing self explicitly you can fix it:
dispatch_sync(connectQueue) { [self] in
self.test = 20
}
EDIT: Apparently it doesn't work, maybe you can try this (not very nice tbh):
var copy = self
dispatch_sync(connectQueue) {
copy.test = 20
}
self = copy
If you want to read more on why, here is the responsible Swift proposal.
The new dispatch API makes the sync method #noreturn so you wouldn't need the explicit capture:
connectQueue.sync {
test = 20
}
You are using Swift3 since you mentioned a recent dev snapshot of Swift. Try below and let me know if it works:
public struct ExampleStruct {
let connectQueue = DispatchQueue(label: "connectQueue", attributes: .concurrent)//This creates a concurrent Queue
var test = 10
mutating func example() {
connectQueue.sync {
self.test = 20
}
}
}
If you are interested in other types of queues, check these:
let serialQueue = DispatchQueue(label: "YOUR_QUEUE", attributes: .serial)
serialQueue.sync {
//
}
Get the mainQueue asynchronously and synchronously:
DispatchQueue.main.async {
//async operations
}
DispatchQueue.main.sync {
//sync operations
}
And if you are interested in Background:
DispatchQueue.global(attributes: .qosDefault).async {
//async operations
}
You could refer this for new features in Swift3 and for changes to existing version: Migrating to Swift 2.3 or Swift 3 from Swift 2.2