Swift 3 Cocoa: use QuickLook to preview file in OS X - swift

Xcode 8.3.2 I don't find QLPreviewPanel in the command list and I don't know how to do (which command must be used) to display a file preview in a ViewController.

First of all you will need to add the import Quartz statement to your NSViewCOntroller. Second step is to add QLPreviewPanelDataSource, QLPreviewPanelDelegate to its declaration. Next you just need to get a reference of the shared QLPreviewPanel, make the view controller its dataSource and delegate and make its window key and order front.
You will need also to add numberOfPreviewItems and previewItemAt methods to your controller. You can do it as follow:
import Quartz
class ViewController: NSViewController, QLPreviewPanelDataSource, QLPreviewPanelDelegate {
#IBAction func button(_ sender: NSButton) {
if let sharedPanel = QLPreviewPanel.shared() {
sharedPanel.delegate = self
sharedPanel.dataSource = self
sharedPanel.makeKeyAndOrderFront(self)
}
}
func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
return 1
}
func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("yourImageAtTheDocs.png")
return url as QLPreviewItem
}
}

Sadly Apples QuickLookDownloader Demo uses Obj-C. I've created a Swift Versiob which is basically the implementation of #Leo Dabus answer in a Demo Project: Panel and Popover example

Related

Prevent NSToolbarItem from being removed

I want to prevent certain toolbar items from being removed by the user. They should still be movable, just not removable.
I tried creating a custom subclass of NSToolbar with a custom removeItem(at:) implementation, but it seems this method is not even called if the user drags an item out of the toolbar in the customization palette.
The delegate also doesn't seem to expose functionality for this.
How can I disable removal of certain NSToolbarItems?
I am not sure if you can prevent it from being removed but you can implement the optional toolbarDidRemoveItem method and insert the item that you don't want it to be removed back:
import Cocoa
class WindowController: NSWindowController, NSToolbarDelegate {
#IBOutlet weak var toolbar: Toolbar!
override func windowDidLoad() {
super.windowDidLoad()
toolbar.delegate = self
}
func toolbarDidRemoveItem(_ notification: Notification) {
if let itemIdentifier = (notification.userInfo?["item"] as? NSToolbarItem)?.itemIdentifier,
itemIdentifier.rawValue == "NSToolbarShowColorsItem" {
toolbar.insertItem(withItemIdentifier: itemIdentifier, at: 0)
}
}
}
Since it is not super critical if they are removed in case a private API call would stop working, I opted for the private API solution.
extension NSToolbarItem {
func setIsUserRemovable(_ flag: Bool) {
let selector = Selector(("_setIsUserRemovable:"))
if responds(to: selector) {
perform(selector, with: flag)
}
}
}
This works exactly as advertised.

macOS Swift QuickLook Warning: setDelegate and setDataSource called while the panel has no controller

Xcode 11, Swift 5.1
I'm getting a couple warnings every time I open a file and view it with QuickLook (QL). It seems to be working fine, but I'm wondering if I can get rid of the warnings.
The warnings say:
-[QLPreviewPanel setDelegate:] called while the panel has no controller - Fix this or this will raise soon.
-[QLPreviewPanel setDataSource:] called while the panel has no controller - Fix this or this will raise soon.
I set up and use QL on an NSTableCellView like this:
import Quartz
class AttachmentCell: NSTableCellView, QLPreviewPanelDataSource, QLPreviewPanelDelegate{
var quickLookItem:URL!
#IBAction func clickPreview(_ sender: Any) {
guard let panel = QLPreviewPanel.shared() else{ return }
panel.delegate = self
panel.dataSource = self
panel.makeKeyAndOrderFront(self)
}
func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
return 1
}
func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
quickLookItem = URL(fileURLWithPath: "...").appendingPathExtension(...)
return quickLookItem as QLPreviewItem
}
}
The clickPreview method is on an NSButton I have in my table cell. The closest thing I could find was this, but I don't see how the responder chain is involved: QLPreviewPanel in tableview with issue: "has no controller"
I also tried setting up my delegate methods on my NSViewController instead, but the same warning shows up.
Any ideas how to solve this? Or can I safely ignore it?

Can't get QuickLook to work when trying to preview files

