How to open a locally saved PDF file with Swift - swift

I've made a Swift menu bar app XCode 8.2.1. One of the menu bar options in this script successfully runs an AppleScript I created. I'd like to put another option in this menu bar app that opens a PDF instruction/read me file for the user. How do I open a locally saved PDF file in Swift?
I've included the code I have in the AppDelegate.swift file in XCode.
//
// AppDelegate.swift
// Layout Ad
//
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var statusMenu: NSMenu!
let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength)
#IBAction func quitClicked(sender: NSMenuItem) {
NSApplication.shared().terminate(self)
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem.menu = statusMenu
let icon = NSImage(named: "statusIcon")
//icon?.isTemplate = true // best for dark mode
statusItem.image = icon
statusItem.menu = statusMenu
constructMenu()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
//Function to get the path to the applescript
func runApplescript(){
let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["/Library/Scripts/1_Page Layout Scripts/Layout Ad.scpt"]
task.launch()
}
//Function to open the script's instructions
func openPDF(){
//Code to open PDF file will go here
//"/Volumes/NAS/Advertising Department/16_SCRIPTS/*Instructions/2_Running Scripts/1_InDesign/Layout Ad Script Instructions.pdf"
}
//Function to make the menu work
func constructMenu() {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Script Instructions", action: #selector(openPDF), keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Layout Ad", action: #selector(runApplescript), keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
statusItem.menu = menu
}
}

NSWorkspace can do that.
let url = URL(fileURLWithPath: "/Volumes/NAS/Advertising Department/16_SCRIPTS/*Instructions/2_Running Scripts/1_InDesign/Layout Ad Script Instructions.pdf")
NSWorkspace.shared.open(url)

Related

How to reopen an macos App using menubar button

I created an app with a menubar button using swift and storyboard, when I click the close button the window closed and the menubar button still sits on the menubar, that's good. What I want to do next is reopen the window by clicking the menubar button. After searching I figured out I can use this code to bring it to the front, but it only works when the window is still open. How can I bring it back when it is closed or miniatured?
NSApplication.shared.activate(ignoringOtherApps: true)
This the appDelegate
class AppDelegate: NSObject, NSApplicationDelegate {
private var statusItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
statusItem = NSStatusBar.system.statusItem(withLength: 16.0)
if let button = statusItem.button {
button.image = NSImage(named: "remote-control")
button.image?.size = NSSize(width: 16.0, height: 16.0)
button.image?.isTemplate = true
button.action = #selector(bringToFront(sender:))
}
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
print("terminate")
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
#objc func bringToFront(sender: AnyObject?) {
NSApplication.shared.activate(ignoringOtherApps: true)
NSApp.windows.last?.makeKeyAndOrderFront(nil)
}}
This is the windowcontroller
class MainWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
window?.title = ""
let styleMask: NSWindow.StyleMask = [.closable, .titled, .miniaturizable]
window?.styleMask = styleMask
}}
Thanks
I got it.
In appdelegate use deminiaturize function does exactly what I want.
for window in NSApp.windows {
window.deminiaturize(nil)
}
Since I only have one window but NSApp.windows has two members, I think I can deminiatureize all the windows.

How to make app start at login or add to login items after install like dropbox? Xcode 11, Swift 5, MacOS 10.13+

