How to add VoiceOver on NSTabViewItem? - objective-c

Voiceover is typically implemented with accessibilityRole instance method. I was able to use it on a button for example
button.accessibilityRole = NSLocalizedString(#"Button", nil);
But when I use it on a NSTabViewItem,
NSTabViewItem *item0 = [NSTabViewItem tabViewItemWithViewController:self.tab0];
item0.acessibilityRole = NSLocalizedString(#"Tab 0",nil);
I get an error saying
Property 'accessibilityRole' not found on object of type 'NSTabViewItem *'
Edit: I also tried accessibilityLabel.

accessibilityLabel is usually called on Views, so I used it on NSView.
NSView * cellView = [NSView newAutoLayoutView];
cellView.accessibilityLabel = "label";

A number of issues here.
The primary problem is that accessibilityRole is a method which you override to return the role of your object. You can't set the role in the way you are attempting, you can only subclass an NSCell/NSView and return the appropriate string. However, you probably don't want to do that in this case because:
You don't return a localized value from a view or cell's accessibilityRole property, you use one of the NSAccessibilityRole types. And more importantly:
The role is only used to provide information about the kind of object VoiceOver has selected. In the case of tab views, the correct role for the tab view itself is NSAccessibilityTabGroupRole and each tab has NSRadioButtonRole and a subrole of NSAccessibilityTabButtonSubrole. This will happen for you automatically when you use an NSTabView. Note, you would never use a role of "Tab 0", which conflates the label with the role. And you don't need a label if the tab has a text title anyway, it would only be necessary if you had a tab with an icon for a title. And then the label would be something like "information" for an icon of ℹ️.
You generally shouldn't override the accessibilityRole on a view/cell, but instead instantiate the right sort of object to begin with. For instance, instead of creating a button with a standard type of NSButtonTypeMomentaryPushIn and then overriding its role to be NSAccessibilityCheckBoxRole, you should just create a check box. Then the role/subrole/role description will be set correctly for you. In general, the only time you would want to override the accessibilityRole on a view is if you are rolling your own view from scratch.
When setting accessbility attributes on a button or any other object with a cell, you shouldn't use the NSView (the NSButton instance). Instead, you need to use the NSCell (NSButtonCell, reached with button.cell). E.g. button.cell.accessibilityLabel = NSLocalizedString("Cancel", nil); While many AX attributes are passed through from the cell to the view, a few are not. Also, different accessibility technologies (VoiceControl, VoiceOver, SwitchControl) are more or less strict about this. You should always set things on the cell where appropriate to be compatible with widest range of AX technologies.
The best way to work out what your app should do is to find analagous UI in an Apple product and explore the AX hierarchy using Accessibility Inspector.

Related

Set AutoLayout Size Class Programmatically?

With iOS 8 and Xcode 6, in storyboards we now have the screen size grid letting us select a size class. Where you can select layout formatting for the different screen sizes.
I have found this brilliantly helpful, as it allows me to set the base constraints and then unique ones for each screen size.
My question is, can you do this programmatically? I create my NSLayoutConstraint as normal but I need to be able to specify different constraints for different screen sizes.
iOS 8 introduces the active property on NSLayoutConstraint. It allows you to activate or deactivate a constraint. There are also methods to activate/deactivate multiple constraints.
+ (void)activateConstraints:(NSArray *)constraints
+ (void)deactivateConstraints:(NSArray *)constraints
Keep your constraints in arrays when creating them programmatically.
Create an array for each of the layouts you need.
Activate/Deactivate whatever set of constraints you need from within willTransitionToTraitCollection
To answer your question, you can set the size class programmatically, however, it's a bit of a pain. You must call "setOverrideTraitCollection" but from the parent view controller, not the one you actually wished to make a trait change to.
In my situation, I wanted to change the Master VC of a split view controller on iPad to look differently than the one on the iPhone, however, they are both set to Compact width / Regular height by default. So I subclassed the Master's nav controller and added code to set the Master's traits to Regular width when it's not an iPhone.
Swift code:
class MasterNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if (self.traitCollection.userInterfaceIdiom != .Phone) {
let newTraitCollection = UITraitCollection(horizontalSizeClass: .Regular)
self.setOverrideTraitCollection(newTraitCollection, forChildViewController: self.topViewController)
}
}
}
I hope this helps someone looking for a similar solution.
It's a bit confusing & hard to find in the documentation because a "size class" isn't actually a "Class" like NSObject. They're really defined in an enum/typedef called: UIUserInterfaceSizeClass
The way to get the horizontal & vertical size class for a view is with a UITraitCollection
Class/Type methods for UITraitCollection allow you to create one based on a particular display scale (e.g. retina or not), from an array of other trait collections, with a UI idiom (iPad/iPhone), or specific horizontal & vertical options (compact, regular), but to be honest I'm not sure yet how you'd use this...
This question discusses updating constraints when the traitCollection changes, using willTransitionToTraitCollection(newCollection: UITraitCollection!,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator!)
You're right that both the UITraitCollection and its properties are readonly, but clearly you can create a new collection for some reason, and handle layout changes when the traitCollection changes.
This previous question is pretty similar & links to an Apple article about using Adaptive Layout. Also check the WWDC video "Building Adaptive Apps with UIKit."

