I have these 3 files:
index.swift
import Cocoa
print("Init")
let app = NSApplication.shared()
let delegate = AppDelegate() // alloc main app's delegate class
app.delegate = delegate // set as app's delegate
app.run()
print("End")
src/delegate.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
var newWindow: NSWindow?
var controller: ViewController?
override init() {
print("App")
super.init()
mainView()
}
func mainView() {
print("View")
newWindow = NSWindow(contentRect: NSMakeRect(10, 10, 200, 200), styleMask: [.titled, .resizable, .closable], backing: .buffered, defer: false)
let content = newWindow!.contentView! as NSView
controller = ViewController()
//controller?.loadView()
let view = controller!.view
content.addSubview(view)
newWindow!.makeKeyAndOrderFront(nil)
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
print("Launch") // Not showing this?
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
print("Bye")
return true
}
}
src/controller.swift
import Cocoa
class ViewController : NSViewController {
var name = "Main"
var button: NSButton?
#IBAction func onClick(_: Any) {
print("Click")
}
override func loadView() {
print("Load")
let view = NSView(frame: NSMakeRect(0,0,200,200))
view.wantsLayer = true
view.layer?.borderWidth = 2
view.layer?.borderColor = NSColor.red.cgColor
button = NSButton(frame: NSMakeRect(20, 20, 100, 32))
button?.title = "Click Me"
button?.target = self
button?.action = #selector(onClick)
view.addSubview(button!)
self.view = view
}
}
I tried compiling it like this:
swift index.swift src/delegate.swift src/controller.swift
But get this error:
index.swift:7:16: error: use of unresolved identifier 'AppDelegate'
let delegate = AppDelegate() // alloc main app's delegate class
^~~~~~~~~~~
Foundation.PortDelegate:1:17: note: did you mean 'PortDelegate'?
public protocol PortDelegate : NSObjectProtocol {
^
CoreText.CTRunDelegate:1:14: note: did you mean 'CTRunDelegate'?
public class CTRunDelegate {
^
<unknown>:0: warning: 'cacheParamsComputed' is deprecated
<unknown>:0: warning: 'cacheAlphaComputed' is deprecated
<unknown>:0: warning: 'keepCacheWindow' is deprecated
<unknown>:0: error: 'memoryless' is unavailable
Metal.MTLCommandBufferError:55:14: note: 'memoryless' has been explicitly marked unavailable here
case memoryless
^
Wondering how to get this compiling. Basically, I only need to know how to require/import these classes AppDelegate and ViewController from where they are currently defined into the scope of index.swift. If I place all the code into 1 file index.swift, then it compiles fine. But I would like to modularize it like you do in XCode, but without invoking XCode. Wondering if this can be solved with a plist of some sort. I tried swift -F and swift -I to specify import directories, but no go.
As #MartinR said, swiftc is the compiler. Your swift command invokes the interpreter, not the compiler. I think the interpreter can only run a single file.
I've only skimmed the following pages, but they may help you run a Swift program consisting of multiple files.
Use a simple shell script to merge all files into one and run the merged file through the swift interpreter:
http://sgeos.github.io/swift/2016/02/09/getting-started-with-multi-file-command-line-swift.html
Use built-in Swift tools:
create a project with swift package init --type executable
use swift build to compile the project into a single executable
https://medium.com/quick-code/lets-build-a-command-line-app-in-swift-328ce274f1cc
I found these (and many similar) pages by searching: swift command line run multiple files
Related
I’ve been experimenting with ARKit on Swift Playgrounds. I’ve written the starter code, but when I run it nothing happens. Instead of evaluating the code, it displays the pop up that shows ay issues in the code.
I know the code I’m using works, because I’ve used the same code on an iPad running an older version of Swift Playgrounds and the code works perfectly. It seems to be a problem with either Swift Playgrounds 3 or Swift 5.
Here’s the interesting part. When I remove the line of code that runs the ARWorldTrackingConfiguration initializer, and the code that makes the view controller the delegate of the session and scene, the code runs just fine. When I put it back, it does the same error again. I don’t know what’s going wrong.
I’m running Swift Playgrounds 3.0 on and iPad 6th Generation. The playground uses ARKit, UIKit, SceneKit, and PlaygroundSupport.
Lastly, here’s some code.
// Code inside modules can be shared between pages and other source files.
import ARKit
import SceneKit
import UIKit
extension ARSCNView {
public func setup(){
antialiasingMode = .multisampling4X
automaticallyUpdatesLighting = false
preferredFramesPerSecond = 60
contentScaleFactor = 1.0
if let camera = pointOfView?.camera {
camera.wantsHDR = true
camera.wantsExposureAdaptation = true
camera.exposureOffset = -1
camera.minimumExposure = -1
camera.maximumExposure = 3
}
}
}
public class vc : UIViewController, ARSessionDelegate, ARSCNViewDelegate {
var arscn : ARSCNView!
var scene : SCNScene!
public override func loadView() {
arscn = ARSCNView(frame: CGRect(x: 0, y: 0, width: 768, height: 1024))
arscn.delegate = self
arscn.setup()
scene = SCNScene()
arscn.scene = scene
var config = ARWorldTrackingConfiguration()
config.planeDetection = .horizontal
arscn.session.delegate = self
self.view = arscn
arscn.session.run(configåå)
}
public func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
public func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
public func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
}
Lastly, please note that I’m presenting the live view in the main playground page and putting the class in the shared code.
I’ve figured out a way to make this work. All I had to do was assign the view controller to a variable and then present the variable. I’m not exactly sure why this works, I just know it does.
import ARKit
import SceneKit
import UIKit
import PlaygroundSupport
public class LiveVC: UIViewController, ARSessionDelegate, ARSCNViewDelegate {
let scene = SCNScene()
public var arscn = ARSCNView(frame: CGRect(x: 0,y: 0,width: 640,height: 360))
override public func viewDidLoad() {
super.viewDidLoad()
arscn.delegate = self
arscn.session.delegate = self
arscn.scene = scene
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal]
arscn.session.run(config)
view.addSubview(arscn)
}
public func session(_ session: ARSession, didFailWithError error: Error) {}
public func sessionWasInterrupted(_ session: ARSession) {}
public func sessionInterruptionEnded(_ session: ARSession) {}
}
var vc = LiveVC()
PlaygroundPage.current.liveView = vc
PlaygroundPage.current.needsIndefiniteExecution = true
Use UpperCamelCasing for classes' names and add two strings of code in the bottom.
This code is suitable for macOS Xcode Playground and iPad Swift Playgrounds:
import ARKit
import PlaygroundSupport
class LiveVC: UIViewController, ARSessionDelegate, ARSCNViewDelegate {
let scene = SCNScene()
var arscn = ARSCNView(frame: CGRect(x: 0,
y: 0,
width: 640,
height: 360))
override func viewDidLoad() {
super.viewDidLoad()
arscn.delegate = self
arscn.session.delegate = self
arscn.scene = scene
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal]
arscn.session.run(config)
}
func session(_ session: ARSession, didFailWithError error: Error) {}
func sessionWasInterrupted(_ session: ARSession) {}
func sessionInterruptionEnded(_ session: ARSession) {}
}
PlaygroundPage.current.liveView = LiveVC().arscn
PlaygroundPage.current.needsIndefiniteExecution = true
P.S. Tip for Playground on macOS (although it doesn't have much sense when using ARKit module):
To turn on Live View in Xcode Playground 11.0 and higher use the following shortcut:
Command+Option+Return
So I am a newbie to Swift and wanted to create a simple example status bar app on MacOS.
To keep things clean I created a subclass App which is creating the status item. This class is then created in the applicationDidFinishLaunching function of the AppDelegate.swift.
But somehow nothing is printed on the console when I press the status icon. However if I copy the code in the AppDelegate file it works. Does someone know what I am doing wrong and why it is not working in the subclass?
Here is the code of my own class:
import Cocoa
class App: NSObject {
let menuBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
override init() {
print("created app instance");
if let button = menuBarItem.button {
button.image = NSImage(named: NSImage.Name("StatusBarButtonImage"))
button.action = #selector(test(_:))
}
}
#objc func test(_ sender: Any?) {
print("button was pressed")
}
}
and the AppDelegate:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var appInstance: App!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
appInstance = App()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
If the button is showing up and nothing is happening when you click it, it looks to me like you need to make sure you're setting your button's target to your App instance. E.g.:
button.target = self
Otherwise the action is only followed up the responder chain.
I am creating an OS X status bar application.
I am trying to achieve the following:
app starts invisible, with menu bar item
click on menu bar item shows the main window
on deactivate, the window is hidden
So I am trying to programmatically show the main window when the menu item is clicked, but with no success.
My main window has "Hide on deactivate" checked. Once hidden, I cannot make it visible again using code.
Here is the code I have for now, but it doesn't work:
#IBAction func menuClick(sender: AnyObject) {
var mainWindow = NSStoryboard(name: "Main", bundle: nil)?.instantiateInitialController()
mainWindow?.makeKeyAndOrderFront(self)
}
This is how you have to do to show your Windows programmatically:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let mainWindow = NSWindow(contentRect: NSMakeRect(0, 0, NSScreen.mainScreen()!.frame.width/2, NSScreen.mainScreen()!.frame.height/2), styleMask: NSTitledWindowMask|NSResizableWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask, backing: NSBackingStoreType.Buffered, defer: false)
func createNewWindow(){
mainWindow.title = "Main Window"
mainWindow.opaque = false
mainWindow.center()
mainWindow.hidesOnDeactivate = true
mainWindow.movableByWindowBackground = true
mainWindow.backgroundColor = NSColor(calibratedHue: 0, saturation: 0, brightness: 1, alpha: 1)
mainWindow.makeKeyAndOrderFront(nil)
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
// lets get rid of the main window just closing it as soon as the app launches
NSApplication.sharedApplication().windows.first!.close()
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
#IBAction func menuClick(sender: AnyObject) {
createNewWindow()
}
}
or you can create an optional NSWindow var to store your window before you close it as follow
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var defaultWindow:NSWindow?
func applicationDidFinishLaunching(aNotification: NSNotification) {
// lets get rid of the main window just closing it as soon as the app launches
defaultWindow = NSApplication.sharedApplication().windows.first as? NSWindow
if let defaultWindow = defaultWindow {
defaultWindow.close()
}
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
#IBAction func menuClick(sender: AnyObject) {
if let defaultWindow = defaultWindow {
defaultWindow.makeKeyAndOrderFront(nil)
}
}
}
The makeKeyAndOrderFront method is a NSWindow method, but instantiateInitialController returns the window controller, not its window.
Also, if the window is hidden on deactivate, you wouldn't want to instantiate another copy. Keep a reference to the window and re-show that.
Finally, you may need to bring the app to the front too. Call [NSApp activateIgnoringOtherApps:YES] (or the Swift equivalent).
Creating a new Cocoa project in XCode gives me an AppDelegate.swift file which looks like this:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
}
The #NSApplicationMain attribute is documented here as
NSApplicationMain
Apply this attribute to a class to indicate that it is the application delegate. Using this attribute is equivalent to calling the NSApplicationMain(_:_:) function.
If you do not use this attribute, supply a main.swift file with code at the top level that calls the NSApplicationMain(_:_:) function as follows:
import AppKit
NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
The instructions in the documentation do not work: the AppDelegate class is never instantiated. In this answer, vadian suggests the following contents for main.swift, which work better than the code in the documentation:
import Cocoa
let appDelegate = AppDelegate()
NSApplication.shared().delegate = appDelegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
However, this still does not provide the same behavior as #NSApplicationMain. Consider using the above main.swift with the following AppDelegate.swift:
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var foo: NSStatusBar! = NSStatusBar.system();
}
The above AppDelegate.swift works with an #NSApplicationMain annotation, but when using the above main.swift, it fails at runtime with the error
Assertion failed: (CGAtomicGet(&is_initialized)), function CGSConnectionByID, file Services/Connection/CGSConnection.c, line 127.
I think this is_initialized error means that #NSApplicationMain sets things up so that the AppDelegate is instantiated after some initialization by the NSApplicationMain function. This suggests the following main.swift, which moves the delegate initialization to after the NSApplicationMain call:
import Cocoa
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
let appDelegate = AppDelegate()
NSApplication.shared().delegate = appDelegate
However, this doesn't work either, because NSApplicationMain never returns! The above main.swift is equivalent to the broken suggestion in the documentation, because the latter two lines are dead code.
I therefore think there must be some way to pass a reference to my AppDelegate class as an argument to the NSApplicationMain function, so that Cocoa can do its initialization and then instantiate my AppDelegate class itself. However, I see no way to do this.
Is there a main.swift which provides behavior which is truly equivalent to the #NSApplicationMain annotation? If so, what does that main.swift look like? If not, what is #NSApplicationMain actually doing, and how do I modify it?
The documentation assumes that there is a xib or storyboard which instantiates the AppDelegate class via an object (blue cube) in Interface Builder. In this case both
main.swift containing NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
and
#NSApplicationMain in the AppDelegate class
behave exactly the same.
If there is no xib or storyboard you are responsible to initialize the AppDelegate class, assign it to NSApplication.shared.delegate and run the app. You have also to consider the order of appearance of the objects. For example you cannot initialize objects related to AppKit before calling NSApplication.shared to launch the app.
For example with this slightly changed syntax
let app = NSApplication.shared
let appDelegate = AppDelegate()
app.delegate = appDelegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
you can initialize the status bar in AppDelegate outside ofapplicationDidFinishLaunching:
let statusItem = NSStatusBar.system().statusItem(withLength: -1)
because NSApplication.shared() to launch the app is called before initializing the AppDelegate class.
Here is what I did in order to run application without #NSApplicationMain annotation and function NSApplicationMain(_, _) while using Storyboard with initial NSWindowController generated by Xcode application template (with slight modification related to Main Menu described below).
File: AppConfig.swift (Swift 4)
struct AppConfig {
static var applicationClass: NSApplication.Type {
guard let principalClassName = Bundle.main.infoDictionary?["NSPrincipalClass"] as? String else {
fatalError("Seems like `NSPrincipalClass` is missed in `Info.plist` file.")
}
guard let principalClass = NSClassFromString(principalClassName) as? NSApplication.Type else {
fatalError("Unable to create `NSApplication` class for `\(principalClassName)`")
}
return principalClass
}
static var mainStoryboard: NSStoryboard {
guard let mainStoryboardName = Bundle.main.infoDictionary?["NSMainStoryboardFile"] as? String else {
fatalError("Seems like `NSMainStoryboardFile` is missed in `Info.plist` file.")
}
let storyboard = NSStoryboard(name: NSStoryboard.Name(mainStoryboardName), bundle: Bundle.main)
return storyboard
}
static var mainMenu: NSNib {
guard let nib = NSNib(nibNamed: NSNib.Name("MainMenu"), bundle: Bundle.main) else {
fatalError("Resource `MainMenu.xib` is not found in the bundle `\(Bundle.main.bundlePath)`")
}
return nib
}
static var mainWindowController: NSWindowController {
guard let wc = mainStoryboard.instantiateInitialController() as? NSWindowController else {
fatalError("Initial controller is not `NSWindowController` in storyboard `\(mainStoryboard)`")
}
return wc
}
}
File main.swift (Swift 4)
// Making NSApplication instance from `NSPrincipalClass` defined in `Info.plist`
let app = AppConfig.applicationClass.shared
// Configuring application as a regular (appearing in Dock and possibly having UI)
app.setActivationPolicy(.regular)
// Loading application menu from `MainMenu.xib` file.
// This will also assign property `NSApplication.mainMenu`.
AppConfig.mainMenu.instantiate(withOwner: app, topLevelObjects: nil)
// Loading initial window controller from `NSMainStoryboardFile` defined in `Info.plist`.
// Initial window accessible via property NSWindowController.window
let windowController = AppConfig.mainWindowController
windowController.window?.makeKeyAndOrderFront(nil)
app.activate(ignoringOtherApps: true)
app.run()
Note regarding MainMenu.xib file:
Xcode application template creates storyboard with Application Scene which contains Main Menu. At the moment seems there is no way programmatically load Main Menu from Application Scene. But there is Xcode file template Main Menu, which creates MainMenu.xib file, which we can load programmatically.
Replace the default Cocoa project's AppDelegate.swift with the following main.swift. The application will behave the same as before. Thus, the following code provides the semantics of the #NSApplicationMain annotation.
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate { }
let myApp: NSApplication = NSApplication.shared()
let myDelegate: AppDelegate = AppDelegate()
myApp.delegate = myDelegate
let mainBundle: Bundle = Bundle.main
let mainNibFileBaseName: String = mainBundle.infoDictionary!["NSMainNibFile"] as! String
mainBundle.loadNibNamed(mainNibFileBaseName, owner: myApp, topLevelObjects: nil)
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
(I constructed this with much help from vadian's answer. If there are any differences in behavior between the above and the default Cocoa project application, please let me know.)
I am a beginning Swift programmer.
The following code seems to compile fine in Xcode 7.0 Playground (no visible errors):
//: Playground - noun: a place where people can play
//#!/usr/bin/env xcrun swift
import WebKit
let application = NSApplication.sharedApplication()
application.setActivationPolicy(NSApplicationActivationPolicy.Regular)
let window = NSWindow()
window.setContentSize(NSSize(width:800, height:600))
window.styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask
window.center()
window.title = "Minimal Swift WebKit Browser"
window.makeKeyAndOrderFront(window)
class WindowDelegate: NSObject, NSWindowDelegate {
func windowWillClose(notification: NSNotification) {
NSApplication.sharedApplication().terminate(0)
}
}
let windowDelegate = WindowDelegate()
window.delegate = windowDelegate
class ApplicationDelegate: NSObject, NSApplicationDelegate {
var _window: NSWindow
init(window: NSWindow) {
self._window = window
}
func applicationDidFinishLaunching(notification: NSNotification) {
let webView = WebView(frame: self._window.contentView!.frame)
self._window.contentView!.addSubview(webView)
webView.mainFrame.loadRequest(NSURLRequest(URL: NSURL(string: "http://www.apple.com")!))
}
}
When pasting that exact same code into the "AppDelegate.swift" file of a new Cocoa application for OSX, I get 7 errors, all exactly the same: "Expressions are not allowed at the top level".
Through searching I've deduced that the Playground allows things that normal projects do not and the errors are occurring because the expressions are "outside of a class or instance method".
However I'm not sure how the program could be modified in order to build correctly.
Yes, normal projects do not allow code at the top level, because there is no obvious time for it to run. You need to decide when your activation policy and window/delegate code should run (that is, move that code inside of a method). I suggest applicationDidFinishLaunching(_:), as it is called when your app is finished launching and is a common place to do this kind of setup. The finished code would read:
import WebKit
class WindowDelegate: NSObject, NSWindowDelegate {
func windowWillClose(notification: NSNotification) {
NSApplication.sharedApplication().terminate(0)
}
}
class ApplicationDelegate: NSObject, NSApplicationDelegate {
var _window: NSWindow
init(window: NSWindow) {
self._window = window
}
func applicationDidFinishLaunching(notification: NSNotification) {
let webView = WebView(frame: self._window.contentView!.frame)
self._window.contentView!.addSubview(webView)
webView.mainFrame.loadRequest(NSURLRequest(URL: NSURL(string: "http://www.apple.com")!))
let application = NSApplication.sharedApplication()
application.setActivationPolicy(NSApplicationActivationPolicy.Regular)
let window = NSWindow()
window.setContentSize(NSSize(width:800, height:600))
window.styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask
window.center()
window.title = "Minimal Swift WebKit Browser"
window.makeKeyAndOrderFront(window)
let windowDelegate = WindowDelegate()
window.delegate = windowDelegate
}
}