Changing the Image for a detail disclosure button (Xcode 4.2) - xcode

I have used the following suggested routine to change the detail disclosure button image in a table view cell (in tableView cellForRowAtIndexPath)
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
UIButton *myAccessoryButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 24, 24)];
[myAccessoryButton setBackgroundColor:[UIColor clearColor]];
[myAccessoryButton setImage:[UIImage imageNamed:#"ball"] forState:UIControlStateNormal];
[cell setAccessoryView:myAccessoryButton];
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
}
However, the event tableView accessoryButtonTappedForRowWithIndexPath is now no longer called on clicking the button.
Has anyone any ideas as to why?

I've +1'd Fry's answer, but just to pull the code in from the other site to make SO more complete: The answer is that you have to accessoryButtonTapped.... call will not be made automatically as it would for the built-in detail-disclosure, so you have to do it yourself with the target of the button.
Translating from the link code to your example above, add this line after setImage:
[myAccessoryButton addTarget:self action:#selector(accessoryButtonTapped:event:) forControlEvents:UIControlEventTouchUpInside];
Then add this later on:
- (void)accessoryButtonTapped:(id)sender event:(id)event
{
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
if (indexPath != nil) {
[self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
}
}
(copied verbatim from this link)
Note that this assumes that the button is created in the UITableViewController (in my case I'm creating one in a custom cell so the references are slightly different)
Explanation: the accessoryButtonTapped is a custom target method for our custom button. When the button is pressed ("TouchUpInside") then we find the indexPath of the cell at the point in which the press occurred, and invoke the method that the table view would ordinarily invoke.

Related

Memory Warning in UICollectionView (with image)

I am currently working on a UICollectionView with a lot of images. However, it sometimes crashes in this view with memory warning. I am using AFNetworking and UIImageView+AFNetworking category to set image through setImageWithURL: method. One issue can be caching. I am not sure if AFNetworking deals with image caching. Anyway, is there a way to optimize this code in terms of memory management? Or if I am to implement didReceiveMemoryWarning method in this view controller, what can be put in this method? I attach the code for cellForItemAtIndexPath for this collection view.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"RecipeCell" forIndexPath:indexPath];
// setting the image view for the cell using AFNetworking. Does this do caching automatically?
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:6];
if (PRODUCTION) {
[recipeImageView setImageWithURL:[[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_image"] placeholderImage:[UIImage imageNamed:#"default_recipe_picture.png"]];
} else {
[recipeImageView setImageWithURL:[NSString stringWithFormat:#"http://localhost:5000/%#", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_image"]] placeholderImage:[UIImage imageNamed:#"default_recipe_picture.png"]];
}
// configure the back of the cell. fill all the info.
UITextView *recipeNameView = (UITextView *)[cell viewWithTag:8];
recipeNameView.text = [NSString stringWithFormat:#"%#", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_name"]];
UILabel *recipeNameLabel = (UILabel *)[cell viewWithTag:2];
recipeNameLabel.text = [NSString stringWithFormat:#"%#", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"recipe_name"]];
NSDictionary *user = [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"user"];
UIButton *chefNameButton = (UIButton *)[cell viewWithTag:3];
[chefNameButton setTitle:[NSString stringWithFormat:#"%# %#", [user objectForKey:#"first_name"], [user objectForKey:#"last_name"]] forState:UIControlStateNormal];
NSMutableArray *missingIngredientsStringArray = [[NSMutableArray alloc] init];
NSArray *missingIngredients = [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"missing_ingredients"];
for (NSDictionary *missingIngredient in missingIngredients) {
[missingIngredientsStringArray addObject:[missingIngredient objectForKey:#"name"]];
}
NSString *missingIngredientsString = [missingIngredientsStringArray componentsJoinedByString:#","];
UITextView *missingIngredientsView = (UITextView *)[cell viewWithTag:4];
missingIngredientsView.text = [NSString stringWithFormat:#"%u Missing Ingredients: %#", missingIngredients.count, missingIngredientsString];
// configure the front of the cell. chef name button and missing ingredients and likes on front view
UIButton *frontNameButton = (UIButton *)[cell viewWithTag:11];
[frontNameButton setTitle:[NSString stringWithFormat:#"%# %#", [user objectForKey:#"first_name"], [user objectForKey:#"last_name"]] forState:UIControlStateNormal];
[frontNameButton sizeToFit];
frontNameButton.frame = CGRectMake(160 - [frontNameButton.titleLabel.text sizeWithFont:[UIFont boldSystemFontOfSize:13]].width - 7, frontNameButton.frame.origin.y, frontNameButton.frame.size.width, frontNameButton.frame.size.height);
UILabel *likesLabel = (UILabel *)[cell viewWithTag:9];
likesLabel.text = [NSString stringWithFormat:#"%# likes", [[self.recipes objectAtIndex:indexPath.row] objectForKey:#"likes"]];
UIButton *missingIngredientsButton = (UIButton *)[cell viewWithTag:12];
[missingIngredientsButton setBackgroundImage:[UIImage imageNamed:#"badge_green.png"] forState:UIControlStateSelected];
if (missingIngredients.count == 0) {
missingIngredientsButton.selected = YES;
[missingIngredientsButton setTitle:#"" forState:UIControlStateNormal];
} else {
missingIngredientsButton.selected = NO;
[missingIngredientsButton setTitle:[NSString stringWithFormat:#"%u", missingIngredients.count] forState:UIControlStateNormal];
}
// make back view invisible.
UIView *backView = [cell viewWithTag:1];
UIView *frontView = [cell viewWithTag:5];
frontView.alpha = 1.0;
backView.alpha = 0;
// adding flip gesture recognizers
UIView *flipView1 = [cell viewWithTag:12];
UIView *flipView2 = [cell viewWithTag:1];
UITapGestureRecognizer *flipGestureRecognizer1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(flipCell:)];
UITapGestureRecognizer *flipGestureRecognizer2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(flipCell:)];
[flipView1 addGestureRecognizer:flipGestureRecognizer1];
[flipView2 addGestureRecognizer:flipGestureRecognizer2];
return cell;
}
[Edit] I attach a screenshot of my Instruments run.
You can see that memory allocation increases as I just push segue and press back button repeatedly. Things that just keep increasing are CFData, CALayer, CABackingStore, UITableView. I doubt these are things that are created after segue, and they are not being released... Please help!
You're probably going to want some sort of image caching strategy to avoid re-downloading images. And UIImageView+AFNetworking category does cache images for you. But you may also have the responses being cached in the in-memory URL cache, which in this case is somewhat redundant.
So you might consider reducing or turning off the in-memory URL cache. I had the issue you're describing and the following reduced my memory issues quite a bit:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
AFNetworking automatically stores images in an NSCache collection, which automatically removes some or all of the images from memory on a low memory warning. AFNetworking is probably not your issue.
In fact, I don't think displaying images is your issue unless you're downloading lots of very large images and displaying them simultaneously. (If this is the case, you should try optimizing your images for display on the device so they don't need to be resized.)
One issue I see is that you are adding a gesture recognizer to the cell every time it comes into the view, but cells are reused, so when a cell comes in again you are adding unnecessary gesture recognizers to it. You could resolve this by subclassing UITableViewCell and assigning the gesture recognizers as properties. You could also resolve this by checking flipView1 and flipView2 to see if they have gesture recognizers attached before adding them. (I'm not sure if this is enough to cause a memory warning though.)
I'd recommend going to Build -> Profile and selecting the Allocations instrument. On the left, select Objective C only, and hide system calls. Then, scroll through your collection view and look at the instrument to see what's taking up all the memory.
UPDATE
Here's a screenshot of the Allocations tool:

Xcode didSelectRowAtIndexPath on tableview not called

I have been looking until 6 o'clock this morning, but I can't figure out why the didSelectRowAtIndexPath method is not being called. I am getting quite desparate on this one.
The tableview is shown properly, but I cannot succeed to enable the selection of a cell.
In the header file , I added:
#interface AlertsTable : UIViewController<UITableViewDelegate, UITableViewDataSource, CMPopTipViewDelegate>{
UITableView *TableView;
}
#property (nonatomic, retain) UITableView *TableView;
In the implementation file:
#synthesize TableView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
//Set the title
//Format Alerts table
//self.view.backgroundColor = [UIColor redColor];
CGFloat sideMargin = 10;
CGFloat topBottomMargin = 44;
CGFloat originX = sideMargin;
// compute width based on view size
CGFloat sizeWidth = (self.view.bounds.size.width - (sideMargin * 2));
CGFloat originY = topBottomMargin;
// compute height based on view size
CGFloat sizeHeight = (self.view.bounds.size.height - (topBottomMargin * 2));
//self.view.frame = CGRectMake(originX, originY, sizeWidth, sizeHeight);
self.TableView = [[UITableView alloc] initWithFrame:CGRectMake(originX, originY, sizeWidth, sizeHeight) style:UITableViewStylePlain];
//Initialize the array.
AlertsItems = [[NSMutableArray alloc] initWithObjects: #"Alert 1", #"Alert 2", #"Alert 3" , #"Alert 4", #"Alert 5", #"Alert 6", nil];
[self.TableView setDelegate:self];
[self.TableView setDataSource:self];
[self.view addSubview:TableView];
TableView.userInteractionEnabled = YES;
TableView.allowsSelection = YES;
TableView.allowsSelectionDuringEditing = YES;
NSLog(#"delegate:%# dataSource:%#", self.TableView.delegate, self.TableView.dataSource);
}
The delegate and datasource are both not nil on the check.
Note that the "Alertstable" inherits from a UIViewController, not a UITableViewController.
This is necessary due to the implementation I chose: the tableview is shown on a popupwindow shown on the screen (using another class that I took from the internet).
This is the didSelectRowAtIndexPath method:
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *alertString = [NSString stringWithFormat:#"Clicked on row #%d", [indexPath row]];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:alertString message:#"" delegate:self cancelButtonTitle:#"Done" otherButtonTitles:nil];
[alert show];
}
The methods:
[super touchesBegan:...];
[super touchesEnded:...];
[super touchesMoved:...];
are not implemented.
I added the AlertTable from another ViewController
AlertsTable *AlertTable = [[AlertsTable alloc] WithButton:sender withArray:self.PopTipViews];
[self.view addSubview:AlertTable.view];
I also implemented:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Set up the cell...
[[cell textLabel] setText:[AlertsItems objectAtIndex:indexPath.row]];
NSLog (#"%#",[AlertsItems objectAtIndex:indexPath.row]);
return cell;
}
Do you have any idea what the problem could be?
I'm not very experienced in Xcode and I'm really stuck.
Edit:
I know that it is not custom, but the link to the project can be found here (perhaps I'm overlooking something:
https://www.bilbu.be/BarConnect_v2.zip
The LoginViewController.xib is not properly linked (it is available in the en.lproj subfolder) so you may need to link first (I noticed too late and I had the file uploaded by a friend before - I cannot re-upload it myself unfortunately).
ViewController VC class calls the AlertsTable VC class. There is more in the project that I suppose you can ignore...
Note that the purpose of this project is only to serve as visual interface prototype. It is not meant to be the best optimized application (this is what other developers will do). If this doesn't work, I will use Photoshop to create the interfaces, but I guess that's not gonna be as convincing...
I successfully compiled your project. The problem is that you're going against the MVC pattern. The controller (AlertsTable) is inside of a view (CMPopTipView). I'd suggest you to rethink the hierarchy of the controllers and views. Hope this will help you.
Try changing
TableView.userInteractionEnabled = YES;
TableView.allowsSelection = YES;
TableView.allowsSelectionDuringEditing = YES;
to
self.TableView.userInteractionEnabled = YES;
self.TableView.allowsSelection = YES;
self.TableView.allowsSelectionDuringEditing = YES;
and are you allocating self.TableView? Something like...
self.TableView = [[UITableView alloc] init];//[[UITableView alloc] initWithStyle:UITableViewStylePlain];

UIBarButtonItem frame? [duplicate]

UIBarButtonItem does not extend UIView, so there is nothing like a frame property.
But is there any way I can get what is it's CGRect frame, relative to the application UIWindow?
Do you like to use private APIs? If yes,
UIView* view = thatItem.view;
return [view convertRect:view.bounds toView:nil];
Of course no one wants this when targeting the AppStore. A more unreliable method, and also uses undocumented features, but will pass Apple's test, is to loop through the subviews to look for the corresponding button item.
NSMutableArray* buttons = [[NSMutableArray alloc] init];
for (UIControl* btn in theToolbarOrNavbar.subviews)
if ([btn isKindOfClass:[UIControl class]])
[buttons addObject:btn];
UIView* view = [buttons objectAtIndex:index];
[buttons release];
return [view convertRect:view.bounds toView:nil];
The index is the index to your bar item in the array of .items, after removing all blank items. This assumes the buttons are arranged in increasing order, which may not be. A more reliable method is to sort the buttons array in increasing .origin.x value. Of course this still assumes the bar button item must inherit the UIControl class, and are direct subviews of the toolbar/nav-bar, which again may not be.
As you can see, there are a lot of uncertainty when dealing with undocumented features. However, you just want to pop up something under the finger right? The UIBarButtonItem's .action can be a selector of the form:
-(void)buttonClicked:(UIBarButtonItem*)sender event:(UIEvent*)event;
note the event argument — you can obtain the position of touch with
[[event.allTouches anyObject] locationInView:theWindow]
or the button view with
[[event.allTouches anyObject] view]
Therefore, there's no need to iterate the subviews or use undocumented features for what you want to do.
I didn't see this option posted (which in my opinion is much simpler), so here it is:
UIView *barButtonView = [barButtonItem valueForKey:#"view"];
In iOS 3.2, there's a much easier way to show an Action Sheet popover from a toolbar button. Merely do something like this:
- (IBAction)buttonClicked:(UIBarButtonItem *)sender event:(UIEvent *)event
{
UIActionSheet *popupSheet;
// Prepare your action sheet
[popupSheet showFromBarButtonItem:sender animated:YES];
}
This is the implementation I use for my WEPopover project: (https://github.com/werner77/WEPopover):
#implementation UIBarButtonItem(WEPopover)
- (CGRect)frameInView:(UIView *)v {
UIView *theView = self.customView;
if (!theView.superview && [self respondsToSelector:#selector(view)]) {
theView = [self performSelector:#selector(view)];
}
UIView *parentView = theView.superview;
NSArray *subviews = parentView.subviews;
NSUInteger indexOfView = [subviews indexOfObject:theView];
NSUInteger subviewCount = subviews.count;
if (subviewCount > 0 && indexOfView != NSNotFound) {
UIView *button = [parentView.subviews objectAtIndex:indexOfView];
return [button convertRect:button.bounds toView:v];
} else {
return CGRectZero;
}
}
#end
As long as UIBarButtonItem (and UITabBarItem) does not inherit from UIView—for historical reasons UIBarItem inherits from NSObject—this craziness continues (as of this writing, iOS 8.2 and counting ... )
The best answer in this thread is obviously #KennyTM's. Don't be silly and use the private API to find the view.
Here's a oneline Swift solution to get an origin.x sorted array (like Kenny's answer suggests):
let buttonFrames = myToolbar.subviews.filter({
$0 is UIControl
}).sorted({
$0.frame.origin.x < $1.frame.origin.x
}).map({
$0.convertRect($0.bounds, toView:nil)
})
The array is now origin.x sorted with the UIBarButtonItem frames.
(If you feel the need to read more about other people's struggles with UIBarButtonItem, I recommend Ash Furrow's blog post from 2012: Exploring UIBarButtonItem)
I was able to get Werner Altewischer's WEpopover to work by passing up the toolbar along with the
UIBarButton:
Mod is in WEPopoverController.m
- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)item toolBar:(UIToolbar *)toolBar
permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections
animated:(BOOL)animated
{
self.currentUIControl = nil;
self.currentView = nil;
self.currentBarButtonItem = item;
self.currentArrowDirections = arrowDirections;
self.currentToolBar = toolBar;
UIView *v = [self keyView];
UIButton *button = nil;
for (UIView *subview in toolBar.subviews)
{
if ([[subview class].description isEqualToString:#"UIToolbarButton"])
{
for (id target in [(UIButton *)subview allTargets])
{
if (target == item)
{
button = (UIButton *)subview;
break;
}
}
if (button != nil) break;
}
}
CGRect rect = [button.superview convertRect:button.frame toView:v];
[self presentPopoverFromRect:rect inView:v permittedArrowDirections:arrowDirections animated:animated];
}
-(CGRect) getBarItemRc :(UIBarButtonItem *)item{
UIView *view = [item valueForKey:#"view"];
return [view frame];
}
You can get it from the UINavigationBar view. The navigationBar is a UIView which has 2 or 3 custom subviews for the parts on the bar.
If you know that the UIBarButtonItem is currently shown in the navbar on the right, you can get its frame from navbar's subviews array.
First you need the navigationBar which you can get from the navigationController which you can get from the UIViewController. Then find the right most subview:
UINavigationBar* navbar = curViewController.navigationController.navigationBar;
UIView* rightView = nil;
for (UIView* v in navbar.subviews) {
if (rightView==nil) {
rightView = v;
} else if (v.frame.origin.x > rightView.frame.origin.x) {
rightView = v; // this view is further right
}
}
// at this point rightView contains the right most subview of the navbar
I haven't compiled this code so YMMV.
This is not the best solution and from some point of view it's not right solution and we can't do like follow because we access to object inside UIBarBattonItem implicitly, but you can try to do something like:
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
[button setImage:[UIImage imageNamed:#"Menu_Icon"] forState:UIControlStateNormal];
[button addTarget:self action:#selector(didPressitem) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:button];
self.navigationItem.rightBarButtonItem = item;
CGPoint point = [self.view convertPoint:button.center fromView:(UIView *)self.navigationItem.rightBarButtonItem];
//this is like view because we use UIButton like "base" obj for
//UIBarButtonItem, but u should note that UIBarButtonItem base class
//is NSObject class not UIView class, for hiding warning we implicity
//cast UIBarButtonItem created with UIButton to UIView
NSLog(#"point %#", NSStringFromCGPoint(point));
as result i got next:
point {289, 22}
Before implement this code, be sure to call [window makeKeyAndVisible] in your Applition delegate application:didFinishLaunchingWithOptions: method!
- (void) someMethod
{
CGRect rect = [barButtonItem convertRect:barButtonItem.customview.bounds toView:[self keyView]];
}
- (UIView *)keyView {
UIWindow *w = [[UIApplication sharedApplication] keyWindow];
if (w.subviews.count > 0) {
return [w.subviews objectAtIndex:0];
} else {
return w;
}
}
I handled it as follows:
- (IBAction)buttonClicked:(UIBarButtonItem *)sender event:(UIEvent *)event
{
UIView* view = [sender valueForKey:#"view"]; //use KVO to return the view
CGRect rect = [view convertRect:view.bounds toView:self.view];
//do stuff with the rect
}

iOS - Three buttons in Nav. Bar - not Tab Bar

I am building an app that sort of calls for three buttons in the top nav. bar. I don't want to do a tab bar, as there will be only one item in the tab.
In the code below, the left button (Edit) works fine. The right button - the link to cards also works fine. However the stupid Add button gives me an error that it cannot find the selector (insertNewObject).
If I take out my custom code and use the code that says "Works in a pinch", well.. it works.
I'd appreciate either knowing what I'm doing wrong, or else, another way of handling the problem at hand - linking to another part of the app.
TIA.
/*
// THIS WORKS IN A PINCH
// Set up the edit and add buttons.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject)];
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];
// END OF WHAT WORKS
*/
// create a toolbar to have two buttons in the right
UIToolbar* tools = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 105, 44.01)];
self.navigationItem.leftBarButtonItem = self.editButtonItem;
// create the array to hold the buttons, which then gets added to the toolbar
NSMutableArray* buttons = [[NSMutableArray alloc] initWithCapacity:3];
UIBarButtonItem* addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject)];
addButton.style = UIBarButtonItemStyleBordered;
[buttons addObject:addButton];
[addButton release];
// create a standard "add" button
// create a flip button
UIBarButtonItem* flipButton = [[UIBarButtonItem alloc]
initWithTitle:#"Cards" style:UIBarButtonItemStyleBordered
target:self action:#selector(flipToFront)];
[buttons addObject:flipButton];
[flipButton release];
// stick the buttons in the toolbar
[tools setItems:buttons animated:NO];
[buttons release];
// and put the toolbar in the nav bar
UIBarButtonItem* rightButtonBar = [[UIBarButtonItem alloc] initWithCustomView:tools];
self.navigationItem.rightBarButtonItem = rightButtonBar;
[rightButtonBar release];
You may be missing a colon after the selector name. I think just putting in #selector(newObject) is a reference to a method with no parameters. #selector(newObject:) may work instead.

Clickable url link in NSTextFieldCell inside NSTableView?

I have a NSAttributedString that I'm using in a NSTextFieldCell. It makes several clickable url links and puts a big NSAttributedString inside the NSTextFieldCell. Whenever I am viewing the NSTextFieldCell normally and it's highlighted, I cannot click on the links.
If I set the TableView so I can edit each column or row, when I click twice, go into Edit mode and view the NSTextFieldCell contents, my links show up and are clickable. When I click away from the row, I can no longer see clickable links.
I have to be in "edit" mode to see the links or click on them.
I feel like there's some setting I'm just missing.
I don't think the tech note answers the question, which was how to put a link in an NSTableView cell. The best way I've found to do this is to use a button cell for the table cell. This assumes that only links will be in a particular column of the table.
In Interface Builder, drag an NSButton cell onto the table column where you want the links.
In your table view delegate, implement tableView:dataCellForTableColumn:row: as follows:
- (NSCell *) tableView: (NSTableView *) tableView
dataCellForTableColumn: (NSTableColumn *) column
row: (NSInteger) row
{
NSButtonCell *buttonCell = nil;
NSAttributedString *title = nil;
NSString *link = nil;
NSDictionary *attributes = nil;
// Cell for entire row -- we don't do headers
if (column == nil)
return(nil);
// Columns other than link do the normal thing
if (![self isLinkColumn:column]) // Implement this as appropriate for your table
return([column dataCellForRow:row]);
// If no link, no button, just a blank text field
if ((link = [self linkForRow:row]) != nil) // Implement this as appropriate for your table
return([[[NSTextFieldCell alloc] initTextCell:#""] autorelease]);
// It's a link. Create the title
attributes = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName,
[NSNumber numberWithInt:NSUnderlineStyleSingle], NSUnderlineStyleAttributeName,
[NSColor blueColor], NSForegroundColorAttributeName,
[NSURL URLWithString:link], NSLinkAttributeName, nil];
title = [[NSAttributedString alloc] initWithString:link attributes:attributes];
[attributes release];
// Create a button cell
buttonCell = [[[NSButtonCell alloc] init] autorelease];
[buttonCell setBezelStyle:NSRoundedBezelStyle];
[buttonCell setButtonType:NSMomentaryPushInButton];
[buttonCell setBordered:NO]; // Don't want a bordered button
[buttonCell setAttributedTitle:title];
[title release];
return(buttonCell);
}
Set the target/action for the table to your delegate and check for clicks on the link column:
- (void) clickTable: (NSTableView *) sender
{
NSTableColumn *column = [[sender tableColumns] objectAtIndex:[sender clickedColumn]];
NSInteger row = [sender clickedRow];
NSString *link = nil;
if ([self isLinkColumn:column] && (link = [self linkForRow:row]) != nil)
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:link]];
}
Now the link looks like a link, but a click on it is actually a button press, which you detect in the action method and dispatch using NSWorkspace.
Have you seen this technical note from Apple regarding hyperlinks?
Embedding Hyperlinks in NSTextField and NSTextView

Resources