NSBrowser setRowHeight, not supported for browsers with matrix delegates

I have an NSBrowser and try to use setRowHeight, but I get the error:
"setRowHeight: is not supported for browsers with matrix delegates."
I really don't understand what this means, and if someone could help me out by either telling me how to fix it or even just what a matrix delegate is, it would be much appreciated.
A delegate is a helper object that you tell the NSBrowser instance about either by using -setDelegate: in code or hooking the delegate outlet up in IB (the NIB editor). It is commonly used to fill the browser's data, determine programmatically the layout options, etc.
If you have a delegate assigned in your NSBrowser instance, you are expected (required) to give the row height using the delegate method:
- (CGFloat)browser:(NSBrowser *)browser heightOfRow:(NSInteger)row inColumn:(NSInteger)columnIndex
Which will allow you to optionally set the row height on a per-row basis, but in your case, you can safely return a constant.
A NSBrowser creates one instance of NSMatrix per column. The browser itself only manages the columns and leaves row management entirely to the matrices. The matrices display cell objects (NSCell or subclasses of it). A cell is a simple displayable object that knows nothing but how to draw itself. Unlike a view (NSView and subclasses), it never manages an own drawing context, it also doesn't belong to a window (and thus won't ever directly access the window's drawing context) and it knows nothing about the "view hierarchy" (superviews, subviews, view order, constraints, etc.), it just manages some properties, some state and knows how to draw itself to a drawing context provided.
If your delegate works with items (the docs speak about "the item delegate methods"), the developers of NSBrowser thought it's unlikely that you ever want to deal with matrices directly, thus the browser will control all the display aspects for you. You are only supposed to hand out items (basically arbitrary objects) in your delegate and answer questions about them by implementing various delegate methods. E.g.: What is the root item? Is item x a leaf item or has children? How many children does it have? What's child number n of item x? What display object (string, image, etc.) shall I use to display that item?
If you don't work with items, you have to work directly with the cells (NSBrowserCell, a subclass of NSCell) that the matrix owns and is going to display. In that case you are said to be a "browser matrix delegate". That means the browser will only ask you how many rows a column has, setup a matrix with cells for you for that column and finally pass every cell once to your delegate, so you can do something meaningful with it, e.g. fill it with displayable content, otherwise the cell would just be empty, and teach it all the stuff the browser has to know (e.g. setting the leaf property).
As a matrix based delegate has to deal with cells directly, it can as well also deal with the matrices directly and in fact that his exactly what you have to do here. E.g. if you want that all rows of your browser have a height of 50 points, implement the following NSBrowser delegate method:
- (void)browser:(NSBrowser *)browser
didChangeLastColumn:(NSInteger)oldLastColumn
toColumn:(NSInteger)column
{
NSMatrix * matrix = [browser matrixInColumn:column];
CGSize cellSize = [matrix cellSize];
if (cellSize.height != 50) {
cellSize.height = 50;
matrix.cellSize = cellSize;
[matrix sizeToCells];
}
}
Every matrix created by the browser is passed to that method at least once before it gets displayed on screen. In case that matrix is not using a cell height of 50 already, we change that and finally tell the matrix to re-layout; that is important as otherwise it won't recalculate its total height. The if is only to avoid that we call sizeToCells more often than necessary as this call may be rather expensive. In this example all columns get equal row height but of course you can set a different row heights per column.

