I would like to implement a NSTokenField that will show Tokens that - when hovering over the token - show a removal icon. Subsequently when I click on the icon I want the token to be removed.
After a lot of searching it seems that this is not possible with the standard NSTokenField. If someone knows how please let me know.
I have taken a look at https://github.com/octiplex/OEXTokenField and based on that code I have made a CustomTokenField implementation in Swift. So far so good I have a working CustomTokenField and when I hover over the token it shows a removal icon.
The next phase turns out to be a problem that I cannot figure out myself. How can I get a click on the token trigger a callback.?
The token class is derived from the NSTextAttachmentCell and the CustomTokenField is derived from the NStokenField:
class CustomTokenAttachmentCell: NSTextAttachmentCell {
. . .
}
class CustomTokenField: NSTokenField {
. . .
}
I have tried to approach this using two different angles:
Through the CustomTokenAttachmentCell
The NSTextAttachmentCell implements the NSTextAttachmentCellProtocol.
public protocol NSTextAttachmentCellProtocol : NSObjectProtocol {
. . .
public func wantsToTrackMouse() -> Bool
public func highlight(flag: Bool, withFrame cellFrame: NSRect, inView controlView: NSView?)
public func trackMouse(theEvent: NSEvent, inRect cellFrame: NSRect, ofView controlView: NSView?, untilMouseUp flag: Bool) -> Bool
. . .
}
This is hopeful. So I implemented these methods in CustomTokenAttachmentCell and wantsToTrackMouse() is actually being called. I have implemented that to return ‘true’.
override func trackMouse(theEvent: NSEvent, inRect cellFrame: NSRect, ofView controlView: NSView?, untilMouseUp flag: Bool) -> Bool {
Swift.print(“trackMouse”)
return true
}
override func highlight(flag: Bool, withFrame cellFrame: NSRect, inView controlView: NSView?) {
Swift.print("highlight")
}
override func wantsToTrackMouse() -> Bool {
Swift.print(“trackMouse”)
return true
}
The other two methods are never called. Is there something else that needs to be done to make that they are being called?
Through the CustomTokenField
I also tried to approach this from the CustomTokenField. It is possible to get mouse events using MouseDown(), however I could not
find a way to actually access the Tokens from the cells.
I have seen many posts here on StackOverflow and I have seen tips but none of them seems to point in the right direction.
Somehow I have come to the conclusion that you can only get mouse events in the case there is a NSControl in the hierarchy. For tokens that is not the case. An NSControl is part of the hierarchy for views hence my attempt to achieve this through the CustomTokenField but I run in a dead-end there as well.
E.g. this question Clicking token in NSTokenField is exactly the same but setting the action or target will generate a fatal error because the setAction and setTarget are stubs for the base class.
I am a beginning programmer on Coacoa, so hopefully this is just a matter of lack of knowledge.
Any advise would be appreciated.
Have you tried adding an NSButton on top of all of the whole CustomTokenAttachmentCell view? Then add an #IBOutlet action to the button for the click and pass that via delegation to the TokenField where you can control the tokens that are displayed.
I am also trying to implement this in my app, so if you are able to share any of the code it would be greatly appreciated. Thanks!
Related
This question deals with tab window restoration in a document-based app.
In an OSX, document-based app, which allows a user to create and convert tab windows, I need to preserve and restore the 'tab' state of each window.
Currently, my document controller restores its documents windows, but not the tab deployment; I get back individual windows; I can merge all back into one, but this is too heavy-handed as their former groupings are lost.
My app document class's - makeWindowControllers() function is where I affect the new controllers, whether they should cascade, which I'd read be false, during restore:
// Determine cascade based on state of application delegate
controller.shouldCascadeWindows = <app did receive applicationWillFinishLaunching>
so it would be false until it's finished launching.
Finally, my window's class features methods:
override func addTabbedWindow(_ window: NSWindow, ordered: NSWindow.OrderingMode) {
super.addTabbedWindow(window, ordered: ordered)
window.invalidateRestorableState()
}
override func moveTabToNewWindow(_ sender: Any?) {
super.moveTabToNewWindow(sender)
self.invalidateRestorableState()
}
override func encodeRestorableState(with coder: NSCoder) {
if let tabGroup = self.tabGroup {
let tabIndex = tabGroup.windows.firstIndex(of: self)
coder.encode(tabIndex, forKey: "tabIndex" )
Swift.print("<- tabIndex: \(String(describing: tabIndex))")
}
}
override func restoreState(with coder: NSCoder) {
let tabIndex = coder.decodeInt64(forKey: "tabIndex")
Swift.print("-> tabIndex: \(tabIndex)")
}
to invalidate the window restore state when the tab state is changed. But I'm not sure with the NSWindowRestoration protocol implementation, who or what needs to implement the protocol when a document controller is involved.
I think this is the reason the last function is never called. I get debug output about the encoding but during the next app execution the restoreStore(coder:) function is never called.
So who implements this window restore protocol in such an environment I guess is my question, or a decent example doing so.
My question reveals you not require anything special for a document based app; I've updated my prototype which features this support and environment here SimpleViewer, which features Swift5, a document based app supporting tabs.
I have File Promises implemented in a Cocoa app that allows dragging images from a view and dropping them to folders on the machine or to apps like preview or evernote. In the past this has worked well using the NSDraggingSource delegate and 'namesOfPromisedFilesDropped' method. This method would return the drop location and would allow me to write the image data directly there. So when dropping to an application icon like preview, or within an app like evernote, the file would be written and the app would either load or the image would simply show within.
Unfortunately this method was deprecated in 10.13 so it no longer gets called in new OS versions. Instead I've switched over to using the filePromiseProvider writePromiseTo url method of the "NSFilePromiseProviderDelegate" delegate. This method gets called, and the image data is processed. I get the destination URL and attempt to write the image data to this location. This works perfectly fine when simply dragging to folders on the Mac. But, when dragging to other app icons like Preview.app, or directly to a folder in Evernote, I get an error 'Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"'.
I've attempted this using full URLs, and, URL paths. Regardless of either, when dragging to other apps it simply will not allow the drop or creation of the file at that location.
Is there some entitlement that may be missing? I've even attempted the Apple source code found here with the same error: File Promises Source
Here is the code I'm using now that's returning the error with inability to write to outside app locations. This only works for dragging to folders on the computer.
extension DragDropContainerView: NSFilePromiseProviderDelegate {
internal func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
let fileName = NameFormatter.getFormattedNameFromDate() + "." + fileType.getFileType().typeIdentifier
return fileName
}
internal func operationQueue(for filePromiseProvider: NSFilePromiseProvider) -> OperationQueue {
return workQueue
}
internal func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL, completionHandler: #escaping (Error?) -> Void) {
if let snapshot = filePromiseProvider.userInfo as? SnapshotItem {
if let data = snapshot.representationData {
do {
try data.write(to: url)
completionHandler(nil)
} catch {
completionHandler(error)
}
}
}
}
}
Any help on this issue would be great. Ultimately, I simply want to be able to drag the image to an app and have that app accept the drag. This used to work but no longer does.
Update 1:
After extensive experimentation I've managed to find a 'solution' that works. I don't see why this works but some how this ultimately kicks off a flow that fires the old 'deprecated' method.
I create the dragged item;
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardWriter(forImageCanvas: self))
draggingItem.setDraggingFrame(screenshotImageView.frame, contents: draggingImage)
beginDraggingSession(with: [draggingItem], event: event, source: self)
This calls the below method;
private func pasteboardWriter(forImageCanvas imageCanvas: DragDropContainerView) -> NSPasteboardWriting {
let provider = FilePromiseProvider(fileType: kUTTypeJPEG as String, delegate: self)
provider.userInfo = imageCanvas.snapshotItem
return provider
}
This sets up the custom file promise session using the subclass below. As you can see, I'm not handling any logic here and it seems to be doing very little or nothing. As an added test to verify it's not really doing much, I'm setting the pasteboard type to 'audio'. I'm dragging an image not audio but it still works.
public class FilePromiseProvider : NSFilePromiseProvider {
public override func writableTypes(for pasteboard: NSPasteboard)
-> [NSPasteboard.PasteboardType] {
return [kUTTypeAudio as NSPasteboard.PasteboardType]
}
public override func writingOptions(forType type: NSPasteboard.PasteboardType,
pasteboard: NSPasteboard)
-> NSPasteboard.WritingOptions {
return super.writingOptions(forType: type, pasteboard: pasteboard)
}
}
So long at the above methods are implemented it apparently kicks off a flow that ultimately calls the 'deprecated' method below. This method works every time perfectly in Mojave showing that there must be an issue with the NSFilePromises API. If this method works, the file promises API should work the same but it does not;
override func namesOfPromisedFilesDropped(atDestination dropDestination: URL) -> [String]?
Once this method gets called everything works perfectly in Mojave for drags to app icons in the dock and directly into applications like Evernote returning the app to 100% drag drop functionality as it used to work in previous OS versions!
My file promises delegate is still in place but looks like the below. As you can see it's not doing anything any longer. However it's still required.
extension DragDropContainerView: NSFilePromiseProviderDelegate {
func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
return ""
}
func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL, completionHandler: #escaping (Error?) -> Void) {
}
}
Any comments on this 'solution' would be great. Any suggestions on how to do this better would also be welcomed. Please note, that according to Apple, there "is no guarantee that the file will be written in time" using the File Promises API. But with the above, the old deprecated method is somehow called in Mojave and works flawlessly every time.
Thanks
I am trying to write an application that forwards the keypresses on one of its TextFields to a different process using its processID.
The code (below) that I have got works well in terms of listening to the key presses (right now, all key presses), but the trouble is coming from forwarding the events (keyDown/keyUp) to the other process.
Things I have tried:
CGEvent.postToPid: Does not work (nothing sent to the other process)
CGEvent.postToPSN: Could not get it to work as it requires the Process Serial Number as an UnsafeMutableRawPointer (not that I know how to get the PSN is in the first place)
Manually creating the CGEvent and then trying 1. and 2.
Reading documentation on the classes and their functions (they don't seem to be that popular)
Any help would be greatly appreciated.
import Cocoa
class ViewController: NSViewController {
var pid: Int32 = 12345
override func viewDidLoad() {
super.viewDidLoad()
NSEvent.addLocalMonitorForEvents(matching: .keyDown) {
(event) -> NSEvent? in
self.keyDown(with: event)
return event
}
// For some reason, keyUp is triggered twice
NSEvent.addLocalMonitorForEvents(matching: .keyUp) {
(event) -> NSEvent? in
self.keyUp(with: event)
return event
}
}
override func keyDown(with event: NSEvent) {
print("Down " + String(event.keyCode))
var cgEvent = event.cgEvent
cgEvent?.postToPid(pid)
}
override func keyUp(with event: NSEvent) {
print("Up " + String(event.keyCode))
var cgEvent = event.cgEvent
cgEvent?.postToPid(pid)
}
}
One of the comments suggested that permissions might be the cause, and it was.
My problem was resolved by going to the Capabilities section of my project and disabling App Sandboxing, which was enabled by default.
Keep in mind that App Sandboxing is a requirement for your app to be on the App Store (reference)
The title is on how to write better Swift code but, my real question is really what is better if I create a function, then call it when the button is clicked vs I write what I want to happen once the button is clicked .
Eg.
var thing = 0
func hi(){
// Do something
thing++
}
#IBAction func somethingHi(sender: AnyObject) {
println(hi)
}
vs
var thing = 0
#IBAction func othersomethingHI(sender: AnyObject) {
thing++
println(thing)
}
I know both do the same thing but, is one "better" written than the other?
If an IBAction does something that you might want to do at some other time then it should call a function that performs that action, so that "others" can effect the same thing without duplicating code. If not, implement it solely in the action.
If you're code is short and won't be reused, you can just put it inside the #IBAction func function.
I have a need to sync two web views, so that anything that happens in one web view happens simultaneously in the other.
I have tried various ways, without success, and the more I try the more convoluted and likely bug ridden this is getting.
I feel there maybe a very simple way to do this, but can not figure it out.
One of the things that I note is not allowed is having the same NSTextField as the "takeStringURLFrom"
override func controlTextDidEndEditing(obj: NSNotification) {
webViewLeft.takeStringURLFrom(hiddenTextField)
webViewRight.takeStringURLFrom(urlField)
}
override func webView(sender: WebView!, didCommitLoadForFrame frame: WebFrame!) {
if frame == sender.mainFrame {
urlField.stringValue = sender.mainFrameURL
hiddenTextField.stringValue = sender.mainFrameURL
webViewRight.takeStringURLFrom(urlField)
webViewLeft.takeStringURLFrom(hiddenTextField)
printLn("realised just creating an infinite loop here")
}
}
I don't like this but it appears to work as needed. I think it needs some refinement. Using a hidden text field to mimic the url for the second web view and tie each web view to there respective text fields via the web view's referenced action "takeStringUrlFrom"
override func webView(sender: WebView!, didStartProvisionalLoadForFrame frame: WebFrame!) {
if frame == sender.mainFrame {
urlField.stringValue = sender.mainFrameURL
hiddenTextField.stringValue = sender.mainFrameURL
window.makeFirstResponder(nil)
window.makeFirstResponder(hiddenTextField)
window.makeFirstResponder(nil)
window.makeFirstResponder(urlField)
window.makeFirstResponder(nil)
}
}