Is there a way of producing a playground app? - swift-playground

Xcode newbie.(me.dumb)
First time poster.
I have the following code and it tested flawlessly in Xcode Playground(h/t D.Budd Data Science).
I want to turn the code into a MacOS app and I cannot fathom the correct Project and Settings
to do this or do I need to import the Playground into a Project and modify it?
Also this app needs to run in the background (ie, no Window/UI) as this would destroy the current window scene(MagicMirror), sort of like an Automater app can do. It would have an app icon, of course.
Thanks so much for any help.
import Cocoa
extension String{
// get the file name from string
func fileName() -> String{
return URL(fileURLWithPath: self).deletingPathExtension().lastPathComponent
}
// get the file extension from a string
func fileExtension() -> String{
return URL(fileURLWithPath: self).pathExtension
}
}
func readFile(inputFile: String) -> String{
//split the file extension and file name
let fileExtension = inputFile.fileExtension()
let fileName = inputFile.fileName()
//get the file URL
let fileURL = try! FileManager.default.url(for: .desktopDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let inputFile = fileURL.appendingPathComponent(fileName).appendingPathExtension(fileExtension)
//get the data
do{
let saveData = try String(contentsOf: inputFile)
return saveData
} catch {
return error.localizedDescription
}
}
func writeFile(outputFile: String, stringData: String){
let fileExtension = outputFile.fileExtension()
let fileName = outputFile.fileName()
//get the fileURL
let fileURL = try! FileManager.default.url(for: .desktopDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let outputFile = fileURL.appendingPathComponent(fileName).appendingPathExtension(fileExtension)
//save the data
guard let data = stringData.data(using: .utf8) else {
print("No Can Do")
return
}
do {
try data.write(to: outputFile)
print("data written: /data")
} catch {
print(error .localizedDescription)
}
let myData = readFile(inputFile: "test.txt")
writeFile(outputFile: "output.txt", stringData: myData)
}

Related

xCode playground and writing files to Documents

I'm trying to test some sqlite database calls through XCode's playground. I start with a database in my Playground's Resources folder and try to move it to the Playgrounds Documents folder, however what happens is that a symbolic link is generated pointing back to the file within the Resources folder so I am unable to write to that file. However, If I figure out where the Documents folder is and then copy the file there by hand from the terminal everything works just fine.
So why does the file manager copy command actually create a sym link to rather than copy? And is there any way to actually make this happen? It seems to only be a problem with the Playground. copy from Resource to Documents works fine in the app itself.
some code to test within the playground...
let dirPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask, true)
let docsDir = dirPaths[0]
let destPath = (docsDir as NSString).appendingPathComponent("/data.sqlite")
print(destPath)
let fileMgr = FileManager.default
let srcPath = Bundle.main.path(forResource: "data", ofType:"sqlite")
// This copies the data.sqlite file from Resources to Documents
// ** However in the playground, only a symlink is generated
do {
try fileMgr.copyItem(atPath: srcPath!, toPath: destPath)
} catch let error {
print("Error (during copy): \(error.localizedDescription)")
}
Rod, It's too late but, I figure this out.
Context:
I add one playground to my project in the same workspace
To make the playground works with my project, I create a framework
I added the Store.db file to the framework target
I haven't added the Store.db to the Playground as a Resource
Swift 3
Playground
#testable import MyAppAsFramework
func copyMockDBToDocumentsFolder(dbPath: String) {
let localDBName = "Store.db"
let documentPath = dbPath / localDBName
let dbFile = Bundle(for: MyAppAsFrameworkSomeClass.self).path(forResource: localDBName.deletingPathExtension, ofType: localDBName.pathExtension)
FileManager.copyFile(dbFile, toFolderPath: documentPath)
}
copyMockDBToDocumentsFolder(dbPath: "The documents path")
Things like / copyFile(x) and the others are operator overloads and extensions
extension String {
public var pathExtension: String {
get {
return (self as NSString).pathExtension
}
}
public var deletingPathExtension: String {
get {
return (self as NSString).deletingPathExtension
}
}
public func appendingPath(_ path: String) -> String {
let nsSt = self as NSString
return nsSt.appendingPathComponent(path)
}
}
infix operator / : MultiplicationPrecedence
public func / (left: String, right: String) -> String {
return left.appendingPath(right)
}
extension FileManager {
class func copyFile(_ filePath: String?, toFolderPath: String?) -> Bool {
guard let file = filePath else { return false }
guard let toFolder = toFolderPath else { return false }
var posibleError: NSError?
do {
try FileManager.default.copyItem(atPath: file, toPath:toFolder)
} catch let error as NSError {
posibleError = error
print("CAN'T COPY \(error.localizedDescription)")
}
return posibleError == nil
}
}

Append swift 2.0 and xcode 7 [duplicate]

I already have read Read and write data from text file
I need to append the data (a string) to the end of my text file.
One obvious way to do it is to read the file from disk and append the string to the end of it and write it back, but it is not efficient, especially if you are dealing with large files and doing in often.
So the question is "How to append string to the end of a text file, without reading the file and writing the whole thing back"?
so far I have:
let dir:NSURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last as NSURL
let fileurl = dir.URLByAppendingPathComponent("log.txt")
var err:NSError?
// until we find a way to append stuff to files
if let current_content_of_file = NSString(contentsOfURL: fileurl, encoding: NSUTF8StringEncoding, error: &err) {
"\(current_content_of_file)\n\(NSDate()) -> \(object)".writeToURL(fileurl, atomically: true, encoding: NSUTF8StringEncoding, error: &err)
}else {
"\(NSDate()) -> \(object)".writeToURL(fileurl, atomically: true, encoding: NSUTF8StringEncoding, error: &err)
}
if err != nil{
println("CANNOT LOG: \(err)")
}
Here's an update for PointZeroTwo's answer in Swift 3.0, with one quick note - in the playground testing using a simple filepath works, but in my actual app I needed to build the URL using .documentDirectory (or which ever directory you chose to use for reading and writing - make sure it's consistent throughout your app):
extension String {
func appendLineToURL(fileURL: URL) throws {
try (self + "\n").appendToURL(fileURL: fileURL)
}
func appendToURL(fileURL: URL) throws {
let data = self.data(using: String.Encoding.utf8)!
try data.append(fileURL: fileURL)
}
}
extension Data {
func append(fileURL: URL) throws {
if let fileHandle = FileHandle(forWritingAtPath: fileURL.path) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(self)
}
else {
try write(to: fileURL, options: .atomic)
}
}
}
//test
do {
let dir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! as URL
let url = dir.appendingPathComponent("logFile.txt")
try "Test \(Date())".appendLineToURL(fileURL: url as URL)
let result = try String(contentsOf: url as URL, encoding: String.Encoding.utf8)
}
catch {
print("Could not write to file")
}
Thanks PointZeroTwo.
You should use NSFileHandle, it can seek to the end of the file
let dir:NSURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last as NSURL
let fileurl = dir.URLByAppendingPathComponent("log.txt")
let string = "\(NSDate())\n"
let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
if NSFileManager.defaultManager().fileExistsAtPath(fileurl.path!) {
var err:NSError?
if let fileHandle = NSFileHandle(forWritingToURL: fileurl, error: &err) {
fileHandle.seekToEndOfFile()
fileHandle.writeData(data)
fileHandle.closeFile()
}
else {
println("Can't open fileHandle \(err)")
}
}
else {
var err:NSError?
if !data.writeToURL(fileurl, options: .DataWritingAtomic, error: &err) {
println("Can't write \(err)")
}
}
A variation over some of the posted answers, with following characteristics:
based on Swift 5
accessible as a static function
appends new entries to the end of the file, if it exists
creates the file, if it doesn't exist
no cast to NS objects (more Swiftly)
fails silently if the text cannot be encoded or the path does not exist
class Logger {
static var logFile: URL? {
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil }
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let dateString = formatter.string(from: Date())
let fileName = "\(dateString).log"
return documentsDirectory.appendingPathComponent(fileName)
}
static func log(_ message: String) {
guard let logFile = logFile else {
return
}
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
let timestamp = formatter.string(from: Date())
guard let data = (timestamp + ": " + message + "\n").data(using: String.Encoding.utf8) else { return }
if FileManager.default.fileExists(atPath: logFile.path) {
if let fileHandle = try? FileHandle(forWritingTo: logFile) {
fileHandle.seekToEndOfFile()
fileHandle.write(data)
fileHandle.closeFile()
}
} else {
try? data.write(to: logFile, options: .atomicWrite)
}
}
}
Here is a way to update a file in a much more efficient way.
let monkeyLine = "\nAdding a šŸµ to the end of the file via FileHandle"
if let fileUpdater = try? FileHandle(forUpdating: newFileUrl) {
// Function which when called will cause all updates to start from end of the file
fileUpdater.seekToEndOfFile()
// Which lets the caller move editing to any position within the file by supplying an offset
fileUpdater.write(monkeyLine.data(using: .utf8)!)
// Once we convert our new content to data and write it, we close the file and thatā€™s it!
fileUpdater.closeFile()
}
Here's a version for Swift 2, using extension methods on String and NSData.
//: Playground - noun: a place where people can play
import UIKit
extension String {
func appendLineToURL(fileURL: NSURL) throws {
try self.stringByAppendingString("\n").appendToURL(fileURL)
}
func appendToURL(fileURL: NSURL) throws {
let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
try data.appendToURL(fileURL)
}
}
extension NSData {
func appendToURL(fileURL: NSURL) throws {
if let fileHandle = try? NSFileHandle(forWritingToURL: fileURL) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.writeData(self)
}
else {
try writeToURL(fileURL, options: .DataWritingAtomic)
}
}
}
// Test
do {
let url = NSURL(fileURLWithPath: "test.log")
try "Test \(NSDate())".appendLineToURL(url)
let result = try String(contentsOfURL: url)
}
catch {
print("Could not write to file")
}
In order to stay in the spirit of #PointZero Two.
Here an update of his code for Swift 4.1
extension String {
func appendLine(to url: URL) throws {
try self.appending("\n").append(to: url)
}
func append(to url: URL) throws {
let data = self.data(using: String.Encoding.utf8)
try data?.append(to: url)
}
}
extension Data {
func append(to url: URL) throws {
if let fileHandle = try? FileHandle(forWritingTo: url) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(self)
} else {
try write(to: url)
}
}
}
Update: I wrote a blog post on this, which you can find here!
Keeping things Swifty, here is an example using a FileWriter protocol with default implementation (Swift 4.1 at the time of this writing):
To use this, have your entity (class, struct, enum) conform to this protocol and call the write function (fyi, it throws!).
Writes to the document directory.
Will append to the text file if the file exists.
Will create a new file if the text file doesn't exist.
Note: this is only for text. You could do something similar to write/append Data.
import Foundation
enum FileWriteError: Error {
case directoryDoesntExist
case convertToDataIssue
}
protocol FileWriter {
var fileName: String { get }
func write(_ text: String) throws
}
extension FileWriter {
var fileName: String { return "File.txt" }
func write(_ text: String) throws {
guard let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
throw FileWriteError.directoryDoesntExist
}
let encoding = String.Encoding.utf8
guard let data = text.data(using: encoding) else {
throw FileWriteError.convertToDataIssue
}
let fileUrl = dir.appendingPathComponent(fileName)
if let fileHandle = FileHandle(forWritingAtPath: fileUrl.path) {
fileHandle.seekToEndOfFile()
fileHandle.write(data)
} else {
try text.write(to: fileUrl, atomically: false, encoding: encoding)
}
}
}
All answers (as of now) recreate the FileHandle for every write operation. This may be fine for most applications, but this is also rather inefficient: A syscall is made, and the filesystem is accessed each time you create the FileHandle.
To avoid creating the filehandle multiple times, use something like:
final class FileHandleBuffer {
let fileHandle: FileHandle
let size: Int
private var buffer: Data
init(fileHandle: FileHandle, size: Int = 1024 * 1024) {
self.fileHandle = fileHandle
self.size = size
self.buffer = Data(capacity: size)
}
deinit { try! flush() }
func flush() throws {
try fileHandle.write(contentsOf: buffer)
buffer = Data(capacity: size)
}
func write(_ data: Data) throws {
buffer.append(data)
if buffer.count > size {
try flush()
}
}
}
// USAGE
// Create the file if it does not yet exist
FileManager.default.createFile(atPath: fileURL.path, contents: nil)
let fileHandle = try FileHandle(forWritingTo: fileURL)
// Seek will make sure to not overwrite the existing content
// Skip the seek to overwrite the file
try fileHandle.seekToEnd()
let buffer = FileHandleBuffer(fileHandle: fileHandle)
for i in 0..<count {
let data = getData() // Your implementation
try buffer.write(data)
print(i)
}