Popup window similar to Xcode's code completion?

I would like to build something similar to the code-completion feature in Xcode 4. (The visual style and behavior, not the data structure type work required for code completion).
As the user is typing, a pop-up window presents other word choices that can be selected.
The Feature in action:
I'm not exactly sure where to start. I am mainly concerned with the visual appearance of the window and how I should populate the list with a given set of words. Later I will get into making the window follow the cursor around the screen and etc.
I am mainly looking for an overview of how to display such data in a "window", and how to cusomize the appearance of the thing so it looks like a nice little informational popup rather than a full-on OS X window.
Just add a subview to your current view that happens to be a tableview. Programmatically cause it to be visible on an event (such as mouseDown), and adjust it's position based on where you want it. You will need to instantiate the proper delegate/datasource methods, but it should be pretty straight forward. You will also need a source for the words you want to use in your autocomplete, and put them in an array or something for your tableview datasource to go through.
Like I said, it's not terribly hard, provided you are comfortable using tableviews and adding views to your existing view. If this doesn't explain enough, leave a comment and I can flesh this comment out more.
Add a (completions) subview to your view and set its visible property to NO. Create a separate AutoComplete object that includes the subview as a property and fills it with potential completions. Your controller can respond to key pressed (key typed) events and give the last word (substring the text from the end to the 1st preceding blank) to the AutoComplete on each event. The basic logic in AutoComplete could be something like:
Given an AutoComplete with a list of known words "dog, spaghetti, minute, horse, spare, speed"
When asked to complete a fragment "sp"
Then the following words should be offered as potential completions, "spaghetti, spare, speed"
Which would imply that you need to instantiate it with a list of words (this could be done in the init of your controller), and create a method "-(NSArray*) completeFragment:(NSString*)fragment;". You could drop this into an OCUnit test case and iterate over your implementation of autocomplete until you get it right. You would then write a method that takes the completions from AutoComplete and lists them in the subview. Even better you might create currentWord and potentialCompletions properties in AutoComplete that gets updated as you send it "newFragment:(NSString*)fragment;" messages. Throw that into OCUnit and work it out then use that potentialCompletions property to drive updated of a the subview (which is probably best modeled as a custom tableView).

objective-c identifying contentView items

Is there a way to identify unique objects within the contentView? For example, I in my mainWin I have a NSView and 2 NSButtons. Using
[[mainWin contentView] subviews];
I can get all the objects within the mainWin. This works fine for my needs if the object is a subclass of NSView and I've given it a class, for example, in this case I've named the class vHUD and when I log the object it comes back as
<vHUD: 0x146e10>
This is fine for NSViews I am creating because for the most part if I am making them they are going to serve multiple purposes (content container, being toggled around screen, etc.) and a class should be necessary. I could subclass all the buttons (under NSObject) and go that way, but it seems like a sledgehammer approach if I end up having a lot of buttons. Is there a way I can uniquely identify all of the buttons with something descriptive in IB that can then be retrieved from the object itself? I tried "description" but that didn't return anything.
Have you tried using the tag instance variable in the NSView class?

textField:shouldChangeCharactersInRange:replacementString vs. UITextFieldTextDidChangeNotification

I'm building a Passcode Lock view, and I want to set the text to a dot on one of the four box labels when a user enters a new character into the text field.
Should I use textField:shouldChangeCharactersInRange:replacementString or UITextFieldTextDidChangeNotification?
Using textField:shouldChangeCharactersInRange:replacementString seems easier, but shouldChangeCharactersInRange seems more correct.
I’d recommend using the delegate method, rather than the notification.
Another option: set the text fields’ secureTextEntry property to YES. (See the documentation for the UITextInputTraits protocol, which UITextField conforms to).