How to determine whether a Swift code is running inside XCode Playground - swift

I'm writing a simple application reading CSV file in Swift and I would like to be able to use the same code in Playground and as an input file to the swift command.
To read a file in Playground I have to use this code
let filePath = XCPlaygroundSharedDataDirectoryURL.URLByAppendingPathComponent("data.csv")
I would like to achieve something like:
#if PLAYGROUND
import XCPlayground
let filePath = XCPlaygroundSharedDataDirectoryURL.URLByAppendingPathComponent("data.csv")
#else
let filePath = NSURL.fileURLWithPath("data.csv")
#endif

The test is quite simple:
let bundleId = NSBundle.mainBundle().bundleIdentifier ?? ""
if bundleId.hasPrefix("com.apple.dt"){
//... Your code
}
But I think you have already seen the problem once you've done that... the import will stop the build elsewhere. I suspect you are trying to build a playground for a framework you have built (if not, I'm not quite sure how the code is being shared)... The way I solved it in the framework was to provide an optional call back hook for the value I wanted to get... so for example
In Framework
public defaultUrlHook : (()->NSURL)? = nil
internal var defaultUrl : NSURL {
return defaultUrlHook?() ?? NSURL.fileURLWithPath("data.csv")
}
In playground
import XCPlayground
import YourFramework
defaultUrlHook = { ()->NSURL in
return XCPlaygroundSharedDataDirectoryURL.URLByAppendingPathComponent("data.csv")
}
//Do your thing....

Related

Swift macOS Process.run() port leak

Current code:
#!/usr/bin/swift
import Foundation
func runSleepProcess() {
let sleepProcess = Process()
sleepProcess.executableURL = URL(fileURLWithPath: "/bin/sleep")
sleepProcess.arguments = ["0"]
try? sleepProcess.run()
sleepProcess.waitUntilExit()
}
while true {
runSleepProcess()
}
Looking in activity monitor, it seems that the mach port usage increases by 1 each loop. Is this expected behavior when running an external process? If not, how do I fix the leak? Thanks.
It's not expected behaviour and this problem is already reported. The workaround for this is using posix_spawn instead of Process.

NSSavePanel name not user editable

