Calling `print` inside NSView opens print dialog - macos

This is bizarre. I have a simple storyboard placeholder with GridView for the class name attribute.
class GridView: NSView {
required init?(coder: NSCoder) {
super.init(coder: coder)
print("coder: \(coder)")
}
override func drawRect(dirtyRect: NSRect) {
let rect = NSBezierPath(rect: dirtyRect)
NSColor.redColor().setFill()
rect.fill()
}
}
This worked as expected with just drawRect implemented, but after I added the initialiser it started opening the print dialog every time I run the app.
Why does this happen and how can I properly reimplement the storyboard initialiser for a custom view?

Calling print() does something different as it should - more precisely: something different as you would expect. It calls NSView's print(sender: AnyObject?) instead of the logging print. You could consider this as a bug or at least as quite unexpected behavior since the Swift.print(...) is generally much more used.
This action method opens the Print panel, and if the user chooses an option other than canceling, prints the receiver and all its subviews to the device specified in the Print panel.
Take a look at this post in the apple dev forum.
In fact it is not a bug since calling the print which is "closer" in the current context is certainly the correct way. Calling the parent's print is a lot more reasonable than calling some arbitrary other print. Only the fact that you normally use the other print is the confusing point here since in general you do not worry in what scope the logging print is located - it just works. If you think the other way around and would want to use the printing print of your parent it would be a lot more confusing having to explicitly state that you want to use the parents print and not the Swift.print(...).
The only "solution" would be to use different names for the two functions which is probably not going to happen.

The reason it's calling the print dialog is that Swift 2 apparently has two methods with the same signature.

This is a pretty old thread, but I must add here that you can write:
Swift.print("something")
and you would be using the "log print" function instead of view's one.

Related

Make "Close All" (cmd+option+W) apply only to one type of document windows

I have an application that manages different types of NSDocument subclasses (along with matching NSWindow subclasses).
For instance, it's possible that the app has one window of type A open, and two windows of type B.
Now, if a window of type B is active, and the user chooses "Close All" or hits cmd+option+W, all my app's windows are sent the close message.
But I only want all of the active window type's windows closed instead, i.e. only the two type B, not the type A window. How do I accomplish this?
I currently have no explicit menu entry for "Close All". Instead, macOS provides that automagically. If there perhaps a way to intercept a "closeAll" message? Can't find one, though.
AppKit will add the Close All menu item if there isn't one. Add an alternate menu item item with key equivalent cmd+option+W below the Close menu and connect it to your own action method.
You might succeed with overriding your document's canClose(withDelegate:,shouldClose:,contextInfo:) to return whether a document should be closed.
If this doesn't behave the way you want, you can create a subclass of NSDocumentController (if you don't have one already). Details on how to do that vary, but usually you have main (menu) XIB or main Storyboard, which has a "Document Controller" object: set its class to your custom class.
Then override closeAllDocuments(withDelegate:,didCloseAllSelector:,contextInfo:) and implement your custom logic.
Note that you should detect whether your app is about to quit and then really do close all you documents (unless you really want to prevent the app quit, e.g. because a document is dirty).
After some digging I figured out where the auto-generated "Close All" menu item sends its action to: To the closeAll: selector of the application target:
Thus my solution is to subclass NSApplication and implement the handler there, which then simply closes all windows that are of the same type (this assumes that I use specific subclasses for my different types of windows):
- (IBAction)closeAll:(id)sender {
Class windowClass = self.keyWindow.class;
for (NSWindow *w in self.windows) {
if (windowClass == w.class) {
[w performClose:sender];
}
}
}
Caution: If you adopt this pattern be aware that:
The closeAll: selector is not documented nor mentioned in the header files, meaning that Apple might feel free to change in a future SDK, though I find that unlikely. It will probably not break anything if that happens, but instead your custom handler won't be called any more.
The code simply tells all windows to close, ignoring the fact that one might reject to be closed, e.g. by user interaction. In that case you may want to stop the loop instead of continuing to close more windows (though I know of no easy way to accomplish that).

How Do I Disable Alt-Drag Selection on an NSTextView?

