How to set the default writing direction for NSTextView? - macos

I'm trying to set up a text view that a user can type Hebrew text into from right-to-left. Currently it defaults to a left-to-right text direction, which the user can manually change by right-clicking and selecting "Writing Direction > Right to Left", but what I need is for the text view to always default to this, without requiring the user to set it manually.
There's an option for setting this in Interface Builder, which is always ignored when I build and run my app.
I would be fine setting it from code, but I can't figure out how to use the method that looks like the closest thing to what I need:
hebrewTextView.setBaseWritingDirection(NSWritingDirection.RightToLeft, range: <#NSRange#>)
The range is stumping me. If it takes a range can this be the method I need? Wouldn't the default behavior of a field be independent of any range of text?
Is there a way to set the default writing direction for a NSTextView? Can this be done from the storyboard/interface builder, or from code? If setBaseWritingDirection is the method for this, what is the range value for and how would I set it for a field that is initially empty?

NSTextView inherits from NSText. NSText has a baseWritingDirection property. Try setting that:
hebrewTextView.baseWritingDirection = NSWritingDirection.RightToLeft
It also inherits the action method makeBaseWritingDirectionRightToLeft() from NSResponder, which presumably what the contextual menu uses. So, you could call that.
If neither of those works, you can set the text view's defaultParagraphStyle to one whose baseWritingDirection is right-to-left:
var style = hebrewTextView.defaultParagraphStyle.mutableCopy() as! NSMutableParagraphStyle
style.baseWritingDirection = NSWritingDirection.RightToLeft
hebrewTextView.defaultParagraphStyle = style

Related

How can I determine what part of text in a scroll view is visible on screen from an Xcode UI test?

I'm new to the Xcode User Interface testing framework. I can successfully manipulate the screen elements, but cannot work out how to produce a meaningful assertion about what text is visible in a scrolling view.
The test I would like to write would go as follows: launch the app, type lots of text into a text view (enough that the first line scrolls out of view), assert that the first line of text is not visible, scroll the view back up to the top, then assert that the first line is now visible. Note that the purpose of this test is to ensure my app has wired things up correctly, not to test Apple's code.
XCUIApplication allows me to type into my NSTextView instance, and also allows me to scroll the associated NSScrollView. But how do I assert whether the first line of text is currently visible? The value attribute on XCUIElement provides the entire text content of the view, whether or not it is currently displayed.
The accessibilityRange(forLine:) and accessibilityString(for:) methods on NSTextView would be ideal, but I can't see how to access them as the UI test only has access to an XCUIElement, not the underlying NSTextView.
Have I missed something, or is there a better way to approach this?
If you set the accessibility identifier in the storyboard or in code for the text view you can get the text view via (assuming you gave it the id "textview1" and the window it's in has the default accessibility identifier of "Window"):
let textview1TextView = app.windows["Window"].textViews["textview1"]
but that won't actually get you what you need.
Instead, set the accessibility identifier of the scrollview and get that:
let scrollview = app.windows["Window"].scrollViews["scrollview1"]
Then use that to get the scrollbars (you should only have one in this case; you can use scrollbars.count to check.
let scrollbars = scrollview.scrollBars
print("scrollbars count: \(scrollbars.count)")
Then you can use the value attribute of the scrollbar to get it's value:
(you're converting a XCUIElemenTypeQueryProvider into an XCUIElement so you can get it's value):
let val = scrollbars.element.value
it will be 0 at the top and a floating point value when scrolled (one line of text in my test code showed a value of {{0.02409638554216868}}.
Documentation that will help you explore further:
XCUIElementTypeQueryProvider
XCUIElementAttributes
Note that you can put a breakpoint in the middle of your test, run it and then use the debugger console to examine things:
(lldb) po scrollbars.element.value
t = 749.66s Find the ScrollBar ▿ Optional<Any>
- some : 0
(lldb) po scrollbars.element.value
t = 758.17s Find the ScrollBar ▿ Optional<Any>
- some : 0.05421686746987952
and while in the debugger you can even interact with your app's window to scroll it manually (which is what I did between typing in those two po calls), or perhaps add text and so on.
OK OP now noted that they're interested in the specific text showing or not rather than the first line in view or not (which is what I previously answered above).
Here's a bit of a hack, but I think it'll work:
Use XCUICoordinate's click(forDuration:, thenDragTo:) method to select the first line of text (use the view frame to calculate coordinates) and then use the typeKey( modifierFlags:) to invoke the edit menu "copy" command. Then use NSPasteboard methods to get the pasteboard contents and check the text.
Here's a quick test I did to validate the approach (selecting the first line of text using XCUICoordinate as noted above is left as an exercise for the reader):
NSPasteboard.general.clearContents()
// stopped at break point on next line and I manually selected the text of the first line of text in my test app and then hit continue in the debugger
textview1TextView.typeKey("c", modifierFlags:.command)
print( NSPasteboard.general.pasteboardItems?.first?.string(forType: NSPasteboard.PasteboardType.string) ?? "none" );
-> "the text of the first line" was printed to the console.
Note that you can scroll the selection off screen so you have to not scroll after doing the select or you won't be getting the answer you want.

How to deselect the contents of a TextField in swift

I have a simple desktop app where a TextField should be focused when the window loads. I have this working, but it's a little annoying that, having loaded the users content into the TextField, the entire contents of the field become selected automatically. The user may want to start editing the content, but they will rarely/never want to replace it all at once (imagine a text editor doing this, to see what I mean).
I see there is an Action for selectAll: but what I want is the opposite Action of selectNone:
I tried passing nil to the selectText method, but that doesn't work:
textField.selectText(nil)
I found a number of answers on StackOverflow that mention a selectedTextRange, but this appears to be outdated, because Xcode 6.3 doesn't recognize this as a valid property on TextField.
Can anyone explain how I do this?
It's been a while since I've dealt with NSTextFields to this level (I work mostly in iOS these days).
After doing a little digging I found this on the net:
NSText* textEditor = [window fieldEditor:YES forObject:textField];
NSRange range = {start, length};
[textEditor setSelectedRange:range];
window is the window containing your field, textField.
This requires the field editor to be managing your field, what can be done simply by previously selecting the whole text of the field using the selectText:sender method.
Here is the final swift code that I got working based on what Duncan C posted:
if let window = NSApplication.sharedApplication().mainWindow {
let textEditor = window.fieldEditor(true, forObject: textField)!
let range = NSRange(0..<0)
textEditor.selectedRange = range
}

Search for an NSView inside a NIB by string

I'm trying to write an application which should display some data. The data in question comes from a different module in our code (written in C, not ObjC), and for various reasons is identified by a string, not an integer or other form of constant. After the glue code, I have an incoming method on my AppDelegate like so:
-(void)newstringdata:(NSString*)data withLabel:(NSString*)label;
This method should always take the value of data and set it as the text for a particular label in the UI. The problem is, which label.
I could of course create an NSDictionary and fill it at run time with the possible values for the label parameter in the newstringdata:withLabel: method and references to outlets, but this seems somewhat ugly and inefficient; it requires me to maintain the outlets, the nib, and the NSDictionary-initializing code.
Instead, if possible, I would like to set a property in the interface designer somewhere, and then do a lookup in my newstringdata:withLabel: method based on the label which was passed which returns the NSLabel.
Is this possible? If so, how would I do it?
If the value of label will never include a slash (/), backslash (\), or colon (:), you can use the identifier property of NSView.
In the xib, enter each label string as the NSTextViews's identifier (in the Identity tab, 3rd tab from left).
Then in your code, loop through all views and:
if ([aView.identifier isEqualToString:label])
[aView setStringValue:data];

Removing text from NSTextView

I have created an NSTextView and managed to populate it using a getter for the scroll view that it is nested in, and using the .insertText() function.
How do I empty the same NSTextView? I have read the documentation and there doesn't seem to be a function .removeText(). It seems a bit weird that Apple would allow you to insert data but not remove it programmatically. I have searched high and low for answers but have come up empty handed.
You can't set the text of the text view directly, but you can set its textStorage's, getting that object's mutableString, which it inherits from NSMutableAttributedString, and then using setString(), passing an empty string.
No idea what I was thinking. Use var string { get set }, inherited from NSText.
If you set the text on the textview directly you'll lose the ability to automatically have the undo manager pick that change up.
I've found it more reliable to first select all the text and then delete it.
[self.textInput selectAll:self];
[self.textInput delete:self];

How to know which slider is selected

I have multiple sliders in my application,how to know which slider is selected. And how to set the slider values to integer type where double is default type;
You can use the Tag property of the slider to assign an integer (or even better, use an enum). This is useful if you have multiple buttons or controls with the same target.
The sender will be the control that triggered the action, and you can get the tag from that. You can also cast back to the original control type if you need to access other properties.
See Objective C IBOutlets for information on the sender.
Also note that normally you would set the tag in Interface Builder (IB), but you can also set them in code.
You should set up the slider to target a method when it changes, that way you can be informed when a slider changes. You should do this in interface builder but if for some reason you can't, perhaps you have to dynamically determine the sliders needed then you can use methods like
[NSControl setAction:]
[NSControl setTarget:]
NSControl can have their value set with various data types (int, float, double) even some types that are not applicable to NSSliders (for example NSString), their is no default type, just use the following method.
-[NSControl setIntValue:]

Resources