I am writing a macOS application with Swift using story boards. I have a NSTableView which contains files that I want the user to be able to preview via QuickLook.
I seemingly have everything in place and my code looks very similar to what has been described here: QuickLook consumer as a delegate from an NSViewController, but I keep getting the error
-[QLPreviewPanel setDataSource:] called while the panel has no controller - Fix this or this will raise soon.
See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.
I've been trying to adapt the solution of above post to my situation with Swift and story boards.
The main pieces are:
import Quartz
class ViewController: NSViewController, QLPreviewPanelDataSource, QLPreviewPanelDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let windowNextResponder = self.view.window?.nextResponder
self.view.window?.nextResponder = self
self.nextResponder = windowNextResponder
}
// *** Quicklook stuff ***
#IBAction func quickLookButtonAction(_ sender: Any) {
guard qlPanel != nil else {
return
}
if qlPanel!.currentController == nil {
print ("No controller")
//qlPanel!.windowController = self.view.window?.windowController
// qlPanel!.updateController()
} else {
print (qlPanel!.currentController)
}
qlPanel!.delegate = self
qlPanel!.dataSource = self
qlPanel!.makeKeyAndOrderFront(self)
}
func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
return CSVarrayController.selectedObjects.count
}
func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
let file = CSVarrayController.selectedObjects[index] as! CSVfile
return file.url as NSURL
}
override func acceptsPreviewPanelControl(_ panel: QLPreviewPanel!) -> Bool {
return true
}
override func beginPreviewPanelControl(_ panel: QLPreviewPanel!) {
panel.dataSource = self
panel.delegate = self
}
override func endPreviewPanelControl(_ panel: QLPreviewPanel!) {
panel.dataSource = nil
panel.delegate = nil
}
}
With or without messing with the responder chain I get the error.
The delegate functions all get called as expected as well.
Remove
qlPanel!.delegate = self
qlPanel!.dataSource = self
in quickLookButtonAction, the viewcontroller isn't in control yet. Wait for beginPreviewPanelControl.
From the documentation for currentController:
You should never change the preview panel’s state (its delegate, datasource, and so on) if you are not controlling it.
From comments in QLPreviewPanel.h for -beginPreviewPanelControl::
Sent to the object taking control of the Preview Panel.
The receiver should setup the preview panel (data source, delegate, binding, etc.) here.

windowWillClose and button action not called Swift

I'm designing a mac app with Xcode 10 (beta) and I got an issue with the Preference Window Controller
I have in my Main.storyboard a NSWindowController of custom class PreferenceWindowController with a toolbar. Here are its connections :
Here is the full class :
class PreferenceWindowController: NSWindowController, NSWindowDelegate {
#IBAction func didClickAuthor(_ sender: Any) {
print("author")
}
#IBAction func didClickTypo(_ sender: Any) {
print("typo")
}
override func windowDidLoad() {
super.windowDidLoad()
}
func windowWillClose(_ notification: Notification) {
print("willClose")
}
}
The window is initiated via the AppDelegate class with this code :
let storyboard = NSStoryboard(name: "Main",bundle: nil)
if let wc = storyboard.instantiateController(withIdentifier: "PreferenceWindowController") as? PreferenceWindowController
{
wc.showWindow(self)
}
The window opens as expected, with the toolbar clickable, but no functions from PreferenceWindowController are called at all, neither the closing of the window, nor the clicks on the toolbar.
I checked every connections, every class name, and I really don't know what's wrong...
SOLUTION
The solution is to store the PreferenceViewController class inside the AppDelegate class as a variable.
My solution :
var preferenceWindowController:PreferenceWindowController? = nil
#IBAction func clickPreferences(_ sender: Any) {
if let wc = storyboard.instantiateController(withIdentifier: "PreferencesWindowController") as? PreferenceWindowController {
let window = wc.window
preferenceWindowController = wc
wc.showWindow(self)
}
}
Thank you for helping !
The comment above seems like it could be on the right track. Based on the code context you've included in your question, it looks like the window controller you create will only have a lifetime for that function call.
Try making the window controller an instance variable. This is normally how I wire things up in an App delegate that creates window controllers. It's a simple pattern that works well.

NSComboBox getGet value on change

I am new to OS X app development. I manage to built the NSComboBox (Selectable, not editable), I can get it indexOfSelectedItem on action button click, working fine.
How to detect the the value on change? When user change their selection, what kind of function I shall use to detect the new selected index?
I tried to use the NSNotification but it didn't pass the new change value, always is the default value when load. It is because I place the postNotificationName in wrong place or there are other method should use to get the value on change?
I tried searching the net, video, tutorial but mostly written for Objective-C. I can't find any answer for this in SWIFT.
import Cocoa
class NewProjectSetup: NSViewController {
let comboxRouterValue: [String] = ["No","Yes"]
#IBOutlet weak var projNewRouter: NSComboBox!
#IBAction func btnAddNewProject(sender: AnyObject) {
let comBoxID = projNewRouter.indexOfSelectedItem
print(“Combo Box ID is: \(comBoxID)”)
}
#IBAction func btnCancel(sender: AnyObject) {
self.dismissViewController(self)
}
override func viewDidLoad() {
super.viewDidLoad()
addComboxValue(comboxRouterValue,myObj:projNewRouter)
self.projNewRouter.selectItemAtIndex(0)
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(
self,
selector: “testNotication:”,
name:"NotificationIdentifier",
object: nil)
NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: projNewRouter.indexOfSelectedItem)
}
func testNotication(notification: NSNotification){
print("Found Combo ID \(notification.object)")
}
func addComboxValue(myVal:[String],myObj:AnyObject){
let myValno: Int = myVal.count
for var i = 0; i < myValno; ++i{
myObj.addItemWithObjectValue(myVal[i])
}
}
}
You need to define a delegate for the combobox that implements the NSComboBoxDelegate protocol, and then use the comboBoxSelectionDidChange(_:) method.
The easiest method is for your NewProjectSetup class to implement the delegate, as in:
class NewProjectSetup: NSViewController, NSComboBoxDelegate { ... etc
Then in viewDidLoad, also include:
self.projNewRouter.delegate = self
// self (ie. NewProjectSetup) implements NSComboBoxDelegate
And then you can pick up the change in:
func comboBoxSelectionDidChange(notification: NSNotification) {
print("Woohoo, it changed")
}