How can I bind to the Image property of a NSPopUpButtonCell? - objective-c

I've created a NSTableView, and have set up all my bindings (via an NSArrayController). I want one of the columns to have a NSPopUpButtonCell, but, instead of showing text, I want it to show an image - so the user can change the image from the popup control.
My column is bound (using the SelectedObject binding) to the correct property, and the the NSPopUpButtonCell is bound (using the ContentValues binding) to my complete list of images (which is from another NSArrayController containing an array of image names - NSStrings).
I've created a NSValueTransformer to convert an image name (NSString) to an NSImage, and have tested this works by creating an addition column with an NSImageCell, and that displays the image correctly.
Is there a way to do this?
I'm hoping I can target the binding at the NSPopUpButtonCell's image property, rather than its title property.
Edit: I had the idea of trying to use an NSAttributedString, with an embedded image. But, the problem remains, in that I want the binding to use setAttributedTitle, rather than setTitle.

After a lot of trial and error, I've managed to solve this problem. I'm convinced there remains a more correct solution, but, for now, this'll do.
My solution:
Change the Pop Up Button Cell > Menu to a custom class
In the custom class, inherit from NSMenu:
#interface RSMenu : NSMenu
Override insertItemWithTitle, and customize to your own needs.
For example:
-(NSMenuItem *)insertItemWithTitle:(NSString *)aString
action:(SEL)aSelector
keyEquivalent:(NSString *)charCode
atIndex:(NSInteger)index {
NSImage *image = [NSImage imageNamed:aString];
NSMenuItem* menuItem = [super insertItemWithTitle:image ? #"" : aString
action:aSelector
keyEquivalent:charCode atIndex:index];
if ( nil != image ) {
[menuItem setImage:image];
}
return menuItem;
}
And here is the result:
Hope this helps someone else.

Related

Binding image to NSPopupbutton from NSArrayController

This question is an extension to link
(The question in the link mainly targets, binding NSPopupbutton to a NSArrayController)
I have a Person class having properties NSString *name and NSImage *avatar
I have to show all the names of persons in Popup button as seen in the below image.
But now, as requirement has changed, I need to show avatar of person also.
How do I use Cocoa bindings to bind person's avatar to NSPopup button so that it looks like the one in above image for michael(last menu option)
Note: Michael has been temporarily added for demonstration using following code:
person.title = #"Michael";
person.image = [NSImage imageNamed:#"avatar.png"];
[_popupButton.menu addItem:person];
There are two ways you can achieve that:-
First take the cell-based tableview inside that two column one for image cell and second for text cell. Populate the table through bindings and then add your tableview inside your popup button in the below way:-
NSMenu *theMenu = [[NSMenu alloc] initWithTitle:#""];
NSMenuItem *Item = [[NSMenuItem alloc] initWithTitle:#"NameList" action:NULL keyEquivalent:#""];
[Item setView:self.tableVw];
[theMenu addItem:Item];
[self.popUptn setMenu:theMenu];
Second follow the below simple steps:-
Assuming your array contains the name elements.
1) Select your arrayContoller bind to FileOwner's and ModelKeyPath->array.
2) Select PopUpButton inside binding Inspector->Selected Object bind to FileOwner's and ModelKeyPath->yourString. This will select the required name accordingly.
3) Select PopUpButton inside binding Inspector->Content Valuesand ModelKeyPath->array
4) Now for setting the image inside popupButton refer below code:-
NSMenuItem *menuItm=[self.popUpBtn itemWithTitle:#"Michael"];
[menuItm setImage:[NSImage imageNamed:#"dot.gif"]];
Edit:-
1) In the first method your text and image both are populating through binding just you need to add that tableview inside your popupbutton.
2) In the second method your popupbutton will display the image for last name but programmatically. And also, if required to display the images for all names then use for loop to set images inside menu item.

UITableView setContentOffset but don't scroll tableView?

I am using setContentOffset on a UITableView because I want to initially hide a search field that is my tableHeaderView.
[self.tableView setContentOffset:CGPointMake(0, 56)]; // No scroll please!
Each time I push a new viewController I want to hide the search bar with contentOffset. But when I pop a viewController that offset is no longer in effect for some reason and shows the search bar. Why is this?
you can try and implement it on the following
- (void)viewWillAppear:(BOOL)animated {
[self.tableView setContentOffset:CGPointMake(0, 56)];
}
that will put the table in the correct position before it is displayed on the screen, I am assuming you mean no animation while setting the position.
I am guessing that you want to stop the user being able to scroll to the very top of the screen. If so you can implement the following UITableView delegate (on iOS5 and above):
scrollViewWillEndDragging:withVelocity:targetContentOffset:
which allows you to modify the final target for a change in the contentOffset. In the implementation you do:
- (void)scrollViewWillEndDragging:(UIScrollView *)theScrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
if(targetContentOffset->y < 56) {
targetContentOffset->y=56;
}
}
If you are trying to preserve the value of something during an action that loses it, the natural solution is to hold onto it yourself ("Hold/Restore"):
"Hold": get content offset to a field or local variable. Apple doc
.. do whatever you want.
"Restore": set content offset to the value you got above.
(Sorry, I don't write Objective C code, so can't provide the exact code. An edit to add the code, would be welcome.)
In a different situation, it might be necessary to hold the row you were at, and then scroll back to that row:
(Adapted from: https://stackoverflow.com/a/34270078/199364)
(Swift)
1. Hold current row.
let holdIndexPath = tableView.indexPathForSelectedRow()
.. do whatever (perhaps ending with "reloadData").
Restore held row:
// The next line is to make sure the row object exists.
tableView.reloadRowsAtIndexPaths([holdIndexPath], withRowAnimation: .None)
tableView.scrollToRowAtIndexPath(holdIndexPath, atScrollPosition: atScrollPosition, animated: true)

setting an accessibilityLabel on a UIImageView contained in UITableView header

I have a UITableView that I build in loadView. One of the things I do in loadView is create a UIView to act as the table header and stuff a UIImageView into it. The image view contains an image that is a stylized title, so I want to add an accessibility label for VoiceOver users. However, I can't get VoiceOver to "focus" on the image in order to read the label, and the Accessibility Inspector doesn't respond to clicking on the image in the simulator. My (abbreviated) code follows:
... in -loadView ...
// Make header view
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(...)];
UIImageView *titleImageView = [[UIImageView alloc] initWithImage:[self titleImage]];
titleImageView.accessibilityLabel = [self accessibilityLabelForTitleImage];
[headerView addSubview:titleImageView];
// Make table view
self.tableView = [[UITableView alloc] initWithFrame:CGRect(...) style:UITableViewStylePlain];
self.tableView.tableHeaderView = headerView;
... code continues ...
I've stepped through in gdb and accessibilityLabelForTitleImage returns a string. po [titleImageView accessibilityLabel] prints out the correct string, but I'm still unable to focus on the image view. Note that the views themselves appear and respond as appropriate.
Am I missing something? Is there a way to force VoiceOver to acknowledge an image view?
In Voice-Over , in order to make an element accessible :-
you have to set setIsAccessibilityElement property as true which i don't find in your code.
The other important point is that to make child elements (subviews) to be accessible , you have to seperately make them accessible while the parent should not be accessible(you have to specify this also).
Implement the UIAccessibilityContainer Protocol in your custom - cell.
It will be a big story if i go on .Please refer this Accessibility voice over by apple.
Hope this helps.
I used KIF for testing my IOS app. In my tableview, I assigned value to tableview.accesssibilityIdentifier instead of tableview.accessibilityLabel. It worked for me. Wanna give it a try?
Voice-Over sometimes can get nasty and just by setting isAccessibilityElement might not work.
In this case try setting accessibilityElements on the parent view and include the child views in the array, like this:
parentView.accessibilityElements = [childView1, childView1, childView1]
Doing it also ensures that the accessibility items are being read in the order you want.

