Cocoa: ABPeoplePickerView - How can I trigger a Method upon single clicking a person? - cocoa

I've looked everywhere, and hope that perhaps someone can point me in the right direction.
I just want to run a method each time the user selects a different record.
The bigger picture (in case there is an alternate way) is that when the user selects the record (single click) the persons phone numbers are to be put into a segmented control.
I've tried:
To connect an action to a button, I usually open the assistant editor, and right-click drag to the .h file. But when I'm doing it with this abpeoplepickerview I only get an Outlet connection type?

the people picker is a . 'compound view' that actually consits of a tableview, 2 buttons and a searchfield (IIRC)
answer:
you're out of luck and this component isnt suitable for you BUT of course you do some hacking:
- (void)viewDidLoad {
//you get the internal tableview
id views = [self findSubviewsOfKind:NSClassFromString(#"ABPeoplePickerTableView") withTag:NSNotFound inView:sef.peoplePickerView];
id view = [views count] ? [views objectAtIndex:0] : nil;
//subscribe to the notification
if([view respondsToSelector:#selector(selectedRow)]) {
[[NSNotificationCenter defaultCenter] addObserverForName:NSTableViewSelectionDidChangeNotification object:view queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self peoplePickerSelectedRecordChanged:self.peoplePickerView];
}];
}
}
- (NSArray *)findSubviewsOfKind:(Class)kind withTag:(NSInteger)tag inView:(NSView*)v {
NSMutableArray *array = [NSMutableArray array];
if(kind==nil || [v isKindOfClass:kind]) {
if(tag==NSNotFound || v.tag==tag) {
[array addObject:v];
}
}
for (id subview in v.subviews) {
NSArray *vChild = [self findSubviewsOfKind:kind withTag:tag inView:subview];
[array addObjectsFromArray:vChild];
}
return array;
}
- (IBAction)peoplePickerSelectedRecordChanged:(id)sender {
NSLog(#"%#", [sender selectedRecords]);
}

ABPeoplePickerView gives notifications for exactly what you need. Look near the end of the class reference.
#implementation someController
#synthesize picker; //your ABPeoplePickerView
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
// or some other method that gets called early on
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(notificate:)
name:ABPeoplePickerNameSelectionDidChangeNotification
object:picker];
}
- (void) notificate: (NSNotification*) notification {
ABPerson *person = picker.selectedRecords.firstObject;
NSLog(#"NOTIFIED %#"), person.name);
// name is a property of ABPerson I added in a category
// do what you will
}
Don't forget to remove the observer if you dismiss the window.

Related

Bindings working from IB, not from my addObserver...

My Document-based app was created without storyboards in Xcode 6.3 so it began life without a window controller (I still don't have a window controller -- just trying to give some background and context).
I have a class structure implemented for working with a gradient and storing it's formative values in my document.
My Document class holds a reference to a Theme object.
My Theme class holds a reference to a Gradient object.
My Gradient class holds a reference to an NSNumber for the start point of the gradient.
In IB an NSSlider is bound to File's Owner, with Model Key Path of "self.theme.gradient.startPointX"
This works fine, as evidenced by Gradient.m -didChangeValueForKey logging out the specific key whose value is being changed.
So why doesn't a similar notification occur in my Document class when the slider for the gradient start point is changed after I have asked to observe it?
Document.m
- (instanceType)init {
self = [super init];
if (self) {
self.theme = [[MyTheme alloc] init];
// first attempt -- not live when second attempt is compiling
[self addObserver:self
forKeyPath:#"theme.gradient.startPointX"
options:NSKeyValueObservingOptionNew
context:#"myDocument"];
// second attempt -- not live when the first attempt is compiling
[self.theme.gradient addObserver:self
forKeyPath:#"startPointX"
options:NSKeyValueObservingOptionNew
context:#"myDocument"];
}
return self;
}
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(#"Document notified that \"%#\" has changed.", key);
}
-
Theme.m
- (instancetype)init
{
if (self = [super init]) {
self.gradient = [[Gradient alloc] init];
}
return self;
}
-
Gradient.h
#interface Gradient : NSObject
#property (strong, nonatomic) NSNumber *startPointX;
#end
-
Gradient.m
- (instancetype)init
{
if (self = [super init]) {
self.startPointX = #(0.47f);
}
return self;
}
- (void)didChangeValueForKey:(NSString *)key
{
if ([key isEqualToString:#"startPointX"]) {
NSLog(#"Gradient notified a change to %# has occurred.", key);
}
It turns out that if you implement -didChangeValueForKey: it blocks/suspends normal notification of those properties you might be observing.
Commenting out my Gradient.m implementation of
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(#"'%#' has been changed.", key);
}
caused my observations from Document to begin working fine.

NSTableView not Populating

I've been trying to get this NSTableView to populate for the last 7 hours. I am trying to get a list of all the currently running application and put them into an NSTableView. Eventually I would like to parse the resultes and organize the PID in one column and the Application Bundle in the other. I am getting an EXC_BAD_ACCESS error on " return [listOfWindows objectAtIndex:row];" I am currently using Xcode 4.3.2 and running OS X Lion 10.7.4. Thanks in advance everyone!
#interface AppDelegate : NSObject <NSApplicationDelegate>
{
IBOutlet NSMenu *statusMenu;
IBOutlet NSButton *button;
IBOutlet NSWindow *menuWindow;
IBOutlet NSTableView *proTable;
NSArray *listOfWindows;
IBOutlet NSArrayController *arrayController;
AppDelegate *mainMenu;
NSWorkspace *workSpace;
NSStatusItem *statusItem;
}
#property (assign) IBOutlet NSWindow *window;
-(IBAction)loadConfig:(id)sender;
#end
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window = _window;
- (void) awakeFromNib
{
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
selector:#selector(loadMenu:)
name:#"WhiteBox"
object:nil];
[self addStatusItem];
//[proTable setDataSource:self];
listOfWindows = [[NSWorkspace sharedWorkspace] runningApplications];
NSLog(#"index %#", listOfWindows);
int y = 0;
y = [listOfWindows count];
NSLog(#"y = %d", y);
[proTable setAllowsMultipleSelection:YES];
}
-(void)applicationWillTerminate
{
NSLog(#"Will Terminate");
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
}
-(void)applicationDidResignActive:(NSNotification *)notification
{
NSLog(#"Resign Active");
}
-(void) addStatusItem
{
//Create a variable length status item from the system statusBar
statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
[statusItem retain];
//Set a Title for it
[statusItem setTitle:#"Status Item"];
//Set an Image and an alternate image
//[statusItem setImage:[NSImage imageNamed:#"lnc"]];
//[statusItem setAlternateImage: [NSImage imageNamed:#"status"]];
//Add a Tool Tip
[statusItem setToolTip:#"Status Item Tooltip"];
//Choose to highlight the item when clicked
[statusItem setHighlightMode:YES];
//To Trigger a method on click use the following two lines of code
[statusItem setMenu:statusMenu];
//[statusItem setAction:#selector(loadMenu:)];
}
-(IBAction)loadConfig:(id)sender
{
if(! [menuWindow isVisible] )
{
[menuWindow makeKeyAndOrderFront:sender];
} else {
[menuWindow performClose:sender];
}
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [listOfWindows count];
}
- (id)tableView:(NSTableView *)tableView
objectValueForTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row
{
return [listOfWindows objectAtIndex:row];
}
#end
What object is the table view's data source? I don't see any object in the source you posted as implementing the NSTableViewDataSource protocol.
Further, have you tried putting breakpoints in the various data source methods to see if the debugger stops in them? If not, it's usually a good sign that your data source isn't connected to your table view.
I got: -[NSRunningApplication copyWithZone:]: unrecognized selector error when I ran your code. This could be fixed by changing your return line in tableView:objectValueForTableColumn:row: to
return [[listOfWindows objectAtIndex:row]localizedName];
NSRunningApplication doesn't conform to NSCopying, so I don't know if you can put instances of that class in a table view. However, you can get its properties like localizedName, processIdentifier, and bundleIdentifier.
I've run into this problem before with classes that don't implement NSCopying, I'd be happy to know if anyone knows a way to use these classes in table views or outline views.

NSTreeController: custom behavior for "canInsert" binding

I have a Cocoa app with an NSOutlineView managed by an NSTreeController.
In addition there's a button for adding new elements to the outline view. I bound the button's enabled flag to the tree controller's canInsert property.
I only want to allow adding up to 5 elements to the outline view. After that, canInsert should return NO.
I created my own sub-class of NSTreeController and overwrote canInsert, but the enabled status of the button does not change, because it doesn't realize that the tree controller has changed when adding elements.
I also implemented: keyPathsForValuesAffectingCanInsert and tried returning various properties such as content, arrangedObjects, but no luck here.
#implementation ILCustomTreeController
- (BOOL)canInsert
{
return [[self arrangedObjects] count] < 5;
}
+ (NSSet *)keyPathsForValuesAffectingCanInsert
{
return [NSSet setWithObject:#"content"]; // I also tried 'arrangedObjects'
}
#end
Here's a workaround that does work (although I still think this should be solved by using keyPathForValuesAffectingCanInsert). Suggestions are welcome.
#implementation ILCustomTreeController
- (BOOL)canInsert
{
return [[self arrangedObjects] count] <= 4;
}
- (void)addObject:(id)object
{
[self willChangeValueForKey:#"canInsert"];
[super addObject:object];
[self didChangeValueForKey:#"canInsert"];
}
- (void)insertObject:(id)object atArrangedObjectIndexPath:(NSIndexPath *)indexPath
{
[self willChangeValueForKey:#"canInsert"];
[super insertObject:object atArrangedObjectIndexPath:indexPath];
[self didChangeValueForKey:#"canInsert"];
}
- (void)remove:(id)sender
{
[self willChangeValueForKey:#"canInsert"];
[super remove:sender];
[self didChangeValueForKey:#"canInsert"];
}
#end

Drag and Drop with NSStatusItem

I'm trying to write an application that allows the user to drag files from the Finder and drop them onto an NSStatusItem. So far, I've created a custom view that implements the drag and drop interface. When I add this view as a subview of an NSWindow it all works correctly -- the mouse cursor gives appropriate feedback, and when dropped my code gets executed.
However, when I use the same view as an NSStatusItem's view it doesn't behave correctly. The mouse cursor gives appropriate feedback indicating that the file can be dropped, but when I drop the file my drop code never gets executed.
Is there something special I need to do to enable drag and drop with an NSStatusItem?
I finally got around to testing this and it works perfectly, so there's definitely something wrong with your code.
Here's a custom view that allows dragging:
#implementation DragStatusView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
//register for drags
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
//the status item will just be a yellow rectangle
[[NSColor yellowColor] set];
NSRectFill([self bounds]);
}
//we want to copy the files
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
return NSDragOperationCopy;
}
//perform the drag and log the files that are dropped
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
NSLog(#"Files: %#",files);
}
return YES;
}
#end
Here's how you'd create the status item:
NSStatusItem* item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
DragStatusView* dragView = [[DragStatusView alloc] initWithFrame:NSMakeRect(0, 0, 24, 24)];
[item setView:dragView];
[dragView release];
Since Yosemite, the method for setting a view on NSStatusItem is deprecated but fortunately there is a much nicer way using the new NSStatusItemButton property on NSStatusItem:
- (void)applicationDidFinishLaunching: (NSNotification *)notification {
NSImage *icon = [NSImage imageNamed:#"iconName"];
//This is the only way to be compatible to all ~30 menu styles (e.g. dark mode) available in Yosemite
[normalImage setTemplate:YES];
statusItem.button.image = normalImage;
// register with an array of types you'd like to accept
[statusItem.button.window registerForDraggedTypes:#[NSFilenamesPboardType]];
statusItem.button.window.delegate = self;
}
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
return NSDragOperationCopy;
}
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
//drag handling logic
}
Please be aware that the button property is only available starting in 10.10 and you might have to keep your old solution if you support 10.9 Mavericks or below.

ipad: predictive search in a popover

I want to implement this
1) when user start typing in a textfield a popOver flashes and shows the list of items in a table view in the popover as per the string entered in textfield.
2) Moreover this data should be refreshed with every new letter entered.
kind of predictive search.
Please help me with this and suggest possible ways to implement this.
UISearchDisplayController does most of the heavy lifting for you.
Place a UISearchBar (not a UITextField) in your view, and wire up a UISearchDisplayController to it.
// ProductViewController.h
#property IBOutlet UISearchBar *searchBar;
#property ProductSearchController *searchController;
// ProductViewController.m
- (void) viewDidLoad
{
[super viewDidLoad];
searchBar.placeholder = #"Search products";
searchBar.showsCancelButton = YES;
self.searchController = [[[ProductSearchController alloc]
initWithSearchBar:searchBar
contentsController:self] autorelease];
}
I usually subclass UISearchDisplayController and have it be it's own delegate, searchResultsDataSource and searchResultsDelegate. The latter two manage the result table in the normal manner.
// ProductSearchController.h
#interface ProductSearchController : UISearchDisplayController
<UISearchDisplayDelegate, UITableViewDelegate, UITableViewDataSource>
// ProductSearchController.m
- (id)initWithSearchBar:(UISearchBar *)searchBar
contentsController:(UIViewController *)viewController
{
self = [super initWithSearchBar:searchBar contentsController:viewController];
self.contents = [[NSMutableArray new] autorelease];
self.delegate = self;
self.searchResultsDataSource = self;
self.searchResultsDelegate = self;
return self;
}
Each keypress in the searchbar calls searchDisplayController:shouldReloadTableForSearchString:. A quick search can be implemented directly here.
- (BOOL) searchDisplayController:(UISearchDisplayController*)controller
shouldReloadTableForSearchString:(NSString*)searchString
{
// perform search and update self.contents (on main thread)
return YES;
}
If your search might take some time, do it in the background with NSOperationQueue. In my example, ProductSearchOperation will call showSearchResult: when and if it completes.
// ProductSearchController.h
#property INSOperationQueue *searchQueue;
// ProductSearchController.m
- (BOOL) searchDisplayController:(UISearchDisplayController*)controller
shouldReloadTableForSearchString:(NSString*)searchString
{
if (!searchQueue) {
self.searchQueue = [[NSOperationQueue new] autorelease];
searchQueue.maxConcurrentOperationCount = 1;
}
[searchQueue cancelAllOperations];
NSInvocationOperation *op = [[[ProductSearchOperation alloc]
initWithController:self
searchTerm:searchString] autorelease];
[searchQueue addOperation:op];
return NO;
}
- (void) showSearchResult:(NSMutableArray*)result
{
self.contents = result;
[self.searchResultsTableView
performSelectorOnMainThread:#selector(reloadData)
withObject:nil waitUntilDone:NO];
}
It sounds like you have a pretty good idea of an implementation already. My suggestion would be to present a UITableView in a popover with the search bar at the top, then simply drive the table view's data source using the search term and call reloadData on the table view every time the user types into the box.

Resources