NSTextview won't show pop-up menu for Korean text replacements - macos

(Disclaimer -- I don't understand Korean, so I have some test examples that don't make sense but demonstrate the issue.)
I have a heavily customized NSTextView/NSTextStorage to provide syntax highlighting and other behaviors in my app. A Korean user pointed out some difficulties using the macOS text replacement functionality.
The pop-up menu to accept/reject text replacements doesn't appear.
Text replacements of multi-character strings don't work -- it seems to only allow the last character to trigger a replacement.
To test, I configured the following text replacements on my (US English) mac:
해 (created by typing go) is replaced with GO (creative, I know...)
새 (to) is replaced with TO
ㅂㅈ (qw) is replaced with qw
햅ㅈ (goqw) with go qw
I enabled the 2-Set Korean input source (the "Caps Lock to switch" feature is a life saver) (If it matters, I have Font Size 18, Input format Hangul (Hanja), and Delete by Gulja.
I am running 10.12.6 on this machine.
Test environments:
I downloaded Apple's source for TextEdit, compiled, and used that (so that I could confirm TextEdit was compiled using same SDK as my app)
I created a "toy" application that consists of a single window. In the ViewController's viewDidLoad, I use this to replace the contents with a basic NSTextView (without any of my customizations):
- (void)viewDidLoad {
[super viewDidLoad];
NSView * contentView = [self view];
NSRect cFrame = [contentView bounds];
NSView * oldView = [[self.view subviews] firstObject];
[oldView removeFromSuperview];
// Create basic NSTextView with mostly default settings
NSTextView * newView = [[NSTextView alloc] initWithFrame:cFrame];
[newView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view setAutoresizingMask:NSViewHeightSizable | NSViewWidthSizable];
[newView setVerticallyResizable:YES];
[newView setHorizontallyResizable:NO];
// Scrollview for convenience
NSScrollView * scroller = [[NSScrollView alloc] initWithFrame:cFrame];
[scroller setAutoresizingMask:NSViewWidthSizable | NSViewWidthSizable];
[scroller setHasVerticalRuler:YES];
[scroller setUsesPredominantAxisScrolling:YES];
[scroller setHorizontalScrollElasticity:NSScrollElasticityNone];
[scroller setDocumentView:newView];
[self.view addSubview:scroller];
// Do any additional setup after loading the view.
textView = newView;
[textView setRulerVisible:YES];
}
Results:
Using TextEdit, all of the replacements display a pop-up menu allowing me to accept/reject the replacement as I type (Korean as well as my own English text replacements).
Using the toy application, single character replacements are changed automatically, but no pop-up menu is displayed.
The two character replacement is not triggered in the toy app.
Typing quickly in the toy app (with a space after) allows all Korean replacements to happen, but still no pop-up.
English text replacements, including multi-letter words, show the pop-up menu (e.g. mmd -> MultiMarkdown).
Changing the font for the NSTextView doesn't change behavior (e.g. Times is same as AppleMyungjo).
Reviewing the TextEdit code, I was unable to find anything relevant to indicate what I am doing wrong. The fact that pop-ups appear with English replacements but not Korean ones seems odd, especially with an unmodified NSTextView. Using a programmatic NSTextView should exclude me doing something wrong with the Xcode interface builder setup. TextEdit uses programmatic NSTextViews, and I couldn't see anything obvious in their setup that differs meaningfully from mine, but obviously there is something going on.
Any solutions?
Thanks!

Related

selectable NSTextField and NSColorPanel – how to break their undesired interplay?

In a seemingly trivial setup, I encounter an undesired interplay between a selectable NSTextField and an NSColorPanel that I cannot get rid of and that drives me nuts.
Here’s the setup: Within one window, I have a selectable Multi-Line Label (de facto an NSTextField) and an NSColorWell.
The Color Well allows the user to color geometric objects in the GUI; it has nothing to do with text whatsoever. Of course, clicking on the color well activates it, i.e. brings up the shared NSColorPanel and connects the color well to it.
The Text Field is completely independent from the colored objects in the GUI and presents data to the user. It is read-only, i.e. not editable. Since the data is organized in columns, I use tabs for text formatting and the setAttributedStringValue: method of NSTextField to display the data.
At first glimpse, everything works as you would expect in a such a trivial setup.
But here comes the rub: I want the user to be able to copy the data in the text field to process it elsewhere. Therefore, the NSTextField has to be selectable. And setting it to be selectable is where the problems start:
When the user clicks on the selectable text field to select the text, the window’s field editor takes over, and as a consequence, all the tab settings of the attributed text are lost and the text gets mingled. The usual way to prevent this is to set the allowsEditingTextAttributes property of the NSTextField to YES. If I do this, the tab formatting is preserved when the user selects the text. But now the NSColorPanel (if visible) unintentionally also switches to the text color (always black), and if the color well is active (connected to the NSColorPanel), it will remain active, thereby changing the color of all geometric GUI objects to black. Ouch!
I have found no way to set the selectable and allowsEditingTextAttributes properties of NSTextField to YES but still prevent it from communicating with the NSColorPanel.
The obvious alternative route would be to preserve the tab formatting for selected text even with allowsEditingTextAttributes set to NO (which would disconnect the color panel from the text field, as desired). But I’ve had no success with this approach either, although I do not really understand why:
My idea was to set the required tabs as the defaultParagraphStyle of the field editor of the text field. So, I set up a customized field editor:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSArray *myTabs = #[
[[NSTextTab alloc] initWithType:NSRightTabStopType location:100],
[[NSTextTab alloc] initWithType:NSRightTabStopType location:200],
[[NSTextTab alloc] initWithType:NSRightTabStopType location:300]
];
NSMutableParagraphStyle *myParagraphStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
[myParagraphStyle setTabStops:myTabs];
myFieldEditor = [NSTextView new]; // myFieldEditor is an instance variable
[myFieldEditor setDefaultParagraphStyle:myParagraphStyle];
[window setDelegate:self];
[window fieldEditor:YES forObject:myTextField];
}
And activate it for the text field in the windowWillReturnFieldEditor:toObject: delegate method:
- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client
{
if (client == myTextField) return myFieldEditor;
return nil;
}
I even made sure that my custom field editor is indeed used by subclassing the NSTextFieldCell of my text field and logging the propagated field editor:
#implementation myTextFieldCell
- (NSText *)setUpFieldEditorAttributes:(NSText *)textObj
{
NSTextView *newTextObj = (NSTextView*)[super setUpFieldEditorAttributes:textObj];
NSLog(#"STYLE: %#", [newTextObj defaultParagraphStyle]);
return newTextObj;
}
#end
Now, when I select the text in the text field, I get the following log output:
2017-11-02 11:51:07.432 Demo[94807:303] STYLE: Alignment 4, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (
100R,
200R,
300R
), DefaultTabInterval 0, Blocks (null), Lists (null), BaseWritingDirection -1, HyphenationFactor 0, TighteningFactor 0.05, HeaderLevel 0
Which is exactly what is expected.
But still, the tab formatting disappears in the text field as soon as I select the text. I have no idea why this does not work.
So I’m stuck either way. If I set the allowsEditingTextAttributes property of NSTextField to YES, tab formatting is preserved when the text is selected, but my colored objects in the GUI unintentionally change to black. If I set the allowsEditingTextAttributes property to NO, the color panel behaves as it should, but the tab formatting is lost as soon as I select the text.
This is a very unfortunate case of Cocoa trying to be too smart and thereby making a completely trivial setup a huge issue.
Any ideas anyone?
OK, so I ended up with the suggestion that #Willeke (thanks!!) made in his comment to my question: To use NSTextView instead of NSTextField to implement my Multi-Line Label.
I will first sum up why it seems impossible to do what I wanted with NSTextField, and then the solution with NSTextView.
Why NSTextField doesn’t work
As described, my idea for a solution was to customize the field editor for the NSTextField, setting the tab stops I need, so that I need not set NSTextField’s allowsEditingTextAttributes property to YES (which would unintentionally couple the text field to the color panel). This, I hoped, would preserve the tab stops of my attributed string’s paragraph style when I select the text in the text field and thereby activate the field editor.
Extensive testing has shown that this does not work for several reasons:
As #Willeke pointed out, setting the usesFontPanel property of NSTextView to NO also breaks the connection of the text view to the color panel (as desired). However, this does not work for the NSTextView that is the field editor of the NSTextField, because in this context, this setting is always overwritten by the allowsEditingTextAttributes property of the NSTextField: If allowsEditingTextAttributes is YES, the font and color panels are coupled regardless of the value of usesFontPanel, if it is NO, the font and color panels are decoupled regardless of the value of usesFontPanel.
The idea to use the tab stops of a customized field editor instead of using the tab stops of my attributed string’s paragraph style (which would require allowsEditingTextAttributes to be YES) wouldn’t work, anyway, because the tab stops settings of the field editor are obviously always completely ignored by NSTextField, regardless of the value of the allowsEditingTextAttributes property. NSTextField always uses the evenly spaced default tab stops.
Judging from intense googling, the other variant – setting allowsEditingTextAttributes to YES but somehow modifying NSColorPanel to not connect to the NSTextField nevertheless – is impossible to implement without recurring to private methods of NSColorPanel.
How to implement the solution with NSTextView
While it seems overkill to instantiate a complete NSTextView embedded in a clip view and a scroll view just to get the functionality of a text field, in the end it’s the easiest (or even only possible) solution.
To make the scroll view disappear, you’ll have to basically uncheck everything in the NSScrollView’s Attribute inspector in IB, in particular Show Vertical Scroller. Set Draw Background and the border type to the kind of appearance you want; if you want to mimic a multi-line label (like I did), uncheck Draw Background and choose the invisible border type. In the Attribute inspector of the embedded NSTextView, also uncheck all attributes except Selectable, in particular Uses Font Panel.
Make sure the size of the NSTextView is large enough to hold the complete content string to avoid unintentional scrolling effects and fix the text position. If your content string ends with a line brake, you’ll need enough space for an empty line beneath it. If you did not uncheck Draw Background and this does not look the way you want, don’t draw the background of either the NSScrollView or the NSTextView, select the invisible border for NSScrollView and then put an NSBox of the desired size and appearance beneath them.
You can now set the attributed content string with:
[[myTextView textStorage] setAttributedString:myAttributedString];
Note that this does work although the editable property of NSTextView is set to NO since you’re modifying the NSTextStorage, not the NSTextView itself.
But unfortunately, we’re not finished yet.
When you’re using an NSTextField to display readonly data as you typically do in a Label, more often than not you would not want the text field to be part of your key view loop (that circles through your controls by pressing the Tab key). To achieve this, you can simply set the refusesFirstResponder property of NSTextField to YES. But NSTextView does not inherit from NSControl and therefore does not have this property. So in the end, we’ll have to subclass NSTextView to add the refusesFirstResponder property.
The implementation overwrites becomeFirstResponder and goes like this:
- (BOOL)becomeFirstResponder
{
if (!_refusesFirstResponder) return [super becomeFirstResponder];
NSEvent *event = [NSApp currentEvent];
if ([event type] == NSLeftMouseDown || [event type] == NSRightMouseDown) return [super becomeFirstResponder];
NSView *validKeyView = ([event modifierFlags] & NSShiftKeyMask)? [[[self previousValidKeyView] previousValidKeyView] previousValidKeyView] : [self nextValidKeyView];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{[[self window] makeFirstResponder:validKeyView];}];
return NO;
}
If refusesFirstResponder is NO, we simply return super’s implementation.
If it is YES, we check if the NSTextView is about to become first responder because of a mouse click within it. If so, we also simply return super’s implementation, thereby allowing text selection with the mouse.
Other than that, we forward the first responder request to the next or previous key view (depending on whether the Shift key was pressed) and return NO, refusing to become first responder. Determining the previous key view is a bit tricky because the closest previous key view is the embedding NSClipView we don’t want or need, but have to use because Interface Builder does not offer a “pure” NSTextView. Then comes the embedding NSScrollView, and only then the previous key view we actually want.
Also, since we’re amidst a process which determines the first responder, we cannot simply invoke makeFirstResponder:, but have to postpone it to the next iteration of the run loop.
Now that we have implemented refusesFirstResponder, we’ll still have to mimic NSTextField’s behavior to dismiss any text selection when it loses first responder state. We can do this in an NSText delegate method. Assuming we don’t need other delegate functionality, we can make our subclass its own delegate and add this delegate method:
- (void)textDidEndEditing:(NSNotification*)notification
{
[[notification object] setSelectedRange:NSMakeRange(UINT64_MAX, 0)];
}
Finally, if we have to subclass, anyway, we might as well add a setAttributedString: convenience method.
So what we’ll end up with is this:
Header:
#import <Cocoa/Cocoa.h>
IB_DESIGNABLE
#interface MyTextFieldLikeTextView : NSTextView <NSTextViewDelegate>
#property IBInspectable BOOL refusesFirstResponder;
- (void)setAttributedString:(NSAttributedString*)attributedString;
#end
Implementation:
#import "MyTextFieldLikeTextView.h"
#implementation MyTextFieldLikeTextView
- (void)awakeFromNib
{
[self setDelegate:self];
}
- (BOOL)becomeFirstResponder
{
if (!_refusesFirstResponder) return [super becomeFirstResponder];
NSEvent *event = [NSApp currentEvent];
if ([event type] == NSLeftMouseDown || [event type] == NSRightMouseDown) return [super becomeFirstResponder];
NSView *validKeyView = ([event modifierFlags] & NSShiftKeyMask)? [[[self previousValidKeyView] previousValidKeyView] previousValidKeyView] : [self nextValidKeyView];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{[[self window] makeFirstResponder:validKeyView];}];
return NO;
}
- (void)textDidEndEditing:(NSNotification*)notification
{
[[notification object] setSelectedRange:NSMakeRange(UINT64_MAX, 0)];
}
- (void)setAttributedString:(NSAttributedString*)attributedString
{
[[self textStorage] setAttributedString:attributedString];
}
#end
Still a lot of effort only because Cocoa tries to outsmart us and insists on connecting the NSColorPanel to each and every NSTextField that allows for attributed text …