Subviews not displaying when using replaceSubview:with:

I'm writing a dynamic wizard application using cocoa/objective c on osx 10.6. The application sequences through a series of views gathering user input along the way. Each view that is displayed is provided by a loadable bundle. When the app starts up, a set of bundles are loaded and as the controller sequences through them, it asks each bundle for its view to display. I use the following to animate the transition between views
[[myContentView animator] replaceSubview:[oldView retain] with:newView];
This works fine most of the time. Every once in a while, a view is displayed and some of the subviews are not displayed. It may be a static text field, a checkbox, or even the entire set of subviews. If, for example, a checkbox is not displayed, I can still click where it should be and it then gets displayed.
I thought it might have something to do with the animation so I tried it like this
[myContentView replaceSubview:[oldView retain] with:newView];
with the same result. Any ideas on what's going on here? Thanks for any assistance.
I don't think is good to use this [oldView retain]. This retain do not make sense. The function replaceSubview will retain it if it is necessary.
Because it work "most of the time" I think it is a memory problem. You try to use a released thing. Test without it and see what happens.
I got the same problem, replaceSubview didn't work.
Finally i found something wrong with my code. so here are the rules :
Both subviews should be in MyContentview's subviews array.
OldView should be the topmost subview on MyContentView or replacesubview will not take place.
here is a simple function to perform replacesubview
- (void) replaceSubView:(NSView *)parentView:(NSView *)oldView:(NSView *)newView {
//Make sure every input pointer is not nill and oldView != newView
if (parentView && oldView && newView && oldView!=newView) {
//if newview is not present in parentview's subview array
//then add it to parentview's subview
if ([[parentView subviews] indexOfObject:newView] == NSNotFound) {
[parentView addSubview:newView];
}
//Note : Sometimes you should make sure that the oldview is the top
//subview in parentview. If not then the view won't change.
//you should change oldview with the top most view on parentview
//replace sub view here
[parentView replaceSubview:oldView with:newView];
}
}

Determining which NSView instance initiated a mouseDown:

I have a gameboard with 25 tiles of myGameTile, a subclass of NSView.
In mouseDown: I want to determine which tile I clicked on and set an ivar to a representative value.
e.g. If I click on tile 12, set clickedTile to "12" or some value that uniquely represents that particular instance.
I'm open anything from the integer value 12 all the way to some sort of introspection/reflection, though built-in features and elegance are preferable to hacks, runtime wrappers, and modification. Still, I'm aware that I may have no choice but to rely on those solutions, so please to answer with those as well. I'd like to know all my options. Thanks!
You could subclass NSView and override the tag method, as written in the documentation.
You have several possibilities:
If you handle the mouseDown in the Tile view, then you need to map self to the tile ID. There are three easy ways to do this:
Pre configure Tile.tag to be the tile ID, then use self.tag.
Search for Tile in the array of Tiles to find the index [parent.tiles indexOfObject:self]
Create a dictionary mapping Tile or tile ID [[parent.tiles objectForKey:self] intValue]
Clearly, using the tag is the easiest, as long as you are not using the tag for anything else.
Alternatively, you could implement hitTest on the parent view, and return the parent view, and then handle the mouseDown in the parent view. Then mouseDown will know where the hit is and hence which tile it is.
I think hitTest: will do what you want. Something like this:
- (void)mouseDown:(NSEvent *)theEvent {
NSPoint aPoint = [theEvent locationInWindow];
NSView* viewHit = [self hitTest:aPoint];
if( viewHit != nil && viewHit != self ) {
// viewHit is our tile.
}
}