I do not know, and cannot find, the standard technical term for the drag-select functionality when the alt/option button is pressed over an NSTextView.
When alt is pressed, the crosshair appears and text can be selected/highlighted in columns/vertically, as in Xcode.
I would like to disable this functionality in my NSTextViews, how can I do that please?
Digging into the disassembly of AppKit quickly reveals a hook which tells NSTextView whether the ALT+drag interaction should be enabled. It's the private method called _allowsMultipleTextSelectionByMouse. Altering its behavior does the trick but it is achievable only via private API. In this case, it should be pretty safe to do so.
There are following two ways for altering the aforementioned method:
Approach 1: NSUserDefaults
The method internally accesses the NSProhibitMultipleTextSelectionByMouse of NSUserDefaults. You can control the boolean value for this key on the app level. The downsides of this approach are:
It can possibly be reenabled by the users of your app (e.g. via the defaults command line tool).
It affects all instances of NSTextView in your app even those you don't necessarily want to alter.
Approach 2: Swizzling
The approach I decided to go with is a simple swizzle of this method in my subclass of NSTextView.
internal final class MyTextView: NSTextView {
internal static func initializeClass() {
try? MyTextView.swizzle(
Selector(("_allowsMultipleTextSelectionByMouse")),
with: #selector(getter: swizzled_allowsMultipleTextSelectionByMouse)
)
}
#objc dynamic internal var swizzled_allowsMultipleTextSelectionByMouse: Bool {
return false
}
}
And somewhere in the app bootstrapping code like the main function you have to trigger the class initialization code by MyTextView.initializeClass().
The code snippet above uses my own wrapper around the swizzling API but there are surely some libraries out there to use or you can follow the advices from this article by PSPDFKit.

UI XCTest, elements which exist in view

I am new to UI test writing.
I wanted to know if it is possible to know what elements are/exists in view. I want to know how many and how they are called.
I tried something like this to loop through all elements, but it does not work.
for element in app.accessibilityElements! {
print(element)
}
You're looking for the debugDescription method of XCUIElement, in your case to get the entire hierarchy of the current visible window:
app.debugDescription
Quoting header comments:
Provides debugging information about the element. The data in the string will vary based on the
time at which it is captured, but it may include any of the following as well as additional data:
• Values for the elements attributes.
• The entire tree of descendants rooted at the element.
• The element's query.
This data should be used for debugging only - depending on any of the data as part of a test is unsupported.
You could always have a break point in the testcase and then make some print calls to the console.
po app.accessibilityElements
po app.accessibilityElements.elementBoundByIndex(0)
po app.buttons["Icon Menu Light"]
etc.
And see what comes back in the output view hierarchy to reference, or just a simple call to po app will print the hierarchy.
Once you know a particular view exists.. app.buttons["Icon Menu Light"].exists.. you can then try using something like this to confirm the button/element is visible within the current view by passing it to a helper function like:
func isElementInView(element: XCUIElement) -> Bool {
let window = XCUIApplication().windows.elementBoundByIndex(0)
return CGRectContainsRect(window.frame, element.frame) }
This will tell you whether the element is visible on the screen, because the element.exist call will return true, even if the element is off screen and hasnt been shown to the user (i.e. if something hides off screen and then transitions into the frame)

How to make XCode 6 show only relevant code completions

In XCode 6, using Swift; lets say I'm creating an instance of some object:
let sessionConfiguration = NSURLSessionConfiguration()
Is there a way to tell XCode 6 code completion to only include methods/properties that is actually defined on the NSURLSessionConfiguration class or it's super classes?
Using the object defined above XCode code completion suggests that I use methods like:
sessionConfiguration.accessibilityActionDescription(action: String)
sessionConfiguration.animationDidStart(anim: CAAnimation!)
But I can't see that those methods would be relevant for this class.
This seems like odd behavior in the new Swift programming paradigm obsessed with code safety.
extension NSObject {
/* Called when the animation begins its active duration. */
func animationDidStart(anim: CAAnimation!)
/* Called when the animation either completes its active duration or
* is removed from the object it is attached to (i.e. the layer). 'flag'
* is true if the animation reached the end of its active duration
* without being removed. */
func animationDidStop(anim: CAAnimation!, finished flag: Bool)
}
extension NSObject {
/*
Implement accessibilityActivate on an element in order to handle the default action.
For example, if a native control requires a swipe gesture, you may implement this method so that a
VoiceOver user will perform a double-tap to activate the item.
If your implementation successfully handles activate, return YES, otherwise return NO.
default == NO
*/
#availability(iOS, introduced=7.0)
func accessibilityActivate() -> Bool
...}
The two methods are Extension of NSObject declare in CAAnimation and UIAccessibility
Based on bumaociyuan's answer. The answer to my original question is:
No.
XCode autocompletion shows every method in the class hierarchy. Some methods are added to the class through inheritance, some by extensions.