Swift. Can`t save file to DocumentDirectory. Whats wrong?

Here`s my code:
let fileName = "someFileName"
func saveDataToFile(urlStr:String){
let url = NSURL(string: urlStr)
var data:NSData!
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let directory = paths[0]
let filePath = directory.stringByAppendingPathComponent(self.fileName)
print(filePath)//prints /Users/.../Library/Developer/CoreSimulator/Devices/1013B940-6FAB-406B-96FD-1774C670A91E/data/Containers/Data/Application/2F7139D6-C137-48BF-96F6-7579821B17B7/Documents/fileName
let fileManager = NSFileManager.defaultManager()
data = NSData(contentsOfURL: url!)
print(data) // prints a lot of data
if data != nil{
fileManager.createFileAtPath(filePath, contents: data, attributes: nil)
}
}
Now I want to read this data:
func readDataFromFile(){
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let directory = paths[0]
let filePath = directory.stringByAppendingPathComponent(self.fileName)
print(filePath) // prints the same path
let fileManager = NSFileManager.defaultManager()
if fileManager.fileExistsAtPath(filePath){
data = fileManager.contentsAtPath(filePath)
}else{
print("*****BAD*****") // always prints this which means that file wasn`t created
}
}
What`s wrong with the first func? What is the right way to save file to DocumentDirectory?
OK, in this case the answer was following:
First need to create directory (aka folder) and only after that create file inside that directory.
Added to code this:
let fullDirPath = directory.stringByAppendingPathComponent(folderName)
let filePath = fullDirPath.stringByAppendingPathComponent(fileName)
do{
try fileManager.createDirectoryAtPath(fullDirPath, withIntermediateDirectories: false, attributes: nil)
}catch let error as NSError{
print(error.localizedDescription)
}
And as I said after this you create your file:
fileManager.createFileAtPath(filePath, contents: data, attributes: nil)
Thanks to Eric.D
Hope someone will find this useful.

Swift 2.0: Could not find overload. Need Explanation

I have the following code in my program.
var detailItem: RSSItem? {
didSet {
self.configureView()
}
}
func configureView() {
if let item: RSSItem = self.detailItem
{
if let webView = self.itemWebView
{
if let templateURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("template", ofType: "html")!)?
{
if var template = NSString(contentsOfURL: templateURL, encoding: NSUTF8StringEncoding)?
{
if let title = item.title
{
template = template.stringByReplacingOccurrencesOfString("###TITLE###", withString: title)
}
if let content = item.content
{
template = template.stringByReplacingOccurrencesOfString("###CONTENT###", withString: content)
}
else if let description = item.itemDescription
{
template = template.stringByReplacingOccurrencesOfString("###CONTENT###", withString: description)
}
if let date = item.pubDate
{
var formatter = NSDateFormatter()
formatter.dateFormat = "MMM dd, yyyy"
template = template.stringByReplacingOccurrencesOfString("###DATE###", withString: formatter.stringFromDate(date))
}
if let author = item.author
{
template = template.stringByReplacingOccurrencesOfString("###AUTHOR###", withString: author)
}
webView.loadHTMLString(template, baseURL: nil)
}
}
else
{
if let content = item.content
{
webView.loadHTMLString(content, baseURL: nil)
}
else if let description = item.itemDescription
{
webView.loadHTMLString(description, baseURL: nil)
}
}
}
}
}
On the line:
if let templateURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("template", ofType: "html")!)?
I get the following error:
-Could not find an overload for 'pathForResource' that accepts the supplied arguments
Can someone explain this error to me and maybe suggest a solution. I have been reading through various searches on Google but can't quite seem to get what I need.
Thanks in advance.
NSURL(fileURLWithPath:) is now return a NSURL so just don't use if let here.
#available(iOS 2.0, *)
init(fileURLWithPath path: String, isDirectory isDir: Bool)
init(fileURLWithPath path: String) // Better to use initFileURLWithPath:isDirectory: if you know if the path is a directory vs non-directory, as it saves an i/o.
#available(iOS 2.0, *)
class func fileURLWithPath(path: String, isDirectory isDir: Bool) -> NSURL
class func fileURLWithPath(path: String) -> NSURL // Better to use fileURLWithPath:isDirectory: if you know if the path is a directory vs non-directory, as it saves an i/o.
for the line: if var template = NSString(contentsOfURL: templateURL, encoding: NSUTF8StringEncoding)?
Its decleration is:
convenience init(contentsOfURL url: NSURL, encoding enc: UInt) throws
convenience init(contentsOfFile path: String, encoding enc: UInt) throws
So it not returns optional NSString either, so you cannot use if let again. You have to use do-try-catch here.
Apple's document about this:
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID508
Also, you should use multiple unwrapping optional:
if let x = OptionalX, y = OptionalY { ... }
Your code is a pyramid of doom :)

Cocoa/Swift: Loop through names of folder in path

I'm currently programming an os x application with swift, but I can't figure how to loop through or even get the names of all folders at a certain path. Maybe something with fm.enumeratorAtPath?
I use enumeratorAtURL. Here's some code that shows an example of how to print the directories in the user's home directory.
if let dirURL = NSURL(fileURLWithPath: NSHomeDirectory()) {
let keys = [NSURLIsDirectoryKey, NSURLLocalizedNameKey]
let fileManager = NSFileManager.defaultManager()
let enumerator = fileManager.enumeratorAtURL(
dirURL,
includingPropertiesForKeys: keys,
options: (NSDirectoryEnumerationOptions.SkipsPackageDescendants |
NSDirectoryEnumerationOptions.SkipsSubdirectoryDescendants |
NSDirectoryEnumerationOptions.SkipsHiddenFiles),
errorHandler: {(url, error) -> Bool in
return true
}
)
while let element = enumerator?.nextObject() as? NSURL {
var getter: AnyObject?
element.getResourceValue(&getter, forKey: NSURLIsDirectoryKey, error: nil)
let isDirectory = getter! as Bool
element.getResourceValue(&getter, forKey: NSURLLocalizedNameKey, error: nil)
let itemName = getter! as String
if isDirectory {
println("\(itemName) is a directory in \(dirURL.absoluteString)")
//do something with element here.
}
}
}

Resources