I'm a total beginner to OSX GUI programming, so please be gentle with me.
I'm trying some experiments with adding light GUI elements from appkit to a CLI, so I'm working on a very small program to take the contents of a PDF and save it to a text file.
Here's the code I have
import AppKit
import Foundation
import Quartz
func helperReadPDF(_ filename: String) -> String {
let pdata = try! NSData(contentsOfFile: filename) as Data
let pdf = PDFDocument(data: pdata)
return pdf!.string!
}
func selectFile() -> URL? {
let dialog = NSOpenPanel()
dialog.allowedFileTypes = ["pdf"]
guard dialog.runModal() == .OK else { return nil }
return dialog.url
}
func getSaveLocation() -> URL? {
let sa = NSSavePanel()
sa.nameFieldStringValue = "Untitled.txt"
sa.canCreateDirectories = true
sa.allowedFileTypes = ["txt"]
guard sa.runModal() == .OK else { return nil }
return sa.url
}
let file = selectFile()?.path ?? ""
print("where to save?")
let dest = getSaveLocation()!
try! helperReadPDF(file).write(to: dest, atomically: true, encoding: .utf8)
(I know, there are lots of unidiomatic things in here, like all the forced unwrapping and pointlessly converting URLs to paths. I have obscure reasons...)
So this code mostly works: when I run it from a terminal window with swift guitest.swift it'll pop up a file picker window, let me select a pdf file, and then pop up a save dialogue and let me choose the directory, and then save the extracted text from the pdf into that directory.
But it won't let me change the filename. I can highlight the "Untitled.txt" provided by default, I can even get a cursor into the field... but it doesn't respond to keyboard input.
In this previous SO, someone suggested adding a nameFieldStringValue to make it editable, but, as you can see from the above code, I did that, and it doesn't work.
I see from this very old SO that at least in Objective-C-land, you have to initiate some kind of application object in order to accept keyboard input. Is that true today in Swift-land as well?
(Even though for some weird reason you can accept mouse input without doing any of that?!) If so, how do I do that here?
Edit: I get from the comments to that last prior SO I linked that this is probably a terrible idea, and that if I want to learn Mac GUI programming I should do it the heavy way with XCode and storyboards and all the rest. But could you indulge my doing it the stupid way in an effort to try to learn one thing at a time? (I.e., learn the GUI APIs on offer without also trying to learn XCode and Apple's preferred style of architecture at the same time.)
Thanks!
(Swift 4.2 on latest version of OSX. Not using XCode at all.)
Setting the application's ActivationPolicy will make it work.
// Import statements... (import Quartz)
NSApplication.shared.setActivationPolicy(.accessory)
// Functions and so on... (func helper..)

How Can I Run JXA from Swift?

If I have a string variable with JXA source code, is there a way to run that from swift? It seems NSAppleScript only works with AppleScript source.
Here is an example showing how to use OSAKit from Swift to run a JavaScript for Automation script stored in a string:
import OSAKit
let scriptString = "Math.PI";
let script = OSAScript.init(source: scriptString, language: OSALanguage.init(forName: "JavaScript"));
var compileError : NSDictionary?
script.compileAndReturnError(&compileError)
if let compileError = compileError {
print(compileError);
return;
}
var scriptError : NSDictionary?
let result = script.executeAndReturnError(&scriptError)
if let scriptError = scriptError {
print(scriptError);
}
else if let result = result?.stringValue {
print(result)
}
This Swift code was adapted from the Hammerspoon source code (Objective-C).
Why? JXA is a dog in every respect. If you just want to run JS code, the JavaScriptCore Obj-C API is far cleaner and easier. If you want to control "AppleScriptable" applications then use AppleScript—it's the only officially supported† option that works right.
(† There is SwiftAutomation, but Apple haven't bitten and I'm not inclined to support it myself given Apple's chronic mismanagement of Mac Automation. We'll see what happens with WWDC17.)

Winter 2015 / Lecture 10 - Broken Twitter Package

Trying to follow along and code the Smashtag project while watching the Lecture 10 iTunes video.
When I add the dowloaded Twitter package to my Smashtag project, XCode couldn't find the Tweet class when I made reference to it in the TweetTableViewController.
Because of the problem described above, I added the four classes belonging to the Twitter package individually to the project. XCode found the four classes but adding them in this manner generated 11 compile errors.
I'm using XCode Version 6.3 (6D570) which is subsequent to the iOS 8.3 release.
Has anyone else encountered this issue?
Thank you for reading my question.
~ Lee
Possibly not the most-correct (read: best practice) way to do this, but I'm going to chalk it up to doing what it takes to finish the course.
I just went through the list of compile errors and changed the relevant properties to var instead of let. Constants can't be changed and in the new version of Swift they can only be instantiated once. So for the sake of not rewriting too much code, I chose to make certain properties vars instead of lets.
Other bugs I found following the iTunes U course:
The named ‘handler:’ argument needs the name explicitly in a few places.
The simulator will show "TwitterRequest: Couldn\'t discover Twitter account type.” until you go to Settings (inside the simulator) and set the Twitter account. At this point I had to reboot the device, as the call is made in the ViewDidLoad, and thus is only called the first time the view loads. (Alternatively, you could close out the app from the app switcher in the simulator and relaunch that way.)
Here is a gist with corrected code that you can use as a Twitter package that will work with the course and has fixes for the aforementioned bugs, minus the Twitter account setting:
https://gist.github.com/mattpetters/ccf87678ccce0c354398
As Christian R. Jimenez said, "I went to Settings in the Simulated iphone and add my Twitter Account. And everything works perfect." in http://cs193p.m2m.at/cs193p-lecture-10-table-view-winter-2015/. I just added my Twitter Account and tested it, it works!
I had similar problems with the Twitter packages using Swift 2.0 and Xcode 7.2
I'm very new to Swift, so there is a good chance the changes I made are not best practices, but the updated files do work: https://gist.github.com/awaxman11/9c48c0b4c622bffb879f.
For the most part I used Xcode's suggested changes. The two larger changes I made were:
In Tweet.swift I updated the the IndexedKeyword struct's init method to use advanceBy() instead of advance()
In TwitterRequest.swift I updated the signature of NSJSONSerialization to conform to the new error handling system
I've just had a big session fixing the Twitter package files for this same version of Xcode.
It seems that what has broken is that in this version of Swift, constants ('let x...') may only be initialized once, so if a constant is given a value in the declaration ('let x = false'), it may not be changed in the init() function. The Twitter package gives some constants initial values, but then changes the values in the init() function.
My solution to this was to follow the styles suggested in the current version of the Apple Swift language book: declare (many of) the constants as implicitly unwrapped optionals, unconditionally assign a value to them in the init() function (which value may be nil), then test whether any of them are nil, and, if so, return nil from init().
See https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html, click "On This Page" and choose "Failable Initializers"
Also, in TwitterRequest.swift, I needed to add the parameter name 'handler:' in a couple of calls to performTwitterRequest(request, handler: handler).
As an example of constant initialization, in MediaItem.swift:
<< Original Code >>
...
public let aspectRatio: Double = 0
...
init?(data: NSDictionary?) {
var valid = false
if let urlString = data?.valueForKeyPath(TwitterKey.MediaURL) as? NSString {
if let url = NSURL(string: urlString) {
self.url = url
let h = data?.valueForKeyPath(TwitterKey.Height) as? NSNumber
let w = data?.valueForKeyPath(TwitterKey.Width) as? NSNumber
if h != nil && w != nil && h?.doubleValue != 0 {
aspectRatio = w!.doubleValue / h!.doubleValue
valid = true
}
}
}
if !valid {
return nil
}
}
...
<< Updated code >>
...
public let aspectRatio: Double
...
init?(data: NSDictionary?) {
if let urlString = data?.valueForKeyPath(TwitterKey.MediaURL) as? NSString {
if let url = NSURL(string: urlString as String) {
self.url = url
let h = data?.valueForKeyPath(TwitterKey.Height) as? NSNumber
let w = data?.valueForKeyPath(TwitterKey.Width) as? NSNumber
if h != nil && w != nil && h?.doubleValue != 0 {
aspectRatio = w!.doubleValue / h!.doubleValue
return
}
}
}
return nil
}
...

How can one use XCTAssertNil with optional structs?

Update 3/23/2016 I just tested my original sample code below and it all compiles fine in XCode 7.3. Looks like XCTAssertNil was updated along the way to take an expression of type () throws -> Any? Therefore this question and answer may be no longer needed (except for a while with older versions of the compiler.)
I'm writing my first unit tests in XCode with XCTest. I'm unsure how one can take advantage of XCTAssertNil as it seems to only compile when using certain types. It appears it will work with optionals made from classes and built-in primitives, but not structs. How would one go about using this method?
For structs the compiler gives the following error (assuming 'SimpleStruct' is the name of your type):
'SimpleStruct' is not identical to 'AnyObject'
Here's a simple test class to illustrate some of the types that compile okay and other's that don't.
import Cocoa
import XCTest
struct SimpleStruct {
}
class SimpleClass {
}
class Tests: XCTestCase {
func testl() {
var simpleStruct:SimpleStruct? = nil;
var simpleClass:SimpleClass? = nil;
var i:Int? = nil;
var s:String? = nil;
var tuple:(Int,String)? = nil;
XCTAssertNil(simpleStruct); // compile error
XCTAssertNil(simpleClass); // OK
XCTAssertNil(i); // OK
XCTAssertNil(s); // OK
XCTAssertNil(tuple); // compile error
}
}
Update 3/23/2016 Updated for XCode 7.3 (however if you see my edit to the question, it would appear this workaround is no longer needed)
Here is a workaround. I created my own generic function:
func AssertNil<T>(#autoclosure expression: () -> T?, message: String = "",
file: StaticString = #file, line: UInt = #line) {
if (expression() != nil) {
XCTFail(message, file:file, line:line)
}
}
Doesn't seem like this should be necessary. Is this just a result of XCTest originally targeting Objective-C and not being updated/bridged enough for Swift yet?
Edit: I've done enough research to see that AnyObject can be used to represent any class but not structs. However, that then doesn't explain why the code in my original post compiles for Int types and String types. (I read somewhere else that Xcode may auto convert these to NSNumber and NSString for you which might explain why. See http://www.scottlogic.com/blog/2014/09/24/swift-anyobject.html and http://www.drewag.me/posts/swift-s-weird-handling-of-basic-value-types-and-anyobject. I'll try removing my import of Cocoa which imports Foundation to see what happens)
Edit2: XCTest also imports Foundation so I can't test what I wanted to. I could create my own methods and test this. For now, I assume that the auto-conversions are what are allowing the int and string optionals to compile. Seems like XCTest isn't quite ready for prime time with Swift.
Update 8/13/2015: Edited the function to be compatible with XCode 7 beta