Modifying NSTextStorage causes insertion point to move to the end of the line

I've got an NSTextView subclass acting as its NSTextStorage delegate. I'm trying to do 2 things:
Highlight the text in some ways
Evaluate the text and then append the answer to the textview.
I'm doing this in two different methods, both invoked by the - (void)textStorageWillProcessEditing:(NSNotification *)notification delegate callback.
I can do the syntax highlighting just fine, but when it comes to appending my answer, the insertion point jumps to the end of the line and I don't really know why. My evaluate method looks like the following:
NSString *result = ..;
NSRange lineRange = [[textStorage string] lineRangeForRange:[self selectedRange]];
NSString *line = [[textStorage string] substringWithRange:lineRange];
line = [self appendResult:result toLine:line]; // appends the answer
[textStorage replaceCharactersInRange:lineRange withString:line];
Doing that will append my result just fine, but the problem is, as mentioned, the insertion point jumps to the end.
I've tried:
Wrapping those above calls up in [textStorage beginEditing] and -endEditing.
Saving the selection range (i.e., the insertion point) before changing the text storage so I can reset it afterwards, but no dice.
Am I doing this right? I'm trying to do this the least hackish way, and I'm also unsure if this is the ideal place to be doing my parsing/highlighting. The docs lead me to believe this, but maybe it's wrong.
Reason for the insertion point to move
Suprisingly, I never found an actual explanation to why these suggestion do (or do not) work.
Digging into it, the reason for the insertion point to move is: .editedCharacters (NSTextStorageEditedCharacters in ObjC)affects the position of the insertion point from NSLayoutManager.processEditing(from:editedMask:...).
If only .editedAttributes/NSTextStorageEditedAttributes is sent, the insertion point will not be touched. This is what you will want to achieve when you highlight: change attributes only.
Why highlighting affects the insertion point
The problem with highlighting here is that NSTextStorage collects all edited calls during a single processing run and combines the ranges, starting with the user-edited change (e.g. the insertion when typing), then forming a union of this and all ranges reported by addAttributes(_:range:). This results in one single NSLayoutManager.processEditing(from:editedMask:...) call -- with an editedMask of both [.editedCharacters, .editedAttributes].
So you want to send .editedAttributes for the highlighted ranges but end up forming a union with .editedCharacters instead. That union moves the insertion point waaaaaaaay beyond where it should go.
Changing the order in processEditing to call super first works because the layout manager will be notified of a finished edit. But this approach will still break for some edge cases, resulting in invalid layout or jiggling scroll views while you type in very large paragraphs.
This is true for hooking into NSTextStorageDelegate, too, by the way.
Hook into callbacks after layout has truly finished to trigger highlighting instead of processEditing
The only solution that will work robustly based on reasons inherent to the Cocoa framework is to perform highlighting from textDidChange(_:) exclusively, i.e. after the layout processing really has been finished. Subscribing to NSTextDidChangeNotification work just as well.
Downside: you have to trigger highlighting passes for programmatic changes to the underlying string as these will not invoke the textDidChange(_:) callback.
In case you want to know more about the source of the problem, I put more my research, different approaches, and details of the solution in a much longer blog post for reference. This post is still a self-contained solution in itself:
http://christiantietze.de/posts/2017/11/syntax-highlight-nstextstorage-insertion-point-change/
I know that this question has been long since answered, however I had exactly the same issue. In my NSTextStorage subclass I was doing the following:
- (void)processEditing {
//Process self.editedRange and apply styles first
[super processEditing];
}
However, the correct thing to do is this:
- (void)processEditing {
[super processEditing];
//Process self.editedRange and apply styles after calling superclass method
}
It's simple! I ended up breaking this problem into 2 parts. I still do my syntax highlighting as a result of the textStorage delegate callback, but now I do my evaluation and appending elsewhere.
I ended up overriding both -insertText: and -deleteBackwards: (I might also want to do the same for -deleteForwards:, too). Both overrides look like the following:
- (void)insertText:(id)insertString {
[super insertText:insertString];
NSRange selectedRange = [self selectedRange];
[self doEvaluationAndAppendResult];
[self setSelectedRange:selectedRange];
}
I ended up having to reset the insertion point manually here. I'd still love to figure out why that's necessary, but at least this feels like less of a hack.

Resources