How to Get User Input in Apple's Swift Language in a Command Line Tool

How do I get user input in Apple's Swift language in a command line tool?

This is actually not easy in Swift at this point. The simplest way is probably the Objective-C way, using an NSFileHandle with standard input:

import Foundation

var fh = NSFileHandle.fileHandleWithStandardInput()

println("What is your name?")
if let data = fh.availableData {
var str = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Your name is \(str)")
}

Or for continuous input:

println("I will repeat strings back at you:")
waitingOnInput: while true {
if let data = fh.availableData {
var str = NSString(data: data, encoding: NSUTF8StringEncoding)
println(str)
}
}

The possible encodings are shown here.

Input from the keyboard in command line application

I managed to figure it out without dropping down in to C:

My solution is as follows:

func input() -> String {
var keyboard = NSFileHandle.fileHandleWithStandardInput()
var inputData = keyboard.availableData
return NSString(data: inputData, encoding:NSUTF8StringEncoding)!
}

More recent versions of Xcode need an explicit typecast (works in Xcode 6.4):

func input() -> String {
var keyboard = NSFileHandle.fileHandleWithStandardInput()
var inputData = keyboard.availableData
return NSString(data: inputData, encoding:NSUTF8StringEncoding)! as String
}

How do you access command line arguments in Swift?

Apple has released the ArgumentParser library for doing just this:

We’re delighted to announce ArgumentParser, a new open-source library that makes it straightforward — even enjoyable! — to parse command-line arguments in Swift.

https://swift.org/blog/argument-parser/



Swift Argument Parser

https://github.com/apple/swift-argument-parser

Begin by declaring a type that defines the information you need to collect from the command line. Decorate each stored property with one of ArgumentParser's property wrappers, and declare conformance to ParsableCommand.

The ArgumentParser library parses the command-line arguments, instantiates your command type, and then either executes your custom run() method or exits with useful a message.

How do I access program arguments in Swift?

Process was just renamed into CommandLine (since Swift 3.0 August 4 snapshot)

let arguments = CommandLine.arguments

(for some reason this wasn't mentioned on the changelog)

What is the minimally-viable GUI for command-line Swift scripts?

No thanks to Apple's documentation, I finally figured out the magical incantations ‍♂️ needed to launch a simple AppKit/Cocoa GUI from a command-line Swift app that accepts keyboard input. Without Xcode!

This is also needed to accept text input in WKWebViews.

// main.swift // Dylan Sharhon // Tested on Catalina, Nov 2019
import AppKit // import Cocoa if you also need Foundation functionality

let app = NSApplication.shared
app.setActivationPolicy(.regular) // Magic to accept keyboard input and be docked!

let window = NSWindow.init(
contentRect: NSRect(x: 300, y: 300, width: 200, height: 85),
styleMask: [
NSWindow.StyleMask.titled // Magic needed to accept keyboard input
],
backing: NSWindow.BackingStoreType.buffered,
defer: false
)
window.makeKeyAndOrderFront(nil) // Magic needed to display the window

// Text input field
let text = NSTextField.init(string: "")
text.frame = NSRect(x: 10, y: 45, width: 180, height: 25)
window.contentView!.addSubview(text)

// Button
class Target {
@objc func onClick () { // Magic @objc needed for the button action
print(text.stringValue) // Goes to stdout
exit(0)
}
}
let target = Target()
let button = NSButton.init(
title: "OK",
target: target,
action: #selector(Target.onClick)
)
button.frame = NSRect(x:50, y:10, width:100, height:30)
window.contentView!.addSubview(button)

app.run()

To noobs like me: This is the entire app and you can run it with swift main.swift or compile it with swiftc main.swift and rename the resulting (merely 40 KB) executable to whatever you want to be in the menubar.

How do I run a terminal command in a Swift script? (e.g. xcodebuild)

If you don't use command outputs in Swift code, following would be sufficient:

#!/usr/bin/env swift

import Foundation

@discardableResult
func shell(_ args: String...) -> Int32 {
let task = Process()
task.launchPath = "/usr/bin/env"
task.arguments = args
task.launch()
task.waitUntilExit()
return task.terminationStatus
}

shell("ls")
shell("xcodebuild", "-workspace", "myApp.xcworkspace")

Updated: for Swift3/Xcode8

CoreBluetooth on Mac Command line application

Callbacks in most Apple frameworks are delivered through your application's main run loop. If your command-line tool does not have a run loop, it cannot receive callbacks that are sent this way.

Without a runloop, the only way for the framework to invoke your callback would be to run it on another thread, which could lead to weird behavior in an application that didn't expect that.

It's sufficient to add:

let runLoop = RunLoop.current
let distantFuture = Date.distantFuture
while running == true && runLoop.run(mode: RunLoopMode.defaultRunLoopMode, before: distantFuture) {

}

Change OSX keyboard layout(input source) programmatically via terminal or AppleScript?

You can do it using the Text Input Services API:

NSArray* sources = CFBridgingRelease(TISCreateInputSourceList((__bridge CFDictionaryRef)@{ (__bridge NSString*)kTISPropertyInputSourceID : @"com.apple.keylayout.French" }, FALSE));
TISInputSourceRef source = (__bridge TISInputSourceRef)sources[0];
OSStatus status = TISSelectInputSource(source);
if (status != noErr)
/* handle error */;

The dictionary in the first line can use other properties for other criteria for picking an input source.

There's also NSTextInputContext. It has a selectedKeyboardInputSource which can be set to an input source ID to select a different input source. The issue there is that you need an instance of NSTextInputContext to work with and one of those exists only when you have a key window with a text view as its first responder.



Related Topics



Leave a reply



Submit