Trouble matching the vibrant background of a Yosemite NSMenuItem containing a custom view

I am attempting to add a custom view to an NSMenuItem in the OS X 10.10 Yosemite menu bar.
The custom view is simply an NSView background with an NSTextField “label”.
The problem is that the background NSView is given Yosemite-style vibrancy/transparency when added to the menu. The NSTextfield label is not.
Through the use of NSRectFillUsingOperation I've gotten this to look good for some background colors in Yosemite. But others continue to not match. When it is working, after manually "highlighting" the view, the original colors change and no longer match. I can dig up some example code for this if needed.
Then, when it is looking somewhat good in Yosemite, it looks terrible in 10.9 Mavericks.
I've also tried setting the wantsLayer property to YES to turn the view into a CALayer-backed view. This creates other issues such as text not anti-aliasing correctly against a clear background.
My Question:
How do I display a label on top of a NSMenuItem custom view? The label's background must exactly match the view's background. Solution must work in Yosemite and Mavericks.
Example code below:
self.statusItem = [[NSStatusBar systemStatusBar]
statusItemWithLength:NSVariableStatusItemLength];
[self.statusItem setTitle:#"TEST"];
[self.statusItem setHighlightMode:YES];
[self.statusItem setEnabled:YES];
[self.statusItem setTarget:self];
NSMenu *menu = [[NSMenu alloc] init];
[menu addItemWithTitle:#"Disabled menu item" action:nil keyEquivalent:#""];
[menu addItemWithTitle:#"Enabled menu item" action:#selector(enabled) keyEquivalent:#""];
NSTextField *label = [[NSTextField alloc] initWithFrame:NSMakeRect(30, 20, 50, 20)];
label.stringValue = #"label";
label.editable = NO;
label.bordered = NO;
label.backgroundColor = [NSColor blueColor];
//label.backgroundColor = [NSColor clearColor];
PKMenuItemView *view = [[PKMenuItemView alloc] initWithFrame:NSMakeRect(0, 0, 200, 50)];
[view addSubview:label];
NSMenuItem *viewMenuItem = [[NSMenuItem alloc] init];
[viewMenuItem setView:view];
[menu addItem:viewMenuItem];
self.statusItem.menu = menu;
I've subclassed the NSView to override drawRect: and draw a colored background:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[[NSColor blueColor] setFill];
NSRectFill(dirtyRect);
//NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver);
}
It is surely kinda hack, but it worked for me.
Try adding an NSImageView with empty image to your custom view. Image view must be occupy the whole view.
I think I have less "hackish" solution. It's indeed caused by the new NSVisualEffectView and Vibrancy stuff in Yosemite. I learned that there are quite complex rules how views are drawn when they're subviews of NSVisualEffectView. It was discussed on WWDC 2014 in session 220 - Adopting Advanced Features of the New UI of OS X Yosemite. I recommend you to watch this session video to get comprehensive explanation.
Shortly, it seems that your problem may be caused by colors you use. There are two new system colors - [NSColor labelColor] and [NSColor secondaryLabelColor]. These two are automatically adjusted when drawn inside NSVisualEffectView. Also, your custom view should support Vibrancy effect. This is done by overriding - (BOOL)allowsVibrancy method and returning YES.
Please check the session video mentioned above or download session slides in PDF to get precise information. This stuff is discussed from slide 124 in PDF and near the middle of the video.
Unfortunately there are currently several problems in Yosemite. As Matthes already mentioned, you can use labelColor() and secondaryLabelColor(). Using those colors do not cause the label to draw the strange background you are seeing.
However, labelColor() only works fine for VibrantDark because there the label color is white when a NSMenuItem is both highlighted and when not highlighted. With VibrantLight the labelColor is black and is therefore very difficult to read on on top of the blue highlight.
For the highlight color of the custom view of your NSMenuItem one might think that you should use selectedMenuItemColor() given its name. The problem with this is that the color doesn't actually match the menu highlight color that you see in NSMenuItems without a custom view. The color is completely wrong for both VibrantLight and VibrantDark.
Tl;dr: So how can you create a custom NSMenuItem that uses the exact same text color and highlight color? You can't. You should use labelColor() and selectedMenuItemColor() but the former only works correctly for VibrantDark, and the latter doesn't match at all.
I really hope I am wrong because I am trying to achieve the same thing :(
Edit: Here is an example project if people want to have a look.
Response from a Apple Developer Technical Support ticket I opened in 2015:
Re: DTS Auto-Ack - Vibrant background and highlighting of Custom View NSMenuItems
This is a difficult problem to tackle, especially in light of the fact that menu selection drawing was not intended for menu items with custom views, and menu selection drawing (colors, etc.) may change in the future. This is why we ask you to file bug reports so that menu selection will be honored with custom views, if asked for, so that future changes to OS X won’t require developers to continually maintain their code to match future color appearances.
The “Application Menu and Pop-up List Programming Topics” says this:
Views in Menu Items -
“A menu item with a view does not draw its title, state, font, or other standard drawing attributes, and assigns drawing responsibility entirely to the view. Keyboard equivalents and type-select continue to use the key equivalent and title as normal.”
Since all drawing is up to the developer, custom views in menu items aren’t necessarily supposed to draw “selected”.
The APIs to obtain the right selection color is obviously not doing what it’s supposed to, hence the request to file a bug report. I wish we could offer more concrete solutions to the problem but a workaround offered today may not hold up tomorrow and we don’t want to set a bad precedent on workarounds that are risky. Apple apps have access to lower level private APIs that achieve their results. We cannot offer you these solutions as they are private.
If selectedMenuItemColor() does not match the menu highlight color with Vibrant light and dark, that’s a bug to be filed and to be fixed.
Lastly, Apple recommends to use NSMenuItem’s APIs as much as possible to achieve what you want in menus. The screenshots you included can likely be done without applying custom views.
I've just discovered that +[NSColor keyboardFocusIndicatorColor] is the right color (on El Capitan at least), whereas the expected selectedMenuItemColor is by far too dark.
Per AppKit engineers at WWDC, this doesn't really work with NSMenuItem. I added that answer to this question as well.
They suggested to instead use an NSPopover to create a faux-NSMenu attached to an NSStatusItem menu bar helper.
Using code similar to the below results in vibrant background selection:
override func viewDidLoad() {
super.viewDidLoad()
let visualEffectView = NSVisualEffectView()
visualEffectView.material = .selection
// .menu or .popover for the non-selected background.
visualEffectView.state = .active
visualEffectView.blendingMode = .behindWindow
visualEffectView.isEmphasized = true
let label = NSTextField(labelWithString: "Hello, world!")
label.cell?.backgroundStyle = .emphasized
visualEffectView.addSubview(label)
visualEffectView.frame = view.bounds
label.setFrameOrigin(.zero)
view.addSubview(visualEffectView)
}
At the WWDC 2019 AppKit Lab I worked through this issue with engineers from the AppKit team.
They were surprised that it did not work by default, and encouraged me to file (more) radars:
FB6143574 - Expose private API for NSMenuItem _viewHandlesEvents
They were aware of a private API _viewHandlesEvents on NSMenuItem.
// VibrantMenuBar-Bridging-Header.h
#import <AppKit/AppKit.h>
#interface NSMenuItem ()
#property (setter=_setViewHandlesEvents:) BOOL _viewHandlesEvents;
#end
Set viewHandlesEvents to false and the background of the custom view in the NSMenuItem will be selected and appear (somewhat) as expected.
There are still issues with how labels and other subviews react to the selection. Text View text is not properly changing color.
let menuItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
menuItem.view = label
menuItem._viewHandlesEvents = false
There are some other references to _viewHandlesEvents on the internet:
How to flash a custom NSMenuItem view after selection?

Preloading font menu

I am writing an application that has a customization dialog box that contains a NSPopupButton that displays all of the available fonts using attributed strings so as to display the font name in the actual font.
Because I anticipate the dialog box only being opened a fraction of the times the application is run, it is in its on xib file and only loaded when needed.
My "problem" is that the first time (only) when the user clicks on the font button, there is 1 5-10 second lag (with spinning beach ball). I assume this because it must render the underlying menu. I am loading the fonts from FSFontManager during the dialog box's awakeFromNib, but that apparently is insufficient.
Before I chase down a bunch of dead ends trying to figure out how to get the menu to "pre-render," does anyone have some suggestions of what my smartest line of attack might be? (Preferably in a separate thread???)
Thanks
Just wanted to share the solution I ended up going with.
In the app delegate's awakeFromNib, I spawned a new thread that did nothing but create an unattached NNTextField, loop overall fonts updating the content of the text field with an attributed string using that font, and then close itself.
Now, unless I am really quick in getting to the font menu that was lagging, it pops up with no lag.
I created a "free floating" NSTextField in MainMenu.xib (i.e. it was NOT part of any window) and connected it to an IBOutlet in the app delegate.
The following is from my app delegate's implementation:
-(void)awakeFromNib
{
// irrelevant stuff deleted...
[NSThread detachNewThreadSelector:#selector(preloadFonts) toTarget:self withObject:nil];
}
-(void)preloadFonts
{
NSTextField *tf = [[NSTextField alloc]init];
NSArray *fonts = [[NSFontManager sharedFontManager] availableFontFamilies];
[fonts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *name=obj;
NSDictionary *attributes = [NSDictionary dictionaryWithObject:[NSFont fontWithName:name size:12.]
forKey:NSFontAttributeName];
NSAttributedString *str = [[NSAttributedString alloc]initWithString:name attributes:attributes];
[tf setAttributedStringValue:str];
}];
}

Text formatting in NSTextField

I have a graphics app that uses a NSTextField instance for in-place text editing, this feature was added long time ago and I never had a reason to check it, however, I have recently received a report: text filed doesn't allow text formatting. Format -> Text menu subitems are all disabled, so there is no way to set the paragraph of a text item.
The question: how should I set up the NSTextField to support paragraph editing? I'm sure it did work before since I have some projects with formatted text and NSTextField was there since the app was born. Did I miss something with system/XCode updates?
My NSTextField is multiline, editable, allowed to edit text attributes.
If someone will face this in the future, I can describe the problem in details:
NSTextView refuses to apply formatting if there is no ruler used (with recent OS update, I suppose)
NSTextField itself does no text editing, it uses shared NSTextView instance driven by the owning NSWindow
Default text editor from the NSWindow doesn't use rulers.
This results disabled text formatting when using NSTextField.
Solution is to subclass the NSWindow:
#implementation MyWindow
- (NSText *)fieldEditor:(BOOL)createWhenNeeded forObject:(id)anObject
{
NSText* text = [super fieldEditor:createWhenNeeded forObject:anObject];
if ([text isKindOfClass:[NSTextView class]])
[(NSTextView *)text setUsesRuler:YES];
return text;
}
#end
And voila, formatting is back.

Cocoa NSTabView coding style question

I have a coding style question which probably should be asked of a senior mac programmer at work - but since I'm the only mac programmer, well, SO it is. I have a pop-up GUI for my software (3D models, data visualization) and the pop-up is Mainly a Tabbed control with a ton of stuff in each tab (sliders, radio buttons, checkboxes, etc.) With something like 20 controls per tab, and maybe half a dozen tabs... using a single controller for all the views is going to get unwieldly very quickly.
Is having a MainViewController which loads a bunch of Tabs good style?
NSView *tabA = [[NSView alloc] initWithNibName:#"tabA.nib" bundle:[NSBundle bundleWithPath:#"/Applications/BOB.app"]];
NSView *tabB = [[NSView alloc] initWithNibName:#"tabB.nib" bundle:[NSBundle bundleWithPath:#"/Applications/BOB.app"]];
It's kindof how I do it on iOS, but I'm not sure for Mac OS X. I prefer a style that offers maintainability and flexibility, as the code is going through prototyping and I may need to change it frequently.
If it's not good style, what is?
Thanks!
I think yours is a reasonable style. You create an NSViewController subclass for each tab, and assign it to the NSTabView using NSTabViewItem.
By the way, I think it's better to have
NSViewController *tabAcontroller = [[TabAController alloc] init];
with #interface TabAController:NSViewController ... #end with init defined as
-init{
self=[super initWithNibName:#"tabA" bundle:nil];
if(self){
...
}
return self;
}
Note that you don't need the extension .nib when you call initWithNibName:bundle:. And you should not specify the hard-coded path of the app. In iOS, the app's position is a given by the OS (with cryptic folder names,) but on OS X a user can freely move the app bundle to anywhere he wants. So, never refer to the main bundle as [NSBundle bundleWithPath:#"hard coded path"]. Use just [NSBundle mainBundle], or just nil in most cases. It's written in the documentation when you can just use nil.

Resources