I am trying to have my app start automatically when a user logs out and back in or after a reboot. The objective is to have the app there indefinitely unless a user closes out, but, to have it start back up when they reboot.
There are several tutorials I have followed and threads I have read but they all seem outdated. I am using this app on 3 different OS' including 10.13, 10.14 and 10.15. I seem to have most of the issues on 10.15 machines. I can't figure out why it is hit or miss whether the app starts on login and why sometimes it does not.
https://martiancraft.com/blog/2015/01/login-items/
https://theswiftdev.com/how-to-launch-a-macos-app-at-login/
I have code signed the application, sandbox is enabled, but maybe since the tutorials and information I have found is outdated I am missing something outside the code. This app is for internal company use only, and thus, I will not be submitting to the Apple. I intend to deploy to machines using our management software, and have built a .pkg to deploy the application + launcher into the applications folder and to run automatically after install.
Any help, cleanup, explanations or suggestions are welcome and appreciated.
Main App:
extension Notification.Name{
static let killLauncher = Notification.Name("killLauncher")
}
extension AppDelegate: NSApplicationDelegate{
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
//assign variables for launcherhelper
let launcherAppId = "Kinetic.KTGHelperLauncher"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter {$0.bundleIdentifier == launcherAppId }.isEmpty
//set launcher to login item
SMLoginItemSetEnabled(launcherAppId as CFString, true)
//status check if running or not running
if isRunning {
DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!)
}
//configure button to display button in assets
if let button = statusItem.button {
button.image = NSImage(named:NSImage.Name("kinetic_websitemain_red"))
}
//builds menu on start
constructMenu()
}
}
#NSApplicationMain
class AppDelegate: NSObject {
let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#objc func TakeScreenshot(_ sender: Any){
//get path to user download folder
let dirPath = FileManager().urls(for:.downloadsDirectory, in:.userDomainMask)[0]
//create time stamp of when picture is taken
func CreateTimeStamp() -> Int32
{
return Int32(Date().timeIntervalSince1970)
}
var displayCount: UInt32 = 0;
var result = CGGetActiveDisplayList(0, nil, &displayCount)
if (result != CGError.success) {
print("error: \(result)")
return
}
let allocated = Int(displayCount)
let activeDisplays = UnsafeMutablePointer<CGDirectDisplayID>.allocate(capacity: allocated)
result = CGGetActiveDisplayList(displayCount, activeDisplays, &displayCount)
if (result != CGError.success) {
print("error: \(result)")
return
}
for i in 1...displayCount {
let unixTimestamp = CreateTimeStamp()
let fileUrl = dirPath.appendingPathComponent("\(unixTimestamp)" + "_" + "\(i)" + ".jpg", isDirectory:false)
let screenShot:CGImage = CGDisplayCreateImage(activeDisplays[Int(i-1)])!
let bitmapRep = NSBitmapImageRep(cgImage: screenShot)
let jpegData = bitmapRep.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [:])!
do {
try jpegData.write(to: fileUrl, options: .atomic)
}
catch {print("error: \(error)")}
}
}
#objc func kineticSelf(_ sender: Any){
let kineticSelfUrl = URL(string: "/Library/Addigy/macmanage/MacManage.app")
NSWorkspace.shared.openFile(kineticSelfUrl!.path)
// NSWorkspace.shared.open(URL(fileURLWithPath: "/Library/Addigy/macmanage/MacManage.app"))
}
//function that opens kinetic helpdesk website
#objc func kineticHelpdesk(_ sender: Any){
let kineticHelpdeskUrl = URL(string: "http://helpdesk.kinetictg.com")!
NSWorkspace.shared.open(kineticHelpdeskUrl)
}
//function that takes user to teamviewer ktg site
#objc func kineticRemote(_ sender: Any){
let kineticRemoteUrl = URL(string: "https://get.teamviewer.com/ktgsupport")!
NSWorkspace.shared.open(kineticRemoteUrl)
}
//call kinetic
#objc func kineticHomepage(_ sender: Any){
let url = URL(string: "https://kinetictg.com")!
NSWorkspace.shared.open(url)
}
//function to build menu
func constructMenu(){
let menu = NSMenu()
//section for "Request Support"
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Request Support", action: nil, keyEquivalent:""))
//support ticket
menu.addItem(NSMenuItem(title: "Support Ticket", action:
#selector(AppDelegate.kineticHelpdesk(_:)), keyEquivalent: ""))
//remote support
menu.addItem(NSMenuItem(title: "Remote Support", action:
#selector(AppDelegate.kineticRemote(_:)), keyEquivalent: ""))
//section for "Tools"
menu.addItem(NSMenuItem.separator( ))
menu.addItem(NSMenuItem(title: "Tools", action: nil, keyEquivalent:""))
//start agent installation audit
menu.addItem(NSMenuItem(title: "Take Screenshot", action:
#selector(AppDelegate.TakeScreenshot(_:)), keyEquivalent: ""))
//open self service
menu.addItem(NSMenuItem(title: "Open Self Service", action:
#selector(AppDelegate.kineticSelf(_:)), keyEquivalent: ""))
//Section for "Info"
menu.addItem(NSMenuItem.separator( ))
menu.addItem(NSMenuItem(title: "Info", action: nil, keyEquivalent:""))
//contact info
menu.addItem(NSMenuItem(title: "Kinetic Homepage", action:
#selector(AppDelegate.kineticHomepage(_:)), keyEquivalent: ""))
//quit app
menu.addItem(NSMenuItem(title: "Quit", action:
#selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
statusItem.menu = menu
}
}
Launcher Application:
import Cocoa
//extension variable for launcher to kill launcher
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
#NSApplicationMain
class HelperAppDelegate: NSObject {
//terminate object
#objc func terminate(){
NSApp.terminate(nil)
}
}
extension HelperAppDelegate: NSApplicationDelegate{
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
//main app identifier
let mainAppIdentifier = "Kinetic.KTG-Helper"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == mainAppIdentifier }.isEmpty
//if app is running kill launcher entity and reset status of killlauncher
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(3)
components.append("MacOS")
components.append("KTG Helper")
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
//}
}

Swift+Macos Auto close popover window when focus is lost

I am writing a small app that display an icon in the menu bar with a popup view.
I followed https://github.com/twostraws/SwiftOnSundays/tree/master/013%20TextTransformer as an example.
One difference I am seeing in my test app is the popover view controller is not getting dismissed when view loses focus.
this is my app delegate,
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let popupOver = NSPopover()
#IBOutlet weak var menu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem.button?.title = "FT";
statusItem.button?.target = self;
statusItem.button?.action = #selector(showMenu)
let storyBoard = NSStoryboard(name: "Main", bundle: nil)
guard let viewController = storyBoard.instantiateController(withIdentifier: "FTViewController") as? ViewController else {
fatalError("Unable to find 'FTViewController'")
}
popupOver.contentViewController = viewController
popupOver.behavior = .semitransient
}
func applicationDidResignActive(_ notification: Notification) {
popupOver.close()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#objc func showMenu() {
if(popupOver.isShown) {
popupOver.close()
}
else {
guard let statusButton = statusItem.button else {
fatalError("Unable to find status button")
}
popupOver.show(relativeTo: statusButton.bounds, of: statusButton, preferredEdge: .maxY)
}
}
}
I tried adding applicationDidResignActive but that's getting triggered only when the application loses focus so if i directly click the menu bar item and click else where on screen I am not getting that event. The sampleapp i referenced doesnt seem to hookup for these events but still works as expected.
I am just starting swift ui programming so couldnt figure out what I am missing here.
Not sure if you're still looking for a solution here, but I've just got stuck with the same problem and I found this example which seems to do the job. Hope this helps.

Can I program Apple Metal graphics without Xcode?

Is it possible to code a Metal graphics program with only an editor an the terminal? If it is, What would be an example of a minimal application?
I'd like to try some generative coding but without Xcode. I was able to make small C or Python programs with OpenGL but its deprecation by Apple is making me consider Metal.
Can I use Swift and some header inclusions or something like that? or do I need a whole lot more?
You can compile and run an app that uses Metal from the command line with no Xcode project at all, but you still need the infrastructure (SDK and toolchain) provided by Xcode, so you'll need to have it installed.
It's quite straightforward to write a simple app in Swift that creates the requisite Metal objects, encodes some work, and writes the result to a file. For the purposes of this question, I'll provide the source for a simple Cocoa app that goes a bit further by creating a window that hosts a MetalKit view and draws to it. With this scaffolding, you could write a very sophisticated app without ever launching Xcode.
import Foundation
import Cocoa
import Metal
import MetalKit
class AppDelegate : NSObject, NSApplicationDelegate {
let window = NSWindow()
let windowDelegate = WindowDelegate()
var rootViewController: NSViewController?
func applicationDidFinishLaunching(_ notification: Notification) {
window.setContentSize(NSSize(width: 800, height: 600))
window.styleMask = [ .titled, .closable, .miniaturizable, .resizable ]
window.title = "Window"
window.level = .normal
window.delegate = windowDelegate
window.center()
let view = window.contentView!
rootViewController = ViewController(nibName: nil, bundle: nil)
rootViewController!.view.frame = view.bounds
view.addSubview(rootViewController!.view)
window.makeKeyAndOrderFront(window)
NSApp.activate(ignoringOtherApps: true)
}
}
class WindowDelegate : NSObject, NSWindowDelegate {
func windowWillClose(_ notification: Notification) {
NSApp.terminate(self)
}
}
class ViewController : NSViewController, MTKViewDelegate {
var device: MTLDevice!
var commandQueue: MTLCommandQueue!
override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func loadView() {
device = MTLCreateSystemDefaultDevice()!
commandQueue = device.makeCommandQueue()!
let metalView = MTKView(frame: .zero, device: device)
metalView.clearColor = MTLClearColorMake(0, 0, 1, 1)
metalView.delegate = self
self.view = metalView
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard let commandBuffer = commandQueue.makeCommandBuffer(),
let passDescriptor = view.currentRenderPassDescriptor else { return }
if let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) {
// set state, issue draw calls, etc.
commandEncoder.endEncoding()
}
commandBuffer.present(view.currentDrawable!)
commandBuffer.commit()
}
}
func makeMainMenu() -> NSMenu {
let mainMenu = NSMenu()
let mainAppMenuItem = NSMenuItem(title: "Application", action: nil, keyEquivalent: "")
let mainFileMenuItem = NSMenuItem(title: "File", action: nil, keyEquivalent: "")
mainMenu.addItem(mainAppMenuItem)
mainMenu.addItem(mainFileMenuItem)
let appMenu = NSMenu()
mainAppMenuItem.submenu = appMenu
let appServicesMenu = NSMenu()
NSApp.servicesMenu = appServicesMenu
appMenu.addItem(withTitle: "Hide", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h")
appMenu.addItem({ () -> NSMenuItem in
let m = NSMenuItem(title: "Hide Others", action: #selector(NSApplication.hideOtherApplications(_:)), keyEquivalent: "h")
m.keyEquivalentModifierMask = [.command, .option]
return m
}())
appMenu.addItem(withTitle: "Show All", action: #selector(NSApplication.unhideAllApplications(_:)), keyEquivalent: "")
appMenu.addItem(NSMenuItem.separator())
appMenu.addItem(withTitle: "Services", action: nil, keyEquivalent: "").submenu = appServicesMenu
appMenu.addItem(NSMenuItem.separator())
appMenu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
let fileMenu = NSMenu(title: "Window")
mainFileMenuItem.submenu = fileMenu
fileMenu.addItem(withTitle: "Close", action: #selector(NSWindowController.close), keyEquivalent: "w")
return mainMenu
}
let app = NSApplication.shared
NSApp.setActivationPolicy(.regular)
NSApp.mainMenu = makeMainMenu()
let appDelegate = AppDelegate()
NSApp.delegate = appDelegate
NSApp.run()
Although this looks like a lot of code, the bulk of it is Cocoa boilerplate for creating and interacting with a window and its main menu. The Metal code is only a few lines tucked away in the middle (see func draw).
To build and run this app, save the code to a Swift file (I called mine MinimalMetal.swift) and use xcrun to locate the tools and SDKs necessary to build:
xcrun -sdk macosx swiftc MinimalMetal.swift -o MinimalMetal
This creates an executable named "MinimalMetal" in the same directory, which you can run with
./MinimalMetal
This is a full-fledged app, with a window, menu, runloop, and 60 FPS drawing. From here, you can use all the features of Metal you might want to.
you can with python, try this:
https://github.com/wtnb75/runmetal you can compile a metal kernel in a string and link it with numpy arrays

Cocoa Xcode: Open Window Controller from AppDelegate Programmatically using Swift 4 for Mac?

I am making a simple menu bar app which has 2 items - Preferences & Quit
I want to open a new Window when I click on Preferences
Currently I have
func applicationDidFinishLaunching(_ aNotification: Notification) {
constructMenu()
}
func constructMenu() {
let menu = NSMenu()
menu.addItem(NSMenuItem(
title: "Preferences...",
action: #selector(AppDelegate.preferencesWindow(_:)),
keyEquivalent: "P"))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(
title: "Quit",
action: #selector(NSApplication.terminate(_:)),
keyEquivalent: "q"))
statusItem.menu = menu
}
#objc func preferencesWindow(_ sender: Any) {
print("open preference window here")
if let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) {
let controller = storyboard.instantiateControllerWithIdentifier("preferencesWindow")
as NSViewController
if let window = NSApplication.shared.mainWindow {
window.contentViewController = controller // just swap
}
}
}
But it throws error on this line
if let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) {
stating
Initializer for conditional binding must have Optional type, not 'NSStoryboard'
When I click Preferences the print statement gets logged but I don't know how to open Window programatically.
I have just dragged Window Controller from Object Library & given StoryBoard ID a value of preferencesWindow
I also tried the following because of the above error
#objc func preferencesWindow(_ sender: Any) {
print("open preference window here")
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
let controller = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "preferencesWindow")) as! NSViewController
if let window = NSApplication.shared.mainWindow {
window.contentViewController = controller // just swap
}
}
but it only logs & never opens a window. What should I do?
I find answers fast when I post a question on SO. Here's what worked for me
#objc func preferencesWindow(_ sender: Any) {
var myWindow: NSWindow? = nil
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"),bundle: nil)
let controller = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "preferencesWindow")) as! NSViewController
myWindow = NSWindow(contentViewController: controller)
NSApp.activate(ignoringOtherApps: true)
myWindow?.makeKeyAndOrderFront(self)
let vc = NSWindowController(window: myWindow)
vc.showWindow(self)
}