From 46bf22035963a057cb6072b290dae75d4e49c25f Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Sun, 8 Mar 2015 13:35:02 -0700 Subject: [PATCH] Lists files in Documents directory, automatically updates when files are added or removed --- Delta.xcodeproj/project.pbxproj | 16 +++- Delta/Base.lproj/Main.storyboard | 2 +- Delta/DirectoryContentsDataSource.swift | 121 ++++++++++++++++++++++++ Delta/GamesViewController.swift | 47 ++++++++- 4 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 Delta/DirectoryContentsDataSource.swift diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index a20a28a..a85d275 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + BF46894F1AAC46EF00A2586D /* DirectoryContentsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF46894E1AAC46EF00A2586D /* DirectoryContentsDataSource.swift */; }; BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */; }; BFFA71DF1AAC406100EE9DD1 /* GamesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFA71DE1AAC406100EE9DD1 /* GamesViewController.swift */; }; BFFA71E21AAC406100EE9DD1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFFA71E01AAC406100EE9DD1 /* Main.storyboard */; }; @@ -15,6 +16,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + BF46894E1AAC46EF00A2586D /* DirectoryContentsDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryContentsDataSource.swift; sourceTree = ""; }; BFFA71D71AAC406100EE9DD1 /* Delta.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Delta.app; sourceTree = BUILT_PRODUCTS_DIR; }; BFFA71DB1AAC406100EE9DD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -35,6 +37,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + BF46894D1AAC469800A2586D /* Game Selection */ = { + isa = PBXGroup; + children = ( + BFFA71DE1AAC406100EE9DD1 /* GamesViewController.swift */, + BF46894E1AAC46EF00A2586D /* DirectoryContentsDataSource.swift */, + ); + name = "Game Selection"; + sourceTree = ""; + }; BFFA71CE1AAC406100EE9DD1 = { isa = PBXGroup; children = ( @@ -55,7 +66,7 @@ isa = PBXGroup; children = ( BFFA71DC1AAC406100EE9DD1 /* AppDelegate.swift */, - BFFA71DE1AAC406100EE9DD1 /* GamesViewController.swift */, + BF46894D1AAC469800A2586D /* Game Selection */, BFFA71E01AAC406100EE9DD1 /* Main.storyboard */, BFFA71E31AAC406100EE9DD1 /* Images.xcassets */, BFFA71E51AAC406100EE9DD1 /* LaunchScreen.xib */, @@ -144,6 +155,7 @@ files = ( BFFA71DF1AAC406100EE9DD1 /* GamesViewController.swift in Sources */, BFFA71DD1AAC406100EE9DD1 /* AppDelegate.swift in Sources */, + BF46894F1AAC46EF00A2586D /* DirectoryContentsDataSource.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -237,6 +249,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -289,6 +302,7 @@ BFFA71F81AAC406100EE9DD1 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/Delta/Base.lproj/Main.storyboard b/Delta/Base.lproj/Main.storyboard index b3baf5b..9ba8d56 100644 --- a/Delta/Base.lproj/Main.storyboard +++ b/Delta/Base.lproj/Main.storyboard @@ -25,7 +25,7 @@ - + diff --git a/Delta/DirectoryContentsDataSource.swift b/Delta/DirectoryContentsDataSource.swift new file mode 100644 index 0000000..1dec4b8 --- /dev/null +++ b/Delta/DirectoryContentsDataSource.swift @@ -0,0 +1,121 @@ +// +// DirectoryContentsDataSource.swift +// Delta +// +// Created by Riley Testut on 3/8/15. +// Copyright (c) 2015 Riley Testut. All rights reserved. +// + +import UIKit +import Foundation + +public extension DirectoryContentsDataSource +{ + func URLAtIndexPath(indexPath: NSIndexPath) -> NSURL + { + let URL = self.directoryContents[indexPath.row] + return URL + } +} + +public class DirectoryContentsDataSource: NSObject +{ + public let directoryURL: NSURL + public var tableViewCellIdentifier: String = "Cell" + + public var contentsUpdateHandler: (Void -> Void)? + public var cellConfigurationBlock: ((UITableViewCell, NSIndexPath, NSURL) -> Void)? + + private let fileDescriptor: Int32 + private let directoryMonitorDispatchQueue: dispatch_queue_t + private let directoryMonitorDispatchSource: dispatch_source_t! + + private var directoryContents: [NSURL] + + required public init?(directoryURL: NSURL) + { + self.directoryURL = directoryURL + self.fileDescriptor = open(self.directoryURL.fileSystemRepresentation, O_EVTONLY) + + self.directoryMonitorDispatchQueue = dispatch_queue_create("com.rileytestut.DirectoryContentsDataSource", DISPATCH_QUEUE_SERIAL) + self.directoryMonitorDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, UInt(self.fileDescriptor), DISPATCH_VNODE_WRITE, self.directoryMonitorDispatchQueue) + + self.directoryContents = [NSURL]() + + super.init() + + if self.fileDescriptor < 0 + { + return nil + } + + if self.directoryMonitorDispatchSource == nil + { + close(self.fileDescriptor); + + return nil + } + + dispatch_source_set_event_handler(self.directoryMonitorDispatchSource, { + self.didUpdateDirectoryContents() + }); + + dispatch_source_set_cancel_handler(self.directoryMonitorDispatchSource, { + close(self.fileDescriptor); + }); + + dispatch_resume(self.directoryMonitorDispatchSource); + + self.didUpdateDirectoryContents() + } + + deinit + { + if self.fileDescriptor >= 0 + { + close(self.fileDescriptor); + } + + if self.directoryMonitorDispatchSource != nil + { + dispatch_source_cancel(self.directoryMonitorDispatchSource); + } + } +} + +private extension DirectoryContentsDataSource +{ + func didUpdateDirectoryContents() + { + var error: NSError? = nil + var contents = NSFileManager.defaultManager().contentsOfDirectoryAtURL(self.directoryURL, includingPropertiesForKeys: nil, options:NSDirectoryEnumerationOptions.SkipsSubdirectoryDescendants | NSDirectoryEnumerationOptions.SkipsHiddenFiles, error: &error) as! [NSURL]? + + if let contents = contents + { + self.directoryContents = contents + } + else + { + println(error?.userInfo) + } + + self.contentsUpdateHandler?() + } +} + +extension DirectoryContentsDataSource: UITableViewDataSource +{ + public func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int + { + return self.directoryContents.count + } + + public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell + { + let tableViewCell = tableView.dequeueReusableCellWithIdentifier(self.tableViewCellIdentifier, forIndexPath: indexPath) as! UITableViewCell + + self.cellConfigurationBlock?(tableViewCell, indexPath, self.URLAtIndexPath(indexPath)) + + return tableViewCell + } +} \ No newline at end of file diff --git a/Delta/GamesViewController.swift b/Delta/GamesViewController.swift index d6b3773..5d3af11 100644 --- a/Delta/GamesViewController.swift +++ b/Delta/GamesViewController.swift @@ -10,11 +10,56 @@ import UIKit class GamesViewController: UITableViewController { + let directoryContentsDataSource: DirectoryContentsDataSource? + + override init(style: UITableViewStyle) + { + let error: NSError? = nil; + let documentsDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first as! NSURL + + self.directoryContentsDataSource = DirectoryContentsDataSource(directoryURL: documentsDirectoryURL) + + super.init(style: style) + } + + required init(coder aDecoder: NSCoder) + { + let error: NSError? = nil; + let documentsDirectoryURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first as! NSURL + + self.directoryContentsDataSource = DirectoryContentsDataSource(directoryURL: documentsDirectoryURL) + + super.init(coder: aDecoder) + } override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. + + self.tableView.dataSource = self.directoryContentsDataSource + + self.directoryContentsDataSource!.contentsUpdateHandler = { + dispatch_async(dispatch_get_main_queue(), { + self.tableView.reloadData() + }) + } + + self.directoryContentsDataSource?.cellConfigurationBlock = { (cell, indexPath, URL) in + cell.textLabel?.text = URL.lastPathComponent + } + } + + override func viewDidAppear(animated: Bool) + { + super.viewDidAppear(animated) + + if self.directoryContentsDataSource == nil + { + let alertController = UIAlertController(title: "Games Directory Invalid", message: "Please ensure the current games directory exists, then restart the app.", preferredStyle: .Alert) + alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)) + + self.presentViewController(alertController, animated: true, completion: nil) + } } override func didReceiveMemoryWarning()