`
* Part 1 - Primitive Functions
<<#experiment0>>
* Experiment 0 - How not to make a window
If we start Xcode, do `File->New->Project` and select `Cocoa Application`
then set the language to `Swift` then Xcode will create a file called
`ApplicationDelegate.swift` with the following content:
@include{file = "../includes/swift/AppDelegate.swift"} DO NOT EDIT BELOW
//
// AppDelegate.swift
// Project
//
// Created by joseph armstrong on 28/12/15.
// Copyright © 2015 joseph armstrong. All rights reserved.
//
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
}
@END
This file cannot be run outside Xcode - here's what happens if we try to
run this directly in a terminal:
swift AppDelegate.swift
AppDelegate.swift:11:1: error: 'NSApplicationMain' attribute cannot be
used in a module that contains top-level code @NSApplicationMain
^
AppDelegate.swift:1:1: note: top-level code defined in this source file
//
^
So this doesn't work - but after a little Googling and some small
edits we can modify this so that we can create a window in the shell, this is show in the next
section.
<<#experiment1>>
* Experiment 1 - A simple window
This window is created with the following code - which is
derived from `AppDelegate.swift`.
@include{ file="../includes/swift/experiment1.swift"} DO NOT EDIT BELOW
// experiment1.swift
// run with:
// $ swift experiment1.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
let window = NSWindow()
func applicationDidFinishLaunching(aNotification: NSNotification) {
window.makeKeyAndOrderFront(window)
window.level = 1
}
}
let app = NSApplication.sharedApplication()
let controller = AppDelegate()
app.delegate = controller
app.run()
@END
To run the program store the above in a file `experiment1.swift` and run
the program in a terminal with the following command:
$ swift experiment1.swift
<<#experiment2>>
* Experiment 2 - A window with a title and specific size
Now I'll make a larger window and add a title:
Here I've make the window a specific size a title and some controls
to the title bar:
@include{ file = "../includes/swift/experiment2.swift"} DO NOT EDIT BELOW
// experiment2.swift
// run with:
// $ swift experiment2.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate
{
let window = NSWindow()
func applicationDidFinishLaunching(aNotification: NSNotification)
{
window.setContentSize(NSSize(width:600, height:200))
window.styleMask = NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask |
NSResizableWindowMask
window.opaque = false
window.center();
window.title = "Experiment 2"
window.makeKeyAndOrderFront(window)
window.level = 1
}
}
let app = NSApplication.sharedApplication()
let controller = AppDelegate()
app.delegate = controller
app.run()
@END
* Adding controls
Now we can make a simple window. The next step is to add some controls.
I've added one control in each experiment.
<<#experiment3>>
* Experiment 3 - A window with a button
Here's a window with a button in it - note that nothing happens when
we click the button - in experiment 5 I'll add a click action to the
button.
The code is as follows:
@include{file = "../includes/swift/experiment3.swift"} DO NOT EDIT BELOW
// experiment3.swift
// run with:
// $ swift experiment3.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate
{
let window = NSWindow()
func applicationDidFinishLaunching(aNotification: NSNotification)
{
window.setContentSize(NSSize(width:600, height:200))
window.styleMask = NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask |
NSResizableWindowMask
window.opaque = false
window.center();
window.title = "Experiment 3"
let button = NSButton(frame: NSMakeRect(20, 100, 180, 30))
button.bezelStyle = .ThickSquareBezelStyle
button.title = "Click Me"
button.target = NSApp
window.contentView!.addSubview(button)
window.makeKeyAndOrderFront(window)
window.level = 1
}
}
let app = NSApplication.sharedApplication()
let controller = AppDelegate()
app.delegate = controller
app.run()
@END
*Exercise:* Try Googling `ThickSquareBezelStyle` you should be able to
find all the available button styles. Try making small edits to the
program to see what happens.
<<#experiment4>>
* Experiment 4 - A window with a text field
This is actually a `TextView` where I've set the background color to the window
background color.
*Exercise:* Make a better or alternative version.
@include{file="../includes/swift/experiment4.swift"} DO NOT EDIT BELOW
// experiment4.swift
// run with:
// $ swift experiment4.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate
{
let window = NSWindow()
func applicationDidFinishLaunching(aNotification: NSNotification)
{
window.setContentSize(NSSize(width:600, height:200))
window.styleMask = NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask |
NSResizableWindowMask
window.opaque = false
window.center();
window.title = "Experiment 4"
let text = NSTextView(frame: NSMakeRect(20, 150, 180, 30))
text.string = "Some Text"
text.editable = false
text.backgroundColor = window.backgroundColor
text.selectable = false
window.contentView!.addSubview(text)
window.makeKeyAndOrderFront(window)
window.level = 1
}
}
let app = NSApplication.sharedApplication()
let controller = AppDelegate()
app.delegate = controller
app.run()
@END
<<#experiment5>>
* Experiment 5 - Connecting the button to the text field
@include{file="../includes/swift/experiment4.swift"} DO NOT EDIT BELOW
// experiment4.swift
// run with:
// $ swift experiment4.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate
{
let window = NSWindow()
func applicationDidFinishLaunching(aNotification: NSNotification)
{
window.setContentSize(NSSize(width:600, height:200))
window.styleMask = NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask |
NSResizableWindowMask
window.opaque = false
window.center();
window.title = "Experiment 4"
let text = NSTextView(frame: NSMakeRect(20, 150, 180, 30))
text.string = "Some Text"
text.editable = false
text.backgroundColor = window.backgroundColor
text.selectable = false
window.contentView!.addSubview(text)
window.makeKeyAndOrderFront(window)
window.level = 1
}
}
let app = NSApplication.sharedApplication()
let controller = AppDelegate()
app.delegate = controller
app.run()
@END
@include{file="../includes/swift/experiment5.swift"} DO NOT EDIT BELOW
// experiment5.swift
// run with:
// $ swift experiment5.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate
{
var text = NSTextView(frame: NSMakeRect(20, 150, 180, 30))
let window = NSWindow()
@IBAction func myAction(sender: AnyObject) {
text.string = "Wow I've been clicked"
}
func applicationDidFinishLaunching(aNotification: NSNotification)
{
window.setContentSize(NSSize(width:600, height:200))
window.styleMask = NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask |
NSResizableWindowMask
window.opaque = false
window.center();
window.title = "Experiment 5"
let button = NSButton(frame: NSMakeRect(20, 100, 280, 30))
button.bezelStyle = .ThickSquareBezelStyle
button.title = "Click me and the text will change"
button.target = self;
button.action = "myAction:" // Note the colon
window.contentView!.addSubview(button)
text.string = "Some Text"
text.editable = false
text.backgroundColor = window.backgroundColor
text.selectable = false
window.contentView!.addSubview(text)
window.makeKeyAndOrderFront(window)
window.level = 1
}
}
let app = NSApplication.sharedApplication()
let controller = AppDelegate()
app.delegate = controller
app.run()
@END
This code is a bit of a mess, it had to be carefully constructed so
that the variable `text` was defined *before* the button callback
function `myAction` (since `text` is referred to in the body of the
`myAction` function).
This is a tad tricky and would indeed be impossible if the relationships
between the controls could not be ordered (for example, it would be
impossible to create two buttons `b1` and `b2` which when clicked
change the labels of the ``other'' button) - I'll solve this problem
later by allowing the click action on a button to be defined *after*
the all the controls have been created.
<<#experiment6>>
* Experiment 6 - A window with an image
The image is from Sydney Padua's most excellent book [[http://sydneypadua.com/2dgoggles/][The Thrilling
Adventures of Lovelace and Babbage]]
@include{file="../includes/swift/experiment6.swift"} DO NOT EDIT BELOW
// experiment6.swift
// run with:
// $ swift experiment6.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate
{
let window = NSWindow()
func applicationDidFinishLaunching(aNotification: NSNotification)
{
window.setContentSize(NSSize(width:500, height:260))
window.styleMask = NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask |
NSResizableWindowMask
window.opaque = false
window.center();
window.title = "Experiment 6"
// load and display an image
let path = "../images/ada.png"
let img = NSImage(contentsOfFile: path)!
let imgView = NSImageView(frame: NSMakeRect(10, 10, 480, 240))
imgView.image = img
window.contentView!.addSubview(imgView)
window.makeKeyAndOrderFront(window)
window.level = 1
}
}
let app = NSApplication.sharedApplication()
let controller = AppDelegate()
app.delegate = controller
app.run()
@END
* Part 2 - Abstractions
The goal of this part is to make a simple set of functions that hides
most of the detail of making windows and adding controls to them.
The end result is going to be a program fragment that looks like this:
let entry1 = make_entry(window, (200, 80, 180, 30), "1")
let text1 = make_text(window, (20, 80, 180, 30), "Hello from me")
let text2 = make_text(window, (20, 120, 180, 30), "Another field")
let button1 = make_button(window, (120, 40, 80, 30), "Click")
// make a click function
let f1 = {() -> Bool in
text1.string = "Callback worked"
print(entry1.textStorage!.string)
text2.string = entry1.textStorage!.string
return true} //
button1.onclick = f1
All the details of making the controls will be hidden in functions
like `make_text`, `make_button` and so ``wiring up'' the controls will
be done by hooking callback functions on the buttons.
To do this needs some trickery, so in the sections that follow I'll
first explain the techniques and then refactor the code in the Part 1
into a form that fits my purpose.
Firt I'll look at parameter passing in Swift.
<<#parameters>>
* Parameter naming gobbledygook
> Swift parameter passing is totally unobvious.
I had expected that the ``obvious'' way to declare and call a function
would be something like this:
@include{file="../includes/swift/funcs1.swift"} DO NOT EDIT BELOW
// funcs1.swift
func add(x:Int, y:Int, z:Int) -> Int {
return x+y+z
}
print("add=", add(1,2,3))
@END
But oh dear:
swift funcs1.swift
funcs1.swift:5:18: error: missing argument labels 'y:z:' in call
print("add=", add(1,2,3))
^
y: z:
A little research revealed that the correct way to call
`add` was to use the totally unobvious syntax:
@include{file = "../includes/swift/funcs2.swift"} DO NOT EDIT BELOW
// funcs2.swift
func add(x:Int, y:Int, z:Int) -> Int {
return x+y+z
}
print("add =", add(1,y:2,z:3))
@END
Inconsistent *moi*? - the first parameter name is omitted. All the other parameters
must be present in the same order as the definition. Great shades of objective C!
This horrible syntax works:
> swift funcs2.swift
add = 6
Better though again unobvious is to prefix each argument in the function definition with
underscore `_` like this:
@include{file="../includes/swift/funcs3.swift"} DO NOT EDIT BELOW
// funcs3.swift
func add(x:Int, _ y:Int, _ z:Int) -> Int {
return x+y+z
}
print("add=", add(1,2,3))
@END
Now at least we can call the function in the same way as we'd do in
just about every other programming language under the sun.
* A button callback function
To make a callback function for a button we have to step back and then understand how
closures work, and then we can make a callback function.
I'll start by defining a simple class with an instance variable:
@include{file="../includes/swift/classes1.swift"} DO NOT EDIT BELOW
class Demo {
var firstname: String = "fred"
}
let x = Demo()
print(x.firstname)
x.firstname = "joe"
print(x.firstname)
@END
This should look familiar to you if you've programmed in an OO language.
Running this we see the following:
> swift classes1.swift
fred
joe
Now I'll do the same things with a functional argument:
@include{file = "../includes/swift/closures1.swift"} DO NOT EDIT BELOW
class Demo {
var call: () -> Bool = {() -> Bool in true}
}
let x = Demo()
print(x.call())
var i = 10
var f = {() -> Bool in print("hello I'm a callback and i =", i); return true}
x.call = f
print(x.call())
i = 20
print(x.call())
@END
Running this:
true
hello I'm a callback and i = 10
true
hello I'm a callback and i = 20
true
> Which to my mind is horrendous. The closure `f` does not capture the value of
`i` at the time when `f` is defined, changing `i` *after* `f` was defined
make nonsense of the idea of a closure.
If we are extremely careful we *can* use this mechanism to add a callback facility to
buttons. We'll do this and make a custom class, by inheriting the properties
of `NSButton` and adding our own callback routine:
class MyButton: NSButton {
var onclick: () -> Bool = {() -> Bool in true}
func myclick(sender: AnyObject) {
self.onclick()
}
}
and to make a button I call `make_button`
func make_button(window: NSWindow,
_ size:(x:Int, y:Int, width:Int, ht:Int),
_ title:String
) -> MyButton {
let button = MyButton()
button.frame = NSMakeRect(CGFloat(size.x), CGFloat(size.y),
CGFloat(size.width), CGFloat(size.ht))
button.title = title
button.bezelStyle = .ThickSquareBezelStyle
button.target = button
button.action = "myclick:"
window.contentView!.addSubview(button)
return button
}
Now we can make some objects and add an `onclick` callback to the buttons (just like
jquery) - the pseudo code to do this will look something like this:
... create a window ...
text1 = make_text(window, Size, Text)
entry1 = make_entry(window, Size, Default)
button1 = make_button(window, Size, Text)
F = ... a function involving text1, entry1, ... etc.
button1.onclick = F
<<#combined>>
* Putting it all together
Now let's put it all together:
The code is rather long - but I was able to pull out most of
the mess into simple reusable functionbs.
> The code to create the window and add controls was easy
and Xcode was not used.
@include{file="../includes/swift/combined1.swift"} DO NOT EDIT BELOW
// combined1.swift
// run with:
// $ swift combined1.swift
import Cocoa
class MyButton: NSButton {
var onclick: () -> Bool = {() -> Bool in true}
func myclick(sender: AnyObject) {
self.onclick()
}
}
func make_button(window: NSWindow,
_ size:(x:Int, y:Int, width:Int, ht:Int),
_ title:String
) -> MyButton {
let button = MyButton()
button.frame = NSMakeRect(CGFloat(size.x), CGFloat(size.y),
CGFloat(size.width), CGFloat(size.ht))
button.title = title
button.bezelStyle = .ThickSquareBezelStyle
button.target = button
button.action = "myclick:"
window.contentView!.addSubview(button)
return button
}
func make_entry(window:NSWindow,
_ size:(x:Int, y:Int, width:Int, ht:Int),
_ str: String
) -> NSTextView {
let text = NSTextView(frame: NSMakeRect(10, 140, 40, 20))
text.frame = NSMakeRect(CGFloat(size.x), CGFloat(size.y),
CGFloat(size.width), CGFloat(size.ht))
text.string = str
text.editable = true
text.selectable = true
window.contentView!.addSubview(text)
return text
}
func make_text(window:NSWindow,
_ size:(x:Int, y:Int, width:Int, ht:Int),
_ str: String
) -> NSTextView {
let text = NSTextView(frame: NSMakeRect(10, 140, 40, 20))
text.frame = NSMakeRect(CGFloat(size.x), CGFloat(size.y),
CGFloat(size.width), CGFloat(size.ht))
text.string = str
text.editable = false
text.backgroundColor = window.backgroundColor
text.selectable = false
window.contentView!.addSubview(text)
return text
}
func make_window(w: Int, _ h: Int, _ title: String) -> NSWindow {
let window = NSWindow()
window.setContentSize(NSSize(width:w, height:h))
window.styleMask = NSTitledWindowMask | NSClosableWindowMask |
NSMiniaturizableWindowMask | NSResizableWindowMask
window.opaque = false
window.center();
window.title = title
return window
}
class AppDelegate: NSObject, NSApplicationDelegate
{
let window = make_window(400, 200, "My title")
func applicationDidFinishLaunching(aNotification: NSNotification)
{
let entry1 = make_entry(window, (200, 80, 180, 30), "1")
let text1 = make_text(window, (20, 80, 180, 30), "Hello from me")
let text2 = make_text(window, (20, 120, 180, 30), "Another field")
let button1 = make_button(window, (120, 40, 80, 30), "Click")
// make a click function
let f1 = {() -> Bool in
text1.string = "Callback worked"
print(entry1.textStorage!.string)
text2.string = entry1.textStorage!.string
return true} //
button1.onclick = f1
window.makeKeyAndOrderFront(window)
window.makeMainWindow()
window.level = 1
}
}
let app = NSApplication.sharedApplication()
app.setActivationPolicy(.Regular)
let controller = AppDelegate()
app.delegate = controller
app.run()
@END
<<#open>>
* Open Problems
I had hoped to write:
let window = make_window(400, 200, "My Title")
let entry1 = make_text(window, ...)
But for some reason this does not work, so I have to move the call to `make_window`
to the initialisation part of the class. I haven't a clue why.
<<#swift_fpl>>
* Swift as a functional language
The bad
+ Verbose syntax - types are declared rather than inferred
+ Mutable data types
+ Weird mix of Classes, Structs and functions
+ No concurrency model
The good
+ It has a REPL
+ I can write apps *outside* XCode
I *like* the syntax of exceptions if the function X can raise an exception
then one must qualify call with ! ? or use an explicit try syntax
In Erlang term the use of ! and ? is easy to explain
Imagine an Erlang function f(X) that returns {ok, Val} when `Val = f(X)` or
otherwise `{eror, Why}` if the function could not compute a value for some argument `X`
The Swift compiler convention if implemented in Erlang would require `f(X)` to be
evaluated within a `catch` statement, like this:
case (catch f(X)) of
{ok, Val} ->
...;
{error, Why} ->
...
end
otherwise an error would be indicted.
If we were absolutely sure that `{ok, Val}` would be returned we'd write:
Val = f!(X)
Which is equivalent to the Erlang
{ok, Val} = f(X)
The ? convention unwraps `{ok, Val}` into `Val` or `{error, Why}` into `nil` in Swift.
<<#comments>>
* Comments on Swift
I've been programming Swift for a couple of weeks now. Do I like it?
Swift is widely marketed as a functional language - so it's
interesting to see how well it shapes up as a functional programming
language - or at least to see how compares to other FPLs that I am
familiar with.
Sequential FPLs gain a lot of power from the mechanisms they offer.
The offer (in varying degrees) type systems that (claim to) prevent
(some run-time) errors. Pattern matching syntaxes make the
programs very short. Immutable data structures simplifies
reasoning about the programming and debugging. Higher order functions
add to power of the language be treating functions as first
class data.
Where Swift shines it is in the integratiion with the underlying Objective C
frameworks on the Mac. To this extent Swift programming is a much
more attractive proposition than programming in C or Objective-C but
but this is not because Swift is a good language rather that C and
objective-C are bad languages for writing user-space application in.
The C family of languages (C, C++, Objective-C) are fine for writing
operating system but not for writing the majority of user-space
applications - here things like Python or visual-basic are far better.
Swift is a good replacement for
contexts in which Objective-C would be used.
If you're coming form Erlang/Haskell world you'll think ``Swift is
verbose and a bit of a mess`` but if you're coming from Objective-C
you'll think ``Swift is concise and elegant''
In Swift I really miss pattern matching and I dislike having to
excessivly declare types.
The lack of a decent concurrency model in Cocoa and Objective-C is
reflected in the Swift code - which although it works is pretty
horrible.
<<#why_i_use_text>>
* Textual vs Interactive interfaces
I've said that I don't like Xcode so I've added some reasons why.
Two common ways to create programs are:
1) We create a text file using a text editor.
2) We create a program by clicking on buttons and dragging objects in
an IDE (Integrated Development Environment) (for example, Xcode).
In the first method we don't usually have to tell the user how to
create a text file with given content. It suffices to give a listing
of the file and assume that the user can create the file using an
editor of their choice. It is totally irrelevant *how* the file is
created the only thing that matters is that the content of the file is
correct.
In order to understand the program only the content of the file must
be understood. We can examine it line by line, asking if we understand
what the lines of code mean.
Using an IDE is horribly different - describing how to interact with a
an IDE is very difficulty to do in text. Usually we have to *show* how to
do this typically with a YouTube video, or in a mixture of text and images.
An excellent example of the difficulty of describing how to do
something is can be found in [[http://swiftrien.blogspot.se/2015/11/swift-example-binding-nstableview-to.html][Notes from a Swift Developer]] -- in this
example the author uses a mixture of text and screenshots to describe
how to build an application. Text alone does not work.
The application described in the link above has a simple layout which could be *easily*
described in text (for example as an html table)
Unfortunately when we try to reproduce what we've seen in a video or
follow a description that is a mixture of text and images, we find that
the description almost invariably describes a different version of the
IDE than the one we have available.
Textual descriptions of the form ``and now click on the doggle control
icon'' are pretty useless if you haven't got a clue what the doggle
control icon looks like.
Worse - when the design process in an interface builder is finished -
all the correct buttons have been clicked the resulting ``state'' of
the system (ie the program) is not available in a textual format so we
cannot ask, line-by-line what the individual statement in the
description mean.
As you might gather - I hate IDEs like Xcode and Eclipse - I like to
totally understand every line of code I wrote - my method of
understanding code is always the same and independent of
language. Find an example program that works then reduce lines until I
can reduce no more making a minimal example that works - then
understand every line.
This is a slow process - but in the long run faster than clicking at
random in a IDE until your program works or Googling like crazy to see
if somebody else has a canned solution to your problem.
I get the impression that developers think that developing in a IDE is
somehow ``quicker'' than developing with no such tools. As far as I can see this
is false. Once I have a working program in a given directory structure
I can make a clone of this in a single terminal command (`cp -R ...`) and then
I'm off in my trusty editor.
Following instructions like
+ enter this ... into a file called ...
+ type the command ... into the shell
are easy to obey and pass the ``telephone test'' (ie can we describe exactly what to do
over a telephone) - no images or videos are needed to ``show'' the users what to do.
Once upon a time we could describe how to do something using text only.
Then we used text and images. Now it needs videos.
Asking questions about text was easy - ``what did you mean in paragraph 4''
now we'd have to ask ``what did you do 41.6-41.8 seconds into your video''
watching somebody doing something in Xcode 200.7 in a video and preforming slow
motion playbacks of selected sections is not my idea of programming.
* References
I've found several documents which helped me - none of them solves the problem
I'm try to solve but they have all provided clues to the solution:
+ [[http://czak.pl/2015/09/23/single-file-cocoa-app-with-swift.html]]
+ [[https://github.com/Eonil/CocoaProgrammaticHowtoCollection]]
+ [[https://forums.developer.apple.com/thread/5137]]
+ [[http://stackoverflow.com/questions/26609778/nsopenpanel-in-swift-how-to-open]]
+ [[https://objectivec2swift.com/#/converter/code]]
+ [[http://practicalswift.com/]]
+ [[https://github.com/tylergaw/js-osx-app-examples]]
+ [[http://www.raywenderlich.com/82046/introduction-to-os-x-tutorial-core-controls-and-swift-part-1]]
+ [[http://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art024]]
+ [[http://mediautopia.weebly.com/swift-1-intro.html]]
+ [[http://dev.iachieved.it/iachievedit/using-swift-as-a-scripting-language/]]
+ [[http://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art024]]
+ [[http://www.knowstack.com/swift-programming-an-introduction/]]
* Help and tips wanted
Does anynody know:
+ How can I create two or more windows? - I seem to only be able to create a single window
+ How can I make a socket client?
+ How can I make a socket server?
Solutions should be single Swift files that can be run from the terminal.
* The future
Articles like this are never finished only started ...