Expressions are not allowed at the top level - swift

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
}
}

Related

How enable autostart for a macOS menu bar app?

I am building an macOS app for the menu bar and it should automatically start with system start.
I started with implementing the autostart functionality for a standard window based macOS app following this tutorial this tutorial. I have
added a new target inside the main project (the helper app)
changed skip install to yes for the helper app
set the helper app to be a background only app
added a new copy file build phase to the main application to copy the helper application into the bundle
linked the ServiceManagement.framework
Implemented the functionality in the app delegates, that the helper app gets launched with system start. After it has launched, it launches the main app (see the tutorial link for more info or the source code down below)
That worked fine, the app launched automatically :) So I started changing the project, that the main application becomes a menu bar app. However than, the app wouldn't auto launch anymore :/ Does someone have a solution for that?
Heres the code of the app delegate of the main app:
import Cocoa
import ServiceManagement
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem.button?.title = "Test"
statusItem.button?.target = self
statusItem.button?.action = #selector(showWindow)
// auto start
let launcherAppId = "com.####.####Helper"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == launcherAppId }.isEmpty
SMLoginItemSetEnabled(launcherAppId as CFString, true)
if isRunning {
DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!)
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#objc func showWindow() {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
guard let vc = storyboard.instantiateController(withIdentifier: "ViewController") as? ViewController else {
fatalError("Unable to find main view controller")
}
guard let button = statusItem.button else {
fatalError("Unable to find status item button")
}
let popover = NSPopover()
popover.contentViewController = vc
popover.behavior = .transient
popover.show(relativeTo: button.bounds, of: button, preferredEdge: .maxY)
}
}
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
And this is the app delegate of the helper app:
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let mainAppIdentifier = "com.####.####"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == mainAppIdentifier }.isEmpty
if !isRunning {
DistributedNotificationCenter.default().addObserver(self, selector: #selector(self.terminate), name: .killLauncher, object: mainAppIdentifier)
let path = Bundle.main.bundlePath as NSString
var components = path.pathComponents
components.removeLast()
components.removeLast()
components.removeLast()
components.append("MacOS")
components.append("####") //main app name
let newPath = NSString.path(withComponents: components)
NSWorkspace.shared.launchApplication(newPath)
}
else {
self.terminate()
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#objc func terminate() {
NSApp.terminate(nil)
}
}
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
Thank you very much for your help :)
My code looks pretty much the same, except how I compose the path in the helper app:
var pathComponents = (Bundle.main.bundlePath as NSString).pathComponents
pathComponents.removeLast()
pathComponents.removeLast()
pathComponents.removeLast()
pathComponents.removeLast()
let newPath = NSString.path(withComponents: pathComponents)
NSWorkspace.shared.launchApplication(newPath)
Also, if I remember correctly, I had to make sure the Main.storyboard file still had the "Application Scene" with an Application object and an empty main menu.

iPad Swift Playgrounds won’t run ARKit code/crashes

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

How to compile Swift from command line

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

Open new window from a status bar app with no dock icon [duplicate]

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).

AppleTV - tvos - Hybrid app using native and TVMLKIT - Can't back to native app

I'm a beginner on TVOS.
I'd like to create an hybrid app on AppleTV using a native app and TVMLKIT.
My native application is just a simple native app with buttons (using swift).
When we click on a button, I launch a a javascript app using TVLMKIT and TVJS.
My TVJS as uses the Player to display a video.
When the video is over, I want to close the TVJS app and back to the native ViewController.
My problem is that when I back to native app, I loose the focus on my native View (the app is frozen).
native ViewController:
import UIKit
import TVMLKit
class ViewController: UIViewController, TVApplicationControllerDelegate {
var window: UIWindow?
var appController: TVApplicationController?
var appControllerContext = TVApplicationControllerContext();
static let TVBaseURL = "http://localhost:9001/"
static let TVBootURL = "\(ViewController.TVBaseURL)/client/js/application.js"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBOutlet weak var label: UILabel!
#IBOutlet weak var viewAd: UIView!
#IBAction func clickOnlaunchAd(sender: AnyObject) {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
guard let javaScriptURL = NSURL(string: ViewController.TVBootURL) else {
fatalError("unable to create NSURL")
}
appControllerContext.javaScriptApplicationURL = javaScriptURL
appControllerContext.launchOptions["BASEURL"] = ViewController.TVBaseURL
appController = TVApplicationController(context: appControllerContext, window: window,delegate: self)
}
#IBAction func clickOnChangeText(sender: AnyObject) {
label.text = "changed";
}
func appController(appController: TVApplicationController, didStopWithOptions options: [String : AnyObject]?) {
self.setNeedsFocusUpdate()
self.updateFocusIfNeeded()
}
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext){
let notifyEventToNative : #convention(block) (NSString!) -> Void = {
(string : NSString!) -> Void in
print("[log]: \(string)\n")
self.appController?.stop()
}
jsContext.setObject(unsafeBitCast(notifyEventToNative, AnyObject.self), forKeyedSubscript: "notifyEventToNative")
}
}
Just before calling "notifyEventToNative" from my TVJS, I call "navigationDocument.clear();" to clear the TVML view.
I can see my native app but I can't interact with it.
Any ideas?
Thanks.
I also had the same problem. I was opened a TVML document from the UIViewController. And I also lost the focus. So, first of all I can advice you to override var called preferredFocusedView in your ViewController. In this method you can return reference to viewAd. But the better solution would be to wrap your ViewController into the TVML-item (with the TVMLKit framework). In that case I hope that you will have no problems with focus because you will use TVML during the whole application.